1<?php
2/* Copyright (C) 2020  Laurent Destailleur <eldy@users.sourceforge.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18/**
19 * \file        class/recruitmentcandidature.class.php
20 * \ingroup     recruitment
21 * \brief       This file is a CRUD class file for RecruitmentCandidature (Create/Read/Update/Delete)
22 */
23
24// Put here all includes required by your class file
25require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
26//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
27//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
28
29/**
30 * Class for RecruitmentCandidature
31 */
32class RecruitmentCandidature extends CommonObject
33{
34	/**
35	 * @var string ID of module.
36	 */
37	public $module = 'recruitment';
38
39	/**
40	 * @var string ID to identify managed object.
41	 */
42	public $element = 'recruitmentcandidature';
43
44	/**
45	 * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management.
46	 */
47	public $table_element = 'recruitment_recruitmentcandidature';
48
49	/**
50	 * @var int  Does this object support multicompany module ?
51	 * 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
52	 */
53	public $ismultientitymanaged = 0;
54
55	/**
56	 * @var int  Does object support extrafields ? 0=No, 1=Yes
57	 */
58	public $isextrafieldmanaged = 1;
59
60	/**
61	 * @var string String with name of icon for recruitmentcandidature. Must be the part after the 'object_' into object_recruitmentcandidature.png
62	 */
63	public $picto = 'recruitmentcandidature';
64
65
66	const STATUS_DRAFT = 0;
67	const STATUS_VALIDATED = 1;
68	//const STATUS_INTERVIEW_SCHEDULED = 2;
69	const STATUS_CONTRACT_PROPOSED = 3;
70	const STATUS_CONTRACT_SIGNED = 5;
71	const STATUS_CONTRACT_REFUSED = 6;
72	const STATUS_REFUSED = 8;
73	const STATUS_CANCELED = 9;
74
75
76	/**
77	 *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
78	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
79	 *  'label' the translation key.
80	 *  'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
81	 *  'position' is the sort order of field.
82	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
83	 *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
84	 *  'noteditable' says if field is not editable (1 or 0)
85	 *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
86	 *  'index' if we want an index in database.
87	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
88	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
89	 *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
90	 *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200'
91	 *  'help' is a string visible as a tooltip on field
92	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
93	 *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
94	 *  'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
95	 *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
96	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
97	 *
98	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
99	 */
100
101	// BEGIN MODULEBUILDER PROPERTIES
102	/**
103	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
104	 */
105	public $fields = array(
106		'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>'1', 'position'=>1, 'notnull'=>1, 'visible'=>0, 'noteditable'=>'1', 'index'=>1, 'comment'=>"Id"),
107		'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'position'=>5, 'notnull'=>1, 'default'=>'1', 'index'=>1),
108		'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>'1', 'position'=>10, 'notnull'=>1, 'visible'=>4, 'noteditable'=>'1', 'default'=>'(PROV)', 'index'=>1, 'searchall'=>1, 'showoncombobox'=>'1', 'comment'=>"Reference of candidature"),
109		'fk_recruitmentjobposition' => array('type'=>'integer:RecruitmentJobPosition:recruitment/class/recruitmentjobposition.class.php', 'label'=>'Job', 'enabled'=>'1', 'position'=>15, 'notnull'=>0, 'visible'=>1, 'index'=>1),
110		'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>'1', 'position'=>61, 'notnull'=>0, 'visible'=>0,),
111		'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>'1', 'position'=>62, 'notnull'=>0, 'visible'=>0,),
112		'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>'1', 'position'=>500, 'notnull'=>1, 'visible'=>-2,),
113		'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>'1', 'position'=>501, 'notnull'=>0, 'visible'=>-2,),
114		'fk_user_creat' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>'1', 'position'=>510, 'notnull'=>1, 'visible'=>-2, 'foreignkey'=>'user.rowid',),
115		'fk_user_modif' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>'1', 'position'=>511, 'notnull'=>-1, 'visible'=>-2,),
116		'lastname' => array('type'=>'varchar(128)', 'label'=>'Lastname', 'enabled'=>'1', 'position'=>20, 'notnull'=>0, 'visible'=>1,),
117		'firstname' => array('type'=>'varchar(128)', 'label'=>'Firstname', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>1,),
118		'email' => array('type'=>'varchar(255)', 'label'=>'EMail', 'enabled'=>'1', 'position'=>30, 'notnull'=>1, 'visible'=>1, 'picto'=>'email'),
119		'phone' => array('type'=>'varchar(64)', 'label'=>'Phone', 'enabled'=>'1', 'position'=>31, 'notnull'=>0, 'visible'=>1,),
120		'date_birth' => array('type'=>'date', 'label'=>'DateOfBirth', 'enabled'=>'1', 'position'=>70, 'visible'=>-1,),
121		'email_msgid' => array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'visible'=>-2, 'enabled'=>1, 'position'=>540, 'notnull'=>-1, 'help'=>'EmailMsgIDDesc'),
122		//'fk_recruitment_origin' => array('type'=>'integer:CRecruitmentOrigin:recruitment/class/crecruitmentorigin.class.php', 'label'=>'Origin', 'enabled'=>'1', 'position'=>45, 'visible'=>1, 'index'=>1),
123		'remuneration_requested' => array('type'=>'integer', 'label'=>'RequestedRemuneration', 'enabled'=>'1', 'position'=>80, 'notnull'=>0, 'visible'=>-1,),
124		'remuneration_proposed' => array('type'=>'integer', 'label'=>'ProposedRemuneration', 'enabled'=>'1', 'position'=>81, 'notnull'=>0, 'visible'=>-1,),
125		'description' => array('type'=>'html', 'label'=>'Description', 'enabled'=>'1', 'position'=>500, 'notnull'=>0, 'visible'=>3,),
126		'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>'1', 'position'=>1000, 'notnull'=>-1, 'visible'=>-2,),
127		'model_pdf' => array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>'1', 'position'=>1010, 'notnull'=>-1, 'visible'=>0,),
128		'status' => array('type'=>'smallint', 'label'=>'Status', 'enabled'=>'1', 'position'=>1000, 'notnull'=>1, 'visible'=>1, 'index'=>1, 'default'=>0, 'arrayofkeyval'=>array('0'=>'Draft', '1'=>'Received', '3'=>'ContractProposed', '5'=>'ContractSigned', '8'=>'Refused', '9'=>'Canceled')),
129	);
130	public $rowid;
131	public $entity;
132	public $ref;
133	public $fk_recruitmentjobposition;
134	public $description;
135	public $note_public;
136	public $note_private;
137	public $date_creation;
138	public $tms;
139	public $fk_user_creat;
140	public $fk_user_modif;
141	public $lastname;
142	public $firstname;
143	public $email;
144	public $phone;
145	public $date_birth;
146	public $email_msgid;
147	public $remuneration_requested;
148	public $remuneration_proposed;
149	public $fk_recruitment_origin;
150	public $import_key;
151	public $model_pdf;
152	public $status;
153	// END MODULEBUILDER PROPERTIES
154
155
156	/**
157	 * Constructor
158	 *
159	 * @param DoliDb $db Database handler
160	 */
161	public function __construct(DoliDB $db)
162	{
163		global $conf, $langs;
164
165		$this->db = $db;
166
167		if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) $this->fields['rowid']['visible'] = 0;
168		if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) $this->fields['entity']['enabled'] = 0;
169
170		// Example to show how to set values of fields definition dynamically
171		/*if ($user->rights->recruitment->recruitmentcandidature->read) {
172			$this->fields['myfield']['visible'] = 1;
173			$this->fields['myfield']['noteditable'] = 0;
174		}*/
175
176		// Unset fields that are disabled
177		foreach ($this->fields as $key => $val)
178		{
179			if (isset($val['enabled']) && empty($val['enabled']))
180			{
181				unset($this->fields[$key]);
182			}
183		}
184
185		// Translate some data of arrayofkeyval
186		if (is_object($langs))
187		{
188			foreach ($this->fields as $key => $val)
189			{
190				if (!empty($val['arrayofkeyval']) && is_array($val['arrayofkeyval']))
191				{
192					foreach ($val['arrayofkeyval'] as $key2 => $val2)
193					{
194						$this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
195					}
196				}
197			}
198		}
199	}
200
201	/**
202	 * Create object into database
203	 *
204	 * @param  User $user      User that creates
205	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
206	 * @return int             <0 if KO, Id of created object if OK
207	 */
208	public function create(User $user, $notrigger = false)
209	{
210		return $this->createCommon($user, $notrigger);
211	}
212
213	/**
214	 * Clone an object into another one
215	 *
216	 * @param  	User 	$user      	User that creates
217	 * @param  	int 	$fromid     Id of object to clone
218	 * @return 	mixed 				New object created, <0 if KO
219	 */
220	public function createFromClone(User $user, $fromid)
221	{
222		global $langs, $extrafields;
223		$error = 0;
224
225		dol_syslog(__METHOD__, LOG_DEBUG);
226
227		$object = new self($this->db);
228
229		$this->db->begin();
230
231		// Load source object
232		$result = $object->fetchCommon($fromid);
233		if ($result > 0 && !empty($object->table_element_line)) $object->fetchLines();
234
235		// get lines so they will be clone
236		//foreach($this->lines as $line)
237		//	$line->fetch_optionals();
238
239		// Reset some properties
240		unset($object->id);
241		unset($object->fk_user_creat);
242		unset($object->import_key);
243
244		// Clear fields
245		if (property_exists($object, 'ref')) $object->ref = empty($this->fields['ref']['default']) ? "Copy_Of_".$object->ref : $this->fields['ref']['default'];
246		if (property_exists($object, 'label')) $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
247		if (property_exists($object, 'status')) { $object->status = self::STATUS_DRAFT; }
248		if (property_exists($object, 'date_creation')) { $object->date_creation = dol_now(); }
249		if (property_exists($object, 'date_modification')) { $object->date_modification = null; }
250
251		// ...
252		// Clear extrafields that are unique
253		if (is_array($object->array_options) && count($object->array_options) > 0)
254		{
255			$extrafields->fetch_name_optionals_label($this->table_element);
256			foreach ($object->array_options as $key => $option)
257			{
258				$shortkey = preg_replace('/options_/', '', $key);
259				if (!empty($extrafields->attributes[$this->table_element]['unique'][$shortkey]))
260				{
261					//var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
262					unset($object->array_options[$key]);
263				}
264			}
265		}
266
267		// Create clone
268		$object->context['createfromclone'] = 'createfromclone';
269		$result = $object->createCommon($user);
270		if ($result < 0) {
271			$error++;
272			$this->error = $object->error;
273			$this->errors = $object->errors;
274		}
275
276		if (!$error)
277		{
278			// copy internal contacts
279			if ($this->copy_linked_contact($object, 'internal') < 0)
280			{
281				$error++;
282			}
283		}
284
285		if (!$error)
286		{
287			// copy external contacts if same company
288			if (property_exists($this, 'socid') && $this->socid == $object->socid)
289			{
290				if ($this->copy_linked_contact($object, 'external') < 0)
291					$error++;
292			}
293		}
294
295		unset($object->context['createfromclone']);
296
297		// End
298		if (!$error) {
299			$this->db->commit();
300			return $object;
301		} else {
302			$this->db->rollback();
303			return -1;
304		}
305	}
306
307	/**
308	 * Load object in memory from the database
309	 *
310	 * @param 	int    	$id   			Id object
311	 * @param	string 	$ref  			Ref
312	 * @param	string	$email_msgid	Email msgid
313	 * @return	int         			<0 if KO, 0 if not found, >0 if OK
314	 */
315	public function fetch($id, $ref = null, $email_msgid = '')
316	{
317		$morewhere = '';
318		if ($email_msgid) $morewhere = " AND email_msgid = '".$this->db->escape($email_msgid)."'";
319		$result = $this->fetchCommon($id, $ref, $morewhere);
320		if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
321		return $result;
322	}
323
324	/**
325	 * Load object lines in memory from the database
326	 *
327	 * @return int         <0 if KO, 0 if not found, >0 if OK
328	 */
329	public function fetchLines()
330	{
331		$this->lines = array();
332
333		$result = $this->fetchLinesCommon();
334		return $result;
335	}
336
337
338	/**
339	 * Load list of objects in memory from the database.
340	 *
341	 * @param  string      $sortorder    Sort Order
342	 * @param  string      $sortfield    Sort field
343	 * @param  int         $limit        limit
344	 * @param  int         $offset       Offset
345	 * @param  array       $filter       Filter array. Example array('field'=>'valueforlike', 'customurl'=>...)
346	 * @param  string      $filtermode   Filter mode (AND or OR)
347	 * @return array|int                 int <0 if KO, array of pages if OK
348	 */
349	public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
350	{
351		global $conf;
352
353		dol_syslog(__METHOD__, LOG_DEBUG);
354
355		$records = array();
356
357		$sql = 'SELECT ';
358		$sql .= $this->getFieldList();
359		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
360		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')';
361		else $sql .= ' WHERE 1 = 1';
362		// Manage filter
363		$sqlwhere = array();
364		if (count($filter) > 0) {
365			foreach ($filter as $key => $value) {
366				if ($key == 't.rowid') {
367					$sqlwhere[] = $key.'='.$value;
368				} elseif (in_array($this->fields[$key]['type'], array('date', 'datetime', 'timestamp'))) {
369					$sqlwhere[] = $key.' = \''.$this->db->idate($value).'\'';
370				} elseif ($key == 'customsql') {
371					$sqlwhere[] = $value;
372				} elseif (strpos($value, '%') === false) {
373					$sqlwhere[] = $key.' IN ('.$this->db->sanitize($this->db->escape($value)).')';
374				} else {
375					$sqlwhere[] = $key.' LIKE \'%'.$this->db->escape($value).'%\'';
376				}
377			}
378		}
379		if (count($sqlwhere) > 0) {
380			$sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')';
381		}
382
383		if (!empty($sortfield)) {
384			$sql .= $this->db->order($sortfield, $sortorder);
385		}
386		if (!empty($limit)) {
387			$sql .= ' '.$this->db->plimit($limit, $offset);
388		}
389
390		$resql = $this->db->query($sql);
391		if ($resql) {
392			$num = $this->db->num_rows($resql);
393			$i = 0;
394			while ($i < ($limit ? min($limit, $num) : $num))
395			{
396				$obj = $this->db->fetch_object($resql);
397
398				$record = new self($this->db);
399				$record->setVarsFromFetchObj($obj);
400
401				$records[$record->id] = $record;
402
403				$i++;
404			}
405			$this->db->free($resql);
406
407			return $records;
408		} else {
409			$this->errors[] = 'Error '.$this->db->lasterror();
410			dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
411
412			return -1;
413		}
414	}
415
416	/**
417	 * Update object into database
418	 *
419	 * @param  User $user      User that modifies
420	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
421	 * @return int             <0 if KO, >0 if OK
422	 */
423	public function update(User $user, $notrigger = false)
424	{
425		return $this->updateCommon($user, $notrigger);
426	}
427
428	/**
429	 * Delete object in database
430	 *
431	 * @param User $user       User that deletes
432	 * @param bool $notrigger  false=launch triggers after, true=disable triggers
433	 * @return int             <0 if KO, >0 if OK
434	 */
435	public function delete(User $user, $notrigger = false)
436	{
437		return $this->deleteCommon($user, $notrigger);
438		//return $this->deleteCommon($user, $notrigger, 1);
439	}
440
441	/**
442	 *  Delete a line of object in database
443	 *
444	 *	@param  User	$user       User that delete
445	 *  @param	int		$idline		Id of line to delete
446	 *  @param 	bool 	$notrigger  false=launch triggers after, true=disable triggers
447	 *  @return int         		>0 if OK, <0 if KO
448	 */
449	public function deleteLine(User $user, $idline, $notrigger = false)
450	{
451		if ($this->status < 0)
452		{
453			$this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
454			return -2;
455		}
456
457		return $this->deleteLineCommon($user, $idline, $notrigger);
458	}
459
460
461	/**
462	 *	Validate object
463	 *
464	 *	@param		User	$user     		User making status change
465	 *  @param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
466	 *	@return  	int						<=0 if OK, 0=Nothing done, >0 if KO
467	 */
468	public function validate($user, $notrigger = 0)
469	{
470		global $conf, $langs;
471
472		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
473
474		$error = 0;
475
476		// Protection
477		if ($this->status == self::STATUS_VALIDATED)
478		{
479			dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
480			return 0;
481		}
482
483		/*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->recruitmentcandidature->write))
484		 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->recruitmentcandidature->recruitmentcandidature_advance->validate))))
485		 {
486		 $this->error='NotEnoughPermissions';
487		 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
488		 return -1;
489		 }*/
490
491		$now = dol_now();
492
493		$this->db->begin();
494
495		// Define new ref
496		if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
497		{
498			$num = $this->getNextNumRef();
499		} else {
500			$num = $this->ref;
501		}
502		$this->newref = $num;
503
504		if (!empty($num)) {
505			// Validate
506			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
507			$sql .= " SET ref = '".$this->db->escape($num)."',";
508			$sql .= " status = ".self::STATUS_VALIDATED;
509			if (!empty($this->fields['date_validation'])) $sql .= ", date_validation = '".$this->db->idate($now)."',";
510			if (!empty($this->fields['fk_user_valid'])) $sql .= ", fk_user_valid = ".$user->id;
511			$sql .= " WHERE rowid = ".$this->id;
512
513			dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
514			$resql = $this->db->query($sql);
515			if (!$resql)
516			{
517				dol_print_error($this->db);
518				$this->error = $this->db->lasterror();
519				$error++;
520			}
521
522			if (!$error && !$notrigger)
523			{
524				// Call trigger
525				$result = $this->call_trigger('RECRUITMENTCANDIDATURE_VALIDATE', $user);
526				if ($result < 0) $error++;
527				// End call triggers
528			}
529		}
530
531		if (!$error)
532		{
533			$this->oldref = $this->ref;
534
535			// Rename directory if dir was a temporary ref
536			if (preg_match('/^[\(]?PROV/i', $this->ref))
537			{
538				// Now we rename also files into index
539				$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'recruitmentcandidature/".$this->db->escape($this->newref)."'";
540				$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'recruitmentcandidature/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
541				$resql = $this->db->query($sql);
542				if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
543
544				// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
545				$oldref = dol_sanitizeFileName($this->ref);
546				$newref = dol_sanitizeFileName($num);
547				$dirsource = $conf->recruitment->dir_output.'/recruitmentcandidature/'.$oldref;
548				$dirdest = $conf->recruitment->dir_output.'/recruitmentcandidature/'.$newref;
549				if (!$error && file_exists($dirsource))
550				{
551					dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
552
553					if (@rename($dirsource, $dirdest))
554					{
555						dol_syslog("Rename ok");
556						// Rename docs starting with $oldref with $newref
557						$listoffiles = dol_dir_list($conf->recruitment->dir_output.'/recruitmentcandidature/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
558						foreach ($listoffiles as $fileentry)
559						{
560							$dirsource = $fileentry['name'];
561							$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
562							$dirsource = $fileentry['path'].'/'.$dirsource;
563							$dirdest = $fileentry['path'].'/'.$dirdest;
564							@rename($dirsource, $dirdest);
565						}
566					}
567				}
568			}
569		}
570
571		// Set new ref and current status
572		if (!$error)
573		{
574			$this->ref = $num;
575			$this->status = self::STATUS_VALIDATED;
576		}
577
578		if (!$error)
579		{
580			$this->db->commit();
581			return 1;
582		} else {
583			$this->db->rollback();
584			return -1;
585		}
586	}
587
588
589	/**
590	 *	Set draft status
591	 *
592	 *	@param	User	$user			Object user that modify
593	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
594	 *	@return	int						<0 if KO, >0 if OK
595	 */
596	public function setDraft($user, $notrigger = 0)
597	{
598		// Protection
599		if ($this->status <= self::STATUS_DRAFT)
600		{
601			return 0;
602		}
603
604		/*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->write))
605		 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->recruitment_advance->validate))))
606		 {
607		 $this->error='Permission denied';
608		 return -1;
609		 }*/
610
611		return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'RECRUITMENTCANDIDATURE_UNVALIDATE');
612	}
613
614	/**
615	 *	Set cancel status
616	 *
617	 *	@param	User	$user			Object user that modify
618	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
619	 *	@return	int						<0 if KO, 0=Nothing done, >0 if OK
620	 */
621	public function cancel($user, $notrigger = 0)
622	{
623		// Protection
624		if ($this->status != self::STATUS_VALIDATED)
625		{
626			return 0;
627		}
628
629		/*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->write))
630		 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->recruitment_advance->validate))))
631		 {
632		 $this->error='Permission denied';
633		 return -1;
634		 }*/
635
636		return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'RECRUITMENTCANDIDATURE_CLOSE');
637	}
638
639	/**
640	 *	Set back to validated status
641	 *
642	 *	@param	User	$user			Object user that modify
643	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
644	 *	@return	int						<0 if KO, 0=Nothing done, >0 if OK
645	 */
646	public function reopen($user, $notrigger = 0)
647	{
648		// Protection
649		if ($this->status != self::STATUS_REFUSED && $this->status != self::STATUS_CANCELED && $this->status != self::STATUS_CONTRACT_REFUSED)
650		{
651			return 0;
652		}
653
654		/*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->write))
655		 || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->recruitment->recruitment_advance->validate))))
656		 {
657		 $this->error='Permission denied';
658		 return -1;
659		 }*/
660
661		return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'RECRUITMENTCANDIDATURE_REOPEN');
662	}
663
664	/**
665	 *  Return a link to the object card (with optionaly the picto)
666	 *
667	 *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
668	 *  @param  string  $option                     On what the link point to ('nolink', ...)
669	 *  @param  int     $notooltip                  1=Disable tooltip
670	 *  @param  string  $morecss                    Add more css on link
671	 *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
672	 *  @return	string                              String with URL
673	 */
674	public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
675	{
676		global $conf, $langs, $hookmanager;
677
678		if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
679
680		$result = '';
681
682		$label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("RecruitmentCandidature").'</u>';
683		if (isset($this->status)) {
684			$label .= ' '.$this->getLibStatut(5);
685		}
686		$label .= '<br>';
687		$label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
688		$label .= '<br><b>'.$langs->trans('Email').':</b> '.$this->email;
689		$label .= '<br><b>'.$langs->trans('Name').':</b> '.$this->getFullName($langs);
690
691		$url = dol_buildpath('/recruitment/recruitmentcandidature_card.php', 1).'?id='.$this->id;
692
693		if ($option != 'nolink')
694		{
695			// Add param to save lastsearch_values or not
696			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
697			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
698			if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
699		}
700
701		$linkclose = '';
702		if (empty($notooltip))
703		{
704			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
705			{
706				$label = $langs->trans("ShowRecruitmentCandidature");
707				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
708			}
709			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
710			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
711		} else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
712
713		$linkstart = '<a href="'.$url.'"';
714		$linkstart .= $linkclose.'>';
715		$linkend = '</a>';
716
717		$result .= $linkstart;
718
719		if (empty($this->showphoto_on_popup)) {
720			if ($withpicto) $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
721		} else {
722			if ($withpicto) {
723				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
724
725				list($class, $module) = explode('@', $this->picto);
726				$upload_dir = $conf->$module->multidir_output[$conf->entity]."/$class/".dol_sanitizeFileName($this->ref);
727				$filearray = dol_dir_list($upload_dir, "files");
728				$filename = $filearray[0]['name'];
729				if (!empty($filename)) {
730					$pospoint = strpos($filearray[0]['name'], '.');
731
732					$pathtophoto = $class.'/'.$this->ref.'/thumbs/'.substr($filename, 0, $pospoint).'_mini'.substr($filename, $pospoint);
733					if (empty($conf->global->{strtoupper($module.'_'.$class).'_FORMATLISTPHOTOSASUSERS'})) {
734						$result .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref"><img class="photo'.$module.'" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div></div>';
735					} else {
736						$result .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photouserphoto userphoto" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div>';
737					}
738
739					$result .= '</div>';
740				} else {
741					$result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
742				}
743			}
744		}
745
746		if ($withpicto != 2) $result .= $this->ref;
747
748		$result .= $linkend;
749		//if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
750
751		global $action, $hookmanager;
752		$hookmanager->initHooks(array('recruitmentcandidaturedao'));
753		$parameters = array('id'=>$this->id, 'getnomurl'=>$result);
754		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
755		if ($reshook > 0) $result = $hookmanager->resPrint;
756		else $result .= $hookmanager->resPrint;
757
758		return $result;
759	}
760
761	/**
762	 *  Return label of the status
763	 *
764	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
765	 *  @return	string 			       Label of status
766	 */
767	public function getLibStatut($mode = 0)
768	{
769		return $this->LibStatut($this->status, $mode);
770	}
771
772	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
773	/**
774	 *  Return the status
775	 *
776	 *  @param	int		$status        Id status
777	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
778	 *  @return string 			       Label of status
779	 */
780	public function LibStatut($status, $mode = 0)
781	{
782		// phpcs:enable
783		if (empty($this->labelStatus) || empty($this->labelStatusShort))
784		{
785			global $langs;
786			//$langs->load("recruitment@recruitment");
787			$this->labelStatus[self::STATUS_DRAFT] = $langs->trans('Draft');
788			$this->labelStatus[self::STATUS_VALIDATED] = $langs->trans('Received').' ('.$langs->trans("InterviewToDo").')';
789			$this->labelStatus[self::STATUS_CONTRACT_PROPOSED] = $langs->trans('ContractProposed');
790			$this->labelStatus[self::STATUS_CONTRACT_SIGNED] = $langs->trans('ContractSigned');
791			$this->labelStatus[self::STATUS_CONTRACT_REFUSED] = $langs->trans('ContractRefused');
792			$this->labelStatus[self::STATUS_REFUSED] = $langs->trans('Refused');
793			$this->labelStatus[self::STATUS_CANCELED] = $langs->trans('Canceled');
794			$this->labelStatusShort[self::STATUS_DRAFT] = $langs->trans('Draft');
795			$this->labelStatusShort[self::STATUS_VALIDATED] = $langs->trans('Received');
796			$this->labelStatusShort[self::STATUS_CONTRACT_PROPOSED] = $langs->trans('ContractProposed');
797			$this->labelStatusShort[self::STATUS_CONTRACT_SIGNED] = $langs->trans('ContractSigned');
798			$this->labelStatusShort[self::STATUS_CONTRACT_REFUSED] = $langs->trans('ContractRefused');
799			$this->labelStatusShort[self::STATUS_REFUSED] = $langs->trans('Refused');
800			$this->labelStatusShort[self::STATUS_CANCELED] = $langs->trans('Canceled');
801		}
802
803		$statusType = 'status'.$status;
804		//if ($status == self::STATUS_VALIDATED) $statusType = 'status1';
805		if ($status == self::STATUS_CANCELED) $statusType = 'status6';
806
807		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
808	}
809
810	/**
811	 *	Load the info information in the object
812	 *
813	 *	@param  int		$id       Id of object
814	 *	@return	void
815	 */
816	public function info($id)
817	{
818		$sql = 'SELECT rowid, date_creation as datec, tms as datem,';
819		$sql .= ' fk_user_creat, fk_user_modif';
820		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
821		$sql .= ' WHERE t.rowid = '.$id;
822		$result = $this->db->query($sql);
823		if ($result)
824		{
825			if ($this->db->num_rows($result))
826			{
827				$obj = $this->db->fetch_object($result);
828				$this->id = $obj->rowid;
829				if ($obj->fk_user_author)
830				{
831					$cuser = new User($this->db);
832					$cuser->fetch($obj->fk_user_author);
833					$this->user_creation = $cuser;
834				}
835
836				if ($obj->fk_user_valid)
837				{
838					$vuser = new User($this->db);
839					$vuser->fetch($obj->fk_user_valid);
840					$this->user_validation = $vuser;
841				}
842
843				if ($obj->fk_user_cloture)
844				{
845					$cluser = new User($this->db);
846					$cluser->fetch($obj->fk_user_cloture);
847					$this->user_cloture = $cluser;
848				}
849
850				$this->date_creation     = $this->db->jdate($obj->datec);
851				$this->date_modification = $this->db->jdate($obj->datem);
852				$this->date_validation   = $this->db->jdate($obj->datev);
853			}
854
855			$this->db->free($result);
856		} else {
857			dol_print_error($this->db);
858		}
859	}
860
861	/**
862	 * Initialise object with example values
863	 * Id must be 0 if object instance is a specimen
864	 *
865	 * @return void
866	 */
867	public function initAsSpecimen()
868	{
869		$this->initAsSpecimenCommon();
870	}
871
872	/**
873	 * 	Create an array of lines
874	 *
875	 * 	@return array|int		array of lines if OK, <0 if KO
876	 */
877	public function getLinesArray()
878	{
879		$this->lines = array();
880
881		$objectline = new RecruitmentCandidatureLine($this->db);
882		$result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_recruitmentcandidature = '.$this->id));
883
884		if (is_numeric($result))
885		{
886			$this->error = $this->error;
887			$this->errors = $this->errors;
888			return $result;
889		} else {
890			$this->lines = $result;
891			return $this->lines;
892		}
893	}
894
895	/**
896	 *  Returns the reference to the following non used object depending on the active numbering module.
897	 *
898	 *  @return string      		Object free reference
899	 */
900	public function getNextNumRef()
901	{
902		global $langs, $conf;
903		$langs->load("recruitment@recruitment");
904
905		if (empty($conf->global->RECRUITMENT_RECRUITMENTCANDIDATURE_ADDON)) {
906			$conf->global->RECRUITMENT_RECRUITMENTCANDIDATURE_ADDON = 'mod_recruitmentcandidature_standard';
907		}
908
909		if (!empty($conf->global->RECRUITMENT_RECRUITMENTCANDIDATURE_ADDON))
910		{
911			$mybool = false;
912
913			$file = $conf->global->RECRUITMENT_RECRUITMENTCANDIDATURE_ADDON.".php";
914			$classname = $conf->global->RECRUITMENT_RECRUITMENTCANDIDATURE_ADDON;
915
916			// Include file with class
917			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
918			foreach ($dirmodels as $reldir)
919			{
920				$dir = dol_buildpath($reldir."core/modules/recruitment/");
921
922				// Load file with numbering class (if found)
923				$mybool |= @include_once $dir.$file;
924			}
925
926			if ($mybool === false)
927			{
928				dol_print_error('', "Failed to include file ".$file);
929				return '';
930			}
931
932			if (class_exists($classname)) {
933				$obj = new $classname();
934				$numref = $obj->getNextValue($this);
935
936				if ($numref != '' && $numref != '-1')
937				{
938					return $numref;
939				} else {
940					$this->error = $obj->error;
941					//dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
942					return "";
943				}
944			} else {
945				print $langs->trans("Error")." ".$langs->trans("ClassNotFound").' '.$classname;
946				return "";
947			}
948		} else {
949			print $langs->trans("ErrorNumberingModuleNotSetup", $this->element);
950			return "";
951		}
952	}
953
954	/**
955	 *  Create a document onto disk according to template module.
956	 *
957	 *  @param	    string		$modele			Force template to use ('' to not force)
958	 *  @param		Translate	$outputlangs	objet lang a utiliser pour traduction
959	 *  @param      int			$hidedetails    Hide details of lines
960	 *  @param      int			$hidedesc       Hide description
961	 *  @param      int			$hideref        Hide ref
962	 *  @param      null|array  $moreparams     Array to provide more information
963	 *  @return     int         				0 if KO, 1 if OK
964	 */
965	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
966	{
967		global $conf, $langs;
968
969		$result = 0;
970		$includedocgeneration = 0;
971
972		$langs->load("recruitment@recruitment");
973
974		if (!dol_strlen($modele)) {
975			if (!empty($conf->global->RECRUITMENTCANDIDATURE_ADDON_PDF))
976			{
977				$modele = $conf->global->RECRUITMENTCANDIDATURE_ADDON_PDF;
978			} else {
979				$modele = ''; // No default value. For job application, we allow to disable all PDF generation
980			}
981		}
982
983		$modelpath = "core/modules/recruitment/doc/";
984
985		if ($includedocgeneration && !empty($modele)) {
986			$result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
987		}
988
989		return $result;
990	}
991
992	/**
993	 * Action executed by scheduler
994	 * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters'
995	 * Use public function doScheduledJob($param1, $param2, ...) to get parameters
996	 *
997	 * @return	int			0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
998	 */
999	public function doScheduledJob()
1000	{
1001		global $conf, $langs;
1002
1003		//$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
1004
1005		$error = 0;
1006		$this->output = '';
1007		$this->error = '';
1008
1009		dol_syslog(__METHOD__, LOG_DEBUG);
1010
1011		$now = dol_now();
1012
1013		$this->db->begin();
1014
1015		// ...
1016
1017		$this->db->commit();
1018
1019		return $error;
1020	}
1021}
1022
1023
1024require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1025
1026/**
1027 * Class RecruitmentCandidatureLine. You can also remove this and generate a CRUD class for lines objects.
1028 */
1029class RecruitmentCandidatureLine extends CommonObjectLine
1030{
1031	// To complete with content of an object RecruitmentCandidatureLine
1032	// We should have a field rowid, fk_recruitmentcandidature and position
1033
1034	/**
1035	 * Constructor
1036	 *
1037	 * @param DoliDb $db Database handler
1038	 */
1039	public function __construct(DoliDB $db)
1040	{
1041		$this->db = $db;
1042	}
1043}
1044