dolibarr  17.0.4
project.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2002-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2005-2020 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2010 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
6  * Copyright (C) 2014-2017 Marcos GarcĂ­a <marcosgdf@gmail.com>
7  * Copyright (C) 2017 Ferran Marcet <fmarcet@2byte.es>
8  * Copyright (C) 2019 Juanjo Menent <jmenent@2byte.es>
9  * Copyright (C) 2022 Charlene Benke <charlene@patas-monkey.com>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see <https://www.gnu.org/licenses/>.
23  */
24 
30 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
31 
35 class Project extends CommonObject
36 {
37 
41  public $element = 'project';
42 
46  public $table_element = 'projet';
47 
51  public $table_element_line = 'projet_task';
52 
56  public $table_element_date;
57 
61  public $fk_element = 'fk_projet';
62 
67  public $ismultientitymanaged = 1;
68 
72  public $picto = 'project';
73 
77  protected $table_ref_field = 'ref';
78 
82  public $description;
83 
87  public $title;
88 
94  public $dateo;
95 
99  public $date_start;
100 
106  public $datee;
107 
111  public $date_end;
112 
116  public $date_start_event;
117 
121  public $date_end_event;
122 
126  public $location;
127 
131  public $date_close;
132 
133  public $socid; // To store id of thirdparty
134  public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
135 
137 
141  public $fk_user_close;
142 
146  public $user_close_id;
147  public $public;
148 
152  public $budget_amount;
153 
157  public $usage_opportunity;
158 
162  public $usage_task;
163 
167  public $usage_bill_time; // Is the time spent on project must be invoiced or not
168 
172  public $usage_organize_event;
173 
177  public $accept_conference_suggestions;
178 
182  public $accept_booth_suggestions;
183 
187  public $price_registration;
188 
192  public $price_booth;
193 
197  public $max_attendees;
198 
199  public $statuts_short;
200  public $statuts_long;
201 
202  public $statut; // 0=draft, 1=opened, 2=closed
203 
204  public $opp_status; // opportunity status, into table llx_c_lead_status
205  public $fk_opp_status; // opportunity status, into table llx_c_lead_status
206  public $opp_percent; // opportunity probability
207 
208  public $email_msgid;
209 
210  public $oldcopy;
211 
212  public $weekWorkLoad; // Used to store workload details of a projet
213  public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
214 
220  public $datec;
221 
225  public $date_c;
226 
232  public $datem;
233 
237  public $date_m;
238 
242  public $lines;
243 
268  // BEGIN MODULEBUILDER PROPERTIES
272  public $fields = array(
273  'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
274  'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>15, 'searchall'=>1),
275  'title' =>array('type'=>'varchar(255)', 'label'=>'ProjectLabel', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>17, 'showoncombobox'=>2, 'searchall'=>1),
276  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>19),
277  'fk_soc' =>array('type'=>'integer', 'label'=>'Thirdparty', 'enabled'=>1, 'visible'=>0, 'position'=>20),
278  'dateo' =>array('type'=>'date', 'label'=>'DateStart', 'enabled'=>1, 'visible'=>1, 'position'=>30),
279  'datee' =>array('type'=>'date', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>1, 'position'=>35),
280  'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>1),
281  'public' =>array('type'=>'integer', 'label'=>'Visibility', 'enabled'=>1, 'visible'=>1, 'position'=>65),
282  'fk_opp_status' =>array('type'=>'integer', 'label'=>'OpportunityStatusShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>75),
283  'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'OpportunityProbabilityShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>80),
284  'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85, 'searchall'=>1),
285  'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90, 'searchall'=>1),
286  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPdf', 'enabled'=>1, 'visible'=>0, 'position'=>95),
287  'date_close' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>0, 'position'=>105),
288  'fk_user_close' =>array('type'=>'integer', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>0, 'position'=>110),
289  'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'OpportunityAmountShort', 'enabled'=>1, 'visible'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position'=>115),
290  'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget', 'enabled'=>1, 'visible'=>-1, 'position'=>119),
291  'usage_bill_time' =>array('type'=>'integer', 'label'=>'UsageBillTimeShort', 'enabled'=>1, 'visible'=>-1, 'position'=>130),
292  'usage_opportunity' =>array('type'=>'integer', 'label'=>'UsageOpportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135),
293  'usage_task' =>array('type'=>'integer', 'label'=>'UsageTasks', 'enabled'=>1, 'visible'=>-1, 'position'=>140),
294  'usage_organize_event' =>array('type'=>'integer', 'label'=>'UsageOrganizeEvent', 'enabled'=>1, 'visible'=>-1, 'position'=>145),
295  // Properties for event organization
296  'date_start_event' =>array('type'=>'date', 'label'=>'DateStartEvent', 'enabled'=>"isModEnabled('eventorganization')", 'visible'=>1, 'position'=>200),
297  'date_end_event' =>array('type'=>'date', 'label'=>'DateEndEvent', 'enabled'=>"isModEnabled('eventorganization')", 'visible'=>1, 'position'=>201),
298  'location' =>array('type'=>'text', 'label'=>'Location', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>202),
299  'accept_conference_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestConf', 'enabled'=>1, 'visible'=>-1, 'position'=>210),
300  'accept_booth_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>211),
301  'price_registration' =>array('type'=>'double(24,8)', 'label'=>'PriceOfRegistration', 'enabled'=>1, 'visible'=>-1, 'position'=>212),
302  'price_booth' =>array('type'=>'double(24,8)', 'label'=>'PriceOfBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
303  'max_attendees' =>array('type'=>'integer', 'label'=>'MaxNbOfAttendees', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
304  // Generic
305  'datec' =>array('type'=>'datetime', 'label'=>'DateCreationShort', 'enabled'=>1, 'visible'=>-2, 'position'=>400),
306  'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>405),
307  'fk_user_creat' =>array('type'=>'integer', 'label'=>'UserCreation', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>410),
308  'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModification', 'enabled'=>1, 'visible'=>0, 'position'=>415),
309  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-1, 'position'=>420),
310  'email_msgid'=>array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'enabled'=>1, 'visible'=>-1, 'position'=>450, 'help'=>'EmailMsgIDWhenSourceisEmail', 'csslist'=>'tdoverflowmax125'),
311  'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>500),
312  );
313  // END MODULEBUILDER PROPERTIES
314 
318  const STATUS_DRAFT = 0;
319 
323  const STATUS_VALIDATED = 1;
324 
328  const STATUS_CLOSED = 2;
329 
335  public function __construct($db)
336  {
337  global $conf;
338 
339  $this->db = $db;
340 
341  $this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
342  $this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
343 
344  global $conf;
345 
346  if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
347  $this->fields['rowid']['visible'] = 0;
348  }
349 
350  if (empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
351  $this->fields['fk_opp_status']['enabled'] = 0;
352  $this->fields['opp_percent']['enabled'] = 0;
353  $this->fields['opp_amount']['enabled'] = 0;
354  $this->fields['usage_opportunity']['enabled'] = 0;
355  }
356 
357  if (!empty($conf->global->PROJECT_HIDE_TASKS)) {
358  $this->fields['usage_bill_time']['visible'] = 0;
359  $this->fields['usage_task']['visible'] = 0;
360  }
361 
362  if (empty($conf->eventorganization->enabled)) {
363  $this->fields['usage_organize_event']['visible'] = 0;
364  $this->fields['accept_conference_suggestions']['enabled'] = 0;
365  $this->fields['accept_booth_suggestions']['enabled'] = 0;
366  $this->fields['price_registration']['enabled'] = 0;
367  $this->fields['price_booth']['enabled'] = 0;
368  $this->fields['max_attendees']['enabled'] = 0;
369  }
370  }
371 
379  public function create($user, $notrigger = 0)
380  {
381  global $conf, $langs;
382 
383  $error = 0;
384  $ret = 0;
385 
386  $now = dol_now();
387 
388  // Clean parameters
389  $this->note_private = dol_substr($this->note_private, 0, 65535);
390  $this->note_public = dol_substr($this->note_public, 0, 65535);
391 
392  // Check parameters
393  if (!trim($this->ref)) {
394  $this->error = 'ErrorFieldsRequired';
395  dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
396  return -1;
397  }
398  if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0)) {
399  $this->error = 'ErrorFieldsRequired';
400  dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
401  return -1;
402  }
403 
404  // Create project
405  $this->db->begin();
406 
407  $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
408  $sql .= "ref";
409  $sql .= ", title";
410  $sql .= ", description";
411  $sql .= ", fk_soc";
412  $sql .= ", fk_user_creat";
413  $sql .= ", fk_statut";
414  $sql .= ", fk_opp_status";
415  $sql .= ", opp_percent";
416  $sql .= ", public";
417  $sql .= ", datec";
418  $sql .= ", dateo";
419  $sql .= ", datee";
420  $sql .= ", opp_amount";
421  $sql .= ", budget_amount";
422  $sql .= ", usage_opportunity";
423  $sql .= ", usage_task";
424  $sql .= ", usage_bill_time";
425  $sql .= ", usage_organize_event";
426  $sql .= ", accept_conference_suggestions";
427  $sql .= ", accept_booth_suggestions";
428  $sql .= ", price_registration";
429  $sql .= ", price_booth";
430  $sql .= ", max_attendees";
431  $sql .= ", date_start_event";
432  $sql .= ", date_end_event";
433  $sql .= ", location";
434  $sql .= ", email_msgid";
435  $sql .= ", note_private";
436  $sql .= ", note_public";
437  $sql .= ", entity";
438  $sql .= ", ip";
439  $sql .= ") VALUES (";
440  $sql .= "'".$this->db->escape($this->ref)."'";
441  $sql .= ", '".$this->db->escape($this->title)."'";
442  $sql .= ", '".$this->db->escape($this->description)."'";
443  $sql .= ", ".($this->socid > 0 ? $this->socid : "null");
444  $sql .= ", ".((int) $user->id);
445  $sql .= ", ".(is_numeric($this->statut) ? ((int) $this->statut) : '0');
446  $sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
447  $sql .= ", ".(is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
448  $sql .= ", ".($this->public ? 1 : 0);
449  $sql .= ", '".$this->db->idate($now)."'";
450  $sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
451  $sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
452  $sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
453  $sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
454  $sql .= ", ".($this->usage_opportunity ? 1 : 0);
455  $sql .= ", ".($this->usage_task ? 1 : 0);
456  $sql .= ", ".($this->usage_bill_time ? 1 : 0);
457  $sql .= ", ".($this->usage_organize_event ? 1 : 0);
458  $sql .= ", ".($this->accept_conference_suggestions ? 1 : 0);
459  $sql .= ", ".($this->accept_booth_suggestions ? 1 : 0);
460  $sql .= ", ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : 'null');
461  $sql .= ", ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : 'null');
462  $sql .= ", ".(strcmp($this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
463  $sql .= ", ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
464  $sql .= ", ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
465  $sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : 'null');
466  $sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
467  $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
468  $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
469  $sql .= ", ".((int) $conf->entity);
470  $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
471  $sql .= ")";
472 
473  dol_syslog(get_class($this)."::create", LOG_DEBUG);
474  $resql = $this->db->query($sql);
475  if ($resql) {
476  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
477  $ret = $this->id;
478 
479  if (!$notrigger) {
480  // Call trigger
481  $result = $this->call_trigger('PROJECT_CREATE', $user);
482  if ($result < 0) {
483  $error++;
484  }
485  // End call triggers
486  }
487  } else {
488  $this->error = $this->db->lasterror();
489  $this->errno = $this->db->lasterrno();
490  $error++;
491  }
492 
493  // Update extrafield
494  if (!$error) {
495  $result = $this->insertExtraFields();
496  if ($result < 0) {
497  $error++;
498  }
499  }
500 
501  if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
502  $res = $this->setValid($user);
503  if ($res < 0) {
504  $error++;
505  }
506  }
507 
508  if (!$error) {
509  $this->db->commit();
510  return $ret;
511  } else {
512  $this->db->rollback();
513  return -1;
514  }
515  }
516 
524  public function update($user, $notrigger = 0)
525  {
526  global $langs, $conf;
527 
528  $error = 0;
529 
530  // Clean parameters
531  $this->title = trim($this->title);
532  $this->description = trim($this->description);
533  if ($this->opp_amount < 0) {
534  $this->opp_amount = '';
535  }
536  if ($this->opp_percent < 0) {
537  $this->opp_percent = '';
538  }
539  if ($this->date_end && $this->date_end < $this->date_start) {
540  $this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
541  $this->errors[] = $this->error;
542  $this->db->rollback();
543  dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
544  return -3;
545  }
546 
547  $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
548 
549  if (dol_strlen(trim($this->ref)) > 0) {
550  $this->db->begin();
551 
552  $sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
553  $sql .= " ref='".$this->db->escape($this->ref)."'";
554  $sql .= ", title = '".$this->db->escape($this->title)."'";
555  $sql .= ", description = '".$this->db->escape($this->description)."'";
556  $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
557  $sql .= ", fk_statut = ".((int) $this->statut);
558  $sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
559  $sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
560  $sql .= ", public = ".($this->public ? 1 : 0);
561  $sql .= ", datec = ".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
562  $sql .= ", dateo = ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
563  $sql .= ", datee = ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
564  $sql .= ", date_close = ".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
565  $sql .= ", fk_user_close = ".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
566  $sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
567  $sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
568  $sql .= ", fk_user_modif = ".$user->id;
569  $sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
570  $sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
571  $sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
572  $sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
573  $sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0);
574  $sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0);
575  $sql .= ", price_registration = ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
576  $sql .= ", price_booth = ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : "null");
577  $sql .= ", max_attendees = ".(strcmp($this->max_attendees, '') ? price2num($this->max_attendees) : "null");
578  $sql .= ", date_start_event = ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
579  $sql .= ", date_end_event = ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
580  $sql .= ", location = '".$this->db->escape($this->location)."'";
581  $sql .= ", entity = ".((int) $this->entity);
582  $sql .= " WHERE rowid = ".((int) $this->id);
583 
584  dol_syslog(get_class($this)."::update", LOG_DEBUG);
585  $resql = $this->db->query($sql);
586  if ($resql) {
587  // Update extrafield
588  if (!$error) {
589  $result = $this->insertExtraFields();
590  if ($result < 0) {
591  $error++;
592  }
593  }
594 
595  if (!$error && !$notrigger) {
596  // Call trigger
597  $result = $this->call_trigger('PROJECT_MODIFY', $user);
598  if ($result < 0) {
599  $error++;
600  }
601  // End call triggers
602  }
603 
604  if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
605  // We remove directory
606  if ($conf->project->dir_output) {
607  $olddir = $conf->project->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
608  $newdir = $conf->project->dir_output."/".dol_sanitizeFileName($this->ref);
609  if (file_exists($olddir)) {
610  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
611  $res = @rename($olddir, $newdir);
612  if (!$res) {
613  $langs->load("errors");
614  $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
615  $error++;
616  }
617  }
618  }
619  }
620  if (!$error) {
621  $this->db->commit();
622  $result = 1;
623  } else {
624  $this->db->rollback();
625  $result = -1;
626  }
627  } else {
628  $this->error = $this->db->lasterror();
629  $this->errors[] = $this->error;
630  $this->db->rollback();
631  if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
632  $result = -4;
633  } else {
634  $result = -2;
635  }
636  dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
637  }
638  } else {
639  dol_syslog(get_class($this)."::update ref null");
640  $result = -1;
641  }
642 
643  return $result;
644  }
645 
655  public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
656  {
657  global $conf;
658 
659  if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
660  dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING);
661  return -1;
662  }
663 
664  $sql = "SELECT rowid, entity, ref, title, description, public, datec, opp_amount, budget_amount,";
665  $sql .= " tms, dateo as date_start, datee as date_end, date_close, fk_soc, fk_user_creat, fk_user_modif, fk_user_close, fk_statut as status, fk_opp_status, opp_percent,";
666  $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
667  $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location";
668  $sql .= " FROM ".MAIN_DB_PREFIX."projet";
669  if (!empty($id)) {
670  $sql .= " WHERE rowid = ".((int) $id);
671  } else {
672  $sql .= " WHERE entity IN (".getEntity('project').")";
673  if (!empty($ref)) {
674  $sql .= " AND ref = '".$this->db->escape($ref)."'";
675  } elseif (!empty($ref_ext)) {
676  $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
677  } else {
678  $sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
679  }
680  }
681 
682  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
683  $resql = $this->db->query($sql);
684  if ($resql) {
685  $num_rows = $this->db->num_rows($resql);
686 
687  if ($num_rows) {
688  $obj = $this->db->fetch_object($resql);
689 
690  $this->id = $obj->rowid;
691  $this->entity = $obj->entity;
692  $this->ref = $obj->ref;
693  $this->title = $obj->title;
694  $this->description = $obj->description;
695  $this->date_c = $this->db->jdate($obj->datec);
696  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
697  $this->date_m = $this->db->jdate($obj->tms);
698  $this->datem = $this->db->jdate($obj->tms); // TODO deprecated
699  $this->date_start = $this->db->jdate($obj->date_start);
700  $this->date_end = $this->db->jdate($obj->date_end);
701  $this->date_close = $this->db->jdate($obj->date_close);
702  $this->note_private = $obj->note_private;
703  $this->note_public = $obj->note_public;
704  $this->socid = $obj->fk_soc;
705  $this->user_author_id = $obj->fk_user_creat;
706  $this->user_modification_id = $obj->fk_user_modif;
707  $this->user_close_id = $obj->fk_user_close;
708  $this->public = $obj->public;
709  $this->statut = $obj->status; // deprecated
710  $this->status = $obj->status;
711  $this->opp_status = $obj->fk_opp_status;
712  $this->opp_amount = $obj->opp_amount;
713  $this->opp_percent = $obj->opp_percent;
714  $this->budget_amount = $obj->budget_amount;
715  $this->model_pdf = $obj->model_pdf;
716  $this->modelpdf = $obj->model_pdf; // deprecated
717  $this->usage_opportunity = (int) $obj->usage_opportunity;
718  $this->usage_task = (int) $obj->usage_task;
719  $this->usage_bill_time = (int) $obj->usage_bill_time;
720  $this->usage_organize_event = (int) $obj->usage_organize_event;
721  $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
722  $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
723  $this->price_registration = $obj->price_registration;
724  $this->price_booth = $obj->price_booth;
725  $this->max_attendees = $obj->max_attendees;
726  $this->date_start_event = $this->db->jdate($obj->date_start_event);
727  $this->date_end_event = $this->db->jdate($obj->date_end_event);
728  $this->location = $obj->location;
729  $this->email_msgid = $obj->email_msgid;
730 
731  $this->db->free($resql);
732 
733  // Retrieve all extrafield
734  // fetch optionals attributes and labels
735  $this->fetch_optionals();
736 
737  return 1;
738  }
739 
740  $this->db->free($resql);
741 
742  return 0;
743  } else {
744  $this->error = $this->db->lasterror();
745  $this->errors[] = $this->db->lasterror();
746  return -1;
747  }
748  }
749 
750  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
762  public function get_element_list($type, $tablename, $datefieldname = '', $date_start = '', $date_end = '', $projectkey = 'fk_projet')
763  {
764  // phpcs:enable
765 
766  global $hookmanager;
767 
768  $elements = array();
769 
770  if ($this->id <= 0) {
771  return $elements;
772  }
773 
774  $ids = $this->id;
775 
776  if ($type == 'agenda') {
777  $sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity('agenda').")";
778  } elseif ($type == 'expensereport') {
779  $sql = "SELECT ed.rowid FROM ".MAIN_DB_PREFIX."expensereport as e, ".MAIN_DB_PREFIX."expensereport_det as ed WHERE e.rowid = ed.fk_expensereport AND e.entity IN (".getEntity('expensereport').") AND ed.fk_projet IN (".$this->db->sanitize($ids).")";
780  } elseif ($type == 'project_task') {
781  $sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize($ids).")";
782  } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user
783  $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet IN (".$this->db->sanitize($ids).")";
784  } elseif ($type == 'stock_mouvement') {
785  $sql = "SELECT ms.rowid, ms.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin IN (".$this->db->sanitize($ids).") AND ms.type_mouvement = 1";
786  } elseif ($type == 'loan') {
787  $sql = "SELECT l.rowid, l.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet IN (".$this->db->sanitize($ids).")";
788  } else {
789  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity($type).")";
790  }
791 
792  if ($date_start > 0 && $type == 'loan') {
793  $sql .= " AND (dateend > '".$this->db->idate($date_start)."' OR dateend IS NULL)";
794  } elseif ($date_start > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
795  if (empty($datefieldname) && !empty($this->table_element_date)) {
796  $datefieldname = $this->table_element_date;
797  }
798  if (empty($datefieldname)) {
799  return 'Error this object has no date field defined';
800  }
801  $sql .= " AND (".$datefieldname." >= '".$this->db->idate($date_start)."' OR ".$datefieldname." IS NULL)";
802  }
803 
804  if ($date_end > 0 && $type == 'loan') {
805  $sql .= " AND (datestart < '".$this->db->idate($date_end)."' OR datestart IS NULL)";
806  } elseif ($date_end > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
807  if (empty($datefieldname) && !empty($this->table_element_date)) {
808  $datefieldname = $this->table_element_date;
809  }
810  if (empty($datefieldname)) {
811  return 'Error this object has no date field defined';
812  }
813  $sql .= " AND (".$datefieldname." <= '".$this->db->idate($date_end)."' OR ".$datefieldname." IS NULL)";
814  }
815 
816  $parameters = array(
817  'sql'=>$sql,
818  'type' => $type,
819  'tablename' => $tablename,
820  'datefieldname' => $datefieldname,
821  'dates' => $date_start,
822  'datee' => $date_end,
823  'fk_projet' => $projectkey,
824  'ids' => $ids,
825  );
826  $reshook = $hookmanager->executeHooks('getElementList', $parameters);
827  if ($reshook > 0) {
828  $sql = $hookmanager->resPrint;
829  } else {
830  $sql .= $hookmanager->resPrint;
831  }
832 
833  if (!$sql) {
834  return -1;
835  }
836 
837  //print $sql;
838  dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
839  $result = $this->db->query($sql);
840  if ($result) {
841  $nump = $this->db->num_rows($result);
842  if ($nump) {
843  $i = 0;
844  while ($i < $nump) {
845  $obj = $this->db->fetch_object($result);
846 
847  $elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
848 
849  $i++;
850  }
851  $this->db->free($result);
852  }
853 
854  /* Return array even if empty*/
855  return $elements;
856  } else {
857  dol_print_error($this->db);
858  }
859  }
860 
868  public function delete($user, $notrigger = 0)
869  {
870  global $langs, $conf;
871  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
872 
873  $error = 0;
874 
875  $this->db->begin();
876 
877  if (!$error) {
878  // Delete linked contacts
879  $res = $this->delete_linked_contact();
880  if ($res < 0) {
881  $this->error = 'ErrorFailToDeleteLinkedContact';
882  //$error++;
883  $this->db->rollback();
884  return 0;
885  }
886  }
887 
888  // Set fk_projet into elements to null
889  $listoftables = array(
890  'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet',
891  'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet',
892  'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet',
893  'fichinter'=>'fk_projet',
894  'don'=>array('field'=>'fk_projet', 'module'=>'don'),
895  'actioncomm'=>'fk_project',
896  'mrp_mo'=>'fk_project',
897  'entrepot'=>'fk_project'
898  );
899  foreach ($listoftables as $key => $value) {
900  if (is_array($value)) {
901  if (!isModEnabled($value['module'])) {
902  continue;
903  }
904  $fieldname = $value['field'];
905  } else {
906  $fieldname = $value;
907  }
908  $sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$fieldname." = NULL where ".$fieldname." = ".((int) $this->id);
909 
910  $resql = $this->db->query($sql);
911  if (!$resql) {
912  $this->errors[] = $this->db->lasterror();
913  $error++;
914  break;
915  }
916  }
917 
918  // Remove linked categories.
919  if (!$error) {
920  $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
921  $sql .= " WHERE fk_project = ".((int) $this->id);
922 
923  $result = $this->db->query($sql);
924  if (!$result) {
925  $error++;
926  $this->errors[] = $this->db->lasterror();
927  }
928  }
929 
930  // Fetch tasks
931  $this->getLinesArray($user, 0);
932 
933  // Delete tasks
934  $ret = $this->deleteTasks($user);
935  if ($ret < 0) {
936  $error++;
937  }
938 
939 
940  // Delete all child tables
941  if (!$error) {
942  $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
943  foreach ($elements as $table) {
944  if (!$error) {
945  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
946  $sql .= " WHERE fk_project = ".((int) $this->id);
947 
948  $result = $this->db->query($sql);
949  if (!$result) {
950  $error++;
951  $this->errors[] = $this->db->lasterror();
952  }
953  }
954  }
955  }
956 
957  if (!$error) {
958  $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
959  $sql .= " WHERE fk_object = ".((int) $this->id);
960 
961  $resql = $this->db->query($sql);
962  if (!$resql) {
963  $this->errors[] = $this->db->lasterror();
964  $error++;
965  }
966  }
967 
968  // Delete project
969  if (!$error) {
970  $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
971  $sql .= " WHERE rowid=".((int) $this->id);
972 
973  $resql = $this->db->query($sql);
974  if (!$resql) {
975  $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
976  $error++;
977  }
978  }
979 
980 
981 
982  if (empty($error)) {
983  // We remove directory
984  $projectref = dol_sanitizeFileName($this->ref);
985  if ($conf->project->dir_output) {
986  $dir = $conf->project->dir_output."/".$projectref;
987  if (file_exists($dir)) {
988  $res = @dol_delete_dir_recursive($dir);
989  if (!$res) {
990  $this->errors[] = 'ErrorFailToDeleteDir';
991  $error++;
992  }
993  }
994  }
995 
996  if (!$notrigger) {
997  // Call trigger
998  $result = $this->call_trigger('PROJECT_DELETE', $user);
999 
1000  if ($result < 0) {
1001  $error++;
1002  }
1003  // End call triggers
1004  }
1005  }
1006 
1007  if (empty($error)) {
1008  $this->db->commit();
1009  return 1;
1010  } else {
1011  foreach ($this->errors as $errmsg) {
1012  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1013  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1014  }
1015  dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
1016  $this->db->rollback();
1017  return -1;
1018  }
1019  }
1020 
1029  public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
1030  {
1031  if ($this->id <= 0) {
1032  return 0;
1033  }
1034 
1035  if ($type == 'agenda') {
1036  $sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".((int) $this->id)." AND entity IN (".getEntity('agenda').")";
1037  } elseif ($type == 'expensereport') {
1038  $sql = "SELECT COUNT(ed.rowid) as nb FROM ".MAIN_DB_PREFIX."expensereport as e, ".MAIN_DB_PREFIX."expensereport_det as ed WHERE e.rowid = ed.fk_expensereport AND e.entity IN (".getEntity('expensereport').") AND ed.fk_projet = ".((int) $this->id);
1039  } elseif ($type == 'project_task') {
1040  $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id);
1041  } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user
1042  $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet = ".((int) $this->id);
1043  } elseif ($type == 'stock_mouvement') {
1044  $sql = "SELECT COUNT(ms.rowid) as nb FROM ".MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin = ".((int) $this->id)." AND ms.type_mouvement = 1";
1045  } elseif ($type == 'loan') {
1046  $sql = "SELECT COUNT(l.rowid) as nb FROM ".MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet = ".((int) $this->id);
1047  } else {
1048  $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".((int) $this->id)." AND entity IN (".getEntity($type).")";
1049  }
1050 
1051  $result = $this->db->query($sql);
1052 
1053  if (!$result) {
1054  return 0;
1055  }
1056 
1057  $obj = $this->db->fetch_object($result);
1058 
1059  $this->db->free($result);
1060 
1061  return $obj->nb;
1062  }
1063 
1070  public function deleteTasks($user)
1071  {
1072  $countTasks = count($this->lines);
1073  $deleted = false;
1074  if ($countTasks) {
1075  foreach ($this->lines as $task) {
1076  if ($task->hasChildren() <= 0) { // If there is no children (or error to detect them)
1077  $deleted = true;
1078  $ret = $task->delete($user);
1079  if ($ret <= 0) {
1080  $this->errors[] = $this->db->lasterror();
1081  return -1;
1082  }
1083  }
1084  }
1085  }
1086  $this->getLinesArray($user);
1087  if ($deleted && count($this->lines) < $countTasks) {
1088  if (count($this->lines)) {
1089  $this->deleteTasks($this->lines);
1090  }
1091  }
1092 
1093  return 1;
1094  }
1095 
1103  public function setValid($user, $notrigger = 0)
1104  {
1105  global $langs, $conf;
1106 
1107  $error = 0;
1108 
1109  // Protection
1110  if ($this->status == self::STATUS_VALIDATED) {
1111  dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
1112  return 0;
1113  }
1114 
1115  // Check parameters
1116  if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
1117  $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1118  return -1;
1119  }
1120 
1121  $this->db->begin();
1122 
1123  $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1124  $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1125  $sql .= " WHERE rowid = ".((int) $this->id);
1126  //$sql .= " AND entity = ".((int) $conf->entity); // Disabled, when we use the ID for the where, we must not add any other search condition
1127 
1128  dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
1129  $resql = $this->db->query($sql);
1130  if ($resql) {
1131  // Call trigger
1132  if (empty($notrigger)) {
1133  $result = $this->call_trigger('PROJECT_VALIDATE', $user);
1134  if ($result < 0) {
1135  $error++;
1136  }
1137  // End call triggers
1138  }
1139 
1140  if (!$error) {
1141  $this->statut = 1;
1142  $this->db->commit();
1143  return 1;
1144  } else {
1145  $this->db->rollback();
1146  $this->error = join(',', $this->errors);
1147  dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
1148  return -1;
1149  }
1150  } else {
1151  $this->db->rollback();
1152  $this->error = $this->db->lasterror();
1153  return -1;
1154  }
1155  }
1156 
1163  public function setClose($user)
1164  {
1165  global $langs, $conf;
1166 
1167  $now = dol_now();
1168 
1169  $error = 0;
1170 
1171  if ($this->statut != self::STATUS_CLOSED) {
1172  $this->db->begin();
1173 
1174  $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1175  $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'";
1176  $sql .= " WHERE rowid = ".((int) $this->id);
1177  $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1178 
1179  if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
1180  // TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1181  }
1182 
1183  dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
1184  $resql = $this->db->query($sql);
1185  if ($resql) {
1186  // Call trigger
1187  $result = $this->call_trigger('PROJECT_CLOSE', $user);
1188  if ($result < 0) {
1189  $error++;
1190  }
1191  // End call triggers
1192 
1193  if (!$error) {
1194  $this->statut = 2;
1195  $this->db->commit();
1196  return 1;
1197  } else {
1198  $this->db->rollback();
1199  $this->error = join(',', $this->errors);
1200  dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
1201  return -1;
1202  }
1203  } else {
1204  $this->db->rollback();
1205  $this->error = $this->db->lasterror();
1206  return -1;
1207  }
1208  }
1209 
1210  return 0;
1211  }
1212 
1219  public function getLibStatut($mode = 0)
1220  {
1221  return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
1222  }
1223 
1224  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1232  public function LibStatut($status, $mode = 0)
1233  {
1234  // phpcs:enable
1235  global $langs;
1236 
1237  $statustrans = array(
1238  0 => 'status0',
1239  1 => 'status4',
1240  2 => 'status6',
1241  );
1242 
1243  $statusClass = 'status0';
1244  if (!empty($statustrans[$status])) {
1245  $statusClass = $statustrans[$status];
1246  }
1247 
1248  return dolGetStatus($langs->transnoentitiesnoconv($this->statuts_long[$status]), $langs->transnoentitiesnoconv($this->statuts_short[$status]), '', $statusClass, $mode);
1249  }
1250 
1264  public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
1265  {
1266  global $conf, $langs, $user, $hookmanager;
1267 
1268  if (!empty($conf->dol_no_mouse_hover)) {
1269  $notooltip = 1; // Force disable tooltips
1270  }
1271 
1272  $result = '';
1273  if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
1274  $option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
1275  }
1276 
1277  $label = '';
1278  if ($option != 'nolink') {
1279  $label = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1280  }
1281  if (isset($this->status)) {
1282  $label .= ' '.$this->getLibStatut(5);
1283  }
1284  $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Ref').': </b>'.$this->ref; // The space must be after the : to not being explode when showing the title in img_picto
1285  $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Label').': </b>'.$this->title; // The space must be after the : to not being explode when showing the title in img_picto
1286  if (isset($this->public)) {
1287  $label .= '<br><b>'.$langs->trans("Visibility").":</b> ";
1288  $label .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
1289  }
1290  if (!empty($this->thirdparty_name)) {
1291  $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('ThirdParty').': </b>'.$this->thirdparty_name; // The space must be after the : to not being explode when showing the title in img_picto
1292  }
1293  if (!empty($this->date_start)) {
1294  $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateStart').': </b>'.dol_print_date($this->date_start, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1295  }
1296  if (!empty($this->date_end)) {
1297  $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateEnd').': </b>'.dol_print_date($this->date_end, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1298  }
1299  if ($moreinpopup) {
1300  $label .= '<br>'.$moreinpopup;
1301  }
1302 
1303  $url = '';
1304  if ($option != 'nolink') {
1305  if (preg_match('/\.php$/', $option)) {
1306  $url = dol_buildpath($option, 1).'?id='.$this->id;
1307  } elseif ($option == 'task') {
1308  $url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1309  } elseif ($option == 'preview') {
1310  $url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1311  } elseif ($option == 'eventorganization') {
1312  $url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
1313  } else {
1314  $url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1315  }
1316  // Add param to save lastsearch_values or not
1317  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1318  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1319  $add_save_lastsearch_values = 1;
1320  }
1321  if ($add_save_lastsearch_values) {
1322  $url .= '&save_lastsearch_values=1';
1323  }
1324  }
1325 
1326  $linkclose = '';
1327  if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1328  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1329  $label = $langs->trans("ShowProject");
1330  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1331  }
1332  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1333  $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1334  } else {
1335  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1336  }
1337 
1338  $picto = 'projectpub';
1339  if (!$this->public) {
1340  $picto = 'project';
1341  }
1342 
1343  $linkstart = '<a href="'.$url.'"';
1344  $linkstart .= $linkclose.'>';
1345  $linkend = '</a>';
1346 
1347  $result .= $linkstart;
1348  if ($withpicto) {
1349  $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="pictofixedwidth"' : '') : 'class="'.(($withpicto != 2) ? 'pictofixedwidth ' : '').'classfortooltip pictofixedwidth em088"'), 0, 0, $notooltip ? 0 : 1);
1350  }
1351  if ($withpicto != 2) {
1352  $result .= $this->ref;
1353  }
1354  $result .= $linkend;
1355  if ($withpicto != 2) {
1356  $result .= (($addlabel && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1357  }
1358 
1359  global $action;
1360  $hookmanager->initHooks(array('projectdao'));
1361  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1362  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1363  if ($reshook > 0) {
1364  $result = $hookmanager->resPrint;
1365  } else {
1366  $result .= $hookmanager->resPrint;
1367  }
1368 
1369  return $result;
1370  }
1371 
1379  public function initAsSpecimen()
1380  {
1381  global $user, $langs, $conf;
1382 
1383  $now = dol_now();
1384 
1385  // Initialise parameters
1386  $this->id = 0;
1387  $this->ref = 'SPECIMEN';
1388  $this->entity = $conf->entity;
1389  $this->specimen = 1;
1390  $this->socid = 1;
1391  $this->date_c = $now;
1392  $this->date_m = $now;
1393  $this->date_start = $now;
1394  $this->date_end = $now + (3600 * 24 * 365);
1395  $this->note_public = 'SPECIMEN';
1396  $this->fk_ele = 20000;
1397  $this->opp_amount = 20000;
1398  $this->budget_amount = 10000;
1399 
1400  $this->usage_opportunity = 1;
1401  $this->usage_task = 1;
1402  $this->usage_bill_time = 1;
1403  $this->usage_organize_event = 1;
1404 
1405  /*
1406  $nbp = mt_rand(1, 9);
1407  $xnbp = 0;
1408  while ($xnbp < $nbp)
1409  {
1410  $line = new Task($this->db);
1411  $line->fk_project = 0;
1412  $line->label = $langs->trans("Label") . " " . $xnbp;
1413  $line->description = $langs->trans("Description") . " " . $xnbp;
1414 
1415  $this->lines[]=$line;
1416  $xnbp++;
1417  }
1418  */
1419  }
1420 
1428  public function restrictedProjectArea(User $user, $mode = 'read')
1429  {
1430  // To verify role of users
1431  $userAccess = 0;
1432  if (($mode == 'read' && !empty($user->rights->projet->all->lire)) || ($mode == 'write' && !empty($user->rights->projet->all->creer)) || ($mode == 'delete' && !empty($user->rights->projet->all->supprimer))) {
1433  $userAccess = 1;
1434  } elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer)))) {
1435  $userAccess = 1;
1436  } else { // No access due to permission to read all projects, so we check if we are a contact of project
1437  foreach (array('internal', 'external') as $source) {
1438  $userRole = $this->liste_contact(4, $source);
1439  $num = count($userRole);
1440 
1441  $nblinks = 0;
1442  while ($nblinks < $num) {
1443  if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) { // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1444  if ($mode == 'read' && $user->rights->projet->lire) {
1445  $userAccess++;
1446  }
1447  if ($mode == 'write' && $user->rights->projet->creer) {
1448  $userAccess++;
1449  }
1450  if ($mode == 'delete' && $user->rights->projet->supprimer) {
1451  $userAccess++;
1452  }
1453  }
1454  if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) { // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1455  if ($mode == 'read' && $user->rights->projet->lire) {
1456  $userAccess++;
1457  }
1458  if ($mode == 'write' && $user->rights->projet->creer) {
1459  $userAccess++;
1460  }
1461  if ($mode == 'delete' && $user->rights->projet->supprimer) {
1462  $userAccess++;
1463  }
1464  }
1465  $nblinks++;
1466  }
1467  }
1468  //if (empty($nblinks)) // If nobody has permission, we grant creator
1469  //{
1470  // if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1471  // {
1472  // $userAccess = 1;
1473  // }
1474  //}
1475  }
1476 
1477  return ($userAccess ? $userAccess : -1);
1478  }
1479 
1490  public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1491  {
1492  $projects = array();
1493  $temp = array();
1494 
1495  $sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1496  $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1497  if ($mode == 0) {
1498  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
1499  } elseif ($mode == 1) {
1500  $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1501  } elseif ($mode == 2) {
1502  // No filter. Use this if user has permission to see all project
1503  }
1504  $sql .= " WHERE p.entity IN (".getEntity('project').")";
1505  // Internal users must see project he is contact to even if project linked to a third party he can't see.
1506  //if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1507  if ($socid > 0) {
1508  $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1509  }
1510 
1511  // Get id of types of contacts for projects (This list never contains a lot of elements)
1512  $listofprojectcontacttype = array();
1513  $sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1514  $sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1515  $sql2 .= " AND ctc.source = 'internal'";
1516  $resql = $this->db->query($sql2);
1517  if ($resql) {
1518  while ($obj = $this->db->fetch_object($resql)) {
1519  $listofprojectcontacttype[$obj->rowid] = $obj->code;
1520  }
1521  } else {
1522  dol_print_error($this->db);
1523  }
1524  if (count($listofprojectcontacttype) == 0) {
1525  $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1526  }
1527 
1528  if ($mode == 0) {
1529  $sql .= " AND ( p.public = 1";
1530  $sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1531  $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1532  $sql .= " )";
1533  } elseif ($mode == 1) {
1534  $sql .= " AND ec.element_id = p.rowid";
1535  $sql .= " AND (";
1536  $sql .= " ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1537  $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1538  $sql .= " )";
1539  } elseif ($mode == 2) {
1540  // No filter. Use this if user has permission to see all project
1541  }
1542 
1543  $sql .= $filter;
1544  //print $sql;
1545 
1546  $resql = $this->db->query($sql);
1547  if ($resql) {
1548  $num = $this->db->num_rows($resql);
1549  $i = 0;
1550  while ($i < $num) {
1551  $row = $this->db->fetch_row($resql);
1552  $projects[$row[0]] = $row[1];
1553  $temp[] = $row[0];
1554  $i++;
1555  }
1556 
1557  $this->db->free($resql);
1558 
1559  if ($list) {
1560  if (empty($temp)) {
1561  return '0';
1562  }
1563  $result = implode(',', $temp);
1564  return $result;
1565  }
1566  } else {
1567  dol_print_error($this->db);
1568  }
1569 
1570  return $projects;
1571  }
1572 
1588  public function createFromClone(User $user, $fromid, $clone_contact = false, $clone_task = true, $clone_project_file = false, $clone_task_file = false, $clone_note = true, $move_date = true, $notrigger = 0, $newthirdpartyid = 0)
1589  {
1590  global $langs, $conf;
1591 
1592  $error = 0;
1593 
1594  dol_syslog("createFromClone clone_contact=".$clone_contact." clone_task=".$clone_task." clone_project_file=".$clone_project_file." clone_note=".$clone_note." move_date=".$move_date, LOG_DEBUG);
1595 
1596  $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1597 
1598  $clone_project = new Project($this->db);
1599 
1600  $clone_project->context['createfromclone'] = 'createfromclone';
1601 
1602  $this->db->begin();
1603 
1604  // Load source object
1605  $clone_project->fetch($fromid);
1606  $clone_project->fetch_optionals();
1607  if ($newthirdpartyid > 0) {
1608  $clone_project->socid = $newthirdpartyid;
1609  }
1610  $clone_project->fetch_thirdparty();
1611 
1612  $orign_dt_start = $clone_project->date_start;
1613  $orign_project_ref = $clone_project->ref;
1614 
1615  $clone_project->id = 0;
1616  if ($move_date) {
1617  $clone_project->date_start = $now;
1618  if (!(empty($clone_project->date_end))) {
1619  $clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1620  }
1621  }
1622 
1623  $clone_project->date_c = $now;
1624 
1625  if (!$clone_note) {
1626  $clone_project->note_private = '';
1627  $clone_project->note_public = '';
1628  }
1629 
1630  //Generate next ref
1631  $defaultref = '';
1632  $obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1633  // Search template files
1634  $file = ''; $classname = ''; $filefound = 0;
1635  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1636  foreach ($dirmodels as $reldir) {
1637  $file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1638  if (file_exists($file)) {
1639  $filefound = 1;
1640  dol_include_once($reldir."core/modules/project/".$obj.'.php');
1641  $modProject = new $obj;
1642  $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1643  break;
1644  }
1645  }
1646  if (is_numeric($defaultref) && $defaultref <= 0) {
1647  $defaultref = '';
1648  }
1649 
1650  $clone_project->ref = $defaultref;
1651  $clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1652 
1653  // Create clone
1654  $result = $clone_project->create($user, $notrigger);
1655 
1656  // Other options
1657  if ($result < 0) {
1658  $this->error .= $clone_project->error;
1659  $error++;
1660  }
1661 
1662  if (!$error) {
1663  //Get the new project id
1664  $clone_project_id = $clone_project->id;
1665 
1666  //Note Update
1667  if (!$clone_note) {
1668  $clone_project->note_private = '';
1669  $clone_project->note_public = '';
1670  } else {
1671  $this->db->begin();
1672  $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1673  if ($res < 0) {
1674  $this->error .= $clone_project->error;
1675  $error++;
1676  $this->db->rollback();
1677  } else {
1678  $this->db->commit();
1679  }
1680 
1681  $this->db->begin();
1682  $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1683  if ($res < 0) {
1684  $this->error .= $clone_project->error;
1685  $error++;
1686  $this->db->rollback();
1687  } else {
1688  $this->db->commit();
1689  }
1690  }
1691 
1692  //Duplicate contact
1693  if ($clone_contact) {
1694  $origin_project = new Project($this->db);
1695  $origin_project->fetch($fromid);
1696 
1697  foreach (array('internal', 'external') as $source) {
1698  $tab = $origin_project->liste_contact(-1, $source);
1699  if (is_array($tab) && count($tab)>0) {
1700  foreach ($tab as $contacttoadd) {
1701  $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1702  if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1703  $langs->load("errors");
1704  $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1705  $error++;
1706  } else {
1707  if ($clone_project->error != '') {
1708  $this->error .= $clone_project->error;
1709  $error++;
1710  }
1711  }
1712  }
1713  } elseif ($tab < 0) {
1714  $this->error .= $origin_project->error;
1715  $error++;
1716  }
1717  }
1718  }
1719 
1720  //Duplicate file
1721  if ($clone_project_file) {
1722  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1723 
1724  $clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
1725  $ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1726 
1727  if (dol_mkdir($clone_project_dir) >= 0) {
1728  $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1729  foreach ($filearray as $key => $file) {
1730  $rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
1731  if (is_numeric($rescopy) && $rescopy < 0) {
1732  $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1733  $error++;
1734  }
1735  }
1736  } else {
1737  $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1738  $error++;
1739  }
1740  }
1741 
1742  //Duplicate task
1743  if ($clone_task) {
1744  require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1745 
1746  $taskstatic = new Task($this->db);
1747 
1748  // Security check
1749  $socid = 0;
1750  if ($user->socid > 0) {
1751  $socid = $user->socid;
1752  }
1753 
1754  $tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1755 
1756  $tab_conv_child_parent = array();
1757 
1758  // Loop on each task, to clone it
1759  foreach ($tasksarray as $tasktoclone) {
1760  $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false);
1761  if ($result_clone <= 0) {
1762  $this->error .= $taskstatic->error;
1763  $error++;
1764  } else {
1765  $new_task_id = $result_clone;
1766  $taskstatic->fetch($tasktoclone->id);
1767 
1768  //manage new parent clone task id
1769  // if the current task has child we store the original task id and the equivalent clone task id
1770  if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1771  $tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1772  }
1773  }
1774  }
1775 
1776  //Parse all clone node to be sure to update new parent
1777  $tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1778  foreach ($tasksarray as $task_cloned) {
1779  $taskstatic->fetch($task_cloned->id);
1780  if ($taskstatic->fk_task_parent != 0) {
1781  $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1782  }
1783  $res = $taskstatic->update($user, $notrigger);
1784  if ($result_clone <= 0) {
1785  $this->error .= $taskstatic->error;
1786  $error++;
1787  }
1788  }
1789  }
1790  }
1791 
1792  unset($clone_project->context['createfromclone']);
1793 
1794  if (!$error) {
1795  $this->db->commit();
1796  return $clone_project_id;
1797  } else {
1798  $this->db->rollback();
1799  dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1800  return -1;
1801  }
1802  }
1803 
1804 
1811  public function shiftTaskDate($old_project_dt_start)
1812  {
1813  global $user, $langs, $conf;
1814 
1815  $error = 0;
1816  $result = 0;
1817 
1818  $taskstatic = new Task($this->db);
1819 
1820  // Security check
1821  $socid = 0;
1822  if ($user->socid > 0) {
1823  $socid = $user->socid;
1824  }
1825 
1826  $tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1827 
1828  foreach ($tasksarray as $tasktoshiftdate) {
1829  $to_update = false;
1830  // Fetch only if update of date will be made
1831  if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
1832  //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1833  $to_update = true;
1834  $task = new Task($this->db);
1835  $result = $task->fetch($tasktoshiftdate->id);
1836  if (!$result) {
1837  $error++;
1838  $this->error .= $task->error;
1839  }
1840  }
1841  //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1842 
1843  //Calcultate new task start date with difference between old proj start date and origin task start date
1844  if (!empty($tasktoshiftdate->date_start)) {
1845  $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
1846  }
1847 
1848  //Calcultate new task end date with difference between origin proj end date and origin task end date
1849  if (!empty($tasktoshiftdate->date_end)) {
1850  $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1851  }
1852 
1853  if ($to_update) {
1854  $result = $task->update($user);
1855  if (!$result) {
1856  $error++;
1857  $this->error .= $task->error;
1858  }
1859  }
1860  }
1861  if ($error != 0) {
1862  return -1;
1863  }
1864  return $result;
1865  }
1866 
1867 
1868  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1876  public function update_element($tableName, $elementSelectId)
1877  {
1878  // phpcs:enable
1879  $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1880 
1881  if ($tableName == "actioncomm") {
1882  $sql .= " SET fk_project=".$this->id;
1883  $sql .= " WHERE id=".((int) $elementSelectId);
1884  } elseif ($tableName == "entrepot") {
1885  $sql .= " SET fk_project=".$this->id;
1886  $sql .= " WHERE rowid=".((int) $elementSelectId);
1887  } else {
1888  $sql .= " SET fk_projet=".$this->id;
1889  $sql .= " WHERE rowid=".((int) $elementSelectId);
1890  }
1891 
1892  dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
1893  $resql = $this->db->query($sql);
1894  if (!$resql) {
1895  $this->error = $this->db->lasterror();
1896  return -1;
1897  } else {
1898  return 1;
1899  }
1900  }
1901 
1902  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1912  public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
1913  {
1914  // phpcs:enable
1915  $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1916 
1917  if ($tableName == "actioncomm") {
1918  $sql .= " SET fk_project=NULL";
1919  $sql .= " WHERE id=".((int) $elementSelectId);
1920  } else {
1921  $sql .= " SET ".$projectfield."=NULL";
1922  $sql .= " WHERE rowid=".((int) $elementSelectId);
1923  }
1924 
1925  dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
1926  $resql = $this->db->query($sql);
1927  if (!$resql) {
1928  $this->error = $this->db->lasterror();
1929  return -1;
1930  } else {
1931  return 1;
1932  }
1933  }
1934 
1945  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1946  {
1947  global $conf, $langs;
1948 
1949  $langs->load("projects");
1950 
1951  if (!dol_strlen($modele)) {
1952  $modele = 'baleine';
1953 
1954  if ($this->model_pdf) {
1955  $modele = $this->model_pdf;
1956  } elseif (!empty($conf->global->PROJECT_ADDON_PDF)) {
1957  $modele = $conf->global->PROJECT_ADDON_PDF;
1958  }
1959  }
1960 
1961  $modelpath = "core/modules/project/doc/";
1962 
1963  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1964  }
1965 
1966 
1976  public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
1977  {
1978  $error = 0;
1979 
1980  $this->weekWorkLoad = array();
1981  $this->weekWorkLoadPerTask = array();
1982 
1983  if (empty($datestart)) {
1984  dol_print_error('', 'Error datestart parameter is empty');
1985  }
1986 
1987  $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
1988  $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
1989  $sql .= " WHERE ptt.fk_task = pt.rowid";
1990  $sql .= " AND pt.fk_projet = ".((int) $this->id);
1991  $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
1992  $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
1993  if ($taskid) {
1994  $sql .= " AND ptt.fk_task=".((int) $taskid);
1995  }
1996  if (is_numeric($userid)) {
1997  $sql .= " AND ptt.fk_user=".((int) $userid);
1998  }
1999 
2000  //print $sql;
2001  $resql = $this->db->query($sql);
2002  if ($resql) {
2003  $daylareadyfound = array();
2004 
2005  $num = $this->db->num_rows($resql);
2006  $i = 0;
2007  // Loop on each record found, so each couple (project id, task id)
2008  while ($i < $num) {
2009  $obj = $this->db->fetch_object($resql);
2010  $day = $this->db->jdate($obj->task_date); // task_date is date without hours
2011  if (empty($daylareadyfound[$day])) {
2012  $this->weekWorkLoad[$day] = $obj->task_duration;
2013  $this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration;
2014  } else {
2015  $this->weekWorkLoad[$day] += $obj->task_duration;
2016  $this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration;
2017  }
2018  $daylareadyfound[$day] = 1;
2019  $i++;
2020  }
2021  $this->db->free($resql);
2022  return 1;
2023  } else {
2024  $this->error = "Error ".$this->db->lasterror();
2025  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2026  return -1;
2027  }
2028  }
2029 
2039  public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2040  {
2041  $error = 0;
2042 
2043  $this->monthWorkLoad = array();
2044  $this->monthWorkLoadPerTask = array();
2045 
2046  if (empty($datestart)) {
2047  dol_print_error('', 'Error datestart parameter is empty');
2048  }
2049 
2050  $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
2051  $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2052  $sql .= " WHERE ptt.fk_task = pt.rowid";
2053  $sql .= " AND pt.fk_projet = ".((int) $this->id);
2054  $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
2055  $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
2056  if ($taskid) {
2057  $sql .= " AND ptt.fk_task=".((int) $taskid);
2058  }
2059  if (is_numeric($userid)) {
2060  $sql .= " AND ptt.fk_user=".((int) $userid);
2061  }
2062 
2063  //print $sql;
2064  $resql = $this->db->query($sql);
2065  if ($resql) {
2066  $weekalreadyfound = array();
2067 
2068  $num = $this->db->num_rows($resql);
2069  $i = 0;
2070  // Loop on each record found, so each couple (project id, task id)
2071  while ($i < $num) {
2072  $obj = $this->db->fetch_object($resql);
2073  if (!empty($obj->task_date)) {
2074  $date = explode('-', $obj->task_date);
2075  $week_number = getWeekNumber($date[2], $date[1], $date[0]);
2076  }
2077  if (empty($weekalreadyfound[$week_number])) {
2078  $this->monthWorkLoad[$week_number] = $obj->task_duration;
2079  $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration;
2080  } else {
2081  $this->monthWorkLoad[$week_number] += $obj->task_duration;
2082  $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration;
2083  }
2084  $weekalreadyfound[$week_number] = 1;
2085  $i++;
2086  }
2087  $this->db->free($resql);
2088  return 1;
2089  } else {
2090  $this->error = "Error ".$this->db->lasterror();
2091  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2092  return -1;
2093  }
2094  }
2095 
2096  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2103  public function load_board($user)
2104  {
2105  // phpcs:enable
2106  global $conf, $langs;
2107 
2108  // For external user, no check is done on company because readability is managed by public status of project and assignement.
2109  //$socid=$user->socid;
2110 
2111  $response = new WorkboardResponse();
2112  $response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2113  $response->label = $langs->trans("OpenedProjects");
2114  $response->labelShort = $langs->trans("Opened");
2115  $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2116  $response->img = img_object('', "projectpub");
2117  $response->nbtodo = 0;
2118  $response->nbtodolate = 0;
2119 
2120  $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2121  $sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
2122  $sql .= ")";
2123  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2124  // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2125  //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2126  $sql .= " WHERE p.fk_statut = 1";
2127  $sql .= " AND p.entity IN (".getEntity('project').')';
2128 
2129 
2130  $projectsListId = null;
2131  if (!$user->hasRight("projet", "all", "lire")) {
2132  $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
2133  $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2134  if (empty($projectsListId)) {
2135  return $response;
2136  }
2137 
2138  $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2139  }
2140 
2141  // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2142  //if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2143  // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2144  //if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
2145 
2146  //print $sql;
2147  $resql = $this->db->query($sql);
2148  if ($resql) {
2149  $project_static = new Project($this->db);
2150 
2151 
2152  // This assignment in condition is not a bug. It allows walking the results.
2153  while ($obj = $this->db->fetch_object($resql)) {
2154  $response->nbtodo++;
2155 
2156  $project_static->statut = $obj->status;
2157  $project_static->opp_status = $obj->fk_opp_status;
2158  $project_static->date_end = $this->db->jdate($obj->datee);
2159 
2160  if ($project_static->hasDelay()) {
2161  $response->nbtodolate++;
2162  }
2163  }
2164 
2165  return $response;
2166  }
2167 
2168  $this->error = $this->db->error();
2169  return -1;
2170  }
2171 
2180  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
2181  {
2182  $tables = array(
2183  'projet'
2184  );
2185 
2186  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
2187  }
2188 
2189 
2190  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2196  public function load_state_board()
2197  {
2198  // phpcs:enable
2199  global $user;
2200 
2201  $this->nb = array();
2202 
2203  $sql = "SELECT count(p.rowid) as nb";
2204  $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2205  $sql .= " WHERE";
2206  $sql .= " p.entity IN (".getEntity('project').")";
2207  if (empty($user->rights->projet->all->lire)) {
2208  $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2209  $sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2210  }
2211 
2212  $resql = $this->db->query($sql);
2213  if ($resql) {
2214  while ($obj = $this->db->fetch_object($resql)) {
2215  $this->nb["projects"] = $obj->nb;
2216  }
2217  $this->db->free($resql);
2218  return 1;
2219  } else {
2220  dol_print_error($this->db);
2221  $this->error = $this->db->error();
2222  return -1;
2223  }
2224  }
2225 
2226 
2232  public function hasDelay()
2233  {
2234  global $conf;
2235 
2236  if (!($this->statut == self::STATUS_VALIDATED)) {
2237  return false;
2238  }
2239  if (!$this->date_end) {
2240  return false;
2241  }
2242 
2243  $now = dol_now();
2244 
2245  return ($this->date_end) < ($now - $conf->project->warning_delay);
2246  }
2247 
2248 
2255  public function info($id)
2256  {
2257  $sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2258  $sql .= ' date_close as datecloture,';
2259  $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture';
2260  $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
2261  $sql .= ' WHERE c.rowid = '.((int) $id);
2262  $result = $this->db->query($sql);
2263  if ($result) {
2264  if ($this->db->num_rows($result)) {
2265  $obj = $this->db->fetch_object($result);
2266  $this->id = $obj->rowid;
2267  if ($obj->fk_user_author) {
2268  $cuser = new User($this->db);
2269  $cuser->fetch($obj->fk_user_author);
2270  $this->user_creation = $cuser;
2271  }
2272 
2273  if (!empty($obj->fk_user_cloture)) {
2274  $cluser = new User($this->db);
2275  $cluser->fetch($obj->fk_user_cloture);
2276  $this->user_cloture = $cluser;
2277  }
2278 
2279  $this->date_creation = $this->db->jdate($obj->datec);
2280  $this->date_modification = $this->db->jdate($obj->datem);
2281  $this->date_cloture = $this->db->jdate($obj->datecloture);
2282  }
2283 
2284  $this->db->free($result);
2285  } else {
2286  dol_print_error($this->db);
2287  }
2288  }
2289 
2300  public function setCategories($categories)
2301  {
2302  require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2303  return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2304  }
2305 
2306 
2314  public function getLinesArray($user, $loadRoleMode = 1)
2315  {
2316  require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2317  $taskstatic = new Task($this->db);
2318 
2319  $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '', '-1', '', 0, 0, array(), 0, array(), 0, $loadRoleMode);
2320  }
2321 }
$object ref
Definition: info.php:78
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
static commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Class to manage Dolibarr database access.
Class to manage projects.
$user_author_id
Id of project creator. Not defined if shared project.
LibStatut($status, $mode=0)
Renvoi status label for a status.
getLibStatut($mode=0)
Return status label of object.
getElementCount($type, $tablename, $projectkey='fk_projet')
Return the count of a type of linked elements of this project.
setValid($user, $notrigger=0)
Validate a project.
const STATUS_VALIDATED
Open/Validated status.
$public
Tell if this is a public or private project.
getProjectsAuthorizedForUser($user, $mode=0, $list=0, $socid=0, $filter='')
Return array of projects a user has permission on, is affected to, or all projects.
create($user, $notrigger=0)
Create a project into database.
load_state_board()
Charge indicateurs this->nb pour le tableau de bord.
const STATUS_CLOSED
Closed status.
const STATUS_DRAFT
Draft status.
loadTimeSpentMonth($datestart, $taskid=0, $userid=0)
Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of projec...
__construct($db)
Constructor.
get_element_list($type, $tablename, $datefieldname='', $date_start='', $date_end='', $projectkey='fk_projet')
Return list of elements for type, linked to a project.
fetch($id, $ref='', $ref_ext='', $email_msgid='')
Get object from database.
shiftTaskDate($old_project_dt_start)
Shift project task date from current date to delta.
$table_ref_field
{}
hasDelay()
Is the project delayed?
remove_element($tableName, $elementSelectId, $projectfield='fk_projet')
Associate element to a project.
setCategories($categories)
Sets object to supplied categories.
update_element($tableName, $elementSelectId)
Associate element to a project.
createFromClone(User $user, $fromid, $clone_contact=false, $clone_task=true, $clone_project_file=false, $clone_task_file=false, $clone_note=true, $move_date=true, $notrigger=0, $newthirdpartyid=0)
Load an object from its id and create a new one in database.
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
getLinesArray($user, $loadRoleMode=1)
Create an array of tasks of current project.
info($id)
Charge les informations d'ordre info dans l'objet commande.
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
initAsSpecimen()
Initialise an instance with random values.
setClose($user)
Close a project.
loadTimeSpent($datestart, $taskid=0, $userid=0)
Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of projec...
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create an intervention document on disk using template defined into PROJECT_ADDON_PDF.
getNomUrl($withpicto=0, $option='', $addlabel=0, $moreinpopup='', $sep=' - ', $notooltip=0, $save_lastsearch_value=-1, $morecss='')
Return clickable name (with picto eventually)
restrictedProjectArea(User $user, $mode='read')
Check if user has permission on current project.
deleteTasks($user)
Delete tasks with no children first, then task with children recursively.
update($user, $notrigger=0)
Update a project.
Class to manage tasks.
Definition: task.class.php:38
Class to manage Dolibarr users.
Definition: user.class.php:47
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:745
getWeekNumber($day, $month, $year)
Return week number.
Definition: date.lib.php:1167
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:121
print *****$script_file(".$version.") pid cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
Definition: files.lib.php:1402
dol_copy($srcfile, $destfile, $newmask=0, $overwriteifexists=1)
Copy a file to another file.
Definition: files.lib.php:713
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition: files.lib.php:61
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_now($mode='auto')
Return date for now.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_substr($string, $start, $length, $stringencoding='', $trunconbytes=0)
Make a substring.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
if(!function_exists('utf8_encode')) if(!function_exists('utf8_decode')) getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
$conf db
API class for accounts.
Definition: inc.php:41