1<?php
2/* Copyright (C) 2008-2012  Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2012-2015  Regis Houssin       <regis.houssin@inodbox.com>
4 * Copyright (C) 2012-2016  Juanjo Menent       <jmenent@2byte.es>
5 * Copyright (C) 2015       Marcos García       <marcosgdf@gmail.com>
6 * Copyright (C) 2016       Raphaël Doursenaud  <rdoursenaud@gpcsolutions.fr>
7 * Copyright (C) 2019       Frédéric France     <frederic.france@netlogic.fr>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 * or see https://www.gnu.org/
22 */
23
24/**
25 *  \file		htdocs/core/lib/files.lib.php
26 *  \brief		Library for file managing functions
27 */
28
29/**
30 * Make a basename working with all page code (default PHP basenamed fails with cyrillic).
31 * We supose dir separator for input is '/'.
32 *
33 * @param	string	$pathfile	String to find basename.
34 * @return	string				Basename of input
35 */
36function dol_basename($pathfile)
37{
38	return preg_replace('/^.*\/([^\/]+)$/', '$1', rtrim($pathfile, '/'));
39}
40
41/**
42 *  Scan a directory and return a list of files/directories.
43 *  Content for string is UTF8 and dir separator is "/".
44 *
45 *  @param	string		$path        	Starting path from which to search. This is a full path.
46 *  @param	string		$types        	Can be "directories", "files", or "all"
47 *  @param	int			$recursive		Determines whether subdirectories are searched
48 *  @param	string		$filter        	Regex filter to restrict list. This regex value must be escaped for '/' by doing preg_quote($var,'/'), since this char is used for preg_match function,
49 *                                      but must not contains the start and end '/'. Filter is checked into basename only.
50 *  @param	array		$excludefilter  Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). Exclude is checked both into fullpath and into basename (So '^xxx' may exclude 'xxx/dirscanned/...' and dirscanned/xxx').
51 *  @param	string		$sortcriteria	Sort criteria ('','fullname','relativename','name','date','size')
52 *  @param	string		$sortorder		Sort order (SORT_ASC, SORT_DESC)
53 *	@param	int			$mode			0=Return array minimum keys loaded (faster), 1=Force all keys like date and size to be loaded (slower), 2=Force load of date only, 3=Force load of size only, 4=Force load of perm
54 *  @param	int			$nohook			Disable all hooks
55 *  @param	string		$relativename	For recursive purpose only. Must be "" at first call.
56 *  @param	string		$donotfollowsymlinks	Do not follow symbolic links
57 *  @return	array						Array of array('name'=>'xxx','fullname'=>'/abc/xxx','date'=>'yyy','size'=>99,'type'=>'dir|file',...)
58 *  @see dol_dir_list_in_database()
59 */
60function dol_dir_list($path, $types = "all", $recursive = 0, $filter = "", $excludefilter = null, $sortcriteria = "name", $sortorder = SORT_ASC, $mode = 0, $nohook = 0, $relativename = "", $donotfollowsymlinks = 0)
61{
62	global $db, $hookmanager;
63	global $object;
64
65	if ($recursive <= 1) {	// Avoid too verbose log
66		dol_syslog("files.lib.php::dol_dir_list path=".$path." types=".$types." recursive=".$recursive." filter=".$filter." excludefilter=".json_encode($excludefilter));
67		//print 'xxx'."files.lib.php::dol_dir_list path=".$path." types=".$types." recursive=".$recursive." filter=".$filter." excludefilter=".json_encode($excludefilter);
68	}
69
70	$loaddate = ($mode == 1 || $mode == 2) ?true:false;
71	$loadsize = ($mode == 1 || $mode == 3) ?true:false;
72	$loadperm = ($mode == 1 || $mode == 4) ?true:false;
73
74	// Clean parameters
75	$path = preg_replace('/([\\/]+)$/i', '', $path);
76	$newpath = dol_osencode($path);
77
78	$reshook = 0;
79	$file_list = array();
80
81	if (is_object($hookmanager) && !$nohook) {
82		$hookmanager->resArray = array();
83
84		$hookmanager->initHooks(array('fileslib'));
85
86		$parameters = array(
87			'path' => $newpath,
88			'types'=> $types,
89			'recursive' => $recursive,
90			'filter' => $filter,
91			'excludefilter' => $excludefilter,
92			'sortcriteria' => $sortcriteria,
93			'sortorder' => $sortorder,
94			'loaddate' => $loaddate,
95			'loadsize' => $loadsize,
96			'mode' => $mode
97		);
98		$reshook = $hookmanager->executeHooks('getDirList', $parameters, $object);
99	}
100
101	// $hookmanager->resArray may contain array stacked by other modules
102	if (empty($reshook)) {
103		if (!is_dir($newpath)) {
104			return array();
105		}
106
107		if ($dir = opendir($newpath)) {
108			$filedate = '';
109			$filesize = '';
110
111			while (false !== ($file = readdir($dir))) {        // $file is always a basename (into directory $newpath)
112				if (!utf8_check($file)) {
113					$file = utf8_encode($file); // To be sure data is stored in utf8 in memory
114				}
115				$fullpathfile = ($newpath ? $newpath.'/' : '').$file;
116
117				$qualified = 1;
118
119				// Define excludefilterarray
120				$excludefilterarray = array('^\.');
121				if (is_array($excludefilter)) {
122					$excludefilterarray = array_merge($excludefilterarray, $excludefilter);
123				} elseif ($excludefilter) {
124					$excludefilterarray[] = $excludefilter;
125				}
126				// Check if file is qualified
127				foreach ($excludefilterarray as $filt) {
128					if (preg_match('/'.$filt.'/i', $file) || preg_match('/'.$filt.'/i', $fullpathfile)) {
129						$qualified = 0;
130						break;
131					}
132				}
133				//print $fullpathfile.' '.$file.' '.$qualified.'<br>';
134
135				if ($qualified) {
136					$isdir = is_dir(dol_osencode($path."/".$file));
137					// Check whether this is a file or directory and whether we're interested in that type
138					if ($isdir && (($types == "directories") || ($types == "all") || $recursive > 0)) {
139						// Add entry into file_list array
140						if (($types == "directories") || ($types == "all")) {
141							if ($loaddate || $sortcriteria == 'date') {
142								$filedate = dol_filemtime($path."/".$file);
143							}
144							if ($loadsize || $sortcriteria == 'size') {
145								$filesize = dol_filesize($path."/".$file);
146							}
147							if ($loadperm || $sortcriteria == 'perm') {
148								$fileperm = dol_fileperm($path."/".$file);
149							}
150
151							if (!$filter || preg_match('/'.$filter.'/i', $file)) {	// We do not search key $filter into all $path, only into $file part
152								$reg = array();
153								preg_match('/([^\/]+)\/[^\/]+$/', $path.'/'.$file, $reg);
154								$level1name = (isset($reg[1]) ? $reg[1] : '');
155								$file_list[] = array(
156									"name" => $file,
157									"path" => $path,
158									"level1name" => $level1name,
159									"relativename" => ($relativename ? $relativename.'/' : '').$file,
160									"fullname" => $path.'/'.$file,
161									"date" => $filedate,
162									"size" => $filesize,
163									"perm" => $fileperm,
164									"type" => 'dir'
165								);
166							}
167						}
168
169						// if we're in a directory and we want recursive behavior, call this function again
170						if ($recursive > 0) {
171							if (empty($donotfollowsymlinks) || !is_link($path."/".$file)) {
172								//var_dump('eee '. $path."/".$file. ' '.is_dir($path."/".$file).' '.is_link($path."/".$file));
173								$file_list = array_merge($file_list, dol_dir_list($path."/".$file, $types, $recursive + 1, $filter, $excludefilter, $sortcriteria, $sortorder, $mode, $nohook, ($relativename != '' ? $relativename.'/' : '').$file, $donotfollowsymlinks));
174							}
175						}
176					} elseif (!$isdir && (($types == "files") || ($types == "all"))) {
177						// Add file into file_list array
178						if ($loaddate || $sortcriteria == 'date') {
179							$filedate = dol_filemtime($path."/".$file);
180						}
181						if ($loadsize || $sortcriteria == 'size') {
182							$filesize = dol_filesize($path."/".$file);
183						}
184
185						if (!$filter || preg_match('/'.$filter.'/i', $file)) {	// We do not search key $filter into $path, only into $file
186							preg_match('/([^\/]+)\/[^\/]+$/', $path.'/'.$file, $reg);
187							$level1name = (isset($reg[1]) ? $reg[1] : '');
188							$file_list[] = array(
189								"name" => $file,
190								"path" => $path,
191								"level1name" => $level1name,
192								"relativename" => ($relativename ? $relativename.'/' : '').$file,
193								"fullname" => $path.'/'.$file,
194								"date" => $filedate,
195								"size" => $filesize,
196								"type" => 'file'
197							);
198						}
199					}
200				}
201			}
202			closedir($dir);
203
204			// Obtain a list of columns
205			if (!empty($sortcriteria) && $sortorder) {
206				$file_list = dol_sort_array($file_list, $sortcriteria, ($sortorder == SORT_ASC ? 'asc' : 'desc'));
207			}
208		}
209	}
210
211	if (is_object($hookmanager) && is_array($hookmanager->resArray)) {
212		$file_list = array_merge($file_list, $hookmanager->resArray);
213	}
214
215	return $file_list;
216}
217
218
219/**
220 *  Scan a directory and return a list of files/directories.
221 *  Content for string is UTF8 and dir separator is "/".
222 *
223 *  @param	string		$path        	Starting path from which to search. Example: 'produit/MYPROD'
224 *  @param	string		$filter        	Regex filter to restrict list. This regex value must be escaped for '/', since this char is used for preg_match function
225 *  @param	array|null	$excludefilter  Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.'))
226 *  @param	string		$sortcriteria	Sort criteria ("","fullname","name","date","size")
227 *  @param	string		$sortorder		Sort order (SORT_ASC, SORT_DESC)
228 *	@param	int			$mode			0=Return array minimum keys loaded (faster), 1=Force all keys like description
229 *  @return	array						Array of array('name'=>'xxx','fullname'=>'/abc/xxx','type'=>'dir|file',...)
230 *  @see dol_dir_list()
231 */
232function dol_dir_list_in_database($path, $filter = "", $excludefilter = null, $sortcriteria = "name", $sortorder = SORT_ASC, $mode = 0)
233{
234	global $conf, $db;
235
236	$sql = " SELECT rowid, label, entity, filename, filepath, fullpath_orig, keywords, cover, gen_or_uploaded, extraparams,";
237	$sql .= " date_c, tms as date_m, fk_user_c, fk_user_m, acl, position, share";
238	if ($mode) {
239		$sql .= ", description";
240	}
241	$sql .= " FROM ".MAIN_DB_PREFIX."ecm_files";
242	$sql .= " WHERE entity = ".$conf->entity;
243	if (preg_match('/%$/', $path)) {
244		$sql .= " AND filepath LIKE '".$db->escape($path)."'";
245	} else {
246		$sql .= " AND filepath = '".$db->escape($path)."'";
247	}
248
249	$resql = $db->query($sql);
250	if ($resql) {
251		$file_list = array();
252		$num = $db->num_rows($resql);
253		$i = 0;
254		while ($i < $num) {
255			$obj = $db->fetch_object($resql);
256			if ($obj) {
257				$reg = array();
258				preg_match('/([^\/]+)\/[^\/]+$/', DOL_DATA_ROOT.'/'.$obj->filepath.'/'.$obj->filename, $reg);
259				$level1name = (isset($reg[1]) ? $reg[1] : '');
260				$file_list[] = array(
261					"rowid" => $obj->rowid,
262					"label" => $obj->label, // md5
263					"name" => $obj->filename,
264					"path" => DOL_DATA_ROOT.'/'.$obj->filepath,
265					"level1name" => $level1name,
266					"fullname" => DOL_DATA_ROOT.'/'.$obj->filepath.'/'.$obj->filename,
267					"fullpath_orig" => $obj->fullpath_orig,
268					"date_c" => $db->jdate($obj->date_c),
269					"date_m" => $db->jdate($obj->date_m),
270					"type" => 'file',
271					"keywords" => $obj->keywords,
272					"cover" => $obj->cover,
273					"position" => (int) $obj->position,
274					"acl" => $obj->acl,
275					"share" => $obj->share
276				);
277			}
278			$i++;
279		}
280
281		// Obtain a list of columns
282		if (!empty($sortcriteria)) {
283			$myarray = array();
284			foreach ($file_list as $key => $row) {
285				$myarray[$key] = (isset($row[$sortcriteria]) ? $row[$sortcriteria] : '');
286			}
287			// Sort the data
288			if ($sortorder) {
289				array_multisort($myarray, $sortorder, $file_list);
290			}
291		}
292
293		return $file_list;
294	} else {
295		dol_print_error($db);
296		return array();
297	}
298}
299
300
301/**
302 * Complete $filearray with data from database.
303 * This will call doldir_list_indatabase to complate filearray.
304 *
305 * @param	array	$filearray			Array of files obtained using dol_dir_list
306 * @param	string	$relativedir		Relative dir from DOL_DATA_ROOT
307 * @return	void
308 */
309function completeFileArrayWithDatabaseInfo(&$filearray, $relativedir)
310{
311	global $conf, $db, $user;
312
313	$filearrayindatabase = dol_dir_list_in_database($relativedir, '', null, 'name', SORT_ASC);
314
315	// TODO Remove this when PRODUCT_USE_OLD_PATH_FOR_PHOTO will be removed
316	global $modulepart;
317	if ($modulepart == 'produit' && !empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
318		global $object;
319		if (!empty($object->id)) {
320			if (!empty($conf->product->enabled)) {
321				$upload_dirold = $conf->product->multidir_output[$object->entity].'/'.substr(substr("000".$object->id, -2), 1, 1).'/'.substr(substr("000".$object->id, -2), 0, 1).'/'.$object->id."/photos";
322			} else {
323				$upload_dirold = $conf->service->multidir_output[$object->entity].'/'.substr(substr("000".$object->id, -2), 1, 1).'/'.substr(substr("000".$object->id, -2), 0, 1).'/'.$object->id."/photos";
324			}
325
326			$relativedirold = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dirold);
327			$relativedirold = preg_replace('/^[\\/]/', '', $relativedirold);
328
329			$filearrayindatabase = array_merge($filearrayindatabase, dol_dir_list_in_database($relativedirold, '', null, 'name', SORT_ASC));
330		}
331	}
332
333	/*var_dump($relativedir);
334	var_dump($filearray);
335	var_dump($filearrayindatabase);*/
336
337	// Complete filearray with properties found into $filearrayindatabase
338	foreach ($filearray as $key => $val) {
339		$tmpfilename = preg_replace('/\.noexe$/', '', $filearray[$key]['name']);
340		$found = 0;
341		// Search if it exists into $filearrayindatabase
342		foreach ($filearrayindatabase as $key2 => $val2) {
343			if (($filearrayindatabase[$key2]['path'] == $filearray[$key]['path']) && ($filearrayindatabase[$key2]['name'] == $tmpfilename)) {
344				$filearray[$key]['position_name'] = ($filearrayindatabase[$key2]['position'] ? $filearrayindatabase[$key2]['position'] : '0').'_'.$filearrayindatabase[$key2]['name'];
345				$filearray[$key]['position'] = $filearrayindatabase[$key2]['position'];
346				$filearray[$key]['cover'] = $filearrayindatabase[$key2]['cover'];
347				$filearray[$key]['acl'] = $filearrayindatabase[$key2]['acl'];
348				$filearray[$key]['rowid'] = $filearrayindatabase[$key2]['rowid'];
349				$filearray[$key]['label'] = $filearrayindatabase[$key2]['label'];
350				$filearray[$key]['share'] = $filearrayindatabase[$key2]['share'];
351				$found = 1;
352				break;
353			}
354		}
355
356		if (!$found) {    // This happen in transition toward version 6, or if files were added manually into os dir.
357			$filearray[$key]['position'] = '999999'; // File not indexed are at end. So if we add a file, it will not replace an existing position
358			$filearray[$key]['cover'] = 0;
359			$filearray[$key]['acl'] = '';
360
361			$rel_filename = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $filearray[$key]['fullname']);
362
363			if (!preg_match('/([\\/]temp[\\/]|[\\/]thumbs|\.meta$)/', $rel_filename)) {     // If not a tmp file
364				dol_syslog("list_of_documents We found a file called '".$filearray[$key]['name']."' not indexed into database. We add it");
365				include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
366				$ecmfile = new EcmFiles($db);
367
368				// Add entry into database
369				$filename = basename($rel_filename);
370				$rel_dir = dirname($rel_filename);
371				$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
372				$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
373
374				$ecmfile->filepath = $rel_dir;
375				$ecmfile->filename = $filename;
376				$ecmfile->label = md5_file(dol_osencode($filearray[$key]['fullname'])); // $destfile is a full path to file
377				$ecmfile->fullpath_orig = $filearray[$key]['fullname'];
378				$ecmfile->gen_or_uploaded = 'unknown';
379				$ecmfile->description = ''; // indexed content
380				$ecmfile->keywords = ''; // keyword content
381				$result = $ecmfile->create($user);
382				if ($result < 0) {
383					setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
384				} else {
385					$filearray[$key]['rowid'] = $result;
386				}
387			} else {
388				$filearray[$key]['rowid'] = 0; // Should not happened
389			}
390		}
391	}
392	//var_dump($filearray); var_dump($relativedir.' - tmpfilename='.$tmpfilename.' - found='.$found);
393}
394
395
396/**
397 * Fast compare of 2 files identified by their properties ->name, ->date and ->size
398 *
399 * @param	string 	$a		File 1
400 * @param 	string	$b		File 2
401 * @return 	int				1, 0, 1
402 */
403function dol_compare_file($a, $b)
404{
405	global $sortorder;
406	global $sortfield;
407
408	$sortorder = strtoupper($sortorder);
409
410	if ($sortorder == 'ASC') {
411		$retup = -1;
412		$retdown = 1;
413	} else {
414		$retup = 1;
415		$retdown = -1;
416	}
417
418	if ($sortfield == 'name') {
419		if ($a->name == $b->name) {
420			return 0;
421		}
422		return ($a->name < $b->name) ? $retup : $retdown;
423	}
424	if ($sortfield == 'date') {
425		if ($a->date == $b->date) {
426			return 0;
427		}
428		return ($a->date < $b->date) ? $retup : $retdown;
429	}
430	if ($sortfield == 'size') {
431		if ($a->size == $b->size) {
432			return 0;
433		}
434		return ($a->size < $b->size) ? $retup : $retdown;
435	}
436}
437
438
439/**
440 * Test if filename is a directory
441 *
442 * @param	string		$folder     Name of folder
443 * @return	boolean     			True if it's a directory, False if not found
444 */
445function dol_is_dir($folder)
446{
447	$newfolder = dol_osencode($folder);
448	if (is_dir($newfolder)) {
449		return true;
450	} else {
451		return false;
452	}
453}
454
455/**
456 * Return if path is empty
457 *
458 * @param   string		$dir		Path of Directory
459 * @return  boolean     		    True or false
460 */
461function dol_is_dir_empty($dir)
462{
463	if (!is_readable($dir)) {
464		return false;
465	}
466	return (count(scandir($dir)) == 2);
467}
468
469/**
470 * Return if path is a file
471 *
472 * @param   string		$pathoffile		Path of file
473 * @return  boolean     			    True or false
474 */
475function dol_is_file($pathoffile)
476{
477	$newpathoffile = dol_osencode($pathoffile);
478	return is_file($newpathoffile);
479}
480
481/**
482 * Return if path is a symbolic link
483 *
484 * @param   string		$pathoffile		Path of file
485 * @return  boolean     			    True or false
486 */
487function dol_is_link($pathoffile)
488{
489	$newpathoffile = dol_osencode($pathoffile);
490	return is_link($newpathoffile);
491}
492
493/**
494 * Return if path is an URL
495 *
496 * @param   string		$url	Url
497 * @return  boolean      	   	True or false
498 */
499function dol_is_url($url)
500{
501	$tmpprot = array('file', 'http', 'https', 'ftp', 'zlib', 'data', 'ssh', 'ssh2', 'ogg', 'expect');
502	foreach ($tmpprot as $prot) {
503		if (preg_match('/^'.$prot.':/i', $url)) {
504			return true;
505		}
506	}
507	return false;
508}
509
510/**
511 * 	Test if a folder is empty
512 *
513 * 	@param	string	$folder		Name of folder
514 * 	@return boolean				True if dir is empty or non-existing, False if it contains files
515 */
516function dol_dir_is_emtpy($folder)
517{
518	$newfolder = dol_osencode($folder);
519	if (is_dir($newfolder)) {
520		$handle = opendir($newfolder);
521		$folder_content = '';
522		while ((gettype($name = readdir($handle)) != "boolean")) {
523			$name_array[] = $name;
524		}
525		foreach ($name_array as $temp) {
526			$folder_content .= $temp;
527		}
528
529		closedir($handle);
530
531		if ($folder_content == "...") {
532			return true;
533		} else {
534			return false;
535		}
536	} else {
537		return true; // Dir does not exists
538	}
539}
540
541/**
542 * 	Count number of lines in a file
543 *
544 * 	@param	string	$file		Filename
545 * 	@return int					<0 if KO, Number of lines in files if OK
546 *  @see dol_nboflines()
547 */
548function dol_count_nb_of_line($file)
549{
550	$nb = 0;
551
552	$newfile = dol_osencode($file);
553	//print 'x'.$file;
554	$fp = fopen($newfile, 'r');
555	if ($fp) {
556		while (!feof($fp)) {
557			$line = fgets($fp);
558			// We increase count only if read was success. We need test because feof return true only after fgets so we do n+1 fgets for a file with n lines.
559			if (!$line === false) {
560				$nb++;
561			}
562		}
563		fclose($fp);
564	} else {
565		$nb = -1;
566	}
567
568	return $nb;
569}
570
571
572/**
573 * Return size of a file
574 *
575 * @param 	string		$pathoffile		Path of file
576 * @return 	integer						File size
577 * @see dol_print_size()
578 */
579function dol_filesize($pathoffile)
580{
581	$newpathoffile = dol_osencode($pathoffile);
582	return filesize($newpathoffile);
583}
584
585/**
586 * Return time of a file
587 *
588 * @param 	string		$pathoffile		Path of file
589 * @return 	int					Time of file
590 */
591function dol_filemtime($pathoffile)
592{
593	$newpathoffile = dol_osencode($pathoffile);
594	return @filemtime($newpathoffile); // @Is to avoid errors if files does not exists
595}
596
597/**
598 * Return permissions of a file
599 *
600 * @param 	string		$pathoffile		Path of file
601 * @return 	integer						File permissions
602 */
603function dol_fileperm($pathoffile)
604{
605	$newpathoffile = dol_osencode($pathoffile);
606	return fileperms($newpathoffile);
607}
608
609/**
610 * Make replacement of strings into a file.
611 *
612 * @param	string	$srcfile			       Source file (can't be a directory)
613 * @param	array	$arrayreplacement	       Array with strings to replace. Example: array('valuebefore'=>'valueafter', ...)
614 * @param	string	$destfile			       Destination file (can't be a directory). If empty, will be same than source file.
615 * @param	int		$newmask			       Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
616 * @param	int		$indexdatabase		       1=index new file into database.
617 * @param   int     $arrayreplacementisregex   1=Array of replacement is regex
618 * @return	int							       <0 if error, 0 if nothing done (dest file already exists), >0 if OK
619 * @see		dol_copy()
620 */
621function dolReplaceInFile($srcfile, $arrayreplacement, $destfile = '', $newmask = 0, $indexdatabase = 0, $arrayreplacementisregex = 0)
622{
623	global $conf;
624
625	dol_syslog("files.lib.php::dolReplaceInFile srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." indexdatabase=".$indexdatabase." arrayreplacementisregex=".$arrayreplacementisregex);
626
627	if (empty($srcfile)) {
628		return -1;
629	}
630	if (empty($destfile)) {
631		$destfile = $srcfile;
632	}
633
634	$destexists = dol_is_file($destfile);
635	if (($destfile != $srcfile) && $destexists) {
636		return 0;
637	}
638
639	$tmpdestfile = $destfile.'.tmp';
640
641	$newpathofsrcfile = dol_osencode($srcfile);
642	$newpathoftmpdestfile = dol_osencode($tmpdestfile);
643	$newpathofdestfile = dol_osencode($destfile);
644	$newdirdestfile = dirname($newpathofdestfile);
645
646	if ($destexists && !is_writable($newpathofdestfile)) {
647		dol_syslog("files.lib.php::dolReplaceInFile failed Permission denied to overwrite target file", LOG_WARNING);
648		return -1;
649	}
650	if (!is_writable($newdirdestfile)) {
651		dol_syslog("files.lib.php::dolReplaceInFile failed Permission denied to write into target directory ".$newdirdestfile, LOG_WARNING);
652		return -2;
653	}
654
655	dol_delete_file($tmpdestfile);
656
657	// Create $newpathoftmpdestfile from $newpathofsrcfile
658	$content = file_get_contents($newpathofsrcfile, 'r');
659
660	if (empty($arrayreplacementisregex)) {
661		$content = make_substitutions($content, $arrayreplacement, null);
662	} else {
663		foreach ($arrayreplacement as $key => $value) {
664			$content = preg_replace($key, $value, $content);
665		}
666	}
667
668	file_put_contents($newpathoftmpdestfile, $content);
669	@chmod($newpathoftmpdestfile, octdec($newmask));
670
671	// Rename
672	$result = dol_move($newpathoftmpdestfile, $newpathofdestfile, $newmask, (($destfile == $srcfile) ? 1 : 0), 0, $indexdatabase);
673	if (!$result) {
674		dol_syslog("files.lib.php::dolReplaceInFile failed to move tmp file to final dest", LOG_WARNING);
675		return -3;
676	}
677	if (empty($newmask) && !empty($conf->global->MAIN_UMASK)) {
678		$newmask = $conf->global->MAIN_UMASK;
679	}
680	if (empty($newmask)) {	// This should no happen
681		dol_syslog("Warning: dolReplaceInFile called with empty value for newmask and no default value defined", LOG_WARNING);
682		$newmask = '0664';
683	}
684
685	@chmod($newpathofdestfile, octdec($newmask));
686
687	return 1;
688}
689
690
691/**
692 * Copy a file to another file.
693 *
694 * @param	string	$srcfile			Source file (can't be a directory)
695 * @param	string	$destfile			Destination file (can't be a directory)
696 * @param	int		$newmask			Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
697 * @param 	int		$overwriteifexists	Overwrite file if exists (1 by default)
698 * @return	int							<0 if error, 0 if nothing done (dest file already exists and overwriteifexists=0), >0 if OK
699 * @see		dol_delete_file() dolCopyDir()
700 */
701function dol_copy($srcfile, $destfile, $newmask = 0, $overwriteifexists = 1)
702{
703	global $conf;
704
705	dol_syslog("files.lib.php::dol_copy srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwriteifexists=".$overwriteifexists);
706
707	if (empty($srcfile) || empty($destfile)) {
708		return -1;
709	}
710
711	$destexists = dol_is_file($destfile);
712	if (!$overwriteifexists && $destexists) {
713		return 0;
714	}
715
716	$newpathofsrcfile = dol_osencode($srcfile);
717	$newpathofdestfile = dol_osencode($destfile);
718	$newdirdestfile = dirname($newpathofdestfile);
719
720	if ($destexists && !is_writable($newpathofdestfile)) {
721		dol_syslog("files.lib.php::dol_copy failed Permission denied to overwrite target file", LOG_WARNING);
722		return -1;
723	}
724	if (!is_writable($newdirdestfile)) {
725		dol_syslog("files.lib.php::dol_copy failed Permission denied to write into target directory ".$newdirdestfile, LOG_WARNING);
726		return -2;
727	}
728	// Copy with overwriting if exists
729	$result = @copy($newpathofsrcfile, $newpathofdestfile);
730	//$result=copy($newpathofsrcfile, $newpathofdestfile);	// To see errors, remove @
731	if (!$result) {
732		dol_syslog("files.lib.php::dol_copy failed to copy", LOG_WARNING);
733		return -3;
734	}
735	if (empty($newmask) && !empty($conf->global->MAIN_UMASK)) {
736		$newmask = $conf->global->MAIN_UMASK;
737	}
738	if (empty($newmask)) {	// This should no happen
739		dol_syslog("Warning: dol_copy called with empty value for newmask and no default value defined", LOG_WARNING);
740		$newmask = '0664';
741	}
742
743	@chmod($newpathofdestfile, octdec($newmask));
744
745	return 1;
746}
747
748/**
749 * Copy a dir to another dir. This include recursive subdirectories.
750 *
751 * @param	string	$srcfile			Source file (a directory)
752 * @param	string	$destfile			Destination file (a directory)
753 * @param	int		$newmask			Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
754 * @param 	int		$overwriteifexists	Overwrite file if exists (1 by default)
755 * @param	array	$arrayreplacement	Array to use to replace filenames with another one during the copy (works only on file names, not on directory names).
756 * @param	int		$excludesubdir		0=Do not exclude subdirectories, 1=Exclude subdirectories, 2=Exclude subdirectories if name is not a 2 chars (used for country codes subdirectories).
757 * @return	int							<0 if error, 0 if nothing done (all files already exists and overwriteifexists=0), >0 if OK
758 * @see		dol_copy()
759 */
760function dolCopyDir($srcfile, $destfile, $newmask, $overwriteifexists, $arrayreplacement = null, $excludesubdir = 0)
761{
762	global $conf;
763
764	$result = 0;
765
766	dol_syslog("files.lib.php::dolCopyDir srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwriteifexists=".$overwriteifexists);
767
768	if (empty($srcfile) || empty($destfile)) {
769		return -1;
770	}
771
772	$destexists = dol_is_dir($destfile);
773	//if (! $overwriteifexists && $destexists) return 0;	// The overwriteifexists is for files only, so propagated to dol_copy only.
774
775	if (!$destexists) {
776		// We must set mask just before creating dir, becaause it can be set differently by dol_copy
777		umask(0);
778		$dirmaskdec = octdec($newmask);
779		if (empty($newmask) && !empty($conf->global->MAIN_UMASK)) {
780			$dirmaskdec = octdec($conf->global->MAIN_UMASK);
781		}
782		$dirmaskdec |= octdec('0200'); // Set w bit required to be able to create content for recursive subdirs files
783		dol_mkdir($destfile, '', decoct($dirmaskdec));
784	}
785
786	$ossrcfile = dol_osencode($srcfile);
787	$osdestfile = dol_osencode($destfile);
788
789	// Recursive function to copy all subdirectories and contents:
790	if (is_dir($ossrcfile)) {
791		$dir_handle = opendir($ossrcfile);
792		while ($file = readdir($dir_handle)) {
793			if ($file != "." && $file != ".." && !is_link($ossrcfile."/".$file)) {
794				if (is_dir($ossrcfile."/".$file)) {
795					if (empty($excludesubdir) || ($excludesubdir == 2 && strlen($file) == 2)) {
796						$newfile = $file;
797						// Replace destination filename with a new one
798						if (is_array($arrayreplacement)) {
799							foreach ($arrayreplacement as $key => $val) {
800								$newfile = str_replace($key, $val, $newfile);
801							}
802						}
803						//var_dump("xxx dolCopyDir $srcfile/$file, $destfile/$file, $newmask, $overwriteifexists");
804						$tmpresult = dolCopyDir($srcfile."/".$file, $destfile."/".$newfile, $newmask, $overwriteifexists, $arrayreplacement, $excludesubdir);
805					}
806				} else {
807					$newfile = $file;
808					// Replace destination filename with a new one
809					if (is_array($arrayreplacement)) {
810						foreach ($arrayreplacement as $key => $val) {
811							$newfile = str_replace($key, $val, $newfile);
812						}
813					}
814					$tmpresult = dol_copy($srcfile."/".$file, $destfile."/".$newfile, $newmask, $overwriteifexists);
815				}
816				// Set result
817				if ($result > 0 && $tmpresult >= 0) {
818					// Do nothing, so we don't set result to 0 if tmpresult is 0 and result was success in a previous pass
819				} else {
820					$result = $tmpresult;
821				}
822				if ($result < 0) {
823					break;
824				}
825			}
826		}
827		closedir($dir_handle);
828	} else {
829		// Source directory does not exists
830		$result = -2;
831	}
832
833	return $result;
834}
835
836
837/**
838 * Move a file into another name.
839 * Note:
840 *  - This function differs from dol_move_uploaded_file, because it can be called in any context.
841 *  - Database indexes for files are updated.
842 *  - Test on antivirus is done only if param testvirus is provided and an antivirus was set.
843 *
844 * @param	string  $srcfile            Source file (can't be a directory. use native php @rename() to move a directory)
845 * @param   string	$destfile           Destination file (can't be a directory. use native php @rename() to move a directory)
846 * @param   integer	$newmask            Mask in octal string for new file (0 by default means $conf->global->MAIN_UMASK)
847 * @param   int		$overwriteifexists  Overwrite file if exists (1 by default)
848 * @param   int     $testvirus          Do an antivirus test. Move is canceled if a virus is found.
849 * @param	int		$indexdatabase		Index new file into database.
850 * @return  boolean 		            True if OK, false if KO
851 * @see dol_move_uploaded_file()
852 */
853function dol_move($srcfile, $destfile, $newmask = 0, $overwriteifexists = 1, $testvirus = 0, $indexdatabase = 1)
854{
855	global $user, $db, $conf;
856	$result = false;
857
858	dol_syslog("files.lib.php::dol_move srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwritifexists=".$overwriteifexists);
859	$srcexists = dol_is_file($srcfile);
860	$destexists = dol_is_file($destfile);
861
862	if (!$srcexists) {
863		dol_syslog("files.lib.php::dol_move srcfile does not exists. we ignore the move request.");
864		return false;
865	}
866
867	if ($overwriteifexists || !$destexists) {
868		$newpathofsrcfile = dol_osencode($srcfile);
869		$newpathofdestfile = dol_osencode($destfile);
870
871		// Check virus
872		$testvirusarray = array();
873		if ($testvirus) {
874			$testvirusarray = dolCheckVirus($newpathofsrcfile);
875			if (count($testvirusarray)) {
876				dol_syslog("files.lib.php::dol_move canceled because a virus was found into source file. we ignore the move request.", LOG_WARNING);
877				return false;
878			}
879		}
880
881		$result = @rename($newpathofsrcfile, $newpathofdestfile); // To see errors, remove @
882		if (!$result) {
883			if ($destexists) {
884				dol_syslog("files.lib.php::dol_move Failed. We try to delete target first and move after.", LOG_WARNING);
885				// We force delete and try again. Rename function sometimes fails to replace dest file with some windows NTFS partitions.
886				dol_delete_file($destfile);
887				$result = @rename($newpathofsrcfile, $newpathofdestfile); // To see errors, remove @
888			} else {
889				dol_syslog("files.lib.php::dol_move Failed.", LOG_WARNING);
890			}
891		}
892
893		// Move ok
894		if ($result && $indexdatabase) {
895			// Rename entry into ecm database
896			$rel_filetorenamebefore = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $srcfile);
897			$rel_filetorenameafter = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $destfile);
898			if (!preg_match('/([\\/]temp[\\/]|[\\/]thumbs|\.meta$)/', $rel_filetorenameafter)) {     // If not a tmp file
899				$rel_filetorenamebefore = preg_replace('/^[\\/]/', '', $rel_filetorenamebefore);
900				$rel_filetorenameafter = preg_replace('/^[\\/]/', '', $rel_filetorenameafter);
901				//var_dump($rel_filetorenamebefore.' - '.$rel_filetorenameafter);exit;
902
903				dol_syslog("Try to rename also entries in database for full relative path before = ".$rel_filetorenamebefore." after = ".$rel_filetorenameafter, LOG_DEBUG);
904				include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
905
906				$ecmfiletarget = new EcmFiles($db);
907				$resultecmtarget = $ecmfiletarget->fetch(0, '', $rel_filetorenameafter);
908				if ($resultecmtarget > 0) {   // An entry for target name already exists for target, we delete it, a new one will be created.
909					$ecmfiletarget->delete($user);
910				}
911
912				$ecmfile = new EcmFiles($db);
913				$resultecm = $ecmfile->fetch(0, '', $rel_filetorenamebefore);
914				if ($resultecm > 0) {   // If an entry was found for src file, we use it to move entry
915					$filename = basename($rel_filetorenameafter);
916					$rel_dir = dirname($rel_filetorenameafter);
917					$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
918					$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
919
920					$ecmfile->filepath = $rel_dir;
921					$ecmfile->filename = $filename;
922
923					$resultecm = $ecmfile->update($user);
924				} elseif ($resultecm == 0) {   // If no entry were found for src files, create/update target file
925					$filename = basename($rel_filetorenameafter);
926					$rel_dir = dirname($rel_filetorenameafter);
927					$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
928					$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
929
930					$ecmfile->filepath = $rel_dir;
931					$ecmfile->filename = $filename;
932					$ecmfile->label = md5_file(dol_osencode($destfile)); // $destfile is a full path to file
933					$ecmfile->fullpath_orig = $srcfile;
934					$ecmfile->gen_or_uploaded = 'unknown';
935					$ecmfile->description = ''; // indexed content
936					$ecmfile->keywords = ''; // keyword content
937					$resultecm = $ecmfile->create($user);
938					if ($resultecm < 0) {
939						setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
940					}
941				} elseif ($resultecm < 0) {
942					setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
943				}
944
945				if ($resultecm > 0) {
946					$result = true;
947				} else {
948					$result = false;
949				}
950			}
951		}
952
953		if (empty($newmask)) {
954			$newmask = empty($conf->global->MAIN_UMASK) ? '0755' : $conf->global->MAIN_UMASK;
955		}
956		$newmaskdec = octdec($newmask);
957		// Currently method is restricted to files (dol_delete_files previously used is for files, and mask usage if for files too)
958		// to allow mask usage for dir, we shoul introduce a new param "isdir" to 1 to complete newmask like this
959		// if ($isdir) $newmaskdec |= octdec('0111');  // Set x bit required for directories
960		@chmod($newpathofdestfile, $newmaskdec);
961	}
962
963	return $result;
964}
965
966/**
967 *	Unescape a file submitted by upload.
968 *  PHP escape char " (%22) or char ' (%27) into $FILES.
969 *
970 *	@param	string	$filename		Filename
971 *	@return	string					Filename sanitized
972 */
973function dol_unescapefile($filename)
974{
975	// Remove path information and dots around the filename, to prevent uploading
976	// into different directories or replacing hidden system files.
977	// Also remove control characters and spaces (\x00..\x20) around the filename:
978	return trim(basename($filename), ".\x00..\x20");
979}
980
981
982/**
983 * Check virus into a file
984 *
985 * @param   string      $src_file       Source file to check
986 * @return  array                       Array of errors or empty array if not virus found
987 */
988function dolCheckVirus($src_file)
989{
990	global $conf;
991
992	if (!empty($conf->global->MAIN_ANTIVIRUS_COMMAND)) {
993		if (!class_exists('AntiVir')) {
994			require_once DOL_DOCUMENT_ROOT.'/core/class/antivir.class.php';
995		}
996		$antivir = new AntiVir($db);
997		$result = $antivir->dol_avscan_file($src_file);
998		if ($result < 0) {	// If virus or error, we stop here
999			$reterrors = $antivir->errors;
1000			return $reterrors;
1001		}
1002	}
1003	return array();
1004}
1005
1006
1007/**
1008 *	Make control on an uploaded file from an GUI page and move it to final destination.
1009 * 	If there is errors (virus found, antivir in error, bad filename), file is not moved.
1010 *  Note:
1011 *  - This function can be used only into a HTML page context. Use dol_move if you are outside.
1012 *  - Test on antivirus is always done (if antivirus set).
1013 *  - Database of files is NOT updated (this is done by dol_add_file_process() that calls this function).
1014 *  - Extension .noexe may be added if file is executable and MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED is not set.
1015 *
1016 *	@param	string	$src_file			Source full path filename ($_FILES['field']['tmp_name'])
1017 *	@param	string	$dest_file			Target full path filename  ($_FILES['field']['name'])
1018 * 	@param	int		$allowoverwrite		1=Overwrite target file if it already exists
1019 * 	@param	int		$disablevirusscan	1=Disable virus scan
1020 * 	@param	integer	$uploaderrorcode	Value of PHP upload error code ($_FILES['field']['error'])
1021 * 	@param	int		$nohook				Disable all hooks
1022 * 	@param	string	$varfiles			_FILES var name
1023 *  @param	string	$upload_dir			For information. Already included into $dest_file.
1024 *	@return int|string       			1 if OK, 2 if OK and .noexe appended, <0 or string if KO
1025 *  @see    dol_move()
1026 */
1027function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan = 0, $uploaderrorcode = 0, $nohook = 0, $varfiles = 'addedfile', $upload_dir = '')
1028{
1029	global $conf, $db, $user, $langs;
1030	global $object, $hookmanager;
1031
1032	$reshook = 0;
1033	$file_name = $dest_file;
1034	$successcode = 1;
1035
1036	if (empty($nohook)) {
1037		$reshook = $hookmanager->initHooks(array('fileslib'));
1038
1039		$parameters = array('dest_file' => $dest_file, 'src_file' => $src_file, 'file_name' => $file_name, 'varfiles' => $varfiles, 'allowoverwrite' => $allowoverwrite);
1040		$reshook = $hookmanager->executeHooks('moveUploadedFile', $parameters, $object);
1041	}
1042
1043	if (empty($reshook)) {
1044		// If an upload error has been reported
1045		if ($uploaderrorcode) {
1046			switch ($uploaderrorcode) {
1047				case UPLOAD_ERR_INI_SIZE:	// 1
1048					return 'ErrorFileSizeTooLarge';
1049				case UPLOAD_ERR_FORM_SIZE:	// 2
1050					return 'ErrorFileSizeTooLarge';
1051				case UPLOAD_ERR_PARTIAL:	// 3
1052					return 'ErrorPartialFile';
1053				case UPLOAD_ERR_NO_TMP_DIR:	//
1054					return 'ErrorNoTmpDir';
1055				case UPLOAD_ERR_CANT_WRITE:
1056					return 'ErrorFailedToWriteInDir';
1057				case UPLOAD_ERR_EXTENSION:
1058					return 'ErrorUploadBlockedByAddon';
1059				default:
1060					break;
1061			}
1062		}
1063
1064		// If we need to make a virus scan
1065		if (empty($disablevirusscan) && file_exists($src_file)) {
1066			$checkvirusarray = dolCheckVirus($src_file);
1067			if (count($checkvirusarray)) {
1068				dol_syslog('Files.lib::dol_move_uploaded_file File "'.$src_file.'" (target name "'.$dest_file.'") KO with antivirus: errors='.join(',', $checkvirusarray), LOG_WARNING);
1069				return 'ErrorFileIsInfectedWithAVirus: '.join(',', $checkvirusarray);
1070			}
1071		}
1072
1073		// Security:
1074		// Disallow file with some extensions. We rename them.
1075		// Because if we put the documents directory into a directory inside web root (very bad), this allows to execute on demand arbitrary code.
1076		if (isAFileWithExecutableContent($dest_file) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED)) {
1077			// $upload_dir ends with a slash, so be must be sure the medias dir to compare to ends with slash too.
1078			$publicmediasdirwithslash = $conf->medias->multidir_output[$conf->entity];
1079			if (!preg_match('/\/$/', $publicmediasdirwithslash)) {
1080				$publicmediasdirwithslash .= '/';
1081			}
1082
1083			if (strpos($upload_dir, $publicmediasdirwithslash) !== 0) {	// We never add .noexe on files into media directory
1084				$file_name .= '.noexe';
1085				$successcode = 2;
1086			}
1087		}
1088
1089		// Security:
1090		// We refuse cache files/dirs, upload using .. and pipes into filenames.
1091		if (preg_match('/^\./', basename($src_file)) || preg_match('/\.\./', $src_file) || preg_match('/[<>|]/', $src_file)) {
1092			dol_syslog("Refused to deliver file ".$src_file, LOG_WARNING);
1093			return -1;
1094		}
1095
1096		// Security:
1097		// We refuse cache files/dirs, upload using .. and pipes into filenames.
1098		if (preg_match('/^\./', basename($dest_file)) || preg_match('/\.\./', $dest_file) || preg_match('/[<>|]/', $dest_file)) {
1099			dol_syslog("Refused to deliver file ".$dest_file, LOG_WARNING);
1100			return -2;
1101		}
1102	}
1103
1104	if ($reshook < 0) {	// At least one blocking error returned by one hook
1105		$errmsg = join(',', $hookmanager->errors);
1106		if (empty($errmsg)) {
1107			$errmsg = 'ErrorReturnedBySomeHooks'; // Should not occurs. Added if hook is bugged and does not set ->errors when there is error.
1108		}
1109		return $errmsg;
1110	} elseif (empty($reshook)) {
1111		// The file functions must be in OS filesystem encoding.
1112		$src_file_osencoded = dol_osencode($src_file);
1113		$file_name_osencoded = dol_osencode($file_name);
1114
1115		// Check if destination dir is writable
1116		if (!is_writable(dirname($file_name_osencoded))) {
1117			dol_syslog("Files.lib::dol_move_uploaded_file Dir ".dirname($file_name_osencoded)." is not writable. Return 'ErrorDirNotWritable'", LOG_WARNING);
1118			return 'ErrorDirNotWritable';
1119		}
1120
1121		// Check if destination file already exists
1122		if (!$allowoverwrite) {
1123			if (file_exists($file_name_osencoded)) {
1124				dol_syslog("Files.lib::dol_move_uploaded_file File ".$file_name." already exists. Return 'ErrorFileAlreadyExists'", LOG_WARNING);
1125				return 'ErrorFileAlreadyExists';
1126			}
1127		} else {	// We are allowed to erase
1128			if (is_dir($file_name_osencoded)) {	// If there is a directory with name of file to create
1129				dol_syslog("Files.lib::dol_move_uploaded_file A directory with name ".$file_name." already exists. Return 'ErrorDirWithFileNameAlreadyExists'", LOG_WARNING);
1130				return 'ErrorDirWithFileNameAlreadyExists';
1131			}
1132		}
1133
1134		// Move file
1135		$return = move_uploaded_file($src_file_osencoded, $file_name_osencoded);
1136		if ($return) {
1137			if (!empty($conf->global->MAIN_UMASK)) {
1138				@chmod($file_name_osencoded, octdec($conf->global->MAIN_UMASK));
1139			}
1140			dol_syslog("Files.lib::dol_move_uploaded_file Success to move ".$src_file." to ".$file_name." - Umask=".$conf->global->MAIN_UMASK, LOG_DEBUG);
1141			return $successcode; // Success
1142		} else {
1143			dol_syslog("Files.lib::dol_move_uploaded_file Failed to move ".$src_file." to ".$file_name, LOG_ERR);
1144			return -3; // Unknown error
1145		}
1146	}
1147
1148	return $successcode; // Success
1149}
1150
1151/**
1152 *  Remove a file or several files with a mask.
1153 *  This delete file physically but also database indexes.
1154 *
1155 *  @param	string	$file           File to delete or mask of files to delete
1156 *  @param  int		$disableglob    Disable usage of glob like * so function is an exact delete function that will return error if no file found
1157 *  @param  int		$nophperrors    Disable all PHP output errors
1158 *  @param	int		$nohook			Disable all hooks
1159 *  @param	object	$object			Current object in use
1160 *  @param	boolean	$allowdotdot	Allow to delete file path with .. inside. Never use this, it is reserved for migration purpose.
1161 *  @param	int		$indexdatabase	Try to remove also index entries.
1162 *  @param	int		$nolog			Disable log file
1163 *  @return boolean         		True if no error (file is deleted or if glob is used and there's nothing to delete), False if error
1164 *  @see dol_delete_dir()
1165 */
1166function dol_delete_file($file, $disableglob = 0, $nophperrors = 0, $nohook = 0, $object = null, $allowdotdot = false, $indexdatabase = 1, $nolog = 0)
1167{
1168	global $db, $conf, $user, $langs;
1169	global $hookmanager;
1170
1171	// Load translation files required by the page
1172	$langs->loadLangs(array('other', 'errors'));
1173
1174	if (empty($nolog)) {
1175		dol_syslog("dol_delete_file file=".$file." disableglob=".$disableglob." nophperrors=".$nophperrors." nohook=".$nohook);
1176	}
1177
1178	// Security:
1179	// We refuse transversal using .. and pipes into filenames.
1180	if ((!$allowdotdot && preg_match('/\.\./', $file)) || preg_match('/[<>|]/', $file)) {
1181		dol_syslog("Refused to delete file ".$file, LOG_WARNING);
1182		return false;
1183	}
1184
1185	$reshook = 0;
1186	if (empty($nohook)) {
1187		$hookmanager->initHooks(array('fileslib'));
1188
1189		$parameters = array(
1190			'GET' => $_GET,
1191			'file' => $file,
1192			'disableglob'=> $disableglob,
1193			'nophperrors' => $nophperrors
1194		);
1195		$reshook = $hookmanager->executeHooks('deleteFile', $parameters, $object);
1196	}
1197
1198	if (empty($nohook) && $reshook != 0) { // reshook = 0 to do standard actions, 1 = ok and replace, -1 = ko
1199		dol_syslog("reshook=".$reshook);
1200		if ($reshook < 0) {
1201			return false;
1202		}
1203		return true;
1204	} else {
1205		$file_osencoded = dol_osencode($file); // New filename encoded in OS filesystem encoding charset
1206		if (empty($disableglob) && !empty($file_osencoded)) {
1207			$ok = true;
1208			$globencoded = str_replace('[', '\[', $file_osencoded);
1209			$globencoded = str_replace(']', '\]', $globencoded);
1210			$listofdir = glob($globencoded);
1211			if (!empty($listofdir) && is_array($listofdir)) {
1212				foreach ($listofdir as $filename) {
1213					if ($nophperrors) {
1214						$ok = @unlink($filename);
1215					} else {
1216						$ok = unlink($filename);
1217					}
1218
1219					// If it fails and it is because of the missing write permission on parent dir
1220					if (!$ok && file_exists(dirname($filename)) && !(fileperms(dirname($filename)) & 0200)) {
1221						dol_syslog("Error in deletion, but parent directory exists with no permission to write, we try to change permission on parent directory and retry...", LOG_DEBUG);
1222						@chmod(dirname($filename), fileperms(dirname($filename)) | 0200);
1223						// Now we retry deletion
1224						if ($nophperrors) {
1225							$ok = @unlink($filename);
1226						} else {
1227							$ok = unlink($filename);
1228						}
1229					}
1230
1231					if ($ok) {
1232						if (empty($nolog)) {
1233							dol_syslog("Removed file ".$filename, LOG_DEBUG);
1234						}
1235
1236						// Delete entry into ecm database
1237						$rel_filetodelete = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $filename);
1238						if (!preg_match('/(\/temp\/|\/thumbs\/|\.meta$)/', $rel_filetodelete)) {     // If not a tmp file
1239							if (is_object($db) && $indexdatabase) {		// $db may not be defined when lib is in a context with define('NOREQUIREDB',1)
1240								$rel_filetodelete = preg_replace('/^[\\/]/', '', $rel_filetodelete);
1241								$rel_filetodelete = preg_replace('/\.noexe$/', '', $rel_filetodelete);
1242
1243								dol_syslog("Try to remove also entries in database for full relative path = ".$rel_filetodelete, LOG_DEBUG);
1244								include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1245								$ecmfile = new EcmFiles($db);
1246								$result = $ecmfile->fetch(0, '', $rel_filetodelete);
1247								if ($result >= 0 && $ecmfile->id > 0) {
1248									$result = $ecmfile->delete($user);
1249								}
1250								if ($result < 0) {
1251									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
1252								}
1253							}
1254						}
1255					} else {
1256						dol_syslog("Failed to remove file ".$filename, LOG_WARNING);
1257						// TODO Failure to remove can be because file was already removed or because of permission
1258						// If error because it does not exists, we should return true, and we should return false if this is a permission problem
1259					}
1260				}
1261			} else {
1262				dol_syslog("No files to delete found", LOG_DEBUG);
1263			}
1264		} else {
1265			$ok = false;
1266			if ($nophperrors) {
1267				$ok = @unlink($file_osencoded);
1268			} else {
1269				$ok = unlink($file_osencoded);
1270			}
1271			if ($ok) {
1272				if (empty($nolog)) {
1273					dol_syslog("Removed file ".$file_osencoded, LOG_DEBUG);
1274				}
1275			} else {
1276				dol_syslog("Failed to remove file ".$file_osencoded, LOG_WARNING);
1277			}
1278		}
1279
1280		return $ok;
1281	}
1282}
1283
1284/**
1285 *  Remove a directory (not recursive, so content must be empty).
1286 *  If directory is not empty, return false
1287 *
1288 *  @param	string	$dir            Directory to delete
1289 *  @param  int		$nophperrors    Disable all PHP output errors
1290 *  @return boolean         		True if success, false if error
1291 *  @see dol_delete_file() dolCopyDir()
1292 */
1293function dol_delete_dir($dir, $nophperrors = 0)
1294{
1295	// Security:
1296	// We refuse transversal using .. and pipes into filenames.
1297	if (preg_match('/\.\./', $dir) || preg_match('/[<>|]/', $dir)) {
1298		dol_syslog("Refused to delete dir ".$dir, LOG_WARNING);
1299		return false;
1300	}
1301
1302	$dir_osencoded = dol_osencode($dir);
1303	return ($nophperrors ? @rmdir($dir_osencoded) : rmdir($dir_osencoded));
1304}
1305
1306/**
1307 *  Remove a directory $dir and its subdirectories (or only files and subdirectories)
1308 *
1309 *  @param	string	$dir            Dir to delete
1310 *  @param  int		$count          Counter to count nb of elements found to delete
1311 *  @param  int		$nophperrors    Disable all PHP output errors
1312 *  @param	int		$onlysub		Delete only files and subdir, not main directory
1313 *  @param  int		$countdeleted   Counter to count nb of elements found really deleted
1314 *  @param	int		$indexdatabase	Try to remove also index entries.
1315 *  @param	int		$nolog			Disable log files (too verbose when making recursive directories)
1316 *  @return int             		Number of files and directory we try to remove. NB really removed is returned into var by reference $countdeleted.
1317 */
1318function dol_delete_dir_recursive($dir, $count = 0, $nophperrors = 0, $onlysub = 0, &$countdeleted = 0, $indexdatabase = 1, $nolog = 0)
1319{
1320	if (empty($nolog)) {
1321		dol_syslog("functions.lib:dol_delete_dir_recursive ".$dir, LOG_DEBUG);
1322	}
1323	if (dol_is_dir($dir)) {
1324		$dir_osencoded = dol_osencode($dir);
1325		if ($handle = opendir("$dir_osencoded")) {
1326			while (false !== ($item = readdir($handle))) {
1327				if (!utf8_check($item)) {
1328					$item = utf8_encode($item); // should be useless
1329				}
1330
1331				if ($item != "." && $item != "..") {
1332					if (is_dir(dol_osencode("$dir/$item")) && !is_link(dol_osencode("$dir/$item"))) {
1333						$count = dol_delete_dir_recursive("$dir/$item", $count, $nophperrors, 0, $countdeleted, $indexdatabase, $nolog);
1334					} else {
1335						$result = dol_delete_file("$dir/$item", 1, $nophperrors, 0, null, false, $indexdatabase, $nolog);
1336						$count++;
1337						if ($result) {
1338							$countdeleted++;
1339						}
1340						//else print 'Error on '.$item."\n";
1341					}
1342				}
1343			}
1344			closedir($handle);
1345
1346			// Delete also the main directory
1347			if (empty($onlysub)) {
1348				$result = dol_delete_dir($dir, $nophperrors);
1349				$count++;
1350				if ($result) {
1351					$countdeleted++;
1352				}
1353				//else print 'Error on '.$dir."\n";
1354			}
1355		}
1356	}
1357
1358	return $count;
1359}
1360
1361
1362/**
1363 *  Delete all preview files linked to object instance.
1364 *  Note that preview image of PDF files is generated when required, by dol_banner_tab() for example.
1365 *
1366 *  @param	object	$object		Object to clean
1367 *  @return	int					0 if error, 1 if OK
1368 *  @see dol_convert_file()
1369 */
1370function dol_delete_preview($object)
1371{
1372	global $langs, $conf;
1373
1374	// Define parent dir of elements
1375	$element = $object->element;
1376
1377	if ($object->element == 'order_supplier') {
1378		$dir = $conf->fournisseur->commande->dir_output;
1379	} elseif ($object->element == 'invoice_supplier') {
1380		$dir = $conf->fournisseur->facture->dir_output;
1381	} elseif ($object->element == 'project') {
1382		$dir = $conf->projet->dir_output;
1383	} elseif ($object->element == 'shipping') {
1384		$dir = $conf->expedition->dir_output.'/sending';
1385	} elseif ($object->element == 'delivery') {
1386		$dir = $conf->expedition->dir_output.'/receipt';
1387	} elseif ($object->element == 'fichinter') {
1388		$dir = $conf->ficheinter->dir_output;
1389	} else {
1390		$dir = empty($conf->$element->dir_output) ? '' : $conf->$element->dir_output;
1391	}
1392
1393	if (empty($dir)) {
1394		return 'ErrorObjectNoSupportedByFunction';
1395	}
1396
1397	$refsan = dol_sanitizeFileName($object->ref);
1398	$dir = $dir."/".$refsan;
1399	$filepreviewnew = $dir."/".$refsan.".pdf_preview.png";
1400	$filepreviewnewbis = $dir."/".$refsan.".pdf_preview-0.png";
1401	$filepreviewold = $dir."/".$refsan.".pdf.png";
1402
1403	// For new preview files
1404	if (file_exists($filepreviewnew) && is_writable($filepreviewnew)) {
1405		if (!dol_delete_file($filepreviewnew, 1)) {
1406			$object->error = $langs->trans("ErrorFailedToDeleteFile", $filepreviewnew);
1407			return 0;
1408		}
1409	}
1410	if (file_exists($filepreviewnewbis) && is_writable($filepreviewnewbis)) {
1411		if (!dol_delete_file($filepreviewnewbis, 1)) {
1412			$object->error = $langs->trans("ErrorFailedToDeleteFile", $filepreviewnewbis);
1413			return 0;
1414		}
1415	}
1416	// For old preview files
1417	if (file_exists($filepreviewold) && is_writable($filepreviewold)) {
1418		if (!dol_delete_file($filepreviewold, 1)) {
1419			$object->error = $langs->trans("ErrorFailedToDeleteFile", $filepreviewold);
1420			return 0;
1421		}
1422	} else {
1423		$multiple = $filepreviewold.".";
1424		for ($i = 0; $i < 20; $i++) {
1425			$preview = $multiple.$i;
1426
1427			if (file_exists($preview) && is_writable($preview)) {
1428				if (!dol_delete_file($preview, 1)) {
1429					$object->error = $langs->trans("ErrorFailedToOpenFile", $preview);
1430					return 0;
1431				}
1432			}
1433		}
1434	}
1435
1436	return 1;
1437}
1438
1439/**
1440 *	Create a meta file with document file into same directory.
1441 *	This make "grep" search possible.
1442 *  This feature to generate the meta file is enabled only if option MAIN_DOC_CREATE_METAFILE is set.
1443 *
1444 *	@param	CommonObject	$object		Object
1445 *	@return	int							0 if do nothing, >0 if we update meta file too, <0 if KO
1446 */
1447function dol_meta_create($object)
1448{
1449	global $conf;
1450
1451	// Create meta file
1452	if (empty($conf->global->MAIN_DOC_CREATE_METAFILE)) {
1453		return 0; // By default, no metafile.
1454	}
1455
1456	// Define parent dir of elements
1457	$element = $object->element;
1458
1459	if ($object->element == 'order_supplier') {
1460		$dir = $conf->fournisseur->dir_output.'/commande';
1461	} elseif ($object->element == 'invoice_supplier') {
1462		$dir = $conf->fournisseur->dir_output.'/facture';
1463	} elseif ($object->element == 'project') {
1464		$dir = $conf->projet->dir_output;
1465	} elseif ($object->element == 'shipping') {
1466		$dir = $conf->expedition->dir_output.'/sending';
1467	} elseif ($object->element == 'delivery') {
1468		$dir = $conf->expedition->dir_output.'/receipt';
1469	} elseif ($object->element == 'fichinter') {
1470		$dir = $conf->ficheinter->dir_output;
1471	} else {
1472		$dir = empty($conf->$element->dir_output) ? '' : $conf->$element->dir_output;
1473	}
1474
1475	if ($dir) {
1476		$object->fetch_thirdparty();
1477
1478		$objectref = dol_sanitizeFileName($object->ref);
1479		$dir = $dir."/".$objectref;
1480		$file = $dir."/".$objectref.".meta";
1481
1482		if (!is_dir($dir)) {
1483			dol_mkdir($dir);
1484		}
1485
1486		if (is_dir($dir)) {
1487			$nblines = count($object->lines);
1488			$client = $object->thirdparty->name." ".$object->thirdparty->address." ".$object->thirdparty->zip." ".$object->thirdparty->town;
1489			$meta = "REFERENCE=\"".$object->ref."\"
1490			DATE=\"" . dol_print_date($object->date, '')."\"
1491			NB_ITEMS=\"" . $nblines."\"
1492			CLIENT=\"" . $client."\"
1493			AMOUNT_EXCL_TAX=\"" . $object->total_ht."\"
1494			AMOUNT=\"" . $object->total_ttc."\"\n";
1495
1496			for ($i = 0; $i < $nblines; $i++) {
1497				//Pour les articles
1498				$meta .= "ITEM_".$i."_QUANTITY=\"".$object->lines[$i]->qty."\"
1499				ITEM_" . $i."_AMOUNT_WO_TAX=\"".$object->lines[$i]->total_ht."\"
1500				ITEM_" . $i."_VAT=\"".$object->lines[$i]->tva_tx."\"
1501				ITEM_" . $i."_DESCRIPTION=\"".str_replace("\r\n", "", nl2br($object->lines[$i]->desc))."\"
1502				";
1503			}
1504		}
1505
1506		$fp = fopen($file, "w");
1507		fputs($fp, $meta);
1508		fclose($fp);
1509		if (!empty($conf->global->MAIN_UMASK)) {
1510			@chmod($file, octdec($conf->global->MAIN_UMASK));
1511		}
1512
1513			return 1;
1514	} else {
1515		dol_syslog('FailedToDetectDirInDolMetaCreateFor'.$object->element, LOG_WARNING);
1516	}
1517
1518	return 0;
1519}
1520
1521
1522
1523/**
1524 * Scan a directory and init $_SESSION to manage uploaded files with list of all found files.
1525 * Note: Only email module seems to use this. Other feature initialize the $_SESSION doing $formmail->clear_attached_files(); $formmail->add_attached_files()
1526 *
1527 * @param	string	$pathtoscan				Path to scan
1528 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1529 * @return	void
1530 */
1531function dol_init_file_process($pathtoscan = '', $trackid = '')
1532{
1533	$listofpaths = array();
1534	$listofnames = array();
1535	$listofmimes = array();
1536
1537	if ($pathtoscan) {
1538		$listoffiles = dol_dir_list($pathtoscan, 'files');
1539		foreach ($listoffiles as $key => $val) {
1540			$listofpaths[] = $val['fullname'];
1541			$listofnames[] = $val['name'];
1542			$listofmimes[] = dol_mimetype($val['name']);
1543		}
1544	}
1545	$keytoavoidconflict = empty($trackid) ? '' : '-'.$trackid;
1546	$_SESSION["listofpaths".$keytoavoidconflict] = join(';', $listofpaths);
1547	$_SESSION["listofnames".$keytoavoidconflict] = join(';', $listofnames);
1548	$_SESSION["listofmimes".$keytoavoidconflict] = join(';', $listofmimes);
1549}
1550
1551
1552/**
1553 * Get and save an upload file (for example after submitting a new file a mail form). Database index of file is also updated if donotupdatesession is set.
1554 * All information used are in db, conf, langs, user and _FILES.
1555 * Note: This function can be used only into a HTML page context.
1556 *
1557 * @param	string	$upload_dir				Directory where to store uploaded file (note: used to forge $destpath = $upload_dir + filename)
1558 * @param	int		$allowoverwrite			1=Allow overwrite existing file
1559 * @param	int		$donotupdatesession		1=Do no edit _SESSION variable but update database index. 0=Update _SESSION and not database index. -1=Do not update SESSION neither db.
1560 * @param	string	$varfiles				_FILES var name
1561 * @param	string	$savingdocmask			Mask to use to define output filename. For example 'XXXXX-__YYYYMMDD__-__file__'
1562 * @param	string	$link					Link to add (to add a link instead of a file)
1563 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1564 * @param	int		$generatethumbs			1=Generate also thumbs for uploaded image files
1565 * @param   Object  $object                 Object used to set 'src_object_*' fields
1566 * @return	int                             <=0 if KO, >0 if OK
1567 * @see dol_remove_file_process()
1568 */
1569function dol_add_file_process($upload_dir, $allowoverwrite = 0, $donotupdatesession = 0, $varfiles = 'addedfile', $savingdocmask = '', $link = null, $trackid = '', $generatethumbs = 1, $object = null)
1570{
1571	global $db, $user, $conf, $langs;
1572
1573	$res = 0;
1574
1575	if (!empty($_FILES[$varfiles])) { // For view $_FILES[$varfiles]['error']
1576		dol_syslog('dol_add_file_process upload_dir='.$upload_dir.' allowoverwrite='.$allowoverwrite.' donotupdatesession='.$donotupdatesession.' savingdocmask='.$savingdocmask, LOG_DEBUG);
1577
1578		$result = dol_mkdir($upload_dir);
1579		//      var_dump($result);exit;
1580		if ($result >= 0) {
1581			$TFile = $_FILES[$varfiles];
1582			if (!is_array($TFile['name'])) {
1583				foreach ($TFile as $key => &$val) {
1584					$val = array($val);
1585				}
1586			}
1587
1588			$nbfile = count($TFile['name']);
1589			$nbok = 0;
1590			for ($i = 0; $i < $nbfile; $i++) {
1591				if (empty($TFile['name'][$i])) {
1592					continue; // For example, when submitting a form with no file name
1593				}
1594
1595				// Define $destfull (path to file including filename) and $destfile (only filename)
1596				$destfull = $upload_dir."/".$TFile['name'][$i];
1597				$destfile = $TFile['name'][$i];
1598				$destfilewithoutext = preg_replace('/\.[^\.]+$/', '', $destfile);
1599
1600				if ($savingdocmask && strpos($savingdocmask, $destfilewithoutext) !== 0) {
1601					$destfull = $upload_dir."/".preg_replace('/__file__/', $TFile['name'][$i], $savingdocmask);
1602					$destfile = preg_replace('/__file__/', $TFile['name'][$i], $savingdocmask);
1603				}
1604
1605				$filenameto = basename($destfile);
1606				if (preg_match('/^\./', $filenameto)) {
1607					$langs->load("errors"); // key must be loaded because we can't rely on loading during output, we need var substitution to be done now.
1608					setEventMessages($langs->trans("ErrorFilenameCantStartWithDot", $filenameto), null, 'errors');
1609					break;
1610				}
1611
1612				// dol_sanitizeFileName the file name and lowercase extension
1613				$info = pathinfo($destfull);
1614				$destfull = $info['dirname'].'/'.dol_sanitizeFileName($info['filename'].($info['extension'] != '' ? ('.'.strtolower($info['extension'])) : ''));
1615				$info = pathinfo($destfile);
1616
1617				$destfile = dol_sanitizeFileName($info['filename'].($info['extension'] != '' ? ('.'.strtolower($info['extension'])) : ''));
1618
1619				// We apply dol_string_nohtmltag also to clean file names (this remove duplicate spaces) because
1620				// this function is also applied when we rename and when we make try to download file (by the GETPOST(filename, 'alphanohtml') call).
1621				$destfile = dol_string_nohtmltag($destfile);
1622				$destfull = dol_string_nohtmltag($destfull);
1623
1624				// Move file from temp directory to final directory. A .noexe may also be appended on file name.
1625				$resupload = dol_move_uploaded_file($TFile['tmp_name'][$i], $destfull, $allowoverwrite, 0, $TFile['error'][$i], 0, $varfiles, $upload_dir);
1626
1627				if (is_numeric($resupload) && $resupload > 0) {   // $resupload can be 'ErrorFileAlreadyExists'
1628					global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini;
1629
1630					include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1631
1632					// Generate thumbs.
1633					if ($generatethumbs) {
1634						if (image_format_supported($destfull) == 1) {
1635							// Create thumbs
1636							// We can't use $object->addThumbs here because there is no $object known
1637
1638							// Used on logon for example
1639							$imgThumbSmall = vignette($destfull, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
1640							// Create mini thumbs for image (Ratio is near 16/9)
1641							// Used on menu or for setup page for example
1642							$imgThumbMini = vignette($destfull, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
1643						}
1644					}
1645
1646					// Update session
1647					if (empty($donotupdatesession)) {
1648						include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
1649						$formmail = new FormMail($db);
1650						$formmail->trackid = $trackid;
1651						$formmail->add_attached_files($destfull, $destfile, $TFile['type'][$i]);
1652					}
1653
1654					// Update index table of files (llx_ecm_files)
1655					if ($donotupdatesession == 1) {
1656						$result = addFileIntoDatabaseIndex($upload_dir, basename($destfile).($resupload == 2 ? '.noexe' : ''), $TFile['name'][$i], 'uploaded', 0, $object);
1657						if ($result < 0) {
1658							if ($allowoverwrite) {
1659								// Do not show error message. We can have an error due to DB_ERROR_RECORD_ALREADY_EXISTS
1660							} else {
1661								setEventMessages('WarningFailedToAddFileIntoDatabaseIndex', '', 'warnings');
1662							}
1663						}
1664					}
1665
1666					$nbok++;
1667				} else {
1668					$langs->load("errors");
1669					if ($resupload < 0) {	// Unknown error
1670						setEventMessages($langs->trans("ErrorFileNotUploaded"), null, 'errors');
1671					} elseif (preg_match('/ErrorFileIsInfectedWithAVirus/', $resupload)) {	// Files infected by a virus
1672						setEventMessages($langs->trans("ErrorFileIsInfectedWithAVirus"), null, 'errors');
1673					} else // Known error
1674					{
1675						setEventMessages($langs->trans($resupload), null, 'errors');
1676					}
1677				}
1678			}
1679			if ($nbok > 0) {
1680				$res = 1;
1681				setEventMessages($langs->trans("FileTransferComplete"), null, 'mesgs');
1682			}
1683		} else {
1684			setEventMessages($langs->trans("ErrorFailedToCreateDir", $upload_dir), null, 'errors');
1685		}
1686	} elseif ($link) {
1687		require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
1688		$linkObject = new Link($db);
1689		$linkObject->entity = $conf->entity;
1690		$linkObject->url = $link;
1691		$linkObject->objecttype = GETPOST('objecttype', 'alpha');
1692		$linkObject->objectid = GETPOST('objectid', 'int');
1693		$linkObject->label = GETPOST('label', 'alpha');
1694		$res = $linkObject->create($user);
1695		$langs->load('link');
1696		if ($res > 0) {
1697			setEventMessages($langs->trans("LinkComplete"), null, 'mesgs');
1698		} else {
1699			setEventMessages($langs->trans("ErrorFileNotLinked"), null, 'errors');
1700		}
1701	} else {
1702		$langs->load("errors");
1703		setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("File")), null, 'errors');
1704	}
1705
1706	return $res;
1707}
1708
1709
1710/**
1711 * Remove an uploaded file (for example after submitting a new file a mail form).
1712 * All information used are in db, conf, langs, user and _FILES.
1713 *
1714 * @param	int		$filenb					File nb to delete
1715 * @param	int		$donotupdatesession		-1 or 1 = Do not update _SESSION variable
1716 * @param   int		$donotdeletefile        1=Do not delete physically file
1717 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1718 * @return	void
1719 * @see dol_add_file_process()
1720 */
1721function dol_remove_file_process($filenb, $donotupdatesession = 0, $donotdeletefile = 1, $trackid = '')
1722{
1723	global $db, $user, $conf, $langs, $_FILES;
1724
1725	$keytodelete = $filenb;
1726	$keytodelete--;
1727
1728	$listofpaths = array();
1729	$listofnames = array();
1730	$listofmimes = array();
1731	$keytoavoidconflict = empty($trackid) ? '' : '-'.$trackid;
1732	if (!empty($_SESSION["listofpaths".$keytoavoidconflict])) {
1733		$listofpaths = explode(';', $_SESSION["listofpaths".$keytoavoidconflict]);
1734	}
1735	if (!empty($_SESSION["listofnames".$keytoavoidconflict])) {
1736		$listofnames = explode(';', $_SESSION["listofnames".$keytoavoidconflict]);
1737	}
1738	if (!empty($_SESSION["listofmimes".$keytoavoidconflict])) {
1739		$listofmimes = explode(';', $_SESSION["listofmimes".$keytoavoidconflict]);
1740	}
1741
1742	if ($keytodelete >= 0) {
1743		$pathtodelete = $listofpaths[$keytodelete];
1744		$filetodelete = $listofnames[$keytodelete];
1745		if (empty($donotdeletefile)) {
1746			$result = dol_delete_file($pathtodelete, 1); // The delete of ecm database is inside the function dol_delete_file
1747		} else {
1748			$result = 0;
1749		}
1750		if ($result >= 0) {
1751			if (empty($donotdeletefile)) {
1752				$langs->load("other");
1753				setEventMessages($langs->trans("FileWasRemoved", $filetodelete), null, 'mesgs');
1754			}
1755			if (empty($donotupdatesession)) {
1756				include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
1757				$formmail = new FormMail($db);
1758				$formmail->trackid = $trackid;
1759				$formmail->remove_attached_files($keytodelete);
1760			}
1761		}
1762	}
1763}
1764
1765
1766/**
1767 *  Add a file into database index.
1768 *  Called by dol_add_file_process when uploading a file and on other cases.
1769 *  See also commonGenerateDocument that also add/update database index when a file is generated.
1770 *
1771 *  @param      string	$dir			Directory name (full real path without ending /)
1772 *  @param		string	$file			File name (May end with '.noexe')
1773 *  @param		string	$fullpathorig	Full path of origin for file (can be '')
1774 *  @param		string	$mode			How file was created ('uploaded', 'generated', ...)
1775 *  @param		int		$setsharekey	Set also the share key
1776 *  @param      Object  $object         Object used to set 'src_object_*' fields
1777 *	@return		int						<0 if KO, 0 if nothing done, >0 if OK
1778 */
1779function addFileIntoDatabaseIndex($dir, $file, $fullpathorig = '', $mode = 'uploaded', $setsharekey = 0, $object = null)
1780{
1781	global $db, $user;
1782
1783	$result = 0;
1784
1785	$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
1786
1787	if (!preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir)) {     // If not a tmp dir
1788		$filename = basename(preg_replace('/\.noexe$/', '', $file));
1789		$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
1790		$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
1791
1792		include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1793		$ecmfile = new EcmFiles($db);
1794		$ecmfile->filepath = $rel_dir;
1795		$ecmfile->filename = $filename;
1796		$ecmfile->label = md5_file(dol_osencode($dir.'/'.$file)); // MD5 of file content
1797		$ecmfile->fullpath_orig = $fullpathorig;
1798		$ecmfile->gen_or_uploaded = $mode;
1799		$ecmfile->description = ''; // indexed content
1800		$ecmfile->keywords = ''; // keyword content
1801
1802		if (is_object($object) && $object->id > 0) {
1803			$ecmfile->src_object_id = $object->id;
1804			$ecmfile->src_object_type = $object->table_element;
1805		}
1806
1807		if ($setsharekey) {
1808			require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1809			$ecmfile->share = getRandomPassword(true);
1810		}
1811
1812		$result = $ecmfile->create($user);
1813		if ($result < 0) {
1814			dol_syslog($ecmfile->error);
1815		}
1816	}
1817
1818	return $result;
1819}
1820
1821/**
1822 *  Delete files into database index using search criterias.
1823 *
1824 *  @param      string	$dir			Directory name (full real path without ending /)
1825 *  @param		string	$file			File name
1826 *  @param		string	$mode			How file was created ('uploaded', 'generated', ...)
1827 *	@return		int						<0 if KO, 0 if nothing done, >0 if OK
1828 */
1829function deleteFilesIntoDatabaseIndex($dir, $file, $mode = 'uploaded')
1830{
1831	global $conf, $db, $user;
1832
1833	$error = 0;
1834
1835	if (empty($dir)) {
1836		dol_syslog("deleteFilesIntoDatabaseIndex: dir parameter can't be empty", LOG_ERR);
1837		return -1;
1838	}
1839
1840	$db->begin();
1841
1842	$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
1843
1844	$filename = basename($file);
1845	$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
1846	$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
1847
1848	if (!$error) {
1849		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'ecm_files';
1850		$sql .= ' WHERE entity = '.$conf->entity;
1851		$sql .= " AND filepath = '".$db->escape($rel_dir)."'";
1852		if ($file) {
1853			$sql .= " AND filename = '".$db->escape($file)."'";
1854		}
1855		if ($mode) {
1856			$sql .= " AND gen_or_uploaded = '".$db->escape($mode)."'";
1857		}
1858
1859		$resql = $db->query($sql);
1860		if (!$resql) {
1861			$error++;
1862			dol_syslog(__METHOD__.' '.$db->lasterror(), LOG_ERR);
1863		}
1864	}
1865
1866	// Commit or rollback
1867	if ($error) {
1868		$db->rollback();
1869		return -1 * $error;
1870	} else {
1871		$db->commit();
1872		return 1;
1873	}
1874}
1875
1876
1877/**
1878 * 	Convert an image file or a PDF into another image format.
1879 *  This need Imagick php extension. You can use dol_imageResizeOrCrop() for a function that need GD.
1880 *
1881 *  @param	string	$fileinput  Input file name
1882 *  @param  string	$ext        Format of target file (It is also extension added to file if fileoutput is not provided).
1883 *  @param	string	$fileoutput	Output filename
1884 *  @param  string  $page       Page number if we convert a PDF into png
1885 *  @return	int					<0 if KO, 0=Nothing done, >0 if OK
1886 *  @see dol_imageResizeOrCrop()
1887 */
1888function dol_convert_file($fileinput, $ext = 'png', $fileoutput = '', $page = '')
1889{
1890	global $langs;
1891	if (class_exists('Imagick')) {
1892		$image = new Imagick();
1893		try {
1894			$filetoconvert = $fileinput.(($page != '') ? '['.$page.']' : '');
1895			//var_dump($filetoconvert);
1896			$ret = $image->readImage($filetoconvert);
1897		} catch (Exception $e) {
1898			$ext = pathinfo($fileinput, PATHINFO_EXTENSION);
1899			dol_syslog("Failed to read image using Imagick (Try to install package 'apt-get install php-imagick ghostscript' and check there is no policy to disable ".$ext." convertion in /etc/ImageMagick*/policy.xml): ".$e->getMessage(), LOG_WARNING);
1900			return 0;
1901		}
1902		if ($ret) {
1903			$ret = $image->setImageFormat($ext);
1904			if ($ret) {
1905				if (empty($fileoutput)) {
1906					$fileoutput = $fileinput.".".$ext;
1907				}
1908
1909				$count = $image->getNumberImages();
1910
1911				if (!dol_is_file($fileoutput) || is_writeable($fileoutput)) {
1912					try {
1913						$ret = $image->writeImages($fileoutput, true);
1914					} catch (Exception $e) {
1915						dol_syslog($e->getMessage(), LOG_WARNING);
1916					}
1917				} else {
1918					dol_syslog("Warning: Failed to write cache preview file '.$fileoutput.'. Check permission on file/dir", LOG_ERR);
1919				}
1920				if ($ret) {
1921					return $count;
1922				} else {
1923					return -3;
1924				}
1925			} else {
1926				return -2;
1927			}
1928		} else {
1929			return -1;
1930		}
1931	} else {
1932		return 0;
1933	}
1934}
1935
1936
1937/**
1938 * Compress a file.
1939 * An error string may be returned into parameters.
1940 *
1941 * @param 	string	$inputfile		Source file name
1942 * @param 	string	$outputfile		Target file name
1943 * @param 	string	$mode			'gz' or 'bz' or 'zip'
1944 * @param	string	$errorstring	Error string
1945 * @return	int						<0 if KO, >0 if OK
1946 */
1947function dol_compress_file($inputfile, $outputfile, $mode = "gz", &$errorstring = null)
1948{
1949	global $conf;
1950
1951	$foundhandler = 0;
1952
1953	try {
1954		dol_syslog("dol_compress_file mode=".$mode." inputfile=".$inputfile." outputfile=".$outputfile);
1955
1956		$data = implode("", file(dol_osencode($inputfile)));
1957		if ($mode == 'gz') {
1958			$foundhandler = 1;
1959			$compressdata = gzencode($data, 9);
1960		} elseif ($mode == 'bz') {
1961			$foundhandler = 1;
1962			$compressdata = bzcompress($data, 9);
1963		} elseif ($mode == 'zip') {
1964			if (class_exists('ZipArchive') && !empty($conf->global->MAIN_USE_ZIPARCHIVE_FOR_ZIP_COMPRESS)) {
1965				$foundhandler = 1;
1966
1967				$rootPath = realpath($inputfile);
1968
1969				dol_syslog("Class ZipArchive is set so we zip using ZipArchive to zip into ".$outputfile.' rootPath='.$rootPath);
1970				$zip = new ZipArchive;
1971
1972				if ($zip->open($outputfile, ZipArchive::CREATE) !== true) {
1973					$errorstring = "dol_compress_file failure - Failed to open file ".$outputfile."\n";
1974					dol_syslog($errorstring, LOG_ERR);
1975
1976					global $errormsg;
1977					$errormsg = $errorstring;
1978
1979					return -6;
1980				}
1981
1982				// Create recursive directory iterator
1983				/** @var SplFileInfo[] $files */
1984				$files = new RecursiveIteratorIterator(
1985					new RecursiveDirectoryIterator($rootPath),
1986					RecursiveIteratorIterator::LEAVES_ONLY
1987				);
1988
1989				foreach ($files as $name => $file) {
1990					// Skip directories (they would be added automatically)
1991					if (!$file->isDir()) {
1992						// Get real and relative path for current file
1993						$filePath = $file->getRealPath();
1994						$relativePath = substr($filePath, strlen($rootPath) + 1);
1995
1996						// Add current file to archive
1997						$zip->addFile($filePath, $relativePath);
1998					}
1999				}
2000
2001				// Zip archive will be created only after closing object
2002				$zip->close();
2003
2004				dol_syslog("dol_compress_file success - ".count($zip->numFiles)." files");
2005				return 1;
2006			}
2007
2008			if (defined('ODTPHP_PATHTOPCLZIP')) {
2009				$foundhandler = 1;
2010
2011				include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
2012				$archive = new PclZip($outputfile);
2013				$result = $archive->add($inputfile, PCLZIP_OPT_REMOVE_PATH, dirname($inputfile));
2014
2015				if ($result === 0) {
2016					global $errormsg;
2017					$errormsg = $archive->errorInfo(true);
2018
2019					if ($archive->errorCode() == PCLZIP_ERR_WRITE_OPEN_FAIL) {
2020						$errorstring = "PCLZIP_ERR_WRITE_OPEN_FAIL";
2021						dol_syslog("dol_compress_file error - archive->errorCode() = PCLZIP_ERR_WRITE_OPEN_FAIL", LOG_ERR);
2022						return -4;
2023					}
2024
2025					$errorstring = "dol_compress_file error archive->errorCode = ".$archive->errorCode()." errormsg=".$errormsg;
2026					dol_syslog("dol_compress_file failure - ".$errormsg, LOG_ERR);
2027					return -3;
2028				} else {
2029					dol_syslog("dol_compress_file success - ".count($result)." files");
2030					return 1;
2031				}
2032			}
2033		}
2034
2035		if ($foundhandler) {
2036			$fp = fopen($outputfile, "w");
2037			fwrite($fp, $compressdata);
2038			fclose($fp);
2039			return 1;
2040		} else {
2041			$errorstring = "Try to zip with format ".$mode." with no handler for this format";
2042			dol_syslog($errorstring, LOG_ERR);
2043
2044			global $errormsg;
2045			$errormsg = $errorstring;
2046			return -2;
2047		}
2048	} catch (Exception $e) {
2049		global $langs, $errormsg;
2050		$langs->load("errors");
2051		$errormsg = $langs->trans("ErrorFailedToWriteInDir");
2052
2053		$errorstring = "Failed to open file ".$outputfile;
2054		dol_syslog($errorstring, LOG_ERR);
2055		return -1;
2056	}
2057}
2058
2059/**
2060 * Uncompress a file
2061 *
2062 * @param 	string 	$inputfile		File to uncompress
2063 * @param 	string	$outputdir		Target dir name
2064 * @return 	array					array('error'=>'Error code') or array() if no error
2065 */
2066function dol_uncompress($inputfile, $outputdir)
2067{
2068	global $conf, $langs;
2069
2070	if (defined('ODTPHP_PATHTOPCLZIP') && empty($conf->global->MAIN_USE_ZIPARCHIVE_FOR_ZIP_UNCOMPRESS)) {
2071		dol_syslog("Constant ODTPHP_PATHTOPCLZIP for pclzip library is set to ".ODTPHP_PATHTOPCLZIP.", so we use Pclzip to unzip into ".$outputdir);
2072		include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
2073		$archive = new PclZip($inputfile);
2074
2075		// Extract into outputdir, but only files that match the regex '/^((?!\.\.).)*$/' that means "does not include .."
2076		$result = $archive->extract(PCLZIP_OPT_PATH, $outputdir, PCLZIP_OPT_BY_PREG, '/^((?!\.\.).)*$/');
2077
2078		if (!is_array($result) && $result <= 0) {
2079			return array('error'=>$archive->errorInfo(true));
2080		} else {
2081			$ok = 1;
2082			$errmsg = '';
2083			// Loop on each file to check result for unzipping file
2084			foreach ($result as $key => $val) {
2085				if ($val['status'] == 'path_creation_fail') {
2086					$langs->load("errors");
2087					$ok = 0;
2088					$errmsg = $langs->trans("ErrorFailToCreateDir", $val['filename']);
2089					break;
2090				}
2091			}
2092
2093			if ($ok) {
2094				return array();
2095			} else {
2096				return array('error'=>$errmsg);
2097			}
2098		}
2099	}
2100
2101	if (class_exists('ZipArchive')) {	// Must install php-zip to have it
2102		dol_syslog("Class ZipArchive is set so we unzip using ZipArchive to unzip into ".$outputdir);
2103		$zip = new ZipArchive;
2104		$res = $zip->open($inputfile);
2105		if ($res === true) {
2106			//$zip->extractTo($outputdir.'/');
2107			// We must extract one file at time so we can check that file name does not contains '..' to avoid transversal path of zip built for example using
2108			// python3 path_traversal_archiver.py <Created_file_name> test.zip -l 10 -p tmp/
2109			// with -l is the range of dot to go back in path.
2110			// and path_traversal_archiver.py found at https://github.com/Alamot/code-snippets/blob/master/path_traversal/path_traversal_archiver.py
2111			for ($i = 0; $i < $zip->numFiles; $i++) {
2112				if (preg_match('/\.\./', $zip->getNameIndex($i))) {
2113					dol_syslog("Warning: Try to unzip a file with a transversal path ".$zip->getNameIndex($i), LOG_WARNING);
2114					continue; // Discard the file
2115				}
2116				$zip->extractTo($outputdir.'/', array($zip->getNameIndex($i)));
2117			}
2118
2119			$zip->close();
2120			return array();
2121		} else {
2122			return array('error'=>'ErrUnzipFails');
2123		}
2124	}
2125
2126	return array('error'=>'ErrNoZipEngine');
2127}
2128
2129
2130/**
2131 * Compress a directory and subdirectories into a package file.
2132 *
2133 * @param 	string	$inputdir		Source dir name
2134 * @param 	string	$outputfile		Target file name (output directory must exists and be writable)
2135 * @param 	string	$mode			'zip'
2136 * @param	string	$excludefiles   A regex pattern. For example: '/\.log$|\/temp\//'
2137 * @param	string	$rootdirinzip	Add a root dir level in zip file
2138 * @return	int						<0 if KO, >0 if OK
2139 */
2140function dol_compress_dir($inputdir, $outputfile, $mode = "zip", $excludefiles = '', $rootdirinzip = '')
2141{
2142	$foundhandler = 0;
2143
2144	dol_syslog("Try to zip dir ".$inputdir." into ".$outputfile." mode=".$mode);
2145
2146	if (!dol_is_dir(dirname($outputfile)) || !is_writable(dirname($outputfile))) {
2147		global $langs, $errormsg;
2148		$langs->load("errors");
2149		$errormsg = $langs->trans("ErrorFailedToWriteInDir", $outputfile);
2150		return -3;
2151	}
2152
2153	try {
2154		if ($mode == 'gz') {
2155			$foundhandler = 0;
2156		} elseif ($mode == 'bz') {
2157			$foundhandler = 0;
2158		} elseif ($mode == 'zip') {
2159			/*if (defined('ODTPHP_PATHTOPCLZIP'))
2160			 {
2161			 $foundhandler=0;        // TODO implement this
2162
2163			 include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
2164			 $archive = new PclZip($outputfile);
2165			 $archive->add($inputfile, PCLZIP_OPT_REMOVE_PATH, dirname($inputfile));
2166			 //$archive->add($inputfile);
2167			 return 1;
2168			 }
2169			 else*/
2170			//if (class_exists('ZipArchive') && ! empty($conf->global->MAIN_USE_ZIPARCHIVE_FOR_ZIP_COMPRESS))
2171			if (class_exists('ZipArchive')) {
2172				$foundhandler = 1;
2173
2174				// Initialize archive object
2175				$zip = new ZipArchive();
2176				$result = $zip->open($outputfile, ZipArchive::CREATE | ZipArchive::OVERWRITE);
2177				if (!$result) {
2178					global $langs, $errormsg;
2179					$langs->load("errors");
2180					$errormsg = $langs->trans("ErrorFailedToWriteInFile", $outputfile);
2181					return -4;
2182				}
2183
2184				// Create recursive directory iterator
2185				/** @var SplFileInfo[] $files */
2186				$files = new RecursiveIteratorIterator(
2187					new RecursiveDirectoryIterator($inputdir),
2188					RecursiveIteratorIterator::LEAVES_ONLY
2189				);
2190
2191				foreach ($files as $name => $file) {
2192					// Skip directories (they would be added automatically)
2193					if (!$file->isDir()) {
2194						// Get real and relative path for current file
2195						$filePath = $file->getRealPath();
2196						$relativePath = ($rootdirinzip ? $rootdirinzip.'/' : '').substr($filePath, strlen($inputdir) + 1);
2197
2198						if (empty($excludefiles) || !preg_match($excludefiles, $filePath)) {
2199							// Add current file to archive
2200							$zip->addFile($filePath, $relativePath);
2201						}
2202					}
2203				}
2204
2205				// Zip archive will be created only after closing object
2206				$zip->close();
2207
2208				return 1;
2209			}
2210		}
2211
2212		if (!$foundhandler) {
2213			dol_syslog("Try to zip with format ".$mode." with no handler for this format", LOG_ERR);
2214			return -2;
2215		} else {
2216			return 0;
2217		}
2218	} catch (Exception $e) {
2219		global $langs, $errormsg;
2220		$langs->load("errors");
2221		dol_syslog("Failed to open file ".$outputfile, LOG_ERR);
2222		dol_syslog($e->getMessage(), LOG_ERR);
2223		$errormsg = $langs->trans("ErrorFailedToWriteInDir", $outputfile);
2224		return -1;
2225	}
2226}
2227
2228
2229
2230/**
2231 * Return file(s) into a directory (by default most recent)
2232 *
2233 * @param 	string		$dir			Directory to scan
2234 * @param	string		$regexfilter	Regex filter to restrict list. This regex value must be escaped for '/', since this char is used for preg_match function
2235 * @param	array		$excludefilter  Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.')). This regex value must be escaped for '/', since this char is used for preg_match function
2236 * @param	int			$nohook			Disable all hooks
2237 * @param	int			$mode			0=Return array minimum keys loaded (faster), 1=Force all keys like date and size to be loaded (slower), 2=Force load of date only, 3=Force load of size only
2238 * @return	string						Full path to most recent file
2239 */
2240function dol_most_recent_file($dir, $regexfilter = '', $excludefilter = array('(\.meta|_preview.*\.png)$', '^\.'), $nohook = false, $mode = '')
2241{
2242	$tmparray = dol_dir_list($dir, 'files', 0, $regexfilter, $excludefilter, 'date', SORT_DESC, $mode, $nohook);
2243	return $tmparray[0];
2244}
2245
2246/**
2247 * Security check when accessing to a document (used by document.php, viewimage.php and webservices to get documents).
2248 * TODO Replace code that set $accesallowed by a call to restrictedArea()
2249 *
2250 * @param	string	$modulepart			Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp'). Exemple: 'medias', 'invoice', 'logs', 'tax-vat', ...
2251 * @param	string	$original_file		Relative path with filename, relative to modulepart.
2252 * @param	string	$entity				Restrict onto entity (0=no restriction)
2253 * @param  	User	$fuser				User object (forced)
2254 * @param	string	$refname			Ref of object to check permission for external users (autodetect if not provided)
2255 * @param   string  $mode               Check permission for 'read' or 'write'
2256 * @return	mixed						Array with access information : 'accessallowed' & 'sqlprotectagainstexternals' & 'original_file' (as a full path name)
2257 * @see restrictedArea()
2258 */
2259function dol_check_secure_access_document($modulepart, $original_file, $entity, $fuser = '', $refname = '', $mode = 'read')
2260{
2261	global $conf, $db, $user;
2262	global $dolibarr_main_data_root, $dolibarr_main_document_root_alt;
2263
2264	if (!is_object($fuser)) {
2265		$fuser = $user;
2266	}
2267
2268	if (empty($modulepart)) {
2269		return 'ErrorBadParameter';
2270	}
2271	if (empty($entity)) {
2272		if (empty($conf->multicompany->enabled)) {
2273			$entity = 1;
2274		} else {
2275			$entity = 0;
2276		}
2277	}
2278	// Fix modulepart for backward compatibility
2279	if ($modulepart == 'users') {
2280		$modulepart = 'user';
2281	}
2282	if ($modulepart == 'tva') {
2283		$modulepart = 'tax-vat';
2284	}
2285
2286	//print 'dol_check_secure_access_document modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity;
2287	dol_syslog('dol_check_secure_access_document modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity);
2288
2289	// We define $accessallowed and $sqlprotectagainstexternals
2290	$accessallowed = 0;
2291	$sqlprotectagainstexternals = '';
2292	$ret = array();
2293
2294	// Find the subdirectory name as the reference. For example original_file='10/myfile.pdf' -> refname='10'
2295	if (empty($refname)) {
2296		$refname = basename(dirname($original_file)."/");
2297		if ($refname == 'thumbs') {
2298			// If we get the thumbns directory, we must go one step higher. For example original_file='10/thumbs/myfile_small.jpg' -> refname='10'
2299			$refname = basename(dirname(dirname($original_file))."/");
2300		}
2301	}
2302
2303	// Define possible keys to use for permission check
2304	$lire = 'lire';
2305	$read = 'read';
2306	$download = 'download';
2307	if ($mode == 'write') {
2308		$lire = 'creer';
2309		$read = 'write';
2310		$download = 'upload';
2311	}
2312
2313	// Wrapping for miscellaneous medias files
2314	if ($modulepart == 'medias' && !empty($dolibarr_main_data_root)) {
2315		if (empty($entity) || empty($conf->medias->multidir_output[$entity])) {
2316			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2317		}
2318		$accessallowed = 1;
2319		$original_file = $conf->medias->multidir_output[$entity].'/'.$original_file;
2320	} elseif ($modulepart == 'logs' && !empty($dolibarr_main_data_root)) {
2321		// Wrapping for *.log files, like when used with url http://.../document.php?modulepart=logs&file=dolibarr.log
2322		$accessallowed = ($user->admin && basename($original_file) == $original_file && preg_match('/^dolibarr.*\.log$/', basename($original_file)));
2323		$original_file = $dolibarr_main_data_root.'/'.$original_file;
2324	} elseif ($modulepart == 'doctemplates' && !empty($dolibarr_main_data_root)) {
2325		// Wrapping for *.log files, like when used with url http://.../document.php?modulepart=logs&file=dolibarr.log
2326		$accessallowed = $user->admin;
2327		$original_file = $dolibarr_main_data_root.'/doctemplates/'.$original_file;
2328	} elseif ($modulepart == 'doctemplateswebsite' && !empty($dolibarr_main_data_root)) {
2329		// Wrapping for *.zip files, like when used with url http://.../document.php?modulepart=packages&file=module_myfile.zip
2330		$accessallowed = ($fuser->rights->website->write && preg_match('/\.jpg$/i', basename($original_file)));
2331		$original_file = $dolibarr_main_data_root.'/doctemplates/websites/'.$original_file;
2332	} elseif ($modulepart == 'packages' && !empty($dolibarr_main_data_root)) {
2333		// Wrapping for *.zip files, like when used with url http://.../document.php?modulepart=packages&file=module_myfile.zip
2334		// Dir for custom dirs
2335		$tmp = explode(',', $dolibarr_main_document_root_alt);
2336		$dirins = $tmp[0];
2337
2338		$accessallowed = ($user->admin && preg_match('/^module_.*\.zip$/', basename($original_file)));
2339		$original_file = $dirins.'/'.$original_file;
2340	} elseif ($modulepart == 'mycompany' && !empty($conf->mycompany->dir_output)) {
2341		// Wrapping for some images
2342		$accessallowed = 1;
2343		$original_file = $conf->mycompany->dir_output.'/'.$original_file;
2344	} elseif ($modulepart == 'userphoto' && !empty($conf->user->dir_output)) {
2345		// Wrapping for users photos
2346		$accessallowed = 1;
2347		$original_file = $conf->user->dir_output.'/'.$original_file;
2348	} elseif ($modulepart == 'memberphoto' && !empty($conf->adherent->dir_output)) {
2349		// Wrapping for members photos
2350		$accessallowed = 1;
2351		$original_file = $conf->adherent->dir_output.'/'.$original_file;
2352	} elseif ($modulepart == 'apercufacture' && !empty($conf->facture->multidir_output[$entity])) {
2353		// Wrapping pour les apercu factures
2354		if ($fuser->rights->facture->{$lire}) {
2355			$accessallowed = 1;
2356		}
2357		$original_file = $conf->facture->multidir_output[$entity].'/'.$original_file;
2358	} elseif ($modulepart == 'apercupropal' && !empty($conf->propal->multidir_output[$entity])) {
2359		// Wrapping pour les apercu propal
2360		if ($fuser->rights->propale->{$lire}) {
2361			$accessallowed = 1;
2362		}
2363		$original_file = $conf->propal->multidir_output[$entity].'/'.$original_file;
2364	} elseif ($modulepart == 'apercucommande' && !empty($conf->commande->multidir_output[$entity])) {
2365		// Wrapping pour les apercu commande
2366		if ($fuser->rights->commande->{$lire}) {
2367			$accessallowed = 1;
2368		}
2369		$original_file = $conf->commande->multidir_output[$entity].'/'.$original_file;
2370	} elseif (($modulepart == 'apercufichinter' || $modulepart == 'apercuficheinter') && !empty($conf->ficheinter->dir_output)) {
2371		// Wrapping pour les apercu intervention
2372		if ($fuser->rights->ficheinter->{$lire}) {
2373			$accessallowed = 1;
2374		}
2375		$original_file = $conf->ficheinter->dir_output.'/'.$original_file;
2376	} elseif (($modulepart == 'apercucontract') && !empty($conf->contrat->multidir_output[$entity])) {
2377		// Wrapping pour les apercu contrat
2378		if ($fuser->rights->contrat->{$lire}) {
2379			$accessallowed = 1;
2380		}
2381		$original_file = $conf->contrat->multidir_output[$entity].'/'.$original_file;
2382	} elseif (($modulepart == 'apercusupplier_proposal' || $modulepart == 'apercusupplier_proposal') && !empty($conf->supplier_proposal->dir_output)) {
2383		// Wrapping pour les apercu supplier proposal
2384		if ($fuser->rights->supplier_proposal->{$lire}) {
2385			$accessallowed = 1;
2386		}
2387		$original_file = $conf->supplier_proposal->dir_output.'/'.$original_file;
2388	} elseif (($modulepart == 'apercusupplier_order' || $modulepart == 'apercusupplier_order') && !empty($conf->fournisseur->commande->dir_output)) {
2389		// Wrapping pour les apercu supplier order
2390		if ($fuser->rights->fournisseur->commande->{$lire}) {
2391			$accessallowed = 1;
2392		}
2393		$original_file = $conf->fournisseur->commande->dir_output.'/'.$original_file;
2394	} elseif (($modulepart == 'apercusupplier_invoice' || $modulepart == 'apercusupplier_invoice') && !empty($conf->fournisseur->facture->dir_output)) {
2395		// Wrapping pour les apercu supplier invoice
2396		if ($fuser->rights->fournisseur->facture->{$lire}) {
2397			$accessallowed = 1;
2398		}
2399		$original_file = $conf->fournisseur->facture->dir_output.'/'.$original_file;
2400	} elseif (($modulepart == 'apercuexpensereport') && !empty($conf->expensereport->dir_output)) {
2401		// Wrapping pour les apercu supplier invoice
2402		if ($fuser->rights->expensereport->{$lire}) {
2403			$accessallowed = 1;
2404		}
2405		$original_file = $conf->expensereport->dir_output.'/'.$original_file;
2406	} elseif ($modulepart == 'propalstats' && !empty($conf->propal->multidir_temp[$entity])) {
2407		// Wrapping pour les images des stats propales
2408		if ($fuser->rights->propale->{$lire}) {
2409			$accessallowed = 1;
2410		}
2411		$original_file = $conf->propal->multidir_temp[$entity].'/'.$original_file;
2412	} elseif ($modulepart == 'orderstats' && !empty($conf->commande->dir_temp)) {
2413		// Wrapping pour les images des stats commandes
2414		if ($fuser->rights->commande->{$lire}) {
2415			$accessallowed = 1;
2416		}
2417		$original_file = $conf->commande->dir_temp.'/'.$original_file;
2418	} elseif ($modulepart == 'orderstatssupplier' && !empty($conf->fournisseur->dir_output)) {
2419		if ($fuser->rights->fournisseur->commande->{$lire}) {
2420			$accessallowed = 1;
2421		}
2422		$original_file = $conf->fournisseur->commande->dir_temp.'/'.$original_file;
2423	} elseif ($modulepart == 'billstats' && !empty($conf->facture->dir_temp)) {
2424		// Wrapping pour les images des stats factures
2425		if ($fuser->rights->facture->{$lire}) {
2426			$accessallowed = 1;
2427		}
2428		$original_file = $conf->facture->dir_temp.'/'.$original_file;
2429	} elseif ($modulepart == 'billstatssupplier' && !empty($conf->fournisseur->dir_output)) {
2430		if ($fuser->rights->fournisseur->facture->{$lire}) {
2431			$accessallowed = 1;
2432		}
2433		$original_file = $conf->fournisseur->facture->dir_temp.'/'.$original_file;
2434	} elseif ($modulepart == 'expeditionstats' && !empty($conf->expedition->dir_temp)) {
2435		// Wrapping pour les images des stats expeditions
2436		if ($fuser->rights->expedition->{$lire}) {
2437			$accessallowed = 1;
2438		}
2439		$original_file = $conf->expedition->dir_temp.'/'.$original_file;
2440	} elseif ($modulepart == 'tripsexpensesstats' && !empty($conf->deplacement->dir_temp)) {
2441		// Wrapping pour les images des stats expeditions
2442		if ($fuser->rights->deplacement->{$lire}) {
2443			$accessallowed = 1;
2444		}
2445		$original_file = $conf->deplacement->dir_temp.'/'.$original_file;
2446	} elseif ($modulepart == 'memberstats' && !empty($conf->adherent->dir_temp)) {
2447		// Wrapping pour les images des stats expeditions
2448		if ($fuser->rights->adherent->{$lire}) {
2449			$accessallowed = 1;
2450		}
2451		$original_file = $conf->adherent->dir_temp.'/'.$original_file;
2452	} elseif (preg_match('/^productstats_/i', $modulepart) && !empty($conf->product->dir_temp)) {
2453		// Wrapping pour les images des stats produits
2454		if ($fuser->rights->produit->{$lire} || $fuser->rights->service->{$lire}) {
2455			$accessallowed = 1;
2456		}
2457		$original_file = (!empty($conf->product->multidir_temp[$entity]) ? $conf->product->multidir_temp[$entity] : $conf->service->multidir_temp[$entity]).'/'.$original_file;
2458	} elseif (in_array($modulepart, array('tax', 'tax-vat', 'tva')) && !empty($conf->tax->dir_output)) {
2459		// Wrapping for taxes
2460		if ($fuser->rights->tax->charges->{$lire}) {
2461			$accessallowed = 1;
2462		}
2463		$modulepartsuffix = str_replace('tax-', '', $modulepart);
2464		$original_file = $conf->tax->dir_output.'/'.($modulepartsuffix != 'tax' ? $modulepartsuffix.'/' : '').$original_file;
2465	} elseif ($modulepart == 'actions' && !empty($conf->agenda->dir_output)) {
2466		// Wrapping for events
2467		if ($fuser->rights->agenda->myactions->{$read}) {
2468			$accessallowed = 1;
2469			// If we known $id of project, call checkUserAccessToObject to check permission on the given agenda event on properties and assigned users
2470			if ($refname && !preg_match('/^specimen/i', $original_file)) {
2471				include_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2472				$tmpobject = new ActionComm($db);
2473				$tmpobject->fetch((int) $refname);
2474				$accessallowed = checkUserAccessToObject($user, array('agenda'), $tmpobject->id, 'actioncomm&societe', 'myactions|allactions', 'fk_soc', 'id', '');
2475				if ($user->socid && $tmpobject->socid) {
2476					$accessallowed = checkUserAccessToObject($user, array('societe'), $tmpobject->socid);
2477				}
2478			}
2479		}
2480		$original_file = $conf->agenda->dir_output.'/'.$original_file;
2481	} elseif ($modulepart == 'category' && !empty($conf->categorie->multidir_output[$entity])) {
2482		// Wrapping for categories
2483		if (empty($entity) || empty($conf->categorie->multidir_output[$entity])) {
2484			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2485		}
2486		if ($fuser->rights->categorie->{$lire}) {
2487			$accessallowed = 1;
2488		}
2489		$original_file = $conf->categorie->multidir_output[$entity].'/'.$original_file;
2490	} elseif ($modulepart == 'prelevement' && !empty($conf->prelevement->dir_output)) {
2491		// Wrapping pour les prelevements
2492		if ($fuser->rights->prelevement->bons->{$lire} || preg_match('/^specimen/i', $original_file)) {
2493			$accessallowed = 1;
2494		}
2495		$original_file = $conf->prelevement->dir_output.'/'.$original_file;
2496	} elseif ($modulepart == 'graph_stock' && !empty($conf->stock->dir_temp)) {
2497		// Wrapping pour les graph energie
2498		$accessallowed = 1;
2499		$original_file = $conf->stock->dir_temp.'/'.$original_file;
2500	} elseif ($modulepart == 'graph_fourn' && !empty($conf->fournisseur->dir_temp)) {
2501		// Wrapping pour les graph fournisseurs
2502		$accessallowed = 1;
2503		$original_file = $conf->fournisseur->dir_temp.'/'.$original_file;
2504	} elseif ($modulepart == 'graph_product' && !empty($conf->product->dir_temp)) {
2505		// Wrapping pour les graph des produits
2506		$accessallowed = 1;
2507		$original_file = $conf->product->multidir_temp[$entity].'/'.$original_file;
2508	} elseif ($modulepart == 'barcode') {
2509		// Wrapping pour les code barre
2510		$accessallowed = 1;
2511		// If viewimage is called for barcode, we try to output an image on the fly, with no build of file on disk.
2512		//$original_file=$conf->barcode->dir_temp.'/'.$original_file;
2513		$original_file = '';
2514	} elseif ($modulepart == 'iconmailing' && !empty($conf->mailing->dir_temp)) {
2515		// Wrapping pour les icones de background des mailings
2516		$accessallowed = 1;
2517		$original_file = $conf->mailing->dir_temp.'/'.$original_file;
2518	} elseif ($modulepart == 'scanner_user_temp' && !empty($conf->scanner->dir_temp)) {
2519		// Wrapping pour le scanner
2520		$accessallowed = 1;
2521		$original_file = $conf->scanner->dir_temp.'/'.$fuser->id.'/'.$original_file;
2522	} elseif ($modulepart == 'fckeditor' && !empty($conf->fckeditor->dir_output)) {
2523		// Wrapping pour les images fckeditor
2524		$accessallowed = 1;
2525		$original_file = $conf->fckeditor->dir_output.'/'.$original_file;
2526	} elseif ($modulepart == 'user' && !empty($conf->user->dir_output)) {
2527		// Wrapping for users
2528		$canreaduser = (!empty($fuser->admin) || $fuser->rights->user->user->{$lire});
2529		if ($fuser->id == (int) $refname) {
2530			$canreaduser = 1;
2531		} // A user can always read its own card
2532		if ($canreaduser || preg_match('/^specimen/i', $original_file)) {
2533			$accessallowed = 1;
2534		}
2535		$original_file = $conf->user->dir_output.'/'.$original_file;
2536	} elseif (($modulepart == 'company' || $modulepart == 'societe' || $modulepart == 'thirdparty') && !empty($conf->societe->multidir_output[$entity])) {
2537		// Wrapping for third parties
2538		if (empty($entity) || empty($conf->societe->multidir_output[$entity])) {
2539			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2540		}
2541		if ($fuser->rights->societe->{$lire} || preg_match('/^specimen/i', $original_file)) {
2542			$accessallowed = 1;
2543		}
2544		$original_file = $conf->societe->multidir_output[$entity].'/'.$original_file;
2545		$sqlprotectagainstexternals = "SELECT rowid as fk_soc FROM ".MAIN_DB_PREFIX."societe WHERE rowid='".$db->escape($refname)."' AND entity IN (".getEntity('societe').")";
2546	} elseif ($modulepart == 'contact' && !empty($conf->societe->multidir_output[$entity])) {
2547		// Wrapping for contact
2548		if (empty($entity) || empty($conf->societe->multidir_output[$entity])) {
2549			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2550		}
2551		if ($fuser->rights->societe->{$lire}) {
2552			$accessallowed = 1;
2553		}
2554		$original_file = $conf->societe->multidir_output[$entity].'/contact/'.$original_file;
2555	} elseif (($modulepart == 'facture' || $modulepart == 'invoice') && !empty($conf->facture->multidir_output[$entity])) {
2556		// Wrapping for invoices
2557		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2558			$accessallowed = 1;
2559		}
2560		$original_file = $conf->facture->multidir_output[$entity].'/'.$original_file;
2561		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('invoice').")";
2562	} elseif ($modulepart == 'massfilesarea_proposals' && !empty($conf->propal->multidir_output[$entity])) {
2563		// Wrapping for mass actions
2564		if ($fuser->rights->propal->{$lire} || preg_match('/^specimen/i', $original_file)) {
2565			$accessallowed = 1;
2566		}
2567		$original_file = $conf->propal->multidir_output[$entity].'/temp/massgeneration/'.$user->id.'/'.$original_file;
2568	} elseif ($modulepart == 'massfilesarea_orders') {
2569		if ($fuser->rights->commande->{$lire} || preg_match('/^specimen/i', $original_file)) {
2570			$accessallowed = 1;
2571		}
2572		$original_file = $conf->commande->multidir_output[$entity].'/temp/massgeneration/'.$user->id.'/'.$original_file;
2573	} elseif ($modulepart == 'massfilesarea_sendings') {
2574		if ($fuser->rights->expedition->{$lire} || preg_match('/^specimen/i', $original_file)) {
2575			$accessallowed = 1;
2576		}
2577		$original_file = $conf->expedition->dir_output.'/sending/temp/massgeneration/'.$user->id.'/'.$original_file;
2578	} elseif ($modulepart == 'massfilesarea_invoices') {
2579		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2580			$accessallowed = 1;
2581		}
2582		$original_file = $conf->facture->multidir_output[$entity].'/temp/massgeneration/'.$user->id.'/'.$original_file;
2583	} elseif ($modulepart == 'massfilesarea_expensereport') {
2584		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2585			$accessallowed = 1;
2586		}
2587		$original_file = $conf->expensereport->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2588	} elseif ($modulepart == 'massfilesarea_interventions') {
2589		if ($fuser->rights->ficheinter->{$lire} || preg_match('/^specimen/i', $original_file)) {
2590			$accessallowed = 1;
2591		}
2592		$original_file = $conf->ficheinter->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2593	} elseif ($modulepart == 'massfilesarea_supplier_proposal' && !empty($conf->supplier_proposal->dir_output)) {
2594		if ($fuser->rights->supplier_proposal->{$lire} || preg_match('/^specimen/i', $original_file)) {
2595			$accessallowed = 1;
2596		}
2597		$original_file = $conf->supplier_proposal->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2598	} elseif ($modulepart == 'massfilesarea_supplier_order') {
2599		if ($fuser->rights->fournisseur->commande->{$lire} || preg_match('/^specimen/i', $original_file)) {
2600			$accessallowed = 1;
2601		}
2602		$original_file = $conf->fournisseur->commande->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2603	} elseif ($modulepart == 'massfilesarea_supplier_invoice') {
2604		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2605			$accessallowed = 1;
2606		}
2607		$original_file = $conf->fournisseur->facture->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2608	} elseif ($modulepart == 'massfilesarea_contract' && !empty($conf->contrat->dir_output)) {
2609		if ($fuser->rights->contrat->{$lire} || preg_match('/^specimen/i', $original_file)) {
2610			$accessallowed = 1;
2611		}
2612		$original_file = $conf->contrat->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2613	} elseif (($modulepart == 'fichinter' || $modulepart == 'ficheinter') && !empty($conf->ficheinter->dir_output)) {
2614		// Wrapping for interventions
2615		if ($fuser->rights->ficheinter->{$lire} || preg_match('/^specimen/i', $original_file)) {
2616			$accessallowed = 1;
2617		}
2618		$original_file = $conf->ficheinter->dir_output.'/'.$original_file;
2619		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2620	} elseif ($modulepart == 'deplacement' && !empty($conf->deplacement->dir_output)) {
2621		// Wrapping pour les deplacements et notes de frais
2622		if ($fuser->rights->deplacement->{$lire} || preg_match('/^specimen/i', $original_file)) {
2623			$accessallowed = 1;
2624		}
2625		$original_file = $conf->deplacement->dir_output.'/'.$original_file;
2626		//$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2627	} elseif (($modulepart == 'propal' || $modulepart == 'propale') && !empty($conf->propal->multidir_output[$entity])) {
2628		// Wrapping pour les propales
2629		if ($fuser->rights->propale->{$lire} || preg_match('/^specimen/i', $original_file)) {
2630			$accessallowed = 1;
2631		}
2632		$original_file = $conf->propal->multidir_output[$entity].'/'.$original_file;
2633		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."propal WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('propal').")";
2634	} elseif (($modulepart == 'commande' || $modulepart == 'order') && !empty($conf->commande->multidir_output[$entity])) {
2635		// Wrapping pour les commandes
2636		if ($fuser->rights->commande->{$lire} || preg_match('/^specimen/i', $original_file)) {
2637			$accessallowed = 1;
2638		}
2639		$original_file = $conf->commande->multidir_output[$entity].'/'.$original_file;
2640		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('order').")";
2641	} elseif ($modulepart == 'project' && !empty($conf->projet->dir_output)) {
2642		// Wrapping pour les projets
2643		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file)) {
2644			$accessallowed = 1;
2645			// If we known $id of project, call checkUserAccessToObject to check permission on properties and contact of project
2646			if ($refname && !preg_match('/^specimen/i', $original_file)) {
2647				include_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
2648				$tmpproject = new Project($db);
2649				$tmpproject->fetch('', $refname);
2650				$accessallowed = checkUserAccessToObject($user, array('projet'), $tmpproject->id, 'projet&project', '', '', 'rowid', '');
2651			}
2652		}
2653		$original_file = $conf->projet->dir_output.'/'.$original_file;
2654		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
2655	} elseif ($modulepart == 'project_task' && !empty($conf->projet->dir_output)) {
2656		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file)) {
2657			$accessallowed = 1;
2658			// If we known $id of project, call checkUserAccessToObject to check permission on properties and contact of project
2659			if ($refname && !preg_match('/^specimen/i', $original_file)) {
2660				include_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2661				$tmptask = new Task($db);
2662				$tmptask->fetch('', $refname);
2663				$accessallowed = checkUserAccessToObject($user, array('projet_task'), $tmptask->id, 'projet&project', '', '', 'rowid', '');
2664			}
2665		}
2666		$original_file = $conf->projet->dir_output.'/'.$original_file;
2667		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
2668	} elseif (($modulepart == 'commande_fournisseur' || $modulepart == 'order_supplier') && !empty($conf->fournisseur->commande->dir_output)) {
2669		// Wrapping pour les commandes fournisseurs
2670		if ($fuser->rights->fournisseur->commande->{$lire} || preg_match('/^specimen/i', $original_file)) {
2671			$accessallowed = 1;
2672		}
2673		$original_file = $conf->fournisseur->commande->dir_output.'/'.$original_file;
2674		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2675	} elseif (($modulepart == 'facture_fournisseur' || $modulepart == 'invoice_supplier') && !empty($conf->fournisseur->facture->dir_output)) {
2676		// Wrapping pour les factures fournisseurs
2677		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2678			$accessallowed = 1;
2679		}
2680		$original_file = $conf->fournisseur->facture->dir_output.'/'.$original_file;
2681		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture_fourn WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2682	} elseif ($modulepart == 'supplier_payment') {
2683		// Wrapping pour les rapport de paiements
2684		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2685			$accessallowed = 1;
2686		}
2687		$original_file = $conf->fournisseur->payment->dir_output.'/'.$original_file;
2688		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."paiementfournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2689	} elseif ($modulepart == 'facture_paiement' && !empty($conf->facture->dir_output)) {
2690		// Wrapping pour les rapport de paiements
2691		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file)) {
2692			$accessallowed = 1;
2693		}
2694		if ($fuser->societe_id > 0) {
2695			$original_file = $conf->facture->dir_output.'/payments/private/'.$fuser->id.'/'.$original_file;
2696		} else {
2697			$original_file = $conf->facture->dir_output.'/payments/'.$original_file;
2698		}
2699	} elseif ($modulepart == 'export_compta' && !empty($conf->accounting->dir_output)) {
2700		// Wrapping for accounting exports
2701		if ($fuser->rights->accounting->bind->write || preg_match('/^specimen/i', $original_file)) {
2702			$accessallowed = 1;
2703		}
2704		$original_file = $conf->accounting->dir_output.'/'.$original_file;
2705	} elseif (($modulepart == 'expedition' || $modulepart == 'shipment') && !empty($conf->expedition->dir_output)) {
2706		// Wrapping pour les expedition
2707		if ($fuser->rights->expedition->{$lire} || preg_match('/^specimen/i', $original_file)) {
2708			$accessallowed = 1;
2709		}
2710		$original_file = $conf->expedition->dir_output."/".(strpos('sending/', $original_file) === 0 ? '' : 'sending/').$original_file;
2711		//$original_file = $conf->expedition->dir_output."/".$original_file;
2712	} elseif (($modulepart == 'livraison' || $modulepart == 'delivery') && !empty($conf->expedition->dir_output)) {
2713		// Delivery Note Wrapping
2714		if ($fuser->rights->expedition->delivery->{$lire} || preg_match('/^specimen/i', $original_file)) {
2715			$accessallowed = 1;
2716		}
2717		$original_file = $conf->expedition->dir_output."/".(strpos('receipt/', $original_file) === 0 ? '' : 'receipt/').$original_file;
2718	} elseif ($modulepart == 'actions' && !empty($conf->agenda->dir_output)) {
2719		// Wrapping pour les actions
2720		if ($fuser->rights->agenda->myactions->{$read} || preg_match('/^specimen/i', $original_file)) {
2721			$accessallowed = 1;
2722		}
2723		$original_file = $conf->agenda->dir_output.'/'.$original_file;
2724	} elseif ($modulepart == 'actionsreport' && !empty($conf->agenda->dir_temp)) {
2725		// Wrapping pour les actions
2726		if ($fuser->rights->agenda->allactions->{$read} || preg_match('/^specimen/i', $original_file)) {
2727			$accessallowed = 1;
2728		}
2729		$original_file = $conf->agenda->dir_temp."/".$original_file;
2730	} elseif ($modulepart == 'product' || $modulepart == 'produit' || $modulepart == 'service' || $modulepart == 'produit|service') {
2731		// Wrapping pour les produits et services
2732		if (empty($entity) || (empty($conf->product->multidir_output[$entity]) && empty($conf->service->multidir_output[$entity]))) {
2733			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2734		}
2735		if (($fuser->rights->produit->{$lire} || $fuser->rights->service->{$lire}) || preg_match('/^specimen/i', $original_file)) {
2736			$accessallowed = 1;
2737		}
2738		if (!empty($conf->product->enabled)) {
2739			$original_file = $conf->product->multidir_output[$entity].'/'.$original_file;
2740		} elseif (!empty($conf->service->enabled)) {
2741			$original_file = $conf->service->multidir_output[$entity].'/'.$original_file;
2742		}
2743	} elseif ($modulepart == 'product_batch' || $modulepart == 'produitlot') {
2744		// Wrapping pour les lots produits
2745		if (empty($entity) || (empty($conf->productbatch->multidir_output[$entity]))) {
2746			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2747		}
2748		if (($fuser->rights->produit->{$lire} ) || preg_match('/^specimen/i', $original_file)) {
2749			$accessallowed = 1;
2750		}
2751		if (!empty($conf->productbatch->enabled)) {
2752			$original_file = $conf->productbatch->multidir_output[$entity].'/'.$original_file;
2753		}
2754	} elseif ($modulepart == 'movement' || $modulepart == 'mouvement') {
2755		// Wrapping for stock movements
2756		if (empty($entity) || empty($conf->stock->multidir_output[$entity])) {
2757			return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2758		}
2759		if (($fuser->rights->stock->{$lire} || $fuser->rights->stock->movement->{$lire} || $fuser->rights->stock->mouvement->{$lire}) || preg_match('/^specimen/i', $original_file)) {
2760			$accessallowed = 1;
2761		}
2762		if (!empty($conf->stock->enabled)) {
2763			$original_file = $conf->stock->multidir_output[$entity].'/movement/'.$original_file;
2764		}
2765	} elseif ($modulepart == 'contract' && !empty($conf->contrat->multidir_output[$entity])) {
2766		// Wrapping pour les contrats
2767		if ($fuser->rights->contrat->{$lire} || preg_match('/^specimen/i', $original_file)) {
2768			$accessallowed = 1;
2769		}
2770		$original_file = $conf->contrat->multidir_output[$entity].'/'.$original_file;
2771		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."contrat WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('contract').")";
2772	} elseif ($modulepart == 'donation' && !empty($conf->don->dir_output)) {
2773		// Wrapping pour les dons
2774		if ($fuser->rights->don->{$lire} || preg_match('/^specimen/i', $original_file)) {
2775			$accessallowed = 1;
2776		}
2777		$original_file = $conf->don->dir_output.'/'.$original_file;
2778	} elseif ($modulepart == 'dolresource' && !empty($conf->resource->dir_output)) {
2779		// Wrapping pour les dons
2780		if ($fuser->rights->resource->{$read} || preg_match('/^specimen/i', $original_file)) {
2781			$accessallowed = 1;
2782		}
2783		$original_file = $conf->resource->dir_output.'/'.$original_file;
2784	} elseif ($modulepart == 'remisecheque' && !empty($conf->bank->dir_output)) {
2785		// Wrapping pour les remises de cheques
2786		if ($fuser->rights->banque->{$lire} || preg_match('/^specimen/i', $original_file)) {
2787			$accessallowed = 1;
2788		}
2789
2790		$original_file = $conf->bank->dir_output.'/checkdeposits/'.$original_file; // original_file should contains relative path so include the get_exdir result
2791	} elseif (($modulepart == 'banque' || $modulepart == 'bank') && !empty($conf->bank->dir_output)) {
2792		// Wrapping for bank
2793		if ($fuser->rights->banque->{$lire}) {
2794			$accessallowed = 1;
2795		}
2796		$original_file = $conf->bank->dir_output.'/'.$original_file;
2797	} elseif ($modulepart == 'export' && !empty($conf->export->dir_temp)) {
2798		// Wrapping for export module
2799		// Note that a test may not be required because we force the dir of download on the directory of the user that export
2800		$accessallowed = $user->rights->export->lire;
2801		$original_file = $conf->export->dir_temp.'/'.$fuser->id.'/'.$original_file;
2802	} elseif ($modulepart == 'import' && !empty($conf->import->dir_temp)) {
2803		// Wrapping for import module
2804		$accessallowed = $user->rights->import->run;
2805		$original_file = $conf->import->dir_temp.'/'.$original_file;
2806	} elseif ($modulepart == 'editor' && !empty($conf->fckeditor->dir_output)) {
2807		// Wrapping for wysiwyg editor
2808		$accessallowed = 1;
2809		$original_file = $conf->fckeditor->dir_output.'/'.$original_file;
2810	} elseif ($modulepart == 'systemtools' && !empty($conf->admin->dir_output)) {
2811		// Wrapping for backups
2812		if ($fuser->admin) {
2813			$accessallowed = 1;
2814		}
2815		$original_file = $conf->admin->dir_output.'/'.$original_file;
2816	} elseif ($modulepart == 'admin_temp' && !empty($conf->admin->dir_temp)) {
2817		// Wrapping for upload file test
2818		if ($fuser->admin) {
2819			$accessallowed = 1;
2820		}
2821		$original_file = $conf->admin->dir_temp.'/'.$original_file;
2822	} elseif ($modulepart == 'bittorrent' && !empty($conf->bittorrent->dir_output)) {
2823		// Wrapping pour BitTorrent
2824		$accessallowed = 1;
2825		$dir = 'files';
2826		if (dol_mimetype($original_file) == 'application/x-bittorrent') {
2827			$dir = 'torrents';
2828		}
2829		$original_file = $conf->bittorrent->dir_output.'/'.$dir.'/'.$original_file;
2830	} elseif ($modulepart == 'member' && !empty($conf->adherent->dir_output)) {
2831		// Wrapping pour Foundation module
2832		if ($fuser->rights->adherent->{$lire} || preg_match('/^specimen/i', $original_file)) {
2833			$accessallowed = 1;
2834		}
2835		$original_file = $conf->adherent->dir_output.'/'.$original_file;
2836	} elseif ($modulepart == 'scanner_user_temp' && !empty($conf->scanner->dir_temp)) {
2837		// Wrapping for Scanner
2838		$accessallowed = 1;
2839		$original_file = $conf->scanner->dir_temp.'/'.$fuser->id.'/'.$original_file;
2840		// If modulepart=module_user_temp	Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp/iduser
2841		// If modulepart=module_temp		Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp
2842		// If modulepart=module_user		Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/iduser
2843		// If modulepart=module				Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart
2844		// If modulepart=module-abc			Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart
2845	} else {
2846		// GENERIC Wrapping
2847		//var_dump($modulepart);
2848		//var_dump($original_file);
2849		if (preg_match('/^specimen/i', $original_file)) {
2850			$accessallowed = 1; // If link to a file called specimen. Test must be done before changing $original_file int full path.
2851		}
2852		if ($fuser->admin) {
2853			$accessallowed = 1; // If user is admin
2854		}
2855		$tmpmodulepart = explode('-', $modulepart);
2856		if (!empty($tmpmodulepart[1])) {
2857				$modulepart = $tmpmodulepart[0];
2858				$original_file = $tmpmodulepart[1].'/'.$original_file;
2859		}
2860
2861		// Define $accessallowed
2862		$reg = array();
2863		if (preg_match('/^([a-z]+)_user_temp$/i', $modulepart, $reg)) {
2864			if (empty($conf->{$reg[1]}->dir_temp)) {	// modulepart not supported
2865				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2866				exit;
2867			}
2868			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) {
2869				$accessallowed = 1;
2870			}
2871			$original_file = $conf->{$reg[1]}->dir_temp.'/'.$fuser->id.'/'.$original_file;
2872		} elseif (preg_match('/^([a-z]+)_temp$/i', $modulepart, $reg)) {
2873			if (empty($conf->{$reg[1]}->dir_temp)) {	// modulepart not supported
2874				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2875				exit;
2876			}
2877			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) {
2878				$accessallowed = 1;
2879			}
2880			$original_file = $conf->{$reg[1]}->dir_temp.'/'.$original_file;
2881		} elseif (preg_match('/^([a-z]+)_user$/i', $modulepart, $reg)) {
2882			if (empty($conf->{$reg[1]}->dir_output)) {	// modulepart not supported
2883				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2884				exit;
2885			}
2886			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) {
2887				$accessallowed = 1;
2888			}
2889			$original_file = $conf->{$reg[1]}->dir_output.'/'.$fuser->id.'/'.$original_file;
2890		} elseif (preg_match('/^massfilesarea_([a-z]+)$/i', $modulepart, $reg)) {
2891			if (empty($conf->{$reg[1]}->dir_output)) {	// modulepart not supported
2892				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2893				exit;
2894			}
2895			if ($fuser->rights->{$reg[1]}->{$lire} || preg_match('/^specimen/i', $original_file)) {
2896				$accessallowed = 1;
2897			}
2898			$original_file = $conf->{$reg[1]}->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2899		} else {
2900			if (empty($conf->$modulepart->dir_output)) {	// modulepart not supported
2901				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.'). The module for this modulepart value may not be activated.');
2902				exit;
2903			}
2904
2905			// Check fuser->rights->modulepart->myobject->read and fuser->rights->modulepart->read
2906			$partsofdirinoriginalfile = explode('/', $original_file);
2907			if (!empty($partsofdirinoriginalfile[1])) {	// If original_file is xxx/filename (xxx is a part we will use)
2908				$partofdirinoriginalfile = $partsofdirinoriginalfile[0];
2909				if ($partofdirinoriginalfile && !empty($fuser->rights->$modulepart->$partofdirinoriginalfile) && ($fuser->rights->$modulepart->$partofdirinoriginalfile->{$lire} || $fuser->rights->$modulepart->$partofdirinoriginalfile->{$read})) {
2910					$accessallowed = 1;
2911				}
2912			}
2913			if (!empty($fuser->rights->$modulepart->{$lire}) || !empty($fuser->rights->$modulepart->{$read})) {
2914				$accessallowed = 1;
2915			}
2916
2917			if (is_array($conf->$modulepart->multidir_output) && !empty($conf->$modulepart->multidir_output[$entity])) {
2918				$original_file = $conf->$modulepart->multidir_output[$entity].'/'.$original_file;
2919			} else {
2920				$original_file = $conf->$modulepart->dir_output.'/'.$original_file;
2921			}
2922		}
2923
2924		// For modules who wants to manage different levels of permissions for documents
2925		$subPermCategoryConstName = strtoupper($modulepart).'_SUBPERMCATEGORY_FOR_DOCUMENTS';
2926		if (!empty($conf->global->$subPermCategoryConstName)) {
2927			$subPermCategory = $conf->global->$subPermCategoryConstName;
2928			if (!empty($subPermCategory) && (($fuser->rights->$modulepart->$subPermCategory->{$lire}) || ($fuser->rights->$modulepart->$subPermCategory->{$read}) || ($fuser->rights->$modulepart->$subPermCategory->{$download}))) {
2929				$accessallowed = 1;
2930			}
2931		}
2932
2933		// Define $sqlprotectagainstexternals for modules who want to protect access using a SQL query.
2934		$sqlProtectConstName = strtoupper($modulepart).'_SQLPROTECTAGAINSTEXTERNALS_FOR_DOCUMENTS';
2935		if (!empty($conf->global->$sqlProtectConstName)) {	// If module want to define its own $sqlprotectagainstexternals
2936			// Example: mymodule__SQLPROTECTAGAINSTEXTERNALS_FOR_DOCUMENTS = "SELECT fk_soc FROM ".MAIN_DB_PREFIX.$modulepart." WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2937			eval('$sqlprotectagainstexternals = "'.$conf->global->$sqlProtectConstName.'";');
2938		}
2939	}
2940
2941	$ret = array(
2942		'accessallowed' => $accessallowed,
2943		'sqlprotectagainstexternals'=>$sqlprotectagainstexternals,
2944		'original_file'=>$original_file
2945	);
2946
2947	return $ret;
2948}
2949
2950/**
2951 * Store object in file.
2952 *
2953 * @param string $directory Directory of cache
2954 * @param string $filename Name of filecache
2955 * @param mixed $object Object to store in cachefile
2956 * @return void
2957 */
2958function dol_filecache($directory, $filename, $object)
2959{
2960	if (!dol_is_dir($directory)) {
2961		dol_mkdir($directory);
2962	}
2963	$cachefile = $directory.$filename;
2964	file_put_contents($cachefile, serialize($object), LOCK_EX);
2965	@chmod($cachefile, 0644);
2966}
2967
2968/**
2969 * Test if Refresh needed.
2970 *
2971 * @param string $directory Directory of cache
2972 * @param string $filename Name of filecache
2973 * @param int $cachetime Cachetime delay
2974 * @return boolean 0 no refresh 1 if refresh needed
2975 */
2976function dol_cache_refresh($directory, $filename, $cachetime)
2977{
2978	$now = dol_now();
2979	$cachefile = $directory.$filename;
2980	$refresh = !file_exists($cachefile) || ($now - $cachetime) > dol_filemtime($cachefile);
2981	return $refresh;
2982}
2983
2984/**
2985 * Read object from cachefile.
2986 *
2987 * @param string $directory Directory of cache
2988 * @param string $filename Name of filecache
2989 * @return mixed Unserialise from file
2990 */
2991function dol_readcachefile($directory, $filename)
2992{
2993	$cachefile = $directory.$filename;
2994	$object = unserialize(file_get_contents($cachefile));
2995	return $object;
2996}
2997
2998
2999/**
3000 * Function to get list of updated or modified files.
3001 * $file_list is used as global variable
3002 *
3003 * @param	array				$file_list	        Array for response
3004 * @param   SimpleXMLElement	$dir    	        SimpleXMLElement of files to test
3005 * @param   string   			$path   	        Path of files relative to $pathref. We start with ''. Used by recursive calls.
3006 * @param   string              $pathref            Path ref (DOL_DOCUMENT_ROOT)
3007 * @param   array               $checksumconcat     Array of checksum
3008 * @return  array               			        Array of filenames
3009 */
3010function getFilesUpdated(&$file_list, SimpleXMLElement $dir, $path = '', $pathref = '', &$checksumconcat = array())
3011{
3012	global $conffile;
3013
3014	$exclude = 'install';
3015
3016	foreach ($dir->md5file as $file) {    // $file is a simpleXMLElement
3017		$filename = $path.$file['name'];
3018		$file_list['insignature'][] = $filename;
3019		$expectedsize = (empty($file['size']) ? '' : $file['size']);
3020		$expectedmd5 = (string) $file;
3021
3022		//if (preg_match('#'.$exclude.'#', $filename)) continue;
3023
3024		if (!file_exists($pathref.'/'.$filename)) {
3025			$file_list['missing'][] = array('filename'=>$filename, 'expectedmd5'=>$expectedmd5, 'expectedsize'=>$expectedsize);
3026		} else {
3027			$md5_local = md5_file($pathref.'/'.$filename);
3028
3029			if ($conffile == '/etc/dolibarr/conf.php' && $filename == '/filefunc.inc.php') {	// For install with deb or rpm, we ignore test on filefunc.inc.php that was modified by package
3030				$checksumconcat[] = $expectedmd5;
3031			} else {
3032				if ($md5_local != $expectedmd5) {
3033					$file_list['updated'][] = array('filename'=>$filename, 'expectedmd5'=>$expectedmd5, 'expectedsize'=>$expectedsize, 'md5'=>(string) $md5_local);
3034				}
3035				$checksumconcat[] = $md5_local;
3036			}
3037		}
3038	}
3039
3040	foreach ($dir->dir as $subdir) {			// $subdir['name'] is  '' or '/accountancy/admin' for example
3041		getFilesUpdated($file_list, $subdir, $path.$subdir['name'].'/', $pathref, $checksumconcat);
3042	}
3043
3044	return $file_list;
3045}
3046