1<?php 2/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr> 3 * Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use Luracast\Restler\RestException; 20 21 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; 22 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; 23 24/** 25 * API class for projects 26 * 27 * @access protected 28 * @class DolibarrApiAccess {@requires user,external} 29 */ 30class Projects extends DolibarrApi 31{ 32 33 /** 34 * @var array $FIELDS Mandatory fields, checked when create and update object 35 */ 36 public static $FIELDS = array( 37 'ref', 38 'title' 39 ); 40 41 /** 42 * @var Project $project {@type Project} 43 */ 44 public $project; 45 46 /** 47 * Constructor 48 */ 49 public function __construct() 50 { 51 global $db, $conf; 52 $this->db = $db; 53 $this->project = new Project($this->db); 54 $this->task = new Task($this->db); 55 } 56 57 /** 58 * Get properties of a project object 59 * 60 * Return an array with project informations 61 * 62 * @param int $id ID of project 63 * @return array|mixed data without useless information 64 * 65 * @throws RestException 66 */ 67 public function get($id) 68 { 69 if (!DolibarrApiAccess::$user->rights->projet->lire) { 70 throw new RestException(401); 71 } 72 73 $result = $this->project->fetch($id); 74 if (!$result) { 75 throw new RestException(404, 'Project not found'); 76 } 77 78 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 79 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 80 } 81 82 $this->project->fetchObjectLinked(); 83 return $this->_cleanObjectDatas($this->project); 84 } 85 86 87 88 /** 89 * List projects 90 * 91 * Get a list of projects 92 * 93 * @param string $sortfield Sort field 94 * @param string $sortorder Sort order 95 * @param int $limit Limit for list 96 * @param int $page Page number 97 * @param string $thirdparty_ids Thirdparty ids to filter projects of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i} 98 * @param int $category Use this param to filter list by category 99 * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')" 100 * @return array Array of project objects 101 */ 102 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $category = 0, $sqlfilters = '') 103 { 104 global $db, $conf; 105 106 if (!DolibarrApiAccess::$user->rights->projet->lire) { 107 throw new RestException(401); 108 } 109 110 $obj_ret = array(); 111 112 // case of external user, $thirdparty_ids param is ignored and replaced by user's socid 113 $socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids; 114 115 // If the internal user must only see his customers, force searching by him 116 $search_sale = 0; 117 if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) { 118 $search_sale = DolibarrApiAccess::$user->id; 119 } 120 121 $sql = "SELECT t.rowid"; 122 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 123 $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects) 124 } 125 $sql .= " FROM ".MAIN_DB_PREFIX."projet as t"; 126 if ($category > 0) { 127 $sql .= ", ".MAIN_DB_PREFIX."categorie_project as c"; 128 } 129 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 130 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale 131 } 132 133 $sql .= ' WHERE t.entity IN ('.getEntity('project').')'; 134 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 135 $sql .= " AND t.fk_soc = sc.fk_soc"; 136 } 137 if ($socids) { 138 $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")"; 139 } 140 if ($search_sale > 0) { 141 $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale 142 } 143 // Insert sale filter 144 if ($search_sale > 0) { 145 $sql .= " AND sc.fk_user = ".((int) $search_sale); 146 } 147 // Select projects of given category 148 if ($category > 0) { 149 $sql .= " AND c.fk_categorie = ".((int) $category)." AND c.fk_project = t.rowid "; 150 } 151 // Add sql filters 152 if ($sqlfilters) { 153 if (!DolibarrApi::_checkFilters($sqlfilters)) { 154 throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters); 155 } 156 $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)'; 157 $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; 158 } 159 160 $sql .= $this->db->order($sortfield, $sortorder); 161 if ($limit) { 162 if ($page < 0) { 163 $page = 0; 164 } 165 $offset = $limit * $page; 166 167 $sql .= $this->db->plimit($limit + 1, $offset); 168 } 169 170 dol_syslog("API Rest request"); 171 $result = $this->db->query($sql); 172 173 if ($result) { 174 $num = $this->db->num_rows($result); 175 $min = min($num, ($limit <= 0 ? $num : $limit)); 176 $i = 0; 177 while ($i < $min) { 178 $obj = $this->db->fetch_object($result); 179 $project_static = new Project($this->db); 180 if ($project_static->fetch($obj->rowid)) { 181 $obj_ret[] = $this->_cleanObjectDatas($project_static); 182 } 183 $i++; 184 } 185 } else { 186 throw new RestException(503, 'Error when retrieve project list : '.$this->db->lasterror()); 187 } 188 if (!count($obj_ret)) { 189 throw new RestException(404, 'No project found'); 190 } 191 return $obj_ret; 192 } 193 194 /** 195 * Create project object 196 * 197 * @param array $request_data Request data 198 * @return int ID of project 199 */ 200 public function post($request_data = null) 201 { 202 if (!DolibarrApiAccess::$user->rights->projet->creer) { 203 throw new RestException(401, "Insuffisant rights"); 204 } 205 // Check mandatory fields 206 $result = $this->_validate($request_data); 207 208 foreach ($request_data as $field => $value) { 209 $this->project->$field = $value; 210 } 211 /*if (isset($request_data["lines"])) { 212 $lines = array(); 213 foreach ($request_data["lines"] as $line) { 214 array_push($lines, (object) $line); 215 } 216 $this->project->lines = $lines; 217 }*/ 218 if ($this->project->create(DolibarrApiAccess::$user) < 0) { 219 throw new RestException(500, "Error creating project", array_merge(array($this->project->error), $this->project->errors)); 220 } 221 222 return $this->project->id; 223 } 224 225 /** 226 * Get tasks of a project. 227 * See also API /tasks 228 * 229 * @param int $id Id of project 230 * @param int $includetimespent 0=Return only list of tasks. 1=Include a summary of time spent, 2=Include details of time spent lines (2 is no implemented yet) 231 * @return int 232 * 233 * @url GET {id}/tasks 234 */ 235 public function getLines($id, $includetimespent = 0) 236 { 237 if (!DolibarrApiAccess::$user->rights->projet->lire) { 238 throw new RestException(401); 239 } 240 241 $result = $this->project->fetch($id); 242 if (!$result) { 243 throw new RestException(404, 'Project not found'); 244 } 245 246 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 247 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 248 } 249 $this->project->getLinesArray(DolibarrApiAccess::$user); 250 $result = array(); 251 foreach ($this->project->lines as $line) { // $line is a task 252 if ($includetimespent == 1) { 253 $timespent = $line->getSummaryOfTimeSpent(0); 254 } 255 if ($includetimespent == 1) { 256 // TODO 257 // Add class for timespent records and loop and fill $line->lines with records of timespent 258 } 259 array_push($result, $this->_cleanObjectDatas($line)); 260 } 261 return $result; 262 } 263 264 265 /** 266 * Get roles a user is assigned to a project with 267 * 268 * @param int $id Id of project 269 * @param int $userid Id of user (0 = connected user) 270 * 271 * @url GET {id}/roles 272 * 273 * @return int 274 */ 275 public function getRoles($id, $userid = 0) 276 { 277 global $db; 278 279 if (!DolibarrApiAccess::$user->rights->projet->lire) { 280 throw new RestException(401); 281 } 282 283 $result = $this->project->fetch($id); 284 if (!$result) { 285 throw new RestException(404, 'Project not found'); 286 } 287 288 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 289 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 290 } 291 292 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; 293 $taskstatic = new Task($this->db); 294 $userp = DolibarrApiAccess::$user; 295 if ($userid > 0) { 296 $userp = new User($this->db); 297 $userp->fetch($userid); 298 } 299 $this->project->roles = $taskstatic->getUserRolesForProjectsOrTasks($userp, 0, $id, 0); 300 $result = array(); 301 foreach ($this->project->roles as $line) { 302 array_push($result, $this->_cleanObjectDatas($line)); 303 } 304 return $result; 305 } 306 307 308 /** 309 * Add a task to given project 310 * 311 * @param int $id Id of project to update 312 * @param array $request_data Projectline data 313 * 314 * @url POST {id}/tasks 315 * 316 * @return int 317 */ 318 /* 319 public function postLine($id, $request_data = null) 320 { 321 if(! DolibarrApiAccess::$user->rights->projet->creer) { 322 throw new RestException(401); 323 } 324 325 $result = $this->project->fetch($id); 326 if( ! $result ) { 327 throw new RestException(404, 'Project not found'); 328 } 329 330 if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) { 331 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 332 } 333 334 $request_data = (object) $request_data; 335 336 $request_data->desc = checkVal($request_data->desc, 'restricthtml'); 337 338 $updateRes = $this->project->addline( 339 $request_data->desc, 340 $request_data->subprice, 341 $request_data->qty, 342 $request_data->tva_tx, 343 $request_data->localtax1_tx, 344 $request_data->localtax2_tx, 345 $request_data->fk_product, 346 $request_data->remise_percent, 347 $request_data->info_bits, 348 $request_data->fk_remise_except, 349 'HT', 350 0, 351 $request_data->date_start, 352 $request_data->date_end, 353 $request_data->product_type, 354 $request_data->rang, 355 $request_data->special_code, 356 $fk_parent_line, 357 $request_data->fk_fournprice, 358 $request_data->pa_ht, 359 $request_data->label, 360 $request_data->array_options, 361 $request_data->fk_unit, 362 $this->element, 363 $request_data->id 364 ); 365 366 if ($updateRes > 0) { 367 return $updateRes; 368 369 } 370 return false; 371 } 372 */ 373 374 /** 375 * Update a task to given project 376 * 377 * @param int $id Id of project to update 378 * @param int $taskid Id of task to update 379 * @param array $request_data Projectline data 380 * 381 * @url PUT {id}/tasks/{taskid} 382 * 383 * @return object 384 */ 385 /* 386 public function putLine($id, $lineid, $request_data = null) 387 { 388 if(! DolibarrApiAccess::$user->rights->projet->creer) { 389 throw new RestException(401); 390 } 391 392 $result = $this->project->fetch($id); 393 if( ! $result ) { 394 throw new RestException(404, 'Project not found'); 395 } 396 397 if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) { 398 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 399 } 400 401 $request_data = (object) $request_data; 402 403 $request_data->desc = checkVal($request_data->desc, 'restricthtml'); 404 405 $updateRes = $this->project->updateline( 406 $lineid, 407 $request_data->desc, 408 $request_data->subprice, 409 $request_data->qty, 410 $request_data->remise_percent, 411 $request_data->tva_tx, 412 $request_data->localtax1_tx, 413 $request_data->localtax2_tx, 414 'HT', 415 $request_data->info_bits, 416 $request_data->date_start, 417 $request_data->date_end, 418 $request_data->product_type, 419 $request_data->fk_parent_line, 420 0, 421 $request_data->fk_fournprice, 422 $request_data->pa_ht, 423 $request_data->label, 424 $request_data->special_code, 425 $request_data->array_options, 426 $request_data->fk_unit 427 ); 428 429 if ($updateRes > 0) { 430 $result = $this->get($id); 431 unset($result->line); 432 return $this->_cleanObjectDatas($result); 433 } 434 return false; 435 }*/ 436 437 438 439 /** 440 * Update project general fields (won't touch lines of project) 441 * 442 * @param int $id Id of project to update 443 * @param array $request_data Datas 444 * 445 * @return int 446 */ 447 public function put($id, $request_data = null) 448 { 449 if (!DolibarrApiAccess::$user->rights->projet->creer) { 450 throw new RestException(401); 451 } 452 453 $result = $this->project->fetch($id); 454 if ($result <= 0) { 455 throw new RestException(404, 'Project not found'); 456 } 457 458 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 459 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 460 } 461 foreach ($request_data as $field => $value) { 462 if ($field == 'id') { 463 continue; 464 } 465 $this->project->$field = $value; 466 } 467 468 if ($this->project->update(DolibarrApiAccess::$user) >= 0) { 469 return $this->get($id); 470 } else { 471 throw new RestException(500, $this->project->error); 472 } 473 } 474 475 /** 476 * Delete project 477 * 478 * @param int $id Project ID 479 * 480 * @return array 481 */ 482 public function delete($id) 483 { 484 if (!DolibarrApiAccess::$user->rights->projet->supprimer) { 485 throw new RestException(401); 486 } 487 $result = $this->project->fetch($id); 488 if (!$result) { 489 throw new RestException(404, 'Project not found'); 490 } 491 492 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 493 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 494 } 495 496 if (!$this->project->delete(DolibarrApiAccess::$user)) { 497 throw new RestException(500, 'Error when delete project : '.$this->project->error); 498 } 499 500 return array( 501 'success' => array( 502 'code' => 200, 503 'message' => 'Project deleted' 504 ) 505 ); 506 } 507 508 /** 509 * Validate a project. 510 * You can test this API with the following input message 511 * { "notrigger": 0 } 512 * 513 * @param int $id Project ID 514 * @param int $notrigger 1=Does not execute triggers, 0= execute triggers 515 * 516 * @url POST {id}/validate 517 * 518 * @return array 519 * FIXME An error 403 is returned if the request has an empty body. 520 * Error message: "Forbidden: Content type `text/plain` is not supported." 521 * Workaround: send this in the body 522 * { 523 * "notrigger": 0 524 * } 525 */ 526 public function validate($id, $notrigger = 0) 527 { 528 if (!DolibarrApiAccess::$user->rights->projet->creer) { 529 throw new RestException(401); 530 } 531 $result = $this->project->fetch($id); 532 if (!$result) { 533 throw new RestException(404, 'Project not found'); 534 } 535 536 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 537 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 538 } 539 540 $result = $this->project->setValid(DolibarrApiAccess::$user, $notrigger); 541 if ($result == 0) { 542 throw new RestException(304, 'Error nothing done. May be object is already validated'); 543 } 544 if ($result < 0) { 545 throw new RestException(500, 'Error when validating Project: '.$this->project->error); 546 } 547 548 return array( 549 'success' => array( 550 'code' => 200, 551 'message' => 'Project validated' 552 ) 553 ); 554 } 555 556 557 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore 558 /** 559 * Clean sensible object datas 560 * 561 * @param Object $object Object to clean 562 * @return Object Object with cleaned properties 563 */ 564 protected function _cleanObjectDatas($object) 565 { 566 // phpcs:enable 567 $object = parent::_cleanObjectDatas($object); 568 569 unset($object->datec); 570 unset($object->datem); 571 unset($object->barcode_type); 572 unset($object->barcode_type_code); 573 unset($object->barcode_type_label); 574 unset($object->barcode_type_coder); 575 unset($object->cond_reglement_id); 576 unset($object->cond_reglement); 577 unset($object->fk_delivery_address); 578 unset($object->shipping_method_id); 579 unset($object->fk_account); 580 unset($object->note); 581 unset($object->fk_incoterms); 582 unset($object->label_incoterms); 583 unset($object->location_incoterms); 584 unset($object->name); 585 unset($object->lastname); 586 unset($object->firstname); 587 unset($object->civility_id); 588 unset($object->mode_reglement_id); 589 unset($object->country); 590 unset($object->country_id); 591 unset($object->country_code); 592 593 unset($object->weekWorkLoad); 594 unset($object->weekWorkLoad); 595 596 //unset($object->lines); // for task we use timespent_lines, but for project we use lines 597 598 unset($object->total_ht); 599 unset($object->total_tva); 600 unset($object->total_localtax1); 601 unset($object->total_localtax2); 602 unset($object->total_ttc); 603 604 unset($object->comments); 605 606 return $object; 607 } 608 609 /** 610 * Validate fields before create or update object 611 * 612 * @param array $data Array with data to verify 613 * @return array 614 * @throws RestException 615 */ 616 private function _validate($data) 617 { 618 $object = array(); 619 foreach (self::$FIELDS as $field) { 620 if (!isset($data[$field])) { 621 throw new RestException(400, "$field field missing"); 622 } 623 $object[$field] = $data[$field]; 624 } 625 return $object; 626 } 627 628 629 // TODO 630 // getSummaryOfTimeSpent 631} 632