dolibarr  21.0.0-alpha
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  * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
11  * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
12  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program. If not, see <https://www.gnu.org/licenses/>.
26  */
27 
33 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
34 
38 class Project extends CommonObject
39 {
43  public $element = 'project';
44 
48  public $table_element = 'projet';
49 
53  public $table_element_line = 'projet_task';
54 
58  public $table_element_date;
59 
63  public $fk_element = 'fk_projet';
64 
68  public $picto = 'project';
69 
73  protected $table_ref_field = 'ref';
74 
78  public $fk_project;
79 
83  public $description;
84 
88  public $title;
89 
95  public $dateo;
96 
100  public $date_start;
101 
107  public $datee;
108 
112  public $date_end;
113 
117  public $date_start_event;
118 
122  public $date_end_event;
123 
127  public $location;
128 
132  public $date_close;
133 
134  public $socid; // To store id of thirdparty
135  public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
136 
138 
142  public $fk_user_close;
143 
144  public $public;
145 
149  public $budget_amount;
150 
154  public $usage_opportunity;
155 
159  public $usage_task;
160 
164  public $usage_bill_time; // Is the time spent on project must be invoiced or not
165 
169  public $usage_organize_event;
170 
174  public $accept_conference_suggestions;
175 
179  public $accept_booth_suggestions;
180 
184  public $price_registration;
185 
189  public $price_booth;
190 
194  public $max_attendees;
195 
196  public $statut; // 0=draft, 1=opened, 2=closed
197 
198  public $opp_status; // opportunity status, into table llx_c_lead_status
199  public $opp_status_code;
200  public $fk_opp_status; // opportunity status, into table llx_c_lead_status
201  public $opp_amount; // opportunity amount
202  public $opp_percent; // opportunity probability
203  public $opp_weighted_amount; // opportunity weighted amount
204 
205  public $email_msgid;
206 
207  public $oldcopy;
208 
209  public $weekWorkLoad; // Used to store workload details of a projet
210  public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
211 
215  public $monthWorkLoad;
216 
220  public $monthWorkLoadPerTask;
221 
227  public $datec;
228 
232  public $date_c;
233 
239  public $datem;
240 
244  public $date_m;
245 
249  public $ip;
250 
254  public $lines;
255 
296  // BEGIN MODULEBUILDER PROPERTIES
300  public $fields = array(
301  'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
302  'fk_project' => array('type' => 'integer', 'label' => 'Parent', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 12),
303  'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 15, 'searchall' => 1),
304  'title' => array('type' => 'varchar(255)', 'label' => 'ProjectLabel', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 17, 'showoncombobox' => 2, 'searchall' => 1),
305  'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 19),
306  'fk_soc' => array('type' => 'integer', 'label' => 'Thirdparty', 'enabled' => 1, 'visible' => 0, 'position' => 20),
307  'dateo' => array('type' => 'date', 'label' => 'DateStart', 'enabled' => 1, 'visible' => -1, 'position' => 30),
308  'datee' => array('type' => 'date', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => 1, 'position' => 35),
309  'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => 3, 'position' => 55, 'searchall' => 1),
310  'public' => array('type' => 'integer', 'label' => 'Visibility', 'enabled' => 1, 'visible' => -1, 'position' => 65),
311  'fk_opp_status' => array('type' => 'integer:CLeadStatus:core/class/cleadstatus.class.php', 'label' => 'OpportunityStatusShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 75),
312  'opp_percent' => array('type' => 'double(5,2)', 'label' => 'OpportunityProbabilityShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 80),
313  'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 85, 'searchall' => 1),
314  'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 90, 'searchall' => 1),
315  'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 95),
316  'date_close' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => 0, 'position' => 105),
317  'fk_user_close' => array('type' => 'integer', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => 0, 'position' => 110),
318  'opp_amount' => array('type' => 'double(24,8)', 'label' => 'OpportunityAmountShort', 'enabled' => 1, 'visible' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position' => 115),
319  'budget_amount' => array('type' => 'double(24,8)', 'label' => 'Budget', 'enabled' => 1, 'visible' => -1, 'position' => 119),
320  'usage_opportunity' => array('type' => 'integer', 'label' => 'UsageOpportunity', 'enabled' => 1, 'visible' => -1, 'position' => 130),
321  'usage_task' => array('type' => 'integer', 'label' => 'UsageTasks', 'enabled' => 1, 'visible' => -1, 'position' => 135),
322  'usage_bill_time' => array('type' => 'integer', 'label' => 'UsageBillTimeShort', 'enabled' => 1, 'visible' => -1, 'position' => 140),
323  'usage_organize_event' => array('type' => 'integer', 'label' => 'UsageOrganizeEvent', 'enabled' => 1, 'visible' => -1, 'position' => 145),
324  // Properties for event organization
325  'date_start_event' => array('type' => 'date', 'label' => 'DateStartEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 200),
326  'date_end_event' => array('type' => 'date', 'label' => 'DateEndEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 201),
327  'location' => array('type' => 'text', 'label' => 'Location', 'enabled' => 1, 'visible' => 3, 'position' => 55, 'searchall' => 202),
328  'accept_conference_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestConf', 'enabled' => 1, 'visible' => -1, 'position' => 210),
329  'accept_booth_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestBooth', 'enabled' => 1, 'visible' => -1, 'position' => 211),
330  'price_registration' => array('type' => 'double(24,8)', 'label' => 'PriceOfRegistration', 'enabled' => 1, 'visible' => -1, 'position' => 212),
331  'price_booth' => array('type' => 'double(24,8)', 'label' => 'PriceOfBooth', 'enabled' => 1, 'visible' => -1, 'position' => 215),
332  'max_attendees' => array('type' => 'integer', 'label' => 'MaxNbOfAttendees', 'enabled' => 1, 'visible' => -1, 'position' => 215),
333  // Generic
334  'datec' => array('type' => 'datetime', 'label' => 'DateCreationShort', 'enabled' => 1, 'visible' => -2, 'position' => 400),
335  'tms' => array('type' => 'timestamp', 'label' => 'DateModificationShort', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 405),
336  'fk_user_creat' => array('type' => 'integer', 'label' => 'UserCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 410),
337  'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModification', 'enabled' => 1, 'visible' => 0, 'position' => 415),
338  'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 420),
339  'email_msgid' => array('type' => 'varchar(255)', 'label' => 'EmailMsgID', 'enabled' => 1, 'visible' => -1, 'position' => 450, 'help' => 'EmailMsgIDWhenSourceisEmail', 'csslist' => 'tdoverflowmax125'),
340  'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'alias' => 'status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 500, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Closed')),
341  );
342  // END MODULEBUILDER PROPERTIES
343 
347  const STATUS_DRAFT = 0;
348 
352  const STATUS_VALIDATED = 1;
353 
357  const STATUS_CLOSED = 2;
358 
359 
365  public function __construct($db)
366  {
367  global $conf;
368 
369  $this->db = $db;
370 
371  $this->ismultientitymanaged = 1;
372  $this->isextrafieldmanaged = 1;
373 
374  $this->labelStatusShort = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
375  $this->labelStatus = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
376 
377  global $conf;
378 
379  if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID')) {
380  $this->fields['rowid']['visible'] = 0;
381  }
382 
383  if (!getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
384  $this->fields['fk_opp_status']['enabled'] = 0;
385  $this->fields['opp_percent']['enabled'] = 0;
386  $this->fields['opp_amount']['enabled'] = 0;
387  $this->fields['usage_opportunity']['enabled'] = 0;
388  }
389 
390  if (getDolGlobalString('PROJECT_HIDE_TASKS')) {
391  $this->fields['usage_bill_time']['visible'] = 0;
392  $this->fields['usage_task']['visible'] = 0;
393  }
394 
395  if (empty($conf->eventorganization->enabled)) {
396  $this->fields['usage_organize_event']['visible'] = 0;
397  $this->fields['accept_conference_suggestions']['enabled'] = 0;
398  $this->fields['accept_booth_suggestions']['enabled'] = 0;
399  $this->fields['price_registration']['enabled'] = 0;
400  $this->fields['price_booth']['enabled'] = 0;
401  $this->fields['max_attendees']['enabled'] = 0;
402  }
403  }
404 
412  public function create($user, $notrigger = 0)
413  {
414  $error = 0;
415  $ret = 0;
416 
417  $now = dol_now();
418 
419  // Clean parameters
420  $this->note_private = dol_substr($this->note_private, 0, 65535);
421  $this->note_public = dol_substr($this->note_public, 0, 65535);
422 
423  // Check parameters
424  if (!trim($this->ref)) {
425  $this->error = 'ErrorFieldsRequired';
426  dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
427  return -1;
428  }
429  if (getDolGlobalString('PROJECT_THIRDPARTY_REQUIRED') && !($this->socid > 0)) {
430  $this->error = 'ErrorFieldsRequired';
431  dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
432  return -1;
433  }
434 
435  // Create project
436  $this->db->begin();
437 
438  $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
439  $sql .= "ref";
440  $sql .= ", fk_project";
441  $sql .= ", title";
442  $sql .= ", description";
443  $sql .= ", fk_soc";
444  $sql .= ", fk_user_creat";
445  $sql .= ", fk_statut";
446  $sql .= ", fk_opp_status";
447  $sql .= ", opp_percent";
448  $sql .= ", public";
449  $sql .= ", datec";
450  $sql .= ", dateo";
451  $sql .= ", datee";
452  $sql .= ", opp_amount";
453  $sql .= ", budget_amount";
454  $sql .= ", usage_opportunity";
455  $sql .= ", usage_task";
456  $sql .= ", usage_bill_time";
457  $sql .= ", usage_organize_event";
458  $sql .= ", accept_conference_suggestions";
459  $sql .= ", accept_booth_suggestions";
460  $sql .= ", price_registration";
461  $sql .= ", price_booth";
462  $sql .= ", max_attendees";
463  $sql .= ", date_start_event";
464  $sql .= ", date_end_event";
465  $sql .= ", location";
466  $sql .= ", email_msgid";
467  $sql .= ", note_private";
468  $sql .= ", note_public";
469  $sql .= ", entity";
470  $sql .= ", ip";
471  $sql .= ") VALUES (";
472  $sql .= "'".$this->db->escape($this->ref)."'";
473  $sql .= ", ".($this->fk_project ? ((int) $this->fk_project) : "null");
474  $sql .= ", '".$this->db->escape($this->title)."'";
475  $sql .= ", '".$this->db->escape($this->description)."'";
476  $sql .= ", ".($this->socid > 0 ? $this->socid : "null");
477  $sql .= ", ".((int) $user->id);
478  $sql .= ", ".(is_numeric($this->statut) ? ((int) $this->statut) : '0');
479  $sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
480  $sql .= ", ".(is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
481  $sql .= ", ".($this->public ? 1 : 0);
482  $sql .= ", '".$this->db->idate($now)."'";
483  $sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
484  $sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
485  $sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
486  $sql .= ", ".(strcmp((string) $this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
487  $sql .= ", ".($this->usage_opportunity ? 1 : 0);
488  $sql .= ", ".($this->usage_task ? 1 : 0);
489  $sql .= ", ".($this->usage_bill_time ? 1 : 0);
490  $sql .= ", ".($this->usage_organize_event ? 1 : 0);
491  $sql .= ", ".($this->accept_conference_suggestions ? 1 : 0);
492  $sql .= ", ".($this->accept_booth_suggestions ? 1 : 0);
493  $sql .= ", ".(strcmp((string) $this->price_registration, '') ? price2num($this->price_registration) : 'null');
494  $sql .= ", ".(strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : 'null');
495  $sql .= ", ".(strcmp((string) $this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
496  $sql .= ", ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
497  $sql .= ", ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
498  $sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : 'null');
499  $sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
500  $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
501  $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
502  $sql .= ", ".setEntity($this);
503  $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
504  $sql .= ")";
505 
506  dol_syslog(get_class($this)."::create", LOG_DEBUG);
507  $resql = $this->db->query($sql);
508  if ($resql) {
509  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
510  $ret = $this->id;
511 
512  if (!$notrigger) {
513  // Call trigger
514  $result = $this->call_trigger('PROJECT_CREATE', $user);
515  if ($result < 0) {
516  $error++;
517  }
518  // End call triggers
519  }
520  } else {
521  $this->error = $this->db->lasterror();
522  $error++;
523  }
524 
525  // Update extrafield
526  if (!$error) {
527  $result = $this->insertExtraFields();
528  if ($result < 0) {
529  $error++;
530  }
531  }
532 
533  if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
534  $res = $this->setValid($user);
535  if ($res < 0) {
536  $error++;
537  }
538  }
539 
540  if (!$error) {
541  $this->db->commit();
542  return $ret;
543  } else {
544  $this->db->rollback();
545  return -1;
546  }
547  }
548 
556  public function update($user, $notrigger = 0)
557  {
558  global $langs, $conf;
559 
560  $error = 0;
561 
562  // Clean parameters
563  $this->title = trim($this->title);
564  $this->description = trim($this->description);
565  if ($this->opp_amount < 0) {
566  $this->opp_amount = '';
567  }
568  if ($this->opp_percent < 0) {
569  $this->opp_percent = '';
570  }
571  if ($this->date_end && $this->date_end < $this->date_start) {
572  $this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
573  $this->errors[] = $this->error;
574  $this->db->rollback();
575  dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
576  return -3;
577  }
578 
579  $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
580 
581  if (dol_strlen(trim($this->ref)) > 0) {
582  $this->db->begin();
583 
584  $sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
585  $sql .= " ref='".$this->db->escape($this->ref)."'";
586  $sql .= ", fk_project=".($this->fk_project ? ((int) $this->fk_project) : "null");
587  $sql .= ", title = '".$this->db->escape($this->title)."'";
588  $sql .= ", description = '".$this->db->escape($this->description)."'";
589  $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
590  $sql .= ", fk_statut = ".((int) $this->statut);
591  $sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
592  $sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
593  $sql .= ", public = ".($this->public ? 1 : 0);
594  $sql .= ", datec = ".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
595  $sql .= ", dateo = ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
596  $sql .= ", datee = ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
597  $sql .= ", date_close = ".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
598  $sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
599  $sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
600  $sql .= ", fk_user_close = ".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
601  $sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
602  $sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
603  $sql .= ", fk_user_modif = ".$user->id;
604  $sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
605  $sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
606  $sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
607  $sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
608  $sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0);
609  $sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0);
610  $sql .= ", price_registration = ".(isset($this->price_registration) && strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
611  $sql .= ", price_booth = ".(isset($this->price_booth) && strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : "null");
612  $sql .= ", max_attendees = ".(strcmp((string) $this->max_attendees, '') ? (int) $this->max_attendees : "null");
613  $sql .= ", date_start_event = ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
614  $sql .= ", date_end_event = ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
615  $sql .= ", location = '".$this->db->escape($this->location)."'";
616  $sql .= ", entity = ".((int) $this->entity);
617  $sql .= " WHERE rowid = ".((int) $this->id);
618 
619  dol_syslog(get_class($this)."::update", LOG_DEBUG);
620  $resql = $this->db->query($sql);
621  if ($resql) {
622  // Update extrafield
623  if (!$error) {
624  $result = $this->insertExtraFields();
625  if ($result < 0) {
626  $error++;
627  }
628  }
629 
630  if (!$error && !$notrigger) {
631  // Call trigger
632  $result = $this->call_trigger('PROJECT_MODIFY', $user);
633  if ($result < 0) {
634  $error++;
635  }
636  // End call triggers
637  }
638 
639  if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
640  // We remove directory
641  if ($conf->project->dir_output) {
642  $olddir = $conf->project->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
643  $newdir = $conf->project->dir_output."/".dol_sanitizeFileName($this->ref);
644  if (file_exists($olddir)) {
645  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
646  $res = @rename($olddir, $newdir);
647  if (!$res) {
648  $langs->load("errors");
649  $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
650  $error++;
651  }
652  }
653  }
654  }
655  if (!$error) {
656  $this->db->commit();
657  $result = 1;
658  } else {
659  $this->db->rollback();
660  $result = -1;
661  }
662  } else {
663  $this->error = $this->db->lasterror();
664  $this->errors[] = $this->error;
665  $this->db->rollback();
666  if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
667  $result = -4;
668  } else {
669  $result = -2;
670  }
671  dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
672  }
673  } else {
674  dol_syslog(get_class($this)."::update ref null");
675  $result = -1;
676  }
677 
678  return $result;
679  }
680 
690  public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
691  {
692  if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
693  dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING);
694  return -1;
695  }
696 
697  $sql = "SELECT rowid, entity, fk_project, ref, title, description, public, datec, opp_amount, budget_amount,";
698  $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,";
699  $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
700  $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location, extraparams";
701  $sql .= " FROM ".MAIN_DB_PREFIX."projet";
702  if (!empty($id)) {
703  $sql .= " WHERE rowid = ".((int) $id);
704  } else {
705  $sql .= " WHERE entity IN (".getEntity('project').")";
706  if (!empty($ref)) {
707  $sql .= " AND ref = '".$this->db->escape($ref)."'";
708  } elseif (!empty($ref_ext)) {
709  $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
710  } else {
711  $sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
712  }
713  }
714 
715  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
716  $resql = $this->db->query($sql);
717  if ($resql) {
718  $num_rows = $this->db->num_rows($resql);
719 
720  if ($num_rows) {
721  $obj = $this->db->fetch_object($resql);
722 
723  $this->id = $obj->rowid;
724  $this->entity = $obj->entity;
725  $this->ref = $obj->ref;
726  $this->fk_project = $obj->fk_project;
727  $this->title = $obj->title;
728  $this->description = $obj->description;
729  $this->date_c = $this->db->jdate($obj->datec);
730  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
731  $this->date_m = $this->db->jdate($obj->tms);
732  $this->datem = $this->db->jdate($obj->tms); // TODO deprecated
733  $this->date_start = $this->db->jdate($obj->date_start);
734  $this->date_end = $this->db->jdate($obj->date_end);
735  $this->date_close = $this->db->jdate($obj->date_close);
736  $this->note_private = $obj->note_private;
737  $this->note_public = $obj->note_public;
738  $this->socid = $obj->fk_soc;
739  $this->user_author_id = $obj->fk_user_creat;
740  $this->user_modification_id = $obj->fk_user_modif;
741  $this->user_closing_id = $obj->fk_user_close;
742  $this->public = $obj->public;
743  $this->statut = $obj->status; // deprecated
744  $this->status = $obj->status;
745  $this->opp_status = $obj->fk_opp_status;
746  $this->opp_amount = $obj->opp_amount;
747  $this->opp_percent = $obj->opp_percent;
748  $this->budget_amount = $obj->budget_amount;
749  $this->model_pdf = $obj->model_pdf;
750  $this->usage_opportunity = (int) $obj->usage_opportunity;
751  $this->usage_task = (int) $obj->usage_task;
752  $this->usage_bill_time = (int) $obj->usage_bill_time;
753  $this->usage_organize_event = (int) $obj->usage_organize_event;
754  $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
755  $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
756  $this->price_registration = $obj->price_registration;
757  $this->price_booth = $obj->price_booth;
758  $this->max_attendees = $obj->max_attendees;
759  $this->date_start_event = $this->db->jdate($obj->date_start_event);
760  $this->date_end_event = $this->db->jdate($obj->date_end_event);
761  $this->location = $obj->location;
762  $this->email_msgid = $obj->email_msgid;
763  $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
764 
765  $this->db->free($resql);
766 
767  // Retrieve all extrafield
768  // fetch optionals attributes and labels
769  $this->fetch_optionals();
770 
771  return 1;
772  }
773 
774  $this->db->free($resql);
775 
776  return 0;
777  } else {
778  $this->error = $this->db->lasterror();
779  $this->errors[] = $this->db->lasterror();
780  return -1;
781  }
782  }
783 
792  public function fetchAndSetSubstitution($id, $key, $fetched = false)
793  {
794  $substitution = '';
795 
796  if ($fetched === false) {
797  $res = $this->fetch($id);
798  if ($res > 0) {
799  $fetched = true;
800  }
801  }
802 
803  if ($fetched === true) {
804  if ($key == '__PROJECT_ID__') {
805  $substitution = ($this->id > 0 ? $this->id : '');
806  } elseif ($key == '__PROJECT_REF__') {
807  $substitution = $this->ref;
808  } elseif ($key == '__PROJECT_NAME__') {
809  $substitution = $this->title;
810  }
811  }
812 
813  return $substitution;
814  }
815 
816  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
828  public function get_element_list($type, $tablename, $datefieldname = '', $date_start = null, $date_end = null, $projectkey = 'fk_projet')
829  {
830  // phpcs:enable
831 
832  global $hookmanager;
833 
834  $elements = array();
835 
836  if ($this->id <= 0) {
837  return $elements;
838  }
839 
840  $ids = $this->id;
841 
842  if ($type == 'agenda') {
843  $sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity('agenda').")";
844  } elseif ($type == 'expensereport') {
845  $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).")";
846  } elseif ($type == 'project_task') {
847  $sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize($ids).")";
848  } elseif ($type == 'element_time') { // Case we want to duplicate line foreach user
849  $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet IN (".$this->db->sanitize($ids).")";
850  } elseif ($type == 'stocktransfer_stocktransfer') {
851  $sql = "SELECT ms.rowid, ms.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."stocktransfer_stocktransfer 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";
852  } elseif ($type == 'loan') {
853  $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).")";
854  } else {
855  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity($type).")";
856  }
857 
858  if (isDolTms($date_start) && $type == 'loan') {
859  $sql .= " AND (dateend > '".$this->db->idate($date_start)."' OR dateend IS NULL)";
860  } elseif (isDolTms($date_start) && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
861  if (empty($datefieldname) && !empty($this->table_element_date)) {
862  $datefieldname = $this->table_element_date;
863  }
864  if (empty($datefieldname)) {
865  return 'Error this object has no date field defined';
866  }
867  $sql .= " AND (".$datefieldname." >= '".$this->db->idate($date_start)."' OR ".$datefieldname." IS NULL)";
868  }
869 
870  if (isDolTms($date_end) && $type == 'loan') {
871  $sql .= " AND (datestart < '".$this->db->idate($date_end)."' OR datestart IS NULL)";
872  } elseif (isDolTms($date_end) && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
873  if (empty($datefieldname) && !empty($this->table_element_date)) {
874  $datefieldname = $this->table_element_date;
875  }
876  if (empty($datefieldname)) {
877  return 'Error this object has no date field defined';
878  }
879  $sql .= " AND (".$datefieldname." <= '".$this->db->idate($date_end)."' OR ".$datefieldname." IS NULL)";
880  }
881 
882  $parameters = array(
883  'sql' => $sql,
884  'type' => $type,
885  'tablename' => $tablename,
886  'datefieldname' => $datefieldname,
887  'dates' => $date_start,
888  'datee' => $date_end,
889  'fk_projet' => $projectkey,
890  'ids' => $ids,
891  );
892  $reshook = $hookmanager->executeHooks('getElementList', $parameters);
893  if ($reshook > 0) {
894  $sql = $hookmanager->resPrint;
895  } else {
896  $sql .= $hookmanager->resPrint;
897  }
898 
899  if (!$sql) {
900  return -1;
901  }
902 
903  //print $sql;
904  dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
905  $result = $this->db->query($sql);
906  if ($result) {
907  $nump = $this->db->num_rows($result);
908  if ($nump) {
909  $i = 0;
910  while ($i < $nump) {
911  $obj = $this->db->fetch_object($result);
912 
913  $elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
914 
915  $i++;
916  }
917  $this->db->free($result);
918  }
919 
920  /* Return array even if empty*/
921  return $elements;
922  } else {
923  dol_print_error($this->db);
924  }
925  return -1;
926  }
927 
935  public function delete($user, $notrigger = 0)
936  {
937  global $langs, $conf;
938  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
939 
940  $error = 0;
941 
942  $this->db->begin();
943 
944  if (!$error) {
945  // Delete linked contacts
946  $res = $this->delete_linked_contact();
947  if ($res < 0) {
948  $this->error = 'ErrorFailToDeleteLinkedContact';
949  //$error++;
950  $this->db->rollback();
951  return 0;
952  }
953  }
954 
955  // Set fk_projet into elements to null
956  $listoftables = array(
957  'propal' => 'fk_projet', 'commande' => 'fk_projet', 'facture' => 'fk_projet',
958  'supplier_proposal' => 'fk_projet', 'commande_fournisseur' => 'fk_projet', 'facture_fourn' => 'fk_projet',
959  'expensereport_det' => 'fk_projet', 'contrat' => 'fk_projet',
960  'fichinter' => 'fk_projet',
961  'don' => array('field' => 'fk_projet', 'module' => 'don'),
962  'actioncomm' => 'fk_project',
963  'mrp_mo' => 'fk_project',
964  'entrepot' => 'fk_project'
965  );
966  foreach ($listoftables as $key => $value) {
967  if (is_array($value)) {
968  if (!isModEnabled($value['module'])) {
969  continue;
970  }
971  $fieldname = $value['field'];
972  } else {
973  $fieldname = $value;
974  }
975  $sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$fieldname." = NULL where ".$fieldname." = ".((int) $this->id);
976 
977  $resql = $this->db->query($sql);
978  if (!$resql) {
979  $this->errors[] = $this->db->lasterror();
980  $error++;
981  break;
982  }
983  }
984 
985  // Remove linked categories.
986  if (!$error) {
987  $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
988  $sql .= " WHERE fk_project = ".((int) $this->id);
989 
990  $result = $this->db->query($sql);
991  if (!$result) {
992  $error++;
993  $this->errors[] = $this->db->lasterror();
994  }
995  }
996 
997  // Fetch tasks
998  $this->getLinesArray($user, 0);
999 
1000  // Delete tasks
1001  $ret = $this->deleteTasks($user);
1002  if ($ret < 0) {
1003  $error++;
1004  }
1005 
1006 
1007  // Delete all child tables
1008  if (!$error) {
1009  $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
1010  foreach ($elements as $table) {
1011  if (!$error) {
1012  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
1013  $sql .= " WHERE fk_project = ".((int) $this->id);
1014 
1015  $result = $this->db->query($sql);
1016  if (!$result) {
1017  $error++;
1018  $this->errors[] = $this->db->lasterror();
1019  }
1020  }
1021  }
1022  }
1023 
1024  if (!$error) {
1025  $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
1026  $sql .= " WHERE fk_object = ".((int) $this->id);
1027 
1028  $resql = $this->db->query($sql);
1029  if (!$resql) {
1030  $this->errors[] = $this->db->lasterror();
1031  $error++;
1032  }
1033  }
1034 
1035  // Delete project
1036  if (!$error) {
1037  $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
1038  $sql .= " WHERE rowid=".((int) $this->id);
1039 
1040  $resql = $this->db->query($sql);
1041  if (!$resql) {
1042  $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
1043  $error++;
1044  }
1045  }
1046 
1047 
1048 
1049  if (empty($error)) {
1050  // We remove directory
1051  $projectref = dol_sanitizeFileName($this->ref);
1052  if ($conf->project->dir_output) {
1053  $dir = $conf->project->dir_output."/".$projectref;
1054  if (file_exists($dir)) {
1055  $res = @dol_delete_dir_recursive($dir);
1056  if (!$res) {
1057  $this->errors[] = 'ErrorFailToDeleteDir';
1058  $error++;
1059  }
1060  }
1061  }
1062 
1063  if (!$notrigger) {
1064  // Call trigger
1065  $result = $this->call_trigger('PROJECT_DELETE', $user);
1066 
1067  if ($result < 0) {
1068  $error++;
1069  }
1070  // End call triggers
1071  }
1072  }
1073 
1074  if (empty($error)) {
1075  $this->db->commit();
1076  return 1;
1077  } else {
1078  foreach ($this->errors as $errmsg) {
1079  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1080  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1081  }
1082  dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
1083  $this->db->rollback();
1084  return -1;
1085  }
1086  }
1087 
1096  public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
1097  {
1098  if ($this->id <= 0) {
1099  return 0;
1100  }
1101 
1102  if ($type == 'agenda') {
1103  $sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".((int) $this->id)." AND entity IN (".getEntity('agenda').")";
1104  } elseif ($type == 'expensereport') {
1105  $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);
1106  } elseif ($type == 'project_task') {
1107  $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id);
1108  } elseif ($type == 'element_time') { // Case we want to duplicate line foreach user
1109  $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet = ".((int) $this->id);
1110  } elseif ($type == 'stock_mouvement') {
1111  $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";
1112  } elseif ($type == 'loan') {
1113  $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);
1114  } else {
1115  $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".((int) $this->id)." AND entity IN (".getEntity($type).")";
1116  }
1117 
1118  $result = $this->db->query($sql);
1119 
1120  if (!$result) {
1121  return 0;
1122  }
1123 
1124  $obj = $this->db->fetch_object($result);
1125 
1126  $this->db->free($result);
1127 
1128  return $obj->nb;
1129  }
1130 
1137  public function deleteTasks($user)
1138  {
1139  $countTasks = count($this->lines);
1140  $deleted = false;
1141  if ($countTasks) {
1142  foreach ($this->lines as $task) {
1143  if ($task->hasChildren() <= 0) { // If there is no children (or error to detect them)
1144  $deleted = true;
1145  $ret = $task->delete($user);
1146  if ($ret <= 0) {
1147  $this->errors[] = $this->db->lasterror();
1148  return -1;
1149  }
1150  }
1151  }
1152  }
1153  $this->getLinesArray($user);
1154  if ($deleted && count($this->lines) < $countTasks) {
1155  if (count($this->lines)) {
1156  $this->deleteTasks($this->lines);
1157  }
1158  }
1159 
1160  return 1;
1161  }
1162 
1170  public function setValid($user, $notrigger = 0)
1171  {
1172  global $langs, $conf;
1173 
1174  $error = 0;
1175 
1176  // Protection
1177  if ($this->status == self::STATUS_VALIDATED) {
1178  dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
1179  return 0;
1180  }
1181 
1182  // Check parameters
1183  if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
1184  $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1185  return -1;
1186  }
1187 
1188  $this->db->begin();
1189 
1190  $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1191  $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1192  $sql .= " WHERE rowid = ".((int) $this->id);
1193  //$sql .= " AND entity = ".((int) $conf->entity); // Disabled, when we use the ID for the where, we must not add any other search condition
1194 
1195  dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
1196  $resql = $this->db->query($sql);
1197  if ($resql) {
1198  // Call trigger
1199  if (empty($notrigger)) {
1200  $result = $this->call_trigger('PROJECT_VALIDATE', $user);
1201  if ($result < 0) {
1202  $error++;
1203  }
1204  // End call triggers
1205  }
1206 
1207  if (!$error) {
1208  $this->statut = 1;
1209  $this->db->commit();
1210  return 1;
1211  } else {
1212  $this->db->rollback();
1213  $this->error = implode(',', $this->errors);
1214  dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
1215  return -1;
1216  }
1217  } else {
1218  $this->db->rollback();
1219  $this->error = $this->db->lasterror();
1220  return -1;
1221  }
1222  }
1223 
1230  public function setClose($user)
1231  {
1232  $now = dol_now();
1233 
1234  $error = 0;
1235 
1236  if ($this->statut != self::STATUS_CLOSED) {
1237  $this->db->begin();
1238 
1239  $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1240  $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'";
1241  $sql .= " WHERE rowid = ".((int) $this->id);
1242  $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1243 
1244  if (getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
1245  // TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1246  }
1247 
1248  dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
1249  $resql = $this->db->query($sql);
1250  if ($resql) {
1251  // Call trigger
1252  $result = $this->call_trigger('PROJECT_CLOSE', $user);
1253  if ($result < 0) {
1254  $error++;
1255  }
1256  // End call triggers
1257 
1258  if (!$error) {
1259  $this->statut = 2;
1260  $this->db->commit();
1261  return 1;
1262  } else {
1263  $this->db->rollback();
1264  $this->error = implode(',', $this->errors);
1265  dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
1266  return -1;
1267  }
1268  } else {
1269  $this->db->rollback();
1270  $this->error = $this->db->lasterror();
1271  return -1;
1272  }
1273  }
1274 
1275  return 0;
1276  }
1277 
1284  public function getLibStatut($mode = 0)
1285  {
1286  return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
1287  }
1288 
1289  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1297  public function LibStatut($status, $mode = 0)
1298  {
1299  // phpcs:enable
1300  global $langs;
1301 
1302  if (is_null($status)) {
1303  return '';
1304  }
1305 
1306  $statustrans = array(
1307  0 => 'status0',
1308  1 => 'status4',
1309  2 => 'status6',
1310  );
1311 
1312  $statusClass = 'status0';
1313  if (!empty($statustrans[$status])) {
1314  $statusClass = $statustrans[$status];
1315  }
1316 
1317  return dolGetStatus($langs->transnoentitiesnoconv($this->labelStatus[$status]), $langs->transnoentitiesnoconv($this->labelStatusShort[$status]), '', $statusClass, $mode);
1318  }
1319 
1327  public function getTooltipContentArray($params)
1328  {
1329  global $conf, $langs;
1330 
1331  $langs->load('projects');
1332  $option = $params['option'] ?? '';
1333  $moreinpopup = $params['moreinpopup'] ?? '';
1334 
1335  $datas = [];
1336  if ($option != 'nolink') {
1337  $datas['picto'] = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1338  }
1339  if (isset($this->status)) {
1340  $datas['picto'] .= ' '.$this->getLibStatut(5);
1341  }
1342  $datas['ref'] = (isset($datas['picto']) ? '<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
1343  $datas['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
1344  if (isset($this->public)) {
1345  $datas['visibility'] = '<br><b>'.$langs->trans("Visibility").":</b> ";
1346  $datas['visibility'] .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
1347  }
1348  if (!empty($this->thirdparty_name)) {
1349  $datas['thirdparty'] = '<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
1350  }
1351  if (!empty($this->date_start)) {
1352  $datas['datestart'] = '<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
1353  }
1354  if (!empty($this->date_end)) {
1355  $datas['dateend'] = '<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
1356  }
1357  if ($moreinpopup) {
1358  $datas['moreinpopup'] = '<br>'.$moreinpopup;
1359  }
1360 
1361  return $datas;
1362  }
1363 
1378  public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '', $save_pageforbacktolist = '')
1379  {
1380  global $conf, $langs, $user, $hookmanager;
1381 
1382  if (!empty($conf->dol_no_mouse_hover)) {
1383  $notooltip = 1; // Force disable tooltips
1384  }
1385 
1386  $result = '';
1387  if (getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB')) {
1388  $option = getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB');
1389  }
1390  $params = [
1391  'id' => $this->id,
1392  'objecttype' => $this->element,
1393  'moreinpopup' => $moreinpopup,
1394  'option' => $option,
1395  ];
1396  $classfortooltip = 'classfortooltip';
1397  $dataparams = '';
1398  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1399  $classfortooltip = 'classforajaxtooltip';
1400  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1401  $label = '';
1402  } else {
1403  $label = implode($this->getTooltipContentArray($params));
1404  }
1405 
1406  $url = '';
1407  if ($option != 'nolink') {
1408  if (preg_match('/\.php$/', $option)) {
1409  $url = dol_buildpath($option, 1).'?id='.$this->id;
1410  } elseif ($option == 'task') {
1411  $url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1412  } elseif ($option == 'preview') {
1413  $url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1414  } elseif ($option == 'eventorganization') {
1415  $url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
1416  } else {
1417  $url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1418  }
1419  // Add param to save lastsearch_values or not
1420  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1421  if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1422  $add_save_lastsearch_values = 1;
1423  }
1424  if ($add_save_lastsearch_values) {
1425  $url .= '&save_lastsearch_values=1';
1426  }
1427  $add_save_backpagefor = ($save_pageforbacktolist ? 1 : 0);
1428  if ($add_save_backpagefor) {
1429  $url .= "&save_pageforbacktolist=".urlencode($save_pageforbacktolist);
1430  }
1431  }
1432 
1433  $linkclose = '';
1434  if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1435  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1436  $label = $langs->trans("ShowProject");
1437  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1438  }
1439  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1440  $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1441  } else {
1442  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1443  }
1444 
1445  $picto = 'projectpub';
1446  if (!$this->public) {
1447  $picto = 'project';
1448  }
1449 
1450  $linkstart = '<a href="'.$url.'"';
1451  $linkstart .= $linkclose.'>';
1452  $linkend = '</a>';
1453 
1454  $result .= $linkstart;
1455  if ($withpicto) {
1456  $result .= img_object(($notooltip ? '' : $label), $picto, 'class="pictofixedwidth em088"', 0, 0, $notooltip ? 0 : 1);
1457  }
1458  if ($withpicto != 2) {
1459  $result .= $this->ref;
1460  }
1461  $result .= $linkend;
1462  if ($withpicto != 2) {
1463  $result .= (($addlabel && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1464  }
1465 
1466  global $action;
1467  $hookmanager->initHooks(array('projectdao'));
1468  $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1469  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1470  if ($reshook > 0) {
1471  $result = $hookmanager->resPrint;
1472  } else {
1473  $result .= $hookmanager->resPrint;
1474  }
1475 
1476  return $result;
1477  }
1478 
1486  public function initAsSpecimen()
1487  {
1488  global $user, $langs, $conf;
1489 
1490  $now = dol_now();
1491 
1492  // Initialise parameters
1493  $this->id = 0;
1494  $this->ref = 'SPECIMEN';
1495  $this->entity = $conf->entity;
1496  $this->specimen = 1;
1497  $this->socid = 1;
1498  $this->date_c = $now;
1499  $this->date_m = $now;
1500  $this->date_start = $now;
1501  $this->date_end = $now + (3600 * 24 * 365);
1502  $this->note_public = 'SPECIMEN';
1503  $this->note_private = 'Private Note';
1504  $this->fk_project = 0;
1505  $this->opp_amount = 20000;
1506  $this->budget_amount = 10000;
1507 
1508  $this->usage_opportunity = 1;
1509  $this->usage_task = 1;
1510  $this->usage_bill_time = 1;
1511  $this->usage_organize_event = 1;
1512 
1513  /*
1514  $nbp = mt_rand(1, 9);
1515  $xnbp = 0;
1516  while ($xnbp < $nbp)
1517  {
1518  $line = new Task($this->db);
1519  $line->fk_project = 0;
1520  $line->label = $langs->trans("Label") . " " . $xnbp;
1521  $line->description = $langs->trans("Description") . " " . $xnbp;
1522 
1523  $this->lines[]=$line;
1524  $xnbp++;
1525  }
1526  */
1527 
1528  return 1;
1529  }
1530 
1538  public function restrictedProjectArea(User $user, $mode = 'read')
1539  {
1540  // To verify role of users
1541  $userAccess = 0;
1542  if (($mode == 'read' && $user->hasRight('projet', 'all', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'all', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'all', 'supprimer'))) {
1543  $userAccess = 1;
1544  } elseif ($this->public && (($mode == 'read' && $user->hasRight('projet', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'supprimer')))) {
1545  $userAccess = 1;
1546  } else { // No access due to permission to read all projects, so we check if we are a contact of project
1547  foreach (array('internal', 'external') as $source) {
1548  $userRole = $this->liste_contact(4, $source);
1549  $num = count($userRole);
1550 
1551  $nblinks = 0;
1552  while ($nblinks < $num) {
1553  if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) { // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1554  if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1555  $userAccess++;
1556  }
1557  if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1558  $userAccess++;
1559  }
1560  if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1561  $userAccess++;
1562  }
1563  }
1564  if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) { // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1565  if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1566  $userAccess++;
1567  }
1568  if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1569  $userAccess++;
1570  }
1571  if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1572  $userAccess++;
1573  }
1574  }
1575  $nblinks++;
1576  }
1577  }
1578  //if (empty($nblinks)) // If nobody has permission, we grant creator
1579  //{
1580  // if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1581  // {
1582  // $userAccess = 1;
1583  // }
1584  //}
1585  }
1586 
1587  return ($userAccess ? $userAccess : -1);
1588  }
1589 
1600  public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1601  {
1602  $projects = array();
1603  $temp = array();
1604 
1605  $sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1606  $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1607  if ($mode == 0) {
1608  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
1609  } elseif ($mode == 1) {
1610  $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1611  } elseif ($mode == 2) {
1612  // No filter. Use this if user has permission to see all project
1613  }
1614  $sql .= " WHERE p.entity IN (".getEntity('project').")";
1615  // Internal users must see project he is contact to even if project linked to a third party he can't see.
1616  //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).")";
1617  if ($socid > 0) {
1618  $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1619  }
1620 
1621  // Get id of types of contacts for projects (This list never contains a lot of elements)
1622  $listofprojectcontacttype = array();
1623  $sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1624  $sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1625  $sql2 .= " AND ctc.source = 'internal'";
1626  $resql = $this->db->query($sql2);
1627  if ($resql) {
1628  while ($obj = $this->db->fetch_object($resql)) {
1629  $listofprojectcontacttype[$obj->rowid] = $obj->code;
1630  }
1631  } else {
1632  dol_print_error($this->db);
1633  }
1634  if (count($listofprojectcontacttype) == 0) {
1635  $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1636  }
1637 
1638  if ($mode == 0) {
1639  $sql .= " AND ( p.public = 1";
1640  $sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))).")";
1641  $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1642  $sql .= " )";
1643  } elseif ($mode == 1) {
1644  $sql .= " AND ec.element_id = p.rowid";
1645  $sql .= " AND (";
1646  $sql .= " ( ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))).")";
1647  $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1648  $sql .= " )";
1649  } elseif ($mode == 2) {
1650  // No filter. Use this if user has permission to see all project
1651  }
1652 
1653  $sql .= $filter;
1654  //print $sql;
1655 
1656  $resql = $this->db->query($sql);
1657  if ($resql) {
1658  $num = $this->db->num_rows($resql);
1659  $i = 0;
1660  while ($i < $num) {
1661  $row = $this->db->fetch_row($resql);
1662  $projects[$row[0]] = $row[1];
1663  $temp[] = $row[0];
1664  $i++;
1665  }
1666 
1667  $this->db->free($resql);
1668 
1669  if ($list) {
1670  if (empty($temp)) {
1671  return '0';
1672  }
1673  $result = implode(',', $temp);
1674  return $result;
1675  }
1676  } else {
1677  dol_print_error($this->db);
1678  }
1679 
1680  return $projects;
1681  }
1682 
1698  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)
1699  {
1700  global $langs, $conf;
1701 
1702  $error = 0;
1703  $clone_project_id = 0; // For static toolcheck
1704 
1705  dol_syslog("createFromClone clone_contact=".json_encode($clone_contact)." clone_task=".json_encode($clone_task)." clone_project_file=".json_encode($clone_project_file)." clone_note=".json_encode($clone_note)." move_date=".json_encode($move_date), LOG_DEBUG);
1706 
1707  $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1708 
1709  $clone_project = new Project($this->db);
1710 
1711  $clone_project->context['createfromclone'] = 'createfromclone';
1712 
1713  $this->db->begin();
1714 
1715  // Load source object
1716  $clone_project->fetch($fromid);
1717  $clone_project->fetch_optionals();
1718  if ($newthirdpartyid > 0) {
1719  $clone_project->socid = $newthirdpartyid;
1720  }
1721  $clone_project->fetch_thirdparty();
1722 
1723  $orign_dt_start = $clone_project->date_start;
1724  $orign_project_ref = $clone_project->ref;
1725 
1726  $clone_project->id = 0;
1727  if ($move_date) {
1728  $clone_project->date_start = $now;
1729  if (!(empty($clone_project->date_end))) {
1730  $clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1731  }
1732  }
1733 
1734  $clone_project->date_c = $now;
1735 
1736  if (!$clone_note) {
1737  $clone_project->note_private = '';
1738  $clone_project->note_public = '';
1739  }
1740 
1741  //Generate next ref
1742  $defaultref = '';
1743  $obj = !getDolGlobalString('PROJECT_ADDON') ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1744  // Search template files
1745  $file = '';
1746  $classname = '';
1747  $filefound = 0;
1748  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1749  foreach ($dirmodels as $reldir) {
1750  $file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1751  if (file_exists($file)) {
1752  $filefound = 1;
1753  dol_include_once($reldir."core/modules/project/".$obj.'.php');
1754  $modProject = new $obj();
1755  $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1756  break;
1757  }
1758  }
1759  if (is_numeric($defaultref) && $defaultref <= 0) {
1760  $defaultref = '';
1761  }
1762 
1763  $clone_project->ref = $defaultref;
1764  $clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1765 
1766  // Create clone
1767  $result = $clone_project->create($user, $notrigger);
1768 
1769  // Other options
1770  if ($result < 0) {
1771  $this->error .= $clone_project->error;
1772  $error++;
1773  }
1774 
1775  if (!$error) {
1776  //Get the new project id
1777  $clone_project_id = $clone_project->id;
1778 
1779  //Note Update
1780  if (!$clone_note) {
1781  $clone_project->note_private = '';
1782  $clone_project->note_public = '';
1783  } else {
1784  $this->db->begin();
1785  $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1786  if ($res < 0) {
1787  $this->error .= $clone_project->error;
1788  $error++;
1789  $this->db->rollback();
1790  } else {
1791  $this->db->commit();
1792  }
1793 
1794  $this->db->begin();
1795  $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1796  if ($res < 0) {
1797  $this->error .= $clone_project->error;
1798  $error++;
1799  $this->db->rollback();
1800  } else {
1801  $this->db->commit();
1802  }
1803  }
1804 
1805  //Duplicate contact
1806  if ($clone_contact) {
1807  $origin_project = new Project($this->db);
1808  $origin_project->fetch($fromid);
1809 
1810  foreach (array('internal', 'external') as $source) {
1811  $tab = $origin_project->liste_contact(-1, $source);
1812  if (is_array($tab) && count($tab) > 0) {
1813  foreach ($tab as $contacttoadd) {
1814  $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1815  if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1816  $langs->load("errors");
1817  $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1818  $error++;
1819  } else {
1820  if ($clone_project->error != '') {
1821  $this->error .= $clone_project->error;
1822  $error++;
1823  }
1824  }
1825  }
1826  } elseif ($tab < 0) {
1827  $this->error .= $origin_project->error;
1828  $error++;
1829  }
1830  }
1831  }
1832 
1833  //Duplicate file
1834  if ($clone_project_file) {
1835  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1836 
1837  $clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
1838  $ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1839 
1840  if (dol_mkdir($clone_project_dir) >= 0) {
1841  $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1842  foreach ($filearray as $key => $file) {
1843  $rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
1844  if (is_numeric($rescopy) && $rescopy < 0) {
1845  $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1846  $error++;
1847  }
1848  }
1849  } else {
1850  $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1851  $error++;
1852  }
1853  }
1854 
1855  //Duplicate task
1856  if ($clone_task) {
1857  require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1858 
1859  $taskstatic = new Task($this->db);
1860 
1861  // Security check
1862  $socid = 0;
1863  if ($user->socid > 0) {
1864  $socid = $user->socid;
1865  }
1866 
1867  $tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1868 
1869  $tab_conv_child_parent = array();
1870 
1871  // Loop on each task, to clone it
1872  foreach ($tasksarray as $tasktoclone) {
1873  $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_task_parent, $move_date, true, false, $clone_task_file, true, false);
1874  if ($result_clone <= 0) {
1875  $this->error .= $taskstatic->error;
1876  $error++;
1877  } else {
1878  $new_task_id = $result_clone;
1879  $taskstatic->fetch($tasktoclone->id);
1880 
1881  //manage new parent clone task id
1882  // if the current task has child we store the original task id and the equivalent clone task id
1883  if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1884  $tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1885  }
1886  }
1887  }
1888 
1889  //Parse all clone node to be sure to update new parent
1890  $tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1891  foreach ($tasksarray as $task_cloned) {
1892  $taskstatic->fetch($task_cloned->id);
1893  if ($taskstatic->fk_task_parent != 0) {
1894  $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1895  }
1896  $res = $taskstatic->update($user, $notrigger);
1897  if ($result_clone <= 0) {
1898  $this->error .= $taskstatic->error;
1899  $error++;
1900  }
1901  }
1902  }
1903  }
1904 
1905  unset($clone_project->context['createfromclone']);
1906 
1907  if (!$error && $clone_project_id != 0) {
1908  $this->db->commit();
1909  return $clone_project_id;
1910  } else {
1911  $this->db->rollback();
1912  dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1913  return -1;
1914  }
1915  }
1916 
1917 
1924  public function shiftTaskDate($old_project_dt_start)
1925  {
1926  global $user, $langs, $conf;
1927 
1928  $error = 0;
1929  $result = 0;
1930 
1931  $taskstatic = new Task($this->db);
1932 
1933  // Security check
1934  $socid = 0;
1935  if ($user->socid > 0) {
1936  $socid = $user->socid;
1937  }
1938 
1939  $tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1940 
1941  foreach ($tasksarray as $tasktoshiftdate) {
1942  $to_update = false;
1943  // Fetch only if update of date will be made
1944  if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
1945  //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1946  $to_update = true;
1947  $task = new Task($this->db);
1948  $result = $task->fetch($tasktoshiftdate->id);
1949  if (!$result) {
1950  $error++;
1951  $this->error .= $task->error;
1952  }
1953  }
1954  //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1955 
1956  //Calculate new task start date with difference between old proj start date and origin task start date
1957  if (!empty($tasktoshiftdate->date_start)) {
1958  $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
1959  }
1960 
1961  //Calculate new task end date with difference between origin proj end date and origin task end date
1962  if (!empty($tasktoshiftdate->date_end)) {
1963  $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1964  }
1965 
1966  if ($to_update) {
1967  $result = $task->update($user);
1968  if (!$result) {
1969  $error++;
1970  $this->error .= $task->error;
1971  }
1972  }
1973  }
1974  if ($error != 0) {
1975  return -1;
1976  }
1977  return $result;
1978  }
1979 
1980 
1981  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1989  public function update_element($tableName, $elementSelectId)
1990  {
1991  // phpcs:enable
1992  $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1993 
1994  if ($tableName == "actioncomm") {
1995  $sql .= " SET fk_project=".$this->id;
1996  $sql .= " WHERE id=".((int) $elementSelectId);
1997  } elseif (in_array($tableName, ["entrepot","mrp_mo","stocktransfer_stocktransfer"])) {
1998  $sql .= " SET fk_project=".$this->id;
1999  $sql .= " WHERE rowid=".((int) $elementSelectId);
2000  } else {
2001  $sql .= " SET fk_projet=".$this->id;
2002  $sql .= " WHERE rowid=".((int) $elementSelectId);
2003  }
2004 
2005  dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
2006  $resql = $this->db->query($sql);
2007  if (!$resql) {
2008  $this->error = $this->db->lasterror();
2009  return -1;
2010  } else {
2011  return 1;
2012  }
2013  }
2014 
2015  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2025  public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
2026  {
2027  // phpcs:enable
2028  $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
2029 
2030  if ($tableName == "actioncomm") {
2031  $sql .= " SET fk_project=NULL";
2032  $sql .= " WHERE id=".((int) $elementSelectId);
2033  } else {
2034  $sql .= " SET ".$projectfield."=NULL";
2035  $sql .= " WHERE rowid=".((int) $elementSelectId);
2036  }
2037 
2038  dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
2039  $resql = $this->db->query($sql);
2040  if (!$resql) {
2041  $this->error = $this->db->lasterror();
2042  return -1;
2043  } else {
2044  return 1;
2045  }
2046  }
2047 
2058  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2059  {
2060  global $conf, $langs;
2061 
2062  $langs->load("projects");
2063 
2064  if (!dol_strlen($modele)) {
2065  $modele = 'baleine';
2066 
2067  if ($this->model_pdf) {
2068  $modele = $this->model_pdf;
2069  } elseif (getDolGlobalString('PROJECT_ADDON_PDF')) {
2070  $modele = getDolGlobalString('PROJECT_ADDON_PDF');
2071  }
2072  }
2073 
2074  $modelpath = "core/modules/project/doc/";
2075 
2076  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2077  }
2078 
2079 
2089  public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
2090  {
2091  $this->weekWorkLoad = array();
2092  $this->weekWorkLoadPerTask = array();
2093 
2094  if (empty($datestart)) {
2095  dol_print_error(null, 'Error datestart parameter is empty');
2096  }
2097 
2098  $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2099  $sql .= " FROM ".MAIN_DB_PREFIX."element_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2100  $sql .= " WHERE ptt.fk_element = pt.rowid";
2101  $sql .= " AND ptt.elementtype = 'task'";
2102  $sql .= " AND pt.fk_projet = ".((int) $this->id);
2103  $sql .= " AND (ptt.element_date >= '".$this->db->idate($datestart)."' ";
2104  $sql .= " AND ptt.element_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
2105  if ($taskid) {
2106  $sql .= " AND ptt.fk_element=".((int) $taskid);
2107  }
2108  if (is_numeric($userid)) {
2109  $sql .= " AND ptt.fk_user=".((int) $userid);
2110  }
2111 
2112  //print $sql;
2113  $resql = $this->db->query($sql);
2114  if ($resql) {
2115  $daylareadyfound = array();
2116 
2117  $num = $this->db->num_rows($resql);
2118  $i = 0;
2119  // Loop on each record found, so each couple (project id, task id)
2120  while ($i < $num) {
2121  $obj = $this->db->fetch_object($resql);
2122  $day = $this->db->jdate($obj->element_date); // task_date is date without hours
2123  if (empty($daylareadyfound[$day])) {
2124  $this->weekWorkLoad[$day] = $obj->element_duration;
2125  $this->weekWorkLoadPerTask[$day][$obj->fk_element] = $obj->element_duration;
2126  } else {
2127  $this->weekWorkLoad[$day] += $obj->element_duration;
2128  $this->weekWorkLoadPerTask[$day][$obj->fk_element] += $obj->element_duration;
2129  }
2130  $daylareadyfound[$day] = 1;
2131  $i++;
2132  }
2133  $this->db->free($resql);
2134  return 1;
2135  } else {
2136  $this->error = "Error ".$this->db->lasterror();
2137  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2138  return -1;
2139  }
2140  }
2141 
2151  public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2152  {
2153  $this->monthWorkLoad = array();
2154  $this->monthWorkLoadPerTask = array();
2155 
2156  if (empty($datestart)) {
2157  dol_print_error(null, 'Error datestart parameter is empty');
2158  }
2159 
2160  $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2161  $sql .= " FROM ".MAIN_DB_PREFIX."element_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2162  $sql .= " WHERE ptt.fk_element = pt.rowid";
2163  $sql .= " AND ptt.elementtype = 'task'";
2164  $sql .= " AND pt.fk_projet = ".((int) $this->id);
2165  $sql .= " AND (ptt.element_date >= '".$this->db->idate($datestart)."' ";
2166  $sql .= " AND ptt.element_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
2167  if ($taskid) {
2168  $sql .= " AND ptt.fk_element=".((int) $taskid);
2169  }
2170  if (is_numeric($userid)) {
2171  $sql .= " AND ptt.fk_user=".((int) $userid);
2172  }
2173 
2174  //print $sql;
2175  $resql = $this->db->query($sql);
2176  if ($resql) {
2177  $weekalreadyfound = array();
2178 
2179  $num = $this->db->num_rows($resql);
2180  $i = 0;
2181  // Loop on each record found, so each couple (project id, task id)
2182  while ($i < $num) {
2183  $obj = $this->db->fetch_object($resql);
2184  if (!empty($obj->element_date)) {
2185  $date = explode('-', $obj->element_date);
2186  $week_number = getWeekNumber($date[2], $date[1], $date[0]);
2187  }
2188  '@phan-var-force int $week_number'; // Needed because phan considers it might be null
2189  if (empty($weekalreadyfound[$week_number])) {
2190  $this->monthWorkLoad[$week_number] = $obj->element_duration;
2191  $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] = $obj->element_duration;
2192  } else {
2193  $this->monthWorkLoad[$week_number] += $obj->element_duration;
2194  $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] += $obj->element_duration;
2195  }
2196  $weekalreadyfound[$week_number] = 1;
2197  $i++;
2198  }
2199  $this->db->free($resql);
2200  return 1;
2201  } else {
2202  $this->error = "Error ".$this->db->lasterror();
2203  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2204  return -1;
2205  }
2206  }
2207 
2208  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2215  public function load_board($user)
2216  {
2217  // phpcs:enable
2218  global $conf, $langs;
2219 
2220  // For external user, no check is done on company because readability is managed by public status of project and assignment.
2221  //$socid=$user->socid;
2222 
2223  $response = new WorkboardResponse();
2224  $response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2225  $response->label = $langs->trans("OpenedProjects");
2226  $response->labelShort = $langs->trans("Opened");
2227  $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2228  $response->img = img_object('', "projectpub");
2229  $response->nbtodo = 0;
2230  $response->nbtodolate = 0;
2231 
2232  $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2233  $sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
2234  $sql .= ")";
2235  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2236  // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2237  //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2238  $sql .= " WHERE p.fk_statut = 1";
2239  $sql .= " AND p.entity IN (".getEntity('project').')';
2240 
2241 
2242  $projectsListId = null;
2243  if (!$user->hasRight("projet", "all", "lire")) {
2244  $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
2245  $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2246  if (empty($projectsListId)) {
2247  return $response;
2248  }
2249 
2250  $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2251  }
2252 
2253  // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2254  //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).")";
2255  // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2256  //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))";
2257 
2258  //print $sql;
2259  $resql = $this->db->query($sql);
2260  if ($resql) {
2261  $project_static = new Project($this->db);
2262 
2263 
2264  // This assignment in condition is not a bug. It allows walking the results.
2265  while ($obj = $this->db->fetch_object($resql)) {
2266  $response->nbtodo++;
2267 
2268  $project_static->statut = $obj->status;
2269  $project_static->opp_status = $obj->fk_opp_status;
2270  $project_static->date_end = $this->db->jdate($obj->datee);
2271 
2272  if ($project_static->hasDelay()) {
2273  $response->nbtodolate++;
2274  }
2275  }
2276 
2277  return $response;
2278  }
2279 
2280  $this->error = $this->db->error();
2281  return -1;
2282  }
2283 
2292  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2293  {
2294  $tables = array(
2295  'projet'
2296  );
2297 
2298  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2299  }
2300 
2301 
2307  public function loadStateBoard()
2308  {
2309  global $user;
2310 
2311  $this->nb = array();
2312 
2313  $sql = "SELECT count(p.rowid) as nb";
2314  $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2315  $sql .= " WHERE";
2316  $sql .= " p.entity IN (".getEntity('project').")";
2317  if (!$user->hasRight('projet', 'all', 'lire')) {
2318  $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2319  $sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2320  }
2321 
2322  $resql = $this->db->query($sql);
2323  if ($resql) {
2324  while ($obj = $this->db->fetch_object($resql)) {
2325  $this->nb["projects"] = $obj->nb;
2326  }
2327  $this->db->free($resql);
2328  return 1;
2329  } else {
2330  dol_print_error($this->db);
2331  $this->error = $this->db->error();
2332  return -1;
2333  }
2334  }
2335 
2336 
2342  public function hasDelay()
2343  {
2344  global $conf;
2345 
2346  if (!($this->statut == self::STATUS_VALIDATED)) {
2347  return false;
2348  }
2349  if (!$this->date_end) {
2350  return false;
2351  }
2352 
2353  $now = dol_now();
2354 
2355  return ($this->date_end) < ($now - $conf->project->warning_delay);
2356  }
2357 
2358 
2365  public function info($id)
2366  {
2367  $sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2368  $sql .= ' date_close as datecloture,';
2369  $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_user_cloture';
2370  $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
2371  $sql .= ' WHERE c.rowid = '.((int) $id);
2372  $result = $this->db->query($sql);
2373  if ($result) {
2374  if ($this->db->num_rows($result)) {
2375  $obj = $this->db->fetch_object($result);
2376 
2377  $this->id = $obj->rowid;
2378 
2379  $this->user_creation_id = $obj->fk_user_author;
2380  $this->user_closing_id = $obj->fk_user_cloture;
2381 
2382  $this->date_creation = $this->db->jdate($obj->datec);
2383  $this->date_modification = $this->db->jdate($obj->datem);
2384  $this->date_cloture = $this->db->jdate($obj->datecloture);
2385  }
2386 
2387  $this->db->free($result);
2388  } else {
2389  dol_print_error($this->db);
2390  }
2391  }
2392 
2403  public function setCategories($categories)
2404  {
2405  require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2406  return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2407  }
2408 
2409 
2417  public function getLinesArray($user, $loadRoleMode = 1)
2418  {
2419  require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2420  $taskstatic = new Task($this->db);
2421 
2422  $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '', '-1', '', 0, 0, array(), 0, array(), 0, $loadRoleMode);
2423  return 1;
2424  }
2425 
2443  public function sendEmail($text, $subject, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = -1, $errors_to = '', $moreinheader = '')
2444  {
2445  global $conf, $langs;
2446  // TODO EMAIL
2447 
2448  return 1;
2449  }
2458  public function getKanbanView($option = '', $arraydata = null, $size = '')
2459  {
2460  global $conf, $langs;
2461 
2462  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2463 
2464  if (empty($size)) {
2465  if (empty($conf->dol_optimize_smallscreen)) {
2466  $size = 'large';
2467  } else {
2468  $size = 'small';
2469  }
2470  }
2471 
2472  $return = '<div class="box-flex-item '.($size == 'small' ? 'box-flex-item-small' : '').' box-flex-grow-zero">';
2473  $return .= '<div class="info-box info-box-sm">';
2474  $return .= '<span class="info-box-icon bg-infobox-action">';
2475  $return .= img_picto('', $this->public ? 'projectpub' : $this->picto);
2476  //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2477  $return .= '</span>';
2478  $return .= '<div class="info-box-content">';
2479  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref);
2480  if ($this->hasDelay()) {
2481  $return .= img_warning($langs->trans('Late'));
2482  }
2483  $return .= '</span>';
2484  if ($selected >= 0) {
2485  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2486  }
2487  // Date
2488  /*
2489  if (property_exists($this, 'date_start') && $this->date_start) {
2490  $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_start, 'day').'</>';
2491  }
2492  if (property_exists($this, 'date_end') && $this->date_end) {
2493  if ($this->date_start) {
2494  $return .= ' - ';
2495  } else {
2496  $return .= '<br>';
2497  }
2498  $return .= '<span class="info-box-label">'.dol_print_date($this->date_end, 'day').'</span>';
2499  }*/
2500  if (property_exists($this, 'thirdparty') && !is_null($this->thirdparty) && is_object($this->thirdparty) && $this->thirdparty instanceof Societe) {
2501  $return .= '<br><div class="info-box-ref tdoverflowmax150 inline-block valignmiddle">'.$this->thirdparty->getNomUrl(1);
2502  $return .= '</div>';
2503  if (!empty($this->thirdparty->phone)) {
2504  $return .= '<div class="inline-block valignmiddle">';
2505  $return .= dol_print_phone($this->thirdparty->phone, $this->thirdparty->country_code, 0, $this->thirdparty->id, 'tel', 'hidenum', 'phone', $this->thirdparty->phone, 0, 'marginleftonly');
2506  $return .= '</div>';
2507  }
2508  }
2509  if (!empty($arraydata['assignedusers'])) {
2510  $return .= '<br>';
2511  if ($this->public) {
2512  $return .= img_picto($langs->trans('Visibility').': '.$langs->trans('SharedProject'), 'world', 'class="paddingrightonly valignmiddle"');
2513  //print $langs->trans('SharedProject');
2514  } else {
2515  $return .= img_picto($langs->trans('Visibility').': '.$langs->trans('PrivateProject'), 'private', 'class="paddingrightonly valignmiddle"');
2516  //print $langs->trans('PrivateProject');
2517  }
2518 
2519  $return .= ' <span class="small valignmiddle">'.$arraydata['assignedusers'].'</span>';
2520  }
2521  /*if (property_exists($this, 'user_author_id')) {
2522  $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Author").'</span>';
2523  $return .= '<span> : '.$user->getNomUrl(1).'</span>';
2524  }*/
2525  $return .= '<br><div>'; // start div line status
2526  if ($this->usage_opportunity && $this->opp_status_code) {
2527  //$return .= '<br><span class="info-bo-label opacitymedium">'.$langs->trans("OpportunityStatusShort").'</span>';
2528  //$return .= '<div class="small inline-block">'.dol_trunc($langs->trans("OppStatus".$this->opp_status_code), 5).'</div>';
2529  $return .= '<div class="opacitymedium small marginrightonly inline-block" title="'.dol_escape_htmltag($langs->trans("OppStatus".$this->opp_status_code)).'">'.round($this->opp_percent).'%</div>';
2530  $return .= ' <div class="amount small marginrightonly inline-block">'.price($this->opp_amount).'</div>';
2531  }
2532  if (method_exists($this, 'getLibStatut')) {
2533  $return .= '<div class="info-box-status small inline-block valignmiddle">'.$this->getLibStatut(3).'</div>';
2534  }
2535  $return .= '</div>'; // end div line status
2536 
2537  $return .= '</div>';
2538  $return .= '</div>';
2539  $return .= '</div>';
2540 
2541  return $return;
2542  }
2543 
2549  public function getChildren()
2550  {
2551  $children = [];
2552  $sql = 'SELECT rowid,title';
2553  $sql .= ' FROM '.MAIN_DB_PREFIX.'projet';
2554  $sql .= ' WHERE fk_project = '.((int) $this->id);
2555  $sql .= ' ORDER BY title';
2556  $result = $this->db->query($sql);
2557  if ($result) {
2558  $n = $this->db->num_rows($result);
2559  while ($n) {
2560  $children[] = $this->db->fetch_object($result);
2561  $n--;
2562  }
2563  $this->db->free($result);
2564  } else {
2565  dol_print_error($this->db);
2566  }
2567 
2568  return $children;
2569  }
2570 
2575  public function createWeeklyReport()
2576  {
2577  global $mysoc, $user;
2578 
2579  $now = dol_now();
2580  $nowDate = dol_getdate($now, true);
2581 
2582  $errormesg = '';
2583  $errorsMsg = array();
2584 
2585  $firstDayOfWeekTS = dol_get_first_day_week($nowDate['mday'], $nowDate['mon'], $nowDate['year']);
2586 
2587  $firstDayOfWeekDate = dol_mktime(0, 0, 0, $nowDate['mon'], $firstDayOfWeekTS['first_day'], $nowDate['year']);
2588 
2589  $lastWeekStartTS = dol_time_plus_duree($firstDayOfWeekDate, -7, 'd');
2590 
2591  $lastWeekEndTS = dol_time_plus_duree($lastWeekStartTS, 6, 'd');
2592 
2593  $startDate = dol_print_date($lastWeekStartTS, '%Y-%m-%d 00:00:00');
2594  $endDate = dol_print_date($lastWeekEndTS, '%Y-%m-%d 23:59:59');
2595 
2596  $sql = "SELECT
2597  u.rowid AS user_id,
2598  CONCAT(u.firstname, ' ', u.lastname) AS name,
2599  u.email,u.weeklyhours,
2600  SUM(et.element_duration) AS total_seconds
2601  FROM
2602  ".MAIN_DB_PREFIX."element_time AS et
2603  JOIN
2604  ".MAIN_DB_PREFIX."user AS u ON et.fk_user = u.rowid
2605  WHERE
2606  et.element_date BETWEEN '".$this->db->escape($startDate)."' AND '".$this->db->escape($endDate)."'
2607  AND et.elementtype = 'task'
2608  GROUP BY
2609  et.fk_user";
2610 
2611  $resql = $this->db->query($sql);
2612  if (!$resql) {
2613  dol_print_error($this->db);
2614  return -1;
2615  } else {
2616  $reportContent = "<span>Weekly time report from $startDate to $endDate </span><br><br>";
2617  $reportContent .= '<table border="1" style="border-collapse: collapse;">';
2618  $reportContent .= '<tr><th>Nom d\'utilisateur</th><th>Temps saisi (heures)</th><th>Temps travaillé par semaine (heures)</th></tr>';
2619 
2620  $weekendEnabled = 0;
2621  $to = '';
2622  $nbMailSend = 0;
2623  $error = 0;
2624  $errors_to = '';
2625  while ($obj = $this->db->fetch_object($resql)) {
2626  $to = $obj->email;
2627  $numHolidays = num_public_holiday($lastWeekStartTS, $lastWeekEndTS, $mysoc->country_code, 1);
2628  if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY') && getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY')) {
2629  $numHolidays = $numHolidays - 2;
2630  $weekendEnabled = 2;
2631  }
2632 
2633  $dailyHours = $obj->weeklyhours / (7 - $weekendEnabled);
2634 
2635  // Adjust total on seconde
2636  $adjustedSeconds = $obj->total_seconds + ($numHolidays * $dailyHours * 3600);
2637 
2638  $totalHours = round($adjustedSeconds / 3600, 2);
2639 
2640  $reportContent .= "<tr><td>{$obj->name}</td><td>{$totalHours}</td><td>".round($obj->weeklyhours, 2)."</td></tr>";
2641 
2642  $reportContent .= '</table>';
2643 
2644  require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
2645 
2646  // PREPARE EMAIL
2647  $errormesg = '';
2648 
2649  $subject = 'Rapport hebdomadaire des temps travaillés';
2650 
2651  $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
2652  if (empty($from)) {
2653  $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
2654  $error++;
2655  }
2656 
2657  $mail = new CMailFile($subject, $to, $from, $reportContent, array(), array(), array(), '', '', 0, -1, '', '', 0, 'text/html');
2658 
2659  if ($mail->sendfile()) {
2660  $nbMailSend++;
2661 
2662  // Add a line into event table
2663  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2664 
2665  // Insert record of emails sent
2666  $actioncomm = new ActionComm($this->db);
2667 
2668  $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2669  $actioncomm->socid = $this->thirdparty->id; // To link to a company
2670  $actioncomm->contact_id = 0;
2671 
2672  $actioncomm->code = 'AC_EMAIL';
2673  $actioncomm->label = 'createWeeklyReportOK()';
2674  $actioncomm->fk_project = $this->id;
2675  $actioncomm->datep = dol_now();
2676  $actioncomm->datef = $actioncomm->datep;
2677  $actioncomm->percentage = -1; // Not applicable
2678  $actioncomm->authorid = $user->id; // User saving action
2679  $actioncomm->userownerid = $user->id; // Owner of action
2680  // Fields when action is an email (content should be added into note)
2681  $actioncomm->email_msgid = $mail->msgid;
2682  $actioncomm->email_subject = $subject;
2683  $actioncomm->email_from = $from;
2684  $actioncomm->email_sender = '';
2685  $actioncomm->email_to = $to;
2686 
2687  $actioncomm->errors_to = $errors_to;
2688 
2689  $actioncomm->elementtype = 'project_task';
2690  $actioncomm->fk_element = (int) $this->element;
2691 
2692  $actioncomm->create($user);
2693  } else {
2694  $errormesg = $mail->error.' : '.$to;
2695  $error++;
2696 
2697  // Add a line into event table
2698  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2699 
2700  // Insert record of emails sent
2701  $actioncomm = new ActionComm($this->db);
2702 
2703  $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2704  $actioncomm->socid = $this->thirdparty->id; // To link to a company
2705  $actioncomm->contact_id = 0;
2706 
2707  $actioncomm->code = 'AC_EMAIL';
2708  $actioncomm->label = 'createWeeklyReportKO()';
2709  $actioncomm->note_private = $errormesg;
2710  $actioncomm->fk_project = $this->id;
2711  $actioncomm->datep = dol_now();
2712  $actioncomm->datef = $actioncomm->datep;
2713  $actioncomm->authorid = $user->id; // User saving action
2714  $actioncomm->userownerid = $user->id; // Owner of action
2715  // Fields when action is an email (content should be added into note)
2716  $actioncomm->email_msgid = $mail->msgid;
2717  $actioncomm->email_from = $from;
2718  $actioncomm->email_sender = '';
2719  $actioncomm->email_to = $to;
2720 
2721  $actioncomm->errors_to = $errors_to;
2722 
2723  $actioncomm->elementtype = 'project_task';
2724  $actioncomm->fk_element = (int) $this->element;
2725 
2726  $actioncomm->create($user);
2727  }
2728  $this->db->commit();
2729  }
2730  }
2731  if (!empty($errormesg)) {
2732  $errorsMsg[] = $errormesg;
2733  }
2734 
2735  if (!$error) {
2736  $this->output .= 'Nb of emails sent : '.$nbMailSend;
2737  dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
2738  return 0;
2739  } else {
2740  $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
2741  dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
2742  return $error;
2743  }
2744  }
2745 }
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:624
$object ref
Definition: info.php:79
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
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 $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
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.
getNomUrl($withpicto=0, $option='', $addlabel=0, $moreinpopup='', $sep=' - ', $notooltip=0, $save_lastsearch_value=-1, $morecss='', $save_pageforbacktolist='')
Return clickable name (with picto eventually)
fetchAndSetSubstitution($id, $key, $fetched=false)
Fetch object and substitute key.
getChildren()
Return array of sub-projects of the current project.
setValid($user, $notrigger=0)
Validate a project.
createWeeklyReport()
Method for calculating weekly hours worked and generating a report.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
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.
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.
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.
loadStateBoard()
Load indicators this->nb for the state board.
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
getLinesArray($user, $loadRoleMode=1)
Create an array of tasks of current project.
get_element_list($type, $tablename, $datefieldname='', $date_start=null, $date_end=null, $projectkey='fk_projet')
Return list of elements for type, linked to a project.
getKanbanView($option='', $arraydata=null, $size='')
Return clicable link of object (with eventually picto)
info($id)
Charge les information d'ordre info dans l'objet commande.
getTooltipContentArray($params)
getTooltipContentArray
sendEmail($text, $subject, $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array(), $addr_cc="", $addr_bcc="", $deliveryreceipt=0, $msgishtml=-1, $errors_to='', $moreinheader='')
Function sending an email to the current member with the text supplied in parameter.
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.
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 third parties objects (customers, suppliers, prospects...)
Class to manage tasks.
Definition: task.class.php:41
Class to manage Dolibarr users.
Definition: user.class.php:50
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:751
dol_get_first_day_week($day, $month, $year, $gm=false)
Return first day of week for a date.
Definition: date.lib.php:669
getWeekNumber($day, $month, $year)
Return week number.
Definition: date.lib.php:1194
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:124
num_public_holiday($timestampStart, $timestampEnd, $country_code='', $lastday=0, $includesaturday=-1, $includesunday=-1, $includefriday=-1, $includemonday=-1)
Return the number of non working days including Friday, Saturday and Sunday (or not) between 2 dates ...
Definition: date.lib.php:763
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:1620
dol_copy($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=0)
Copy a file to another file.
Definition: files.lib.php:767
dol_dir_list($utf8_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:63
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 information (by default a local PHP server timestamp) Rep...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_print_phone($phone, $countrycode='', $cid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='')
Format phone numbers according to country.
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
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_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)