dolibarr 23.0.3
api_tickets.class.php
1<?php
2/* Copyright (C) 2016 Jean-François Ferry <hello@librethic.io>
3 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20use Luracast\Restler\RestException;
21
22require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php';
23require_once DOL_DOCUMENT_ROOT.'/core/lib/ticket.lib.php';
24
25
32class Tickets extends DolibarrApi
33{
37 public static $FIELDS = array(
38 'subject',
39 'message'
40 );
41
45 public static $FIELDS_MESSAGES = array(
46 'track_id',
47 'message'
48 );
49
53 public $ticket;
54
58 public function __construct()
59 {
60 global $db;
61 $this->db = $db;
62 $this->ticket = new Ticket($this->db);
63 }
64
78 public function get($id, $contact_list = 1)
79 {
80 return $this->getCommon($id, '', '', $contact_list);
81 }
82
98 public function getByTrackId($track_id, $contact_list = 1)
99 {
100 return $this->getCommon(0, $track_id, '', $contact_list);
101 }
102
118 public function getByRef($ref, $contact_list = 1)
119 {
120 return $this->getCommon(0, '', $ref, $contact_list);
121 }
122
133 private function getCommon($id = 0, $track_id = '', $ref = '', $contact_list = 1)
134 {
135 if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) {
136 throw new RestException(403);
137 }
138
139 // Check parameters
140 if (($id < 0) && !$track_id && !$ref) {
141 throw new RestException(400, 'Wrong parameters');
142 }
143 if (empty($id) && empty($ref) && empty($track_id)) {
144 $result = $this->ticket->initAsSpecimen();
145 } else {
146 $result = $this->ticket->fetch($id, $ref, $track_id);
147 }
148 if (!$result) {
149 throw new RestException(404, 'Ticket not found');
150 }
151
152 // String for user assigned
153 if ($this->ticket->fk_user_assign > 0) {
154 $userStatic = new User($this->db);
155 $userStatic->fetch($this->ticket->fk_user_assign);
156 $this->ticket->fk_user_assign_string = $userStatic->firstname.' '.$userStatic->lastname;
157 }
158
159 // Messages of ticket
160 $messages = array();
161 $this->ticket->loadCacheMsgsTicket();
162 if (is_array($this->ticket->cache_msgs_ticket) && count($this->ticket->cache_msgs_ticket) > 0) {
163 $num = count($this->ticket->cache_msgs_ticket);
164 $i = 0;
165 while ($i < $num) {
166 if ($this->ticket->cache_msgs_ticket[$i]['fk_user_author'] > 0) {
167 $user_action = new User($this->db);
168 $user_action->fetch($this->ticket->cache_msgs_ticket[$i]['fk_user_author']);
169 } else {
170 $user_action = null;
171 }
172
173 // Now define messages
174 $messages[] = array(
175 'id' => $this->ticket->cache_msgs_ticket[$i]['id'],
176 'fk_user_action' => $this->ticket->cache_msgs_ticket[$i]['fk_user_author'],
177 'fk_user_action_socid' => $user_action === null ? '' : $user_action->socid,
178 'fk_user_action_string' => $user_action === null ? '' : dolGetFirstLastname($user_action->firstname, $user_action->lastname),
179 'message' => $this->ticket->cache_msgs_ticket[$i]['message'],
180 'datec' => $this->ticket->cache_msgs_ticket[$i]['datec'],
181 'private' => $this->ticket->cache_msgs_ticket[$i]['private']
182 );
183 $i++;
184 }
185 $this->ticket->messages = $messages;
186 }
187
188 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
189 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
190 }
191
192 if ($contact_list > -1) {
193 // Add external contacts ids
194 $tmparray = $this->ticket->liste_contact(-1, 'external', $contact_list);
195 if (is_array($tmparray)) {
196 $this->ticket->contacts_ids = $tmparray;
197 }
198 $tmparray = $this->ticket->liste_contact(-1, 'internal', $contact_list);
199 if (is_array($tmparray)) {
200 $this->ticket->contacts_ids_internal = $tmparray;
201 }
202 }
203
204 return $this->_cleanObjectDatas($this->ticket);
205 }
206
226 public function index($socid = 0, $sortfield = "t.rowid", $sortorder = "ASC", $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $loadcontacts = 0, $pagination_data = false)
227 {
228 if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) {
229 throw new RestException(403);
230 }
231
232 $obj_ret = array();
233
234 $socid = DolibarrApiAccess::$user->socid ?: $socid;
235
236 $search_sale = null;
237 // If the internal user must only see his customers, force searching by him
238 $search_sale = 0;
239 if (!DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
240 $search_sale = DolibarrApiAccess::$user->id;
241 }
242
243 $sql = "SELECT t.rowid";
244 $sql .= " FROM ".MAIN_DB_PREFIX."ticket AS t";
245 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe AS s ON (s.rowid = t.fk_soc)";
246 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."ticket_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
247 $sql .= ' WHERE t.entity IN ('.getEntity('ticket', 1).')';
248 if ($socid > 0) {
249 $sql .= " AND t.fk_soc = ".((int) $socid);
250 }
251 // Search on sale representative
252 if ($search_sale && $search_sale != '-1') {
253 if ($search_sale == -2) {
254 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
255 } elseif ($search_sale > 0) {
256 $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
257 }
258 }
259 // Add sql filters
260 if ($sqlfilters) {
261 $errormessage = '';
262 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
263 if ($errormessage) {
264 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
265 }
266 }
267
268 //this query will return total orders with the filters given
269 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
270
271 $sql .= $this->db->order($sortfield, $sortorder);
272
273 if ($limit) {
274 if ($page < 0) {
275 $page = 0;
276 }
277 $offset = $limit * $page;
278
279 $sql .= $this->db->plimit($limit, $offset);
280 }
281
282 $result = $this->db->query($sql);
283 if ($result) {
284 $i = 0;
285 $num = $this->db->num_rows($result);
286 $min = min($num, ($limit <= 0 ? $num : $limit));
287 while ($i < $min) {
288 $obj = $this->db->fetch_object($result);
289 $ticket_static = new Ticket($this->db);
290 if ($ticket_static->fetch($obj->rowid)) {
291 if ($ticket_static->fk_user_assign > 0) {
292 $userStatic = new User($this->db);
293 $userStatic->fetch($ticket_static->fk_user_assign);
294 $ticket_static->fk_user_assign_string = $userStatic->firstname.' '.$userStatic->lastname;
295 }
296
297 if ($loadcontacts) {
298 // Add external contacts ids
299 $tmparray = $ticket_static->liste_contact(-1, 'external', 1);
300 if (is_array($tmparray)) {
301 $ticket_static->contacts_ids = $tmparray;
302 }
303 $tmparray = $ticket_static->liste_contact(-1, 'internal', 1);
304 if (is_array($tmparray)) {
305 $ticket_static->contacts_ids_internal = $tmparray;
306 }
307 }
308
309 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($ticket_static), $properties);
310 }
311 $i++;
312 }
313 } else {
314 throw new RestException(503, 'Error when retrieve ticket list');
315 }
316
317 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
318 if ($pagination_data) {
319 $totalsResult = $this->db->query($sqlTotals);
320 $total = $this->db->fetch_object($totalsResult)->total;
321
322 $tmp = $obj_ret;
323 $obj_ret = [];
324
325 $obj_ret['data'] = $tmp;
326 $obj_ret['pagination'] = [
327 'total' => (int) $total,
328 'page' => $page, //count starts from 0
329 'page_count' => ceil((int) $total / $limit),
330 'limit' => $limit
331 ];
332 }
333
334 return $obj_ret;
335 }
336
345 public function post($request_data = null)
346 {
347 $ticketstatic = new Ticket($this->db);
348 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
349 throw new RestException(403);
350 }
351
352 // Check mandatory fields
353 $this->_validate($request_data);
354
355 // Check thirdparty validity
356 $socid = (int) $request_data['socid'];
357 if ($socid > 0) {
358 $thirdpartytmp = new Societe($this->db);
359 $thirdparty_result = $thirdpartytmp->fetch($socid);
360 if ($thirdparty_result < 1) {
361 throw new RestException(404, 'Thirdparty with id='.$socid.' not found or not allowed');
362 }
363 if (!DolibarrApi::_checkAccessToResource('societe', $thirdpartytmp->id)) {
364 throw new RestException(404, 'Thirdparty with id='.$thirdpartytmp->id.' not found or not allowed');
365 }
366 }
367
368 foreach ($request_data as $field => $value) {
369 if ($field === 'caller') {
370 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
371 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
372 continue;
373 }
374
375 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
376 }
377 if (empty($this->ticket->ref)) {
378 $this->ticket->ref = $ticketstatic->getDefaultRef();
379 }
380 if (empty($this->ticket->track_id)) {
381 $this->ticket->track_id = generate_random_id(16);
382 }
383
384 if ($this->ticket->create(DolibarrApiAccess::$user) < 0) {
385 throw new RestException(500, "Error creating ticket", array_merge(array($this->ticket->error), $this->ticket->errors));
386 }
387
388 return $this->ticket->id;
389 }
390
399 public function postNewMessage($request_data = null)
400 {
401 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
402 throw new RestException(403);
403 }
404
405 // Check mandatory fields
406 $result = $this->_validateMessage($request_data);
407
408 foreach ($request_data as $field => $value) {
409 if ($field === 'caller') {
410 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
411 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
412 continue;
413 }
414
415 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
416 }
417 $ticketMessageText = $this->ticket->message;
418 $result = $this->ticket->fetch(0, '', $this->ticket->track_id);
419 if (!$result) {
420 throw new RestException(404, 'Ticket not found');
421 }
422
423 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
424 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
425 }
426
427 $this->ticket->message = $ticketMessageText;
428 if (!$this->ticket->createTicketMessage(DolibarrApiAccess::$user)) {
429 throw new RestException(500, 'Error when creating ticket');
430 }
431 return $this->ticket->id;
432 }
433
443 public function put($id, $request_data = null)
444 {
445 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
446 throw new RestException(403);
447 }
448
449 $result = $this->ticket->fetch($id);
450 if (!$result) {
451 throw new RestException(404, 'Ticket not found');
452 }
453
454 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
455 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
456 }
457
458 // Check thirdparty validity
459 $socid = (int) $request_data['socid'];
460 if ($socid > 0) {
461 $thirdpartytmp = new Societe($this->db);
462 $thirdparty_result = $thirdpartytmp->fetch($socid);
463 if ($thirdparty_result < 1) {
464 throw new RestException(404, 'Thirdparty with id='.$socid.' not found or not allowed');
465 }
466 if (!DolibarrApi::_checkAccessToResource('societe', $thirdpartytmp->id)) {
467 throw new RestException(404, 'Thirdparty with id='.$thirdpartytmp->id.' not found or not allowed');
468 }
469 }
470
471 foreach ($request_data as $field => $value) {
472 if ($field === 'caller') {
473 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
474 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
475 continue;
476 }
477
478 if ($field == 'id') {
479 continue;
480 }
481 if ($field == 'array_options' && is_array($value)) {
482 foreach ($value as $index => $val) {
483 $this->ticket->array_options[$index] = $this->_checkValForAPI($field, $val, $this->ticket);
484 }
485 continue;
486 }
487
488 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
489 }
490
491 if ($this->ticket->update(DolibarrApiAccess::$user) > 0) {
492 return $this->get($id);
493 } else {
494 throw new RestException(500, $this->ticket->error);
495 }
496 }
497
506 public function delete($id)
507 {
508 if (!DolibarrApiAccess::$user->hasRight('ticket', 'delete')) {
509 throw new RestException(403);
510 }
511 $result = $this->ticket->fetch($id);
512 if (!$result) {
513 throw new RestException(404, 'Ticket not found');
514 }
515
516 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
517 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
518 }
519
520 if (!$this->ticket->delete(DolibarrApiAccess::$user)) {
521 throw new RestException(500, 'Error when deleting ticket');
522 }
523
524 return array(
525 'success' => array(
526 'code' => 200,
527 'message' => 'Ticket deleted'
528 )
529 );
530 }
531
540 private function _validate($data)
541 {
542 if ($data === null) {
543 $data = array();
544 }
545 $ticket = array();
546 foreach (Tickets::$FIELDS as $field) {
547 if (!isset($data[$field])) {
548 throw new RestException(400, "$field field missing");
549 }
550 $ticket[$field] = $data[$field];
551 }
552 return $ticket;
553 }
554
563 private function _validateMessage($data)
564 {
565 if ($data === null) {
566 $data = array();
567 }
568 $ticket = array();
569 foreach (Tickets::$FIELDS_MESSAGES as $field) {
570 if (!isset($data[$field])) {
571 throw new RestException(400, "$field field missing");
572 }
573 $ticket[$field] = $data[$field];
574 }
575 return $ticket;
576 }
577
578 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
590 protected function _cleanObjectDatas($object)
591 {
592 // phpcs:enable
593 $object = parent::_cleanObjectDatas($object);
594
595 // Other attributes to clean
596 $attr2clean = array(
597 "contact",
598 "contact_id",
599 "ref_previous",
600 "ref_next",
601 "ref_ext",
602 "table_element_line",
603 "statut",
604 "country",
605 "country_id",
606 "country_code",
607 "barcode_type",
608 "barcode_type_code",
609 "barcode_type_label",
610 "barcode_type_coder",
611 "mode_reglement_id",
612 "cond_reglement_id",
613 "cond_reglement",
614 "fk_delivery_address",
615 "shipping_method_id",
616 "modelpdf",
617 "fk_account",
618 "note_public",
619 "note_private",
620 "note",
621 "total_ht",
622 "total_tva",
623 "total_localtax1",
624 "total_localtax2",
625 "total_ttc",
626 "fk_incoterms",
627 "label_incoterms",
628 "location_incoterms",
629 "name",
630 "lastname",
631 "firstname",
632 "civility_id",
633 "canvas",
634 "cache_msgs_ticket",
635 "cache_logs_ticket",
636 "cache_types_tickets",
637 "regeximgext",
638 "labelStatus",
639 "labelStatusShort",
640 "multicurrency_code",
641 "multicurrency_tx",
642 "multicurrency_total_ht",
643 "multicurrency_total_ttc",
644 "multicurrency_total_tva",
645 "multicurrency_total_localtax1",
646 "multicurrency_total_localtax2"
647 );
648 foreach ($attr2clean as $toclean) {
649 unset($object->$toclean);
650 }
651
652 // If object has lines, remove $db property
653 if (isset($object->lines) && count($object->lines) > 0) {
654 $nboflines = count($object->lines);
655 for ($i = 0; $i < $nboflines; $i++) {
656 $this->_cleanObjectDatas($object->lines[$i]);
657 }
658 }
659
660 // If object has linked objects, remove $db property
661 if (isset($object->linkedObjects) && count($object->linkedObjects) > 0) {
662 foreach ($object->linkedObjects as $type_object => $linked_object) {
663 foreach ($linked_object as $object2clean) {
664 $this->_cleanObjectDatas($object2clean);
665 }
666 }
667 }
668 return $object;
669 }
670}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class for API REST v1.
Definition api.class.php:33
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid')
Check access by user to a given resource.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
Definition api.class.php:98
Class to manage third parties objects (customers, suppliers, prospects...)
getByTrackId($track_id, $contact_list=1)
Get properties of a Ticket object from track id.
getCommon($id=0, $track_id='', $ref='', $contact_list=1)
Get properties of a Ticket object Return an array with ticket information.
__construct()
Constructor.
index($socid=0, $sortfield="t.rowid", $sortorder="ASC", $limit=100, $page=0, $sqlfilters='', $properties='', $loadcontacts=0, $pagination_data=false)
List tickets.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
postNewMessage($request_data=null)
Add a new message to an existing ticket identified by property ->track_id into request.
post($request_data=null)
Create ticket object.
put($id, $request_data=null)
Update ticket.
_validateMessage($data)
Validate fields before create or update object message.
getByRef($ref, $contact_list=1)
Get properties of a Ticket object from ref.
_validate($data)
Validate fields before create or update object.
Class to manage Dolibarr users.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
Class to generate the form for creating a new ticket.
generate_random_id($car=16)
Generate a random id.