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