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