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 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 $obj_ret = array(); 107 108 // case of external user, $thirdparty_ids param is ignored and replaced by user's socid 109 $socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids; 110 111 // If the internal user must only see his customers, force searching by him 112 $search_sale = 0; 113 if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) $search_sale = DolibarrApiAccess::$user->id; 114 115 $sql = "SELECT t.rowid"; 116 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $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) 117 $sql .= " FROM ".MAIN_DB_PREFIX."projet as t"; 118 if ($category > 0) { 119 $sql .= ", ".MAIN_DB_PREFIX."categorie_project as c"; 120 } 121 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale 122 123 $sql .= ' WHERE t.entity IN ('.getEntity('project').')'; 124 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) $sql .= " AND t.fk_soc = sc.fk_soc"; 125 if ($socids) $sql .= " AND t.fk_soc IN (".$socids.")"; 126 if ($search_sale > 0) $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale 127 // Insert sale filter 128 if ($search_sale > 0) 129 { 130 $sql .= " AND sc.fk_user = ".$search_sale; 131 } 132 // Select projects of given category 133 if ($category > 0) { 134 $sql .= " AND c.fk_categorie = ".$this->db->escape($category)." AND c.fk_project = t.rowid "; 135 } 136 // Add sql filters 137 if ($sqlfilters) 138 { 139 if (!DolibarrApi::_checkFilters($sqlfilters)) 140 { 141 throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters); 142 } 143 $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)'; 144 $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; 145 } 146 147 $sql .= $this->db->order($sortfield, $sortorder); 148 if ($limit) { 149 if ($page < 0) { 150 $page = 0; 151 } 152 $offset = $limit * $page; 153 154 $sql .= $this->db->plimit($limit + 1, $offset); 155 } 156 157 dol_syslog("API Rest request"); 158 $result = $this->db->query($sql); 159 160 if ($result) 161 { 162 $num = $this->db->num_rows($result); 163 $min = min($num, ($limit <= 0 ? $num : $limit)); 164 $i = 0; 165 while ($i < $min) { 166 $obj = $this->db->fetch_object($result); 167 $project_static = new Project($this->db); 168 if ($project_static->fetch($obj->rowid)) { 169 $obj_ret[] = $this->_cleanObjectDatas($project_static); 170 } 171 $i++; 172 } 173 } else { 174 throw new RestException(503, 'Error when retrieve project list : '.$this->db->lasterror()); 175 } 176 if (!count($obj_ret)) { 177 throw new RestException(404, 'No project found'); 178 } 179 return $obj_ret; 180 } 181 182 /** 183 * Create project object 184 * 185 * @param array $request_data Request data 186 * @return int ID of project 187 */ 188 public function post($request_data = null) 189 { 190 if (!DolibarrApiAccess::$user->rights->projet->creer) { 191 throw new RestException(401, "Insuffisant rights"); 192 } 193 // Check mandatory fields 194 $result = $this->_validate($request_data); 195 196 foreach ($request_data as $field => $value) { 197 $this->project->$field = $value; 198 } 199 /*if (isset($request_data["lines"])) { 200 $lines = array(); 201 foreach ($request_data["lines"] as $line) { 202 array_push($lines, (object) $line); 203 } 204 $this->project->lines = $lines; 205 }*/ 206 if ($this->project->create(DolibarrApiAccess::$user) < 0) { 207 throw new RestException(500, "Error creating project", array_merge(array($this->project->error), $this->project->errors)); 208 } 209 210 return $this->project->id; 211 } 212 213 /** 214 * Get tasks of a project. 215 * See also API /tasks 216 * 217 * @param int $id Id of project 218 * @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) 219 * @return int 220 * 221 * @url GET {id}/tasks 222 */ 223 public function getLines($id, $includetimespent = 0) 224 { 225 if (!DolibarrApiAccess::$user->rights->projet->lire) { 226 throw new RestException(401); 227 } 228 229 $result = $this->project->fetch($id); 230 if (!$result) { 231 throw new RestException(404, 'Project not found'); 232 } 233 234 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 235 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 236 } 237 $this->project->getLinesArray(DolibarrApiAccess::$user); 238 $result = array(); 239 foreach ($this->project->lines as $line) // $line is a task 240 { 241 if ($includetimespent == 1) 242 { 243 $timespent = $line->getSummaryOfTimeSpent(0); 244 } 245 if ($includetimespent == 1) 246 { 247 // TODO 248 // Add class for timespent records and loop and fill $line->lines with records of timespent 249 } 250 array_push($result, $this->_cleanObjectDatas($line)); 251 } 252 return $result; 253 } 254 255 256 /** 257 * Get roles a user is assigned to a project with 258 * 259 * @param int $id Id of project 260 * @param int $userid Id of user (0 = connected user) 261 * 262 * @url GET {id}/roles 263 * 264 * @return int 265 */ 266 public function getRoles($id, $userid = 0) 267 { 268 global $db; 269 270 if (!DolibarrApiAccess::$user->rights->projet->lire) { 271 throw new RestException(401); 272 } 273 274 $result = $this->project->fetch($id); 275 if (!$result) { 276 throw new RestException(404, 'Project not found'); 277 } 278 279 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 280 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 281 } 282 283 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php'; 284 $taskstatic = new Task($this->db); 285 $userp = DolibarrApiAccess::$user; 286 if ($userid > 0) 287 { 288 $userp = new User($this->db); 289 $userp->fetch($userid); 290 } 291 $this->project->roles = $taskstatic->getUserRolesForProjectsOrTasks($userp, 0, $id, 0); 292 $result = array(); 293 foreach ($this->project->roles as $line) { 294 array_push($result, $this->_cleanObjectDatas($line)); 295 } 296 return $result; 297 } 298 299 300 /** 301 * Add a task to given project 302 * 303 * @param int $id Id of project to update 304 * @param array $request_data Projectline data 305 * 306 * @url POST {id}/tasks 307 * 308 * @return int 309 */ 310 /* 311 public function postLine($id, $request_data = null) 312 { 313 if(! DolibarrApiAccess::$user->rights->projet->creer) { 314 throw new RestException(401); 315 } 316 317 $result = $this->project->fetch($id); 318 if( ! $result ) { 319 throw new RestException(404, 'Project not found'); 320 } 321 322 if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) { 323 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 324 } 325 $request_data = (object) $request_data; 326 $updateRes = $this->project->addline( 327 $request_data->desc, 328 $request_data->subprice, 329 $request_data->qty, 330 $request_data->tva_tx, 331 $request_data->localtax1_tx, 332 $request_data->localtax2_tx, 333 $request_data->fk_product, 334 $request_data->remise_percent, 335 $request_data->info_bits, 336 $request_data->fk_remise_except, 337 'HT', 338 0, 339 $request_data->date_start, 340 $request_data->date_end, 341 $request_data->product_type, 342 $request_data->rang, 343 $request_data->special_code, 344 $fk_parent_line, 345 $request_data->fk_fournprice, 346 $request_data->pa_ht, 347 $request_data->label, 348 $request_data->array_options, 349 $request_data->fk_unit, 350 $this->element, 351 $request_data->id 352 ); 353 354 if ($updateRes > 0) { 355 return $updateRes; 356 357 } 358 return false; 359 } 360 */ 361 362 /** 363 * Update a task to given project 364 * 365 * @param int $id Id of project to update 366 * @param int $taskid Id of task to update 367 * @param array $request_data Projectline data 368 * 369 * @url PUT {id}/tasks/{taskid} 370 * 371 * @return object 372 */ 373 /* 374 public function putLine($id, $lineid, $request_data = null) 375 { 376 if(! DolibarrApiAccess::$user->rights->projet->creer) { 377 throw new RestException(401); 378 } 379 380 $result = $this->project->fetch($id); 381 if( ! $result ) { 382 throw new RestException(404, 'Project not found'); 383 } 384 385 if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) { 386 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 387 } 388 $request_data = (object) $request_data; 389 $updateRes = $this->project->updateline( 390 $lineid, 391 $request_data->desc, 392 $request_data->subprice, 393 $request_data->qty, 394 $request_data->remise_percent, 395 $request_data->tva_tx, 396 $request_data->localtax1_tx, 397 $request_data->localtax2_tx, 398 'HT', 399 $request_data->info_bits, 400 $request_data->date_start, 401 $request_data->date_end, 402 $request_data->product_type, 403 $request_data->fk_parent_line, 404 0, 405 $request_data->fk_fournprice, 406 $request_data->pa_ht, 407 $request_data->label, 408 $request_data->special_code, 409 $request_data->array_options, 410 $request_data->fk_unit 411 ); 412 413 if ($updateRes > 0) { 414 $result = $this->get($id); 415 unset($result->line); 416 return $this->_cleanObjectDatas($result); 417 } 418 return false; 419 }*/ 420 421 422 423 /** 424 * Update project general fields (won't touch lines of project) 425 * 426 * @param int $id Id of project to update 427 * @param array $request_data Datas 428 * 429 * @return int 430 */ 431 public function put($id, $request_data = null) 432 { 433 if (!DolibarrApiAccess::$user->rights->projet->creer) { 434 throw new RestException(401); 435 } 436 437 $result = $this->project->fetch($id); 438 if ($result <= 0) { 439 throw new RestException(404, 'Project not found'); 440 } 441 442 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 443 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 444 } 445 foreach ($request_data as $field => $value) { 446 if ($field == 'id') continue; 447 $this->project->$field = $value; 448 } 449 450 if ($this->project->update(DolibarrApiAccess::$user) >= 0) 451 { 452 return $this->get($id); 453 } else { 454 throw new RestException(500, $this->project->error); 455 } 456 } 457 458 /** 459 * Delete project 460 * 461 * @param int $id Project ID 462 * 463 * @return array 464 */ 465 public function delete($id) 466 { 467 if (!DolibarrApiAccess::$user->rights->projet->supprimer) { 468 throw new RestException(401); 469 } 470 $result = $this->project->fetch($id); 471 if (!$result) { 472 throw new RestException(404, 'Project not found'); 473 } 474 475 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 476 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 477 } 478 479 if (!$this->project->delete(DolibarrApiAccess::$user)) { 480 throw new RestException(500, 'Error when delete project : '.$this->project->error); 481 } 482 483 return array( 484 'success' => array( 485 'code' => 200, 486 'message' => 'Project deleted' 487 ) 488 ); 489 } 490 491 /** 492 * Validate a project. 493 * You can test this API with the following input message 494 * { "notrigger": 0 } 495 * 496 * @param int $id Project ID 497 * @param int $notrigger 1=Does not execute triggers, 0= execute triggers 498 * 499 * @url POST {id}/validate 500 * 501 * @return array 502 * FIXME An error 403 is returned if the request has an empty body. 503 * Error message: "Forbidden: Content type `text/plain` is not supported." 504 * Workaround: send this in the body 505 * { 506 * "notrigger": 0 507 * } 508 */ 509 public function validate($id, $notrigger = 0) 510 { 511 if (!DolibarrApiAccess::$user->rights->projet->creer) { 512 throw new RestException(401); 513 } 514 $result = $this->project->fetch($id); 515 if (!$result) { 516 throw new RestException(404, 'Project not found'); 517 } 518 519 if (!DolibarrApi::_checkAccessToResource('project', $this->project->id)) { 520 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 521 } 522 523 $result = $this->project->setValid(DolibarrApiAccess::$user, $notrigger); 524 if ($result == 0) { 525 throw new RestException(304, 'Error nothing done. May be object is already validated'); 526 } 527 if ($result < 0) { 528 throw new RestException(500, 'Error when validating Project: '.$this->project->error); 529 } 530 531 return array( 532 'success' => array( 533 'code' => 200, 534 'message' => 'Project validated' 535 ) 536 ); 537 } 538 539 540 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore 541 /** 542 * Clean sensible object datas 543 * 544 * @param Object $object Object to clean 545 * @return Object Object with cleaned properties 546 */ 547 protected function _cleanObjectDatas($object) 548 { 549 // phpcs:enable 550 $object = parent::_cleanObjectDatas($object); 551 552 unset($object->datec); 553 unset($object->datem); 554 unset($object->barcode_type); 555 unset($object->barcode_type_code); 556 unset($object->barcode_type_label); 557 unset($object->barcode_type_coder); 558 unset($object->cond_reglement_id); 559 unset($object->cond_reglement); 560 unset($object->fk_delivery_address); 561 unset($object->shipping_method_id); 562 unset($object->fk_account); 563 unset($object->note); 564 unset($object->fk_incoterms); 565 unset($object->label_incoterms); 566 unset($object->location_incoterms); 567 unset($object->name); 568 unset($object->lastname); 569 unset($object->firstname); 570 unset($object->civility_id); 571 unset($object->mode_reglement_id); 572 unset($object->country); 573 unset($object->country_id); 574 unset($object->country_code); 575 576 unset($object->weekWorkLoad); 577 unset($object->weekWorkLoad); 578 579 //unset($object->lines); // for task we use timespent_lines, but for project we use lines 580 581 unset($object->total_ht); 582 unset($object->total_tva); 583 unset($object->total_localtax1); 584 unset($object->total_localtax2); 585 unset($object->total_ttc); 586 587 unset($object->comments); 588 589 return $object; 590 } 591 592 /** 593 * Validate fields before create or update object 594 * 595 * @param array $data Array with data to verify 596 * @return array 597 * @throws RestException 598 */ 599 private function _validate($data) 600 { 601 $object = array(); 602 foreach (self::$FIELDS as $field) { 603 if (!isset($data[$field])) 604 throw new RestException(400, "$field field missing"); 605 $object[$field] = $data[$field]; 606 } 607 return $object; 608 } 609 610 611 // TODO 612 // getSummaryOfTimeSpent 613} 614