Dépôt officiel du core SPIP * Anciennement présent sur svn://trac.rezo.net/spip * Les plugins-dist faisant partie de la distribution SPIP sont présents dans https://git.spip.net/SPIP/[nom du plugin dist] https://www.spip.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

733 lines
20 KiB

17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. <?php
  2. /***************************************************************************\
  3. * SPIP, Système de publication pour l'internet *
  4. * *
  5. * Copyright © avec tendresse depuis 2001 *
  6. * Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
  7. * *
  8. * Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
  9. * Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. *
  10. \***************************************************************************/
  11. /**
  12. * Gestion de recherche et d'écriture de répertoire ou fichiers
  13. *
  14. * @package SPIP\Core\Flock
  15. **/
  16. if (!defined('_ECRIRE_INC_VERSION')) {
  17. return;
  18. }
  19. if (!defined('_TEST_FILE_EXISTS')) {
  20. /** Permettre d'éviter des tests file_exists sur certains hébergeurs */
  21. define('_TEST_FILE_EXISTS', preg_match(',(online|free)[.]fr$,', isset($_ENV["HTTP_HOST"]) ? $_ENV["HTTP_HOST"] : ""));
  22. }
  23. #define('_SPIP_LOCK_MODE',0); // ne pas utiliser de lock (deconseille)
  24. #define('_SPIP_LOCK_MODE',1); // utiliser le flock php
  25. #define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip
  26. if (_SPIP_LOCK_MODE == 2) {
  27. include_spip('inc/nfslock');
  28. }
  29. $GLOBALS['liste_verrous'] = array();
  30. /**
  31. * Ouvre un fichier et le vérrouille
  32. *
  33. * @link http://php.net/manual/fr/function.flock.php pour le type de verrou.
  34. * @see _SPIP_LOCK_MODE
  35. * @see spip_fclose_unlock()
  36. * @uses spip_nfslock() si _SPIP_LOCK_MODE = 2.
  37. *
  38. * @param string $fichier
  39. * Chemin du fichier
  40. * @param string $mode
  41. * Mode d'ouverture du fichier (r,w,...)
  42. * @param string $verrou
  43. * Type de verrou (avec _SPIP_LOCK_MODE = 1)
  44. * @return Resource
  45. * Ressource sur le fichier ouvert, sinon false.
  46. **/
  47. function spip_fopen_lock($fichier, $mode, $verrou) {
  48. if (_SPIP_LOCK_MODE == 1) {
  49. if ($fl = @fopen($fichier, $mode)) {
  50. // verrou
  51. @flock($fl, $verrou);
  52. }
  53. return $fl;
  54. } elseif (_SPIP_LOCK_MODE == 2) {
  55. if (($verrou = spip_nfslock($fichier)) && ($fl = @fopen($fichier, $mode))) {
  56. $GLOBALS['liste_verrous'][$fl] = array($fichier, $verrou);
  57. return $fl;
  58. } else {
  59. return false;
  60. }
  61. }
  62. return @fopen($fichier, $mode);
  63. }
  64. /**
  65. * Dévérrouille et ferme un fichier
  66. *
  67. * @see _SPIP_LOCK_MODE
  68. * @see spip_fopen_lock()
  69. *
  70. * @param string $handle
  71. * Chemin du fichier
  72. * @return bool
  73. * true si succès, false sinon.
  74. **/
  75. function spip_fclose_unlock($handle) {
  76. if (_SPIP_LOCK_MODE == 1) {
  77. @flock($handle, LOCK_UN);
  78. } elseif (_SPIP_LOCK_MODE == 2) {
  79. spip_nfsunlock(reset($GLOBALS['liste_verrous'][$handle]), end($GLOBALS['liste_verrous'][$handle]));
  80. unset($GLOBALS['liste_verrous'][$handle]);
  81. }
  82. return @fclose($handle);
  83. }
  84. /**
  85. * Retourne le contenu d'un fichier, même si celui ci est compréssé
  86. * avec une extension en `.gz`
  87. *
  88. * @param string $fichier
  89. * Chemin du fichier
  90. * @return string
  91. * Contenu du fichier
  92. **/
  93. function spip_file_get_contents($fichier) {
  94. if (substr($fichier, -3) != '.gz') {
  95. if (function_exists('file_get_contents')) {
  96. // quand on est sous windows on ne sait pas si file_get_contents marche
  97. // on essaye : si ca retourne du contenu alors c'est bon
  98. // sinon on fait un file() pour avoir le coeur net
  99. $contenu = @file_get_contents($fichier);
  100. if (!$contenu and _OS_SERVEUR == 'windows') {
  101. $contenu = @file($fichier);
  102. }
  103. } else {
  104. $contenu = @file($fichier);
  105. }
  106. } else {
  107. $contenu = @gzfile($fichier);
  108. }
  109. return is_array($contenu) ? join('', $contenu) : (string)$contenu;
  110. }
  111. /**
  112. * Lit un fichier et place son contenu dans le paramètre transmis.
  113. *
  114. * Décompresse automatiquement les fichiers `.gz`
  115. *
  116. * @uses spip_fopen_lock()
  117. * @uses spip_file_get_contents()
  118. * @uses spip_fclose_unlock()
  119. *
  120. * @param string $fichier
  121. * Chemin du fichier
  122. * @param string $contenu
  123. * Le contenu du fichier sera placé dans cette variable
  124. * @param array $options
  125. * Options tel que :
  126. *
  127. * - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
  128. * @return bool
  129. * true si l'opération a réussie, false sinon.
  130. **/
  131. function lire_fichier($fichier, &$contenu, $options = array()) {
  132. $contenu = '';
  133. // inutile car si le fichier n'existe pas, le lock va renvoyer false juste apres
  134. // economisons donc les acces disque, sauf chez free qui rale pour un rien
  135. if (_TEST_FILE_EXISTS and !@file_exists($fichier)) {
  136. return false;
  137. }
  138. #spip_timer('lire_fichier');
  139. // pas de @ sur spip_fopen_lock qui est silencieux de toute facon
  140. if ($fl = spip_fopen_lock($fichier, 'r', LOCK_SH)) {
  141. // lire le fichier avant tout
  142. $contenu = spip_file_get_contents($fichier);
  143. // le fichier a-t-il ete supprime par le locker ?
  144. // on ne verifie que si la tentative de lecture a echoue
  145. // pour discriminer un contenu vide d'un fichier absent
  146. // et eviter un acces disque
  147. if (!$contenu and !@file_exists($fichier)) {
  148. spip_fclose_unlock($fl);
  149. return false;
  150. }
  151. // liberer le verrou
  152. spip_fclose_unlock($fl);
  153. // Verifications
  154. $ok = true;
  155. if (isset($options['phpcheck']) and $options['phpcheck'] == 'oui') {
  156. $ok &= (preg_match(",[?]>\n?$,", $contenu));
  157. }
  158. #spip_log("$fread $fichier ".spip_timer('lire_fichier'));
  159. if (!$ok) {
  160. spip_log("echec lecture $fichier");
  161. }
  162. return $ok;
  163. }
  164. return false;
  165. }
  166. /**
  167. * Écrit un fichier de manière un peu sûre
  168. *
  169. * Cette écriture s’exécute de façon sécurisée en posant un verrou sur
  170. * le fichier avant sa modification. Les fichiers .gz sont compressés.
  171. *
  172. * @uses raler_fichier() Si le fichier n'a pu peut être écrit
  173. * @see lire_fichier()
  174. * @see supprimer_fichier()
  175. *
  176. * @param string $fichier
  177. * Chemin du fichier
  178. * @param string $contenu
  179. * Contenu à écrire
  180. * @param bool $ignorer_echec
  181. * - true pour ne pas raler en cas d'erreur
  182. * - false affichera un message si on est webmestre
  183. * @param bool $truncate
  184. * Écriture avec troncation ?
  185. * @return bool
  186. * - true si l’écriture s’est déroulée sans problème.
  187. **/
  188. function ecrire_fichier($fichier, $contenu, $ignorer_echec = false, $truncate = true) {
  189. #spip_timer('ecrire_fichier');
  190. // verrouiller le fichier destination
  191. if ($fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
  192. // ecrire les donnees, compressees le cas echeant
  193. // (on ouvre un nouveau pointeur sur le fichier, ce qui a l'avantage
  194. // de le recreer si le locker qui nous precede l'avait supprime...)
  195. if (substr($fichier, -3) == '.gz') {
  196. $contenu = gzencode($contenu);
  197. }
  198. // si c'est une ecriture avec troncation , on fait plutot une ecriture complete a cote suivie unlink+rename
  199. // pour etre sur d'avoir une operation atomique
  200. // y compris en NFS : http://www.ietf.org/rfc/rfc1094.txt
  201. // sauf sous wintruc ou ca ne marche pas
  202. $ok = false;
  203. if ($truncate and _OS_SERVEUR != 'windows') {
  204. if (!function_exists('creer_uniqid')) {
  205. include_spip('inc/acces');
  206. }
  207. $id = creer_uniqid();
  208. // on ouvre un pointeur sur un fichier temporaire en ecriture +raz
  209. if ($fp2 = spip_fopen_lock("$fichier.$id", 'w', LOCK_EX)) {
  210. $s = @fputs($fp2, $contenu, $a = strlen($contenu));
  211. $ok = ($s == $a);
  212. spip_fclose_unlock($fp2);
  213. spip_fclose_unlock($fp);
  214. // unlink direct et pas spip_unlink car on avait deja le verrou
  215. // a priori pas besoin car rename ecrase la cible
  216. // @unlink($fichier);
  217. // le rename aussitot, atomique quand on est pas sous windows
  218. // au pire on arrive en second en cas de concourance, et le rename echoue
  219. // --> on a la version de l'autre process qui doit etre identique
  220. @rename("$fichier.$id", $fichier);
  221. // precaution en cas d'echec du rename
  222. if (!_TEST_FILE_EXISTS or @file_exists("$fichier.$id")) {
  223. @unlink("$fichier.$id");
  224. }
  225. if ($ok) {
  226. $ok = file_exists($fichier);
  227. }
  228. } else // echec mais penser a fermer ..
  229. {
  230. spip_fclose_unlock($fp);
  231. }
  232. }
  233. // sinon ou si methode precedente a echoueee
  234. // on se rabat sur la methode ancienne
  235. if (!$ok) {
  236. // ici on est en ajout ou sous windows, cas desespere
  237. if ($truncate) {
  238. @ftruncate($fp, 0);
  239. }
  240. $s = @fputs($fp, $contenu, $a = strlen($contenu));
  241. $ok = ($s == $a);
  242. spip_fclose_unlock($fp);
  243. }
  244. // liberer le verrou et fermer le fichier
  245. @chmod($fichier, _SPIP_CHMOD & 0666);
  246. if ($ok) {
  247. if (strpos($fichier, ".php") !== false) {
  248. spip_clear_opcode_cache(realpath($fichier));
  249. }
  250. return $ok;
  251. }
  252. }
  253. if (!$ignorer_echec) {
  254. include_spip('inc/autoriser');
  255. if (autoriser('chargerftp')) {
  256. raler_fichier($fichier);
  257. }
  258. spip_unlink($fichier);
  259. }
  260. spip_log("Ecriture fichier $fichier impossible", _LOG_INFO_IMPORTANTE);
  261. return false;
  262. }
  263. /**
  264. * Écrire un contenu dans un fichier encapsulé en PHP pour en empêcher l'accès en l'absence
  265. * de fichier htaccess
  266. *
  267. * @uses ecrire_fichier()
  268. *
  269. * @param string $fichier
  270. * Chemin du fichier
  271. * @param string $contenu
  272. * Contenu à écrire
  273. * @param bool $ecrire_quand_meme
  274. * - true pour ne pas raler en cas d'erreur
  275. * - false affichera un message si on est webmestre
  276. * @param bool $truncate
  277. * Écriture avec troncation ?
  278. */
  279. function ecrire_fichier_securise($fichier, $contenu, $ecrire_quand_meme = false, $truncate = true) {
  280. if (substr($fichier, -4) !== '.php') {
  281. spip_log('Erreur de programmation: ' . $fichier . ' doit finir par .php');
  282. }
  283. $contenu = "<" . "?php die ('Acces interdit'); ?" . ">\n" . $contenu;
  284. return ecrire_fichier($fichier, $contenu, $ecrire_quand_meme, $truncate);
  285. }
  286. /**
  287. * @param string $fichier
  288. * @param string $contenu
  289. * @param bool $force
  290. * @return bool
  291. */
  292. function ecrire_fichier_calcule_si_modifie($fichier, $contenu, $force=false, $use_copy=false) {
  293. $fichier_tmp = $fichier . '.last';
  294. if (!ecrire_fichier($fichier_tmp, $contenu, true)){
  295. return false;
  296. }
  297. if ($force
  298. or !file_exists($fichier)
  299. or md5_file($fichier) != md5_file($fichier_tmp)) {
  300. if ($use_copy) {
  301. @copy($fichier_tmp, $fichier);
  302. }
  303. else {
  304. @rename($fichier_tmp, $fichier);
  305. }
  306. // eviter que PHP ne reserve le vieux timestamp
  307. clearstatcache(true, $fichier);
  308. }
  309. return true;
  310. }
  311. /**
  312. * Lire un fichier encapsulé en PHP
  313. *
  314. * @uses lire_fichier()
  315. *
  316. * @param string $fichier
  317. * Chemin du fichier
  318. * @param string $contenu
  319. * Le contenu du fichier sera placé dans cette variable
  320. * @param array $options
  321. * Options tel que :
  322. *
  323. * - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
  324. * @return bool
  325. * true si l'opération a réussie, false sinon.
  326. */
  327. function lire_fichier_securise($fichier, &$contenu, $options = array()) {
  328. if ($res = lire_fichier($fichier, $contenu, $options)) {
  329. $contenu = substr($contenu, strlen("<" . "?php die ('Acces interdit'); ?" . ">\n"));
  330. }
  331. return $res;
  332. }
  333. /**
  334. * Affiche un message d’erreur bloquant, indiquant qu’il n’est pas possible de créer
  335. * le fichier à cause des droits sur le répertoire parent au fichier.
  336. *
  337. * Arrête le script PHP par un exit;
  338. *
  339. * @uses minipres() Pour afficher le message
  340. *
  341. * @param string $fichier
  342. * Chemin du fichier
  343. **/
  344. function raler_fichier($fichier) {
  345. include_spip('inc/minipres');
  346. $dir = dirname($fichier);
  347. http_status(401);
  348. echo minipres(_T('texte_inc_meta_2'), "<h4 style='color: red'>"
  349. . _T('texte_inc_meta_1', array('fichier' => $fichier))
  350. . " <a href='"
  351. . generer_url_ecrire('install', "etape=chmod&test_dir=$dir")
  352. . "'>"
  353. . _T('texte_inc_meta_2')
  354. . "</a> "
  355. . _T('texte_inc_meta_3',
  356. array('repertoire' => joli_repertoire($dir)))
  357. . "</h4>\n");
  358. exit;
  359. }
  360. /**
  361. * Teste si un fichier est récent (moins de n secondes)
  362. *
  363. * @param string $fichier
  364. * Chemin du fichier
  365. * @param int $n
  366. * Âge testé, en secondes
  367. * @return bool
  368. * - true si récent, false sinon
  369. */
  370. function jeune_fichier($fichier, $n) {
  371. if (!file_exists($fichier)) {
  372. return false;
  373. }
  374. if (!$c = @filemtime($fichier)) {
  375. return false;
  376. }
  377. return (time() - $n <= $c);
  378. }
  379. /**
  380. * Supprimer un fichier de manière sympa (flock)
  381. *
  382. * @param string $fichier
  383. * Chemin du fichier
  384. * @param bool $lock
  385. * true pour utiliser un verrou
  386. * @return bool
  387. * - true si le fichier n'existe pas ou s'il a bien été supprimé
  388. * - false si on n'arrive pas poser le verrou ou si la suppression échoue
  389. */
  390. function supprimer_fichier($fichier, $lock = true) {
  391. if (!@file_exists($fichier)) {
  392. return true;
  393. }
  394. if ($lock) {
  395. // verrouiller le fichier destination
  396. if (!$fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
  397. return false;
  398. }
  399. // liberer le verrou
  400. spip_fclose_unlock($fp);
  401. }
  402. // supprimer
  403. return @unlink($fichier);
  404. }
  405. /**
  406. * Supprimer brutalement un fichier ou un dossier, s'il existe
  407. *
  408. * @param string $f
  409. * Chemin du fichier
  410. */
  411. function spip_unlink($f) {
  412. if (!is_dir($f)) {
  413. supprimer_fichier($f, false);
  414. } else {
  415. @unlink("$f/.ok");
  416. @rmdir($f);
  417. }
  418. }
  419. /**
  420. * Invalidates a PHP file from any active opcode caches.
  421. *
  422. * If the opcode cache does not support the invalidation of individual files,
  423. * the entire cache will be flushed.
  424. * kudo : http://cgit.drupalcode.org/drupal/commit/?id=be97f50
  425. *
  426. * @param string $filepath
  427. * The absolute path of the PHP file to invalidate.
  428. */
  429. function spip_clear_opcode_cache($filepath) {
  430. clearstatcache(true, $filepath);
  431. // Zend OPcache
  432. if (function_exists('opcache_invalidate')) {
  433. $invalidate = @opcache_invalidate($filepath, true);
  434. // si l'invalidation a echoue lever un flag
  435. if (!$invalidate and !defined('_spip_attend_invalidation_opcode_cache')) {
  436. define('_spip_attend_invalidation_opcode_cache',true);
  437. }
  438. } elseif (!defined('_spip_attend_invalidation_opcode_cache')) {
  439. // n'agira que si opcache est effectivement actif (il semble qu'on a pas toujours la fonction opcache_invalidate)
  440. define('_spip_attend_invalidation_opcode_cache',true);
  441. }
  442. // APC.
  443. if (function_exists('apc_delete_file')) {
  444. // apc_delete_file() throws a PHP warning in case the specified file was
  445. // not compiled yet.
  446. // @see http://php.net/apc-delete-file
  447. @apc_delete_file($filepath);
  448. }
  449. }
  450. /**
  451. * Attendre l'invalidation de l'opcache
  452. *
  453. * Si opcache est actif et en mode `validate_timestamps`,
  454. * le timestamp du fichier ne sera vérifié qu'après une durée
  455. * en secondes fixée par `revalidate_freq`.
  456. *
  457. * Il faut donc attendre ce temps pour être sûr qu'on va bien
  458. * bénéficier de la recompilation du fichier par l'opcache.
  459. *
  460. * Ne fait rien en dehors de ce cas
  461. *
  462. * @note
  463. * C'est une config foireuse déconseillée de opcode cache mais
  464. * malheureusement utilisée par Octave.
  465. * @link http://stackoverflow.com/questions/25649416/when-exactly-does-php-5-5-opcache-check-file-timestamp-based-on-revalidate-freq
  466. * @link http://wiki.mikejung.biz/PHP_OPcache
  467. *
  468. */
  469. function spip_attend_invalidation_opcode_cache($timestamp = null) {
  470. if (function_exists('opcache_get_configuration')
  471. and @ini_get('opcache.enable')
  472. and @ini_get('opcache.validate_timestamps')
  473. and ($duree = intval(@ini_get('opcache.revalidate_freq')) or $duree = 2)
  474. and defined('_spip_attend_invalidation_opcode_cache') // des invalidations ont echouees
  475. ) {
  476. $wait = $duree + 1;
  477. if ($timestamp) {
  478. $wait -= (time() - $timestamp);
  479. if ($wait<0) {
  480. $wait = 0;
  481. }
  482. }
  483. spip_log('Probleme de configuration opcache.revalidate_freq '. $duree .'s : on attend '.$wait.'s', _LOG_INFO_IMPORTANTE);
  484. if ($wait) {
  485. sleep($duree + 1);
  486. }
  487. }
  488. }
  489. /**
  490. * Suppression complete d'un repertoire.
  491. *
  492. * @link http://www.php.net/manual/en/function.rmdir.php#92050
  493. *
  494. * @param string $dir Chemin du repertoire
  495. * @return bool Suppression reussie.
  496. */
  497. function supprimer_repertoire($dir) {
  498. if (!file_exists($dir)) {
  499. return true;
  500. }
  501. if (!is_dir($dir) || is_link($dir)) {
  502. return @unlink($dir);
  503. }
  504. foreach (scandir($dir) as $item) {
  505. if ($item == '.' || $item == '..') {
  506. continue;
  507. }
  508. if (!supprimer_repertoire($dir . "/" . $item)) {
  509. @chmod($dir . "/" . $item, 0777);
  510. if (!supprimer_repertoire($dir . "/" . $item)) {
  511. return false;
  512. }
  513. };
  514. }
  515. return @rmdir($dir);
  516. }
  517. /**
  518. * Crée un sous répertoire
  519. *
  520. * Retourne `$base/${subdir}/` si le sous-repertoire peut être crée
  521. *
  522. * @example
  523. * ```
  524. * sous_repertoire(_DIR_CACHE, 'demo');
  525. * sous_repertoire(_DIR_CACHE . '/demo');
  526. * ```
  527. *
  528. * @param string $base
  529. * - Chemin du répertoire parent (avec $subdir)
  530. * - sinon chemin du répertoire à créer
  531. * @param string $subdir
  532. * - Nom du sous répertoire à créer,
  533. * - non transmis, `$subdir` vaut alors ce qui suit le dernier `/` dans `$base`
  534. * @param bool $nobase
  535. * true pour ne pas avoir le chemin du parent `$base/` dans le retour
  536. * @param bool $tantpis
  537. * true pour ne pas raler en cas de non création du répertoire
  538. * @return string
  539. * Chemin du répertoire créé.
  540. **/
  541. function sous_repertoire($base, $subdir = '', $nobase = false, $tantpis = false) {
  542. static $dirs = array();
  543. $base = str_replace("//", "/", $base);
  544. # suppr le dernier caractere si c'est un /
  545. $base = rtrim($base, '/');
  546. if (!strlen($subdir)) {
  547. $n = strrpos($base, "/");
  548. if ($n === false) {
  549. return $nobase ? '' : ($base . '/');
  550. }
  551. $subdir = substr($base, $n + 1);
  552. $base = substr($base, 0, $n + 1);
  553. } else {
  554. $base .= '/';
  555. $subdir = str_replace("/", "", $subdir);
  556. }
  557. $baseaff = $nobase ? '' : $base;
  558. if (isset($dirs[$base . $subdir])) {
  559. return $baseaff . $dirs[$base . $subdir];
  560. }
  561. $path = $base . $subdir; # $path = 'IMG/distant/pdf' ou 'IMG/distant_pdf'
  562. if (file_exists("$path/.ok")) {
  563. return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
  564. }
  565. @mkdir($path, _SPIP_CHMOD);
  566. @chmod($path, _SPIP_CHMOD);
  567. if (is_dir($path) && is_writable($path)) {
  568. @touch("$path/.ok");
  569. spip_log("creation $base$subdir/");
  570. return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
  571. }
  572. // en cas d'echec c'est peut etre tout simplement que le disque est plein :
  573. // l'inode du fichier dir_test existe, mais impossible d'y mettre du contenu
  574. spip_log("echec creation $base${subdir}");
  575. if ($tantpis) {
  576. return '';
  577. }
  578. if (!_DIR_RESTREINT) {
  579. $base = preg_replace(',^' . _DIR_RACINE . ',', '', $base);
  580. }
  581. $base .= $subdir;
  582. raler_fichier($base . '/.ok');
  583. }
  584. /**
  585. * Parcourt récursivement le repertoire `$dir`, et renvoie les
  586. * fichiers dont le chemin vérifie le pattern (preg) donné en argument.
  587. *
  588. * En cas d'echec retourne un `array()` vide
  589. *
  590. * @example
  591. * ```
  592. * $x = preg_files('ecrire/data/', '[.]lock$');
  593. * // $x array()
  594. * ```
  595. *
  596. * @note
  597. * Attention, afin de conserver la compatibilite avec les repertoires '.plat'
  598. * si `$dir = 'rep/sous_rep_'` au lieu de `rep/sous_rep/` on scanne `rep/` et on
  599. * applique un pattern `^rep/sous_rep_`
  600. *
  601. * @param string $dir
  602. * Répertoire à parcourir
  603. * @param int|string $pattern
  604. * Expression régulière pour trouver des fichiers, tel que `[.]lock$`
  605. * @param int $maxfiles
  606. * Nombre de fichiers maximums retournés
  607. * @param array $recurs
  608. * false pour ne pas descendre dans les sous répertoires
  609. * @return array
  610. * Chemins des fichiers trouvés.
  611. **/
  612. function preg_files($dir, $pattern = -1 /* AUTO */, $maxfiles = 10000, $recurs = array()) {
  613. $nbfiles = 0;
  614. if ($pattern == -1) {
  615. $pattern = "^$dir";
  616. }
  617. $fichiers = array();
  618. // revenir au repertoire racine si on a recu dossier/truc
  619. // pour regarder dossier/truc/ ne pas oublier le / final
  620. $dir = preg_replace(',/[^/]*$,', '', $dir);
  621. if ($dir == '') {
  622. $dir = '.';
  623. }
  624. if (@is_dir($dir) and is_readable($dir) and $d = opendir($dir)) {
  625. while (($f = readdir($d)) !== false && ($nbfiles < $maxfiles)) {
  626. if ($f[0] != '.' # ignorer . .. .svn etc
  627. and $f != 'CVS'
  628. and $f != 'remove.txt'
  629. and is_readable($f = "$dir/$f")
  630. ) {
  631. if (is_file($f)) {
  632. if (preg_match(";$pattern;iS", $f)) {
  633. $fichiers[] = $f;
  634. $nbfiles++;
  635. }
  636. } else {
  637. if (is_dir($f) and is_array($recurs)) {
  638. $rp = @realpath($f);
  639. if (!is_string($rp) or !strlen($rp)) {
  640. $rp = $f;
  641. } # realpath n'est peut etre pas autorise
  642. if (!isset($recurs[$rp])) {
  643. $recurs[$rp] = true;
  644. $beginning = $fichiers;
  645. $end = preg_files("$f/", $pattern,
  646. $maxfiles - $nbfiles, $recurs);
  647. $fichiers = array_merge((array)$beginning, (array)$end);
  648. $nbfiles = count($fichiers);
  649. }
  650. }
  651. }
  652. }
  653. }
  654. closedir($d);
  655. }
  656. sort($fichiers);
  657. return $fichiers;
  658. }