dolibarr 22.0.5
api_tickets.class.php
1<?php
2/* Copyright (C) 2016 Jean-François Ferry <hello@librethic.io>
3 * Copyright (C) 2024 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
77 public function get($id)
78 {
79 return $this->getCommon($id, '', '');
80 }
81
96 public function getByTrackId($track_id)
97 {
98 return $this->getCommon(0, $track_id, '');
99 }
100
115 public function getByRef($ref)
116 {
117 return $this->getCommon(0, '', $ref);
118 }
119
129 private function getCommon($id = 0, $track_id = '', $ref = '')
130 {
131 if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) {
132 throw new RestException(403);
133 }
134
135 // Check parameters
136 if (($id < 0) && !$track_id && !$ref) {
137 throw new RestException(400, 'Wrong parameters');
138 }
139 if (empty($id) && empty($ref) && empty($track_id)) {
140 $result = $this->ticket->initAsSpecimen();
141 } else {
142 $result = $this->ticket->fetch($id, $ref, $track_id);
143 }
144 if (!$result) {
145 throw new RestException(404, 'Ticket not found');
146 }
147
148 // String for user assigned
149 if ($this->ticket->fk_user_assign > 0) {
150 $userStatic = new User($this->db);
151 $userStatic->fetch($this->ticket->fk_user_assign);
152 $this->ticket->fk_user_assign_string = $userStatic->firstname.' '.$userStatic->lastname;
153 }
154
155 // Messages of ticket
156 $messages = array();
157 $this->ticket->loadCacheMsgsTicket();
158 if (is_array($this->ticket->cache_msgs_ticket) && count($this->ticket->cache_msgs_ticket) > 0) {
159 $num = count($this->ticket->cache_msgs_ticket);
160 $i = 0;
161 while ($i < $num) {
162 if ($this->ticket->cache_msgs_ticket[$i]['fk_user_author'] > 0) {
163 $user_action = new User($this->db);
164 $user_action->fetch($this->ticket->cache_msgs_ticket[$i]['fk_user_author']);
165 } else {
166 $user_action = null;
167 }
168
169 // Now define messages
170 $messages[] = array(
171 'id' => $this->ticket->cache_msgs_ticket[$i]['id'],
172 'fk_user_action' => $this->ticket->cache_msgs_ticket[$i]['fk_user_author'],
173 'fk_user_action_socid' => $user_action === null ? '' : $user_action->socid,
174 'fk_user_action_string' => $user_action === null ? '' : dolGetFirstLastname($user_action->firstname, $user_action->lastname),
175 'message' => $this->ticket->cache_msgs_ticket[$i]['message'],
176 'datec' => $this->ticket->cache_msgs_ticket[$i]['datec'],
177 'private' => $this->ticket->cache_msgs_ticket[$i]['private']
178 );
179 $i++;
180 }
181 $this->ticket->messages = $messages;
182 }
183
184 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
185 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
186 }
187 return $this->_cleanObjectDatas($this->ticket);
188 }
189
208 public function index($socid = 0, $sortfield = "t.rowid", $sortorder = "ASC", $limit = 100, $page = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
209 {
210 if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) {
211 throw new RestException(403);
212 }
213
214 $obj_ret = array();
215
216 $socid = DolibarrApiAccess::$user->socid ?: $socid;
217
218 $search_sale = null;
219 // If the internal user must only see his customers, force searching by him
220 $search_sale = 0;
221 if (!DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
222 $search_sale = DolibarrApiAccess::$user->id;
223 }
224
225 $sql = "SELECT t.rowid";
226 $sql .= " FROM ".MAIN_DB_PREFIX."ticket AS t";
227 $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
228 $sql .= ' WHERE t.entity IN ('.getEntity('ticket', 1).')';
229 if ($socid > 0) {
230 $sql .= " AND t.fk_soc = ".((int) $socid);
231 }
232 // Search on sale representative
233 if ($search_sale && $search_sale != '-1') {
234 if ($search_sale == -2) {
235 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
236 } elseif ($search_sale > 0) {
237 $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).")";
238 }
239 }
240 // Add sql filters
241 if ($sqlfilters) {
242 $errormessage = '';
243 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
244 if ($errormessage) {
245 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
246 }
247 }
248
249 //this query will return total orders with the filters given
250 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
251
252 $sql .= $this->db->order($sortfield, $sortorder);
253
254 if ($limit) {
255 if ($page < 0) {
256 $page = 0;
257 }
258 $offset = $limit * $page;
259
260 $sql .= $this->db->plimit($limit, $offset);
261 }
262
263 $result = $this->db->query($sql);
264 if ($result) {
265 $num = $this->db->num_rows($result);
266 $i = 0;
267 while ($i < $num) {
268 $obj = $this->db->fetch_object($result);
269 $ticket_static = new Ticket($this->db);
270 if ($ticket_static->fetch($obj->rowid)) {
271 if ($ticket_static->fk_user_assign > 0) {
272 $userStatic = new User($this->db);
273 $userStatic->fetch($ticket_static->fk_user_assign);
274 $ticket_static->fk_user_assign_string = $userStatic->firstname.' '.$userStatic->lastname;
275 }
276 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($ticket_static), $properties);
277 }
278 $i++;
279 }
280 } else {
281 throw new RestException(503, 'Error when retrieve ticket list');
282 }
283
284 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
285 if ($pagination_data) {
286 $totalsResult = $this->db->query($sqlTotals);
287 $total = $this->db->fetch_object($totalsResult)->total;
288
289 $tmp = $obj_ret;
290 $obj_ret = [];
291
292 $obj_ret['data'] = $tmp;
293 $obj_ret['pagination'] = [
294 'total' => (int) $total,
295 'page' => $page, //count starts from 0
296 'page_count' => ceil((int) $total / $limit),
297 'limit' => $limit
298 ];
299 }
300
301 return $obj_ret;
302 }
303
312 public function post($request_data = null)
313 {
314 $ticketstatic = new Ticket($this->db);
315 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
316 throw new RestException(403);
317 }
318 // Check mandatory fields
319 $result = $this->_validate($request_data);
320
321 foreach ($request_data as $field => $value) {
322 if ($field === 'caller') {
323 // 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
324 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
325 continue;
326 }
327
328 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
329 }
330 if (empty($this->ticket->ref)) {
331 $this->ticket->ref = $ticketstatic->getDefaultRef();
332 }
333 if (empty($this->ticket->track_id)) {
334 $this->ticket->track_id = generate_random_id(16);
335 }
336
337 if ($this->ticket->create(DolibarrApiAccess::$user) < 0) {
338 throw new RestException(500, "Error creating ticket", array_merge(array($this->ticket->error), $this->ticket->errors));
339 }
340
341 return $this->ticket->id;
342 }
343
352 public function postNewMessage($request_data = null)
353 {
354 $ticketstatic = new Ticket($this->db);
355 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
356 throw new RestException(403);
357 }
358 // Check mandatory fields
359 $result = $this->_validateMessage($request_data);
360
361 foreach ($request_data as $field => $value) {
362 if ($field === 'caller') {
363 // 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
364 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
365 continue;
366 }
367
368 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
369 }
370 $ticketMessageText = $this->ticket->message;
371 $result = $this->ticket->fetch(0, '', $this->ticket->track_id);
372 if (!$result) {
373 throw new RestException(404, 'Ticket not found');
374 }
375 $this->ticket->message = $ticketMessageText;
376 if (!$this->ticket->createTicketMessage(DolibarrApiAccess::$user)) {
377 throw new RestException(500, 'Error when creating ticket');
378 }
379 return $this->ticket->id;
380 }
381
391 public function put($id, $request_data = null)
392 {
393 if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) {
394 throw new RestException(403);
395 }
396
397 $result = $this->ticket->fetch($id);
398 if (!$result) {
399 throw new RestException(404, 'Ticket not found');
400 }
401
402 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
403 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
404 }
405
406 foreach ($request_data as $field => $value) {
407 if ($field === 'caller') {
408 // 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
409 $this->ticket->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
410 continue;
411 }
412
413 if ($field == 'id') {
414 continue;
415 }
416 if ($field == 'array_options' && is_array($value)) {
417 foreach ($value as $index => $val) {
418 $this->ticket->array_options[$index] = $this->_checkValForAPI($field, $val, $this->ticket);
419 }
420 continue;
421 }
422
423 $this->ticket->$field = $this->_checkValForAPI($field, $value, $this->ticket);
424 }
425
426 if ($this->ticket->update(DolibarrApiAccess::$user) > 0) {
427 return $this->get($id);
428 } else {
429 throw new RestException(500, $this->ticket->error);
430 }
431 }
432
441 public function delete($id)
442 {
443 if (!DolibarrApiAccess::$user->hasRight('ticket', 'delete')) {
444 throw new RestException(403);
445 }
446 $result = $this->ticket->fetch($id);
447 if (!$result) {
448 throw new RestException(404, 'Ticket not found');
449 }
450
451 if (!DolibarrApi::_checkAccessToResource('ticket', $this->ticket->id)) {
452 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
453 }
454
455 if (!$this->ticket->delete(DolibarrApiAccess::$user)) {
456 throw new RestException(500, 'Error when deleting ticket');
457 }
458
459 return array(
460 'success' => array(
461 'code' => 200,
462 'message' => 'Ticket deleted'
463 )
464 );
465 }
466
475 private function _validate($data)
476 {
477 if ($data === null) {
478 $data = array();
479 }
480 $ticket = array();
481 foreach (Tickets::$FIELDS as $field) {
482 if (!isset($data[$field])) {
483 throw new RestException(400, "$field field missing");
484 }
485 $ticket[$field] = $data[$field];
486 }
487 return $ticket;
488 }
489
498 private function _validateMessage($data)
499 {
500 if ($data === null) {
501 $data = array();
502 }
503 $ticket = array();
504 foreach (Tickets::$FIELDS_MESSAGES as $field) {
505 if (!isset($data[$field])) {
506 throw new RestException(400, "$field field missing");
507 }
508 $ticket[$field] = $data[$field];
509 }
510 return $ticket;
511 }
512
513 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
522 protected function _cleanObjectDatas($object)
523 {
524 // phpcs:enable
525 $object = parent::_cleanObjectDatas($object);
526
527 // Other attributes to clean
528 $attr2clean = array(
529 "contact",
530 "contact_id",
531 "ref_previous",
532 "ref_next",
533 "ref_ext",
534 "table_element_line",
535 "statut",
536 "country",
537 "country_id",
538 "country_code",
539 "barcode_type",
540 "barcode_type_code",
541 "barcode_type_label",
542 "barcode_type_coder",
543 "mode_reglement_id",
544 "cond_reglement_id",
545 "cond_reglement",
546 "fk_delivery_address",
547 "shipping_method_id",
548 "modelpdf",
549 "fk_account",
550 "note_public",
551 "note_private",
552 "note",
553 "total_ht",
554 "total_tva",
555 "total_localtax1",
556 "total_localtax2",
557 "total_ttc",
558 "fk_incoterms",
559 "label_incoterms",
560 "location_incoterms",
561 "name",
562 "lastname",
563 "firstname",
564 "civility_id",
565 "canvas",
566 "cache_msgs_ticket",
567 "cache_logs_ticket",
568 "cache_types_tickets",
569 "regeximgext",
570 "labelStatus",
571 "labelStatusShort",
572 "multicurrency_code",
573 "multicurrency_tx",
574 "multicurrency_total_ht",
575 "multicurrency_total_ttc",
576 "multicurrency_total_tva",
577 "multicurrency_total_localtax1",
578 "multicurrency_total_localtax2"
579 );
580 foreach ($attr2clean as $toclean) {
581 unset($object->$toclean);
582 }
583
584 // If object has lines, remove $db property
585 if (isset($object->lines) && count($object->lines) > 0) {
586 $nboflines = count($object->lines);
587 for ($i = 0; $i < $nboflines; $i++) {
588 $this->_cleanObjectDatas($object->lines[$i]);
589 }
590 }
591
592 // If object has linked objects, remove $db property
593 if (isset($object->linkedObjects) && count($object->linkedObjects) > 0) {
594 foreach ($object->linkedObjects as $type_object => $linked_object) {
595 foreach ($linked_object as $object2clean) {
596 $this->_cleanObjectDatas($object2clean);
597 }
598 }
599 }
600 return $object;
601 }
602}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:48
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
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
getCommon($id=0, $track_id='', $ref='')
Get properties of a Ticket object Return an array with ticket information.
getByRef($ref)
Get properties of a Ticket object from ref.
__construct()
Constructor.
_cleanObjectDatas($object)
Clean sensible object datas.
index($socid=0, $sortfield="t.rowid", $sortorder="ASC", $limit=100, $page=0, $sqlfilters='', $properties='', $pagination_data=false)
List tickets.
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.
getByTrackId($track_id)
Get properties of a Ticket object from track id.
_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.