Passed
Branch develop (10a3e7)
by
unknown
31:16
created

dol_compress_file()   C

Complexity

Conditions 14
Paths 63

Size

Total Lines 106
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 58
c 0
b 0
f 0
nc 63
nop 3
dl 0
loc 106
rs 6.2666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* Copyright (C) 2008-2012  Laurent Destailleur <[email protected]>
3
 * Copyright (C) 2012-2015  Regis Houssin       <[email protected]>
4
 * Copyright (C) 2012-2016  Juanjo Menent       <[email protected]>
5
 * Copyright (C) 2015       Marcos García       <[email protected]>
6
 * Copyright (C) 2016       Raphaël Doursenaud  <[email protected]>
7
 * Copyright (C) 2019       Frédéric France     <[email protected]>
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 <http://www.gnu.org/licenses/>.
21
 * or see http://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
 */
36
function 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
 */
60
function 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
				}
122
				elseif ($excludefilter) $excludefilterarray[]=$excludefilter;
123
				// Check if file is qualified
124
				foreach($excludefilterarray as $filt)
125
				{
126
					if (preg_match('/'.$filt.'/i', $file) || preg_match('/'.$filt.'/i', $fullpathfile)) {
127
						$qualified=0; break;
128
					}
129
				}
130
				//print $fullpathfile.' '.$file.' '.$qualified.'<br>';
131
132
				if ($qualified)
133
				{
134
					$isdir=is_dir(dol_osencode($path."/".$file));
135
					// Check whether this is a file or directory and whether we're interested in that type
136
					if ($isdir && (($types=="directories") || ($types=="all") || $recursive))
137
					{
138
						// Add entry into file_list array
139
						if (($types=="directories") || ($types=="all"))
140
						{
141
							if ($loaddate || $sortcriteria == 'date') $filedate=dol_filemtime($path."/".$file);
142
							if ($loadsize || $sortcriteria == 'size') $filesize=dol_filesize($path."/".$file);
143
144
							if (! $filter || preg_match('/'.$filter.'/i', $file))	// We do not search key $filter into all $path, only into $file part
145
							{
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
					}
171
					elseif (! $isdir && (($types == "files") || ($types == "all")))
172
					{
173
						// Add file into file_list array
174
						if ($loaddate || $sortcriteria == 'date') $filedate=dol_filemtime($path."/".$file);
175
						if ($loadsize || $sortcriteria == 'size') $filesize=dol_filesize($path."/".$file);
176
177
						if (! $filter || preg_match('/'.$filter.'/i', $file))	// We do not search key $filter into $path, only into $file
178
						{
179
							preg_match('/([^\/]+)\/[^\/]+$/', $path.'/'.$file, $reg);
180
							$level1name=(isset($reg[1])?$reg[1]:'');
181
							$file_list[] = array(
182
									"name" => $file,
183
									"path" => $path,
184
									"level1name" => $level1name,
185
									"relativename" => ($relativename?$relativename.'/':'').$file,
186
									"fullname" => $path.'/'.$file,
187
									"date" => $filedate,
188
									"size" => $filesize,
189
									"type" => 'file'
190
							);
191
						}
192
					}
193
				}
194
			}
195
			closedir($dir);
196
197
			// Obtain a list of columns
198
			if (! empty($sortcriteria) && $sortorder)
199
			{
200
			    $file_list = dol_sort_array($file_list, $sortcriteria, ($sortorder == SORT_ASC ? 'asc' : 'desc'));
201
			}
202
		}
203
	}
204
205
	if (is_object($hookmanager) && is_array($hookmanager->resArray)) $file_list = array_merge($file_list, $hookmanager->resArray);
206
207
	return $file_list;
208
}
209
210
211
/**
212
 *  Scan a directory and return a list of files/directories.
213
 *  Content for string is UTF8 and dir separator is "/".
214
 *
215
 *  @param	string		$path        	Starting path from which to search. Example: 'produit/MYPROD'
216
 *  @param	string		$filter        	Regex filter to restrict list. This regex value must be escaped for '/', since this char is used for preg_match function
217
 *  @param	array|null	$excludefilter  Array of Regex for exclude filter (example: array('(\.meta|_preview.*\.png)$','^\.'))
218
 *  @param	string		$sortcriteria	Sort criteria ("","fullname","name","date","size")
219
 *  @param	string		$sortorder		Sort order (SORT_ASC, SORT_DESC)
220
 *	@param	int			$mode			0=Return array minimum keys loaded (faster), 1=Force all keys like description
221
 *  @return	array						Array of array('name'=>'xxx','fullname'=>'/abc/xxx','type'=>'dir|file',...)
222
 *  @see dol_dir_list()
223
 */
224
function dol_dir_list_in_database($path, $filter = "", $excludefilter = null, $sortcriteria = "name", $sortorder = SORT_ASC, $mode = 0)
225
{
226
	global $conf, $db;
227
228
	$sql =" SELECT rowid, label, entity, filename, filepath, fullpath_orig, keywords, cover, gen_or_uploaded, extraparams, date_c, date_m, fk_user_c, fk_user_m,";
229
	$sql.=" acl, position, share";
230
	if ($mode) $sql.=", description";
231
	$sql.=" FROM ".MAIN_DB_PREFIX."ecm_files";
232
	$sql.=" WHERE filepath = '".$db->escape($path)."'";
233
	$sql.=" AND entity = ".$conf->entity;
234
235
	$resql = $db->query($sql);
236
	if ($resql)
237
	{
238
		$file_list=array();
239
		$num = $db->num_rows($resql);
240
		$i = 0;
241
		while ($i < $num)
242
		{
243
			$obj = $db->fetch_object($resql);
244
			if ($obj)
245
			{
246
				preg_match('/([^\/]+)\/[^\/]+$/', DOL_DATA_ROOT.'/'.$obj->filepath.'/'.$obj->filename, $reg);
247
				$level1name=(isset($reg[1])?$reg[1]:'');
248
				$file_list[] = array(
249
					"rowid" => $obj->rowid,
250
					"label" => $obj->label,         // md5
251
					"name" => $obj->filename,
252
					"path" => DOL_DATA_ROOT.'/'.$obj->filepath,
253
					"level1name" => $level1name,
254
					"fullname" => DOL_DATA_ROOT.'/'.$obj->filepath.'/'.$obj->filename,
255
					"fullpath_orig" => $obj->fullpath_orig,
256
					"date_c" => $db->jdate($obj->date_c),
257
					"date_m" => $db->jdate($obj->date_m),
258
					"type" => 'file',
259
					"keywords" => $obj->keywords,
260
					"cover" => $obj->cover,
261
					"position" => (int) $obj->position,
262
					"acl" => $obj->acl,
263
					"share" => $obj->share
264
				);
265
			}
266
			$i++;
267
		}
268
269
		// Obtain a list of columns
270
		if (! empty($sortcriteria))
271
		{
272
			$myarray=array();
273
			foreach ($file_list as $key => $row)
274
			{
275
				$myarray[$key] = (isset($row[$sortcriteria])?$row[$sortcriteria]:'');
276
			}
277
			// Sort the data
278
			if ($sortorder) array_multisort($myarray, $sortorder, $file_list);
279
		}
280
281
		return $file_list;
282
	}
283
	else
284
	{
285
		dol_print_error($db);
286
		return array();
287
	}
288
}
289
290
291
/**
292
 * Complete $filearray with data from database.
293
 * This will call doldir_list_indatabase to complate filearray.
294
 *
295
 * @param	array	$filearray			Array of files get using dol_dir_list
296
 * @param	string	$relativedir		Relative dir from DOL_DATA_ROOT
297
 * @return	void
298
 */
299
function completeFileArrayWithDatabaseInfo(&$filearray, $relativedir)
300
{
301
	global $conf, $db, $user;
302
303
	$filearrayindatabase = dol_dir_list_in_database($relativedir, '', null, 'name', SORT_ASC);
304
305
	// TODO Remove this when PRODUCT_USE_OLD_PATH_FOR_PHOTO will be removed
306
	global $modulepart;
307
	if ($modulepart == 'produit' && ! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
308
		global $object;
309
		if (! empty($object->id))
310
		{
311
			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";
312
			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";
313
314
			$relativedirold = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $upload_dirold);
315
			$relativedirold = preg_replace('/^[\\/]/', '', $relativedirold);
316
317
			$filearrayindatabase = array_merge($filearrayindatabase, dol_dir_list_in_database($relativedirold, '', null, 'name', SORT_ASC));
318
		}
319
	}
320
321
	//var_dump($filearray);
322
	//var_dump($filearrayindatabase);
323
324
	// Complete filearray with properties found into $filearrayindatabase
325
	foreach($filearray as $key => $val)
326
	{
327
		$found=0;
328
		// Search if it exists into $filearrayindatabase
329
		foreach($filearrayindatabase as $key2 => $val2)
330
		{
331
			if ($filearrayindatabase[$key2]['name'] == $filearray[$key]['name'])
332
			{
333
				$filearray[$key]['position_name']=($filearrayindatabase[$key2]['position']?$filearrayindatabase[$key2]['position']:'0').'_'.$filearrayindatabase[$key2]['name'];
334
				$filearray[$key]['position']=$filearrayindatabase[$key2]['position'];
335
				$filearray[$key]['cover']=$filearrayindatabase[$key2]['cover'];
336
				$filearray[$key]['acl']=$filearrayindatabase[$key2]['acl'];
337
				$filearray[$key]['rowid']=$filearrayindatabase[$key2]['rowid'];
338
				$filearray[$key]['label']=$filearrayindatabase[$key2]['label'];
339
				$filearray[$key]['share']=$filearrayindatabase[$key2]['share'];
340
				$found=1;
341
				break;
342
			}
343
		}
344
345
		if (! $found)    // This happen in transition toward version 6, or if files were added manually into os dir.
346
		{
347
			$filearray[$key]['position']='999999';     // File not indexed are at end. So if we add a file, it will not replace an existing position
348
			$filearray[$key]['cover']=0;
349
			$filearray[$key]['acl']='';
350
351
			$rel_filename = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $filearray[$key]['fullname']);
352
			if (! preg_match('/([\\/]temp[\\/]|[\\/]thumbs|\.meta$)/', $rel_filetorenameafter))     // If not a tmp file
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rel_filetorenameafter seems to be never defined.
Loading history...
353
			{
354
				dol_syslog("list_of_documents We found a file called '".$filearray[$key]['name']."' not indexed into database. We add it");
355
				include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
356
				$ecmfile=new EcmFiles($db);
357
358
				// Add entry into database
359
				$filename = basename($rel_filename);
360
				$rel_dir = dirname($rel_filename);
361
				$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
362
				$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
363
364
				$ecmfile->filepath = $rel_dir;
365
				$ecmfile->filename = $filename;
366
				$ecmfile->label = md5_file(dol_osencode($filearray[$key]['fullname']));        // $destfile is a full path to file
367
				$ecmfile->fullpath_orig = $filearray[$key]['fullname'];
368
				$ecmfile->gen_or_uploaded = 'unknown';
369
				$ecmfile->description = '';    // indexed content
370
				$ecmfile->keyword = '';        // keyword content
371
				$result = $ecmfile->create($user);
372
				if ($result < 0)
373
				{
374
					setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
375
				}
376
				else
377
				{
378
					$filearray[$key]['rowid']=$result;
379
				}
380
			}
381
			else
382
			{
383
				$filearray[$key]['rowid']=0;     // Should not happened
384
			}
385
		}
386
	}
387
388
	/*var_dump($filearray);*/
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
 */
399
function 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; }
407
	else { $retup=1; $retdown=-1; }
408
409
	if ($sortfield == 'name')
410
	{
411
		if ($a->name == $b->name) return 0;
0 ignored issues
show
Bug introduced by
The property name does not exist on string.
Loading history...
412
		return ($a->name < $b->name) ? $retup : $retdown;
413
	}
414
	if ($sortfield == 'date')
415
	{
416
		if ($a->date == $b->date) return 0;
0 ignored issues
show
Bug introduced by
The property date does not exist on string.
Loading history...
417
		return ($a->date < $b->date) ? $retup : $retdown;
418
	}
419
	if ($sortfield == 'size')
420
	{
421
		if ($a->size == $b->size) return 0;
0 ignored issues
show
Bug introduced by
The property size does not exist on string.
Loading history...
422
		return ($a->size < $b->size) ? $retup : $retdown;
423
	}
424
}
425
426
427
/**
428
 * Test if filename is a directory
429
 *
430
 * @param	string		$folder     Name of folder
431
 * @return	boolean     			True if it's a directory, False if not found
432
 */
433
function dol_is_dir($folder)
434
{
435
	$newfolder=dol_osencode($folder);
436
	if (is_dir($newfolder)) return true;
437
	else return false;
438
}
439
440
/**
441
 * Return if path is empty
442
 *
443
 * @param   string		$dir		Path of Directory
444
 * @return  boolean     		    True or false
445
 */
446
function dol_is_dir_empty($dir)
447
{
448
    if (!is_readable($dir)) return false;
449
    return (count(scandir($dir)) == 2);
450
}
451
452
/**
453
 * Return if path is a file
454
 *
455
 * @param   string		$pathoffile		Path of file
456
 * @return  boolean     			    True or false
457
 */
458
function dol_is_file($pathoffile)
459
{
460
	$newpathoffile=dol_osencode($pathoffile);
461
	return is_file($newpathoffile);
462
}
463
464
/**
465
 * Return if path is a symbolic link
466
 *
467
 * @param   string		$pathoffile		Path of file
468
 * @return  boolean     			    True or false
469
 */
470
function dol_is_link($pathoffile)
471
{
472
	$newpathoffile=dol_osencode($pathoffile);
473
	return is_link($newpathoffile);
474
}
475
476
/**
477
 * Return if path is an URL
478
 *
479
 * @param   string		$url	Url
480
 * @return  boolean      	   	True or false
481
 */
482
function dol_is_url($url)
483
{
484
	$tmpprot=array('file','http','https','ftp','zlib','data','ssh','ssh2','ogg','expect');
485
	foreach($tmpprot as $prot)
486
	{
487
		if (preg_match('/^'.$prot.':/i', $url)) return true;
488
	}
489
	return false;
490
}
491
492
/**
493
 * 	Test if a folder is empty
494
 *
495
 * 	@param	string	$folder		Name of folder
496
 * 	@return boolean				True if dir is empty or non-existing, False if it contains files
497
 */
498
function dol_dir_is_emtpy($folder)
499
{
500
	$newfolder=dol_osencode($folder);
501
	if (is_dir($newfolder))
502
	{
503
		$handle = opendir($newfolder);
504
		$folder_content = '';
505
		while ((gettype($name = readdir($handle)) != "boolean"))
506
		{
507
			$name_array[] = $name;
508
		}
509
		foreach($name_array as $temp) $folder_content .= $temp;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name_array does not seem to be defined for all execution paths leading up to this point.
Loading history...
510
511
		closedir($handle);
512
513
		if ($folder_content == "...") return true;
514
		else return false;
515
	}
516
	else
517
	return true; // Dir does not exists
518
}
519
520
/**
521
 * 	Count number of lines in a file
522
 *
523
 * 	@param	string	$file		Filename
524
 * 	@return int					<0 if KO, Number of lines in files if OK
525
 *  @see dol_nboflines()
526
 */
527
function dol_count_nb_of_line($file)
528
{
529
	$nb=0;
530
531
	$newfile=dol_osencode($file);
532
	//print 'x'.$file;
533
	$fp=fopen($newfile, 'r');
534
	if ($fp)
535
	{
536
		while (!feof($fp))
537
		{
538
			$line=fgets($fp);
539
			// 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.
540
			if (! $line === false) $nb++;
541
		}
542
		fclose($fp);
543
	}
544
	else
545
	{
546
		$nb=-1;
547
	}
548
549
	return $nb;
550
}
551
552
553
/**
554
 * Return size of a file
555
 *
556
 * @param 	string		$pathoffile		Path of file
557
 * @return 	integer						File size
558
 * @see dol_print_size()
559
 */
560
function dol_filesize($pathoffile)
561
{
562
	$newpathoffile=dol_osencode($pathoffile);
563
	return filesize($newpathoffile);
564
}
565
566
/**
567
 * Return time of a file
568
 *
569
 * @param 	string		$pathoffile		Path of file
570
 * @return 	int					Time of file
571
 */
572
function dol_filemtime($pathoffile)
573
{
574
	$newpathoffile=dol_osencode($pathoffile);
575
	return @filemtime($newpathoffile); // @Is to avoid errors if files does not exists
576
}
577
578
/**
579
 * Make replacement of strings into a file.
580
 *
581
 * @param	string	$srcfile			       Source file (can't be a directory)
582
 * @param	array	$arrayreplacement	       Array with strings to replace. Example: array('valuebefore'=>'valueafter', ...)
583
 * @param	string	$destfile			       Destination file (can't be a directory). If empty, will be same than source file.
584
 * @param	int		$newmask			       Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
585
 * @param	int		$indexdatabase		       1=index new file into database.
586
 * @param   int     $arrayreplacementisregex   1=Array of replacement is regex
587
 * @return	int							       <0 if error, 0 if nothing done (dest file already exists), >0 if OK
588
 * @see		dol_copy()
589
 */
590
function dolReplaceInFile($srcfile, $arrayreplacement, $destfile = '', $newmask = 0, $indexdatabase = 0, $arrayreplacementisregex = 0)
591
{
592
	global $conf;
593
594
	dol_syslog("files.lib.php::dolReplaceInFile srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." indexdatabase=".$indexdatabase." arrayreplacementisregex=".$arrayreplacementisregex);
595
596
	if (empty($srcfile)) return -1;
597
	if (empty($destfile)) $destfile=$srcfile;
598
599
	$destexists=dol_is_file($destfile);
600
	if (($destfile != $srcfile) && $destexists) return 0;
601
602
	$tmpdestfile=$destfile.'.tmp';
603
604
	$newpathofsrcfile=dol_osencode($srcfile);
605
	$newpathoftmpdestfile=dol_osencode($tmpdestfile);
606
	$newpathofdestfile=dol_osencode($destfile);
607
	$newdirdestfile=dirname($newpathofdestfile);
608
609
	if ($destexists && ! is_writable($newpathofdestfile))
610
	{
611
		dol_syslog("files.lib.php::dolReplaceInFile failed Permission denied to overwrite target file", LOG_WARNING);
612
		return -1;
613
	}
614
	if (! is_writable($newdirdestfile))
615
	{
616
		dol_syslog("files.lib.php::dolReplaceInFile failed Permission denied to write into target directory ".$newdirdestfile, LOG_WARNING);
617
		return -2;
618
	}
619
620
	dol_delete_file($tmpdestfile);
621
622
	// Create $newpathoftmpdestfile from $newpathofsrcfile
623
	$content = file_get_contents($newpathofsrcfile, 'r');
624
625
	if (empty($arrayreplacementisregex))
626
	{
627
	   $content = make_substitutions($content, $arrayreplacement, null);
628
	}
629
	else
630
	{
631
	    foreach ($arrayreplacement as $key => $value)
632
	    {
633
	        $content = preg_replace($key, $value, $content);
634
	    }
635
	}
636
637
	file_put_contents($newpathoftmpdestfile, $content);
638
	@chmod($newpathoftmpdestfile, octdec($newmask));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

638
	/** @scrutinizer ignore-unhandled */ @chmod($newpathoftmpdestfile, octdec($newmask));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
639
640
	// Rename
641
	$result=dol_move($newpathoftmpdestfile, $newpathofdestfile, $newmask, (($destfile == $srcfile)?1:0), 0, $indexdatabase);
642
	if (! $result)
643
	{
644
		dol_syslog("files.lib.php::dolReplaceInFile failed to move tmp file to final dest", LOG_WARNING);
645
		return -3;
646
	}
647
	if (empty($newmask) && ! empty($conf->global->MAIN_UMASK)) $newmask=$conf->global->MAIN_UMASK;
648
	if (empty($newmask))	// This should no happen
649
	{
650
		dol_syslog("Warning: dolReplaceInFile called with empty value for newmask and no default value defined", LOG_WARNING);
651
		$newmask='0664';
652
	}
653
654
	@chmod($newpathofdestfile, octdec($newmask));
655
656
	return 1;
657
}
658
659
660
/**
661
 * Copy a file to another file.
662
 *
663
 * @param	string	$srcfile			Source file (can't be a directory)
664
 * @param	string	$destfile			Destination file (can't be a directory)
665
 * @param	int		$newmask			Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
666
 * @param 	int		$overwriteifexists	Overwrite file if exists (1 by default)
667
 * @return	int							<0 if error, 0 if nothing done (dest file already exists and overwriteifexists=0), >0 if OK
668
 * @see		dol_delete_file() dolCopyDir()
669
 */
670
function dol_copy($srcfile, $destfile, $newmask = 0, $overwriteifexists = 1)
671
{
672
	global $conf;
673
674
	dol_syslog("files.lib.php::dol_copy srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwriteifexists=".$overwriteifexists);
675
676
	if (empty($srcfile) || empty($destfile)) return -1;
677
678
	$destexists=dol_is_file($destfile);
679
	if (! $overwriteifexists && $destexists) return 0;
680
681
	$newpathofsrcfile=dol_osencode($srcfile);
682
	$newpathofdestfile=dol_osencode($destfile);
683
	$newdirdestfile=dirname($newpathofdestfile);
684
685
	if ($destexists && ! is_writable($newpathofdestfile))
686
	{
687
		dol_syslog("files.lib.php::dol_copy failed Permission denied to overwrite target file", LOG_WARNING);
688
		return -1;
689
	}
690
	if (! is_writable($newdirdestfile))
691
	{
692
		dol_syslog("files.lib.php::dol_copy failed Permission denied to write into target directory ".$newdirdestfile, LOG_WARNING);
693
		return -2;
694
	}
695
	// Copy with overwriting if exists
696
	$result=@copy($newpathofsrcfile, $newpathofdestfile);
697
	//$result=copy($newpathofsrcfile, $newpathofdestfile);	// To see errors, remove @
698
	if (! $result)
699
	{
700
		dol_syslog("files.lib.php::dol_copy failed to copy", LOG_WARNING);
701
		return -3;
702
	}
703
	if (empty($newmask) && ! empty($conf->global->MAIN_UMASK)) $newmask=$conf->global->MAIN_UMASK;
704
	if (empty($newmask))	// This should no happen
705
	{
706
		dol_syslog("Warning: dol_copy called with empty value for newmask and no default value defined", LOG_WARNING);
707
		$newmask='0664';
708
	}
709
710
	@chmod($newpathofdestfile, octdec($newmask));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

710
	/** @scrutinizer ignore-unhandled */ @chmod($newpathofdestfile, octdec($newmask));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
711
712
	return 1;
713
}
714
715
/**
716
 * Copy a dir to another dir. This include recursive subdirectories.
717
 *
718
 * @param	string	$srcfile			Source file (a directory)
719
 * @param	string	$destfile			Destination file (a directory)
720
 * @param	int		$newmask			Mask for new file (0 by default means $conf->global->MAIN_UMASK). Example: '0666'
721
 * @param 	int		$overwriteifexists	Overwrite file if exists (1 by default)
722
 * @param	array	$arrayreplacement	Array to use to replace filenames with another one during the copy (works only on file names, not on directory names).
723
 * @return	int							<0 if error, 0 if nothing done (all files already exists and overwriteifexists=0), >0 if OK
724
 * @see		dol_copy()
725
 */
726
function dolCopyDir($srcfile, $destfile, $newmask, $overwriteifexists, $arrayreplacement = null)
727
{
728
	global $conf;
729
730
	$result=0;
731
732
	dol_syslog("files.lib.php::dolCopyDir srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwriteifexists=".$overwriteifexists);
733
734
	if (empty($srcfile) || empty($destfile)) return -1;
735
736
	$destexists=dol_is_dir($destfile);
737
	//if (! $overwriteifexists && $destexists) return 0;	// The overwriteifexists is for files only, so propagated to dol_copy only.
738
739
	if (! $destexists)
740
	{
741
		// We must set mask just before creating dir, becaause it can be set differently by dol_copy
742
		umask(0);
743
		$dirmaskdec=octdec($newmask);
744
		if (empty($newmask) && ! empty($conf->global->MAIN_UMASK)) $dirmaskdec=octdec($conf->global->MAIN_UMASK);
745
		$dirmaskdec |= octdec('0200');  // Set w bit required to be able to create content for recursive subdirs files
746
		dol_mkdir($destfile, '', decoct($dirmaskdec));
747
	}
748
749
	$ossrcfile=dol_osencode($srcfile);
750
	$osdestfile=dol_osencode($destfile);
751
752
	// Recursive function to copy all subdirectories and contents:
753
	if (is_dir($ossrcfile))
754
	{
755
		$dir_handle=opendir($ossrcfile);
756
		while ($file=readdir($dir_handle))
757
		{
758
			if ($file != "." && $file != ".." && ! is_link($ossrcfile."/".$file))
759
			{
760
				if (is_dir($ossrcfile."/".$file))
761
				{
762
					//var_dump("xxx dolCopyDir $srcfile/$file, $destfile/$file, $newmask, $overwriteifexists");
763
					$tmpresult=dolCopyDir($srcfile."/".$file, $destfile."/".$file, $newmask, $overwriteifexists, $arrayreplacement);
764
				}
765
				else
766
				{
767
					$newfile = $file;
768
					// Replace destination filename with a new one
769
					if (is_array($arrayreplacement))
770
					{
771
						foreach($arrayreplacement as $key => $val)
772
						{
773
							$newfile = str_replace($key, $val, $newfile);
774
						}
775
					}
776
					$tmpresult=dol_copy($srcfile."/".$file, $destfile."/".$newfile, $newmask, $overwriteifexists);
777
				}
778
				// Set result
779
				if ($result > 0 && $tmpresult >= 0)
780
				{
781
					// Do nothing, so we don't set result to 0 if tmpresult is 0 and result was success in a previous pass
782
				}
783
				else
784
				{
785
					$result=$tmpresult;
786
				}
787
				if ($result < 0) break;
788
			}
789
		}
790
		closedir($dir_handle);
791
	}
792
	else
793
	{
794
		// Source directory does not exists
795
		$result = -2;
796
	}
797
798
	return $result;
799
}
800
801
802
/**
803
 * Move a file into another name.
804
 * Note:
805
 *  - This function differs from dol_move_uploaded_file, because it can be called in any context.
806
 *  - Database indexes for files are updated.
807
 *  - Test on antivirus is done only if param testvirus is provided and an antivirus was set.
808
 *
809
 * @param	string  $srcfile            Source file (can't be a directory. use native php @rename() to move a directory)
810
 * @param   string	$destfile           Destination file (can't be a directory. use native php @rename() to move a directory)
811
 * @param   integer	$newmask            Mask in octal string for new file (0 by default means $conf->global->MAIN_UMASK)
812
 * @param   int		$overwriteifexists  Overwrite file if exists (1 by default)
813
 * @param   int     $testvirus          Do an antivirus test. Move is canceled if a virus is found.
814
 * @param	int		$indexdatabase		Index new file into database.
815
 * @return  boolean 		            True if OK, false if KO
816
 * @see dol_move_uploaded_file()
817
 */
818
function dol_move($srcfile, $destfile, $newmask = 0, $overwriteifexists = 1, $testvirus = 0, $indexdatabase = 1)
819
{
820
	global $user, $db, $conf;
821
	$result=false;
822
823
	dol_syslog("files.lib.php::dol_move srcfile=".$srcfile." destfile=".$destfile." newmask=".$newmask." overwritifexists=".$overwriteifexists);
824
	$srcexists=dol_is_file($srcfile);
825
	$destexists=dol_is_file($destfile);
826
827
	if (! $srcexists)
828
	{
829
		dol_syslog("files.lib.php::dol_move srcfile does not exists. we ignore the move request.");
830
		return false;
831
	}
832
833
	if ($overwriteifexists || ! $destexists)
834
	{
835
		$newpathofsrcfile=dol_osencode($srcfile);
836
		$newpathofdestfile=dol_osencode($destfile);
837
838
		// Check virus
839
		$testvirusarray=array();
840
		if ($testvirus)
841
		{
842
			$testvirusarray=dolCheckVirus($newpathofsrcfile);
843
			if (count($testvirusarray))
844
			{
845
				dol_syslog("files.lib.php::dol_move canceled because a virus was found into source file. we ignore the move request.", LOG_WARNING);
846
				return false;
847
			}
848
		}
849
850
		$result=@rename($newpathofsrcfile, $newpathofdestfile); // To see errors, remove @
851
		if (! $result)
852
		{
853
			if ($destexists)
854
			{
855
				dol_syslog("files.lib.php::dol_move Failed. We try to delete target first and move after.", LOG_WARNING);
856
				// We force delete and try again. Rename function sometimes fails to replace dest file with some windows NTFS partitions.
857
				dol_delete_file($destfile);
858
				$result=@rename($newpathofsrcfile, $newpathofdestfile); // To see errors, remove @
859
			}
860
			else dol_syslog("files.lib.php::dol_move Failed.", LOG_WARNING);
861
		}
862
863
		// Move ok
864
		if ($result && $indexdatabase)
865
		{
866
			// Rename entry into ecm database
867
			$rel_filetorenamebefore = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $srcfile);
868
			$rel_filetorenameafter = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $destfile);
869
			if (! preg_match('/([\\/]temp[\\/]|[\\/]thumbs|\.meta$)/', $rel_filetorenameafter))     // If not a tmp file
870
			{
871
				$rel_filetorenamebefore = preg_replace('/^[\\/]/', '', $rel_filetorenamebefore);
872
				$rel_filetorenameafter = preg_replace('/^[\\/]/', '', $rel_filetorenameafter);
873
				//var_dump($rel_filetorenamebefore.' - '.$rel_filetorenameafter);
874
875
				dol_syslog("Try to rename also entries in database for full relative path before = ".$rel_filetorenamebefore." after = ".$rel_filetorenameafter, LOG_DEBUG);
876
				include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
877
878
				$ecmfiletarget=new EcmFiles($db);
879
				$resultecmtarget = $ecmfiletarget->fetch(0, '', $rel_filetorenameafter);
880
				if ($resultecmtarget > 0)   // An entry for target name already exists for target, we delete it, a new one will be created.
881
				{
882
					$ecmfiletarget->delete($user);
883
				}
884
885
				$ecmfile=new EcmFiles($db);
886
				$resultecm = $ecmfile->fetch(0, '', $rel_filetorenamebefore);
887
				if ($resultecm > 0)   // If an entry was found for src file, we use it to move entry
888
				{
889
					$filename = basename($rel_filetorenameafter);
890
					$rel_dir = dirname($rel_filetorenameafter);
891
					$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
892
					$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
893
894
					$ecmfile->filepath = $rel_dir;
895
					$ecmfile->filename = $filename;
896
					$resultecm = $ecmfile->update($user);
897
				}
898
				elseif ($resultecm == 0)   // If no entry were found for src files, create/update target file
899
				{
900
					$filename = basename($rel_filetorenameafter);
901
					$rel_dir = dirname($rel_filetorenameafter);
902
					$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
903
					$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
904
905
					$ecmfile->filepath = $rel_dir;
906
					$ecmfile->filename = $filename;
907
					$ecmfile->label = md5_file(dol_osencode($destfile));        // $destfile is a full path to file
908
					$ecmfile->fullpath_orig = $srcfile;
909
					$ecmfile->gen_or_uploaded = 'unknown';
910
					$ecmfile->description = '';    // indexed content
911
					$ecmfile->keyword = '';        // keyword content
912
					$resultecm = $ecmfile->create($user);
913
					if ($resultecm < 0)
914
					{
915
						setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
916
					}
917
				}
918
				elseif ($resultecm < 0)
919
				{
920
					setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
921
				}
922
923
				if ($resultecm > 0) $result=true;
924
				else $result = false;
925
			}
926
		}
927
928
		if (empty($newmask)) $newmask=empty($conf->global->MAIN_UMASK)?'0755':$conf->global->MAIN_UMASK;
929
		$newmaskdec=octdec($newmask);
930
		// Currently method is restricted to files (dol_delete_files previously used is for files, and mask usage if for files too)
931
		// to allow mask usage for dir, we shoul introduce a new param "isdir" to 1 to complete newmask like this
932
		// if ($isdir) $newmaskdec |= octdec('0111');  // Set x bit required for directories
933
		@chmod($newpathofdestfile, $newmaskdec);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

933
		/** @scrutinizer ignore-unhandled */ @chmod($newpathofdestfile, $newmaskdec);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
934
	}
935
936
	return $result;
937
}
938
939
/**
940
 *	Unescape a file submitted by upload.
941
 *  PHP escape char " (%22) or char ' (%27) into $FILES.
942
 *
943
 *	@param	string	$filename		Filename
944
 *	@return	string					Filename sanitized
945
 */
946
function dol_unescapefile($filename)
947
{
948
	// Remove path information and dots around the filename, to prevent uploading
949
	// into different directories or replacing hidden system files.
950
	// Also remove control characters and spaces (\x00..\x20) around the filename:
951
	return trim(basename($filename), ".\x00..\x20");
952
}
953
954
955
/**
956
 * Check virus into a file
957
 *
958
 * @param   string      $src_file       Source file to check
959
 * @return  array                       Array of errors or empty array if not virus found
960
 */
961
function dolCheckVirus($src_file)
962
{
963
	global $conf;
964
965
	if (! empty($conf->global->MAIN_ANTIVIRUS_COMMAND))
966
	{
967
		if (! class_exists('AntiVir')) {
968
			require_once DOL_DOCUMENT_ROOT.'/core/class/antivir.class.php';
969
		}
970
		$antivir=new AntiVir($db);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $db seems to be never defined.
Loading history...
971
		$result = $antivir->dol_avscan_file($src_file);
972
		if ($result < 0)	// If virus or error, we stop here
973
		{
974
			$reterrors=$antivir->errors;
975
			return $reterrors;
976
		}
977
	}
978
	return array();
979
}
980
981
982
/**
983
 *	Make control on an uploaded file from an GUI page and move it to final destination.
984
 * 	If there is errors (virus found, antivir in error, bad filename), file is not moved.
985
 *  Note:
986
 *  - This function can be used only into a HTML page context. Use dol_move if you are outside.
987
 *  - Test on antivirus is always done (if antivirus set).
988
 *  - Database of files is NOT updated (this is done by dol_add_file_process() that calls this function).
989
 *
990
 *	@param	string	$src_file			Source full path filename ($_FILES['field']['tmp_name'])
991
 *	@param	string	$dest_file			Target full path filename  ($_FILES['field']['name'])
992
 * 	@param	int		$allowoverwrite		1=Overwrite target file if it already exists
993
 * 	@param	int		$disablevirusscan	1=Disable virus scan
994
 * 	@param	integer	$uploaderrorcode	Value of PHP upload error code ($_FILES['field']['error'])
995
 * 	@param	int		$nohook				Disable all hooks
996
 * 	@param	string	$varfiles			_FILES var name
997
 *	@return int       			  		>0 if OK, <0 or string if KO
998
 *  @see    dol_move()
999
 */
1000
function dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan = 0, $uploaderrorcode = 0, $nohook = 0, $varfiles = 'addedfile')
1001
{
1002
	global $conf, $db, $user, $langs;
1003
	global $object, $hookmanager;
1004
1005
	$reshook=0;
1006
	$file_name = $dest_file;
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
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1026
				case UPLOAD_ERR_FORM_SIZE:	// 2
1027
					return 'ErrorFileSizeTooLarge';
1028
					break;
1029
				case UPLOAD_ERR_PARTIAL:	// 3
1030
					return 'ErrorPartialFile';
1031
					break;
1032
				case UPLOAD_ERR_NO_TMP_DIR:	//
1033
					return 'ErrorNoTmpDir';
1034
					break;
1035
				case UPLOAD_ERR_CANT_WRITE:
1036
					return 'ErrorFailedToWriteInDir';
1037
					break;
1038
				case UPLOAD_ERR_EXTENSION:
1039
					return 'ErrorUploadBlockedByAddon';
1040
					break;
1041
				default:
1042
					break;
1043
			}
1044
		}
1045
1046
		// If we need to make a virus scan
1047
		if (empty($disablevirusscan) && file_exists($src_file))
1048
		{
1049
			$checkvirusarray=dolCheckVirus($src_file);
1050
			if (count($checkvirusarray))
1051
			{
1052
			   dol_syslog('Files.lib::dol_move_uploaded_file File "'.$src_file.'" (target name "'.$dest_file.'") KO with antivirus: errors='.join(',', $checkvirusarray), LOG_WARNING);
1053
			   return 'ErrorFileIsInfectedWithAVirus: '.join(',', $checkvirusarray);
1054
			}
1055
		}
1056
1057
		// Security:
1058
		// Disallow file with some extensions. We rename them.
1059
		// Because if we put the documents directory into a directory inside web root (very bad), this allows to execute on demand arbitrary code.
1060
		if (isAFileWithExecutableContent($dest_file) && empty($conf->global->MAIN_DOCUMENT_IS_OUTSIDE_WEBROOT_SO_NOEXE_NOT_REQUIRED))
1061
		{
1062
			$file_name.= '.noexe';
1063
		}
1064
1065
		// Security:
1066
		// We refuse cache files/dirs, upload using .. and pipes into filenames.
1067
		if (preg_match('/^\./', $src_file) || preg_match('/\.\./', $src_file) || preg_match('/[<>|]/', $src_file))
1068
		{
1069
			dol_syslog("Refused to deliver file ".$src_file, LOG_WARNING);
1070
			return -1;
1071
		}
1072
1073
		// Security:
1074
		// On interdit fichiers caches, remontees de repertoire ainsi que les pipe dans les noms de fichiers.
1075
		if (preg_match('/^\./', $dest_file) || preg_match('/\.\./', $dest_file) || preg_match('/[<>|]/', $dest_file))
1076
		{
1077
			dol_syslog("Refused to deliver file ".$dest_file, LOG_WARNING);
1078
			return -2;
1079
		}
1080
	}
1081
1082
	if ($reshook < 0)	// At least one blocking error returned by one hook
1083
	{
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
	}
1088
	elseif (empty($reshook))
1089
	{
1090
		// The file functions must be in OS filesystem encoding.
1091
		$src_file_osencoded=dol_osencode($src_file);
1092
		$file_name_osencoded=dol_osencode($file_name);
1093
1094
		// Check if destination dir is writable
1095
		if (! is_writable(dirname($file_name_osencoded)))
1096
		{
1097
			dol_syslog("Files.lib::dol_move_uploaded_file Dir ".dirname($file_name_osencoded)." is not writable. Return 'ErrorDirNotWritable'", LOG_WARNING);
1098
			return 'ErrorDirNotWritable';
1099
		}
1100
1101
		// Check if destination file already exists
1102
		if (! $allowoverwrite)
1103
		{
1104
			if (file_exists($file_name_osencoded))
1105
			{
1106
				dol_syslog("Files.lib::dol_move_uploaded_file File ".$file_name." already exists. Return 'ErrorFileAlreadyExists'", LOG_WARNING);
1107
				return 'ErrorFileAlreadyExists';
1108
			}
1109
		}
1110
1111
		// Move file
1112
		$return=move_uploaded_file($src_file_osencoded, $file_name_osencoded);
1113
		if ($return)
1114
		{
1115
			if (! empty($conf->global->MAIN_UMASK)) @chmod($file_name_osencoded, octdec($conf->global->MAIN_UMASK));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1115
			if (! empty($conf->global->MAIN_UMASK)) /** @scrutinizer ignore-unhandled */ @chmod($file_name_osencoded, octdec($conf->global->MAIN_UMASK));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1116
			dol_syslog("Files.lib::dol_move_uploaded_file Success to move ".$src_file." to ".$file_name." - Umask=".$conf->global->MAIN_UMASK, LOG_DEBUG);
1117
			return 1;	// Success
1118
		}
1119
		else
1120
		{
1121
			dol_syslog("Files.lib::dol_move_uploaded_file Failed to move ".$src_file." to ".$file_name, LOG_ERR);
1122
			return -3;	// Unknown error
1123
		}
1124
	}
1125
1126
	return 1;	// Success
1127
}
1128
1129
/**
1130
 *  Remove a file or several files with a mask.
1131
 *  This delete file physically but also database indexes.
1132
 *
1133
 *  @param	string	$file           File to delete or mask of files to delete
1134
 *  @param  int		$disableglob    Disable usage of glob like * so function is an exact delete function that will return error if no file found
1135
 *  @param  int		$nophperrors    Disable all PHP output errors
1136
 *  @param	int		$nohook			Disable all hooks
1137
 *  @param	object	$object			Current object in use
1138
 *  @param	boolean	$allowdotdot	Allow to delete file path with .. inside. Never use this, it is reserved for migration purpose.
1139
 *  @param	int		$indexdatabase	Try to remove also index entries.
1140
 *  @return boolean         		True if no error (file is deleted or if glob is used and there's nothing to delete), False if error
1141
 *  @see dol_delete_dir()
1142
 */
1143
function dol_delete_file($file, $disableglob = 0, $nophperrors = 0, $nohook = 0, $object = null, $allowdotdot = false, $indexdatabase = 1)
1144
{
1145
	global $db, $conf, $user, $langs;
1146
	global $hookmanager;
1147
1148
	// Load translation files required by the page
1149
    $langs->loadLangs(array('other', 'errors'));
1150
1151
	dol_syslog("dol_delete_file file=".$file." disableglob=".$disableglob." nophperrors=".$nophperrors." nohook=".$nohook);
1152
1153
	// Security:
1154
	// We refuse transversal using .. and pipes into filenames.
1155
	if ((! $allowdotdot && preg_match('/\.\./', $file)) || preg_match('/[<>|]/', $file))
1156
	{
1157
		dol_syslog("Refused to delete file ".$file, LOG_WARNING);
1158
		return false;
1159
	}
1160
1161
	if (empty($nohook))
1162
	{
1163
		$hookmanager->initHooks(array('fileslib'));
1164
1165
		$parameters=array(
1166
				'GET' => $_GET,
1167
				'file' => $file,
1168
				'disableglob'=> $disableglob,
1169
				'nophperrors' => $nophperrors
1170
		);
1171
		$reshook=$hookmanager->executeHooks('deleteFile', $parameters, $object);
1172
	}
1173
1174
	if (empty($nohook) && $reshook != 0) // reshook = 0 to do standard actions, 1 = ok, -1 = ko
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reshook does not seem to be defined for all execution paths leading up to this point.
Loading history...
1175
	{
1176
		if ($reshook < 0) return false;
1177
		return true;
1178
	}
1179
	else
1180
	{
1181
		$error=0;
1182
1183
		//print "x".$file." ".$disableglob;exit;
1184
		$file_osencoded=dol_osencode($file);    // New filename encoded in OS filesystem encoding charset
1185
		if (empty($disableglob) && ! empty($file_osencoded))
1186
		{
1187
			$ok=true;
1188
			$globencoded=str_replace('[', '\[', $file_osencoded);
1189
			$globencoded=str_replace(']', '\]', $globencoded);
1190
			$listofdir=glob($globencoded);
1191
			if (! empty($listofdir) && is_array($listofdir))
1192
			{
1193
				foreach ($listofdir as $filename)
1194
				{
1195
					if ($nophperrors) $ok=@unlink($filename);
1196
					else $ok=unlink($filename);
1197
					if ($ok)
1198
					{
1199
						dol_syslog("Removed file ".$filename, LOG_DEBUG);
1200
1201
						// Delete entry into ecm database
1202
						$rel_filetodelete = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $filename);
1203
						if (! preg_match('/(\/temp\/|\/thumbs\/|\.meta$)/', $rel_filetodelete))     // If not a tmp file
1204
						{
1205
							$rel_filetodelete = preg_replace('/^[\\/]/', '', $rel_filetodelete);
1206
1207
							if (is_object($db) && $indexdatabase)		// $db may not be defined when lib is in a context with define('NOREQUIREDB',1)
1208
							{
1209
								dol_syslog("Try to remove also entries in database for full relative path = ".$rel_filetodelete, LOG_DEBUG);
1210
								include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1211
								$ecmfile=new EcmFiles($db);
1212
								$result = $ecmfile->fetch(0, '', $rel_filetodelete);
1213
								if ($result >= 0 && $ecmfile->id > 0)
1214
								{
1215
									$result = $ecmfile->delete($user);
1216
								}
1217
								if ($result < 0)
1218
								{
1219
									setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
1220
								}
1221
							}
1222
						}
1223
					}
1224
					else dol_syslog("Failed to remove file ".$filename, LOG_WARNING);
1225
					// TODO Failure to remove can be because file was already removed or because of permission
1226
					// If error because it does not exists, we should return true, and we should return false if this is a permission problem
1227
				}
1228
			}
1229
			else dol_syslog("No files to delete found", LOG_DEBUG);
1230
		}
1231
		else
1232
		{
1233
			$ok=false;
1234
			if ($nophperrors) $ok=@unlink($file_osencoded);
1235
			else $ok=unlink($file_osencoded);
1236
			if ($ok) dol_syslog("Removed file ".$file_osencoded, LOG_DEBUG);
1237
			else dol_syslog("Failed to remove file ".$file_osencoded, LOG_WARNING);
1238
		}
1239
1240
		return $ok;
1241
	}
1242
}
1243
1244
/**
1245
 *  Remove a directory (not recursive, so content must be empty).
1246
 *  If directory is not empty, return false
1247
 *
1248
 *  @param	string	$dir            Directory to delete
1249
 *  @param  int		$nophperrors    Disable all PHP output errors
1250
 *  @return boolean         		True if success, false if error
1251
 *  @see dol_delete_file() dolCopyDir()
1252
 */
1253
function dol_delete_dir($dir, $nophperrors = 0)
1254
{
1255
	// Security:
1256
	// We refuse transversal using .. and pipes into filenames.
1257
	if (preg_match('/\.\./', $dir) || preg_match('/[<>|]/', $dir))
1258
	{
1259
		dol_syslog("Refused to delete dir ".$dir, LOG_WARNING);
1260
		return false;
1261
	}
1262
1263
	$dir_osencoded=dol_osencode($dir);
1264
	return ($nophperrors?@rmdir($dir_osencoded):rmdir($dir_osencoded));
1265
}
1266
1267
/**
1268
 *  Remove a directory $dir and its subdirectories (or only files and subdirectories)
1269
 *
1270
 *  @param	string	$dir            Dir to delete
1271
 *  @param  int		$count          Counter to count nb of elements found to delete
1272
 *  @param  int		$nophperrors    Disable all PHP output errors
1273
 *  @param	int		$onlysub		Delete only files and subdir, not main directory
1274
 *  @param  int		$countdeleted   Counter to count nb of elements found really deleted
1275
 *  @return int             		Number of files and directory we try to remove. NB really removed is returned into var by reference $countdeleted.
1276
 */
1277
function dol_delete_dir_recursive($dir, $count = 0, $nophperrors = 0, $onlysub = 0, &$countdeleted = 0)
1278
{
1279
	dol_syslog("functions.lib:dol_delete_dir_recursive ".$dir, LOG_DEBUG);
1280
	if (dol_is_dir($dir))
1281
	{
1282
		$dir_osencoded=dol_osencode($dir);
1283
		if ($handle = opendir("$dir_osencoded"))
1284
		{
1285
			while (false !== ($item = readdir($handle)))
1286
			{
1287
				if (! utf8_check($item)) $item=utf8_encode($item);  // should be useless
1288
1289
				if ($item != "." && $item != "..")
1290
				{
1291
					if (is_dir(dol_osencode("$dir/$item")) && ! is_link(dol_osencode("$dir/$item")))
1292
					{
1293
						$count=dol_delete_dir_recursive("$dir/$item", $count, $nophperrors, 0, $countdeleted);
1294
					}
1295
					else
1296
					{
1297
						$result=dol_delete_file("$dir/$item", 1, $nophperrors);
1298
						$count++;
1299
						if ($result) $countdeleted++;
1300
						//else print 'Error on '.$item."\n";
1301
					}
1302
				}
1303
			}
1304
			closedir($handle);
1305
1306
			if (empty($onlysub))
1307
			{
1308
				$result=dol_delete_dir($dir, $nophperrors);
1309
				$count++;
1310
				if ($result) $countdeleted++;
1311
				//else print 'Error on '.$dir."\n";
1312
			}
1313
		}
1314
	}
1315
1316
	return $count;
1317
}
1318
1319
1320
/**
1321
 *  Delete all preview files linked to object instance.
1322
 *  Note that preview image of PDF files is generated when required, by dol_banner_tab() for example.
1323
 *
1324
 *  @param	object	$object		Object to clean
1325
 *  @return	int					0 if error, 1 if OK
1326
 *  @see dol_convert_file()
1327
 */
1328
function dol_delete_preview($object)
1329
{
1330
	global $langs,$conf;
1331
1332
	// Define parent dir of elements
1333
	$element = $object->element;
1334
1335
	if ($object->element == 'order_supplier')		$dir = $conf->fournisseur->commande->dir_output;
1336
	elseif ($object->element == 'invoice_supplier')	$dir = $conf->fournisseur->facture->dir_output;
1337
	elseif ($object->element == 'project')			$dir = $conf->projet->dir_output;
1338
	elseif ($object->element == 'shipping')			$dir = $conf->expedition->dir_output.'/sending';
1339
	elseif ($object->element == 'delivery')			$dir = $conf->expedition->dir_output.'/receipt';
1340
	elseif ($object->element == 'fichinter')		$dir = $conf->ficheinter->dir_output;
1341
	else $dir=empty($conf->$element->dir_output)?'':$conf->$element->dir_output;
1342
1343
	if (empty($dir)) return 'ErrorObjectNoSupportedByFunction';
1344
1345
	$refsan = dol_sanitizeFileName($object->ref);
1346
	$dir = $dir . "/" . $refsan ;
1347
	$filepreviewnew = $dir . "/" . $refsan . ".pdf_preview.png";
1348
	$filepreviewnewbis = $dir . "/" . $refsan . ".pdf_preview-0.png";
1349
	$filepreviewold = $dir . "/" . $refsan . ".pdf.png";
1350
1351
	// For new preview files
1352
	if (file_exists($filepreviewnew) && is_writable($filepreviewnew))
1353
	{
1354
		if (! dol_delete_file($filepreviewnew, 1))
1355
		{
1356
			$object->error=$langs->trans("ErrorFailedToDeleteFile", $filepreviewnew);
1357
			return 0;
1358
		}
1359
	}
1360
	if (file_exists($filepreviewnewbis) && is_writable($filepreviewnewbis))
1361
	{
1362
		if (! dol_delete_file($filepreviewnewbis, 1))
1363
		{
1364
			$object->error=$langs->trans("ErrorFailedToDeleteFile", $filepreviewnewbis);
1365
			return 0;
1366
		}
1367
	}
1368
	// For old preview files
1369
	if (file_exists($filepreviewold) && is_writable($filepreviewold))
1370
	{
1371
		if (! dol_delete_file($filepreviewold, 1))
1372
		{
1373
			$object->error=$langs->trans("ErrorFailedToDeleteFile", $filepreviewold);
1374
			return 0;
1375
		}
1376
	}
1377
	else
1378
	{
1379
		$multiple = $filepreviewold . ".";
1380
		for ($i = 0; $i < 20; $i++)
1381
		{
1382
			$preview = $multiple.$i;
1383
1384
			if (file_exists($preview) && is_writable($preview))
1385
			{
1386
				if ( ! dol_delete_file($preview, 1) )
1387
				{
1388
					$object->error=$langs->trans("ErrorFailedToOpenFile", $preview);
1389
					return 0;
1390
				}
1391
			}
1392
		}
1393
	}
1394
1395
	return 1;
1396
}
1397
1398
/**
1399
 *	Create a meta file with document file into same directory.
1400
 *	This make "grep" search possible.
1401
 *  This feature to generate the meta file is enabled only if option MAIN_DOC_CREATE_METAFILE is set.
1402
 *
1403
 *	@param	CommonObject	$object		Object
1404
 *	@return	int							0 if do nothing, >0 if we update meta file too, <0 if KO
1405
 */
1406
function dol_meta_create($object)
1407
{
1408
	global $conf;
1409
1410
	// Create meta file
1411
	if (empty($conf->global->MAIN_DOC_CREATE_METAFILE)) return 0;	// By default, no metafile.
1412
1413
	// Define parent dir of elements
1414
	$element=$object->element;
1415
1416
	if ($object->element == 'order_supplier')		$dir = $conf->fournisseur->dir_output.'/commande';
1417
	elseif ($object->element == 'invoice_supplier')	$dir = $conf->fournisseur->dir_output.'/facture';
1418
	elseif ($object->element == 'project')			$dir = $conf->projet->dir_output;
1419
	elseif ($object->element == 'shipping')			$dir = $conf->expedition->dir_output.'/sending';
1420
	elseif ($object->element == 'delivery')			$dir = $conf->expedition->dir_output.'/receipt';
1421
	elseif ($object->element == 'fichinter')		$dir = $conf->ficheinter->dir_output;
1422
	else $dir=empty($conf->$element->dir_output)?'':$conf->$element->dir_output;
1423
1424
	if ($dir)
1425
	{
1426
		$object->fetch_thirdparty();
1427
1428
		$objectref = dol_sanitizeFileName($object->ref);
1429
		$dir = $dir . "/" . $objectref;
1430
		$file = $dir . "/" . $objectref . ".meta";
1431
1432
		if (! is_dir($dir))
1433
		{
1434
			dol_mkdir($dir);
1435
		}
1436
1437
		if (is_dir($dir))
1438
		{
1439
			$nblines = count($object->lines);
1440
			$client = $object->thirdparty->name . " " . $object->thirdparty->address . " " . $object->thirdparty->zip . " " . $object->thirdparty->town;
1441
			$meta = "REFERENCE=\"" . $object->ref . "\"
1442
			DATE=\"" . dol_print_date($object->date, '') . "\"
1443
			NB_ITEMS=\"" . $nblines . "\"
1444
			CLIENT=\"" . $client . "\"
1445
			AMOUNT_EXCL_TAX=\"" . $object->total_ht . "\"
1446
			AMOUNT=\"" . $object->total_ttc . "\"\n";
1447
1448
			for ($i = 0 ; $i < $nblines ; $i++)
1449
			{
1450
				//Pour les articles
1451
				$meta .= "ITEM_" . $i . "_QUANTITY=\"" . $object->lines[$i]->qty . "\"
1452
				ITEM_" . $i . "_AMOUNT_WO_TAX=\"" . $object->lines[$i]->total_ht . "\"
1453
				ITEM_" . $i . "_VAT=\"" .$object->lines[$i]->tva_tx . "\"
1454
				ITEM_" . $i . "_DESCRIPTION=\"" . str_replace("\r\n", "", nl2br($object->lines[$i]->desc)) . "\"
1455
				";
1456
			}
1457
		}
1458
1459
		$fp = fopen($file, "w");
1460
		fputs($fp, $meta);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $meta does not seem to be defined for all execution paths leading up to this point.
Loading history...
1461
		fclose($fp);
1462
		if (! empty($conf->global->MAIN_UMASK))
1463
		@chmod($file, octdec($conf->global->MAIN_UMASK));
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1463
		/** @scrutinizer ignore-unhandled */ @chmod($file, octdec($conf->global->MAIN_UMASK));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1464
1465
		return 1;
1466
	}
1467
	else
1468
	{
1469
		dol_syslog('FailedToDetectDirInDolMetaCreateFor'.$object->element, LOG_WARNING);
1470
	}
1471
1472
	return 0;
1473
}
1474
1475
1476
1477
/**
1478
 * Scan a directory and init $_SESSION to manage uploaded files with list of all found files.
1479
 * Note: Only email module seems to use this. Other feature initialize the $_SESSION doing $formmail->clear_attached_files(); $formmail->add_attached_files()
1480
 *
1481
 * @param	string	$pathtoscan				Path to scan
1482
 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1483
 * @return	void
1484
 */
1485
function dol_init_file_process($pathtoscan = '', $trackid = '')
1486
{
1487
	$listofpaths=array();
1488
	$listofnames=array();
1489
	$listofmimes=array();
1490
1491
	if ($pathtoscan)
1492
	{
1493
		$listoffiles=dol_dir_list($pathtoscan, 'files');
1494
		foreach($listoffiles as $key => $val)
1495
		{
1496
			$listofpaths[]=$val['fullname'];
1497
			$listofnames[]=$val['name'];
1498
			$listofmimes[]=dol_mimetype($val['name']);
1499
		}
1500
	}
1501
	$keytoavoidconflict = empty($trackid)?'':'-'.$trackid;
1502
	$_SESSION["listofpaths".$keytoavoidconflict]=join(';', $listofpaths);
1503
	$_SESSION["listofnames".$keytoavoidconflict]=join(';', $listofnames);
1504
	$_SESSION["listofmimes".$keytoavoidconflict]=join(';', $listofmimes);
1505
}
1506
1507
1508
/**
1509
 * 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.
1510
 * All information used are in db, conf, langs, user and _FILES.
1511
 * Note: This function can be used only into a HTML page context.
1512
 *
1513
 * @param	string	$upload_dir				Directory where to store uploaded file (note: used to forge $destpath = $upload_dir + filename)
1514
 * @param	int		$allowoverwrite			1=Allow overwrite existing file
1515
 * @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.
1516
 * @param	string	$varfiles				_FILES var name
1517
 * @param	string	$savingdocmask			Mask to use to define output filename. For example 'XXXXX-__YYYYMMDD__-__file__'
1518
 * @param	string	$link					Link to add (to add a link instead of a file)
1519
 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1520
 * @param	int		$generatethumbs			1=Generate also thumbs for uploaded image files
1521
 * @return	int                             <=0 if KO, >0 if OK
1522
 */
1523
function dol_add_file_process($upload_dir, $allowoverwrite = 0, $donotupdatesession = 0, $varfiles = 'addedfile', $savingdocmask = '', $link = null, $trackid = '', $generatethumbs = 1)
1524
{
1525
	global $db,$user,$conf,$langs;
1526
1527
	$res = 0;
1528
1529
	if (! empty($_FILES[$varfiles])) // For view $_FILES[$varfiles]['error']
1530
	{
1531
		dol_syslog('dol_add_file_process upload_dir='.$upload_dir.' allowoverwrite='.$allowoverwrite.' donotupdatesession='.$donotupdatesession.' savingdocmask='.$savingdocmask, LOG_DEBUG);
1532
		if (dol_mkdir($upload_dir) >= 0)
1533
		{
1534
			$TFile = $_FILES[$varfiles];
1535
			if (!is_array($TFile['name']))
1536
			{
1537
				foreach ($TFile as $key => &$val)
1538
				{
1539
					$val = array($val);
1540
				}
1541
			}
1542
1543
			$nbfile = count($TFile['name']);
1544
			$nbok = 0;
1545
			for ($i = 0; $i < $nbfile; $i++)
1546
			{
1547
				// Define $destfull (path to file including filename) and $destfile (only filename)
1548
				$destfull=$upload_dir . "/" . $TFile['name'][$i];
1549
				$destfile=$TFile['name'][$i];
1550
1551
				if ($savingdocmask)
1552
				{
1553
					$destfull=$upload_dir . "/" . preg_replace('/__file__/', $TFile['name'][$i], $savingdocmask);
1554
					$destfile=preg_replace('/__file__/', $TFile['name'][$i], $savingdocmask);
1555
				}
1556
1557
				// dol_sanitizeFileName the file name and lowercase extension
1558
				$info = pathinfo($destfull);
1559
				$destfull = $info['dirname'].'/'.dol_sanitizeFileName($info['filename'].($info['extension']!='' ? ('.'.strtolower($info['extension'])) : ''));
1560
				$info = pathinfo($destfile);
1561
1562
				$destfile = dol_sanitizeFileName($info['filename'].($info['extension']!='' ? ('.'.strtolower($info['extension'])) : ''));
1563
1564
				// We apply dol_string_nohtmltag also to clean file names (this remove duplicate spaces) because
1565
				// this function is also applied when we make try to download file (by the GETPOST(filename, 'alphanohtml') call).
1566
				$destfile = dol_string_nohtmltag($destfile);
1567
				$destfull = dol_string_nohtmltag($destfull);
1568
1569
				$resupload = dol_move_uploaded_file($TFile['tmp_name'][$i], $destfull, $allowoverwrite, 0, $TFile['error'][$i], 0, $varfiles);
1570
1571
				if (is_numeric($resupload) && $resupload > 0)   // $resupload can be 'ErrorFileAlreadyExists'
1572
				{
1573
					global $maxwidthsmall, $maxheightsmall, $maxwidthmini, $maxheightmini;
1574
1575
					include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
1576
1577
					// Generate thumbs.
1578
					if ($generatethumbs)
1579
					{
1580
						if (image_format_supported($destfull) == 1)
1581
						{
1582
							// Create thumbs
1583
							// We can't use $object->addThumbs here because there is no $object known
1584
1585
							// Used on logon for example
1586
							$imgThumbSmall = vignette($destfull, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
1587
							// Create mini thumbs for image (Ratio is near 16/9)
1588
							// Used on menu or for setup page for example
1589
							$imgThumbMini = vignette($destfull, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
1590
						}
1591
					}
1592
1593
					// Update session
1594
					if (empty($donotupdatesession))
1595
					{
1596
						include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
1597
						$formmail = new FormMail($db);
1598
						$formmail->trackid = $trackid;
1599
						$formmail->add_attached_files($destfull, $destfile, $TFile['type'][$i]);
1600
					}
1601
1602
					// Update table of files
1603
					if ($donotupdatesession == 1)
1604
					{
1605
						$result = addFileIntoDatabaseIndex($upload_dir, basename($destfile), $TFile['name'][$i], 'uploaded', 0);
1606
						if ($result < 0)
1607
						{
1608
							setEventMessages('FailedToAddFileIntoDatabaseIndex', '', 'warnings');
1609
						}
1610
					}
1611
1612
					$nbok++;
1613
				}
1614
				else
1615
				{
1616
					$langs->load("errors");
1617
					if ($resupload < 0)	// Unknown error
1618
					{
1619
						setEventMessages($langs->trans("ErrorFileNotUploaded"), null, 'errors');
1620
					}
1621
					elseif (preg_match('/ErrorFileIsInfectedWithAVirus/', $resupload))	// Files infected by a virus
1622
					{
1623
						setEventMessages($langs->trans("ErrorFileIsInfectedWithAVirus"), null, 'errors');
1624
					}
1625
					else	// Known error
1626
					{
1627
						setEventMessages($langs->trans($resupload), null, 'errors');
1628
					}
1629
				}
1630
			}
1631
			if ($nbok > 0)
1632
			{
1633
				$res = 1;
1634
				setEventMessages($langs->trans("FileTransferComplete"), null, 'mesgs');
1635
			}
1636
		}
1637
	} elseif ($link) {
1638
		require_once DOL_DOCUMENT_ROOT . '/core/class/link.class.php';
1639
		$linkObject = new Link($db);
1640
		$linkObject->entity = $conf->entity;
1641
		$linkObject->url = $link;
1642
		$linkObject->objecttype = GETPOST('objecttype', 'alpha');
1643
		$linkObject->objectid = GETPOST('objectid', 'int');
1644
		$linkObject->label = GETPOST('label', 'alpha');
1645
		$res = $linkObject->create($user);
1646
		$langs->load('link');
1647
		if ($res > 0) {
1648
			setEventMessages($langs->trans("LinkComplete"), null, 'mesgs');
1649
		} else {
1650
			setEventMessages($langs->trans("ErrorFileNotLinked"), null, 'errors');
1651
		}
1652
	}
1653
	else
1654
	{
1655
		$langs->load("errors");
1656
		setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentities("File")), null, 'errors');
1657
	}
1658
1659
	return $res;
1660
}
1661
1662
1663
/**
1664
 * Remove an uploaded file (for example after submitting a new file a mail form).
1665
 * All information used are in db, conf, langs, user and _FILES.
1666
 *
1667
 * @param	int		$filenb					File nb to delete
1668
 * @param	int		$donotupdatesession		-1 or 1 = Do not update _SESSION variable
1669
 * @param   int		$donotdeletefile        1=Do not delete physically file
1670
 * @param   string  $trackid                Track id (used to prefix name of session vars to avoid conflict)
1671
 * @return	void
1672
 */
1673
function dol_remove_file_process($filenb, $donotupdatesession = 0, $donotdeletefile = 1, $trackid = '')
1674
{
1675
	global $db,$user,$conf,$langs,$_FILES;
1676
1677
	$keytodelete=$filenb;
1678
	$keytodelete--;
1679
1680
	$listofpaths=array();
1681
	$listofnames=array();
1682
	$listofmimes=array();
1683
	$keytoavoidconflict = empty($trackid)?'':'-'.$trackid;
1684
	if (! empty($_SESSION["listofpaths".$keytoavoidconflict])) $listofpaths=explode(';', $_SESSION["listofpaths".$keytoavoidconflict]);
1685
	if (! empty($_SESSION["listofnames".$keytoavoidconflict])) $listofnames=explode(';', $_SESSION["listofnames".$keytoavoidconflict]);
1686
	if (! empty($_SESSION["listofmimes".$keytoavoidconflict])) $listofmimes=explode(';', $_SESSION["listofmimes".$keytoavoidconflict]);
1687
1688
	if ($keytodelete >= 0)
1689
	{
1690
		$pathtodelete=$listofpaths[$keytodelete];
1691
		$filetodelete=$listofnames[$keytodelete];
1692
		if (empty($donotdeletefile)) $result = dol_delete_file($pathtodelete, 1);  // The delete of ecm database is inside the function dol_delete_file
1693
		else $result=0;
1694
		if ($result >= 0)
1695
		{
1696
			if (empty($donotdeletefile))
1697
			{
1698
				$langs->load("other");
1699
				setEventMessages($langs->trans("FileWasRemoved", $filetodelete), null, 'mesgs');
1700
			}
1701
			if (empty($donotupdatesession))
1702
			{
1703
				include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
1704
				$formmail = new FormMail($db);
1705
				$formmail->trackid = $trackid;
1706
				$formmail->remove_attached_files($keytodelete);
1707
			}
1708
		}
1709
	}
1710
}
1711
1712
1713
/**
1714
 *  Add a file into database index.
1715
 *  Called by dol_add_file_process when uploading a file and on other cases.
1716
 *  See also commonGenerateDocument that also add/update database index when a file is generated.
1717
 *
1718
 *  @param      string	$dir			Directory name (full real path without ending /)
1719
 *  @param		string	$file			File name
1720
 *  @param		string	$fullpathorig	Full path of origin for file (can be '')
1721
 *  @param		string	$mode			How file was created ('uploaded', 'generated', ...)
1722
 *  @param		int		$setsharekey	Set also the share key
1723
 *	@return		int						<0 if KO, 0 if nothing done, >0 if OK
1724
 */
1725
function addFileIntoDatabaseIndex($dir, $file, $fullpathorig = '', $mode = 'uploaded', $setsharekey = 0)
1726
{
1727
	global $db, $user;
1728
1729
	$result = 0;
1730
1731
	$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
1732
1733
	if (! preg_match('/[\\/]temp[\\/]|[\\/]thumbs|\.meta$/', $rel_dir))     // If not a tmp dir
1734
	{
1735
		$filename = basename($file);
1736
		$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
1737
		$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
1738
1739
		include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
1740
		$ecmfile=new EcmFiles($db);
1741
		$ecmfile->filepath = $rel_dir;
1742
		$ecmfile->filename = $filename;
1743
		$ecmfile->label = md5_file(dol_osencode($dir.'/'.$file));	// MD5 of file content
1744
		$ecmfile->fullpath_orig = $fullpathorig;
1745
		$ecmfile->gen_or_uploaded = $mode;
1746
		$ecmfile->description = '';    // indexed content
1747
		$ecmfile->keyword = '';        // keyword content
1748
		if ($setsharekey)
1749
		{
1750
			require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
1751
			$ecmfile->share = getRandomPassword(true);
1752
		}
1753
1754
		$result = $ecmfile->create($user);
1755
		if ($result < 0)
1756
		{
1757
			dol_syslog($ecmfile->error);
1758
		}
1759
	}
1760
1761
	return $result;
1762
}
1763
1764
1765
/**
1766
 *  Delete files into database index using search criterias.
1767
 *
1768
 *  @param      string	$dir			Directory name (full real path without ending /)
1769
 *  @param		string	$file			File name
1770
 *  @param		string	$mode			How file was created ('uploaded', 'generated', ...)
1771
 *	@return		int						<0 if KO, 0 if nothing done, >0 if OK
1772
 */
1773
function deleteFilesIntoDatabaseIndex($dir, $file, $mode = 'uploaded')
1774
{
1775
	global $conf, $db, $user;
1776
1777
	$error = 0;
1778
1779
	if (empty($dir))
1780
	{
1781
		dol_syslog("deleteFilesIntoDatabaseIndex: dir parameter can't be empty", LOG_ERR);
1782
		return -1;
1783
	}
1784
1785
	$db->begin();
1786
1787
	$rel_dir = preg_replace('/^'.preg_quote(DOL_DATA_ROOT, '/').'/', '', $dir);
1788
1789
	$filename = basename($file);
1790
	$rel_dir = preg_replace('/[\\/]$/', '', $rel_dir);
1791
	$rel_dir = preg_replace('/^[\\/]/', '', $rel_dir);
1792
1793
	if (! $error)
1794
	{
1795
		$sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'ecm_files';
1796
		$sql.= ' WHERE entity = '.$conf->entity;
1797
		$sql.= " AND filepath = '" . $db->escape($rel_dir) . "'";
1798
		if ($file) $sql.= " AND filename = '" . $db->escape($file) . "'";
1799
		if ($mode) $sql.= " AND gen_or_uploaded = '" . $db->escape($mode) . "'";
1800
1801
		$resql = $db->query($sql);
1802
		if (!$resql)
1803
		{
1804
			$error++;
1805
			dol_syslog(__METHOD__ . ' ' . $db->lasterror(), LOG_ERR);
1806
		}
1807
	}
1808
1809
	// Commit or rollback
1810
	if ($error) {
1811
		$db->rollback();
1812
		return - 1 * $error;
1813
	} else {
1814
		$db->commit();
1815
		return 1;
1816
	}
1817
}
1818
1819
1820
/**
1821
 * 	Convert an image file into another format.
1822
 *  This need Imagick php extension.
1823
 *
1824
 *  @param	string	$fileinput  Input file name
1825
 *  @param  string	$ext        Format of target file (It is also extension added to file if fileoutput is not provided).
1826
 *  @param	string	$fileoutput	Output filename
1827
 *  @param  string  $page       Page number if we convert a PDF into png
1828
 *  @return	int					<0 if KO, 0=Nothing done, >0 if OK
1829
 */
1830
function dol_convert_file($fileinput, $ext = 'png', $fileoutput = '', $page = '')
1831
{
1832
	global $langs;
1833
	if (class_exists('Imagick'))
1834
	{
1835
	    $image=new Imagick();
1836
		try {
1837
		    $filetoconvert=$fileinput.(($page != '')?'['.$page.']':'');
1838
		    //var_dump($filetoconvert);
1839
		    $ret = $image->readImage($filetoconvert);
1840
		} catch(Exception $e) {
1841
		    $ext = pathinfo($fileinput, PATHINFO_EXTENSION);
1842
		    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);
1843
			return 0;
1844
		}
1845
		if ($ret)
1846
		{
1847
		    $ret = $image->setImageFormat($ext);
1848
			if ($ret)
1849
			{
1850
				if (empty($fileoutput)) $fileoutput=$fileinput.".".$ext;
1851
1852
				$count = $image->getNumberImages();
1853
1854
				if (! dol_is_file($fileoutput) || is_writeable($fileoutput))
1855
				{
1856
					$ret = $image->writeImages($fileoutput, true);
1857
				}
1858
				else
1859
				{
1860
					dol_syslog("Warning: Failed to write cache preview file '.$fileoutput.'. Check permission on file/dir", LOG_ERR);
1861
				}
1862
				if ($ret) return $count;
1863
				else return -3;
1864
			}
1865
			else
1866
			{
1867
				return -2;
1868
			}
1869
		}
1870
		else
1871
		{
1872
			return -1;
1873
		}
1874
	}
1875
	else
1876
	{
1877
		return 0;
1878
	}
1879
}
1880
1881
1882
/**
1883
 * Compress a file
1884
 *
1885
 * @param 	string	$inputfile		Source file name
1886
 * @param 	string	$outputfile		Target file name
1887
 * @param 	string	$mode			'gz' or 'bz' or 'zip'
1888
 * @return	int						<0 if KO, >0 if OK
1889
 */
1890
function dol_compress_file($inputfile, $outputfile, $mode = "gz")
1891
{
1892
	global $conf;
1893
1894
	$foundhandler=0;
1895
1896
	try
1897
	{
1898
		dol_syslog("dol_compress_file mode=".$mode." inputfile=".$inputfile." outputfile=".$outputfile);
1899
1900
		$data = implode("", file(dol_osencode($inputfile)));
1901
		if ($mode == 'gz')     { $foundhandler=1; $compressdata = gzencode($data, 9); }
1902
		elseif ($mode == 'bz') { $foundhandler=1; $compressdata = bzcompress($data, 9); }
1903
		elseif ($mode == 'zip')
1904
		{
1905
			if (class_exists('ZipArchive') && ! empty($conf->global->MAIN_USE_ZIPARCHIVE_FOR_ZIP_COMPRESS))
1906
			{
1907
				$foundhandler=1;
1908
1909
				$rootPath = realpath($inputfile);
1910
1911
				dol_syslog("Class ZipArchive is set so we zip using ZipArchive to zip into ".$outputfile.' rootPath='.$rootPath);
1912
				$zip = new ZipArchive;
1913
1914
				if ($zip->open($outputfile, ZipArchive::CREATE)!==TRUE) {
1915
					$errormsg="Failed to open file ".$outputfile."\n";
1916
					dol_syslog("dol_compress_file failure - ".$errormsg, LOG_ERR);
1917
					return -6;
1918
				}
1919
1920
				// Create recursive directory iterator
1921
				/** @var SplFileInfo[] $files */
1922
				$files = new RecursiveIteratorIterator(
1923
					new RecursiveDirectoryIterator($rootPath),
1924
					RecursiveIteratorIterator::LEAVES_ONLY
1925
					);
1926
1927
				foreach ($files as $name => $file)
1928
				{
1929
					// Skip directories (they would be added automatically)
1930
					if (!$file->isDir())
1931
					{
1932
						// Get real and relative path for current file
1933
						$filePath = $file->getRealPath();
1934
						$relativePath = substr($filePath, strlen($rootPath) + 1);
1935
1936
						// Add current file to archive
1937
						$zip->addFile($filePath, $relativePath);
1938
					}
1939
				}
1940
1941
				// Zip archive will be created only after closing object
1942
				$zip->close();
1943
1944
				dol_syslog("dol_compress_file success - ".count($zip->numFiles)." files");
1945
				return 1;
1946
			}
1947
1948
			if (defined('ODTPHP_PATHTOPCLZIP'))
1949
			{
1950
				$foundhandler=1;
1951
1952
				include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
1953
				$archive = new PclZip($outputfile);
1954
				$result = $archive->add($inputfile, PCLZIP_OPT_REMOVE_PATH, dirname($inputfile));
1955
1956
				if ($result === 0)
1957
				{
1958
					global $errormsg;
1959
					$errormsg=$archive->errorInfo(true);
1960
					dol_syslog("dol_compress_file failure - ".$errormsg, LOG_ERR);
1961
					if ($archive->errorCode() == PCLZIP_ERR_WRITE_OPEN_FAIL)
1962
					{
1963
						dol_syslog("dol_compress_file error PCLZIP_ERR_WRITE_OPEN_FAIL", LOG_ERR);
1964
						return -4;
1965
					}
1966
					return -3;
1967
				}
1968
				else
1969
				{
1970
					dol_syslog("dol_compress_file success - ".count($result)." files");
1971
					return 1;
1972
				}
1973
			}
1974
		}
1975
1976
		if ($foundhandler)
1977
		{
1978
			$fp = fopen($outputfile, "w");
1979
			fwrite($fp, $compressdata);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $compressdata does not seem to be defined for all execution paths leading up to this point.
Loading history...
1980
			fclose($fp);
1981
			return 1;
1982
		}
1983
		else
1984
		{
1985
			dol_syslog("Try to zip with format ".$mode." with no handler for this format", LOG_ERR);
1986
			return -2;
1987
		}
1988
	}
1989
	catch (Exception $e)
1990
	{
1991
		global $langs, $errormsg;
1992
		$langs->load("errors");
1993
		dol_syslog("Failed to open file ".$outputfile, LOG_ERR);
1994
		$errormsg=$langs->trans("ErrorFailedToWriteInDir");
1995
		return -1;
1996
	}
1997
}
1998
1999
/**
2000
 * Uncompress a file
2001
 *
2002
 * @param 	string 	$inputfile		File to uncompress
2003
 * @param 	string	$outputdir		Target dir name
2004
 * @return 	array					array('error'=>'Error code') or array() if no error
2005
 */
2006
function dol_uncompress($inputfile, $outputdir)
2007
{
2008
	global $conf, $langs;
2009
2010
	if (defined('ODTPHP_PATHTOPCLZIP') && empty($conf->global->MAIN_USE_ZIPARCHIVE_FOR_ZIP_UNCOMPRESS))
2011
	{
2012
		dol_syslog("Constant ODTPHP_PATHTOPCLZIP for pclzip library is set to ".ODTPHP_PATHTOPCLZIP.", so we use Pclzip to unzip into ".$outputdir);
2013
		include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
2014
		$archive = new PclZip($inputfile);
2015
		$result=$archive->extract(PCLZIP_OPT_PATH, $outputdir);
2016
		//var_dump($result);
2017
		if (! is_array($result) && $result <= 0) return array('error'=>$archive->errorInfo(true));
1 ignored issue
show
introduced by
The condition is_array($result) is always false.
Loading history...
2018
		else
2019
		{
2020
			$ok=1; $errmsg='';
2021
			// Loop on each file to check result for unzipping file
2022
			foreach($result as $key => $val)
0 ignored issues
show
Bug introduced by
The expression $result of type integer is not traversable.
Loading history...
2023
			{
2024
				if ($val['status'] == 'path_creation_fail')
2025
				{
2026
					$langs->load("errors");
2027
					$ok=0;
2028
					$errmsg=$langs->trans("ErrorFailToCreateDir", $val['filename']);
2029
					break;
2030
				}
2031
			}
2032
2033
			if ($ok) return array();
2034
			else return array('error'=>$errmsg);
2035
		}
2036
	}
2037
2038
	if (class_exists('ZipArchive'))
2039
	{
2040
		dol_syslog("Class ZipArchive is set so we unzip using ZipArchive to unzip into ".$outputdir);
2041
		$zip = new ZipArchive;
2042
		$res = $zip->open($inputfile);
2043
		if ($res === true)
2044
		{
2045
			$zip->extractTo($outputdir.'/');
2046
			$zip->close();
2047
			return array();
2048
		}
2049
		else
2050
		{
2051
			return array('error'=>'ErrUnzipFails');
2052
		}
2053
	}
2054
2055
	return array('error'=>'ErrNoZipEngine');
2056
}
2057
2058
2059
/**
2060
 * Compress a directory and subdirectories into a package file.
2061
 *
2062
 * @param 	string	$inputdir		Source dir name
2063
 * @param 	string	$outputfile		Target file name (output directory must exists and be writable)
2064
 * @param 	string	$mode			'zip'
2065
 * @return	int						<0 if KO, >0 if OK
2066
 */
2067
function dol_compress_dir($inputdir, $outputfile, $mode = "zip")
2068
{
2069
	$foundhandler=0;
2070
2071
	dol_syslog("Try to zip dir ".$inputdir." into ".$outputdir." mode=".$mode);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $outputdir does not exist. Did you maybe mean $outputfile?
Loading history...
2072
2073
	if (! dol_is_dir(dirname($outputfile)) || ! is_writable(dirname($outputfile)))
2074
	{
2075
		global $langs, $errormsg;
2076
		$langs->load("errors");
2077
		$errormsg=$langs->trans("ErrorFailedToWriteInDir", $outputfile);
2078
		return -3;
2079
	}
2080
2081
	try
2082
	{
2083
		if ($mode == 'gz')     { $foundhandler=0; }
2084
		elseif ($mode == 'bz') { $foundhandler=0; }
2085
		elseif ($mode == 'zip')
2086
		{
2087
			/*if (defined('ODTPHP_PATHTOPCLZIP'))
2088
            {
2089
                $foundhandler=0;        // TODO implement this
2090
2091
                include_once ODTPHP_PATHTOPCLZIP.'/pclzip.lib.php';
2092
                $archive = new PclZip($outputfile);
2093
                $archive->add($inputfile, PCLZIP_OPT_REMOVE_PATH, dirname($inputfile));
2094
                //$archive->add($inputfile);
2095
                return 1;
2096
            }
2097
            else*/
2098
			if (class_exists('ZipArchive'))
2099
			{
2100
				$foundhandler=1;
2101
2102
				// Initialize archive object
2103
				$zip = new ZipArchive();
2104
				$result = $zip->open($outputfile, ZipArchive::CREATE | ZipArchive::OVERWRITE);
2105
2106
				// Create recursive directory iterator
2107
				/** @var SplFileInfo[] $files */
2108
				$files = new RecursiveIteratorIterator(
2109
					new RecursiveDirectoryIterator($inputdir),
2110
					RecursiveIteratorIterator::LEAVES_ONLY
2111
					);
2112
2113
				foreach ($files as $name => $file)
2114
				{
2115
					// Skip directories (they would be added automatically)
2116
					if (!$file->isDir())
2117
					{
2118
						// Get real and relative path for current file
2119
						$filePath = $file->getRealPath();
2120
						$relativePath = substr($filePath, strlen($inputdir) + 1);
2121
2122
						// Add current file to archive
2123
						$zip->addFile($filePath, $relativePath);
2124
					}
2125
				}
2126
2127
				// Zip archive will be created only after closing object
2128
				$zip->close();
2129
2130
				return 1;
2131
			}
2132
		}
2133
2134
		if (! $foundhandler)
2135
		{
2136
			dol_syslog("Try to zip with format ".$mode." with no handler for this format", LOG_ERR);
2137
			return -2;
2138
		}
2139
		else
2140
		{
2141
			return 0;
2142
		}
2143
	}
2144
	catch (Exception $e)
2145
	{
2146
		global $langs, $errormsg;
2147
		$langs->load("errors");
2148
		dol_syslog("Failed to open file ".$outputfile, LOG_ERR);
2149
		dol_syslog($e->getMessage(), LOG_ERR);
2150
		$errormsg=$langs->trans("ErrorFailedToWriteInDir", $outputfile);
2151
		return -1;
2152
	}
2153
}
2154
2155
2156
2157
/**
2158
 * Return file(s) into a directory (by default most recent)
2159
 *
2160
 * @param 	string		$dir			Directory to scan
2161
 * @param	string		$regexfilter	Regex filter to restrict list. This regex value must be escaped for '/', since this char is used for preg_match function
2162
 * @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
2163
 * @param	int			$nohook			Disable all hooks
2164
 * @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
2165
 * @return	string						Full path to most recent file
2166
 */
2167
function dol_most_recent_file($dir, $regexfilter = '', $excludefilter = array('(\.meta|_preview.*\.png)$','^\.'), $nohook = false, $mode = '')
2168
{
2169
	$tmparray=dol_dir_list($dir, 'files', 0, $regexfilter, $excludefilter, 'date', SORT_DESC, $mode, $nohook);
2170
	return $tmparray[0];
2171
}
2172
2173
/**
2174
 * Security check when accessing to a document (used by document.php, viewimage.php and webservices)
2175
 *
2176
 * @param	string	$modulepart			Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp')
2177
 * @param	string	$original_file		Relative path with filename, relative to modulepart.
2178
 * @param	string	$entity				Restrict onto entity (0=no restriction)
2179
 * @param  	User	$fuser				User object (forced)
2180
 * @param	string	$refname			Ref of object to check permission for external users (autodetect if not provided)
2181
 * @param   string  $mode               Check permission for 'read' or 'write'
2182
 * @return	mixed						Array with access information : 'accessallowed' & 'sqlprotectagainstexternals' & 'original_file' (as a full path name)
2183
 * @see restrictedArea()
2184
 */
2185
function dol_check_secure_access_document($modulepart, $original_file, $entity, $fuser = '', $refname = '', $mode = 'read')
2186
{
2187
	global $conf, $db, $user;
2188
	global $dolibarr_main_data_root, $dolibarr_main_document_root_alt;
2189
2190
	if (! is_object($fuser)) $fuser=$user;
2191
2192
	if (empty($modulepart)) return 'ErrorBadParameter';
2193
	if (empty($entity))
2194
	{
2195
		if (empty($conf->multicompany->enabled)) $entity=1;
2196
		else $entity=0;
2197
	}
2198
	// Fix modulepart
2199
	if ($modulepart == 'users') $modulepart='user';
2200
2201
	dol_syslog('modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity);
2202
2203
	// We define $accessallowed and $sqlprotectagainstexternals
2204
	$accessallowed=0;
2205
	$sqlprotectagainstexternals='';
2206
	$ret=array();
2207
2208
	// Find the subdirectory name as the reference. For exemple original_file='10/myfile.pdf' -> refname='10'
2209
	if (empty($refname)) $refname=basename(dirname($original_file)."/");
2210
2211
	// Define possible keys to use for permission check
2212
	$lire='lire'; $read='read'; $download='download';
2213
	if ($mode == 'write')
2214
	{
2215
		$lire='creer'; $read='write'; $download='upload';
2216
	}
2217
2218
	// Wrapping for miscellaneous medias files
2219
	if ($modulepart == 'medias' && !empty($dolibarr_main_data_root))
2220
	{
2221
		if (empty($entity) || empty($conf->medias->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2222
		$accessallowed=1;
2223
		$original_file=$conf->medias->multidir_output[$entity].'/'.$original_file;
2224
	}
2225
	// Wrapping for *.log files, like when used with url http://.../document.php?modulepart=logs&file=dolibarr.log
2226
	elseif ($modulepart == 'logs' && !empty($dolibarr_main_data_root))
2227
	{
2228
		$accessallowed=($user->admin && basename($original_file) == $original_file && preg_match('/^dolibarr.*\.log$/', basename($original_file)));
2229
		$original_file=$dolibarr_main_data_root.'/'.$original_file;
2230
	}
2231
	// Wrapping for *.log files, like when used with url http://.../document.php?modulepart=logs&file=dolibarr.log
2232
	elseif ($modulepart == 'doctemplateswebsite' && !empty($dolibarr_main_data_root))
2233
	{
2234
		$accessallowed=($fuser->rights->website->write && preg_match('/\.jpg$/i', basename($original_file)));
2235
		$original_file=$dolibarr_main_data_root.'/doctemplates/websites/'.$original_file;
2236
	}
2237
	// Wrapping for *.zip files, like when used with url http://.../document.php?modulepart=packages&file=module_myfile.zip
2238
	elseif ($modulepart == 'packages' && !empty($dolibarr_main_data_root))
2239
	{
2240
		// Dir for custom dirs
2241
		$tmp=explode(',', $dolibarr_main_document_root_alt);
2242
		$dirins = $tmp[0];
2243
2244
		$accessallowed=($user->admin && preg_match('/^module_.*\.zip$/', basename($original_file)));
2245
		$original_file=$dirins.'/'.$original_file;
2246
	}
2247
	// Wrapping for some images
2248
	elseif ($modulepart == 'mycompany' && !empty($conf->mycompany->dir_output))
2249
	{
2250
		$accessallowed=1;
2251
		$original_file=$conf->mycompany->dir_output.'/'.$original_file;
2252
	}
2253
	// Wrapping for users photos
2254
	elseif ($modulepart == 'userphoto' && !empty($conf->user->dir_output))
2255
	{
2256
		$accessallowed=1;
2257
		$original_file=$conf->user->dir_output.'/'.$original_file;
2258
	}
2259
	// Wrapping for members photos
2260
	elseif ($modulepart == 'memberphoto' && !empty($conf->adherent->dir_output))
2261
	{
2262
		$accessallowed=1;
2263
		$original_file=$conf->adherent->dir_output.'/'.$original_file;
2264
	}
2265
	// Wrapping pour les apercu factures
2266
	elseif ($modulepart == 'apercufacture' && !empty($conf->facture->dir_output))
2267
	{
2268
		if ($fuser->rights->facture->{$lire}) $accessallowed=1;
2269
		$original_file=$conf->facture->dir_output.'/'.$original_file;
2270
	}
2271
	// Wrapping pour les apercu propal
2272
	elseif ($modulepart == 'apercupropal' && !empty($conf->propal->multidir_output[$entity]))
2273
	{
2274
		if ($fuser->rights->propale->{$lire}) $accessallowed=1;
2275
		$original_file=$conf->propal->multidir_output[$entity].'/'.$original_file;
2276
	}
2277
	// Wrapping pour les apercu commande
2278
	elseif ($modulepart == 'apercucommande' && !empty($conf->commande->dir_output))
2279
	{
2280
		if ($fuser->rights->commande->{$lire}) $accessallowed=1;
2281
		$original_file=$conf->commande->dir_output.'/'.$original_file;
2282
	}
2283
	// Wrapping pour les apercu intervention
2284
	elseif (($modulepart == 'apercufichinter' || $modulepart == 'apercuficheinter') && !empty($conf->ficheinter->dir_output))
2285
	{
2286
		if ($fuser->rights->ficheinter->{$lire}) $accessallowed=1;
2287
		$original_file=$conf->ficheinter->dir_output.'/'.$original_file;
2288
	}
2289
	// Wrapping pour les apercu conat
2290
	elseif (($modulepart == 'apercucontract') && !empty($conf->contrat->dir_output))
2291
	{
2292
		if ($fuser->rights->contrat->{$lire}) $accessallowed=1;
2293
		$original_file=$conf->contrat->dir_output.'/'.$original_file;
2294
	}
2295
	// Wrapping pour les apercu supplier proposal
2296
	elseif (($modulepart == 'apercusupplier_proposal' || $modulepart == 'apercusupplier_proposal') && !empty($conf->supplier_proposal->dir_output))
2297
	{
2298
		if ($fuser->rights->supplier_proposal->{$lire}) $accessallowed=1;
2299
		$original_file=$conf->supplier_proposal->dir_output.'/'.$original_file;
2300
	}
2301
	// Wrapping pour les apercu supplier order
2302
	elseif (($modulepart == 'apercusupplier_order' || $modulepart == 'apercusupplier_order') && !empty($conf->fournisseur->commande->dir_output))
2303
	{
2304
		if ($fuser->rights->fournisseur->commande->{$lire}) $accessallowed=1;
2305
		$original_file=$conf->fournisseur->commande->dir_output.'/'.$original_file;
2306
	}
2307
	// Wrapping pour les apercu supplier invoice
2308
	elseif (($modulepart == 'apercusupplier_invoice' || $modulepart == 'apercusupplier_invoice') && !empty($conf->fournisseur->facture->dir_output))
2309
	{
2310
		if ($fuser->rights->fournisseur->facture->{$lire}) $accessallowed=1;
2311
		$original_file=$conf->fournisseur->facture->dir_output.'/'.$original_file;
2312
	}
2313
	// Wrapping pour les apercu supplier invoice
2314
	elseif (($modulepart == 'apercuexpensereport') && !empty($conf->expensereport->dir_output))
2315
	{
2316
		if ($fuser->rights->expensereport->{$lire}) $accessallowed=1;
2317
		$original_file=$conf->expensereport->dir_output.'/'.$original_file;
2318
	}
2319
	// Wrapping pour les images des stats propales
2320
	elseif ($modulepart == 'propalstats' && !empty($conf->propal->multidir_temp[$entity]))
2321
	{
2322
		if ($fuser->rights->propale->{$lire}) $accessallowed=1;
2323
		$original_file=$conf->propal->multidir_temp[$entity].'/'.$original_file;
2324
	}
2325
	// Wrapping pour les images des stats commandes
2326
	elseif ($modulepart == 'orderstats' && !empty($conf->commande->dir_temp))
2327
	{
2328
		if ($fuser->rights->commande->{$lire}) $accessallowed=1;
2329
		$original_file=$conf->commande->dir_temp.'/'.$original_file;
2330
	}
2331
	elseif ($modulepart == 'orderstatssupplier' && !empty($conf->fournisseur->dir_output))
2332
	{
2333
		if ($fuser->rights->fournisseur->commande->{$lire}) $accessallowed=1;
2334
		$original_file=$conf->fournisseur->commande->dir_temp.'/'.$original_file;
2335
	}
2336
	// Wrapping pour les images des stats factures
2337
	elseif ($modulepart == 'billstats' && !empty($conf->facture->dir_temp))
2338
	{
2339
		if ($fuser->rights->facture->{$lire}) $accessallowed=1;
2340
		$original_file=$conf->facture->dir_temp.'/'.$original_file;
2341
	}
2342
	elseif ($modulepart == 'billstatssupplier' && !empty($conf->fournisseur->dir_output))
2343
	{
2344
		if ($fuser->rights->fournisseur->facture->{$lire}) $accessallowed=1;
2345
		$original_file=$conf->fournisseur->facture->dir_temp.'/'.$original_file;
2346
	}
2347
	// Wrapping pour les images des stats expeditions
2348
	elseif ($modulepart == 'expeditionstats' && !empty($conf->expedition->dir_temp))
2349
	{
2350
		if ($fuser->rights->expedition->{$lire}) $accessallowed=1;
2351
		$original_file=$conf->expedition->dir_temp.'/'.$original_file;
2352
	}
2353
	// Wrapping pour les images des stats expeditions
2354
	elseif ($modulepart == 'tripsexpensesstats' && !empty($conf->deplacement->dir_temp))
2355
	{
2356
		if ($fuser->rights->deplacement->{$lire}) $accessallowed=1;
2357
		$original_file=$conf->deplacement->dir_temp.'/'.$original_file;
2358
	}
2359
	// Wrapping pour les images des stats expeditions
2360
	elseif ($modulepart == 'memberstats' && !empty($conf->adherent->dir_temp))
2361
	{
2362
		if ($fuser->rights->adherent->{$lire}) $accessallowed=1;
2363
		$original_file=$conf->adherent->dir_temp.'/'.$original_file;
2364
	}
2365
	// Wrapping pour les images des stats produits
2366
	elseif (preg_match('/^productstats_/i', $modulepart) && !empty($conf->product->dir_temp))
2367
	{
2368
		if ($fuser->rights->produit->{$lire} || $fuser->rights->service->{$lire}) $accessallowed=1;
2369
		$original_file=(!empty($conf->product->multidir_temp[$entity])?$conf->product->multidir_temp[$entity]:$conf->service->multidir_temp[$entity]).'/'.$original_file;
2370
	}
2371
	// Wrapping for taxes
2372
	elseif ($modulepart == 'tax' && !empty($conf->tax->dir_output))
2373
	{
2374
		if ($fuser->rights->tax->charges->{$lire}) $accessallowed=1;
2375
		$original_file=$conf->tax->dir_output.'/'.$original_file;
2376
	}
2377
	// Wrapping for events
2378
	elseif ($modulepart == 'actions' && !empty($conf->agenda->dir_output))
2379
	{
2380
		if ($fuser->rights->agenda->myactions->{$read}) $accessallowed=1;
2381
		$original_file=$conf->agenda->dir_output.'/'.$original_file;
2382
	}
2383
	// Wrapping for categories
2384
	elseif ($modulepart == 'category' && !empty($conf->categorie->dir_output))
2385
	{
2386
		if (empty($entity) || empty($conf->categorie->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2387
		if ($fuser->rights->categorie->{$lire}) $accessallowed=1;
2388
		$original_file=$conf->categorie->multidir_output[$entity].'/'.$original_file;
2389
	}
2390
	// Wrapping pour les prelevements
2391
	elseif ($modulepart == 'prelevement' && !empty($conf->prelevement->dir_output))
2392
	{
2393
		if ($fuser->rights->prelevement->bons->{$lire} || preg_match('/^specimen/i', $original_file)) $accessallowed=1;
2394
		$original_file=$conf->prelevement->dir_output.'/'.$original_file;
2395
	}
2396
	// Wrapping pour les graph energie
2397
	elseif ($modulepart == 'graph_stock' && !empty($conf->stock->dir_temp))
2398
	{
2399
		$accessallowed=1;
2400
		$original_file=$conf->stock->dir_temp.'/'.$original_file;
2401
	}
2402
	// Wrapping pour les graph fournisseurs
2403
	elseif ($modulepart == 'graph_fourn' && !empty($conf->fournisseur->dir_temp))
2404
	{
2405
		$accessallowed=1;
2406
		$original_file=$conf->fournisseur->dir_temp.'/'.$original_file;
2407
	}
2408
	// Wrapping pour les graph des produits
2409
	elseif ($modulepart == 'graph_product' && !empty($conf->product->dir_temp))
2410
	{
2411
		$accessallowed=1;
2412
		$original_file=$conf->product->multidir_temp[$entity].'/'.$original_file;
2413
	}
2414
	// Wrapping pour les code barre
2415
	elseif ($modulepart == 'barcode')
2416
	{
2417
		$accessallowed=1;
2418
		// If viewimage is called for barcode, we try to output an image on the fly, with no build of file on disk.
2419
		//$original_file=$conf->barcode->dir_temp.'/'.$original_file;
2420
		$original_file='';
2421
	}
2422
	// Wrapping pour les icones de background des mailings
2423
	elseif ($modulepart == 'iconmailing' && !empty($conf->mailing->dir_temp))
2424
	{
2425
		$accessallowed=1;
2426
		$original_file=$conf->mailing->dir_temp.'/'.$original_file;
2427
	}
2428
	// Wrapping pour le scanner
2429
	elseif ($modulepart == 'scanner_user_temp' && !empty($conf->scanner->dir_temp))
2430
	{
2431
		$accessallowed=1;
2432
		$original_file=$conf->scanner->dir_temp.'/'.$fuser->id.'/'.$original_file;
2433
	}
2434
	// Wrapping pour les images fckeditor
2435
	elseif ($modulepart == 'fckeditor' && !empty($conf->fckeditor->dir_output))
2436
	{
2437
		$accessallowed=1;
2438
		$original_file=$conf->fckeditor->dir_output.'/'.$original_file;
2439
	}
2440
2441
	// Wrapping for users
2442
	elseif ($modulepart == 'user' && !empty($conf->user->dir_output))
2443
	{
2444
		$canreaduser=(! empty($fuser->admin) || $fuser->rights->user->user->{$lire});
2445
		if ($fuser->id == (int) $refname) { $canreaduser=1; } // A user can always read its own card
2446
		if ($canreaduser || preg_match('/^specimen/i', $original_file))
2447
		{
2448
			$accessallowed=1;
2449
		}
2450
		$original_file=$conf->user->dir_output.'/'.$original_file;
2451
	}
2452
2453
	// Wrapping for third parties
2454
	elseif (($modulepart == 'company' || $modulepart == 'societe') && !empty($conf->societe->dir_output))
2455
	{
2456
		if (empty($entity) || empty($conf->societe->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2457
		if ($fuser->rights->societe->{$lire} || preg_match('/^specimen/i', $original_file))
2458
		{
2459
			$accessallowed=1;
2460
		}
2461
		$original_file=$conf->societe->multidir_output[$entity].'/'.$original_file;
2462
		$sqlprotectagainstexternals = "SELECT rowid as fk_soc FROM ".MAIN_DB_PREFIX."societe WHERE rowid='".$db->escape($refname)."' AND entity IN (".getEntity('societe').")";
2463
	}
2464
2465
	// Wrapping for contact
2466
	elseif ($modulepart == 'contact' && !empty($conf->societe->dir_output))
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})
2470
		{
2471
			$accessallowed=1;
2472
		}
2473
		$original_file=$conf->societe->multidir_output[$entity].'/contact/'.$original_file;
2474
	}
2475
2476
	// Wrapping for invoices
2477
	elseif (($modulepart == 'facture' || $modulepart == 'invoice') && !empty($conf->facture->dir_output))
2478
	{
2479
		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2480
		{
2481
			$accessallowed=1;
2482
		}
2483
		$original_file=$conf->facture->dir_output.'/'.$original_file;
2484
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2485
	}
2486
	// Wrapping for mass actions
2487
	elseif ($modulepart == 'massfilesarea_proposals' && !empty($conf->propal->multidir_output[$entity]))
2488
	{
2489
		if ($fuser->rights->propal->{$lire} || preg_match('/^specimen/i', $original_file))
2490
		{
2491
			$accessallowed=1;
2492
		}
2493
		$original_file=$conf->propal->multidir_output[$entity].'/temp/massgeneration/'.$user->id.'/'.$original_file;
2494
	}
2495
	elseif ($modulepart == 'massfilesarea_orders')
2496
	{
2497
		if ($fuser->rights->commande->{$lire} || preg_match('/^specimen/i', $original_file))
2498
		{
2499
			$accessallowed=1;
2500
		}
2501
		$original_file=$conf->commande->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2502
	}
2503
	elseif ($modulepart == 'massfilesarea_invoices')
2504
	{
2505
		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2506
		{
2507
			$accessallowed=1;
2508
		}
2509
		$original_file=$conf->facture->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2510
	}
2511
	elseif ($modulepart == 'massfilesarea_expensereport')
2512
	{
2513
		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2514
		{
2515
			$accessallowed=1;
2516
		}
2517
		$original_file=$conf->expensereport->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2518
	}
2519
	elseif ($modulepart == 'massfilesarea_interventions')
2520
	{
2521
		if ($fuser->rights->ficheinter->{$lire} || preg_match('/^specimen/i', $original_file))
2522
		{
2523
			$accessallowed=1;
2524
		}
2525
		$original_file=$conf->ficheinter->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2526
	}
2527
	elseif ($modulepart == 'massfilesarea_supplier_proposal' && !empty($conf->supplier_proposal->dir_output))
2528
	{
2529
		if ($fuser->rights->supplier_proposal->{$lire} || preg_match('/^specimen/i', $original_file))
2530
		{
2531
			$accessallowed=1;
2532
		}
2533
		$original_file=$conf->supplier_proposal->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2534
	}
2535
	elseif ($modulepart == 'massfilesarea_supplier_order')
2536
	{
2537
		if ($fuser->rights->fournisseur->commande->{$lire} || preg_match('/^specimen/i', $original_file))
2538
		{
2539
			$accessallowed=1;
2540
		}
2541
		$original_file=$conf->fournisseur->commande->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2542
	}
2543
	elseif ($modulepart == 'massfilesarea_supplier_invoice')
2544
	{
2545
		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2546
		{
2547
			$accessallowed=1;
2548
		}
2549
		$original_file=$conf->fournisseur->facture->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2550
	}
2551
	elseif ($modulepart == 'massfilesarea_contract' && !empty($conf->contrat->dir_output))
2552
	{
2553
		if ($fuser->rights->contrat->{$lire} || preg_match('/^specimen/i', $original_file))
2554
		{
2555
			$accessallowed=1;
2556
		}
2557
		$original_file=$conf->contrat->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2558
	}
2559
2560
	// Wrapping for interventions
2561
	elseif (($modulepart == 'fichinter' || $modulepart == 'ficheinter') && !empty($conf->ficheinter->dir_output))
2562
	{
2563
		if ($fuser->rights->ficheinter->{$lire} || preg_match('/^specimen/i', $original_file))
2564
		{
2565
			$accessallowed=1;
2566
		}
2567
		$original_file=$conf->ficheinter->dir_output.'/'.$original_file;
2568
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2569
	}
2570
2571
	// Wrapping pour les deplacements et notes de frais
2572
	elseif ($modulepart == 'deplacement' && !empty($conf->deplacement->dir_output))
2573
	{
2574
		if ($fuser->rights->deplacement->{$lire} || preg_match('/^specimen/i', $original_file))
2575
		{
2576
			$accessallowed=1;
2577
		}
2578
		$original_file=$conf->deplacement->dir_output.'/'.$original_file;
2579
		//$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."fichinter WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2580
	}
2581
	// Wrapping pour les propales
2582
	elseif (($modulepart == 'propal' || $modulepart == 'propale') && !empty($conf->propal->multidir_output[$entity]))
2583
	{
2584
		if ($fuser->rights->propale->{$lire} || preg_match('/^specimen/i', $original_file))
2585
		{
2586
			$accessallowed=1;
2587
		}
2588
		$original_file=$conf->propal->multidir_output[$entity].'/'.$original_file;
2589
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."propal WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2590
	}
2591
2592
	// Wrapping pour les commandes
2593
	elseif (($modulepart == 'commande' || $modulepart == 'order') && !empty($conf->commande->dir_output))
2594
	{
2595
		if ($fuser->rights->commande->{$lire} || preg_match('/^specimen/i', $original_file))
2596
		{
2597
			$accessallowed=1;
2598
		}
2599
		$original_file=$conf->commande->dir_output.'/'.$original_file;
2600
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2601
	}
2602
2603
	// Wrapping pour les projets
2604
	elseif ($modulepart == 'project' && !empty($conf->projet->dir_output))
2605
	{
2606
		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file))
2607
		{
2608
			$accessallowed=1;
2609
		}
2610
		$original_file=$conf->projet->dir_output.'/'.$original_file;
2611
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
2612
	}
2613
	elseif ($modulepart == 'project_task' && !empty($conf->projet->dir_output))
2614
	{
2615
		if ($fuser->rights->projet->{$lire} || preg_match('/^specimen/i', $original_file))
2616
		{
2617
			$accessallowed=1;
2618
		}
2619
		$original_file=$conf->projet->dir_output.'/'.$original_file;
2620
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."projet WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('project').")";
2621
	}
2622
2623
	// Wrapping pour les commandes fournisseurs
2624
	elseif (($modulepart == 'commande_fournisseur' || $modulepart == 'order_supplier') && !empty($conf->fournisseur->commande->dir_output))
2625
	{
2626
		if ($fuser->rights->fournisseur->commande->{$lire} || preg_match('/^specimen/i', $original_file))
2627
		{
2628
			$accessallowed=1;
2629
		}
2630
		$original_file=$conf->fournisseur->commande->dir_output.'/'.$original_file;
2631
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2632
	}
2633
2634
	// Wrapping pour les factures fournisseurs
2635
	elseif (($modulepart == 'facture_fournisseur' || $modulepart == 'invoice_supplier') && !empty($conf->fournisseur->facture->dir_output))
2636
	{
2637
		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2638
		{
2639
			$accessallowed=1;
2640
		}
2641
		$original_file=$conf->fournisseur->facture->dir_output.'/'.$original_file;
2642
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture_fourn WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2643
	}
2644
	// Wrapping pour les rapport de paiements
2645
	elseif ($modulepart == 'supplier_payment')
2646
	{
2647
		if ($fuser->rights->fournisseur->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2648
		{
2649
			$accessallowed=1;
2650
		}
2651
		$original_file=$conf->fournisseur->payment->dir_output.'/'.$original_file;
2652
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."paiementfournisseur WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2653
	}
2654
2655
	// Wrapping pour les rapport de paiements
2656
	elseif ($modulepart == 'facture_paiement' && !empty($conf->facture->dir_output))
2657
	{
2658
		if ($fuser->rights->facture->{$lire} || preg_match('/^specimen/i', $original_file))
2659
		{
2660
			$accessallowed=1;
2661
		}
2662
		if ($fuser->societe_id > 0) $original_file=$conf->facture->dir_output.'/payments/private/'.$fuser->id.'/'.$original_file;
2663
		else $original_file=$conf->facture->dir_output.'/payments/'.$original_file;
2664
	}
2665
2666
	// Wrapping for accounting exports
2667
	elseif ($modulepart == 'export_compta' && !empty($conf->accounting->dir_output))
2668
	{
2669
		if ($fuser->rights->accounting->bind->write || preg_match('/^specimen/i', $original_file))
2670
		{
2671
			$accessallowed=1;
2672
		}
2673
		$original_file=$conf->accounting->dir_output.'/'.$original_file;
2674
	}
2675
2676
	// Wrapping pour les expedition
2677
	elseif ($modulepart == 'expedition' && !empty($conf->expedition->dir_output))
2678
	{
2679
		if ($fuser->rights->expedition->{$lire} || preg_match('/^specimen/i', $original_file))
2680
		{
2681
			$accessallowed=1;
2682
		}
2683
		$original_file=$conf->expedition->dir_output."/sending/".$original_file;
2684
	}
2685
	// Wrapping pour les bons de livraison
2686
	elseif ($modulepart == 'livraison' && !empty($conf->expedition->dir_output))
2687
	{
2688
		if ($fuser->rights->expedition->livraison->{$lire} || preg_match('/^specimen/i', $original_file))
2689
		{
2690
			$accessallowed=1;
2691
		}
2692
		$original_file=$conf->expedition->dir_output."/receipt/".$original_file;
2693
	}
2694
2695
	// Wrapping pour les actions
2696
	elseif ($modulepart == 'actions' && !empty($conf->agenda->dir_output))
2697
	{
2698
		if ($fuser->rights->agenda->myactions->{$read} || preg_match('/^specimen/i', $original_file))
2699
		{
2700
			$accessallowed=1;
2701
		}
2702
		$original_file=$conf->agenda->dir_output.'/'.$original_file;
2703
	}
2704
2705
	// Wrapping pour les actions
2706
	elseif ($modulepart == 'actionsreport' && !empty($conf->agenda->dir_temp))
2707
	{
2708
		if ($fuser->rights->agenda->allactions->{$read} || preg_match('/^specimen/i', $original_file))
2709
		{
2710
			$accessallowed=1;
2711
		}
2712
		$original_file = $conf->agenda->dir_temp."/".$original_file;
2713
	}
2714
2715
	// Wrapping pour les produits et services
2716
	elseif ($modulepart == 'product' || $modulepart == 'produit' || $modulepart == 'service' || $modulepart == 'produit|service')
2717
	{
2718
		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');
2719
		if (($fuser->rights->produit->{$lire} || $fuser->rights->service->{$lire}) || preg_match('/^specimen/i', $original_file))
2720
		{
2721
			$accessallowed=1;
2722
		}
2723
		if (! empty($conf->product->enabled)) $original_file=$conf->product->multidir_output[$entity].'/'.$original_file;
2724
		elseif (! empty($conf->service->enabled)) $original_file=$conf->service->multidir_output[$entity].'/'.$original_file;
2725
	}
2726
2727
	// Wrapping pour les lots produits
2728
	elseif ($modulepart == 'product_batch' || $modulepart == 'produitlot')
2729
	{
2730
		if (empty($entity) || (empty($conf->productbatch->multidir_output[$entity]))) return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2731
		if (($fuser->rights->produit->{$lire} ) || preg_match('/^specimen/i', $original_file))
2732
		{
2733
			$accessallowed=1;
2734
		}
2735
		if (! empty($conf->productbatch->enabled)) $original_file=$conf->productbatch->multidir_output[$entity].'/'.$original_file;
2736
	}
2737
2738
	// Wrapping for stock movements
2739
	elseif ($modulepart == 'movement' || $modulepart == 'mouvement')
2740
	{
2741
		if (empty($entity) || empty($conf->stock->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided');
2742
		if (($fuser->rights->stock->{$lire} || $fuser->rights->stock->movement->{$lire} || $fuser->rights->stock->mouvement->{$lire}) || preg_match('/^specimen/i', $original_file))
2743
		{
2744
			$accessallowed=1;
2745
		}
2746
		if (! empty($conf->stock->enabled)) $original_file=$conf->stock->multidir_output[$entity].'/movement/'.$original_file;
2747
	}
2748
2749
	// Wrapping pour les contrats
2750
	elseif ($modulepart == 'contract' && !empty($conf->contrat->dir_output))
2751
	{
2752
		if ($fuser->rights->contrat->{$lire} || preg_match('/^specimen/i', $original_file))
2753
		{
2754
			$accessallowed=1;
2755
		}
2756
		$original_file=$conf->contrat->dir_output.'/'.$original_file;
2757
		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."contrat WHERE ref='".$db->escape($refname)."' AND entity IN (".getEntity('contract').")";
2758
	}
2759
2760
	// Wrapping pour les dons
2761
	elseif ($modulepart == 'donation' && !empty($conf->don->dir_output))
2762
	{
2763
		if ($fuser->rights->don->{$lire} || preg_match('/^specimen/i', $original_file))
2764
		{
2765
			$accessallowed=1;
2766
		}
2767
		$original_file=$conf->don->dir_output.'/'.$original_file;
2768
	}
2769
2770
	// Wrapping pour les dons
2771
	elseif ($modulepart == 'dolresource' && !empty($conf->resource->dir_output))
2772
	{
2773
		if ($fuser->rights->resource->{$read} || preg_match('/^specimen/i', $original_file))
2774
		{
2775
			$accessallowed=1;
2776
		}
2777
		$original_file=$conf->resource->dir_output.'/'.$original_file;
2778
	}
2779
2780
	// Wrapping pour les remises de cheques
2781
	elseif ($modulepart == 'remisecheque' && !empty($conf->bank->dir_output))
2782
	{
2783
		if ($fuser->rights->banque->{$lire} || preg_match('/^specimen/i', $original_file))
2784
		{
2785
			$accessallowed=1;
2786
		}
2787
2788
		$original_file=$conf->bank->dir_output.'/checkdeposits/'.$original_file;		// original_file should contains relative path so include the get_exdir result
2789
	}
2790
2791
	// Wrapping for bank
2792
	elseif (($modulepart == 'banque' || $modulepart == 'bank') && !empty($conf->bank->dir_output))
2793
	{
2794
		if ($fuser->rights->banque->{$lire})
2795
		{
2796
			$accessallowed=1;
2797
		}
2798
		$original_file=$conf->bank->dir_output.'/'.$original_file;
2799
	}
2800
2801
	// Wrapping for export module
2802
	elseif ($modulepart == 'export' && !empty($conf->export->dir_temp))
2803
	{
2804
		// Aucun test necessaire car on force le rep de download sur
2805
		// le rep export qui est propre a l'utilisateur
2806
		$accessallowed=1;
2807
		$original_file=$conf->export->dir_temp.'/'.$fuser->id.'/'.$original_file;
2808
	}
2809
2810
	// Wrapping for import module
2811
	elseif ($modulepart == 'import' && !empty($conf->import->dir_temp))
2812
	{
2813
		$accessallowed=1;
2814
		$original_file=$conf->import->dir_temp.'/'.$original_file;
2815
	}
2816
2817
	// Wrapping pour l'editeur wysiwyg
2818
	elseif ($modulepart == 'editor' && !empty($conf->fckeditor->dir_output))
2819
	{
2820
		$accessallowed=1;
2821
		$original_file=$conf->fckeditor->dir_output.'/'.$original_file;
2822
	}
2823
2824
	// Wrapping for backups
2825
	elseif ($modulepart == 'systemtools' && !empty($conf->admin->dir_output))
2826
	{
2827
		if ($fuser->admin) $accessallowed=1;
2828
		$original_file=$conf->admin->dir_output.'/'.$original_file;
2829
	}
2830
2831
	// Wrapping for upload file test
2832
	elseif ($modulepart == 'admin_temp' && !empty($conf->admin->dir_temp))
2833
	{
2834
		if ($fuser->admin) $accessallowed=1;
2835
		$original_file=$conf->admin->dir_temp.'/'.$original_file;
2836
	}
2837
2838
	// Wrapping pour BitTorrent
2839
	elseif ($modulepart == 'bittorrent' && !empty($conf->bittorrent->dir_output))
2840
	{
2841
		$accessallowed=1;
2842
		$dir='files';
2843
		if (dol_mimetype($original_file) == 'application/x-bittorrent') $dir='torrents';
2844
		$original_file=$conf->bittorrent->dir_output.'/'.$dir.'/'.$original_file;
2845
	}
2846
2847
	// Wrapping pour Foundation module
2848
	elseif ($modulepart == 'member' && !empty($conf->adherent->dir_output))
2849
	{
2850
		if ($fuser->rights->adherent->{$lire} || preg_match('/^specimen/i', $original_file))
2851
		{
2852
			$accessallowed=1;
2853
		}
2854
		$original_file=$conf->adherent->dir_output.'/'.$original_file;
2855
	}
2856
2857
	// Wrapping for Scanner
2858
	elseif ($modulepart == 'scanner_user_temp' && !empty($conf->scanner->dir_temp))
2859
	{
2860
		$accessallowed=1;
2861
		$original_file=$conf->scanner->dir_temp.'/'.$fuser->id.'/'.$original_file;
2862
	}
2863
2864
	// GENERIC Wrapping
2865
	// If modulepart=module_user_temp	Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp/iduser
2866
	// If modulepart=module_temp		Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/temp
2867
	// If modulepart=module_user		Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart/iduser
2868
	// If modulepart=module				Allows any module to open a file if file is in directory called DOL_DATA_ROOT/modulepart
2869
	else
2870
	{
2871
		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.
2872
		if ($fuser->admin) $accessallowed=1;    // If user is admin
2873
2874
		// Define $accessallowed
2875
		if (preg_match('/^([a-z]+)_user_temp$/i', $modulepart, $reg))
2876
		{
2877
			if (empty($conf->{$reg[1]}->dir_temp))	// modulepart not supported
2878
			{
2879
				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2880
				exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2881
			}
2882
			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) $accessallowed=1;
2883
			$original_file=$conf->{$reg[1]}->dir_temp.'/'.$fuser->id.'/'.$original_file;
2884
		}
2885
		elseif (preg_match('/^([a-z]+)_temp$/i', $modulepart, $reg))
2886
		{
2887
			if (empty($conf->{$reg[1]}->dir_temp))	// modulepart not supported
2888
			{
2889
				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2890
				exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2891
			}
2892
			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) $accessallowed=1;
2893
			$original_file=$conf->{$reg[1]}->dir_temp.'/'.$original_file;
2894
		}
2895
		elseif (preg_match('/^([a-z]+)_user$/i', $modulepart, $reg))
2896
		{
2897
			if (empty($conf->{$reg[1]}->dir_output))	// modulepart not supported
2898
			{
2899
				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2900
				exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2901
			}
2902
			if ($fuser->rights->{$reg[1]}->{$lire} || $fuser->rights->{$reg[1]}->{$read} || ($fuser->rights->{$reg[1]}->{$download})) $accessallowed=1;
2903
			$original_file=$conf->{$reg[1]}->dir_output.'/'.$fuser->id.'/'.$original_file;
2904
		}
2905
		elseif (preg_match('/^massfilesarea_([a-z]+)$/i', $modulepart, $reg))
2906
		{
2907
			if (empty($conf->{$reg[1]}->dir_output))	// modulepart not supported
2908
			{
2909
				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2910
				exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2911
			}
2912
			if ($fuser->rights->{$reg[1]}->{$lire} || preg_match('/^specimen/i', $original_file))
2913
			{
2914
				$accessallowed=1;
2915
			}
2916
			$original_file=$conf->{$reg[1]}->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
2917
		}
2918
		else
2919
		{
2920
			if (empty($conf->$modulepart->dir_output))	// modulepart not supported
2921
			{
2922
				dol_print_error('', 'Error call dol_check_secure_access_document with not supported value for modulepart parameter ('.$modulepart.')');
2923
				exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2924
			}
2925
2926
			$perm=GETPOST('perm');
2927
			$subperm=GETPOST('subperm');
2928
			if ($perm || $subperm)
2929
			{
2930
				if (($perm && ! $subperm && $fuser->rights->$modulepart->$perm) || ($perm && $subperm && $fuser->rights->$modulepart->$perm->$subperm)) $accessallowed=1;
2931
				$original_file=$conf->$modulepart->dir_output.'/'.$original_file;
2932
			}
2933
			else
2934
			{
2935
				if ($fuser->rights->$modulepart->{$lire} || $fuser->rights->$modulepart->{$read}) $accessallowed=1;
2936
				$original_file=$conf->$modulepart->dir_output.'/'.$original_file;
2937
			}
2938
		}
2939
2940
		// For modules who wants to manage different levels of permissions for documents
2941
		$subPermCategoryConstName = strtoupper($modulepart).'_SUBPERMCATEGORY_FOR_DOCUMENTS';
2942
		if (! empty($conf->global->$subPermCategoryConstName))
2943
		{
2944
			$subPermCategory = $conf->global->$subPermCategoryConstName;
2945
			if (! empty($subPermCategory) && (($fuser->rights->$modulepart->$subPermCategory->{$lire}) || ($fuser->rights->$modulepart->$subPermCategory->{$read}) || ($fuser->rights->$modulepart->$subPermCategory->{$download})))
2946
			{
2947
				$accessallowed=1;
2948
			}
2949
		}
2950
2951
		// Define $sqlprotectagainstexternals for modules who want to protect access using a SQL query.
2952
		$sqlProtectConstName = strtoupper($modulepart).'_SQLPROTECTAGAINSTEXTERNALS_FOR_DOCUMENTS';
2953
		if (! empty($conf->global->$sqlProtectConstName))	// If module want to define its own $sqlprotectagainstexternals
2954
		{
2955
			// Example: mymodule__SQLPROTECTAGAINSTEXTERNALS_FOR_DOCUMENTS = "SELECT fk_soc FROM ".MAIN_DB_PREFIX.$modulepart." WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
2956
			eval('$sqlprotectagainstexternals = "'.$conf->global->$sqlProtectConstName.'";');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2957
		}
2958
	}
2959
2960
	$ret = array(
2961
		'accessallowed' => $accessallowed,
2962
		'sqlprotectagainstexternals'=>$sqlprotectagainstexternals,
2963
		'original_file'=>$original_file
2964
	);
2965
2966
	return $ret;
2967
}
2968
2969
/**
2970
 * Store object in file.
2971
 *
2972
 * @param string $directory Directory of cache
2973
 * @param string $filename Name of filecache
2974
 * @param mixed $object Object to store in cachefile
2975
 * @return void
2976
 */
2977
function dol_filecache($directory, $filename, $object)
2978
{
2979
	if (! dol_is_dir($directory)) dol_mkdir($directory);
2980
	$cachefile = $directory . $filename;
2981
	file_put_contents($cachefile, serialize($object), LOCK_EX);
2982
	@chmod($cachefile, 0644);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2982
	/** @scrutinizer ignore-unhandled */ @chmod($cachefile, 0644);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2983
}
2984
2985
/**
2986
 * Test if Refresh needed.
2987
 *
2988
 * @param string $directory Directory of cache
2989
 * @param string $filename Name of filecache
2990
 * @param int $cachetime Cachetime delay
2991
 * @return boolean 0 no refresh 1 if refresh needed
2992
 */
2993
function dol_cache_refresh($directory, $filename, $cachetime)
2994
{
2995
	$now = dol_now();
2996
	$cachefile = $directory . $filename;
2997
	$refresh = !file_exists($cachefile) || ($now-$cachetime) > dol_filemtime($cachefile);
2998
	return $refresh;
2999
}
3000
3001
/**
3002
 * Read object from cachefile.
3003
 *
3004
 * @param string $directory Directory of cache
3005
 * @param string $filename Name of filecache
3006
 * @return mixed Unserialise from file
3007
 */
3008
function dol_readcachefile($directory, $filename)
3009
{
3010
	$cachefile = $directory . $filename;
3011
	$object = unserialize(file_get_contents($cachefile));
3012
	return $object;
3013
}
3014
3015
3016
/**
3017
 * Function to get list of updated or modified files.
3018
 * $file_list is used as global variable
3019
 *
3020
 * @param	array				$file_list	        Array for response
3021
 * @param   SimpleXMLElement	$dir    	        SimpleXMLElement of files to test
3022
 * @param   string   			$path   	        Path of files relative to $pathref. We start with ''. Used by recursive calls.
3023
 * @param   string              $pathref            Path ref (DOL_DOCUMENT_ROOT)
3024
 * @param   array               $checksumconcat     Array of checksum
3025
 * @return  array               			        Array of filenames
3026
 */
3027
function getFilesUpdated(&$file_list, SimpleXMLElement $dir, $path = '', $pathref = '', &$checksumconcat = array())
3028
{
3029
	global $conffile;
3030
3031
	$exclude = 'install';
3032
3033
	foreach ($dir->md5file as $file)    // $file is a simpleXMLElement
3034
	{
3035
		$filename = $path.$file['name'];
3036
		$file_list['insignature'][] = $filename;
3037
		$expectedmd5 = (string) $file;
3038
3039
		//if (preg_match('#'.$exclude.'#', $filename)) continue;
3040
3041
		if (!file_exists($pathref.'/'.$filename))
3042
		{
3043
			$file_list['missing'][] = array('filename'=>$filename, 'expectedmd5'=>$expectedmd5);
3044
		}
3045
		else
3046
		{
3047
			$md5_local = md5_file($pathref.'/'.$filename);
3048
3049
			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
3050
			{
3051
				$checksumconcat[] = $expectedmd5;
3052
			}
3053
			else
3054
			{
3055
				if ($md5_local != $expectedmd5) $file_list['updated'][] = array('filename'=>$filename, 'expectedmd5'=>$expectedmd5, 'md5'=>(string) $md5_local);
3056
				$checksumconcat[] = $md5_local;
3057
			}
3058
		}
3059
	}
3060
3061
	foreach ($dir->dir as $subdir)			// $subdir['name'] is  '' or '/accountancy/admin' for example
3062
	{
3063
		getFilesUpdated($file_list, $subdir, $path.$subdir['name'].'/', $pathref, $checksumconcat);
3064
	}
3065
3066
	return $file_list;
3067
}
3068