dolibarr 25.0.0-alpha
fileupload.class.php
Go to the documentation of this file.
1<?php
2
3/* Copyright (C) 2011-2022 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2011-2023 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
29require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
30require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
31
32
37{
41 public $options;
45 protected $fk_element;
46
50 protected $element;
51
62 public function __construct($options = null, $fk_element = null, $element = null)
63 {
64 global $hookmanager;
65
66 $hookmanager->initHooks(array('fileupload'));
67
68 $element_prop = getElementProperties($element);
69 //var_dump($element_prop);
70
71 $this->fk_element = $fk_element;
72 $this->element = $element;
73
74 $pathname = str_replace('/class', '', $element_prop['classpath']);
75 $filename = dol_sanitizeFileName($element_prop['classfile']);
76 $dir_output = dol_sanitizePathName($element_prop['dir_output']);
77 $savingDocMask = '';
78
79 //print 'fileupload.class.php: element='.$element.' pathname='.$pathname.' filename='.$filename.' dir_output='.$dir_output."\n";
80
81 $object_ref = 'UndefinedReference';
82 $object = null;
83 // If pathname and filename are null then we can still upload files if we have specified upload_dir on $options
84 if ($pathname !== null && $filename !== null) {
85 // Get object from its id and type
86 $object = fetchObjectByElement($fk_element, $element);
87
88 // fetchObjectByElement() also returns an object when the record was not found (fetch() returning 0),
89 // so we must check the object was really loaded. Without this, files would be stored at the root of
90 // the module directory, out of any object and out of any permission check on the object.
91 if (!is_object($object) || empty($object->id)) {
92 dol_syslog(get_class($this)."::__construct object ".$element." with id ".((int) $fk_element)." was not found", LOG_WARNING);
93 throw new Exception('objectnotfound');
94 }
95
96 // Directory of the module, including the sub directory used by some elements (/sending for a shipment,
97 // /commande for a supplier order, /<project ref> for a task, ...). We must use the same directory than
98 // the one used by the "Attached files" tab of the object, otherwise the uploaded file is stored but
99 // never shown to the user.
100 // Note: getMultidirOutput() only knows the elements of its own switch, that is a minority of them. For
101 // all the others it does not return an empty string but the string
102 // 'error-diroutput-not-defined-for-this-object=x', and keeping the directory of getElementProperties()
103 // is then the nominal case, not a degraded one. So we only accept an absolute path: that string is a
104 // relative path, and writing into it would create the files under the web root.
105 $tmpdir = getMultidirOutput($object, $element);
106 if (!empty($tmpdir) && preg_match('/^([a-z]:)?[\\\\\/]/i', $tmpdir)) {
107 $dir_output = dol_sanitizePathName($tmpdir);
108 }
109
110 // Add object reference as file name prefix if const MAIN_DISABLE_SUGGEST_REF_AS_PREFIX is not enabled
111 if (!getDolGlobalInt('MAIN_DISABLE_SUGGEST_REF_AS_PREFIX')) {
112 $savingDocMask = dol_sanitizeFileName($object->ref).'-__file__';
113 }
114
115 // get_exdir() forges the directory of an object the way the "Attached files" tabs do: it always
116 // uses the id for a thirdparty (a thirdparty ref is a company name, so it is not unique), and it
117 // falls back on the id when the ref is empty. Using anything else here would store the file into
118 // a directory the tab never reads.
119 // Note that a few tabs sanitize the ref themselves instead of calling this function, so they have
120 // no fallback: on an object whose ref is empty in database, which the interface does not produce
121 // but old records may hold, they read the root of the directory of the module while we store
122 // under the id. Storing at the root would mix the files of every object of the module, so the
123 // fallback is kept and those tabs are the ones that should be fixed.
124 $object_ref = get_exdir(0, 0, 0, 1, $object, $element);
125
126 // For the modules storing their documents on several levels, get_exdir() returned the level
127 // directories only, so we must append the directory of the object itself.
128 if (in_array($element, array('invoice_supplier', 'supplier_invoice'))) {
129 $object_ref .= '/'.dol_sanitizeFileName($object->ref);
130 }
131 }
132
133 // Tested after the call to getMultidirOutput(), because some elements have no 'dir_output' returned by
134 // getElementProperties() while getMultidirOutput() is still able to resolve their output directory.
135 if (empty($dir_output)) {
136 dol_syslog(get_class($this)."::__construct element ".$element." is not supported for uploading file, dir_output is unknown", LOG_WARNING);
137 throw new Exception('elementnotsupported');
138 }
139
140 // Note: 'upload_url' is not always the url of the file stored into 'upload_dir', because document.php
141 // forges the path of the file with its own rules for each value of modulepart. It is currently not a
142 // problem because the only caller of this class (the drag and drop of a file on a card) does not use
143 // the url returned into the json.
144 $this->options = array(
145 'script_url' => $_SERVER['PHP_SELF'],
146 'upload_dir' => $dir_output.'/'.$object_ref.'/',
147 'upload_url' => DOL_URL_ROOT.'/document.php?modulepart='.$element.'&attachment=1&file=/'.$object_ref.'/',
148 'saving_doc_mask' => $savingDocMask,
149 'param_name' => 'files',
150 // Set the following option to 'POST', if your server does not support
151 // DELETE requests. This is a parameter sent to the client:
152 'delete_type' => 'DELETE',
153 // The php.ini settings upload_max_filesize and post_max_size
154 // take precedence over the following max_file_size setting:
155 'max_file_size' => null,
156 'min_file_size' => 1,
157 'accept_file_types' => '/.+$/i',
158 // The maximum number of files for the upload directory:
159 'max_number_of_files' => null,
160 // Image resolution restrictions:
161 'max_width' => null,
162 'max_height' => null,
163 'min_width' => 1,
164 'min_height' => 1,
165 // Set the following option to false to enable resumable uploads:
166 'discard_aborted_uploads' => true,
167 'image_versions' => array(
168 // Uncomment the following version to restrict the size of
169 // uploaded images. You can also add additional versions with
170 // their own upload directories:
171 /*
172 'large' => array(
173 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/',
174 'upload_url' => $this->getFullUrl().'/files/',
175 'max_width' => 1920,
176 'max_height' => 1200,
177 'jpeg_quality' => 95
178 ),
179 */
180 'thumbnail' => array(
181 'upload_dir' => $dir_output.'/'.$object_ref.'/thumbs/',
182 'upload_url' => DOL_URL_ROOT.'/document.php?modulepart='.urlencode($element).'&attachment=1&file='.urlencode('/'.$object_ref.'/thumbs/'),
183 'max_width' => 80,
184 'max_height' => 80
185 )
186 )
187 );
188
189 global $action;
190
191 $hookmanager->executeHooks(
192 'overrideUploadOptions',
193 array(
194 'options' => &$options,
195 'element' => $element
196 ),
197 $object, // @phan-suppress-current-line PhanTypeMismatchArgumentNullable
198 $action
199 );
200
201 if ($options) {
202 $this->options = array_replace_recursive($this->options, $options);
203 }
204
205 // At this point we should have a valid upload_dir in this->options
206 if (empty($pathname) || empty($filename)) {
207 if (!array_key_exists("upload_dir", $this->options)) {
208 setEventMessage('If $fk_element = null or $element = null you must specify upload_dir on $options', 'errors');
209 throw new Exception('If $fk_element = null or $element = null you must specify upload_dir on $options');
210 } elseif (!is_dir($this->options['upload_dir'])) {
211 setEventMessage('The directory '.$this->options['upload_dir'].' doesn\'t exists', 'errors');
212 throw new Exception('The directory '.$this->options['upload_dir'].' doesn\'t exists');
213 } elseif (!is_writable($this->options['upload_dir'])) {
214 setEventMessage('The directory '.$this->options['upload_dir'].' is not writable', 'errors');
215 throw new Exception('The directory '.$this->options['upload_dir'].' is not writable');
216 }
217 }
218 }
219
225 protected function getFullUrl()
226 {
227 $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
228 return
229 ($https ? 'https://' : 'http://').
230 (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
231 (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
232 ($https && $_SERVER['SERVER_PORT'] === 443 ||
233 $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
234 substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
235 }
236
243 protected function setFileDeleteUrl($file)
244 {
245 $file->delete_url = $this->options['script_url'].'?file='.urlencode((string) ($file->name)).'&fk_element='.urlencode((string) ($this->fk_element)).'&element='.urlencode((string) ($this->element));
246 $file->delete_type = $this->options['delete_type'];
247 if ($file->delete_type !== 'DELETE') {
248 $file->delete_url .= '&_method=DELETE';
249 }
250 }
251
258 protected function getFileObject($file_name)
259 {
260 $file_path = $this->options['upload_dir'].dol_sanitizeFileName($file_name);
261
262 if (dol_is_file($file_path) && $file_name[0] !== '.') {
263 $file = new stdClass();
264 $file->name = $file_name;
265 $file->mime = dol_mimetype($file_name, '', 2);
266 $file->size = filesize($file_path);
267 $file->url = $this->options['upload_url'].urlencode($file->name);
268
269 foreach ($this->options['image_versions'] as $version => $options) {
270 if (dol_is_file($options['upload_dir'].$file_name)) {
271 $tmp = explode('.', $file->name);
272
273 // We save the path of mini file into file->... (seems not used)
274 $keyforfile = $version.'_url';
275 $file->$keyforfile = $options['upload_url'].urlencode($tmp[0].'_mini.'.$tmp[1]);
276 }
277 }
278 $this->setFileDeleteUrl($file);
279 return $file;
280 }
281 return null;
282 }
283
289 protected function getFileObjects()
290 {
291 return array_values(array_filter(array_map(array($this, 'getFileObject'), scandir($this->options['upload_dir']))));
292 }
293
301 protected function createScaledImage($file_name, $options)
302 {
303 global $maxwidthmini, $maxheightmini, $maxwidthsmall, $maxheightsmall;
304
305 $file_path = $this->options['upload_dir'].$file_name;
306 $new_file_path = $options['upload_dir'].$file_name;
307
308 if (dol_mkdir($options['upload_dir']) >= 0) {
309 list($img_width, $img_height) = @getimagesize($file_path);
310 if (!$img_width || !$img_height) {
311 return false;
312 }
313
314 $res = vignette($file_path, $maxwidthmini, $maxheightmini, '_mini'); // We don't use ->addThumbs here because there is no object
315 if (preg_match('/error/i', $res)) {
316 return false;
317 }
318
319 $res = vignette($file_path, $maxwidthsmall, $maxheightsmall, '_small'); // We don't use ->addThumbs here because there is no object
320 if (preg_match('/error/i', $res)) {
321 return false;
322 }
323
324 return true;
325 } else {
326 return false;
327 }
328 }
329
339 protected function validate($uploaded_file, $file, $error, $index)
340 {
341 if ($error) {
342 $file->error = $error;
343 return false;
344 }
345 if (!$file->name) {
346 $file->error = 'missingFileName';
347 return false;
348 }
349 if (!preg_match($this->options['accept_file_types'], $file->name)) {
350 $file->error = 'acceptFileTypes';
351 return false;
352 }
353 if ($uploaded_file && is_uploaded_file($uploaded_file)) {
354 $file_size = dol_filesize($uploaded_file);
355 } else {
356 $file_size = $_SERVER['CONTENT_LENGTH'];
357 }
358 if ($this->options['max_file_size'] && (
359 $file_size > $this->options['max_file_size'] ||
360 $file->size > $this->options['max_file_size']
361 )
362 ) {
363 $file->error = 'maxFileSize';
364 return false;
365 }
366 if ($this->options['min_file_size'] &&
367 $file_size < $this->options['min_file_size']) {
368 $file->error = 'minFileSize';
369 return false;
370 }
371 if (is_numeric($this->options['max_number_of_files']) && (
372 count($this->getFileObjects()) >= $this->options['max_number_of_files']
373 )
374 ) {
375 $file->error = 'maxNumberOfFiles';
376 return false;
377 }
378 list($img_width, $img_height) = @getimagesize($uploaded_file);
379 if (is_numeric($img_width)) {
380 if ($this->options['max_width'] && $img_width > $this->options['max_width'] ||
381 $this->options['max_height'] && $img_height > $this->options['max_height']) {
382 $file->error = 'maxResolution';
383 return false;
384 }
385 if ($this->options['min_width'] && $img_width < $this->options['min_width'] ||
386 $this->options['min_height'] && $img_height < $this->options['min_height']) {
387 $file->error = 'minResolution';
388 return false;
389 }
390 }
391 return true;
392 }
393
400 protected function upcountNameCallback($matches)
401 {
402 $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
403 $ext = isset($matches[2]) ? $matches[2] : '';
404 return ' ('.$index.')'.$ext;
405 }
406
413 protected function upcountName($name)
414 {
415 return preg_replace_callback('/(?:(?: \‍(([\d]+)\‍))?(\.[^.]+))?$/', array($this, 'upcountNameCallback'), $name, 1);
416 }
417
426 protected function trimFileName($name, $type, $index)
427 {
428 // Remove path information and dots around the filename, to prevent uploading
429 // into different directories or replacing hidden system files.
430 $file_name = basename(dol_sanitizeFileName($name));
431 $file_name = preg_replace('/ {2,}/', ' ', $file_name); // replaces multiple spaces into one space like the upload flow via input field
432 // Add missing file extension for known image types:
433 $matches = array();
434 if (strpos($file_name, '.') === false && preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
435 $file_name .= '.'.$matches[1];
436 }
437 if ($this->options['discard_aborted_uploads']) {
438 while (dol_is_file($this->options['upload_dir'].$file_name)) {
439 $file_name = $this->upcountName($file_name);
440 }
441 }
442 return $file_name;
443 }
444
458 protected function handleFileUpload($uploaded_file, $name, $size, $type, $error, $index)
459 {
460 $file = new stdClass();
461 $file->name = $this->trimFileName($name, $type, $index);
462 $file->mime = dol_mimetype($file->name, '', 2);
463 $file->size = intval($size);
464 $file->type = $type;
465
466 // Sanitize to avoid stream execution when calling file_size(). Not that this is a second security because
467 // most streams are already disabled by stream_wrapper_unregister() in filefunc.inc.php
468 $uploaded_file = preg_replace('/\s*(http|ftp|sftp|)s?:/i', '', $uploaded_file);
469 $uploaded_file = realpath($uploaded_file); // A hack to be sure the file point to an existing file on disk (and is not a SSRF attack)
470
471 $validate = $this->validate($uploaded_file, $file, $error, $index);
472
473 if ($validate) {
474 if (dol_mkdir($this->options['upload_dir']) >= 0) {
475 // Add object reference as file name prefix if const MAIN_DISABLE_SUGGEST_REF_AS_PREFIX is not enabled
476 $fileNameWithoutExt = preg_replace('/\.[^\.]+$/', '', $file->name);
477 $savingDocMask = $this->options['saving_doc_mask'];
478 if ($savingDocMask && strpos($savingDocMask, $fileNameWithoutExt) !== 0) {
479 $fileNameWithPrefix = preg_replace('/__file__/', $file->name, $savingDocMask);
480 $file->name = $fileNameWithPrefix;
481 }
482
483 // trimFileName() checked the name is not already used, but it did it before the reference of the
484 // object was added as a prefix above, so it compared a name that is not the one we store. We must
485 // check it again on the final name, otherwise uploading twice the same file silently overwrites
486 // the first one, because dol_move_uploaded_file() is called below with $allowoverwrite = 1.
487 // The .noexe suffix is appended by dol_move_uploaded_file() on an executable file, so we must also
488 // look for the suffixed name, otherwise such a file is never seen as already existing and it is
489 // overwritten at each upload.
490 if ($this->options['discard_aborted_uploads']) {
491 $tmppath = dol_sanitizePathName($this->options['upload_dir']);
492 while (dol_is_file($tmppath.dol_sanitizeFileName($file->name)) || dol_is_file($tmppath.dol_sanitizeFileName($file->name).'.noexe')) {
493 $file->name = $this->upcountName($file->name);
494 }
495 }
496
497 $file_path = dol_sanitizePathName($this->options['upload_dir']).dol_sanitizeFileName($file->name);
498 $append_file = !$this->options['discard_aborted_uploads'] && dol_is_file($file_path) && $file->size > dol_filesize($file_path);
499
500 clearstatcache();
501
502 if ($uploaded_file && is_uploaded_file($uploaded_file)) {
503 // multipart/formdata uploads (POST method uploads)
504 if ($append_file) {
505 file_put_contents($file_path, fopen($uploaded_file, 'r'), FILE_APPEND);
506 } else {
507 // TODO Replace this with a call of dol_add_file_process(... $mode=1)
508 $result = dol_move_uploaded_file($uploaded_file, $file_path, 1, 0, 0, 0, 'userfile');
509
510 // A return of 2 means the file was stored with a .noexe suffix appended on its name.
511 // We must follow that renaming, otherwise the size check below is done on a file that
512 // does not exist, and we report an error on a file that was correctly stored.
513 if ($result == 2) {
514 $file->name .= '.noexe';
515 $file_path .= '.noexe';
516 }
517 }
518 } else {
519 // Non-multipart uploads (PUT method support)
520 file_put_contents($file_path, fopen('php://input', 'r'), $append_file ? FILE_APPEND : 0);
521 }
522 dolChmod($file_path);
523
524 $file_size = dol_filesize($file_path);
525 if ($file_size === $file->size) {
526 $file->url = $this->options['upload_url'].urlencode($file->name);
527 foreach ($this->options['image_versions'] as $version => $options) {
528 if ($this->createScaledImage($file->name, $options)) { // Creation of thumbs mini and small is ok
529 $tmp = explode('.', $file->name);
530
531 // We save the path of mini file into file->... (seems not used)
532 $keyforfile = $version.'_url';
533 $file->$keyforfile = $options['upload_url'].urlencode($tmp[0].'_mini.'.$tmp[1]);
534 }
535 }
536 } elseif ($this->options['discard_aborted_uploads']) {
537 unlink($file_path);
538 $file->error = 'abort';
539 }
540 $file->size = $file_size;
541 $this->setFileDeleteUrl($file);
542 } else {
543 $file->error = 'failedtocreatedestdir';
544 }
545 } else {
546 // should not happen
547 }
548
549 return $file;
550 }
551
557 /*public function get()
558 {
559 $file_name = isset($_REQUEST['file']) ? basename(stripslashes($_REQUEST['file'])) : null;
560 if ($file_name) {
561 $info = $this->getFileObject($file_name);
562 } else {
563 $info = $this->getFileObjects();
564 }
565
566 header('Content-type: application/json');
567 echo json_encode($info);
568 }
569 */
570
576 public function post()
577 {
578 $error = 0;
579
580 $upload = isset($_FILES[$this->options['param_name']]) ? $_FILES[$this->options['param_name']] : null;
581
582 $info = array();
583 if ($upload && is_array($upload['tmp_name'])) {
584 // param_name is an array identifier like "files[]",
585 // $_FILES is a multi-dimensional array:
586 foreach ($upload['tmp_name'] as $index => $value) {
587 $tmpres = $this->handleFileUpload(
588 $upload['tmp_name'][$index],
589 isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : $upload['name'][$index],
590 isset($_SERVER['HTTP_X_FILE_SIZE']) ? $_SERVER['HTTP_X_FILE_SIZE'] : $upload['size'][$index],
591 isset($_SERVER['HTTP_X_FILE_TYPE']) ? $_SERVER['HTTP_X_FILE_TYPE'] : $upload['type'][$index],
592 $upload['error'][$index],
593 (string) $index
594 );
595 if (!empty($tmpres->error)) {
596 $error++;
597 }
598 $info[] = $tmpres;
599 }
600 } elseif ($upload || isset($_SERVER['HTTP_X_FILE_NAME'])) {
601 // param_name is a single object identifier like "file",
602 // $_FILES is a one-dimensional array:
603 $tmpres = $this->handleFileUpload(
604 isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
605 isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : (isset($upload['name']) ? $upload['name'] : null),
606 isset($_SERVER['HTTP_X_FILE_SIZE']) ? $_SERVER['HTTP_X_FILE_SIZE'] : (isset($upload['size']) ? $upload['size'] : null),
607 isset($_SERVER['HTTP_X_FILE_TYPE']) ? $_SERVER['HTTP_X_FILE_TYPE'] : (isset($upload['type']) ? $upload['type'] : null),
608 isset($upload['error']) ? $upload['error'] : null,
609 '0'
610 );
611 if (!empty($tmpres->error)) {
612 $error++;
613 }
614 $info[] = $tmpres;
615 }
616
617 header('Vary: Accept');
618 $json = json_encode($info);
619
620 /* disabled. Param redirect seems not used
621 $redirect = isset($_REQUEST['redirect']) ? stripslashes($_REQUEST['redirect']) : null;
622 if ($redirect) {
623 header('Location: '.sprintf($redirect, urlencode($json)));
624 return;
625 }
626 */
627
628 if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
629 header('Content-type: application/json');
630 } else {
631 header('Content-type: text/plain');
632 }
633 echo $json;
634
635 return $error;
636 }
637
644 /*
645 public function delete($file)
646 {
647 $file_name = $file ? basename($file) : null;
648 $file_path = $this->options['upload_dir'].dol_sanitizeFileName($file_name);
649 $success = dol_is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
650 if ($success) {
651 foreach ($this->options['image_versions'] as $version => $options) {
652 $file = $options['upload_dir'].$file_name;
653 if (dol_is_file($file)) {
654 unlink($file);
655 }
656 }
657 }
658 // Return result in json format
659 header('Content-type: application/json');
660 echo json_encode($success);
661
662 return 0;
663 }
664 */
665}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
This class is used to manage file upload using ajax.
getFileObjects()
getFileObjects
setFileDeleteUrl($file)
Set delete url.
__construct($options=null, $fk_element=null, $element=null)
Constructor.
post()
Output data.
handleFileUpload($uploaded_file, $name, $size, $type, $error, $index)
handleFileUpload.
upcountName($name)
Enter description here ...
getFileObject($file_name)
getFileObject
upcountNameCallback($matches)
Enter description here ...
createScaledImage($file_name, $options)
Create thumbs of a file uploaded.
getFullUrl()
Return full URL.
trimFileName($name, $type, $index)
trimFileName
validate($uploaded_file, $file, $error, $index)
Make validation on an uploaded file.
dol_filesize($pathoffile)
Return size of a file.
dol_is_file($pathoffile)
Return if path is a file.
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $keyforsourcefile='addedfile', $upload_dir='', $mode=0)
Check validity of a file upload from an GUI page, and move it to its final destination.
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dolChmod($filepath, $newmask='')
Change mod of a file.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string