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 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 3 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program. If not, see <https://www.gnu.org/licenses/>. 22 */ 23 24/** 25 * \file htdocs/projet/class/project.class.php 26 * \ingroup projet 27 * \brief File of class to manage projects 28 */ 29require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; 30 31/** 32 * Class to manage projects 33 */ 34class Project extends CommonObject 35{ 36 37 /** 38 * @var string ID to identify managed object 39 */ 40 public $element = 'project'; 41 42 /** 43 * @var string Name of table without prefix where object is stored 44 */ 45 public $table_element = 'projet'; 46 47 /** 48 * @var string Name of subtable line 49 */ 50 public $table_element_line = 'projet_task'; 51 52 /** 53 * @var string Name of field date 54 */ 55 public $table_element_date; 56 57 /** 58 * @var string Field with ID of parent key if this field has a parent 59 */ 60 public $fk_element = 'fk_projet'; 61 62 /** 63 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe 64 * @var int 65 */ 66 public $ismultientitymanaged = 1; 67 68 /** 69 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png 70 */ 71 public $picto = 'project'; 72 73 /** 74 * {@inheritdoc} 75 */ 76 protected $table_ref_field = 'ref'; 77 78 /** 79 * @var string description 80 */ 81 public $description; 82 83 /** 84 * @var string 85 */ 86 public $title; 87 88 public $date_start; 89 public $date_end; 90 public $date_close; 91 92 public $socid; // To store id of thirdparty 93 public $thirdparty_name; // To store name of thirdparty (defined only in some cases) 94 95 public $user_author_id; //!< Id of project creator. Not defined if shared project. 96 97 /** 98 * @var int user close id 99 */ 100 public $fk_user_close; 101 102 /** 103 * @var int user close id 104 */ 105 public $user_close_id; 106 public $public; //!< Tell if this is a public or private project 107 108 /** 109 * @var float budget Amount 110 */ 111 public $budget_amount; 112 113 /** 114 * @var integer Can use projects to follow opportunities 115 */ 116 public $usage_opportunity; 117 118 /** 119 * @var integer Can follow tasks on project and enter time spent on it 120 */ 121 public $usage_task; 122 123 /** 124 * @var integer Use to bill task spend time 125 */ 126 public $usage_bill_time; // Is the time spent on project must be invoiced or not 127 128 /** 129 * @var integer Event organization: Use Event Organization 130 */ 131 public $usage_organize_event; 132 133 /** 134 * @var integer Event organization: Allow unknown people to suggest new conferences 135 */ 136 public $accept_conference_suggestions; 137 138 /** 139 * @var integer Event organization: Allow unknown people to suggest new booth 140 */ 141 public $accept_booth_suggestions; 142 143 /** 144 * @var float Event organization: registration price 145 */ 146 public $price_registration; 147 148 /** 149 * @var float Event organization: booth price 150 */ 151 public $price_booth; 152 153 public $statuts_short; 154 public $statuts_long; 155 156 public $statut; // 0=draft, 1=opened, 2=closed 157 public $opp_status; // opportunity status, into table llx_c_lead_status 158 public $opp_percent; // opportunity probability 159 160 public $email_msgid; 161 162 public $oldcopy; 163 164 public $weekWorkLoad; // Used to store workload details of a projet 165 public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet 166 167 /** 168 * @var int Creation date 169 * @deprecated 170 * @see $date_c 171 */ 172 public $datec; 173 174 /** 175 * @var int Creation date 176 */ 177 public $date_c; 178 179 /** 180 * @var int Modification date 181 * @deprecated 182 * @see $date_m 183 */ 184 public $datem; 185 186 /** 187 * @var int Modification date 188 */ 189 public $date_m; 190 191 /** 192 * @var Task[] 193 */ 194 public $lines; 195 196 /** 197 * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password') 198 * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)" 199 * 'label' the translation key. 200 * 'enabled' is a condition when the field must be managed. 201 * 'position' is the sort order of field. 202 * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). 203 * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing) 204 * 'noteditable' says if field is not editable (1 or 0) 205 * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created. 206 * 'index' if we want an index in database. 207 * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...). 208 * 'searchall' is 1 if we want to search in this field when making a search from the quick search button. 209 * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8). 210 * 'css' is the CSS style to use on field. For example: 'maxwidth200' 211 * 'help' is a string visible as a tooltip on field 212 * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record 213 * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code. 214 * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel") 215 * 'comment' is not used. You can store here any text of your choice. It is not used by application. 216 * 217 * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor. 218 */ 219 220 // BEGIN MODULEBUILDER PROPERTIES 221 /** 222 * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. 223 */ 224 public $fields = array( 225 'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10), 226 'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>15, 'searchall'=>1), 227 'title' =>array('type'=>'varchar(255)', 'label'=>'ProjectLabel', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>17, 'showoncombobox'=>2, 'searchall'=>1), 228 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>19), 229 'fk_soc' =>array('type'=>'integer', 'label'=>'Thirdparty', 'enabled'=>1, 'visible'=>0, 'position'=>20), 230 'dateo' =>array('type'=>'date', 'label'=>'DateStart', 'enabled'=>1, 'visible'=>1, 'position'=>30), 231 'datee' =>array('type'=>'date', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>1, 'position'=>35), 232 'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>1), 233 'public' =>array('type'=>'integer', 'label'=>'Visibility', 'enabled'=>1, 'visible'=>1, 'position'=>65), 234 'fk_opp_status' =>array('type'=>'integer', 'label'=>'OpportunityStatusShort', 'enabled'=>1, 'visible'=>1, 'position'=>75), 235 'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'OpportunityProbabilityShort', 'enabled'=>1, 'visible'=>1, 'position'=>80), 236 'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85, 'searchall'=>1), 237 'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90, 'searchall'=>1), 238 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPdf', 'enabled'=>1, 'visible'=>0, 'position'=>95), 239 'date_close' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>0, 'position'=>105), 240 'fk_user_close' =>array('type'=>'integer', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>0, 'position'=>110), 241 'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'OpportunityAmountShort', 'enabled'=>1, 'visible'=>1, 'position'=>115), 242 'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget', 'enabled'=>1, 'visible'=>1, 'position'=>119), 243 'usage_bill_time' =>array('type'=>'integer', 'label'=>'UsageBillTimeShort', 'enabled'=>1, 'visible'=>-1, 'position'=>130), 244 'usage_opportunity' =>array('type'=>'integer', 'label'=>'UsageOpportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135), 245 'usage_task' =>array('type'=>'integer', 'label'=>'UsageTasks', 'enabled'=>1, 'visible'=>-1, 'position'=>140), 246 'usage_organize_event' =>array('type'=>'integer', 'label'=>'UsageOrganizeEvent', 'enabled'=>1, 'visible'=>-1, 'position'=>145), 247 'accept_conference_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestConf', 'enabled'=>1, 'visible'=>-1, 'position'=>146), 248 'accept_booth_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>147), 249 'price_registration' =>array('type'=>'double(24,8)', 'label'=>'PriceOfRegistration', 'enabled'=>1, 'visible'=>-1, 'position'=>148), 250 'price_booth' =>array('type'=>'double(24,8)', 'label'=>'PriceOfBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>149), 251 'datec' =>array('type'=>'datetime', 'label'=>'DateCreationShort', 'enabled'=>1, 'visible'=>-2, 'position'=>200), 252 'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>205), 253 'fk_user_creat' =>array('type'=>'integer', 'label'=>'UserCreation', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>210), 254 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModification', 'enabled'=>1, 'visible'=>0, 'position'=>215), 255 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>0, 'position'=>220), 256 'email_msgid'=>array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'enabled'=>1, 'visible'=>-1, 'position'=>250, 'help'=>'EmailMsgIDWhenSourceisEmail'), 257 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>500) 258 ); 259 // END MODULEBUILDER PROPERTIES 260 261 /** 262 * Draft status 263 */ 264 const STATUS_DRAFT = 0; 265 266 /** 267 * Open/Validated status 268 */ 269 const STATUS_VALIDATED = 1; 270 271 /** 272 * Closed status 273 */ 274 const STATUS_CLOSED = 2; 275 276 /** 277 * Constructor 278 * 279 * @param DoliDB $db Database handler 280 */ 281 public function __construct($db) 282 { 283 global $conf; 284 285 $this->db = $db; 286 287 $this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed'); 288 $this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed'); 289 290 global $conf; 291 292 if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) { 293 $this->fields['rowid']['visible'] = 0; 294 } 295 296 if (empty($conf->global->PROJECT_USE_OPPORTUNITIES)) { 297 $this->fields['fk_opp_status']['enabled'] = 0; 298 $this->fields['opp_percent']['enabled'] = 0; 299 $this->fields['opp_amount']['enabled'] = 0; 300 $this->fields['usage_opportunity']['enabled'] = 0; 301 } 302 303 if (!empty($conf->global->PROJECT_HIDE_TASKS)) { 304 $this->fields['usage_bill_time']['visible'] = 0; 305 $this->fields['usage_task']['visible'] = 0; 306 } 307 308 if (empty($conf->eventorganization->enabled)) { 309 $this->fields['usage_organize_event']['visible'] = 0; 310 $this->fields['accept_conference_suggestions']['enabled'] = 0; 311 $this->fields['accept_booth_suggestions']['enabled'] = 0; 312 $this->fields['price_registration']['enabled'] = 0; 313 $this->fields['price_booth']['enabled'] = 0; 314 } 315 } 316 317 /** 318 * Create a project into database 319 * 320 * @param User $user User making creation 321 * @param int $notrigger Disable triggers 322 * @return int <0 if KO, id of created project if OK 323 */ 324 public function create($user, $notrigger = 0) 325 { 326 global $conf, $langs; 327 328 $error = 0; 329 $ret = 0; 330 331 $now = dol_now(); 332 333 // Clean parameters 334 $this->note_private = dol_substr($this->note_private, 0, 65535); 335 $this->note_public = dol_substr($this->note_public, 0, 65535); 336 337 // Check parameters 338 if (!trim($this->ref)) { 339 $this->error = 'ErrorFieldsRequired'; 340 dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR); 341 return -1; 342 } 343 if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0)) { 344 $this->error = 'ErrorFieldsRequired'; 345 dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR); 346 return -1; 347 } 348 349 // Create project 350 $this->db->begin(); 351 352 $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet ("; 353 $sql .= "ref"; 354 $sql .= ", title"; 355 $sql .= ", description"; 356 $sql .= ", fk_soc"; 357 $sql .= ", fk_user_creat"; 358 $sql .= ", fk_statut"; 359 $sql .= ", fk_opp_status"; 360 $sql .= ", opp_percent"; 361 $sql .= ", public"; 362 $sql .= ", datec"; 363 $sql .= ", dateo"; 364 $sql .= ", datee"; 365 $sql .= ", opp_amount"; 366 $sql .= ", budget_amount"; 367 $sql .= ", usage_opportunity"; 368 $sql .= ", usage_task"; 369 $sql .= ", usage_bill_time"; 370 $sql .= ", usage_organize_event"; 371 $sql .= ", accept_conference_suggestions"; 372 $sql .= ", accept_booth_suggestions"; 373 $sql .= ", price_registration"; 374 $sql .= ", price_booth"; 375 $sql .= ", email_msgid"; 376 $sql .= ", note_private"; 377 $sql .= ", note_public"; 378 $sql .= ", entity"; 379 $sql .= ") VALUES ("; 380 $sql .= "'".$this->db->escape($this->ref)."'"; 381 $sql .= ", '".$this->db->escape($this->title)."'"; 382 $sql .= ", '".$this->db->escape($this->description)."'"; 383 $sql .= ", ".($this->socid > 0 ? $this->socid : "null"); 384 $sql .= ", ".$user->id; 385 $sql .= ", ".(is_numeric($this->statut) ? $this->statut : '0'); 386 $sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'NULL'); 387 $sql .= ", ".(is_numeric($this->opp_percent) ? $this->opp_percent : 'NULL'); 388 $sql .= ", ".($this->public ? 1 : 0); 389 $sql .= ", '".$this->db->idate($now)."'"; 390 $sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null'); 391 $sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null'); 392 $sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null'); 393 $sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null'); 394 $sql .= ", ".($this->usage_opportunity ? 1 : 0); 395 $sql .= ", ".($this->usage_task ? 1 : 0); 396 $sql .= ", ".($this->usage_bill_time ? 1 : 0); 397 $sql .= ", ".($this->usage_organize_event ? 1 : 0); 398 $sql .= ", ".($this->accept_conference_suggestions ? 1 : 0); 399 $sql .= ", ".($this->accept_booth_suggestions ? 1 : 0); 400 $sql .= ", ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : 'null'); 401 $sql .= ", ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : 'null'); 402 $sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null'); 403 $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null'); 404 $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null'); 405 $sql .= ", ".$conf->entity; 406 $sql .= ")"; 407 408 dol_syslog(get_class($this)."::create", LOG_DEBUG); 409 $resql = $this->db->query($sql); 410 if ($resql) { 411 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet"); 412 $ret = $this->id; 413 414 if (!$notrigger) { 415 // Call trigger 416 $result = $this->call_trigger('PROJECT_CREATE', $user); 417 if ($result < 0) { 418 $error++; 419 } 420 // End call triggers 421 } 422 } else { 423 $this->error = $this->db->lasterror(); 424 $this->errno = $this->db->lasterrno(); 425 $error++; 426 } 427 428 // Update extrafield 429 if (!$error) { 430 $result = $this->insertExtraFields(); 431 if ($result < 0) { 432 $error++; 433 } 434 } 435 436 if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS)) { 437 $res = $this->setValid($user); 438 if ($res < 0) { 439 $error++; 440 } 441 } 442 443 if (!$error) { 444 $this->db->commit(); 445 return $ret; 446 } else { 447 $this->db->rollback(); 448 return -1; 449 } 450 } 451 452 /** 453 * Update a project 454 * 455 * @param User $user User object of making update 456 * @param int $notrigger 1=Disable all triggers 457 * @return int <=0 if KO, >0 if OK 458 */ 459 public function update($user, $notrigger = 0) 460 { 461 global $langs, $conf; 462 463 $error = 0; 464 465 // Clean parameters 466 $this->title = trim($this->title); 467 $this->description = trim($this->description); 468 if ($this->opp_amount < 0) { 469 $this->opp_amount = ''; 470 } 471 if ($this->opp_percent < 0) { 472 $this->opp_percent = ''; 473 } 474 if ($this->date_end && $this->date_end < $this->date_start) { 475 $this->error = $langs->trans("ErrorDateEndLowerThanDateStart"); 476 $this->errors[] = $this->error; 477 $this->db->rollback(); 478 dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR); 479 return -3; 480 } 481 482 if (dol_strlen(trim($this->ref)) > 0) { 483 $this->db->begin(); 484 485 $sql = "UPDATE ".MAIN_DB_PREFIX."projet SET"; 486 $sql .= " ref='".$this->db->escape($this->ref)."'"; 487 $sql .= ", title = '".$this->db->escape($this->title)."'"; 488 $sql .= ", description = '".$this->db->escape($this->description)."'"; 489 $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null"); 490 $sql .= ", fk_statut = ".((int) $this->statut); 491 $sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null'); 492 $sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null'); 493 $sql .= ", public = ".($this->public ? 1 : 0); 494 $sql .= ", datec=".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null'); 495 $sql .= ", dateo=".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null'); 496 $sql .= ", datee=".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null'); 497 $sql .= ", date_close=".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null'); 498 $sql .= ", fk_user_close=".($this->fk_user_close > 0 ? $this->fk_user_close : "null"); 499 $sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null"); 500 $sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null"); 501 $sql .= ", fk_user_modif = ".$user->id; 502 $sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0); 503 $sql .= ", usage_task = ".($this->usage_task ? 1 : 0); 504 $sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0); 505 $sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0); 506 $sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0); 507 $sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0); 508 $sql .= ", price_registration = ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null"); 509 $sql .= ", price_booth = ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : "null"); 510 $sql .= " WHERE rowid = ".((int) $this->id); 511 512 dol_syslog(get_class($this)."::update", LOG_DEBUG); 513 $resql = $this->db->query($sql); 514 if ($resql) { 515 // Update extrafield 516 if (!$error) { 517 $result = $this->insertExtraFields(); 518 if ($result < 0) { 519 $error++; 520 } 521 } 522 523 if (!$error && !$notrigger) { 524 // Call trigger 525 $result = $this->call_trigger('PROJECT_MODIFY', $user); 526 if ($result < 0) { 527 $error++; 528 } 529 // End call triggers 530 } 531 532 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) { 533 // We remove directory 534 if ($conf->projet->dir_output) { 535 $olddir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref); 536 $newdir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->ref); 537 if (file_exists($olddir)) { 538 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 539 $res = @rename($olddir, $newdir); 540 if (!$res) { 541 $langs->load("errors"); 542 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir); 543 $error++; 544 } 545 } 546 } 547 } 548 if (!$error) { 549 $this->db->commit(); 550 $result = 1; 551 } else { 552 $this->db->rollback(); 553 $result = -1; 554 } 555 } else { 556 $this->error = $this->db->lasterror(); 557 $this->errors[] = $this->error; 558 $this->db->rollback(); 559 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') { 560 $result = -4; 561 } else { 562 $result = -2; 563 } 564 dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR); 565 } 566 } else { 567 dol_syslog(get_class($this)."::update ref null"); 568 $result = -1; 569 } 570 571 return $result; 572 } 573 574 /** 575 * Get object from database 576 * 577 * @param int $id Id of object to load 578 * @param string $ref Ref of project 579 * @param string $ref_ext Ref ext of project 580 * @param string $email_msgid Email msgid 581 * @return int >0 if OK, 0 if not found, <0 if KO 582 */ 583 public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '') 584 { 585 global $conf; 586 587 if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) { 588 dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING); 589 return -1; 590 } 591 592 $sql = "SELECT rowid, entity, ref, title, description, public, datec, opp_amount, budget_amount,"; 593 $sql .= " tms, dateo, datee, date_close, fk_soc, fk_user_creat, fk_user_modif, fk_user_close, fk_statut as status, fk_opp_status, opp_percent,"; 594 $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,"; 595 $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth"; 596 $sql .= " FROM ".MAIN_DB_PREFIX."projet"; 597 if (!empty($id)) { 598 $sql .= " WHERE rowid = ".((int) $id); 599 } else { 600 $sql .= " WHERE entity IN (".getEntity('project').")"; 601 if (!empty($ref)) { 602 $sql .= " AND ref = '".$this->db->escape($ref)."'"; 603 } elseif (!empty($ref_ext)) { 604 $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'"; 605 } else { 606 $sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'"; 607 } 608 } 609 610 dol_syslog(get_class($this)."::fetch", LOG_DEBUG); 611 $resql = $this->db->query($sql); 612 if ($resql) { 613 $num_rows = $this->db->num_rows($resql); 614 615 if ($num_rows) { 616 $obj = $this->db->fetch_object($resql); 617 618 $this->id = $obj->rowid; 619 $this->entity = $obj->entity; 620 $this->ref = $obj->ref; 621 $this->title = $obj->title; 622 $this->description = $obj->description; 623 $this->date_c = $this->db->jdate($obj->datec); 624 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated 625 $this->date_m = $this->db->jdate($obj->tms); 626 $this->datem = $this->db->jdate($obj->tms); // TODO deprecated 627 $this->date_start = $this->db->jdate($obj->dateo); 628 $this->date_end = $this->db->jdate($obj->datee); 629 $this->date_close = $this->db->jdate($obj->date_close); 630 $this->note_private = $obj->note_private; 631 $this->note_public = $obj->note_public; 632 $this->socid = $obj->fk_soc; 633 $this->user_author_id = $obj->fk_user_creat; 634 $this->user_modification_id = $obj->fk_user_modif; 635 $this->user_close_id = $obj->fk_user_close; 636 $this->public = $obj->public; 637 $this->statut = $obj->status; // deprecated 638 $this->status = $obj->status; 639 $this->opp_status = $obj->fk_opp_status; 640 $this->opp_amount = $obj->opp_amount; 641 $this->opp_percent = $obj->opp_percent; 642 $this->budget_amount = $obj->budget_amount; 643 $this->model_pdf = $obj->model_pdf; 644 $this->modelpdf = $obj->model_pdf; // deprecated 645 $this->usage_opportunity = (int) $obj->usage_opportunity; 646 $this->usage_task = (int) $obj->usage_task; 647 $this->usage_bill_time = (int) $obj->usage_bill_time; 648 $this->usage_organize_event = (int) $obj->usage_organize_event; 649 $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions; 650 $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions; 651 $this->price_registration = $obj->price_registration; 652 $this->price_booth = $obj->price_booth; 653 $this->email_msgid = $obj->email_msgid; 654 655 $this->db->free($resql); 656 657 // Retrieve all extrafield 658 // fetch optionals attributes and labels 659 $this->fetch_optionals(); 660 661 return 1; 662 } 663 664 $this->db->free($resql); 665 666 return 0; 667 } else { 668 $this->error = $this->db->lasterror(); 669 $this->errors[] = $this->db->lasterror(); 670 return -1; 671 } 672 } 673 674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 675 /** 676 * Return list of elements for type, linked to a project 677 * 678 * @param string $type 'propal','order','invoice','order_supplier','invoice_supplier',... 679 * @param string $tablename name of table associated of the type 680 * @param string $datefieldname name of date field for filter 681 * @param int $dates Start date 682 * @param int $datee End date 683 * @param string $projectkey Equivalent key to fk_projet for actual type 684 * @return mixed Array list of object ids linked to project, < 0 or string if error 685 */ 686 public function get_element_list($type, $tablename, $datefieldname = '', $dates = '', $datee = '', $projectkey = 'fk_projet') 687 { 688 // phpcs:enable 689 690 global $hookmanager; 691 692 $elements = array(); 693 694 if ($this->id <= 0) { 695 return $elements; 696 } 697 698 $ids = $this->id; 699 700 if ($type == 'agenda') { 701 $sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity('agenda').")"; 702 } elseif ($type == 'expensereport') { 703 $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).")"; 704 } elseif ($type == 'project_task') { 705 $sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize($ids).")"; 706 } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user 707 $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet IN (".$this->db->sanitize($ids).")"; 708 } elseif ($type == 'stock_mouvement') { 709 $sql = 'SELECT ms.rowid, ms.fk_user_author as fk_user FROM '.MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin IN (".$this->db->sanitize($ids).") AND ms.type_mouvement = 1"; 710 } elseif ($type == 'loan') { 711 $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).")"; 712 } else { 713 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity($type).")"; 714 } 715 716 if ($dates > 0 && $type == 'loan') { 717 $sql .= " AND (dateend > '".$this->db->idate($dates)."' OR dateend IS NULL)"; 718 } elseif ($dates > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table 719 if (empty($datefieldname) && !empty($this->table_element_date)) { 720 $datefieldname = $this->table_element_date; 721 } 722 if (empty($datefieldname)) { 723 return 'Error this object has no date field defined'; 724 } 725 $sql .= " AND (".$datefieldname." >= '".$this->db->idate($dates)."' OR ".$datefieldname." IS NULL)"; 726 } 727 728 if ($datee > 0 && $type == 'loan') { 729 $sql .= " AND (datestart < '".$this->db->idate($datee)."' OR datestart IS NULL)"; 730 } elseif ($datee > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table 731 if (empty($datefieldname) && !empty($this->table_element_date)) { 732 $datefieldname = $this->table_element_date; 733 } 734 if (empty($datefieldname)) { 735 return 'Error this object has no date field defined'; 736 } 737 $sql .= " AND (".$datefieldname." <= '".$this->db->idate($datee)."' OR ".$datefieldname." IS NULL)"; 738 } 739 740 $parameters = array( 741 'sql'=>$sql, 742 'type' => $type, 743 'tablename' => $tablename, 744 'datefieldname' => $datefieldname, 745 'dates' => $dates, 746 'datee' => $datee, 747 'fk_projet' => $projectkey 748 ); 749 $reshook = $hookmanager->executeHooks('getElementList', $parameters, $object, $action); 750 if ($reshook > 0) { 751 $sql = $hookmanager->resPrint; 752 } else { 753 $sql .= $hookmanager->resPrint; 754 } 755 756 if (!$sql) { 757 return -1; 758 } 759 760 //print $sql; 761 dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG); 762 $result = $this->db->query($sql); 763 if ($result) { 764 $nump = $this->db->num_rows($result); 765 if ($nump) { 766 $i = 0; 767 while ($i < $nump) { 768 $obj = $this->db->fetch_object($result); 769 770 $elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user); 771 772 $i++; 773 } 774 $this->db->free($result); 775 } 776 777 /* Return array even if empty*/ 778 return $elements; 779 } else { 780 dol_print_error($this->db); 781 } 782 } 783 784 /** 785 * Delete a project from database 786 * 787 * @param User $user User 788 * @param int $notrigger Disable triggers 789 * @return int <0 if KO, 0 if not possible, >0 if OK 790 */ 791 public function delete($user, $notrigger = 0) 792 { 793 global $langs, $conf; 794 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 795 796 $error = 0; 797 798 $this->db->begin(); 799 800 if (!$error) { 801 // Delete linked contacts 802 $res = $this->delete_linked_contact(); 803 if ($res < 0) { 804 $this->error = 'ErrorFailToDeleteLinkedContact'; 805 //$error++; 806 $this->db->rollback(); 807 return 0; 808 } 809 } 810 811 // Set fk_projet into elements to null 812 $listoftables = array( 813 'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet', 814 'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet', 815 'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet', 'fichinter'=>'fk_projet', 'don'=>'fk_projet', 816 'actioncomm'=>'fk_project', 'mrp_mo'=>'fk_project', 'entrepot'=>'fk_project' 817 ); 818 foreach ($listoftables as $key => $value) { 819 $sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$value." = NULL where ".$value." = ".((int) $this->id); 820 $resql = $this->db->query($sql); 821 if (!$resql) { 822 $this->errors[] = $this->db->lasterror(); 823 $error++; 824 break; 825 } 826 } 827 828 // Remove linked categories. 829 if (!$error) { 830 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project"; 831 $sql .= " WHERE fk_project = ".((int) $this->id); 832 833 $result = $this->db->query($sql); 834 if (!$result) { 835 $error++; 836 $this->errors[] = $this->db->lasterror(); 837 } 838 } 839 840 // Fetch tasks 841 $this->getLinesArray($user); 842 843 // Delete tasks 844 $ret = $this->deleteTasks($user); 845 if ($ret < 0) { 846 $error++; 847 } 848 849 850 // Delete all child tables 851 if (!$error) { 852 $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete 853 foreach ($elements as $table) { 854 if (!$error) { 855 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table; 856 $sql .= " WHERE fk_project = ".((int) $this->id); 857 858 $result = $this->db->query($sql); 859 if (!$result) { 860 $error++; 861 $this->errors[] = $this->db->lasterror(); 862 } 863 } 864 } 865 } 866 867 if (!$error) { 868 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields"; 869 $sql .= " WHERE fk_object=".$this->id; 870 871 $resql = $this->db->query($sql); 872 if (!$resql) { 873 $this->errors[] = $this->db->lasterror(); 874 $error++; 875 } 876 } 877 878 // Delete project 879 if (!$error) { 880 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet"; 881 $sql .= " WHERE rowid=".((int) $this->id); 882 883 $resql = $this->db->query($sql); 884 if (!$resql) { 885 $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview")); 886 $error++; 887 } 888 } 889 890 891 892 if (empty($error)) { 893 // We remove directory 894 $projectref = dol_sanitizeFileName($this->ref); 895 if ($conf->projet->dir_output) { 896 $dir = $conf->projet->dir_output."/".$projectref; 897 if (file_exists($dir)) { 898 $res = @dol_delete_dir_recursive($dir); 899 if (!$res) { 900 $this->errors[] = 'ErrorFailToDeleteDir'; 901 $error++; 902 } 903 } 904 } 905 906 if (!$notrigger) { 907 // Call trigger 908 $result = $this->call_trigger('PROJECT_DELETE', $user); 909 910 if ($result < 0) { 911 $error++; 912 } 913 // End call triggers 914 } 915 } 916 917 if (empty($error)) { 918 $this->db->commit(); 919 return 1; 920 } else { 921 foreach ($this->errors as $errmsg) { 922 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); 923 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 924 } 925 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR); 926 $this->db->rollback(); 927 return -1; 928 } 929 } 930 931 /** 932 * Return the count of a type of linked elements of this project 933 * 934 * @param string $type The type of the linked elements (e.g. 'propal', 'order', 'invoice', 'order_supplier', 'invoice_supplier') 935 * @param string $tablename The name of table associated of the type 936 * @param string $projectkey (optional) Equivalent key to fk_projet for actual type 937 * @return integer The count of the linked elements (the count is zero on request error too) 938 */ 939 public function getElementCount($type, $tablename, $projectkey = 'fk_projet') 940 { 941 if ($this->id <= 0) { 942 return 0; 943 } 944 945 if ($type == 'agenda') { 946 $sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".$this->id." AND entity IN (".getEntity('agenda').")"; 947 } elseif ($type == 'expensereport') { 948 $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); 949 } elseif ($type == 'project_task') { 950 $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id); 951 } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user 952 $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet = ".((int) $this->id); 953 } elseif ($type == 'stock_mouvement') { 954 $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"; 955 } elseif ($type == 'loan') { 956 $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); 957 } else { 958 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".((int) $this->id)." AND entity IN (".getEntity($type).")"; 959 } 960 961 $result = $this->db->query($sql); 962 963 if (!$result) { 964 return 0; 965 } 966 967 $obj = $this->db->fetch_object($result); 968 969 $this->db->free($result); 970 971 return $obj->nb; 972 } 973 974 /** 975 * Delete tasks with no children first, then task with children recursively 976 * 977 * @param User $user User 978 * @return int <0 if KO, 1 if OK 979 */ 980 public function deleteTasks($user) 981 { 982 $countTasks = count($this->lines); 983 $deleted = false; 984 if ($countTasks) { 985 foreach ($this->lines as $task) { 986 if ($task->hasChildren() <= 0) { // If there is no children (or error to detect them) 987 $deleted = true; 988 $ret = $task->delete($user); 989 if ($ret <= 0) { 990 $this->errors[] = $this->db->lasterror(); 991 return -1; 992 } 993 } 994 } 995 } 996 $this->getLinesArray($user); 997 if ($deleted && count($this->lines) < $countTasks) { 998 if (count($this->lines)) { 999 $this->deleteTasks($this->lines); 1000 } 1001 } 1002 1003 return 1; 1004 } 1005 1006 /** 1007 * Validate a project 1008 * 1009 * @param User $user User that validate 1010 * @param int $notrigger 1=Disable triggers 1011 * @return int <0 if KO, >0 if OK 1012 */ 1013 public function setValid($user, $notrigger = 0) 1014 { 1015 global $langs, $conf; 1016 1017 $error = 0; 1018 1019 if ($this->statut != 1) { 1020 // Check parameters 1021 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) { 1022 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf")); 1023 return -1; 1024 } 1025 1026 $this->db->begin(); 1027 1028 $sql = "UPDATE ".MAIN_DB_PREFIX."projet"; 1029 $sql .= " SET fk_statut = 1"; 1030 $sql .= " WHERE rowid = ".((int) $this->id); 1031 $sql .= " AND entity = ".((int) $conf->entity); 1032 1033 dol_syslog(get_class($this)."::setValid", LOG_DEBUG); 1034 $resql = $this->db->query($sql); 1035 if ($resql) { 1036 // Call trigger 1037 if (empty($notrigger)) { 1038 $result = $this->call_trigger('PROJECT_VALIDATE', $user); 1039 if ($result < 0) { 1040 $error++; 1041 } 1042 // End call triggers 1043 } 1044 1045 if (!$error) { 1046 $this->statut = 1; 1047 $this->db->commit(); 1048 return 1; 1049 } else { 1050 $this->db->rollback(); 1051 $this->error = join(',', $this->errors); 1052 dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR); 1053 return -1; 1054 } 1055 } else { 1056 $this->db->rollback(); 1057 $this->error = $this->db->lasterror(); 1058 return -1; 1059 } 1060 } 1061 } 1062 1063 /** 1064 * Close a project 1065 * 1066 * @param User $user User that close project 1067 * @return int <0 if KO, 0 if already closed, >0 if OK 1068 */ 1069 public function setClose($user) 1070 { 1071 global $langs, $conf; 1072 1073 $now = dol_now(); 1074 1075 $error = 0; 1076 1077 if ($this->statut != self::STATUS_CLOSED) { 1078 $this->db->begin(); 1079 1080 $sql = "UPDATE ".MAIN_DB_PREFIX."projet"; 1081 $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'"; 1082 $sql .= " WHERE rowid = ".$this->id; 1083 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED; 1084 1085 if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) { 1086 // TODO What to do if fk_opp_status is not code 'WON' or 'LOST' 1087 } 1088 1089 dol_syslog(get_class($this)."::setClose", LOG_DEBUG); 1090 $resql = $this->db->query($sql); 1091 if ($resql) { 1092 // Call trigger 1093 $result = $this->call_trigger('PROJECT_CLOSE', $user); 1094 if ($result < 0) { 1095 $error++; 1096 } 1097 // End call triggers 1098 1099 if (!$error) { 1100 $this->statut = 2; 1101 $this->db->commit(); 1102 return 1; 1103 } else { 1104 $this->db->rollback(); 1105 $this->error = join(',', $this->errors); 1106 dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR); 1107 return -1; 1108 } 1109 } else { 1110 $this->db->rollback(); 1111 $this->error = $this->db->lasterror(); 1112 return -1; 1113 } 1114 } 1115 1116 return 0; 1117 } 1118 1119 /** 1120 * Return status label of object 1121 * 1122 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto 1123 * @return string Label 1124 */ 1125 public function getLibStatut($mode = 0) 1126 { 1127 return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode); 1128 } 1129 1130 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1131 /** 1132 * Renvoi status label for a status 1133 * 1134 * @param int $status id status 1135 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 1136 * @return string Label 1137 */ 1138 public function LibStatut($status, $mode = 0) 1139 { 1140 // phpcs:enable 1141 global $langs; 1142 1143 $statustrans = array( 1144 0 => 'status0', 1145 1 => 'status4', 1146 2 => 'status6', 1147 ); 1148 1149 $statusClass = 'status0'; 1150 if (!empty($statustrans[$status])) { 1151 $statusClass = $statustrans[$status]; 1152 } 1153 1154 return dolGetStatus($langs->trans($this->statuts_long[$status]), $langs->trans($this->statuts_short[$status]), '', $statusClass, $mode); 1155 } 1156 1157 /** 1158 * Return clickable name (with picto eventually) 1159 * 1160 * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto 1161 * @param string $option Variant where the link point to ('', 'nolink') 1162 * @param int $addlabel 0=Default, 1=Add label into string, >1=Add first chars into string 1163 * @param string $moreinpopup Text to add into popup 1164 * @param string $sep Separator between ref and label if option addlabel is set 1165 * @param int $notooltip 1=Disable tooltip 1166 * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking 1167 * @param string $morecss More css on a link 1168 * @return string String with URL 1169 */ 1170 public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '') 1171 { 1172 global $conf, $langs, $user, $hookmanager; 1173 1174 if (!empty($conf->dol_no_mouse_hover)) { 1175 $notooltip = 1; // Force disable tooltips 1176 } 1177 1178 $result = ''; 1179 if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) { 1180 $option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB; 1181 } 1182 1183 $label = ''; 1184 if ($option != 'nolink') { 1185 $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Project").'</u>'; 1186 } 1187 if (isset($this->status)) { 1188 $label .= ' '.$this->getLibStatut(5); 1189 } 1190 $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Ref').': </b>'.$this->ref; // The space must be after the : to not being explode when showing the title in img_picto 1191 $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Label').': </b>'.$this->title; // The space must be after the : to not being explode when showing the title in img_picto 1192 if (isset($this->public)) { 1193 $label .= '<br><b>'.$langs->trans("Visibility").":</b> ".($this->public ? $langs->trans("SharedProject") : $langs->trans("PrivateProject")); 1194 } 1195 if (!empty($this->thirdparty_name)) { 1196 $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('ThirdParty').': </b>'.$this->thirdparty_name; // The space must be after the : to not being explode when showing the title in img_picto 1197 } 1198 if (!empty($this->dateo)) { 1199 $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateStart').': </b>'.dol_print_date($this->dateo, 'day'); // The space must be after the : to not being explode when showing the title in img_picto 1200 } 1201 if (!empty($this->datee)) { 1202 $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateEnd').': </b>'.dol_print_date($this->datee, 'day'); // The space must be after the : to not being explode when showing the title in img_picto 1203 } 1204 if ($moreinpopup) { 1205 $label .= '<br>'.$moreinpopup; 1206 } 1207 1208 $url = ''; 1209 if ($option != 'nolink') { 1210 if (preg_match('/\.php$/', $option)) { 1211 $url = dol_buildpath($option, 1).'?id='.$this->id; 1212 } elseif ($option == 'task') { 1213 $url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id; 1214 } elseif ($option == 'preview') { 1215 $url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id; 1216 } elseif ($option == 'eventorganization') { 1217 $url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id; 1218 } else { 1219 $url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id; 1220 } 1221 // Add param to save lastsearch_values or not 1222 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); 1223 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { 1224 $add_save_lastsearch_values = 1; 1225 } 1226 if ($add_save_lastsearch_values) { 1227 $url .= '&save_lastsearch_values=1'; 1228 } 1229 } 1230 1231 $linkclose = ''; 1232 if (empty($notooltip) && $user->rights->projet->lire) { 1233 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { 1234 $label = $langs->trans("ShowProject"); 1235 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; 1236 } 1237 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; 1238 $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"'; 1239 } else { 1240 $linkclose = ($morecss ? ' class="'.$morecss.'"' : ''); 1241 } 1242 1243 $picto = 'projectpub'; 1244 if (!$this->public) { 1245 $picto = 'project'; 1246 } 1247 1248 $linkstart = '<a href="'.$url.'"'; 1249 $linkstart .= $linkclose.'>'; 1250 $linkend = '</a>'; 1251 1252 $result .= $linkstart; 1253 if ($withpicto) { 1254 $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1); 1255 } 1256 if ($withpicto != 2) { 1257 $result .= $this->ref; 1258 } 1259 $result .= $linkend; 1260 if ($withpicto != 2) { 1261 $result .= (($addlabel && $this->title) ? $sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)) : ''); 1262 } 1263 1264 global $action; 1265 $hookmanager->initHooks(array('projectdao')); 1266 $parameters = array('id'=>$this->id, 'getnomurl'=>$result); 1267 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 1268 if ($reshook > 0) { 1269 $result = $hookmanager->resPrint; 1270 } else { 1271 $result .= $hookmanager->resPrint; 1272 } 1273 1274 return $result; 1275 } 1276 1277 /** 1278 * Initialise an instance with random values. 1279 * Used to build previews or test instances. 1280 * id must be 0 if object instance is a specimen. 1281 * 1282 * @return void 1283 */ 1284 public function initAsSpecimen() 1285 { 1286 global $user, $langs, $conf; 1287 1288 $now = dol_now(); 1289 1290 // Initialise parameters 1291 $this->id = 0; 1292 $this->ref = 'SPECIMEN'; 1293 $this->specimen = 1; 1294 $this->socid = 1; 1295 $this->date_c = $now; 1296 $this->date_m = $now; 1297 $this->date_start = $now; 1298 $this->date_end = $now + (3600 * 24 * 365); 1299 $this->note_public = 'SPECIMEN'; 1300 $this->fk_ele = 20000; 1301 $this->opp_amount = 20000; 1302 $this->budget_amount = 10000; 1303 1304 $this->usage_opportunity = 1; 1305 $this->usage_task = 1; 1306 $this->usage_bill_time = 1; 1307 $this->usage_organize_event = 1; 1308 1309 /* 1310 $nbp = mt_rand(1, 9); 1311 $xnbp = 0; 1312 while ($xnbp < $nbp) 1313 { 1314 $line = new Task($this->db); 1315 $line->fk_project = 0; 1316 $line->label = $langs->trans("Label") . " " . $xnbp; 1317 $line->description = $langs->trans("Description") . " " . $xnbp; 1318 1319 $this->lines[]=$line; 1320 $xnbp++; 1321 } 1322 */ 1323 } 1324 1325 /** 1326 * Check if user has permission on current project 1327 * 1328 * @param User $user Object user to evaluate 1329 * @param string $mode Type of permission we want to know: 'read', 'write' 1330 * @return int >0 if user has permission, <0 if user has no permission 1331 */ 1332 public function restrictedProjectArea(User $user, $mode = 'read') 1333 { 1334 // To verify role of users 1335 $userAccess = 0; 1336 if (($mode == 'read' && !empty($user->rights->projet->all->lire)) || ($mode == 'write' && !empty($user->rights->projet->all->creer)) || ($mode == 'delete' && !empty($user->rights->projet->all->supprimer))) { 1337 $userAccess = 1; 1338 } elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer)))) { 1339 $userAccess = 1; 1340 } else { // No access due to permission to read all projects, so we check if we are a contact of project 1341 foreach (array('internal', 'external') as $source) { 1342 $userRole = $this->liste_contact(4, $source); 1343 $num = count($userRole); 1344 1345 $nblinks = 0; 1346 while ($nblinks < $num) { 1347 if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) { // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts 1348 if ($mode == 'read' && $user->rights->projet->lire) { 1349 $userAccess++; 1350 } 1351 if ($mode == 'write' && $user->rights->projet->creer) { 1352 $userAccess++; 1353 } 1354 if ($mode == 'delete' && $user->rights->projet->supprimer) { 1355 $userAccess++; 1356 } 1357 } 1358 if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) { // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts 1359 if ($mode == 'read' && $user->rights->projet->lire) { 1360 $userAccess++; 1361 } 1362 if ($mode == 'write' && $user->rights->projet->creer) { 1363 $userAccess++; 1364 } 1365 if ($mode == 'delete' && $user->rights->projet->supprimer) { 1366 $userAccess++; 1367 } 1368 } 1369 $nblinks++; 1370 } 1371 } 1372 //if (empty($nblinks)) // If nobody has permission, we grant creator 1373 //{ 1374 // if ((!empty($this->user_author_id) && $this->user_author_id == $user->id)) 1375 // { 1376 // $userAccess = 1; 1377 // } 1378 //} 1379 } 1380 1381 return ($userAccess ? $userAccess : -1); 1382 } 1383 1384 /** 1385 * Return array of projects a user has permission on, is affected to, or all projects 1386 * 1387 * @param User $user User object 1388 * @param int $mode 0=All project I have permission on (assigned to me or public), 1=Projects assigned to me only, 2=Will return list of all projects with no test on contacts 1389 * @param int $list 0=Return array, 1=Return string list 1390 * @param int $socid 0=No filter on third party, id of third party 1391 * @param string $filter additionnal filter on project (statut, ref, ...) 1392 * @return array or string Array of projects id, or string with projects id separated with "," if list is 1 1393 */ 1394 public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '') 1395 { 1396 $projects = array(); 1397 $temp = array(); 1398 1399 $sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref"; 1400 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; 1401 if ($mode == 0) { 1402 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid"; 1403 } elseif ($mode == 1) { 1404 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; 1405 } elseif ($mode == 2) { 1406 // No filter. Use this if user has permission to see all project 1407 } 1408 $sql .= " WHERE p.entity IN (".getEntity('project').")"; 1409 // Internal users must see project he is contact to even if project linked to a third party he can't see. 1410 //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).")"; 1411 if ($socid > 0) { 1412 $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")"; 1413 } 1414 1415 // Get id of types of contacts for projects (This list never contains a lot of elements) 1416 $listofprojectcontacttype = array(); 1417 $sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc"; 1418 $sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'"; 1419 $sql2 .= " AND ctc.source = 'internal'"; 1420 $resql = $this->db->query($sql2); 1421 if ($resql) { 1422 while ($obj = $this->db->fetch_object($resql)) { 1423 $listofprojectcontacttype[$obj->rowid] = $obj->code; 1424 } 1425 } else { 1426 dol_print_error($this->db); 1427 } 1428 if (count($listofprojectcontacttype) == 0) { 1429 $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found 1430 } 1431 1432 if ($mode == 0) { 1433 $sql .= " AND ( p.public = 1"; 1434 $sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")"; 1435 $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")"; 1436 $sql .= " )"; 1437 } elseif ($mode == 1) { 1438 $sql .= " AND ec.element_id = p.rowid"; 1439 $sql .= " AND ("; 1440 $sql .= " ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")"; 1441 $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")"; 1442 $sql .= " )"; 1443 } elseif ($mode == 2) { 1444 // No filter. Use this if user has permission to see all project 1445 } 1446 1447 $sql .= $filter; 1448 //print $sql; 1449 1450 $resql = $this->db->query($sql); 1451 if ($resql) { 1452 $num = $this->db->num_rows($resql); 1453 $i = 0; 1454 while ($i < $num) { 1455 $row = $this->db->fetch_row($resql); 1456 $projects[$row[0]] = $row[1]; 1457 $temp[] = $row[0]; 1458 $i++; 1459 } 1460 1461 $this->db->free($resql); 1462 1463 if ($list) { 1464 if (empty($temp)) { 1465 return '0'; 1466 } 1467 $result = implode(',', $temp); 1468 return $result; 1469 } 1470 } else { 1471 dol_print_error($this->db); 1472 } 1473 1474 return $projects; 1475 } 1476 1477 /** 1478 * Load an object from its id and create a new one in database 1479 * 1480 * @param User $user User making the clone 1481 * @param int $fromid Id of object to clone 1482 * @param bool $clone_contact Clone contact of project 1483 * @param bool $clone_task Clone task of project 1484 * @param bool $clone_project_file Clone file of project 1485 * @param bool $clone_task_file Clone file of task (if task are copied) 1486 * @param bool $clone_note Clone note of project 1487 * @param bool $move_date Move task date on clone 1488 * @param integer $notrigger No trigger flag 1489 * @param int $newthirdpartyid New thirdparty id 1490 * @return int New id of clone 1491 */ 1492 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) 1493 { 1494 global $langs, $conf; 1495 1496 $error = 0; 1497 1498 dol_syslog("createFromClone clone_contact=".$clone_contact." clone_task=".$clone_task." clone_project_file=".$clone_project_file." clone_note=".$clone_note." move_date=".$move_date, LOG_DEBUG); 1499 1500 $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now())); 1501 1502 $clone_project = new Project($this->db); 1503 1504 $clone_project->context['createfromclone'] = 'createfromclone'; 1505 1506 $this->db->begin(); 1507 1508 // Load source object 1509 $clone_project->fetch($fromid); 1510 $clone_project->fetch_optionals(); 1511 if ($newthirdpartyid > 0) { 1512 $clone_project->socid = $newthirdpartyid; 1513 } 1514 $clone_project->fetch_thirdparty(); 1515 1516 $orign_dt_start = $clone_project->date_start; 1517 $orign_project_ref = $clone_project->ref; 1518 1519 $clone_project->id = 0; 1520 if ($move_date) { 1521 $clone_project->date_start = $now; 1522 if (!(empty($clone_project->date_end))) { 1523 $clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start); 1524 } 1525 } 1526 1527 $clone_project->date_c = $now; 1528 1529 if (!$clone_note) { 1530 $clone_project->note_private = ''; 1531 $clone_project->note_public = ''; 1532 } 1533 1534 //Generate next ref 1535 $defaultref = ''; 1536 $obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON; 1537 // Search template files 1538 $file = ''; $classname = ''; $filefound = 0; 1539 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); 1540 foreach ($dirmodels as $reldir) { 1541 $file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0); 1542 if (file_exists($file)) { 1543 $filefound = 1; 1544 dol_include_once($reldir."core/modules/project/".$obj.'.php'); 1545 $modProject = new $obj; 1546 $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project); 1547 break; 1548 } 1549 } 1550 if (is_numeric($defaultref) && $defaultref <= 0) { 1551 $defaultref = ''; 1552 } 1553 1554 $clone_project->ref = $defaultref; 1555 $clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title; 1556 1557 // Create clone 1558 $result = $clone_project->create($user, $notrigger); 1559 1560 // Other options 1561 if ($result < 0) { 1562 $this->error .= $clone_project->error; 1563 $error++; 1564 } 1565 1566 if (!$error) { 1567 //Get the new project id 1568 $clone_project_id = $clone_project->id; 1569 1570 //Note Update 1571 if (!$clone_note) { 1572 $clone_project->note_private = ''; 1573 $clone_project->note_public = ''; 1574 } else { 1575 $this->db->begin(); 1576 $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public'); 1577 if ($res < 0) { 1578 $this->error .= $clone_project->error; 1579 $error++; 1580 $this->db->rollback(); 1581 } else { 1582 $this->db->commit(); 1583 } 1584 1585 $this->db->begin(); 1586 $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private'); 1587 if ($res < 0) { 1588 $this->error .= $clone_project->error; 1589 $error++; 1590 $this->db->rollback(); 1591 } else { 1592 $this->db->commit(); 1593 } 1594 } 1595 1596 //Duplicate contact 1597 if ($clone_contact) { 1598 $origin_project = new Project($this->db); 1599 $origin_project->fetch($fromid); 1600 1601 foreach (array('internal', 'external') as $source) { 1602 $tab = $origin_project->liste_contact(-1, $source); 1603 1604 foreach ($tab as $contacttoadd) { 1605 $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger); 1606 if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') { 1607 $langs->load("errors"); 1608 $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType"); 1609 $error++; 1610 } else { 1611 if ($clone_project->error != '') { 1612 $this->error .= $clone_project->error; 1613 $error++; 1614 } 1615 } 1616 } 1617 } 1618 } 1619 1620 //Duplicate file 1621 if ($clone_project_file) { 1622 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 1623 1624 $clone_project_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($defaultref); 1625 $ori_project_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($orign_project_ref); 1626 1627 if (dol_mkdir($clone_project_dir) >= 0) { 1628 $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1); 1629 foreach ($filearray as $key => $file) { 1630 $rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1); 1631 if (is_numeric($rescopy) && $rescopy < 0) { 1632 $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']); 1633 $error++; 1634 } 1635 } 1636 } else { 1637 $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir'; 1638 $error++; 1639 } 1640 } 1641 1642 //Duplicate task 1643 if ($clone_task) { 1644 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; 1645 1646 $taskstatic = new Task($this->db); 1647 1648 // Security check 1649 $socid = 0; 1650 if ($user->socid > 0) { 1651 $socid = $user->socid; 1652 } 1653 1654 $tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0); 1655 1656 $tab_conv_child_parent = array(); 1657 1658 // Loop on each task, to clone it 1659 foreach ($tasksarray as $tasktoclone) { 1660 $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false); 1661 if ($result_clone <= 0) { 1662 $this->error .= $result_clone->error; 1663 $error++; 1664 } else { 1665 $new_task_id = $result_clone; 1666 $taskstatic->fetch($tasktoclone->id); 1667 1668 //manage new parent clone task id 1669 // if the current task has child we store the original task id and the equivalent clone task id 1670 if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) { 1671 $tab_conv_child_parent[$tasktoclone->id] = $new_task_id; 1672 } 1673 } 1674 } 1675 1676 //Parse all clone node to be sure to update new parent 1677 $tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0); 1678 foreach ($tasksarray as $task_cloned) { 1679 $taskstatic->fetch($task_cloned->id); 1680 if ($taskstatic->fk_task_parent != 0) { 1681 $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent]; 1682 } 1683 $res = $taskstatic->update($user, $notrigger); 1684 if ($result_clone <= 0) { 1685 $this->error .= $taskstatic->error; 1686 $error++; 1687 } 1688 } 1689 } 1690 } 1691 1692 unset($clone_project->context['createfromclone']); 1693 1694 if (!$error) { 1695 $this->db->commit(); 1696 return $clone_project_id; 1697 } else { 1698 $this->db->rollback(); 1699 dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR); 1700 return -1; 1701 } 1702 } 1703 1704 1705 /** 1706 * Shift project task date from current date to delta 1707 * 1708 * @param integer $old_project_dt_start Old project start date 1709 * @return int 1 if OK or < 0 if KO 1710 */ 1711 public function shiftTaskDate($old_project_dt_start) 1712 { 1713 global $user, $langs, $conf; 1714 1715 $error = 0; 1716 1717 $taskstatic = new Task($this->db); 1718 1719 // Security check 1720 $socid = 0; 1721 if ($user->socid > 0) { 1722 $socid = $user->socid; 1723 } 1724 1725 $tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0); 1726 1727 foreach ($tasksarray as $tasktoshiftdate) { 1728 $to_update = false; 1729 // Fetch only if update of date will be made 1730 if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) { 1731 //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG); 1732 $to_update = true; 1733 $task = new Task($this->db); 1734 $result = $task->fetch($tasktoshiftdate->id); 1735 if (!$result) { 1736 $error++; 1737 $this->error .= $task->error; 1738 } 1739 } 1740 //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit; 1741 1742 //Calcultate new task start date with difference between old proj start date and origin task start date 1743 if (!empty($tasktoshiftdate->date_start)) { 1744 $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start); 1745 } 1746 1747 //Calcultate new task end date with difference between origin proj end date and origin task end date 1748 if (!empty($tasktoshiftdate->date_end)) { 1749 $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start); 1750 } 1751 1752 if ($to_update) { 1753 $result = $task->update($user); 1754 if (!$result) { 1755 $error++; 1756 $this->error .= $task->error; 1757 } 1758 } 1759 } 1760 if ($error != 0) { 1761 return -1; 1762 } 1763 return $result; 1764 } 1765 1766 1767 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1768 /** 1769 * Associate element to a project 1770 * 1771 * @param string $tableName Table of the element to update 1772 * @param int $elementSelectId Key-rowid of the line of the element to update 1773 * @return int 1 if OK or < 0 if KO 1774 */ 1775 public function update_element($tableName, $elementSelectId) 1776 { 1777 // phpcs:enable 1778 $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; 1779 1780 if ($tableName == "actioncomm") { 1781 $sql .= " SET fk_project=".$this->id; 1782 $sql .= " WHERE id=".((int) $elementSelectId); 1783 } elseif ($tableName == "entrepot") { 1784 $sql .= " SET fk_project=".$this->id; 1785 $sql .= " WHERE rowid=".((int) $elementSelectId); 1786 } else { 1787 $sql .= " SET fk_projet=".$this->id; 1788 $sql .= " WHERE rowid=".((int) $elementSelectId); 1789 } 1790 1791 dol_syslog(get_class($this)."::update_element", LOG_DEBUG); 1792 $resql = $this->db->query($sql); 1793 if (!$resql) { 1794 $this->error = $this->db->lasterror(); 1795 return -1; 1796 } else { 1797 return 1; 1798 } 1799 } 1800 1801 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1802 /** 1803 * Associate element to a project 1804 * 1805 * @param string $tableName Table of the element to update 1806 * @param int $elementSelectId Key-rowid of the line of the element to update 1807 * @param string $projectfield The column name that stores the link with the project 1808 * 1809 * @return int 1 if OK or < 0 if KO 1810 */ 1811 public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet') 1812 { 1813 // phpcs:enable 1814 $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName; 1815 1816 if ($tableName == "actioncomm") { 1817 $sql .= " SET fk_project=NULL"; 1818 $sql .= " WHERE id=".((int) $elementSelectId); 1819 } else { 1820 $sql .= " SET ".$projectfield."=NULL"; 1821 $sql .= " WHERE rowid=".((int) $elementSelectId); 1822 } 1823 1824 dol_syslog(get_class($this)."::remove_element", LOG_DEBUG); 1825 $resql = $this->db->query($sql); 1826 if (!$resql) { 1827 $this->error = $this->db->lasterror(); 1828 return -1; 1829 } else { 1830 return 1; 1831 } 1832 } 1833 1834 /** 1835 * Create an intervention document on disk using template defined into PROJECT_ADDON_PDF 1836 * 1837 * @param string $modele Force template to use ('' by default) 1838 * @param Translate $outputlangs Objet lang to use for translation 1839 * @param int $hidedetails Hide details of lines 1840 * @param int $hidedesc Hide description 1841 * @param int $hideref Hide ref 1842 * @return int 0 if KO, 1 if OK 1843 */ 1844 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) 1845 { 1846 global $conf, $langs; 1847 1848 $langs->load("projects"); 1849 1850 if (!dol_strlen($modele)) { 1851 $modele = 'baleine'; 1852 1853 if ($this->model_pdf) { 1854 $modele = $this->model_pdf; 1855 } elseif (!empty($conf->global->PROJECT_ADDON_PDF)) { 1856 $modele = $conf->global->PROJECT_ADDON_PDF; 1857 } 1858 } 1859 1860 $modelpath = "core/modules/project/doc/"; 1861 1862 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref); 1863 } 1864 1865 1866 /** 1867 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project. 1868 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call. 1869 * 1870 * @param int $datestart First day of week (use dol_get_first_day to find this date) 1871 * @param int $taskid Filter on a task id 1872 * @param int $userid Time spent by a particular user 1873 * @return int <0 if OK, >0 if KO 1874 */ 1875 public function loadTimeSpent($datestart, $taskid = 0, $userid = 0) 1876 { 1877 $error = 0; 1878 1879 $this->weekWorkLoad = array(); 1880 $this->weekWorkLoadPerTask = array(); 1881 1882 if (empty($datestart)) { 1883 dol_print_error('', 'Error datestart parameter is empty'); 1884 } 1885 1886 $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task"; 1887 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt"; 1888 $sql .= " WHERE ptt.fk_task = pt.rowid"; 1889 $sql .= " AND pt.fk_projet = ".((int) $this->id); 1890 $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' "; 1891 $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')"; 1892 if ($taskid) { 1893 $sql .= " AND ptt.fk_task=".((int) $taskid); 1894 } 1895 if (is_numeric($userid)) { 1896 $sql .= " AND ptt.fk_user=".((int) $userid); 1897 } 1898 1899 //print $sql; 1900 $resql = $this->db->query($sql); 1901 if ($resql) { 1902 $daylareadyfound = array(); 1903 1904 $num = $this->db->num_rows($resql); 1905 $i = 0; 1906 // Loop on each record found, so each couple (project id, task id) 1907 while ($i < $num) { 1908 $obj = $this->db->fetch_object($resql); 1909 $day = $this->db->jdate($obj->task_date); // task_date is date without hours 1910 if (empty($daylareadyfound[$day])) { 1911 $this->weekWorkLoad[$day] = $obj->task_duration; 1912 $this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration; 1913 } else { 1914 $this->weekWorkLoad[$day] += $obj->task_duration; 1915 $this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration; 1916 } 1917 $daylareadyfound[$day] = 1; 1918 $i++; 1919 } 1920 $this->db->free($resql); 1921 return 1; 1922 } else { 1923 $this->error = "Error ".$this->db->lasterror(); 1924 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR); 1925 return -1; 1926 } 1927 } 1928 1929 /** 1930 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project. 1931 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call. 1932 * 1933 * @param int $datestart First day of week (use dol_get_first_day to find this date) 1934 * @param int $taskid Filter on a task id 1935 * @param int $userid Time spent by a particular user 1936 * @return int <0 if OK, >0 if KO 1937 */ 1938 public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0) 1939 { 1940 $error = 0; 1941 1942 $this->monthWorkLoad = array(); 1943 $this->monthWorkLoadPerTask = array(); 1944 1945 if (empty($datestart)) { 1946 dol_print_error('', 'Error datestart parameter is empty'); 1947 } 1948 1949 $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task"; 1950 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt"; 1951 $sql .= " WHERE ptt.fk_task = pt.rowid"; 1952 $sql .= " AND pt.fk_projet = ".((int) $this->id); 1953 $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' "; 1954 $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')"; 1955 if ($task_id) { 1956 $sql .= " AND ptt.fk_task=".((int) $taskid); 1957 } 1958 if (is_numeric($userid)) { 1959 $sql .= " AND ptt.fk_user=".((int) $userid); 1960 } 1961 1962 //print $sql; 1963 $resql = $this->db->query($sql); 1964 if ($resql) { 1965 $weekalreadyfound = array(); 1966 1967 $num = $this->db->num_rows($resql); 1968 $i = 0; 1969 // Loop on each record found, so each couple (project id, task id) 1970 while ($i < $num) { 1971 $obj = $this->db->fetch_object($resql); 1972 if (!empty($obj->task_date)) { 1973 $date = explode('-', $obj->task_date); 1974 $week_number = getWeekNumber($date[2], $date[1], $date[0]); 1975 } 1976 if (empty($weekalreadyfound[$week_number])) { 1977 $this->monthWorkLoad[$week_number] = $obj->task_duration; 1978 $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration; 1979 } else { 1980 $this->monthWorkLoad[$week_number] += $obj->task_duration; 1981 $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration; 1982 } 1983 $weekalreadyfound[$week_number] = 1; 1984 $i++; 1985 } 1986 $this->db->free($resql); 1987 return 1; 1988 } else { 1989 $this->error = "Error ".$this->db->lasterror(); 1990 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR); 1991 return -1; 1992 } 1993 } 1994 1995 1996 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1997 /** 1998 * Load indicators for dashboard (this->nbtodo and this->nbtodolate) 1999 * 2000 * @param User $user Objet user 2001 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK 2002 */ 2003 public function load_board($user) 2004 { 2005 // phpcs:enable 2006 global $conf, $langs; 2007 2008 // For external user, no check is done on company because readability is managed by public status of project and assignement. 2009 //$socid=$user->socid; 2010 2011 $projectsListId = null; 2012 if (!$user->rights->projet->all->lire) { 2013 $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1); 2014 } 2015 2016 $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee"; 2017 $sql .= " FROM (".MAIN_DB_PREFIX."projet as p"; 2018 $sql .= ")"; 2019 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid"; 2020 // For external user, no check is done on company permission because readability is managed by public status of project and assignement. 2021 //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid"; 2022 $sql .= " WHERE p.fk_statut = 1"; 2023 $sql .= " AND p.entity IN (".getEntity('project').')'; 2024 if (!empty($projectsListId)) { 2025 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")"; 2026 } 2027 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser 2028 //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).")"; 2029 // For external user, no check is done on company permission because readability is managed by public status of project and assignement. 2030 //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))"; 2031 2032 //print $sql; 2033 $resql = $this->db->query($sql); 2034 if ($resql) { 2035 $project_static = new Project($this->db); 2036 2037 $response = new WorkboardResponse(); 2038 $response->warning_delay = $conf->projet->warning_delay / 60 / 60 / 24; 2039 $response->label = $langs->trans("OpenedProjects"); 2040 $response->labelShort = $langs->trans("Opened"); 2041 if ($user->rights->projet->all->lire) { 2042 $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project'; 2043 } else { 2044 $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project'; 2045 } 2046 $response->img = img_object('', "projectpub"); 2047 2048 // This assignment in condition is not a bug. It allows walking the results. 2049 while ($obj = $this->db->fetch_object($resql)) { 2050 $response->nbtodo++; 2051 2052 $project_static->statut = $obj->status; 2053 $project_static->opp_status = $obj->fk_opp_status; 2054 $project_static->datee = $this->db->jdate($obj->datee); 2055 2056 if ($project_static->hasDelay()) { 2057 $response->nbtodolate++; 2058 } 2059 } 2060 2061 return $response; 2062 } else { 2063 $this->error = $this->db->error(); 2064 return -1; 2065 } 2066 } 2067 2068 2069 /** 2070 * Function used to replace a thirdparty id with another one. 2071 * 2072 * @param DoliDB $db Database handler 2073 * @param int $origin_id Old thirdparty id 2074 * @param int $dest_id New thirdparty id 2075 * @return bool 2076 */ 2077 public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id) 2078 { 2079 $tables = array( 2080 'projet' 2081 ); 2082 2083 return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables); 2084 } 2085 2086 2087 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2088 /** 2089 * Charge indicateurs this->nb pour le tableau de bord 2090 * 2091 * @return int <0 if KO, >0 if OK 2092 */ 2093 public function load_state_board() 2094 { 2095 // phpcs:enable 2096 global $user; 2097 2098 $this->nb = array(); 2099 2100 $sql = "SELECT count(p.rowid) as nb"; 2101 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; 2102 $sql .= " WHERE"; 2103 $sql .= " p.entity IN (".getEntity('project').")"; 2104 if (!$user->rights->projet->all->lire) { 2105 $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1); 2106 $sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")"; 2107 } 2108 2109 $resql = $this->db->query($sql); 2110 if ($resql) { 2111 while ($obj = $this->db->fetch_object($resql)) { 2112 $this->nb["projects"] = $obj->nb; 2113 } 2114 $this->db->free($resql); 2115 return 1; 2116 } else { 2117 dol_print_error($this->db); 2118 $this->error = $this->db->error(); 2119 return -1; 2120 } 2121 } 2122 2123 2124 /** 2125 * Is the project delayed? 2126 * 2127 * @return bool 2128 */ 2129 public function hasDelay() 2130 { 2131 global $conf; 2132 2133 if (!($this->statut == self::STATUS_VALIDATED)) { 2134 return false; 2135 } 2136 if (!$this->datee && !$this->date_end) { 2137 return false; 2138 } 2139 2140 $now = dol_now(); 2141 2142 return ($this->datee ? $this->datee : $this->date_end) < ($now - $conf->projet->warning_delay); 2143 } 2144 2145 2146 /** 2147 * Charge les informations d'ordre info dans l'objet commande 2148 * 2149 * @param int $id Id of order 2150 * @return void 2151 */ 2152 public function info($id) 2153 { 2154 $sql = 'SELECT c.rowid, datec as datec, tms as datem,'; 2155 $sql .= ' date_close as datecloture,'; 2156 $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture'; 2157 $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c'; 2158 $sql .= ' WHERE c.rowid = '.((int) $id); 2159 $result = $this->db->query($sql); 2160 if ($result) { 2161 if ($this->db->num_rows($result)) { 2162 $obj = $this->db->fetch_object($result); 2163 $this->id = $obj->rowid; 2164 if ($obj->fk_user_author) { 2165 $cuser = new User($this->db); 2166 $cuser->fetch($obj->fk_user_author); 2167 $this->user_creation = $cuser; 2168 } 2169 2170 if ($obj->fk_user_cloture) { 2171 $cluser = new User($this->db); 2172 $cluser->fetch($obj->fk_user_cloture); 2173 $this->user_cloture = $cluser; 2174 } 2175 2176 $this->date_creation = $this->db->jdate($obj->datec); 2177 $this->date_modification = $this->db->jdate($obj->datem); 2178 $this->date_cloture = $this->db->jdate($obj->datecloture); 2179 } 2180 2181 $this->db->free($result); 2182 } else { 2183 dol_print_error($this->db); 2184 } 2185 } 2186 2187 /** 2188 * Sets object to supplied categories. 2189 * 2190 * Deletes object from existing categories not supplied. 2191 * Adds it to non existing supplied categories. 2192 * Existing categories are left untouch. 2193 * 2194 * @param int[]|int $categories Category or categories IDs 2195 * @return void 2196 */ 2197 public function setCategories($categories) 2198 { 2199 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; 2200 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT); 2201 } 2202 2203 2204 /** 2205 * Create an array of tasks of current project 2206 * 2207 * @param User $user Object user we want project allowed to 2208 * @return int >0 if OK, <0 if KO 2209 */ 2210 public function getLinesArray($user) 2211 { 2212 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; 2213 $taskstatic = new Task($this->db); 2214 2215 $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0); 2216 } 2217} 2218