2018-03-10 03:23:59 +00:00
< ? php
/* Copyright ( C ) 2016 Jean - François Ferry < hello @ librethic . io >
2025-11-20 00:47:46 +00:00
* Copyright ( C ) 2024 - 2025 Frédéric France < frederic . france @ free . fr >
2025-01-07 14:31:41 +00:00
* Copyright ( C ) 2024 - 2025 MDW < mdeweerd @ users . noreply . github . com >
2026-08-04 16:36:51 +00:00
* Copyright ( C ) 2026 Jose Martinez < jose . martinez @ pichinov . com >
2018-03-10 03:23:59 +00:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
2019-09-23 19:55:30 +00:00
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2018-03-10 03:23:59 +00:00
*/
2024-10-18 23:21:41 +00:00
use Luracast\Restler\RestException ;
2018-03-10 03:23:59 +00:00
2021-08-26 08:32:29 +00:00
require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php' ;
2020-04-10 08:59:32 +00:00
require_once DOL_DOCUMENT_ROOT . '/core/lib/ticket.lib.php' ;
2018-03-10 03:23:59 +00:00
2018-05-16 13:23:52 +00:00
2018-03-10 03:23:59 +00:00
/**
2018-06-04 19:49:29 +00:00
* API class for ticket object
2018-03-10 03:23:59 +00:00
*
* @ access protected
* @ class DolibarrApiAccess { @ requires user , external }
*/
2018-04-29 16:06:31 +00:00
class Tickets extends DolibarrApi
2018-03-10 03:23:59 +00:00
{
2020-12-01 01:41:19 +00:00
/**
2025-03-03 00:49:20 +00:00
* @ var string [] Mandatory fields , checked when create and update object
2020-12-01 01:41:19 +00:00
*/
public static $FIELDS = array (
'subject' ,
'message'
);
/**
2025-03-03 00:49:20 +00:00
* @ var string [] Mandatory fields , checked when create and update object
2020-12-01 01:41:19 +00:00
*/
public static $FIELDS_MESSAGES = array (
'track_id' ,
'message'
);
/**
2025-03-02 11:13:32 +00:00
* @ var Ticket { @ type Ticket }
2020-12-01 01:41:19 +00:00
*/
public $ticket ;
/**
* Constructor
*/
public function __construct ()
{
global $db ;
$this -> db = $db ;
$this -> ticket = new Ticket ( $this -> db );
}
/**
* Get properties of a Ticket object .
*
2024-01-12 16:18:52 +00:00
* Return an array with ticket information
2020-12-01 01:41:19 +00:00
*
2025-09-19 22:45:30 +00:00
* @ param int $id ID of ticket
* @ param int $contact_list 0 : Returned array of contacts / addresses contains all properties , 1 : Return array contains just id , - 1 : Do not return contacts / adddesses
* @ return Object Object with cleaned properties
2020-12-01 01:41:19 +00:00
*
* @ throws RestException 401
* @ throws RestException 403
* @ throws RestException 404
*/
2025-09-19 22:45:30 +00:00
public function get ( $id , $contact_list = 1 )
2020-12-01 01:41:19 +00:00
{
2025-09-19 22:45:30 +00:00
return $this -> getCommon ( $id , '' , '' , $contact_list );
2020-12-01 01:41:19 +00:00
}
/**
* Get properties of a Ticket object from track id
*
2024-01-12 16:18:52 +00:00
* Return an array with ticket information
2020-12-01 01:41:19 +00:00
*
2025-09-19 22:45:30 +00:00
* @ param string $track_id Tracking ID of ticket
* @ param int $contact_list 0 : Returned array of contacts / addresses contains all properties , 1 : Return array contains just id , - 1 : Do not return contacts / adddesses
* @ return array | mixed Data without useless information
2020-12-01 01:41:19 +00:00
*
* @ url GET track_id / { track_id }
*
2023-09-26 16:43:25 +00:00
* @ throws RestException 401
* @ throws RestException 403
* @ throws RestException 404
2020-12-01 01:41:19 +00:00
*/
2025-09-19 22:45:30 +00:00
public function getByTrackId ( $track_id , $contact_list = 1 )
2020-12-01 01:41:19 +00:00
{
2025-09-19 22:45:30 +00:00
return $this -> getCommon ( 0 , $track_id , '' , $contact_list );
2020-12-01 01:41:19 +00:00
}
/**
* Get properties of a Ticket object from ref
*
2024-01-12 16:18:52 +00:00
* Return an array with ticket information
2020-12-01 01:41:19 +00:00
*
2025-09-19 22:45:30 +00:00
* @ param string $ref Reference for ticket
* @ param int $contact_list 0 : Returned array of contacts / addresses contains all properties , 1 : Return array contains just id , - 1 : Do not return contacts / adddesses
* @ return array | mixed Data without useless information
2020-12-01 01:41:19 +00:00
*
* @ url GET ref / { ref }
*
* @ throws RestException 401
* @ throws RestException 403
* @ throws RestException 404
*/
2025-09-19 22:45:30 +00:00
public function getByRef ( $ref , $contact_list = 1 )
2020-12-01 01:41:19 +00:00
{
2025-09-19 22:45:30 +00:00
return $this -> getCommon ( 0 , '' , $ref , $contact_list );
2020-12-01 01:41:19 +00:00
}
/**
* Get properties of a Ticket object
2024-01-12 16:18:52 +00:00
* Return an array with ticket information
2020-12-01 01:41:19 +00:00
*
2025-09-19 22:45:30 +00:00
* @ param int $id ID of ticket
* @ param string $track_id Tracking ID of ticket
* @ param string $ref Reference for ticket
* @ param int $contact_list 0 : Returned array of contacts / addresses contains all properties , 1 : Return array contains just id , - 1 : Do not return contacts / adddesses
* @ return array | mixed Data without useless information
2020-12-01 01:41:19 +00:00
*/
2025-09-19 22:45:30 +00:00
private function getCommon ( $id = 0 , $track_id = '' , $ref = '' , $contact_list = 1 )
2020-12-01 01:41:19 +00:00
{
2026-01-20 16:36:38 +00:00
global $conf ;
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'read' )) {
2020-12-01 01:41:19 +00:00
throw new RestException ( 403 );
}
// Check parameters
if (( $id < 0 ) && ! $track_id && ! $ref ) {
2024-04-07 12:59:25 +00:00
throw new RestException ( 400 , 'Wrong parameters' );
2020-12-01 01:41:19 +00:00
}
2024-08-27 01:39:18 +00:00
if ( empty ( $id ) && empty ( $ref ) && empty ( $track_id )) {
2020-10-30 19:37:23 +00:00
$result = $this -> ticket -> initAsSpecimen ();
} else {
$result = $this -> ticket -> fetch ( $id , $ref , $track_id );
}
2020-12-01 01:41:19 +00:00
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
// Messages of ticket
$messages = array ();
2026-01-20 16:36:38 +00:00
2020-12-01 01:41:19 +00:00
$this -> ticket -> loadCacheMsgsTicket ();
2026-01-20 16:36:38 +00:00
2020-12-01 01:41:19 +00:00
if ( is_array ( $this -> ticket -> cache_msgs_ticket ) && count ( $this -> ticket -> cache_msgs_ticket ) > 0 ) {
$num = count ( $this -> ticket -> cache_msgs_ticket );
$i = 0 ;
while ( $i < $num ) {
2026-01-20 16:36:38 +00:00
$iduseraction = $this -> ticket -> cache_msgs_ticket [ $i ][ 'fk_user_action' ];
if ( $iduseraction > 0 ) {
if ( empty ( $conf -> cache [ 'user' ][ $iduseraction ])) {
$user_action = new User ( $this -> db );
$user_action -> fetch ( $iduseraction );
$conf -> cache [ 'user' ][ $iduseraction ] = $user_action ;
} else {
$user_action = $conf -> cache [ 'user' ][ $iduseraction ];
}
2025-01-07 14:31:41 +00:00
} else {
$user_action = null ;
2020-12-01 01:41:19 +00:00
}
// Now define messages
$messages [] = array (
2025-01-07 14:31:41 +00:00
'id' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'id' ],
2026-01-20 16:36:38 +00:00
'fk_user_action' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'fk_user_action' ], // Id of user owning the event
2025-01-07 14:31:41 +00:00
'fk_user_action_socid' => $user_action === null ? '' : $user_action -> socid ,
'fk_user_action_string' => $user_action === null ? '' : dolGetFirstLastname ( $user_action -> firstname , $user_action -> lastname ),
'datec' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'datec' ],
2026-01-20 16:36:38 +00:00
'datep' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'datep' ],
'subject' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'subject' ],
'message' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'message' ],
2025-01-07 14:31:41 +00:00
'private' => $this -> ticket -> cache_msgs_ticket [ $i ][ 'private' ]
2020-12-01 01:41:19 +00:00
);
$i ++ ;
}
$this -> ticket -> messages = $messages ;
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
2024-04-02 12:47:49 +00:00
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
2020-12-01 01:41:19 +00:00
}
2025-09-19 22:45:30 +00:00
if ( $contact_list > - 1 ) {
// Add external contacts ids
$tmparray = $this -> ticket -> liste_contact ( - 1 , 'external' , $contact_list );
if ( is_array ( $tmparray )) {
$this -> ticket -> contacts_ids = $tmparray ;
}
$tmparray = $this -> ticket -> liste_contact ( - 1 , 'internal' , $contact_list );
if ( is_array ( $tmparray )) {
$this -> ticket -> contacts_ids_internal = $tmparray ;
}
}
2020-12-01 01:41:19 +00:00
return $this -> _cleanObjectDatas ( $this -> ticket );
}
/**
* List tickets
*
* Get a list of tickets
*
2026-01-20 16:36:38 +00:00
* @ param int $socid Filter list with thirdparty ID
* @ param string $sortfield Sort field
* @ param string $sortorder Sort order
* @ param int $limit Limit for list
* @ param int $page Page number
2026-05-29 10:16:42 +00:00
* @ param string $sqlfilters Other criteria to filter answers separated by a comma . Syntax example " (t.ref:like:'SO-%') and (t.date_creation:>:'20160101') and (t.fk_statut:=:1) "
2026-01-20 16:36:38 +00:00
* @ param string $properties Restrict the data returned to these properties . Ignored if empty . Comma separated list of properties names
2025-09-19 22:45:30 +00:00
* @ param int $loadcontacts Load also contacts / addresses ( 0 = No , 1 = Yes )
2026-01-20 16:36:38 +00:00
* @ param bool $pagination_data If this parameter is set to true the response will include pagination data . Default value is false . Page starts from 0 *
2020-12-01 01:41:19 +00:00
*
* @ return array Array of ticket objects
2025-03-03 00:49:20 +00:00
* @ phan - return Ticket [] | array { data : Ticket [], pagination : array { total : int , page : int , page_count : int , limit : int }}
* @ phpstan - return Ticket [] | array { data : Ticket [], pagination : array { total : int , page : int , page_count : int , limit : int }}
2020-12-01 01:41:19 +00:00
*/
2025-09-19 22:45:30 +00:00
public function index ( $socid = 0 , $sortfield = " t.rowid " , $sortorder = " ASC " , $limit = 100 , $page = 0 , $sqlfilters = '' , $properties = '' , $loadcontacts = 0 , $pagination_data = false )
2020-12-01 01:41:19 +00:00
{
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'read' )) {
2021-04-08 17:05:28 +00:00
throw new RestException ( 403 );
}
2020-12-01 01:41:19 +00:00
$obj_ret = array ();
2025-07-01 18:34:09 +00:00
$socid = DolibarrApiAccess :: $user -> socid ? : $socid ;
2020-12-01 01:41:19 +00:00
2022-10-15 17:11:38 +00:00
$search_sale = null ;
2020-12-01 01:41:19 +00:00
// If the internal user must only see his customers, force searching by him
2022-08-10 23:09:56 +00:00
$search_sale = 0 ;
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'societe' , 'client' , 'voir' ) && ! $socid ) {
2020-12-01 01:41:19 +00:00
$search_sale = DolibarrApiAccess :: $user -> id ;
}
$sql = " SELECT t.rowid " ;
2024-01-09 09:44:50 +00:00
$sql .= " FROM " . MAIN_DB_PREFIX . " ticket AS t " ;
2025-07-18 00:35:50 +00:00
$sql .= " LEFT JOIN " . MAIN_DB_PREFIX . " societe AS s ON (s.rowid = t.fk_soc) " ;
2024-01-09 09:44:50 +00:00
$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
2020-12-01 01:41:19 +00:00
$sql .= ' WHERE t.entity IN (' . getEntity ( 'ticket' , 1 ) . ')' ;
if ( $socid > 0 ) {
2021-06-09 13:36:47 +00:00
$sql .= " AND t.fk_soc = " . (( int ) $socid );
2020-12-01 01:41:19 +00:00
}
2024-01-09 09:44:50 +00:00
// Search on sale representative
if ( $search_sale && $search_sale != '-1' ) {
if ( $search_sale == - 2 ) {
$sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . " societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc) " ;
} elseif ( $search_sale > 0 ) {
$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 ) . " ) " ;
}
2020-12-01 01:41:19 +00:00
}
// Add sql filters
if ( $sqlfilters ) {
2021-12-20 19:49:32 +00:00
$errormessage = '' ;
2023-02-25 18:48:33 +00:00
$sql .= forgeSQLFromUniversalSearchCriteria ( $sqlfilters , $errormessage );
if ( $errormessage ) {
throw new RestException ( 400 , 'Error when validating parameter sqlfilters -> ' . $errormessage );
2020-12-01 01:41:19 +00:00
}
}
2024-08-18 14:20:22 +00:00
//this query will return total orders with the filters given
$sqlTotals = str_replace ( 'SELECT t.rowid' , 'SELECT count(t.rowid) as total' , $sql );
2020-12-01 01:41:19 +00:00
$sql .= $this -> db -> order ( $sortfield , $sortorder );
if ( $limit ) {
if ( $page < 0 ) {
$page = 0 ;
}
$offset = $limit * $page ;
$sql .= $this -> db -> plimit ( $limit , $offset );
}
$result = $this -> db -> query ( $sql );
if ( $result ) {
$i = 0 ;
2025-09-28 20:20:44 +00:00
$num = $this -> db -> num_rows ( $result );
$min = min ( $num , ( $limit <= 0 ? $num : $limit ));
while ( $i < $min ) {
2020-12-01 01:41:19 +00:00
$obj = $this -> db -> fetch_object ( $result );
$ticket_static = new Ticket ( $this -> db );
if ( $ticket_static -> fetch ( $obj -> rowid )) {
if ( $ticket_static -> fk_user_assign > 0 ) {
$userStatic = new User ( $this -> db );
$userStatic -> fetch ( $ticket_static -> fk_user_assign );
2026-01-20 16:36:38 +00:00
//$ticket_static->fk_user_assign_string = dolGetFirstLastname($userStatic->firstname, $userStatic->lastname);
2020-12-01 01:41:19 +00:00
}
2025-09-19 22:45:30 +00:00
if ( $loadcontacts ) {
// Add external contacts ids
$tmparray = $ticket_static -> liste_contact ( - 1 , 'external' , 1 );
if ( is_array ( $tmparray )) {
$ticket_static -> contacts_ids = $tmparray ;
}
$tmparray = $ticket_static -> liste_contact ( - 1 , 'internal' , 1 );
if ( is_array ( $tmparray )) {
$ticket_static -> contacts_ids_internal = $tmparray ;
}
}
2023-09-26 16:04:48 +00:00
$obj_ret [] = $this -> _filterObjectProperties ( $this -> _cleanObjectDatas ( $ticket_static ), $properties );
2020-12-01 01:41:19 +00:00
}
$i ++ ;
}
} else {
throw new RestException ( 503 , 'Error when retrieve ticket list' );
}
2023-12-31 13:11:05 +00:00
2024-08-18 14:20:22 +00:00
//if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
if ( $pagination_data ) {
$totalsResult = $this -> db -> query ( $sqlTotals );
$total = $this -> db -> fetch_object ( $totalsResult ) -> total ;
$tmp = $obj_ret ;
$obj_ret = [];
$obj_ret [ 'data' ] = $tmp ;
$obj_ret [ 'pagination' ] = [
'total' => ( int ) $total ,
'page' => $page , //count starts from 0
'page_count' => ceil (( int ) $total / $limit ),
'limit' => $limit
];
}
2023-12-04 12:53:48 +00:00
return $obj_ret ;
2020-12-01 01:41:19 +00:00
}
/**
* Create ticket object
*
2025-03-02 11:13:32 +00:00
* @ param array $request_data Request data
* @ phan - param ? array < string , string > $request_data
* @ phpstan - param ? array < string , string > $request_data
2020-12-01 01:41:19 +00:00
* @ return int ID of ticket
*/
public function post ( $request_data = null )
{
$ticketstatic = new Ticket ( $this -> db );
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'write' )) {
2024-02-01 18:16:58 +00:00
throw new RestException ( 403 );
2020-12-01 01:41:19 +00:00
}
2026-04-23 11:25:52 +00:00
2020-12-01 01:41:19 +00:00
// Check mandatory fields
2026-04-23 11:25:52 +00:00
$this -> _validate ( $request_data );
// Check thirdparty validity
$socid = ( int ) $request_data [ 'socid' ];
if ( $socid > 0 ) {
$thirdpartytmp = new Societe ( $this -> db );
$thirdparty_result = $thirdpartytmp -> fetch ( $socid );
if ( $thirdparty_result < 1 ) {
throw new RestException ( 404 , 'Thirdparty with id=' . $socid . ' not found or not allowed' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'societe' , $thirdpartytmp -> id )) {
throw new RestException ( 404 , 'Thirdparty with id=' . $thirdpartytmp -> id . ' not found or not allowed' );
}
}
2020-12-01 01:41:19 +00:00
foreach ( $request_data as $field => $value ) {
2023-12-15 11:15:33 +00:00
if ( $field === 'caller' ) {
2024-01-12 16:18:52 +00:00
// 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
2024-04-02 10:28:55 +00:00
$this -> ticket -> context [ 'caller' ] = sanitizeVal ( $request_data [ 'caller' ], 'aZ09' );
2023-12-15 11:15:33 +00:00
continue ;
}
2024-04-02 10:28:55 +00:00
$this -> ticket -> $field = $this -> _checkValForAPI ( $field , $value , $this -> ticket );
2020-12-01 01:41:19 +00:00
}
if ( empty ( $this -> ticket -> ref )) {
$this -> ticket -> ref = $ticketstatic -> getDefaultRef ();
}
if ( empty ( $this -> ticket -> track_id )) {
$this -> ticket -> track_id = generate_random_id ( 16 );
}
if ( $this -> ticket -> create ( DolibarrApiAccess :: $user ) < 0 ) {
throw new RestException ( 500 , " Error creating ticket " , array_merge ( array ( $this -> ticket -> error ), $this -> ticket -> errors ));
}
return $this -> ticket -> id ;
}
/**
2024-04-02 11:25:31 +00:00
* Add a new message to an existing ticket identified by property -> track_id into request .
2020-12-01 01:41:19 +00:00
*
2025-03-02 11:13:32 +00:00
* @ param array $request_data Request data
2026-04-03 01:19:07 +00:00
* @ phan - param ? array < string , mixed > $request_data
* @ phpstan - param ? array < string , mixed > $request_data
* @ return int | array ID of ticket , or ticket / action IDs when requested
* @ phan - return int | array { ticket_id : int , action_id : int }
* @ phpstan - return int | array { ticket_id : int , action_id : int }
2020-12-01 01:41:19 +00:00
*/
public function postNewMessage ( $request_data = null )
{
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'write' )) {
2024-02-01 18:16:58 +00:00
throw new RestException ( 403 );
2020-12-01 01:41:19 +00:00
}
2026-04-23 11:33:52 +00:00
2020-12-01 01:41:19 +00:00
// Check mandatory fields
$result = $this -> _validateMessage ( $request_data );
2026-04-03 01:19:07 +00:00
$return_action_id = false ;
if ( isset ( $request_data [ 'return_action_id' ])) {
$return_action_id = ! empty ( $request_data [ 'return_action_id' ]);
unset ( $request_data [ 'return_action_id' ]);
}
$attachments = array ();
if ( isset ( $request_data [ 'attachments' ]) && is_array ( $request_data [ 'attachments' ])) {
$attachments = $request_data [ 'attachments' ];
unset ( $request_data [ 'attachments' ]);
}
2020-12-01 01:41:19 +00:00
foreach ( $request_data as $field => $value ) {
2023-12-15 11:15:33 +00:00
if ( $field === 'caller' ) {
2024-01-12 16:18:52 +00:00
// 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
2024-04-02 10:28:55 +00:00
$this -> ticket -> context [ 'caller' ] = sanitizeVal ( $request_data [ 'caller' ], 'aZ09' );
2023-12-15 11:15:33 +00:00
continue ;
}
2024-04-02 10:28:55 +00:00
$this -> ticket -> $field = $this -> _checkValForAPI ( $field , $value , $this -> ticket );
2020-12-01 01:41:19 +00:00
}
$ticketMessageText = $this -> ticket -> message ;
2026-08-04 16:36:51 +00:00
// Allow targeting the ticket by id or ref, not only track_id
if ( ! empty ( $this -> ticket -> id )) {
$result = $this -> ticket -> fetch ( $this -> ticket -> id );
} elseif ( ! empty ( $this -> ticket -> ref )) {
$result = $this -> ticket -> fetch ( 0 , $this -> ticket -> ref );
} else {
$result = $this -> ticket -> fetch ( 0 , '' , $this -> ticket -> track_id );
}
2020-12-01 01:41:19 +00:00
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
2026-04-23 11:33:52 +00:00
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
}
2020-12-01 01:41:19 +00:00
$this -> ticket -> message = $ticketMessageText ;
2026-04-03 01:19:07 +00:00
$filename_list = array ();
$mimetype_list = array ();
$mimefilename_list = array ();
if ( ! empty ( $attachments )) {
global $conf ;
require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php' ;
$destdir = $conf -> ticket -> dir_output . '/' . $this -> ticket -> ref ;
if ( ! dol_is_dir ( $destdir ) && dol_mkdir ( $destdir ) < 0 ) {
throw new RestException ( 500 , 'Error while trying to create directory ' . $destdir );
}
foreach ( $attachments as $attachment ) {
if ( ! is_array ( $attachment ) || empty ( $attachment [ 'filename' ])) {
continue ;
}
$filename = dol_sanitizeFileName ( $attachment [ 'filename' ]);
if ( empty ( $filename )) {
continue ;
}
$filecontent = isset ( $attachment [ 'filecontent' ]) ? $attachment [ 'filecontent' ] : '' ;
$fileencoding = isset ( $attachment [ 'fileencoding' ]) ? $attachment [ 'fileencoding' ] : '' ;
$content = ( $fileencoding === 'base64' ) ? base64_decode ( $filecontent ) : $filecontent ;
if ( $content === false ) {
throw new RestException ( 400 , 'Failed to decode attachment ' . $filename );
}
$destfile = $destdir . '/' . $filename ;
if ( is_file ( $destfile )) {
$pathinfo = pathinfo ( $filename );
$suffix = ' - ' . dol_print_date ( dol_now (), 'dayhourlog' );
$extension = ! empty ( $pathinfo [ 'extension' ]) ? '.' . $pathinfo [ 'extension' ] : '' ;
$basename = ! empty ( $pathinfo [ 'filename' ]) ? $pathinfo [ 'filename' ] : preg_replace ( '/\.[^.]+$/' , '' , $filename );
$destfile = $destdir . '/' . $basename . $suffix . $extension ;
}
$destfiletmp = DOL_DATA_ROOT . '/admin/temp/' . basename ( $destfile );
if ( ! dol_is_dir ( dirname ( $destfiletmp ))) {
dol_mkdir ( dirname ( $destfiletmp ));
}
$fhandle = @ fopen ( $destfiletmp , 'w' );
if ( ! $fhandle ) {
throw new RestException ( 500 , " Failed to open file ' " . $destfiletmp . " ' for write " );
}
$nbofbyteswrote = fwrite ( $fhandle , $content );
fclose ( $fhandle );
if ( $nbofbyteswrote === false ) {
throw new RestException ( 500 , " Failed to write file ' " . $destfiletmp . " ' " );
}
$moreinfo = array (
'description' => 'File uploaded using ticket API' ,
'src_object_type' => $this -> ticket -> element ,
'src_object_id' => $this -> ticket -> id ,
'gen_or_uploaded' => 'uploaded'
);
$resultmove = dol_move ( $destfiletmp , $destfile , '0' , 1 , 0 , 1 , $moreinfo );
if ( ! $resultmove ) {
throw new RestException ( 500 , " Failed to move file into ' " . $destfile . " ' " );
}
$filename_list [] = $destfile ;
$mimefilename_list [] = basename ( $destfile );
$mimetype_list [] = ! empty ( $attachment [ 'mimetype' ]) ? $attachment [ 'mimetype' ] : '' ;
}
}
$actionid = $this -> ticket -> createTicketMessage ( DolibarrApiAccess :: $user , 0 , $filename_list , $mimetype_list , $mimefilename_list );
if ( $actionid <= 0 ) {
2022-03-18 15:14:20 +00:00
throw new RestException ( 500 , 'Error when creating ticket' );
2020-12-01 01:41:19 +00:00
}
2026-04-03 01:19:07 +00:00
if ( $return_action_id ) {
return array ( 'ticket_id' => $this -> ticket -> id , 'action_id' => $actionid );
}
2020-12-01 01:41:19 +00:00
return $this -> ticket -> id ;
}
/**
* Update ticket
*
2024-02-22 00:32:55 +00:00
* @ param int $id Id of ticket to update
2025-03-02 11:13:32 +00:00
* @ param array $request_data Data
* @ phan - param ? array < string , string > $request_data
* @ phpstan - param ? array < string , string > $request_data
2024-02-22 00:32:55 +00:00
* @ return Object Updated object
2020-12-01 01:41:19 +00:00
*/
public function put ( $id , $request_data = null )
{
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'write' )) {
2024-02-01 18:16:58 +00:00
throw new RestException ( 403 );
2020-12-01 01:41:19 +00:00
}
$result = $this -> ticket -> fetch ( $id );
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
2024-04-02 12:47:49 +00:00
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
2020-12-01 01:41:19 +00:00
}
2026-04-23 11:29:47 +00:00
// Check thirdparty validity
$socid = ( int ) $request_data [ 'socid' ];
if ( $socid > 0 ) {
$thirdpartytmp = new Societe ( $this -> db );
$thirdparty_result = $thirdpartytmp -> fetch ( $socid );
if ( $thirdparty_result < 1 ) {
throw new RestException ( 404 , 'Thirdparty with id=' . $socid . ' not found or not allowed' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'societe' , $thirdpartytmp -> id )) {
throw new RestException ( 404 , 'Thirdparty with id=' . $thirdpartytmp -> id . ' not found or not allowed' );
}
}
2020-12-01 01:41:19 +00:00
foreach ( $request_data as $field => $value ) {
2023-12-15 11:15:33 +00:00
if ( $field === 'caller' ) {
2024-01-12 16:18:52 +00:00
// 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
2024-04-02 10:28:55 +00:00
$this -> ticket -> context [ 'caller' ] = sanitizeVal ( $request_data [ 'caller' ], 'aZ09' );
2023-12-15 11:15:33 +00:00
continue ;
}
2025-01-16 09:37:29 +00:00
if ( $field == 'id' ) {
continue ;
}
if ( $field == 'array_options' && is_array ( $value )) {
foreach ( $value as $index => $val ) {
2026-05-08 23:16:35 +00:00
$this -> ticket -> array_options [ $index ] = $this -> _checkValExtrafieldsForAPI ( $index , $val , $this -> ticket );
2025-01-16 09:37:29 +00:00
}
continue ;
}
2025-01-20 13:57:45 +00:00
2024-04-02 10:28:55 +00:00
$this -> ticket -> $field = $this -> _checkValForAPI ( $field , $value , $this -> ticket );
2020-12-01 01:41:19 +00:00
}
2024-03-01 19:38:27 +00:00
if ( $this -> ticket -> update ( DolibarrApiAccess :: $user ) > 0 ) {
2020-12-01 01:41:19 +00:00
return $this -> get ( $id );
2024-03-01 19:38:27 +00:00
} else {
throw new RestException ( 500 , $this -> ticket -> error );
2020-12-01 01:41:19 +00:00
}
}
/**
* Delete ticket
*
* @ param int $id Ticket ID
* @ return array
2025-03-02 14:10:25 +00:00
* @ phan - return array { success : array { code : int , message : string }}
* @ phpstan - return array { success : array { code : int , message : string }}
2020-12-01 01:41:19 +00:00
*/
public function delete ( $id )
{
2024-02-09 14:58:49 +00:00
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'delete' )) {
2024-02-01 18:16:58 +00:00
throw new RestException ( 403 );
2020-12-01 01:41:19 +00:00
}
$result = $this -> ticket -> fetch ( $id );
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
2024-04-02 12:47:49 +00:00
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
2020-12-01 01:41:19 +00:00
}
2024-03-07 17:48:50 +00:00
if ( ! $this -> ticket -> delete ( DolibarrApiAccess :: $user )) {
2022-03-18 15:14:20 +00:00
throw new RestException ( 500 , 'Error when deleting ticket' );
2020-12-01 01:41:19 +00:00
}
return array (
'success' => array (
'code' => 200 ,
'message' => 'Ticket deleted'
)
);
}
/**
* Validate fields before create or update object
*
2025-03-02 14:10:25 +00:00
* @ param ? array < string , string > $data Data to validate
* @ return array < string , string >
2020-12-01 01:41:19 +00:00
*
* @ throws RestException
*/
private function _validate ( $data )
{
2025-03-02 14:10:25 +00:00
if ( $data === null ) {
$data = array ();
}
2020-12-01 01:41:19 +00:00
$ticket = array ();
foreach ( Tickets :: $FIELDS as $field ) {
if ( ! isset ( $data [ $field ])) {
throw new RestException ( 400 , " $field field missing " );
}
$ticket [ $field ] = $data [ $field ];
}
return $ticket ;
}
/**
* Validate fields before create or update object message
*
2025-03-02 14:10:25 +00:00
* @ param ? array < string , string > $data Data to validate
* @ return array < string , string >
2020-12-01 01:41:19 +00:00
*
* @ throws RestException
*/
private function _validateMessage ( $data )
{
2025-03-02 14:10:25 +00:00
if ( $data === null ) {
$data = array ();
}
2020-12-01 01:41:19 +00:00
$ticket = array ();
foreach ( Tickets :: $FIELDS_MESSAGES as $field ) {
if ( ! isset ( $data [ $field ])) {
throw new RestException ( 400 , " $field field missing " );
}
$ticket [ $field ] = $data [ $field ];
}
return $ticket ;
}
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
/**
* Clean sensible object datas
2025-11-20 00:47:46 +00:00
* @ phpstan - template T
2020-12-01 01:41:19 +00:00
*
* @ param Object $object Object to clean
* @ return Object Object with cleaned properties
*
2025-11-20 00:47:46 +00:00
* @ phpstan - param T $object
* @ phpstan - return T
2020-12-01 01:41:19 +00:00
* @ todo use an array for properties to clean
*/
protected function _cleanObjectDatas ( $object )
{
// phpcs:enable
$object = parent :: _cleanObjectDatas ( $object );
// Other attributes to clean
$attr2clean = array (
" contact " ,
" contact_id " ,
" ref_previous " ,
" ref_next " ,
" ref_ext " ,
" table_element_line " ,
" statut " ,
" country " ,
" country_id " ,
" country_code " ,
" barcode_type " ,
" barcode_type_code " ,
" barcode_type_label " ,
" barcode_type_coder " ,
" mode_reglement_id " ,
" cond_reglement_id " ,
" cond_reglement " ,
" fk_delivery_address " ,
" shipping_method_id " ,
" modelpdf " ,
" fk_account " ,
" note_public " ,
" note_private " ,
" note " ,
" total_ht " ,
" total_tva " ,
" total_localtax1 " ,
" total_localtax2 " ,
" total_ttc " ,
" fk_incoterms " ,
" label_incoterms " ,
" location_incoterms " ,
" name " ,
" lastname " ,
" firstname " ,
" civility_id " ,
" canvas " ,
" cache_msgs_ticket " ,
" cache_logs_ticket " ,
" cache_types_tickets " ,
" regeximgext " ,
2023-11-24 09:10:24 +00:00
" labelStatus " ,
2024-04-02 10:28:55 +00:00
" labelStatusShort " ,
" multicurrency_code " ,
" multicurrency_tx " ,
" multicurrency_total_ht " ,
" multicurrency_total_ttc " ,
" multicurrency_total_tva " ,
" multicurrency_total_localtax1 " ,
" multicurrency_total_localtax2 "
2020-12-01 01:41:19 +00:00
);
foreach ( $attr2clean as $toclean ) {
unset ( $object -> $toclean );
}
// If object has lines, remove $db property
if ( isset ( $object -> lines ) && count ( $object -> lines ) > 0 ) {
$nboflines = count ( $object -> lines );
for ( $i = 0 ; $i < $nboflines ; $i ++ ) {
$this -> _cleanObjectDatas ( $object -> lines [ $i ]);
}
}
// If object has linked objects, remove $db property
if ( isset ( $object -> linkedObjects ) && count ( $object -> linkedObjects ) > 0 ) {
foreach ( $object -> linkedObjects as $type_object => $linked_object ) {
foreach ( $linked_object as $object2clean ) {
$this -> _cleanObjectDatas ( $object2clean );
}
}
}
return $object ;
}
Add endpoints link/unlink contacts on tickets (#37277)
* Avoid ( in sql
* Clean sql
* Fix spellcheck
* Debug v23
* TakePOS hook “AddAction” jamais exécuté (#35961) (#37113)
@defrance
Adam Hocini
* Update modProduct.class.php (#37087)
Fix: allow import of sell_or_eat_by_mandatory for products
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Update modProduct.class.php (#37087)
Fix: allow import of sell_or_eat_by_mandatory for products
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Clean code
* Fix package debian
* NEW Use the js lib into htdocs/public/includes instead of htdocs/includes
* New LNE Collect of buisness informations (#37084)
* Working LNE ping
* remove GPDA
* Add of other informations
* remove testing var
* fix Ci
* fix Ci
* fix ci
* fix CI
* Fix Ci
* fix Ci
---------
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
* CI
* CI
* CI
* CI
* Clean code. File not used.
* CSS
* CSS
* Fix phpunit
* More legal info
* CI
* Fix CI
* Fix: IRPF tax not applied when creating invoice from project times (#37077)
* Remove 'supplier_invoice' from old path array
* Update module path in arrayforoldpath
Sorry Eldy, I was confused. You are absolutely right, it is already corrected.
* Replace localtax2 assignment with get_localtax function
Error when creating an invoice with personal income tax from project times. The rate does not apply
* Refactor localtax1 calculation using get_localtax
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix: IRPF tax not applied when creating invoice from project times (#37077)
* Remove 'supplier_invoice' from old path array
* Update module path in arrayforoldpath
Sorry Eldy, I was confused. You are absolutely right, it is already corrected.
* Replace localtax2 assignment with get_localtax function
Error when creating an invoice with personal income tax from project times. The rate does not apply
* Refactor localtax1 calculation using get_localtax
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Protect module
* Add currency
* CSS
* WIP LNE
* Fix navigation
* cs
* Debug registration process
* Debug setup navigation
* CI
* CI
* Factorize code
* Fix CI
* Fix CI
* Fix CI
* CI
* CI
* CI
* CI
* Disable phan on v23
* CI
* CI
* FIX: missing include for blockedlog lib (#37165)
* Debug CI
* fix ternary always true (#37161)
* fix ternary always true
* Update requests.php
* Update registration.php
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* CI
* FIX: missing include for blockedlog lib (#37165)
* FIX #37134 Use json_encode for IMAP search logging in EmailCollector (#37135)
var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch) as each line becomes a separate log entry.
Using json_encode() produces single-line structured output that works
correctly with all log aggregation tools.
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Populate syslog with placeholder (#37163)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Doc
* datamodel for user change password next time (#37155)
* Qual: Update phan baseline (#37172)
* Fix typo in file path (#37160)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix phpunit
* Complete call to setStatus so we have a trigy as 4th parameter (help to
fix the #37129)
* FIX Product - Warning on admin (#37157)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* FIX Product - Warning on admin + CSS (#37158)
* FIX Product - Warning on admin
* CSS
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* CI
* CI
* CI
* Avoid error if include fails
* More functions in blacklist (even if nw we use the whitelist by default)
* #37166 [SQL] add: email template for ticket admin creation (#37182)
* Replace var_export with new function formatLogObject (#37178)
* FIX BOM - Class product missing, column offset and information recording (form/input overlap) in admin (#37177)
* FIX BOM - Class product missing in admin
* Fix column offset and information recording (form/input overlap)
* ci
* ci
* FIX Intracommreport - Warning & link problem on tab (#37176)
* FIX Bank transfer admin - Warning & save 0 on constant PAYMENTBYBANKTRANSFER_ADDDAYS (#37175)
* Look and feel v24
* Update default time handling in index.php (#37150)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix file path comment in supplier invoice module (#37133)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix file path comment in supplier invoice module (#37133)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix CI
* Add template in migration
* fix phpdoc comment (#37184)
* fix phpdoc comment
* fix phpdoc comment
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* CI
* Fix phpstan
* Replace var_export by formatLogObject (continued) (#37188)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Debug amount suggested on membership public form
* Issue 36923 Fix session title handling in survey creation (#37105)
* Issue 36923 Fix session title handling in survey creation
* Change input field to use id attribute for title
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* fix phpstan errors blocking action baseline (#37189)
* fix phpstan errors blocking action baseline
* fix phpstan errors blocking action baseline
* fix phpstan errors blocking action baseline
* fix phpstan errors blocking action baseline
* fix phpstan errors blocking action baseline
* refresh baseline
* QUAL Replace var_export() with json_encode() in dol_syslog() calls (#37138)
var_export() produces multiline output that breaks log aggregators
(Loki, Splunk, Elasticsearch, Graylog) as each line becomes a separate
log entry.
json_encode() produces single-line structured output that works correctly
with all log aggregation tools. This pattern is already used elsewhere
in Dolibarr (accountancy, install modules).
Files changed:
- core/class/commoninvoice.class.php (payment intent logging)
- core/class/commonobject.class.php (payment terms logging)
- core/modules/mailings/advthirdparties.modules.php (mailing targets)
- core/modules/oauth/google_oauthcallback.php (userinfo logging)
- core/modules/oauth/generic_oauthcallback.php (userinfo logging)
- public/payment/newpayment.php (GET/POST debug logging)
- public/payment/paymentok.php (payment tag logging)
- public/stripe/ipn.php (Stripe event data logging)
- paypal/lib/paypal.lib.php (PayPal response logging)
- api/index.php (API debug logging)
- stripe/class/stripe.class.php (payment/setup intent logging)
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix CI
* CI
* Check if upload_max_filesize is not empty
* clean code
* Debug v23
* CI
* Fix #33521 VAT total false (#36990)
* - Fix #33521 VAT total false
- Fix some warnings
- Fix : delete $this->vat_rate
* - Fix #33521 VAT total false
- Fix some warnings
- Fix :delete $this->tva array (replaced by $this->tva_array)
* - Fix #33521 VAT total false
- Fix some warnings
- Fix :delete $this->tva array (replaced by $this->tva_array)
* Update pdf_octopus.modules.php
* Update pdf_octopus.modules.php
* Update pdf_octopus.modules.php
* Update pdf_octopus.modules.php
* Update pdf_octopus.modules.php
---------
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* NEW #25829 Automatically send the invoice generated from a template (#36967)
* Update DB
* ADD email template
* Ajout d'une clé de trad
* Ajout des traductions
* Suppression des traductions, sauf en_US
* Add flag auto send
* Modif form + cron auto send
* Suppression auto_send
* correction loopError
* ajout du selected au model de mail
* Prise en compte default model
* Fix pre-commit
* ménage
* precommit
* Correction Phan
* Correction Phan
* Correction, double cal du trigger
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Develop force user change pass userclass (#37174)
* datamodel for user change password next time
* add force_pass_change in user object
* Initialize force_pass_change to 0
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Another step for #37171
* Qual: Partial phan run on PR's, complete run on integration branches (#37186)
* Qual: Partial phan run on PR's, complete on main
# Qual: Partial phan run on PR's, complete on main
The selection is based on the branch name.
To run a complete phan run in a PR, the branch name of the PR must include phan_full.
This can help to fix remaining phan issue before re-integrating to the develop branch.
* qual: Update workflow and pre-commit configurations
- Enable phan workflow by uncommenting the relevant lines
- Update actionlint version to v1.7.10
- Add manual stage to actionlint hook in pre-commit-config.yaml
* qual: Update Phan analysis conditions
The conditions for running Phan analysis have been updated to include an additional check for branches containing 'phan_full'.
* qual: Update Phan workflow
- Replace github.event.ref with github.ref_name
- Add FILE_CHANGE_LIST environment variable for better file handling
- Update file list creation and usage in the workflow
* qual: Update Phan workflow conditions
Fix the branch reference (head_ref in PR, ref_name otherwise)
* Add step for debug information
* Remove debug step
* Fix: Missing initialisations members/new.php
Following a suppression of assignments, the variables disabledphy and disabledmor were undefined.
* fix: Update budget selection dropdown arguments in member creation form
Correct the arguments in the member creation form.
* qual: Add cs2pr to phan workflow
- Add cs2pr to the tools list in the phan workflow
- Change the output mode of phan to checkstyle
- Add a step to add results to PR as Github notices
- Add a step to provide phan log as artifact
* qual: Update Phan workflow to use environment variable for file list
The change fixes the Phan workflow to use the environment variable `$FILE_CHANGED_LIST` to clear the file
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix phan
* Fix phan
* Merge branch '23.0' of git@github.com:Dolibarr/dolibarr.git into 23.0
* Doc
* Qual: Fix ambigious redirect error on Phan workflow (#37200)
# Qual: Fix ambigious redirect error on Phan workflow
Rewrote the shell command that is supposed to suppress a file contents
but is flagged by the environment.
* PHPStan > Update baseline (#37197)
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
* Typo fix (#37195)
* Debug v23 - filters on agenda pages
* css
* css
* css
* qual: Update PHPStan workflow to run on all files in integration (#37207)
The PHPStan workflow has been updated to run on all files in integration branches.
* Fix CI
* Add hook in isEditable()
* Debug v23
* CLOSE #37190 ODT Templates for thirdparties - Birthday is returned in epoch format (#37198)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* fix: Remove HTML from accounting menu tooltips in eldy theme (#37203)
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Qual: Update spelling (#37199)
* Qual: Update spelling for pre-select variants
# Qual: Update spelling for pre-select variants
In English, preselect is without the hyphen. Update text and made some translations
related to preselect.
* Qual: Update composant to component and/or adequate translation.
# Qual: Update composant to component and/or adequate translation.
"Composant(s)" was mostly referenced in french file/class comments.
Updated
* Qual: Fix misspellings related to "criteria"
# Qual: Fix misspellings related to "criteria"
* Qual: Fix produt misspellings
# Qual: Fix produt misspellings
Change 'produt' to 'product'.
* Qual: Update French comments with "composants"
#Qual: Update French comments with "composants"
- Translating French comments to English (avoid codespell notice)
* Qual: Fixed typo 'bad practive' to 'bad practice'
# Qual: Fixed typo 'bad practive' to 'bad practice'
* Qual: Update phan.yml to exclude specific files from analysis
- Added file exclusion pattern to match phan configuration
- Added check for empty file list to avoid unnecessary phan execution
* Qual: Update file filtering in phan.yml workflow
The change updates the file filtering process in the phan.yml workflow to correctly redirect the output of the grep command to a temporary file.
* Qual: Ignore $systemfunction always exists
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Error handling methods for commonobject (#37201)
* Error handling methods for commonobject (#37201)
* NEW Can request and force user to change its password (#37196)
* force user to change password : redirect to user card on login
* force user to change password : redirect to user card on login
* redirect to a dedicated page
* bad old idea : self change passwd on user card + edit mode and rights: it makes a hole on security check
* only apply on dolibarr auth mode context
* only on dolibarr auth mode context
* Fix force_pass_change SQL assignment logic
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Doc
* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION
* Setup easier to understand between INVOICE_CHECK_POSTERIOR_DATE and
FAC_FORCE_DATE_VALIDATION
* Prepare 23.0
* Qual: Ignore exit code from `grep -v` in phan flow (#37213)
# Qual: Ignore exit code from `grep -v` in phan flow
`grep -v` returns 1 when the resulting filtered list is empty and would stop the execution.
This is fixed with `|| true` to have a final exit code that is 0.
* WIP LNE
* Debug v23
* Debug v23
* WIP LNE
* FIX Bad header name
* Fix CSP for ping
* Compatibility with multicompany
* Debug
* Fix include
* FIX phpdoc on createFixedAmountDiscount() (#37212)
* FIX phpdoc on createFixedAmountDiscount
* FIX phpdoc
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* Fix in card css modal display (#36569)
* Fix display of cards in a modal
* fix php stan
* fix php stan
* Try a change to force CI
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* More complete data on pings.
* Debug v23
* Debug initdemo
* Debug initdemo
* Debug initdemo
* Debug v23
* Update initdemo
* Update initdemo
* Cleaner GET call
* Add id of perm in url
* Init demo for v24
* Fix translation of "various payment" (not english, not pro) with the
term used in english to mean "Opérations diverses (OD)"
* Sync transifex
* CSS
* Trans
* Doc
* Trans
* Debug v23
* Debug v23
* Trans
* Debug v24
* Debug v23
* Debug v23
* WIP
* Debug v23
* Debug v23
* Debug v24
* Work on LNE
* WIP LNE
* Debug v24
* Doc
* Translation
* Sync transifex
* Debug
* Debug
* Debug
* Responsive
* Dev control archive integrity
* Debug
* Update supplier_proposal.lang (#37238)
* add company name on dropdown contract list (#37245)
* Clean dump file
* Fix CI
* FIX #37246 Modifying resteapayer calculation for credit note (#37247)
* Add 'type' parameter to completeTabsHead hook (#37235)
* #37257 FIX for piesemicircle (#37258)
* Fix #37227 Fix #37233
* Fix #37227 Fix #37233
* QUAL French comment in English (#37225)
* Edit label of $fields
* QUAL French comment in English
* Bump actions/upload-artifact from 4 to 6 (#37250)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-version: '6'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
* UI/UX : Transfert feedback css from experimental to core (#37226)
* Transfert feedback css from experimental to core
* fix php phan
* Fix phan
* Fix phan
* Fix phan
* Missing label
* Debug
* Add method to chech if invoice can be replaced
* Fix return
* Debug v24
* Fix CI
* CI
* CI
* CI
* Fix CI
* Add endpoints link/unlink contacts on tickets
* Check source
* Typage
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
Co-authored-by: adamhocini <72007447+adamhocini@users.noreply.github.com>
Co-authored-by: Jarvis <94354305+Jarvis-69@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <45882981+Hystepik@users.noreply.github.com>
Co-authored-by: Lucas Marcouiller <lmarcouiller@dolicloud.com>
Co-authored-by: Laurent Destailleur <eldy@users.sourceforge.net>
Co-authored-by: Yamil Esteban Garcia <120027058+developmentOYR@users.noreply.github.com>
Co-authored-by: Noé Cendrier <81741011+altairis-noe@users.noreply.github.com>
Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com>
Co-authored-by: minimexat <minimexat@gmail.com>
Co-authored-by: hansemschnokeloch <hansemschnokeloch@users.noreply.github.com>
Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com>
Co-authored-by: MDW <mdeweerd@users.noreply.github.com>
Co-authored-by: Alexandre SPANGARO <alexandre.spangaro@gmail.com>
Co-authored-by: evarisk-kilyan <kilyan.evarisk@gmail.com>
Co-authored-by: Vanyo <vanyolai@gmail.com>
Co-authored-by: jeremydassaud <49372108+jeremydassaud@users.noreply.github.com>
Co-authored-by: f-hoedl <hoefla14@htl-kaindorf.ac.at>
Co-authored-by: Vincent Maury <artec.vm@arnac.net>
Co-authored-by: vmaury <vmaury@vmaury-Lafite-Pro-16-AMD>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dolibot <dolibarr-bot@users.noreply.github.com>
Co-authored-by: Joris Le Blansch <jleblansch@gmail.com>
Co-authored-by: intelliking <tyleradams93226@gmail.com>
Co-authored-by: Benjamin Falière <121813548+BenjaminFlr@users.noreply.github.com>
Co-authored-by: John BOTELLA <68917336+thersane-john@users.noreply.github.com>
Co-authored-by: Charlène Benke <1179011+defrance@users.noreply.github.com>
Co-authored-by: demiton <fabien.cisse@gmail.com>
Co-authored-by: Jyhere <jyhere@gmail.com>
Co-authored-by: Delthair <41671350+Delthair@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 13:49:57 +00:00
/**
* Add a contact type of given ticket
*
* @ param int $id Id of ticket to update
* @ param int $contactid Id of contact to add
* @ param string $type Type ( code in dictionary ) of the contact ( BILLING , SHIPPING , CUSTOMER + possibly your own )
* @ param string $source internal = Contact intern ( llx_user ), external = Contact extern ( llx_socpeople )
* @ param int $notrigger 0 = Enable all triggers ( default ), 1 = Disable all triggers
* @ return array
* @ phan - return array { success : array { code : int , message : string }}
* @ phpstan - return array { success : array { code : int , message : string }}
*
* @ url POST { id } / contact / { contactid } / { type }
*
* @ throws RestException 400
* @ throws RestException 401
* @ throws RestException 403
* @ throws RestException 404
* @ throws RestException 503
*/
public function postContact ( int $id , int $contactid , string $type , string $source = " external " , int $notrigger = 0 ) : array
{
// Check permissions
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'write' )) {
throw new RestException ( 403 );
}
// test source
if ( empty ( $source )) {
throw new RestException ( 400 , 'Source can not be empty' );
}
// test type
if ( empty ( $type )) {
throw new RestException ( 400 , 'type can not be empty' );
}
// Check type/source contact exists
$sqlCheckTypeSource = " SELECT tc.rowid " ;
$sqlCheckTypeSource .= " FROM " . $this -> db -> prefix () . " c_type_contact as tc " ;
$sqlCheckTypeSource .= " WHERE tc.element LIKE 'ticket' " ;
$sqlCheckTypeSource .= " AND tc.source = ' " . $this -> db -> escape ( $source ) . " ' " ;
$sqlCheckTypeSource .= " AND tc.code = ' " . $this -> db -> escape ( $type ) . " ' " ;
$sqlCheckTypeSource .= " AND tc.active = 1 " ;
$result = $this -> db -> query ( $sqlCheckTypeSource );
if ( $result && $this -> db -> num_rows ( $result ) == 0 ) {
throw new RestException ( 400 , 'Contact type not found' );
}
// Check contact exists
if ( $source == " external " ) {
// Check external contact exists
$sqlCheckExternalContact = " SELECT 1 as exist " ;
$sqlCheckExternalContact .= " FROM llx_socpeople " ;
$sqlCheckExternalContact .= " WHERE rowid = " . intval ( $contactid );
$result = $this -> db -> query ( $sqlCheckExternalContact );
if ( $result && $this -> db -> num_rows ( $result ) == 0 ) {
throw new RestException ( 404 , 'External contact not found' );
}
} else {
// Check internal contact exists
$sqlCheckInternalContact = " SELECT 1 as exist " ;
$sqlCheckInternalContact .= " FROM llx_user " ;
$sqlCheckInternalContact .= " WHERE rowid = " . intval ( $contactid );
$result = $this -> db -> query ( $sqlCheckInternalContact );
if ( $result && $this -> db -> num_rows ( $result ) == 0 ) {
throw new RestException ( 404 , 'Internal contact not found' );
}
}
// tests done, let's get it
$result = $this -> ticket -> fetch ( $id );
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
}
$result = $this -> ticket -> add_contact ( $contactid , $type , $source , $notrigger );
if ( $result == 0 ) {
throw new RestException ( 400 , 'Already exists: Contact=' . $contactid . ' is already linked to the ticket=' . $id . ' as source=' . $source . ' and type=' . $type );
} elseif ( $result == - 1 ) {
throw new RestException ( 400 , 'Wrong contact=' . $contactid );
} elseif ( $result == - 2 ) {
throw new RestException ( 400 , 'Wrong type=' . $type );
} elseif ( $result == - 3 ) {
throw new RestException ( 400 , 'Not allowed contacts' );
} elseif ( $result == - 4 ) {
throw new RestException ( 400 , 'ErrorCommercialNotAllowedForThirdparty' );
} elseif ( $result == - 5 ) {
throw new RestException ( 400 , 'Trigger failed' );
} elseif ( $result == - 6 ) {
throw new RestException ( 400 , 'DB_ERROR_RECORD_ALREADY_EXISTS' );
} elseif ( $result == - 7 ) {
throw new RestException ( 400 , 'Some other error' );
} elseif ( $result <= - 8 ) {
throw new RestException ( 400 , 'Unknown error occurred' );
}
return array (
'success' => array (
'code' => 200 ,
'message' => 'Contact=' . $contactid . ' linked to the ticket=' . $id . ' as ' . $source . ' ' . $type
)
);
}
/**
* Unlink a contact type of given ticket
*
* @ since 12.0 . 0 Initial implementation
* @ param int $id Id of ticket to update
* @ param int $contactid Id of contact
* @ param string $type Type of the contact ( BILLING , SHIPPING , CUSTOMER ) .
* @ param string $source internal = Contact intern ( llx_user ), external = Contact extern ( llx_socpeople )
*
* @ url DELETE { id } / contact / { contactid } / { type }
*
* @ return array
* @ phan - return array { success : array { code : int , message : string }}
* @ phpstan - return array { success : array { code : int , message : string }}
*
* @ throws RestException 401
* @ throws RestException 404
* @ throws RestException 500 System error
*/
public function deleteContact ( int $id , int $contactid , string $type , string $source = " external " ) : array
{
// Check permissions
if ( ! DolibarrApiAccess :: $user -> hasRight ( 'ticket' , 'write' )) {
throw new RestException ( 403 );
}
// test source
if ( empty ( $source )) {
throw new RestException ( 400 , 'Source can not be empty' );
}
// test type
if ( empty ( $type )) {
throw new RestException ( 400 , 'type can not be empty' );
}
$result = $this -> ticket -> fetch ( $id );
if ( ! $result ) {
throw new RestException ( 404 , 'Ticket not found' );
}
if ( ! DolibarrApi :: _checkAccessToResource ( 'ticket' , $this -> ticket -> id )) {
throw new RestException ( 403 , 'Access not allowed for login ' . DolibarrApiAccess :: $user -> login );
}
$contacts = $this -> ticket -> liste_contact ( - 1 , $source );
foreach ( $contacts as $contact ) {
if ( $contact [ 'id' ] == $contactid && $contact [ 'code' ] == $type ) {
$result = $this -> ticket -> delete_contact ( $contact [ 'rowid' ]);
if ( ! $result ) {
throw new RestException ( 500 , 'Error when deleting the contact ' . $contact [ 'rowid' ]);
}
}
}
return array (
'success' => array (
'code' => 200 ,
'message' => 'Contact unlinked from ticket'
)
);
}
2018-03-10 03:23:59 +00:00
}