From feff873bf45a8aaaf941c03439277438c75a0de0 Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Mon, 26 Jan 2026 15:01:11 +0100 Subject: [PATCH 1/6] add endpoint for askdoli --- htdocs/api/class/api_documents.class.php | 123 ++++++++++++++++++ .../action/class/api_agendaevents.class.php | 22 +++- htdocs/ticket/card.php | 2 +- 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index cd78a874ec48f..943b2f97da4a8 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -781,6 +781,11 @@ public function post($filename, $modulepart, $ref = '', $subdir = '', $fileconte $modulepart = 'mrp'; require_once DOL_DOCUMENT_ROOT . '/mrp/class/mo.class.php'; $object = new Mo($this->db); + } elseif ($modulepart == 'ticket' ) { + $modulepart = 'ticket'; + require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; + $object = new Ticket($this->db); +// $fetchbyid = true; } else { // TODO Implement additional moduleparts throw new RestException(500, 'Modulepart '.$modulepart.' not implemented yet.'); @@ -1029,4 +1034,122 @@ public function delete($modulepart, $original_file) throw new RestException(403); } + + /** + + * List documents for a module element + + * + + * @param string $modulepart Module part (ticket, invoice...) + + * @param int $id ID of object + + * @param string $ref Ref of object + + * @return array List of documents + + * + + * @url GET /list + + */ + + public function listFiles($modulepart, $id = 0, $ref = '') { + + global $conf; + + // Include Files Lib + require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; + + $upload_dir = ''; + if ($modulepart == 'ticket') { + require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; + $object = new Ticket($this->db); + + if ($object->fetch($id, $ref) > 0) { + $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); + } + + } + + // Add other modules logic if needed... + if ($upload_dir && dol_is_dir($upload_dir)) { + $filelist = dol_dir_list($upload_dir, 'files', 0, '', '(\.meta|_preview.*\.png)$'); + $res = array(); + foreach($filelist as $file) { + $res[] = array( + 'filename' => $file['name'], + 'size' => $file['size'], + 'date' => $file['date'], + 'type' => dol_mimetype($file['name']), + 'relativename' => $file['name'] + ); + } + return $res; + } + return array(); // Retourne vide si pas de dossier ou pas de fichiers + } + /** + * Upload file for Ticket (Custom AskDoli) + * + * @param string $filename Filename + * @param string $ref Ticket Ref + * @param string $content Base64 Content + * @return string Saved filename + * + * @url POST /upload/ticket + */ + public function uploadTicketFile($filename, $ref, $content) { + global $conf, $db; + require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; + require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; + + $object = new Ticket($db); + // Fetch by Ref + if ($object->fetch(0, $ref) <= 0) { + throw new RestException(404, 'Ticket not found'); + } + + $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); + + if (dol_mkdir($upload_dir) < 0) { + throw new RestException(500, 'Error creating dir'); + } + + $file_content = base64_decode($content); + $dest_file = $upload_dir . '/' . dol_sanitizeFileName($filename); + + if (file_put_contents($dest_file, $file_content)) { + return dol_basename($dest_file); + } + throw new RestException(500, 'Error saving file'); + } + + /** + * Download ticket file (Custom AskDoli) + * + * @param string $ref Ticket Ref + * @param string $filename Filename + * @return array File content structure + * + * @url GET /download/ticket + */ + public function downloadTicketFile($ref, $filename) { + global $conf; + // Build path: documents/ticket/REF/filename + $file_path = $conf->ticket->dir_output . '/' . dol_sanitizeFileName($ref) . '/' . dol_sanitizeFileName($filename); + + if (file_exists($file_path)) { + $content = file_get_contents($file_path); + return array( + 'filename' => $filename, + 'content-type' => dol_mimetype($filename), + 'content' => base64_encode($content), + 'encoding' => 'base64' + ); + } + throw new RestException(404, 'File not found at ' . $file_path); + } + } diff --git a/htdocs/comm/action/class/api_agendaevents.class.php b/htdocs/comm/action/class/api_agendaevents.class.php index d2dd06c919cc2..a206667fa9749 100644 --- a/htdocs/comm/action/class/api_agendaevents.class.php +++ b/htdocs/comm/action/class/api_agendaevents.class.php @@ -20,7 +20,7 @@ use Luracast\Restler\RestException; require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; - +require_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php'; /** * API class for Agenda Events @@ -202,6 +202,26 @@ public function index($sortfield = "t.id", $sortorder = 'ASC', $limit = 100, $pa ]; } + $user_cache = array(); + + foreach ($result as $key => $event) { + $author_id = $event->authorid ? $event->authorid : $event->userownerid; + + if ($author_id > 0) { + if (!isset($user_cache[$author_id])) { + $tmp_user = new User($db); + $tmp_user->fetch($author_id); + $user_cache[$author_id] = $tmp_user; + } + + // On injecte les infos dans l'objet retourné + $result[$key]->user_author_firstname = $user_cache[$author_id]->firstname; + $result[$key]->user_author_lastname = $user_cache[$author_id]->lastname; + $result[$key]->user_author_name = $user_cache[$author_id]->getFullName($this->langs); + } + } + // PATCH END + return $obj_ret; } diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index f6a020a2f7372..0218aa952a88e 100644 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -104,7 +104,7 @@ $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_'); -// Initialize array of search criteria +// Initialize array of search criteriaque l $search_all = GETPOST("search_all", 'alpha'); $search = array(); foreach ($object->fields as $key => $val) { From fa02507f1a46268d28dfe0f228fd5925f8348618 Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Tue, 24 Feb 2026 11:35:43 +0100 Subject: [PATCH 2/6] add event nimber on ticket --- htdocs/core/lib/ticket.lib.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/ticket.lib.php b/htdocs/core/lib/ticket.lib.php index 2ed7811961e46..70598a2979a4e 100644 --- a/htdocs/core/lib/ticket.lib.php +++ b/htdocs/core/lib/ticket.lib.php @@ -132,6 +132,15 @@ function ticket_prepare_head($object) // History + $nbEvents = 0; + $sql = "SELECT COUNT(id) FROM " . $db->prefix() . "actioncomm"; + $sql .= " WHERE fk_element = " . (int) $object->id . " AND elementtype = 'ticket'"; + $resql = $db->query($sql); + if ($resql) { + $row = $db->fetch_row($resql); + $nbEvents = $row[0]; + } + $ticketViewType = "messaging"; if (empty($_SESSION['ticket-view-type'])) { $_SESSION['ticket-view-type'] = $ticketViewType; @@ -140,16 +149,19 @@ function ticket_prepare_head($object) } if ($ticketViewType == "messaging") { - $head[$h][0] = DOL_URL_ROOT.'/ticket/messaging.php?track_id='.$object->track_id; + $head[$h][0] = DOL_URL_ROOT . '/ticket/messaging.php?track_id=' . $object->track_id; } else { // $ticketViewType == "list" - $head[$h][0] = DOL_URL_ROOT.'/ticket/agenda.php?track_id='.$object->track_id; + $head[$h][0] = DOL_URL_ROOT . '/ticket/agenda.php?track_id=' . $object->track_id; } $head[$h][1] = $langs->trans('Events'); if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $user->hasRight('agenda', 'allactions', 'read'))) { $head[$h][1] .= '/'; $head[$h][1] .= $langs->trans("Agenda"); } + if ($nbEvents > 0) { + $head[$h][1] .= '' . $nbEvents . ''; + } $head[$h][2] = 'tabTicketLogs'; $h++; From fa746e45f79717df74f291eea541b9683c2e7a41 Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Fri, 6 Mar 2026 10:23:30 +0100 Subject: [PATCH 3/6] fix indent --- htdocs/api/class/api_documents.class.php | 12 +---------- .../action/class/api_agendaevents.class.php | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index 943b2f97da4a8..0e647eca445a9 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -1036,25 +1036,15 @@ public function delete($modulepart, $original_file) } /** - * List documents for a module element - * - * @param string $modulepart Module part (ticket, invoice...) - * @param int $id ID of object - * @param string $ref Ref of object - * @return array List of documents - * - * @url GET /list - */ - public function listFiles($modulepart, $id = 0, $ref = '') { global $conf; @@ -1070,7 +1060,6 @@ public function listFiles($modulepart, $id = 0, $ref = '') { if ($object->fetch($id, $ref) > 0) { $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); } - } // Add other modules logic if needed... @@ -1090,6 +1079,7 @@ public function listFiles($modulepart, $id = 0, $ref = '') { } return array(); // Retourne vide si pas de dossier ou pas de fichiers } + /** * Upload file for Ticket (Custom AskDoli) * diff --git a/htdocs/comm/action/class/api_agendaevents.class.php b/htdocs/comm/action/class/api_agendaevents.class.php index a206667fa9749..8579b6d0bfac1 100644 --- a/htdocs/comm/action/class/api_agendaevents.class.php +++ b/htdocs/comm/action/class/api_agendaevents.class.php @@ -204,8 +204,10 @@ public function index($sortfield = "t.id", $sortorder = 'ASC', $limit = 100, $pa $user_cache = array(); - foreach ($result as $key => $event) { - $author_id = $event->authorid ? $event->authorid : $event->userownerid; + // Fix: Loop through obj_ret instead of SQL result + $data_to_enrich = $pagination_data ? $obj_ret['data'] : $obj_ret; + foreach ($data_to_enrich as $key => $event) { + $author_id = property_exists($event, 'authorid') && $event->authorid ? $event->authorid : (property_exists($event, 'userownerid') ? $event->userownerid : 0); if ($author_id > 0) { if (!isset($user_cache[$author_id])) { @@ -215,12 +217,18 @@ public function index($sortfield = "t.id", $sortorder = 'ASC', $limit = 100, $pa } // On injecte les infos dans l'objet retourné - $result[$key]->user_author_firstname = $user_cache[$author_id]->firstname; - $result[$key]->user_author_lastname = $user_cache[$author_id]->lastname; - $result[$key]->user_author_name = $user_cache[$author_id]->getFullName($this->langs); + $data_to_enrich[$key]->user_author_firstname = $user_cache[$author_id]->firstname; + $data_to_enrich[$key]->user_author_lastname = $user_cache[$author_id]->lastname; + // Use simple concatenation instead of getFullName to avoid langs dependency + $data_to_enrich[$key]->user_author_name = trim($user_cache[$author_id]->firstname . ' ' . $user_cache[$author_id]->lastname); } } - // PATCH END + // Update the enriched data back + if ($pagination_data) { + $obj_ret['data'] = $data_to_enrich; + } else { + $obj_ret = $data_to_enrich; + } return $obj_ret; } From 105f347c6e06c5f8bd9d776c38fbc569d4e577e3 Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Fri, 6 Mar 2026 10:38:31 +0100 Subject: [PATCH 4/6] retours relecteur PR --- htdocs/api/class/api_documents.class.php | 50 ++++++++++++++---------- htdocs/ticket/card.php | 2 +- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index 0e647eca445a9..96ad8c4ff41e4 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -781,11 +781,10 @@ public function post($filename, $modulepart, $ref = '', $subdir = '', $fileconte $modulepart = 'mrp'; require_once DOL_DOCUMENT_ROOT . '/mrp/class/mo.class.php'; $object = new Mo($this->db); - } elseif ($modulepart == 'ticket' ) { + } elseif ($modulepart == 'ticket') { $modulepart = 'ticket'; require_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; $object = new Ticket($this->db); -// $fetchbyid = true; } else { // TODO Implement additional moduleparts throw new RestException(500, 'Modulepart '.$modulepart.' not implemented yet.'); @@ -1054,11 +1053,16 @@ public function listFiles($modulepart, $id = 0, $ref = '') { $upload_dir = ''; if ($modulepart == 'ticket') { + if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) { + throw new RestException(403, 'Missing permission to read ticket documents'); + } require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; $object = new Ticket($this->db); if ($object->fetch($id, $ref) > 0) { $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); + } else { + throw new RestException(404, 'Ticket not found'); } } @@ -1091,29 +1095,29 @@ public function listFiles($modulepart, $id = 0, $ref = '') { * @url POST /upload/ticket */ public function uploadTicketFile($filename, $ref, $content) { - global $conf, $db; - require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; - require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; - - $object = new Ticket($db); - // Fetch by Ref - if ($object->fetch(0, $ref) <= 0) { - throw new RestException(404, 'Ticket not found'); + // Check permissions early + if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) { + throw new RestException(403, 'Missing permission to write ticket documents'); } - $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); - - if (dol_mkdir($upload_dir) < 0) { - throw new RestException(500, 'Error creating dir'); + // Check that file upload is enabled and enforce size limit (MAIN_UPLOAD_DOC is in KB) + $maxUploadKb = getDolGlobalInt('MAIN_UPLOAD_DOC'); + if ($maxUploadKb <= 0) { + throw new RestException(403, 'File upload is disabled on this server'); } - $file_content = base64_decode($content); - $dest_file = $upload_dir . '/' . dol_sanitizeFileName($filename); - - if (file_put_contents($dest_file, $file_content)) { - return dol_basename($dest_file); + // Validate base64 content and check decoded size before writing + $decodedContent = base64_decode($content, true); + if ($decodedContent === false) { + throw new RestException(400, 'Invalid base64 content'); } - throw new RestException(500, 'Error saving file'); + if (strlen($decodedContent) > $maxUploadKb * 1024) { + throw new RestException(400, 'File size exceeds the allowed limit of '.$maxUploadKb.' KB'); + } + + // Delegate to post() which handles virus scan, .noexe renaming, path traversal checks + // and dol_check_secure_access_document for the full security pipeline + return $this->post($filename, 'ticket', $ref, '', $content, 'base64', 1, 1); } /** @@ -1127,6 +1131,12 @@ public function uploadTicketFile($filename, $ref, $content) { */ public function downloadTicketFile($ref, $filename) { global $conf; + + // Check permissions + if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) { + throw new RestException(403, 'Missing permission to read ticket documents'); + } + // Build path: documents/ticket/REF/filename $file_path = $conf->ticket->dir_output . '/' . dol_sanitizeFileName($ref) . '/' . dol_sanitizeFileName($filename); diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index dbdcc9bb10695..3b614a0839285 100644 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -104,7 +104,7 @@ $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_'); -// Initialize array of search criteriaque l +// Initialize array of search criteria $search_all = GETPOST("search_all", 'alpha'); $search = array(); foreach ($object->fields as $key => $val) { From 327e2fc42b7f8980fa2c45641a065c53c9871cfc Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Fri, 6 Mar 2026 11:01:05 +0100 Subject: [PATCH 5/6] retours relecteur PR --- htdocs/api/class/api_documents.class.php | 111 +++++++++++++++++++---- htdocs/core/lib/files.lib.php | 19 ++++ 2 files changed, 111 insertions(+), 19 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index 96ad8c4ff41e4..296894fd6936b 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -1051,22 +1051,47 @@ public function listFiles($modulepart, $id = 0, $ref = '') { // Include Files Lib require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; + // Validate inputs at API boundary before any modulepart-specific logic + $id = (int) $id; + $ref = trim($ref); + if (empty($modulepart)) { + throw new RestException(400, 'Parameter modulepart is required'); + } + if ($id <= 0 && empty($ref)) { + throw new RestException(400, 'Parameter id or ref is required'); + } + $upload_dir = ''; if ($modulepart == 'ticket') { if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) { throw new RestException(403, 'Missing permission to read ticket documents'); } + require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; $object = new Ticket($this->db); - if ($object->fetch($id, $ref) > 0) { - $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); - } else { + if ($object->fetch($id, $ref) <= 0) { throw new RestException(404, 'Ticket not found'); } - } - // Add other modules logic if needed... + // External user: can only access tickets linked to their company + if (DolibarrApiAccess::$user->socid > 0 && $object->fk_soc != DolibarrApiAccess::$user->socid) { + throw new RestException(403, 'Access to this ticket is forbidden'); + } + + // Internal user with restricted view: can only access tickets assigned to them + if (!DolibarrApiAccess::$user->socid + && getDolGlobalString('TICKET_LIMIT_VIEW_ASSIGNED_ONLY') + && $object->fk_user_assign != DolibarrApiAccess::$user->id + && !DolibarrApiAccess::$user->hasRight('ticket', 'manage') + ) { + throw new RestException(403, 'Access to this ticket is forbidden'); + } + + $upload_dir = $conf->ticket->dir_output . "/" . dol_sanitizeFileName($object->ref); + } else { + throw new RestException(501, 'modulepart '.$modulepart.' is not supported by this endpoint'); + } if ($upload_dir && dol_is_dir($upload_dir)) { $filelist = dol_dir_list($upload_dir, 'files', 0, '', '(\.meta|_preview.*\.png)$'); $res = array(); @@ -1117,7 +1142,15 @@ public function uploadTicketFile($filename, $ref, $content) { // Delegate to post() which handles virus scan, .noexe renaming, path traversal checks // and dol_check_secure_access_document for the full security pipeline - return $this->post($filename, 'ticket', $ref, '', $content, 'base64', 1, 1); + try { + return $this->post($filename, 'ticket', $ref, '', $content, 'base64', 1, 1); + } catch (RestException $e) { + // Re-throw as-is: post() already produces meaningful RestExceptions + throw $e; + } catch (\Exception $e) { + dol_syslog('uploadTicketFile unexpected error ref='.$ref.' file='.$filename.': '.$e->getMessage(), LOG_ERR); + throw new RestException(500, 'Unexpected error while uploading file'); + } } /** @@ -1130,26 +1163,66 @@ public function uploadTicketFile($filename, $ref, $content) { * @url GET /download/ticket */ public function downloadTicketFile($ref, $filename) { - global $conf; + global $conf, $db; + require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php'; - // Check permissions + // Check global permission if (!DolibarrApiAccess::$user->hasRight('ticket', 'read')) { throw new RestException(403, 'Missing permission to read ticket documents'); } - // Build path: documents/ticket/REF/filename - $file_path = $conf->ticket->dir_output . '/' . dol_sanitizeFileName($ref) . '/' . dol_sanitizeFileName($filename); + // Fetch the ticket to apply per-object access restrictions + $object = new Ticket($db); + if ($object->fetch(0, $ref) <= 0) { + throw new RestException(404, 'Ticket not found'); + } - if (file_exists($file_path)) { - $content = file_get_contents($file_path); - return array( - 'filename' => $filename, - 'content-type' => dol_mimetype($filename), - 'content' => base64_encode($content), - 'encoding' => 'base64' - ); + // External user: can only access tickets linked to their company + if (DolibarrApiAccess::$user->socid > 0 && $object->fk_soc != DolibarrApiAccess::$user->socid) { + throw new RestException(403, 'Access to this ticket is forbidden'); + } + + // Internal user with restricted view: can only access tickets assigned to them + if (!DolibarrApiAccess::$user->socid + && getDolGlobalString('TICKET_LIMIT_VIEW_ASSIGNED_ONLY') + && $object->fk_user_assign != DolibarrApiAccess::$user->id + && !DolibarrApiAccess::$user->hasRight('ticket', 'manage') + ) { + throw new RestException(403, 'Access to this ticket is forbidden'); + } + + // Build path using the canonical ref from DB, not from user input + $ticket_dir = $conf->ticket->dir_output . '/' . dol_sanitizeFileName($object->ref); + $file_path = $ticket_dir . '/' . dol_sanitizeFileName($filename); + + // Path traversal protection: block .. and shell metacharacters (same checks as index()) + if (preg_match('/\.\./', $file_path) || preg_match('/[<>|]/', $file_path)) { + dol_syslog('Path traversal attempt blocked: '.$file_path, LOG_WARNING); + throw new RestException(403, 'Access denied'); } - throw new RestException(404, 'File not found at ' . $file_path); + + $file_path_os = dol_osencode($file_path); + + if (!file_exists($file_path_os)) { + dol_syslog('Try to download not found file '.$file_path_os, LOG_WARNING); + throw new RestException(404, 'File not found'); + } + + // Confirm the resolved real path is strictly inside the ticket directory (symlink / race-condition guard) + $real_dir = realpath(dol_osencode($ticket_dir)); + $real_file = realpath($file_path_os); + if ($real_dir === false || $real_file === false || strpos($real_file, $real_dir . DIRECTORY_SEPARATOR) !== 0) { + dol_syslog('File outside expected directory blocked: '.$file_path_os, LOG_WARNING); + throw new RestException(403, 'Access denied'); + } + + $content = file_get_contents($real_file); + return array( + 'filename' => basename($real_file), + 'content-type' => dol_mimetype($filename), + 'content' => base64_encode($content), + 'encoding' => 'base64' + ); } } diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 8d82ea46f9fee..39c354319416e 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -3524,6 +3524,25 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, $accessallowed = 1; } $original_file = $conf->member->dir_output.'/'.$original_file; + } elseif ($modulepart == 'ticket' && !empty($conf->ticket->dir_output)) { + // Wrapping for tickets + if ($fuser->hasRight('ticket', $read) || preg_match('/^specimen/i', $original_file)) { + $accessallowed = 1; + + // Per-ticket check for TICKET_LIMIT_VIEW_ASSIGNED_ONLY (internal users only) + if ($accessallowed && !$fuser->socid && getDolGlobalString('TICKET_LIMIT_VIEW_ASSIGNED_ONLY') && !$fuser->hasRight('ticket', 'manage')) { + if (!empty($refname) && !preg_match('/^specimen/i', $original_file)) { + include_once DOL_DOCUMENT_ROOT.'/ticket/class/ticket.class.php'; + $tmpticket = new Ticket($db); + if ($tmpticket->fetch(0, $refname) > 0 && $tmpticket->fk_user_assign != $fuser->id) { + $accessallowed = 0; + } + } + } + } + $original_file = $conf->ticket->dir_output.'/'.$original_file; + // External user check: ticket may or may not be linked to a thirdparty + $sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."ticket WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('ticket').")"; // If modulepart=module_user_temp Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp/iduser // If modulepart=module_temp Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp // If modulepart=module_user Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/iduser From ef3811f7bd13fe898cffe3cca608df7322274107 Mon Sep 17 00:00:00 2001 From: atm-nicolasV Date: Fri, 6 Mar 2026 11:45:16 +0100 Subject: [PATCH 6/6] retours typages PR --- htdocs/api/class/api_documents.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index 296894fd6936b..f22ca5395723d 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -1044,7 +1044,7 @@ public function delete($modulepart, $original_file) * * @url GET /list */ - public function listFiles($modulepart, $id = 0, $ref = '') { + public function listFiles(string $modulepart, int $id = 0, string $ref = ''): array { global $conf; @@ -1119,7 +1119,7 @@ public function listFiles($modulepart, $id = 0, $ref = '') { * * @url POST /upload/ticket */ - public function uploadTicketFile($filename, $ref, $content) { + public function uploadTicketFile(string $filename, string $ref, string $content): string { // Check permissions early if (!DolibarrApiAccess::$user->hasRight('ticket', 'write')) { throw new RestException(403, 'Missing permission to write ticket documents'); @@ -1162,7 +1162,7 @@ public function uploadTicketFile($filename, $ref, $content) { * * @url GET /download/ticket */ - public function downloadTicketFile($ref, $filename) { + public function downloadTicketFile(string $ref, string $filename): array { global $conf, $db; require_once DOL_DOCUMENT_ROOT . '/ticket/class/ticket.class.php';