Completed
Push — master ( 5e1981...37f911 )
by cam
04:36
created

flock.php ➔ spip_clear_opcode_cache()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2019                                                *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Gestion de recherche et d'écriture de répertoire ou fichiers
15
 *
16
 * @package SPIP\Core\Flock
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
if (!defined('_TEST_FILE_EXISTS')) {
24
	/** Permettre d'éviter des tests file_exists sur certains hébergeurs */
25
	define('_TEST_FILE_EXISTS', preg_match(',(online|free)[.]fr$,', isset($_ENV["HTTP_HOST"]) ? $_ENV["HTTP_HOST"] : ""));
26
}
27
28
#define('_SPIP_LOCK_MODE',0); // ne pas utiliser de lock (deconseille)
29
#define('_SPIP_LOCK_MODE',1); // utiliser le flock php
30
#define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip
31
32
if (_SPIP_LOCK_MODE == 2) {
33
	include_spip('inc/nfslock');
34
}
35
36
$GLOBALS['liste_verrous'] = array();
37
38
/**
39
 * Ouvre un fichier et le vérrouille
40
 *
41
 * @link http://php.net/manual/fr/function.flock.php pour le type de verrou.
42
 * @see  _SPIP_LOCK_MODE
43
 * @see  spip_fclose_unlock()
44
 * @uses spip_nfslock() si _SPIP_LOCK_MODE = 2.
45
 *
46
 * @param string $fichier
47
 *     Chemin du fichier
48
 * @param string $mode
49
 *     Mode d'ouverture du fichier (r,w,...)
50
 * @param string $verrou
51
 *     Type de verrou (avec _SPIP_LOCK_MODE = 1)
52
 * @return Resource
53
 *     Ressource sur le fichier ouvert, sinon false.
54
 **/
55
function spip_fopen_lock($fichier, $mode, $verrou) {
56
	if (_SPIP_LOCK_MODE == 1) {
57
		if ($fl = @fopen($fichier, $mode)) {
58
			// verrou
59
			@flock($fl, $verrou);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
60
		}
61
62
		return $fl;
63
	} elseif (_SPIP_LOCK_MODE == 2) {
64
		if (($verrou = spip_nfslock($fichier)) && ($fl = @fopen($fichier, $mode))) {
65
			$GLOBALS['liste_verrous'][$fl] = array($fichier, $verrou);
66
67
			return $fl;
68
		} else {
69
			return false;
70
		}
71
	}
72
73
	return @fopen($fichier, $mode);
74
}
75
76
/**
77
 * Dévérrouille et ferme un fichier
78
 *
79
 * @see _SPIP_LOCK_MODE
80
 * @see spip_fopen_lock()
81
 *
82
 * @param string $handle
83
 *     Chemin du fichier
84
 * @return bool
85
 *     true si succès, false sinon.
86
 **/
87
function spip_fclose_unlock($handle) {
88
	if (_SPIP_LOCK_MODE == 1) {
89
		@flock($handle, LOCK_UN);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
90
	} elseif (_SPIP_LOCK_MODE == 2) {
91
		spip_nfsunlock(reset($GLOBALS['liste_verrous'][$handle]), end($GLOBALS['liste_verrous'][$handle]));
92
		unset($GLOBALS['liste_verrous'][$handle]);
93
	}
94
95
	return @fclose($handle);
96
}
97
98
99
/**
100
 * Retourne le contenu d'un fichier, même si celui ci est compréssé
101
 * avec une extension en `.gz`
102
 *
103
 * @param string $fichier
104
 *     Chemin du fichier
105
 * @return string
106
 *     Contenu du fichier
107
 **/
108
function spip_file_get_contents($fichier) {
109
	if (substr($fichier, -3) != '.gz') {
110
		if (function_exists('file_get_contents')) {
111
			// quand on est sous windows on ne sait pas si file_get_contents marche
112
			// on essaye : si ca retourne du contenu alors c'est bon
113
			// sinon on fait un file() pour avoir le coeur net
114
			$contenu = @file_get_contents($fichier);
115
			if (!$contenu and _OS_SERVEUR == 'windows') {
116
				$contenu = @file($fichier);
117
			}
118
		} else {
119
			$contenu = @file($fichier);
120
		}
121
	} else {
122
		$contenu = @gzfile($fichier);
123
	}
124
125
	return is_array($contenu) ? join('', $contenu) : (string)$contenu;
126
}
127
128
129
/**
130
 * Lit un fichier et place son contenu dans le paramètre transmis.
131
 *
132
 * Décompresse automatiquement les fichiers `.gz`
133
 *
134
 * @uses spip_fopen_lock()
135
 * @uses spip_file_get_contents()
136
 * @uses spip_fclose_unlock()
137
 *
138
 * @param string $fichier
139
 *     Chemin du fichier
140
 * @param string $contenu
141
 *     Le contenu du fichier sera placé dans cette variable
142
 * @param array $options
143
 *     Options tel que :
144
 *
145
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
146
 * @return bool
147
 *     true si l'opération a réussie, false sinon.
148
 **/
149
function lire_fichier($fichier, &$contenu, $options = array()) {
150
	$contenu = '';
151
	// inutile car si le fichier n'existe pas, le lock va renvoyer false juste apres
152
	// economisons donc les acces disque, sauf chez free qui rale pour un rien
153
	if (_TEST_FILE_EXISTS and !@file_exists($fichier)) {
154
		return false;
155
	}
156
157
	#spip_timer('lire_fichier');
158
159
	// pas de @ sur spip_fopen_lock qui est silencieux de toute facon
160
	if ($fl = spip_fopen_lock($fichier, 'r', LOCK_SH)) {
161
		// lire le fichier avant tout
162
		$contenu = spip_file_get_contents($fichier);
163
164
		// le fichier a-t-il ete supprime par le locker ?
165
		// on ne verifie que si la tentative de lecture a echoue
166
		// pour discriminer un contenu vide d'un fichier absent
167
		// et eviter un acces disque
168
		if (!$contenu and !@file_exists($fichier)) {
169
			spip_fclose_unlock($fl);
170
171
			return false;
172
		}
173
174
		// liberer le verrou
175
		spip_fclose_unlock($fl);
176
177
		// Verifications
178
		$ok = true;
179
		if (isset($options['phpcheck']) and $options['phpcheck'] == 'oui') {
180
			$ok &= (preg_match(",[?]>\n?$,", $contenu));
181
		}
182
183
		#spip_log("$fread $fichier ".spip_timer('lire_fichier'));
184
		if (!$ok) {
185
			spip_log("echec lecture $fichier");
186
		}
187
188
		return $ok;
189
	}
190
191
	return false;
192
}
193
194
195
/**
196
 * Écrit un fichier de manière un peu sûre
197
 *
198
 * Cette écriture s’exécute de façon sécurisée en posant un verrou sur
199
 * le fichier avant sa modification. Les fichiers .gz sont compressés.
200
 *
201
 * @uses raler_fichier() Si le fichier n'a pu peut être écrit
202
 * @see  lire_fichier()
203
 * @see  supprimer_fichier()
204
 *
205
 * @param string $fichier
206
 *     Chemin du fichier
207
 * @param string $contenu
208
 *     Contenu à écrire
209
 * @param bool $ignorer_echec
210
 *     - true pour ne pas raler en cas d'erreur
211
 *     - false affichera un message si on est webmestre
212
 * @param bool $truncate
213
 *     Écriture avec troncation ?
214
 * @return bool
215
 *     - true si l’écriture s’est déroulée sans problème.
216
 **/
217
function ecrire_fichier($fichier, $contenu, $ignorer_echec = false, $truncate = true) {
218
219
	#spip_timer('ecrire_fichier');
220
221
	// verrouiller le fichier destination
222
	if ($fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
223
		// ecrire les donnees, compressees le cas echeant
224
		// (on ouvre un nouveau pointeur sur le fichier, ce qui a l'avantage
225
		// de le recreer si le locker qui nous precede l'avait supprime...)
226
		if (substr($fichier, -3) == '.gz') {
227
			$contenu = gzencode($contenu);
228
		}
229
		// si c'est une ecriture avec troncation , on fait plutot une ecriture complete a cote suivie unlink+rename
230
		// pour etre sur d'avoir une operation atomique
231
		// y compris en NFS : http://www.ietf.org/rfc/rfc1094.txt
232
		// sauf sous wintruc ou ca ne marche pas
233
		$ok = false;
234
		if ($truncate and _OS_SERVEUR != 'windows') {
235
			if (!function_exists('creer_uniqid')) {
236
				include_spip('inc/acces');
237
			}
238
			$id = creer_uniqid();
239
			// on ouvre un pointeur sur un fichier temporaire en ecriture +raz
240
			if ($fp2 = spip_fopen_lock("$fichier.$id", 'w', LOCK_EX)) {
241
				$s = @fputs($fp2, $contenu, $a = strlen($contenu));
242
				$ok = ($s == $a);
243
				spip_fclose_unlock($fp2);
244
				spip_fclose_unlock($fp);
245
				// unlink direct et pas spip_unlink car on avait deja le verrou
246
				// a priori pas besoin car rename ecrase la cible
247
				// @unlink($fichier);
248
				// le rename aussitot, atomique quand on est pas sous windows
249
				// au pire on arrive en second en cas de concourance, et le rename echoue
250
				// --> on a la version de l'autre process qui doit etre identique
251
				@rename("$fichier.$id", $fichier);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
252
				// precaution en cas d'echec du rename
253
				if (!_TEST_FILE_EXISTS or @file_exists("$fichier.$id")) {
254
					@unlink("$fichier.$id");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
255
				}
256
				if ($ok) {
257
					$ok = file_exists($fichier);
258
				}
259
			} else // echec mais penser a fermer ..
260
			{
261
				spip_fclose_unlock($fp);
262
			}
263
		}
264
		// sinon ou si methode precedente a echoueee
265
		// on se rabat sur la methode ancienne
266
		if (!$ok) {
267
			// ici on est en ajout ou sous windows, cas desespere
268
			if ($truncate) {
269
				@ftruncate($fp, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
270
			}
271
			$s = @fputs($fp, $contenu, $a = strlen($contenu));
272
273
			$ok = ($s == $a);
274
			spip_fclose_unlock($fp);
275
		}
276
277
		// liberer le verrou et fermer le fichier
278
		@chmod($fichier, _SPIP_CHMOD & 0666);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
279
		if ($ok) {
280
			if (strpos($fichier, ".php") !== false) {
281
				spip_clear_opcode_cache(realpath($fichier));
282
			}
283
284
			return $ok;
285
		}
286
	}
287
288
	if (!$ignorer_echec) {
289
		include_spip('inc/autoriser');
290
		if (autoriser('chargerftp')) {
291
			raler_fichier($fichier);
292
		}
293
		spip_unlink($fichier);
294
	}
295
	spip_log("Ecriture fichier $fichier impossible", _LOG_INFO_IMPORTANTE);
296
297
	return false;
298
}
299
300
/**
301
 * Écrire un contenu dans un fichier encapsulé en PHP pour en empêcher l'accès en l'absence
302
 * de fichier htaccess
303
 *
304
 * @uses ecrire_fichier()
305
 *
306
 * @param string $fichier
307
 *     Chemin du fichier
308
 * @param string $contenu
309
 *     Contenu à écrire
310
 * @param bool $ecrire_quand_meme
311
 *     - true pour ne pas raler en cas d'erreur
312
 *     - false affichera un message si on est webmestre
313
 * @param bool $truncate
314
 *     Écriture avec troncation ?
315
 */
316
function ecrire_fichier_securise($fichier, $contenu, $ecrire_quand_meme = false, $truncate = true) {
317
	if (substr($fichier, -4) !== '.php') {
318
		spip_log('Erreur de programmation: ' . $fichier . ' doit finir par .php');
319
	}
320
	$contenu = "<" . "?php die ('Acces interdit'); ?" . ">\n" . $contenu;
321
322
	return ecrire_fichier($fichier, $contenu, $ecrire_quand_meme, $truncate);
323
}
324
325
/**
326
 * Lire un fichier encapsulé en PHP
327
 *
328
 * @uses lire_fichier()
329
 *
330
 * @param string $fichier
331
 *     Chemin du fichier
332
 * @param string $contenu
333
 *     Le contenu du fichier sera placé dans cette variable
334
 * @param array $options
335
 *     Options tel que :
336
 *
337
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
338
 * @return bool
339
 *     true si l'opération a réussie, false sinon.
340
 */
341
function lire_fichier_securise($fichier, &$contenu, $options = array()) {
342
	if ($res = lire_fichier($fichier, $contenu, $options)) {
343
		$contenu = substr($contenu, strlen("<" . "?php die ('Acces interdit'); ?" . ">\n"));
344
	}
345
346
	return $res;
347
}
348
349
/**
350
 * Affiche un message d’erreur bloquant, indiquant qu’il n’est pas possible de créer
351
 * le fichier à cause des droits sur le répertoire parent au fichier.
352
 *
353
 * Arrête le script PHP par un exit;
354
 *
355
 * @uses minipres() Pour afficher le message
356
 *
357
 * @param string $fichier
358
 *     Chemin du fichier
359
 **/
360
function raler_fichier($fichier) {
361
	include_spip('inc/minipres');
362
	$dir = dirname($fichier);
363
	http_status(401);
364
	echo minipres(_T('texte_inc_meta_2'), "<h4 style='color: red'>"
365
		. _T('texte_inc_meta_1', array('fichier' => $fichier))
366
		. " <a href='"
367
		. generer_url_ecrire('install', "etape=chmod&test_dir=$dir")
368
		. "'>"
369
		. _T('texte_inc_meta_2')
370
		. "</a> "
371
		. _T('texte_inc_meta_3',
372
			array('repertoire' => joli_repertoire($dir)))
373
		. "</h4>\n");
374
	exit;
375
}
376
377
378
/**
379
 * Teste si un fichier est récent (moins de n secondes)
380
 *
381
 * @param string $fichier
382
 *     Chemin du fichier
383
 * @param int $n
384
 *     Âge testé, en secondes
385
 * @return bool
386
 *     - true si récent, false sinon
387
 */
388
function jeune_fichier($fichier, $n) {
389
	if (!file_exists($fichier)) {
390
		return false;
391
	}
392
	if (!$c = @filemtime($fichier)) {
393
		return false;
394
	}
395
396
	return (time() - $n <= $c);
397
}
398
399
/**
400
 * Supprimer un fichier de manière sympa (flock)
401
 *
402
 * @param string $fichier
403
 *     Chemin du fichier
404
 * @param bool $lock
405
 *     true pour utiliser un verrou
406
 * @return bool|void
407
 *     - true si le fichier n'existe pas
408
 *     - false si on n'arrive pas poser le verrou
409
 *     - void sinon
410
 */
411
function supprimer_fichier($fichier, $lock = true) {
412
	if (!@file_exists($fichier)) {
413
		return true;
414
	}
415
416
	if ($lock) {
417
		// verrouiller le fichier destination
418
		if (!$fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
419
			return false;
420
		}
421
422
		// liberer le verrou
423
		spip_fclose_unlock($fp);
424
	}
425
426
	// supprimer
427
	return @unlink($fichier);
428
}
429
430
/**
431
 * Supprimer brutalement un fichier, s'il existe
432
 *
433
 * @param string $f
434
 *     Chemin du fichier
435
 */
436
function spip_unlink($f) {
437
	if (!is_dir($f)) {
438
		supprimer_fichier($f, false);
439
	} else {
440
		@unlink("$f/.ok");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
441
		@rmdir($f);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
442
	}
443
}
444
445
/**
446
 * Invalidates a PHP file from any active opcode caches.
447
 *
448
 * If the opcode cache does not support the invalidation of individual files,
449
 * the entire cache will be flushed.
450
 * kudo : http://cgit.drupalcode.org/drupal/commit/?id=be97f50
451
 *
452
 * @param string $filepath
453
 *   The absolute path of the PHP file to invalidate.
454
 */
455
function spip_clear_opcode_cache($filepath) {
456
	clearstatcache(true, $filepath);
457
458
	// Zend OPcache
459
	if (function_exists('opcache_invalidate')) {
460
		$invalidate = @opcache_invalidate($filepath, true);
461
		// si l'invalidation a echoue lever un flag
462
		if (!$invalidate and !defined('_spip_attend_invalidation_opcode_cache')) {
463
			define('_spip_attend_invalidation_opcode_cache',true);
464
		}
465
	} elseif (!defined('_spip_attend_invalidation_opcode_cache')) {
466
		// n'agira que si opcache est effectivement actif (il semble qu'on a pas toujours la fonction opcache_invalidate)
467
		define('_spip_attend_invalidation_opcode_cache',true);
468
	}
469
	// APC.
470
	if (function_exists('apc_delete_file')) {
471
		// apc_delete_file() throws a PHP warning in case the specified file was
472
		// not compiled yet.
473
		// @see http://php.net/apc-delete-file
474
		@apc_delete_file($filepath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
475
	}
476
}
477
478
/**
479
 * Attendre l'invalidation de l'opcache
480
 * 
481
 * Si opcache est actif et en mode `validate_timestamps`,
482
 * le timestamp du fichier ne sera vérifié qu'après une durée 
483
 * en secondes fixée par `revalidate_freq`.
484
 * 
485
 * Il faut donc attendre ce temps là pour être sûr qu'on va bien 
486
 * bénéficier de la recompilation du fichier par l'opcache.
487
 * 
488
 * Ne fait rien en dehors de ce cas
489
 * 
490
 * @note
491
 *     C'est une config foireuse déconseillée de opcode cache mais 
492
 *     malheureusement utilisée par Octave.
493
 * @link http://stackoverflow.com/questions/25649416/when-exactly-does-php-5-5-opcache-check-file-timestamp-based-on-revalidate-freq
494
 * @link http://wiki.mikejung.biz/PHP_OPcache
495
 *
496
 */
497
function spip_attend_invalidation_opcode_cache($timestamp = null) {
498
	if (function_exists('opcache_get_configuration')
499
		and @ini_get('opcache.enable')
500
		and @ini_get('opcache.validate_timestamps')
501
		and ($duree = intval(@ini_get('opcache.revalidate_freq')) or $duree = 2)
502
		and defined('_spip_attend_invalidation_opcode_cache') // des invalidations ont echouees
503
	) {
504
		$wait = $duree + 1;
505
		if ($timestamp) {
506
			$wait -= (time() - $timestamp);
507
			if ($wait<0) {
508
				$wait = 0;
509
			}
510
		}
511
		spip_log('Probleme de configuration opcache.revalidate_freq '. $duree .'s : on attend '.$wait.'s', _LOG_INFO_IMPORTANTE);
512
		if ($wait) {
513
			sleep($duree + 1);
514
		}
515
	}
516
}
517
518
519
/**
520
 * Suppression complete d'un repertoire.
521
 *
522
 * @link http://www.php.net/manual/en/function.rmdir.php#92050
523
 *
524
 * @param string $dir Chemin du repertoire
525
 * @return bool Suppression reussie.
526
 */
527
function supprimer_repertoire($dir) {
528
	if (!file_exists($dir)) {
529
		return true;
530
	}
531
	if (!is_dir($dir) || is_link($dir)) {
532
		return @unlink($dir);
533
	}
534
535
	foreach (scandir($dir) as $item) {
536
		if ($item == '.' || $item == '..') {
537
			continue;
538
		}
539
		if (!supprimer_repertoire($dir . "/" . $item)) {
540
			@chmod($dir . "/" . $item, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
541
			if (!supprimer_repertoire($dir . "/" . $item)) {
542
				return false;
543
			}
544
		};
545
	}
546
547
	return @rmdir($dir);
548
}
549
550
551
/**
552
 * Crée un sous répertoire
553
 *
554
 * Retourne `$base/${subdir}/` si le sous-repertoire peut être crée
555
 *
556
 * @example
557
 *     ```
558
 *     sous_repertoire(_DIR_CACHE, 'demo');
559
 *     sous_repertoire(_DIR_CACHE . '/demo');
560
 *     ```
561
 *
562
 * @param string $base
563
 *     - Chemin du répertoire parent (avec $subdir)
564
 *     - sinon chemin du répertoire à créer
565
 * @param string $subdir
566
 *     - Nom du sous répertoire à créer,
567
 *     - non transmis, `$subdir` vaut alors ce qui suit le dernier `/` dans `$base`
568
 * @param bool $nobase
569
 *     true pour ne pas avoir le chemin du parent `$base/` dans le retour
570
 * @param bool $tantpis
571
 *     true pour ne pas raler en cas de non création du répertoire
572
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
573
 *     Chemin du répertoire créé.
574
 **/
575
function sous_repertoire($base, $subdir = '', $nobase = false, $tantpis = false) {
576
	static $dirs = array();
577
578
	$base = str_replace("//", "/", $base);
579
580
	# suppr le dernier caractere si c'est un /
581
	$base = rtrim($base, '/');
582
583
	if (!strlen($subdir)) {
584
		$n = strrpos($base, "/");
585
		if ($n === false) {
586
			return $nobase ? '' : ($base . '/');
587
		}
588
		$subdir = substr($base, $n + 1);
589
		$base = substr($base, 0, $n + 1);
590
	} else {
591
		$base .= '/';
592
		$subdir = str_replace("/", "", $subdir);
593
	}
594
595
	$baseaff = $nobase ? '' : $base;
596
	if (isset($dirs[$base . $subdir])) {
597
		return $baseaff . $dirs[$base . $subdir];
598
	}
599
600
	$path = $base . $subdir; # $path = 'IMG/distant/pdf' ou 'IMG/distant_pdf'
601
602
	if (file_exists("$path/.ok")) {
603
		return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
604
	}
605
606
	@mkdir($path, _SPIP_CHMOD);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
607
	@chmod($path, _SPIP_CHMOD);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
608
609
	if (is_dir($path) && is_writable($path)) {
610
		@touch("$path/.ok");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
611
		spip_log("creation $base$subdir/");
612
613
		return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
614
	}
615
616
	// en cas d'echec c'est peut etre tout simplement que le disque est plein :
617
	// l'inode du fichier dir_test existe, mais impossible d'y mettre du contenu
618
	spip_log("echec creation $base${subdir}");
619
	if ($tantpis) {
620
		return '';
621
	}
622
	if (!_DIR_RESTREINT) {
623
		$base = preg_replace(',^' . _DIR_RACINE . ',', '', $base);
624
	}
625
	$base .= $subdir;
626
	raler_fichier($base . '/.ok');
627
}
628
629
630
/**
631
 * Parcourt récursivement le repertoire `$dir`, et renvoie les
632
 * fichiers dont le chemin vérifie le pattern (preg) donné en argument.
633
 *
634
 * En cas d'echec retourne un `array()` vide
635
 *
636
 * @example
637
 *     ```
638
 *     $x = preg_files('ecrire/data/', '[.]lock$');
639
 *     // $x array()
640
 *     ```
641
 *
642
 * @note
643
 *   Attention, afin de conserver la compatibilite avec les repertoires '.plat'
644
 *   si `$dir = 'rep/sous_rep_'` au lieu de `rep/sous_rep/` on scanne `rep/` et on
645
 *   applique un pattern `^rep/sous_rep_`
646
 *
647
 * @param string $dir
648
 *     Répertoire à parcourir
649
 * @param int|string $pattern
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $pattern a bit more specific; maybe use integer.
Loading history...
650
 *     Expression régulière pour trouver des fichiers, tel que `[.]lock$`
651
 * @param int $maxfiles
652
 *     Nombre de fichiers maximums retournés
653
 * @param array $recurs
654
 *     false pour ne pas descendre dans les sous répertoires
655
 * @return array
656
 *     Chemins des fichiers trouvés.
657
 **/
658
function preg_files($dir, $pattern = -1 /* AUTO */, $maxfiles = 10000, $recurs = array()) {
659
	$nbfiles = 0;
660
	if ($pattern == -1) {
661
		$pattern = "^$dir";
662
	}
663
	$fichiers = array();
664
	// revenir au repertoire racine si on a recu dossier/truc
665
	// pour regarder dossier/truc/ ne pas oublier le / final
666
	$dir = preg_replace(',/[^/]*$,', '', $dir);
667
	if ($dir == '') {
668
		$dir = '.';
669
	}
670
671
	if (@is_dir($dir) and is_readable($dir) and $d = opendir($dir)) {
672
		while (($f = readdir($d)) !== false && ($nbfiles < $maxfiles)) {
673
			if ($f[0] != '.' # ignorer . .. .svn etc
674
				and $f != 'CVS'
675
				and $f != 'remove.txt'
676
				and is_readable($f = "$dir/$f")
677
			) {
678
				if (is_file($f)) {
679
					if (preg_match(";$pattern;iS", $f)) {
680
						$fichiers[] = $f;
681
						$nbfiles++;
682
					}
683
				} else {
684
					if (is_dir($f) and is_array($recurs)) {
685
						$rp = @realpath($f);
686
						if (!is_string($rp) or !strlen($rp)) {
687
							$rp = $f;
688
						} # realpath n'est peut etre pas autorise
689
						if (!isset($recurs[$rp])) {
690
							$recurs[$rp] = true;
691
							$beginning = $fichiers;
692
							$end = preg_files("$f/", $pattern,
693
								$maxfiles - $nbfiles, $recurs);
694
							$fichiers = array_merge((array)$beginning, (array)$end);
695
							$nbfiles = count($fichiers);
696
						}
697
					}
698
				}
699
			}
700
		}
701
		closedir($d);
702
	}
703
	sort($fichiers);
704
705
	return $fichiers;
706
}
707