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 public 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 $timespent = $this->task->getSummaryOfTimeSpent(0); 85 } 86 if ($includetimespent == 1) { 87 // TODO 88 // Add class for timespent records and loop and fill $line->lines with records of timespent 89 } 90 91 return $this->_cleanObjectDatas($this->task); 92 } 93 94 95 96 /** 97 * List tasks 98 * 99 * Get a list of tasks 100 * 101 * @param string $sortfield Sort field 102 * @param string $sortorder Sort order 103 * @param int $limit Limit for list 104 * @param int $page Page number 105 * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')" 106 * @return array Array of project objects 107 */ 108 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '') 109 { 110 global $db, $conf; 111 112 if (!DolibarrApiAccess::$user->rights->projet->lire) { 113 throw new RestException(401); 114 } 115 116 $obj_ret = array(); 117 118 // case of external user, $thirdparty_ids param is ignored and replaced by user's socid 119 $socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : ''; 120 121 // If the internal user must only see his customers, force searching by him 122 $search_sale = 0; 123 if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) { 124 $search_sale = DolibarrApiAccess::$user->id; 125 } 126 127 $sql = "SELECT t.rowid"; 128 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 129 $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) 130 } 131 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task as t"; 132 133 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 134 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale 135 } 136 137 $sql .= ' WHERE t.entity IN ('.getEntity('project').')'; 138 if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) { 139 $sql .= " AND t.fk_soc = sc.fk_soc"; 140 } 141 if ($socids) { 142 $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")"; 143 } 144 if ($search_sale > 0) { 145 $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale 146 } 147 // Insert sale filter 148 if ($search_sale > 0) { 149 $sql .= " AND sc.fk_user = ".((int) $search_sale); 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 $task_static = new Task($this->db); 180 if ($task_static->fetch($obj->rowid)) { 181 $obj_ret[] = $this->_cleanObjectDatas($task_static); 182 } 183 $i++; 184 } 185 } else { 186 throw new RestException(503, 'Error when retrieve task list : '.$this->db->lasterror()); 187 } 188 if (!count($obj_ret)) { 189 throw new RestException(404, 'No task found'); 190 } 191 return $obj_ret; 192 } 193 194 /** 195 * Create task 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->task->$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->task->create(DolibarrApiAccess::$user) < 0) { 219 throw new RestException(500, "Error creating task", array_merge(array($this->task->error), $this->task->errors)); 220 } 221 222 return $this->task->id; 223 } 224 225 // /** 226 // * Get time spent of a task 227 // * 228 // * @param int $id Id of task 229 // * @return int 230 // * 231 // * @url GET {id}/tasks 232 // */ 233 /* 234 public function getLines($id, $includetimespent=0) 235 { 236 if(! DolibarrApiAccess::$user->rights->projet->lire) { 237 throw new RestException(401); 238 } 239 240 $result = $this->project->fetch($id); 241 if( ! $result ) { 242 throw new RestException(404, 'Project not found'); 243 } 244 245 if( ! DolibarrApi::_checkAccessToResource('project',$this->project->id)) { 246 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 247 } 248 $this->project->getLinesArray(DolibarrApiAccess::$user); 249 $result = array(); 250 foreach ($this->project->lines as $line) // $line is a task 251 { 252 if ($includetimespent == 1) 253 { 254 $timespent = $line->getSummaryOfTimeSpent(0); 255 } 256 if ($includetimespent == 1) 257 { 258 // TODO 259 // Add class for timespent records and loop and fill $line->lines with records of timespent 260 } 261 array_push($result,$this->_cleanObjectDatas($line)); 262 } 263 return $result; 264 } 265 */ 266 267 /** 268 * Get roles a user is assigned to a task with 269 * 270 * @param int $id Id of task 271 * @param int $userid Id of user (0 = connected user) 272 * 273 * @url GET {id}/roles 274 * 275 * @return int 276 */ 277 public function getRoles($id, $userid = 0) 278 { 279 global $db; 280 281 if (!DolibarrApiAccess::$user->rights->projet->lire) { 282 throw new RestException(401); 283 } 284 285 $result = $this->task->fetch($id); 286 if (!$result) { 287 throw new RestException(404, 'Task not found'); 288 } 289 290 if (!DolibarrApi::_checkAccessToResource('tasks', $this->task->id)) { 291 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 292 } 293 294 $usert = DolibarrApiAccess::$user; 295 if ($userid > 0) { 296 $usert = new User($this->db); 297 $usert->fetch($userid); 298 } 299 $this->task->roles = $this->task->getUserRolesForProjectsOrTasks(0, $usert, 0, $id); 300 $result = array(); 301 foreach ($this->task->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 * Update task general fields (won't touch time spent of task) 440 * 441 * @param int $id Id of task to update 442 * @param array $request_data Datas 443 * 444 * @return int 445 */ 446 public function put($id, $request_data = null) 447 { 448 if (!DolibarrApiAccess::$user->rights->projet->creer) { 449 throw new RestException(401); 450 } 451 452 $result = $this->task->fetch($id); 453 if (!$result) { 454 throw new RestException(404, 'Task not found'); 455 } 456 457 if (!DolibarrApi::_checkAccessToResource('task', $this->task->id)) { 458 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 459 } 460 foreach ($request_data as $field => $value) { 461 if ($field == 'id') { 462 continue; 463 } 464 $this->task->$field = $value; 465 } 466 467 if ($this->task->update(DolibarrApiAccess::$user) > 0) { 468 return $this->get($id); 469 } else { 470 throw new RestException(500, $this->task->error); 471 } 472 } 473 474 /** 475 * Delete task 476 * 477 * @param int $id Task ID 478 * 479 * @return array 480 */ 481 public function delete($id) 482 { 483 if (!DolibarrApiAccess::$user->rights->projet->supprimer) { 484 throw new RestException(401); 485 } 486 $result = $this->task->fetch($id); 487 if (!$result) { 488 throw new RestException(404, 'Task not found'); 489 } 490 491 if (!DolibarrApi::_checkAccessToResource('task', $this->task->id)) { 492 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 493 } 494 495 if (!$this->task->delete(DolibarrApiAccess::$user)) { 496 throw new RestException(500, 'Error when delete task : '.$this->task->error); 497 } 498 499 return array( 500 'success' => array( 501 'code' => 200, 502 'message' => 'Task deleted' 503 ) 504 ); 505 } 506 507 508 /** 509 * Add time spent to a task of a project. 510 * You can test this API with the following input message 511 * { "date": "2016-12-31 23:15:00", "duration": 1800, "user_id": 1, "note": "My time test" } 512 * 513 * @param int $id Task ID 514 * @param datetime $date Date (YYYY-MM-DD HH:MI:SS in GMT) 515 * @param int $duration Duration in seconds (3600 = 1h) 516 * @param int $user_id User (Use 0 for connected user) 517 * @param string $note Note 518 * 519 * @url POST {id}/addtimespent 520 * 521 * @return array 522 */ 523 public function addTimeSpent($id, $date, $duration, $user_id = 0, $note = '') 524 { 525 if (!DolibarrApiAccess::$user->rights->projet->creer) { 526 throw new RestException(401); 527 } 528 $result = $this->task->fetch($id); 529 if ($result <= 0) { 530 throw new RestException(404, 'Task not found'); 531 } 532 533 if (!DolibarrApi::_checkAccessToResource('project', $this->task->fk_project)) { 534 throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); 535 } 536 537 $uid = $user_id; 538 if (empty($uid)) { 539 $uid = DolibarrApiAccess::$user->id; 540 } 541 542 $newdate = dol_stringtotime($date, 1); 543 $this->task->timespent_date = $newdate; 544 $this->task->timespent_datehour = $newdate; 545 $this->task->timespent_withhour = 1; 546 $this->task->timespent_duration = $duration; 547 $this->task->timespent_fk_user = $user_id; 548 $this->task->timespent_note = $note; 549 550 $result = $this->task->addTimeSpent(DolibarrApiAccess::$user, 0); 551 if ($result == 0) { 552 throw new RestException(304, 'Error nothing done. May be object is already validated'); 553 } 554 if ($result < 0) { 555 throw new RestException(500, 'Error when adding time: '.$this->task->error); 556 } 557 558 return array( 559 'success' => array( 560 'code' => 200, 561 'message' => 'Time spent added' 562 ) 563 ); 564 } 565 566 567 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore 568 /** 569 * Clean sensible object datas 570 * 571 * @param Object $object Object to clean 572 * @return Object Object with cleaned properties 573 */ 574 protected function _cleanObjectDatas($object) 575 { 576 // phpcs:enable 577 $object = parent::_cleanObjectDatas($object); 578 579 unset($object->barcode_type); 580 unset($object->barcode_type_code); 581 unset($object->barcode_type_label); 582 unset($object->barcode_type_coder); 583 unset($object->cond_reglement_id); 584 unset($object->cond_reglement); 585 unset($object->fk_delivery_address); 586 unset($object->shipping_method_id); 587 unset($object->fk_account); 588 unset($object->note); 589 unset($object->fk_incoterms); 590 unset($object->label_incoterms); 591 unset($object->location_incoterms); 592 unset($object->name); 593 unset($object->lastname); 594 unset($object->firstname); 595 unset($object->civility_id); 596 unset($object->mode_reglement_id); 597 unset($object->country); 598 unset($object->country_id); 599 unset($object->country_code); 600 601 unset($object->weekWorkLoad); 602 unset($object->weekWorkLoad); 603 604 //unset($object->lines); // for task we use timespent_lines, but for project we use lines 605 606 unset($object->total_ht); 607 unset($object->total_tva); 608 unset($object->total_localtax1); 609 unset($object->total_localtax2); 610 unset($object->total_ttc); 611 612 unset($object->comments); 613 614 return $object; 615 } 616 617 /** 618 * Validate fields before create or update object 619 * 620 * @param array $data Array with data to verify 621 * @return array 622 * @throws RestException 623 */ 624 private function _validate($data) 625 { 626 $object = array(); 627 foreach (self::$FIELDS as $field) { 628 if (!isset($data[$field])) { 629 throw new RestException(400, "$field field missing"); 630 } 631 $object[$field] = $data[$field]; 632 } 633 return $object; 634 } 635 636 637 // \todo 638 // getSummaryOfTimeSpent 639} 640