dolibarr 25.0.0-alpha
download_pdf.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 Nick Fragoulis
3 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.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
26if (!defined('NOTOKENRENEWAL')) {
27 define('NOTOKENRENEWAL', 1);
28}
29if (!defined('NOCSRFCHECK')) { // TODO Enable the CSRF check
30 define('NOCSRFCHECK', 1);
31}
32
33require '../../main.inc.php';
34require_once DOL_DOCUMENT_ROOT . '/core/lib/pdf.lib.php';
35require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
36require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
37require_once DOL_DOCUMENT_ROOT . '/ai/lib/ai.lib.php';
38require_once DOL_DOCUMENT_ROOT . '/includes/tecnickcom/tcpdf/tcpdf.php';
39
40// Security check
41if (!isModEnabled('ai') || !getDolGlobalString('AI_ASSISTANT_ENABLED')) {
42 accessforbidden('Module or feature not allowed');
43}
44
45global $user, $langs;
46
47// Same per-user gate as the assistant page and the other assistant endpoints
48if (!$user->hasRight('ai', 'assistant', 'use')) {
50}
51
52// Must not be reachable from another site
53aiCheckCsrfToken('ai/assistant/download_pdf.php');
54
55$langs->loadLangs(array('products', 'stocks', 'suppliers', 'companies', 'margins', 'bills', 'main', 'reports@reports'));
56
63function aiPdfFieldLabel($k)
64{
65 global $langs;
66
67 $map = array(
68 'ref' => 'Ref', 'label' => 'Label', 'name' => 'ThirdParty', 'socid' => 'Customer', 'fk_soc' => 'Customer',
69 'paye' => 'Paid', 'status' => 'Status', 'type' => 'Type', 'email' => 'Email', 'town' => 'Town',
70 'date' => 'DateInvoice', 'datef' => 'DateInvoice', 'date_lim_reglement' => 'DateMaxPayment',
71 'total_ht' => 'TotalHT', 'total_ttc' => 'TotalTTC', 'total_tva' => 'AmountVAT',
72 'remaintopay' => 'RemainderToPay', 'price' => 'Price', 'price_ttc' => 'PriceTTC', 'tva_tx' => 'VATRate',
73 'code_client' => 'CustomerCode', 'code_fournisseur' => 'SupplierCode', 'fournisseur' => 'Supplier'
74 );
75 if (isset($map[$k])) {
76 return $langs->trans($map[$k]);
77 }
78
79 return dol_ucfirst(str_replace('_', ' ', $k));
80}
81
90function aiPdfFormatValue($k, $v)
91{
92 global $langs;
93
94 if ($v === null || $v === '') {
95 return '-';
96 }
97 if (is_array($v)) {
98 return (string) count($v);
99 }
100 $moneyfields = array('total_ht', 'total_ttc', 'total_tva', 'total_localtax1', 'total_localtax2', 'remaintopay', 'resteapayer', 'price', 'price_ttc', 'price_min', 'subprice', 'totalpaid');
101 if (in_array($k, $moneyfields, true) && is_numeric($v)) {
102 return price($v, 0, $langs, 1, -1, -1, 'auto');
103 }
104 if (($k == 'date' || $k == 'datef' || $k == 'tms' || preg_match('/(^|_)date($|_)|_date$|^date_/', $k)) && is_numeric($v) && (int) $v > 100000000 && (int) $v < 9999999999) {
105 return dol_print_date((int) $v, 'day');
106 }
107 if ($k == 'paye') {
108 return $langs->trans(((string) $v === '1') ? 'Yes' : 'No');
109 }
110
111 return dol_string_nohtmltag((string) $v);
112}
113
114$json = GETPOST('content', 'none');
115if (empty($json)) {
116 $json = file_get_contents('php://input');
117}
118
119// Validate JSON structure
120$data = json_decode($json, true);
121if (json_last_error() !== JSON_ERROR_NONE) {
122 dol_syslog("Invalid JSON data in PDF generation: " . json_last_error_msg(), LOG_ERR);
123 print "Error: Invalid data format provided.";
124 exit;
125}
126
127// Get title and filename
128$title = GETPOST('title', 'restricthtml');
129if (empty($title)) {
130 $title = "AI Report";
131}
132
133$filename = GETPOST('filename', 'restricthtml');
134if (empty($filename)) {
135 $filename = 'Report.pdf';
136}
137
138// Remove control characters and special filesystem characters
139$filename = preg_replace('/[\x00-\x1F\x7F\/\\:*?"<>|]/', '', $filename);
140// Ensure filename has .pdf extension
141if (!preg_match('/\.pdf$/i', $filename)) {
142 $filename .= '.pdf';
143}
144
145if (!$data) {
146 print "No data provided for PDF generation.";
147 exit;
148}
149
150try {
151 $outputlangs = $langs;
152
153 // Get format - use landscape orientation for better column display
154 $format = pdf_getFormat($outputlangs);
155
156 $pdf_dimensions = [$format['width'], $format['height']];
157 $pdf = pdf_getInstance($pdf_dimensions, $format['unit'], 'l');
158 $default_font_size = pdf_getPDFFontSize($outputlangs);
159 $pdf->SetCreator("Dolibarr AI");
160 $pdf->SetAuthor($user->getFullName($langs));
161
162 $pdf->SetTitle($title);
163 $pdf->SetMargins(15, 15, 15);
164 $pdf->SetAutoPageBreak(true, 15);
165 if (class_exists('TCPDF')) {
166 $pdf->setPrintHeader(false);
167 $pdf->setPrintFooter(false);
168 }
169 $pdf->AddPage();
170 $pdf->SetFont('dejavusans', '', 10); // Important for non-Latin languages
171
172 // Header
173 $pdf->SetFont('', 'B', 16);
174 $pdf->Cell(0, 10, $title, 0, 1, 'C');
175 $pdf->SetFont('', '', 9);
176 $pdf->SetTextColor(100, 100, 100);
177 $pdf->Cell(0, 5, dol_print_date(dol_now(), 'dayhour', 'tzuser'), 0, 1, 'R');
178 $pdf->Ln(5);
179 $pdf->SetTextColor(0, 0, 0);
180
181 $html = '<style>
182 h3 { color: #333; border-bottom: 1px solid #ccc; font-size: 12pt; margin-top: 15px; }
183 table { width: 100%; border-collapse: collapse; }
184 th { background-color: #f0f0f0; font-weight: bold; padding: 5px; border: 1px solid #ccc; }
185 td { padding: 5px; border: 1px solid #ccc; }
186 .key { font-weight: bold; width: 30%; background-color: #f9f9f9; }
187 .val { width: 70%; }
188 </style>';
189
190 // Overview / Dashboard (complex object)
191 if (isset($data['customer']) && isset($data['details'])) {
192 // Customer Info
193 $html .= '<h3>' . $langs->trans('ThirdpartyDetails') . '</h3>';
194 $html .= '<table cellpadding="4">';
195 foreach ($data['customer'] as $k => $v) {
196 // Skip internal fields
197 if ($k === 'url' || $k === 'id' || $k === 'rowid') {
198 continue;
199 }
200 $html .= '<tr><td class="key">' . dol_escape_htmltag(ucfirst($k)) . '</td><td class="val">' . dol_escape_htmltag($v) . '</td></tr>';
201 }
202 $html .= '</table>';
203
204 // Sections (Invoices, Orders, etc)
205 foreach ($data['details'] as $section => $rows) {
206 $sectionTitle = dol_escape_htmltag(ucwords((string) str_replace('_', ' ', $section)));
207 $html .= '<h3>' . $sectionTitle . '</h3>';
208
209 if (empty($rows) || !is_array($rows)) {
210 $html .= '<p>' . $langs->trans('NoDataAvailable') . '</p>';
211 } else {
212 // Table Builder for List
213 $keys = array_keys($rows[0]);
214 $keys = array_filter(
215 $keys,
219 static function (string $k) {
220 return $k !== 'url' && $k !== 'rowid';
221 }
222 );
223
224 // Defensive cap: a full API object carries ~130 columns and TCPDF
225 // renders that as illegible overlap. The bridge already sends
226 // compact rows (default_properties); this protects the report
227 // when a caller bypasses it.
228 $keys = array_slice(array_values($keys), 0, 12);
229
230 $html .= '<table cellpadding="4"><thead><tr>';
231 foreach ($keys as $k) {
232 $html .= '<th>' . dol_escape_htmltag(aiPdfFieldLabel($k)) . '</th>';
233 }
234 $html .= '</tr></thead><tbody>';
235 foreach ($rows as $row) {
236 $html .= '<tr>';
237 foreach ($keys as $k) {
238 $val = aiPdfFormatValue($k, isset($row[$k]) ? $row[$k] : null);
239 $html .= '<td>' . dol_escape_htmltag($val) . '</td>';
240 }
241 $html .= '</tr>';
242 }
243 $html .= '</tbody></table>';
244 }
245 }
246 } elseif (isset($data[0]) && is_array($data[0])) {
247 // Simple list (e.g. search invoices)
248 $keys = array_keys($data[0]);
249 $keys = array_filter(
250 $keys,
254 static function (string $k) {
255 return $k !== 'url' && $k !== 'rowid';
256 }
257 );
258
259 // Defensive cap: a full API object carries ~130 columns and TCPDF
260 // renders that as illegible overlap. The bridge already sends
261 // compact rows (default_properties); this protects the report
262 // when a caller bypasses it.
263 $keys = array_slice(array_values($keys), 0, 12);
264
265 $html .= '<table cellpadding="4"><thead><tr nobr="true">';
266 foreach ($keys as $key) {
267 $html .= '<th>' . dol_escape_htmltag(aiPdfFieldLabel($key)) . '</th>';
268 }
269 $html .= '</tr></thead><tbody>';
270 foreach ($data as $row) {
271 $html .= '<tr nobr="true">';
272 foreach ($keys as $key) {
273 $val = aiPdfFormatValue($key, isset($row[$key]) ? $row[$key] : null);
274 $html .= '<td>' . dol_escape_htmltag($val) . '</td>';
275 }
276 $html .= '</tr>';
277 }
278 $html .= '</tbody></table>';
279 } else {
280 // Simple object
281 $html .= '<table cellpadding="5">';
282 foreach ($data as $key => $val) {
283 // Skip internal fields
284 if ($key === 'url') {
285 continue;
286 }
287 $valStr = is_array($val) ? json_encode($val) : $val;
288 $html .= '<tr><td class="key">' . dol_escape_htmltag(ucfirst($key)) . '</td><td class="val">' . dol_escape_htmltag($valStr) . '</td></tr>';
289 }
290 $html .= '</table>';
291 }
292
293 $pdf->writeHTML($html, true, false, true, false, '');
294 $pdf->Output($filename, 'D');
295} catch (Exception $e) {
296 dol_syslog("Error generating PDF: " . $e->getMessage(), LOG_ERR);
297 print "PDF Error: Unable to generate PDF. Please contact administrator.";
298 exit;
299}
aiCheckCsrfToken($context='')
Check the anti-CSRF token of a request sent to one of the AI Assistant endpoints.
Definition ai.lib.php:1065
aiPdfFormatValue($k, $v)
Localized display value for a raw API field (money, dates, flags); strips any HTML a report tool embe...
aiPdfFieldLabel($k)
Translated header for a raw API field name (mirrors FIELD_LABELS in ai_assistant.js).
dol_now($mode='gmt')
Return date for now.
dol_ucfirst($string, $encoding="UTF-8")
Convert first character of the first word of a string to upper.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
pdf_getFormat($outputlangs=null, $mode='setup')
Return array with format properties of default PDF format.
Definition pdf.lib.php:87
pdf_getPDFFontSize($outputlangs)
Return font size to use for PDF generation.
Definition pdf.lib.php:296
pdf_getInstance($format='', $metric='mm', $pagetype='P')
Return a PDF instance object.
Definition pdf.lib.php:129
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.