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