Completed
Push — master ( 1a3b2f...b50fb4 )
by cam
09:59
created

flock.php ➔ spip_file_get_contents()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 8
nop 1
dl 0
loc 19
rs 9.0111
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2018                                                *
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
24
/**
25
 * Autoriser la création de faux répertoires ?
26
 *
27
 * Ajouter `define('_CREER_DIR_PLAT', true);` dans mes_options pour restaurer
28
 * le fonctionnement des faux répertoires en `.plat`
29
 */
30
define('_CREER_DIR_PLAT', false);
31
if (!defined('_TEST_FILE_EXISTS')) {
32
	/** Permettre d'éviter des tests file_exists sur certains hébergeurs */
33
	define('_TEST_FILE_EXISTS', preg_match(',(online|free)[.]fr$,', isset($_ENV["HTTP_HOST"]) ? $_ENV["HTTP_HOST"] : ""));
34
}
35
36
#define('_SPIP_LOCK_MODE',0); // ne pas utiliser de lock (deconseille)
37
#define('_SPIP_LOCK_MODE',1); // utiliser le flock php
38
#define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip
39
40
if (_SPIP_LOCK_MODE == 2) {
41
	include_spip('inc/nfslock');
42
}
43
44
$GLOBALS['liste_verrous'] = array();
45
46
/**
47
 * Ouvre un fichier et le vérrouille
48
 *
49
 * @link http://php.net/manual/fr/function.flock.php pour le type de verrou.
50
 * @see  _SPIP_LOCK_MODE
51
 * @see  spip_fclose_unlock()
52
 * @uses spip_nfslock() si _SPIP_LOCK_MODE = 2.
53
 *
54
 * @param string $fichier
55
 *     Chemin du fichier
56
 * @param string $mode
57
 *     Mode d'ouverture du fichier (r,w,...)
58
 * @param string $verrou
59
 *     Type de verrou (avec _SPIP_LOCK_MODE = 1)
60
 * @return Resource
61
 *     Ressource sur le fichier ouvert, sinon false.
62
 **/
63
function spip_fopen_lock($fichier, $mode, $verrou) {
64
	if (_SPIP_LOCK_MODE == 1) {
65
		if ($fl = @fopen($fichier, $mode)) {
66
			// verrou
67
			@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...
68
		}
69
70
		return $fl;
71
	} elseif (_SPIP_LOCK_MODE == 2) {
72
		if (($verrou = spip_nfslock($fichier)) && ($fl = @fopen($fichier, $mode))) {
73
			$GLOBALS['liste_verrous'][$fl] = array($fichier, $verrou);
74
75
			return $fl;
76
		} else {
77
			return false;
78
		}
79
	}
80
81
	return @fopen($fichier, $mode);
82
}
83
84
/**
85
 * Dévérrouille et ferme un fichier
86
 *
87
 * @see _SPIP_LOCK_MODE
88
 * @see spip_fopen_lock()
89
 *
90
 * @param string $handle
91
 *     Chemin du fichier
92
 * @return bool
93
 *     true si succès, false sinon.
94
 **/
95
function spip_fclose_unlock($handle) {
96
	if (_SPIP_LOCK_MODE == 1) {
97
		@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...
98
	} elseif (_SPIP_LOCK_MODE == 2) {
99
		spip_nfsunlock(reset($GLOBALS['liste_verrous'][$handle]), end($GLOBALS['liste_verrous'][$handle]));
100
		unset($GLOBALS['liste_verrous'][$handle]);
101
	}
102
103
	return @fclose($handle);
104
}
105
106
107
/**
108
 * Retourne le contenu d'un fichier, même si celui ci est compréssé
109
 * avec une extension en `.gz`
110
 *
111
 * @param string $fichier
112
 *     Chemin du fichier
113
 * @return string
114
 *     Contenu du fichier
115
 **/
116
function spip_file_get_contents($fichier) {
117
	if (substr($fichier, -3) != '.gz') {
118
		if (function_exists('file_get_contents')) {
119
			// quand on est sous windows on ne sait pas si file_get_contents marche
120
			// on essaye : si ca retourne du contenu alors c'est bon
121
			// sinon on fait un file() pour avoir le coeur net
122
			$contenu = @file_get_contents($fichier);
123
			if (!$contenu and _OS_SERVEUR == 'windows') {
124
				$contenu = @file($fichier);
125
			}
126
		} else {
127
			$contenu = @file($fichier);
128
		}
129
	} else {
130
		$contenu = @gzfile($fichier);
131
	}
132
133
	return is_array($contenu) ? join('', $contenu) : (string)$contenu;
134
}
135
136
137
/**
138
 * Lit un fichier et place son contenu dans le paramètre transmis.
139
 *
140
 * Décompresse automatiquement les fichiers `.gz`
141
 *
142
 * @uses spip_fopen_lock()
143
 * @uses spip_file_get_contents()
144
 * @uses spip_fclose_unlock()
145
 *
146
 * @param string $fichier
147
 *     Chemin du fichier
148
 * @param string $contenu
149
 *     Le contenu du fichier sera placé dans cette variable
150
 * @param array $options
151
 *     Options tel que :
152
 *
153
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
154
 * @return bool
155
 *     true si l'opération a réussie, false sinon.
156
 **/
157
function lire_fichier($fichier, &$contenu, $options = array()) {
158
	$contenu = '';
159
	// inutile car si le fichier n'existe pas, le lock va renvoyer false juste apres
160
	// economisons donc les acces disque, sauf chez free qui rale pour un rien
161
	if (_TEST_FILE_EXISTS and !@file_exists($fichier)) {
162
		return false;
163
	}
164
165
	#spip_timer('lire_fichier');
166
167
	// pas de @ sur spip_fopen_lock qui est silencieux de toute facon
168
	if ($fl = spip_fopen_lock($fichier, 'r', LOCK_SH)) {
169
		// lire le fichier avant tout
170
		$contenu = spip_file_get_contents($fichier);
171
172
		// le fichier a-t-il ete supprime par le locker ?
173
		// on ne verifie que si la tentative de lecture a echoue
174
		// pour discriminer un contenu vide d'un fichier absent
175
		// et eviter un acces disque
176
		if (!$contenu and !@file_exists($fichier)) {
177
			spip_fclose_unlock($fl);
178
179
			return false;
180
		}
181
182
		// liberer le verrou
183
		spip_fclose_unlock($fl);
184
185
		// Verifications
186
		$ok = true;
187
		if (isset($options['phpcheck']) and $options['phpcheck'] == 'oui') {
188
			$ok &= (preg_match(",[?]>\n?$,", $contenu));
189
		}
190
191
		#spip_log("$fread $fichier ".spip_timer('lire_fichier'));
192
		if (!$ok) {
193
			spip_log("echec lecture $fichier");
194
		}
195
196
		return $ok;
197
	}
198
199
	return false;
200
}
201
202
203
/**
204
 * Écrit un fichier de manière un peu sûre
205
 *
206
 * Cette écriture s’exécute de façon sécurisée en posant un verrou sur
207
 * le fichier avant sa modification. Les fichiers .gz sont compressés.
208
 *
209
 * @uses raler_fichier() Si le fichier n'a pu peut être écrit
210
 * @see  lire_fichier()
211
 * @see  supprimer_fichier()
212
 *
213
 * @param string $fichier
214
 *     Chemin du fichier
215
 * @param string $contenu
216
 *     Contenu à écrire
217
 * @param bool $ignorer_echec
218
 *     - true pour ne pas raler en cas d'erreur
219
 *     - false affichera un message si on est webmestre
220
 * @param bool $truncate
221
 *     Écriture avec troncation ?
222
 * @return bool
223
 *     - true si l’écriture s’est déroulée sans problème.
224
 **/
225
function ecrire_fichier($fichier, $contenu, $ignorer_echec = false, $truncate = true) {
226
227
	#spip_timer('ecrire_fichier');
228
229
	// verrouiller le fichier destination
230
	if ($fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
231
		// ecrire les donnees, compressees le cas echeant
232
		// (on ouvre un nouveau pointeur sur le fichier, ce qui a l'avantage
233
		// de le recreer si le locker qui nous precede l'avait supprime...)
234
		if (substr($fichier, -3) == '.gz') {
235
			$contenu = gzencode($contenu);
236
		}
237
		// si c'est une ecriture avec troncation , on fait plutot une ecriture complete a cote suivie unlink+rename
238
		// pour etre sur d'avoir une operation atomique
239
		// y compris en NFS : http://www.ietf.org/rfc/rfc1094.txt
240
		// sauf sous wintruc ou ca ne marche pas
241
		$ok = false;
242
		if ($truncate and _OS_SERVEUR != 'windows') {
243
			if (!function_exists('creer_uniqid')) {
244
				include_spip('inc/acces');
245
			}
246
			$id = creer_uniqid();
247
			// on ouvre un pointeur sur un fichier temporaire en ecriture +raz
248
			if ($fp2 = spip_fopen_lock("$fichier.$id", 'w', LOCK_EX)) {
249
				$s = @fputs($fp2, $contenu, $a = strlen($contenu));
250
				$ok = ($s == $a);
251
				spip_fclose_unlock($fp2);
252
				spip_fclose_unlock($fp);
253
				// unlink direct et pas spip_unlink car on avait deja le verrou
254
				// a priori pas besoin car rename ecrase la cible
255
				// @unlink($fichier);
256
				// le rename aussitot, atomique quand on est pas sous windows
257
				// au pire on arrive en second en cas de concourance, et le rename echoue
258
				// --> on a la version de l'autre process qui doit etre identique
259
				@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...
260
				// precaution en cas d'echec du rename
261
				if (!_TEST_FILE_EXISTS or @file_exists("$fichier.$id")) {
262
					@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...
263
				}
264
				if ($ok) {
265
					$ok = file_exists($fichier);
266
				}
267
			} else // echec mais penser a fermer ..
268
			{
269
				spip_fclose_unlock($fp);
270
			}
271
		}
272
		// sinon ou si methode precedente a echoueee
273
		// on se rabat sur la methode ancienne
274
		if (!$ok) {
275
			// ici on est en ajout ou sous windows, cas desespere
276
			if ($truncate) {
277
				@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...
278
			}
279
			$s = @fputs($fp, $contenu, $a = strlen($contenu));
280
281
			$ok = ($s == $a);
282
			spip_fclose_unlock($fp);
283
		}
284
285
		// liberer le verrou et fermer le fichier
286
		@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...
287
		if ($ok) {
288
			if (strpos($fichier, ".php") !== false) {
289
				spip_clear_opcode_cache(realpath($fichier));
290
			}
291
292
			return $ok;
293
		}
294
	}
295
296
	if (!$ignorer_echec) {
297
		include_spip('inc/autoriser');
298
		if (autoriser('chargerftp')) {
299
			raler_fichier($fichier);
300
		}
301
		spip_unlink($fichier);
302
	}
303
	spip_log("Ecriture fichier $fichier impossible", _LOG_INFO_IMPORTANTE);
304
305
	return false;
306
}
307
308
/**
309
 * Écrire un contenu dans un fichier encapsulé en PHP pour en empêcher l'accès en l'absence
310
 * de fichier htaccess
311
 *
312
 * @uses ecrire_fichier()
313
 *
314
 * @param string $fichier
315
 *     Chemin du fichier
316
 * @param string $contenu
317
 *     Contenu à écrire
318
 * @param bool $ecrire_quand_meme
319
 *     - true pour ne pas raler en cas d'erreur
320
 *     - false affichera un message si on est webmestre
321
 * @param bool $truncate
322
 *     Écriture avec troncation ?
323
 */
324
function ecrire_fichier_securise($fichier, $contenu, $ecrire_quand_meme = false, $truncate = true) {
325
	if (substr($fichier, -4) !== '.php') {
326
		spip_log('Erreur de programmation: ' . $fichier . ' doit finir par .php');
327
	}
328
	$contenu = "<" . "?php die ('Acces interdit'); ?" . ">\n" . $contenu;
329
330
	return ecrire_fichier($fichier, $contenu, $ecrire_quand_meme, $truncate);
331
}
332
333
/**
334
 * Lire un fichier encapsulé en PHP
335
 *
336
 * @uses lire_fichier()
337
 *
338
 * @param string $fichier
339
 *     Chemin du fichier
340
 * @param string $contenu
341
 *     Le contenu du fichier sera placé dans cette variable
342
 * @param array $options
343
 *     Options tel que :
344
 *
345
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
346
 * @return bool
347
 *     true si l'opération a réussie, false sinon.
348
 */
349
function lire_fichier_securise($fichier, &$contenu, $options = array()) {
350
	if ($res = lire_fichier($fichier, $contenu, $options)) {
351
		$contenu = substr($contenu, strlen("<" . "?php die ('Acces interdit'); ?" . ">\n"));
352
	}
353
354
	return $res;
355
}
356
357
/**
358
 * Affiche un message d’erreur bloquant, indiquant qu’il n’est pas possible de créer
359
 * le fichier à cause des droits sur le répertoire parent au fichier.
360
 *
361
 * Arrête le script PHP par un exit;
362
 *
363
 * @uses minipres() Pour afficher le message
364
 *
365
 * @param string $fichier
366
 *     Chemin du fichier
367
 **/
368
function raler_fichier($fichier) {
369
	include_spip('inc/minipres');
370
	$dir = dirname($fichier);
371
	http_status(401);
372
	echo minipres(_T('texte_inc_meta_2'), "<h4 style='color: red'>"
373
		. _T('texte_inc_meta_1', array('fichier' => $fichier))
374
		. " <a href='"
375
		. generer_url_ecrire('install', "etape=chmod&test_dir=$dir")
376
		. "'>"
377
		. _T('texte_inc_meta_2')
378
		. "</a> "
379
		. _T('texte_inc_meta_3',
380
			array('repertoire' => joli_repertoire($dir)))
381
		. "</h4>\n");
382
	exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function raler_fichier() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
383
}
384
385
386
/**
387
 * Teste si un fichier est récent (moins de n secondes)
388
 *
389
 * @param string $fichier
390
 *     Chemin du fichier
391
 * @param int $n
392
 *     Âge testé, en secondes
393
 * @return bool
394
 *     - true si récent, false sinon
395
 */
396
function jeune_fichier($fichier, $n) {
397
	if (!file_exists($fichier)) {
398
		return false;
399
	}
400
	if (!$c = @filemtime($fichier)) {
401
		return false;
402
	}
403
404
	return (time() - $n <= $c);
405
}
406
407
/**
408
 * Supprimer un fichier de manière sympa (flock)
409
 *
410
 * @param string $fichier
411
 *     Chemin du fichier
412
 * @param bool $lock
413
 *     true pour utiliser un verrou
414
 * @return bool|void
415
 *     - true si le fichier n'existe pas
416
 *     - false si on n'arrive pas poser le verrou
417
 *     - void sinon
418
 */
419
function supprimer_fichier($fichier, $lock = true) {
420
	if (!@file_exists($fichier)) {
421
		return true;
422
	}
423
424
	if ($lock) {
425
		// verrouiller le fichier destination
426
		if (!$fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
427
			return false;
428
		}
429
430
		// liberer le verrou
431
		spip_fclose_unlock($fp);
432
	}
433
434
	// supprimer
435
	return @unlink($fichier);
436
}
437
438
/**
439
 * Supprimer brutalement un fichier, s'il existe
440
 *
441
 * @param string $f
442
 *     Chemin du fichier
443
 */
444
function spip_unlink($f) {
445
	if (!is_dir($f)) {
446
		supprimer_fichier($f, false);
447
	} else {
448
		@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...
449
		@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...
450
	}
451
}
452
453
/**
454
 * Invalidates a PHP file from any active opcode caches.
455
 *
456
 * If the opcode cache does not support the invalidation of individual files,
457
 * the entire cache will be flushed.
458
 * kudo : http://cgit.drupalcode.org/drupal/commit/?id=be97f50
459
 *
460
 * @param string $filepath
461
 *   The absolute path of the PHP file to invalidate.
462
 */
463
function spip_clear_opcode_cache($filepath) {
464
	clearstatcache(true, $filepath);
465
466
	// Zend OPcache
467
	if (function_exists('opcache_invalidate')) {
468
		opcache_invalidate($filepath, true);
469
	}
470
	// APC.
471
	if (function_exists('apc_delete_file')) {
472
		// apc_delete_file() throws a PHP warning in case the specified file was
473
		// not compiled yet.
474
		// @see http://php.net/apc-delete-file
475
		@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...
476
	}
477
}
478
479
/**
480
 * Attendre l'invalidation de l'opcache
481
 * 
482
 * Si opcache est actif et en mode `validate_timestamps`,
483
 * le timestamp du fichier ne sera vérifié qu'après une durée 
484
 * en secondes fixée par `revalidate_freq`.
485
 * 
486
 * Il faut donc attendre ce temps là pour être sûr qu'on va bien 
487
 * bénéficier de la recompilation du fichier par l'opcache.
488
 * 
489
 * Ne fait rien en dehors de ce cas
490
 * 
491
 * @note
492
 *     C'est une config foireuse déconseillée de opcode cache mais 
493
 *     malheureusement utilisée par Octave.
494
 * @link http://stackoverflow.com/questions/25649416/when-exactly-does-php-5-5-opcache-check-file-timestamp-based-on-revalidate-freq
495
 * @link http://wiki.mikejung.biz/PHP_OPcache
496
 *
497
 */
498
function spip_attend_invalidation_opcode_cache() {
499
	if (function_exists('opcache_get_configuration')
500
		and @ini_get('opcache.enable')
501
		and @ini_get('opcache.validate_timestamps')
502
		and $duree = @ini_get('opcache.revalidate_freq')
503
	) {
504
		spip_log('Probleme de configuration opcache.revalidate_freq '. $duree .'s', _LOG_INFO_IMPORTANTE);
505
		sleep($duree + 1);
506
	}
507
}
508
509
510
/**
511
 * Suppression complete d'un repertoire.
512
 *
513
 * @link http://www.php.net/manual/en/function.rmdir.php#92050
514
 *
515
 * @param string $dir Chemin du repertoire
516
 * @return bool Suppression reussie.
517
 */
518
function supprimer_repertoire($dir) {
519
	if (!file_exists($dir)) {
520
		return true;
521
	}
522
	if (!is_dir($dir) || is_link($dir)) {
523
		return @unlink($dir);
524
	}
525
526
	foreach (scandir($dir) as $item) {
527
		if ($item == '.' || $item == '..') {
528
			continue;
529
		}
530
		if (!supprimer_repertoire($dir . "/" . $item)) {
531
			@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...
532
			if (!supprimer_repertoire($dir . "/" . $item)) {
533
				return false;
534
			}
535
		};
536
	}
537
538
	return @rmdir($dir);
539
}
540
541
542
/**
543
 * Crée un sous répertoire
544
 *
545
 * Retourne `$base/${subdir}/` si le sous-repertoire peut être crée,
546
 * `$base/${subdir}_` sinon.
547
 *
548
 * @example
549
 *     ```
550
 *     sous_repertoire(_DIR_CACHE, 'demo');
551
 *     sous_repertoire(_DIR_CACHE . '/demo');
552
 *     ```
553
 *
554
 * @param string $base
555
 *     - Chemin du répertoire parent (avec $subdir)
556
 *     - sinon chemin du répertoire à créer
557
 * @param string $subdir
558
 *     - Nom du sous répertoire à créer,
559
 *     - non transmis, `$subdir` vaut alors ce qui suit le dernier `/` dans `$base`
560
 * @param bool $nobase
561
 *     true pour ne pas avoir le chemin du parent `$base/` dans le retour
562
 * @param bool $tantpis
563
 *     true pour ne pas raler en cas de non création du répertoire
564
 * @return string
565
 *     Chemin du répertoire créé.
566
 **/
567
function sous_repertoire($base, $subdir = '', $nobase = false, $tantpis = false) {
568
	static $dirs = array();
569
570
	$base = str_replace("//", "/", $base);
571
572
	# suppr le dernier caractere si c'est un / ou un _
573
	$base = rtrim($base, '/_');
574
575
	if (!strlen($subdir)) {
576
		$n = strrpos($base, "/");
577
		if ($n === false) {
578
			return $nobase ? '' : ($base . '/');
579
		}
580
		$subdir = substr($base, $n + 1);
581
		$base = substr($base, 0, $n + 1);
582
	} else {
583
		$base .= '/';
584
		$subdir = str_replace("/", "", $subdir);
585
	}
586
587
	$baseaff = $nobase ? '' : $base;
588
	if (isset($dirs[$base . $subdir])) {
589
		return $baseaff . $dirs[$base . $subdir];
590
	}
591
592
593 View Code Duplication
	if (_CREER_DIR_PLAT and @file_exists("$base${subdir}.plat")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
594
		return $baseaff . ($dirs[$base . $subdir] = "${subdir}_");
595
	}
596
597
	$path = $base . $subdir; # $path = 'IMG/distant/pdf' ou 'IMG/distant_pdf'
598
599 View Code Duplication
	if (file_exists("$path/.ok")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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