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