Subversion Repositories Applications.framework

Rev

Rev 269 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
246 jpm 1
<?php
2
class CacheFichier {
3
	/**
282 jpm 4
	 * Options disponibles
5
	 *
6
	 * ====> (string) stockage_chemin :
7
	 * Chemin vers  le dossier devant contenir l'arborescence du cache.
8
	 *
9
	 * =====> (boolean) fichier_verrou :
10
	 * - Active / Désactive le verrouillage des fichiers
11
	 * - Peut éviter la corruption du cache dans de mauvaises circonstances, mais cela ne fonctionne pas sur des serveur
12
	 * multithread et sur les systèmes de fichiers NFS par exemple.
246 jpm 13
	 *
282 jpm 14
	 * =====> (boolean) controle_lecture :
15
	 * - Activer / désactiver le contrôle de lecture
16
	 * - S'il est activé, une clé de contrôle est ajoutée dans le fichier de cache et cette clé est comparée avec celle calculée
17
	 * après la lecture.
246 jpm 18
	 *
282 jpm 19
	 * =====> (string) controle_lecture_type :
20
	 * Type de contrôle de lecture (seulement si le contrôle de lecture est activé).
21
	 * Les valeurs disponibles sont:
22
	 * - «md5» pour un contrôle md5 (le meilleur mais le plus lent)
23
	 * - «crc32» pour un contrôle de hachage crc32 (un peu moins sécurisé, mais plus rapide, un meilleur choix)
24
	 * - «adler32» pour un contrôle de hachage adler32  (excellent choix aussi, plus rapide que crc32)
25
	 * - «strlen» pour un test de longueur uniquement (le plus rapide)
246 jpm 26
	 *
282 jpm 27
	 * =====> (int) dossier_niveau :
28
	 * - Permet de réglez le nombre de niveau de sous-dossier que contiendra l'arborescence des dossiers du cache.
29
	 * 0 signifie "pas de sous-dossier pour le cache",
30
	 * 1 signifie "un niveau de sous-dossier",
31
	 * 2 signifie "deux niveaux" ...
32
	 * Cette option peut accélérer le cache seulement lorsque vous avez plusieurs centaines de fichiers de cache.
33
	 * Seuls des tests spécifiques peuvent vous aider à choisir la meilleure valeur possible pour vous.
34
	 * 1 ou 2 peut être est un bon début.
246 jpm 35
	 *
282 jpm 36
	 * =====> (int) dossier_umask :
37
	 * - Umask pour les sous-dossiers de l'arborescence du cache.
246 jpm 38
	 *
282 jpm 39
	 * =====> (string) fichier_prefixe :
40
	 * - préfixe pour les fichiers du cache
41
	 * - ATTENTION : faite vraiment attention avec cette option, car une valeur trop générique dans le dossier cache du système
42
	 * (comme /tmp) peut provoquer des catastrophes lors du nettoyage du cache.
246 jpm 43
	 *
282 jpm 44
	 * =====> (int) fichier_umask :
45
	 * - Umask pour les fichiers de cache
246 jpm 46
	 *
282 jpm 47
	 * =====> (int) metadonnees_max_taille :
48
	 * - taille maximum pour le tableau de métadonnées du cache (ne changer pas cette valeur sauf si vous savez ce que vous faite)
246 jpm 49
	 *
282 jpm 50
	 * @var array options disponibles
246 jpm 51
	 */
52
	protected $options = array(
53
		'stockage_chemin' => null,
248 jpm 54
		'fichier_verrou' => true,
55
		'controle_lecture' => true,
246 jpm 56
		'controle_lecture_type' => 'crc32',
57
		'dossier_niveau' => 0,
58
		'dossier_umask' => 0700,
59
		'fichier_prefixe' => 'tbf',
60
		'fichier_umask' => 0600,
61
		'metadonnees_max_taille' => 100
62
	);
63
 
64
	/**
65
	 * Array of metadatas (each item is an associative array)
66
	 *
67
	 * @var array
68
	 */
248 jpm 69
	protected $metadonnees = array();
246 jpm 70
 
269 jpm 71
	private $Cache = null;
72
 
246 jpm 73
	/**
74
	 * Constructor
75
	 *
76
	 * @param  array $options associative array of options
77
	 * @throws Zend_Cache_Exception
78
	 * @return void
79
	 */
269 jpm 80
	public function __construct(array $options = array(), Cache $cache) {
81
		$this->Cache = $cache;
282 jpm 82
		$this->initialiserOptionsParConfig();
248 jpm 83
		$this->setOptions($options);
84
 
246 jpm 85
		if (isset($this->options['prefixe_fichier'])) {
86
			if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->options['prefixe_fichier'])) {
87
				trigger_error("Préfixe de nom de fichier invalide : doit contenir seulement [a-zA-Z0-9_]", E_USER_WARNING);
88
			}
89
		}
248 jpm 90
		if ($this->options['metadonnees_max_taille'] < 10) {
246 jpm 91
			trigger_error("Taille du tableau des méta-données invalide, elle doit être > 10", E_USER_WARNING);
92
		}
93
		if (isset($options['dossier_umask']) && is_string($options['dossier_umask'])) {
94
			// See #ZF-4422
95
			$this->options['dossier_umask'] = octdec($this->options['dossier_umask']);
96
		}
97
		if (isset($options['fichier_umask']) && is_string($options['fichier_umask'])) {
98
			// See #ZF-4422
99
			$this->options['fichier_umask'] = octdec($this->options['fichier_umask']);
100
		}
101
	}
248 jpm 102
 
282 jpm 103
	private function initialiserOptionsParConfig() {
104
		while (list($nom, $valeur) = each($this->options)) {
105
			if (Config::existe($nom)) {
106
				$this->options[$nom] = Config::get($nom);
107
			}
108
		}
109
	}
110
 
248 jpm 111
	private function setOptions($options) {
112
		while (list($nom, $valeur) = each($options)) {
113
			if (!is_string($nom)) {
114
				trigger_error("Nom d'option incorecte : $nom", E_USER_WARNING);
115
			}
116
			$nom = strtolower($nom);
117
			if (array_key_exists($nom, $this->options)) {
118
				$this->options[$nom] = $valeur;
119
			}
120
		}
121
	}
246 jpm 122
 
248 jpm 123
   	public function setEmplacement($emplacement) {
246 jpm 124
		if (!is_dir($emplacement)) {
125
			trigger_error("L'emplacement doit être un dossier.", E_USER_WARNING);
126
		}
127
		if (!is_writable($emplacement)) {
128
			trigger_error("Le dossier de stockage du cache n'est pas accessible en écriture", E_USER_WARNING);
129
		}
130
		$emplacement = rtrim(realpath($emplacement), '\\/').DS;
131
		$this->options['stockage_chemin'] = $emplacement;
132
	}
133
 
134
	/**
135
	 * Test if a cache is available for the given id and (if yes) return it (false else)
136
	 *
137
	 * @param string $id cache id
138
	 * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
139
	 * @return string|false cached datas
140
	 */
141
	public function charger($id, $ne_pas_tester_validiter_du_cache = false) {
142
		$donnees = false;
143
		if ($this->tester($id, $ne_pas_tester_validiter_du_cache)) {
144
			$metadonnees = $this->getMetadonneesFichier($id);
145
			$fichier = $this->getFichierNom($id);
146
			$donnees = $this->getContenuFichier($fichier);
147
			if ($this->options['controle_lecture']) {
148
				$cle_secu_donnees = $this->genererCleSecu($donnees, $this->options['controle_lecture_type']);
149
				$cle_secu_controle = $metadonnees['hash'];
150
				if ($cle_secu_donnees != $cle_secu_controle) {
151
					// Probléme détecté par le contrôle de lecture !
152
					// TODO : loguer le pb de sécu
153
					$this->supprimer($id);
154
					$donnees = false;
155
				}
156
			}
157
		}
158
		return $donnees;
159
	}
160
 
161
	/**
162
	 * Teste si un enregistrement en cache est disponible ou pas (pour l'id passé en paramètre).
163
	 *
164
	 * @param string $id identifiant de cache.
165
	 * @return mixed false (le cache n'est pas disponible) ou timestamp (int) "de dernière modification" de l'enregistrement en cache
166
	 */
167
	public function tester($id) {
168
		clearstatcache();
169
		return $this->testerExistenceCache($id, false);
170
	}
171
 
172
	/**
173
	 * Save some string datas into a cache record
174
	 *
175
	 * Note : $data is always "string" (serialization is done by the
176
	 * core not by the backend)
177
	 *
178
	 * @param  string $data			 Datas to cache
179
	 * @param  string $id			   Cache id
180
	 * @param  array  $tags			 Array of strings, the cache record will be tagged by each string entry
181
	 * @param  int	$specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
182
	 * @return boolean true if no problem
183
	 */
248 jpm 184
	public function sauver($donnees, $id, $tags = array(), $duree_vie_specifique = false) {
246 jpm 185
		clearstatcache();
248 jpm 186
		$fichier = $this->getFichierNom($id);
187
		$chemin = $this->getChemin($id);
188
 
189
		$resultat = true;
190
		if ($this->options['dossier_niveau'] > 0) {
191
			if (!is_writable($chemin)) {
246 jpm 192
				// maybe, we just have to build the directory structure
193
				$this->lancerMkdirEtChmodRecursif($id);
194
			}
248 jpm 195
			if (!is_writable($chemin)) {
196
				$resultat = false;
246 jpm 197
			}
198
		}
248 jpm 199
 
200
		if ($resultat === true) {
201
			if ($this->options['controle_lecture']) {
202
				$cle_secu = $this->genererCleSecu($donnees, $this->options['controle_lecture_type']);
203
			} else {
204
				$cle_secu = '';
205
			}
206
 
207
			$metadonnees = array(
208
				'hash' => $cle_secu,
209
				'mtime' => time(),
269 jpm 210
				'expiration' => $this->Cache->getTimestampExpiration($duree_vie_specifique),
248 jpm 211
				'tags' => $tags
212
			);
213
 
214
			if (! $resultat = $this->setMetadonnees($id, $metadonnees)) {
215
				// TODO : ajouter un log
216
			} else {
217
				$resultat = $this->setContenuFichier($fichier, $donnees);
218
			}
246 jpm 219
		}
248 jpm 220
		return $resultat;
246 jpm 221
	}
222
 
223
	/**
224
	 * Remove a cache record
225
	 *
226
	 * @param  string $id cache id
227
	 * @return boolean true if no problem
228
	 */
229
	public function supprimer($id) {
230
		$fichier = $this->getFichierNom($id);
231
		$suppression_fichier = $this->supprimerFichier($fichier);
232
		$suppression_metadonnees = $this->supprimerMetadonnees($id);
233
		return $suppression_metadonnees && $suppression_fichier;
234
	}
235
 
236
	/**
237
	 * Clean some cache records
238
	 *
239
	 * Available modes are :
240
	 * 'all' (default)  => remove all cache entries ($tags is not used)
241
	 * 'old'			=> remove too old cache entries ($tags is not used)
242
	 * 'matchingTag'	=> remove cache entries matching all given tags
243
	 *					 ($tags can be an array of strings or a single string)
244
	 * 'notMatchingTag' => remove cache entries not matching one of the given tags
245
	 *					 ($tags can be an array of strings or a single string)
246
	 * 'matchingAnyTag' => remove cache entries matching any given tags
247
	 *					 ($tags can be an array of strings or a single string)
248
	 *
249
	 * @param string $mode clean mode
250
	 * @param tags array $tags array of tags
251
	 * @return boolean true if no problem
252
	 */
253
	public function nettoyer($mode = Cache::NETTOYAGE_MODE_TOUS, $tags = array()) {
254
		// We use this protected method to hide the recursive stuff
255
		clearstatcache();
248 jpm 256
		return $this->nettoyerFichiers($this->options['stockage_chemin'], $mode, $tags);
246 jpm 257
	}
258
 
259
	/**
260
	 * Return an array of stored cache ids
261
	 *
262
	 * @return array array of stored cache ids (string)
263
	 */
248 jpm 264
	public function getIds() {
265
		return $this->analyserCache($this->options['stockage_chemin'], 'ids', array());
246 jpm 266
	}
267
 
268
	/**
269
	 * Return an array of stored tags
270
	 *
271
	 * @return array array of stored tags (string)
272
	 */
248 jpm 273
	public function getTags() {
274
		return $this->analyserCache($this->options['stockage_chemin'], 'tags', array());
246 jpm 275
	}
276
 
277
	/**
278
	 * Return an array of stored cache ids which match given tags
279
	 *
280
	 * In case of multiple tags, a logical AND is made between tags
281
	 *
282
	 * @param array $tags array of tags
283
	 * @return array array of matching cache ids (string)
284
	 */
248 jpm 285
	public function getIdsAvecLesTags($tags = array()) {
286
		return $this->analyserCache($this->options['stockage_chemin'], 'matching', $tags);
246 jpm 287
	}
288
 
289
	/**
290
	 * Return an array of stored cache ids which don't match given tags
291
	 *
292
	 * In case of multiple tags, a logical OR is made between tags
293
	 *
294
	 * @param array $tags array of tags
295
	 * @return array array of not matching cache ids (string)
296
	 */
248 jpm 297
	public function getIdsSansLesTags($tags = array()) {
298
		return $this->analyserCache($this->options['stockage_chemin'], 'notMatching', $tags);
246 jpm 299
	}
300
 
301
	/**
302
	 * Return an array of stored cache ids which match any given tags
303
	 *
304
	 * In case of multiple tags, a logical AND is made between tags
305
	 *
306
	 * @param array $tags array of tags
307
	 * @return array array of any matching cache ids (string)
308
	 */
248 jpm 309
	public function getIdsAvecUnTag($tags = array()) {
310
		return $this->analyserCache($this->options['stockage_chemin'], 'matchingAny', $tags);
246 jpm 311
	}
312
 
313
	/**
314
	 * Return the filling percentage of the backend storage
315
	 *
316
	 * @throws Zend_Cache_Exception
317
	 * @return int integer between 0 and 100
318
	 */
248 jpm 319
	public function getPourcentageRemplissage() {
250 jpm 320
		$libre = disk_free_space($this->options['stockage_chemin']);
321
		$total = disk_total_space($this->options['stockage_chemin']);
248 jpm 322
 
323
		$pourcentage = 0;
246 jpm 324
		if ($total == 0) {
248 jpm 325
			trigger_error("Impossible d'utiliser la fonction disk_total_space", E_USER_WARNING);
246 jpm 326
		} else {
248 jpm 327
			$pourcentage = ($libre >= $total) ? 100 : ((int) (100. * ($total - $libre) / $total));
246 jpm 328
		}
248 jpm 329
		return $pourcentage;
246 jpm 330
	}
331
 
332
	/**
333
	 * Return an array of metadatas for the given cache id
334
	 *
335
	 * The array must include these keys :
336
	 * - expire : the expire timestamp
337
	 * - tags : a string array of tags
338
	 * - mtime : timestamp of last modification time
339
	 *
340
	 * @param string $id cache id
341
	 * @return array array of metadatas (false if the cache id is not found)
342
	 */
248 jpm 343
	public function getMetadonnees($id) {
344
		if ($metadonnees = $this->getMetadonneesFichier($id)) {
345
			if (time() > $metadonnees['expiration']) {
346
				$metadonnees = false;
347
			} else {
348
				$metadonnees = array(
349
					'expiration' => $metadonnees['expiration'],
350
					'tags' => $metadonnees['tags'],
351
					'mtime' => $metadonnees['mtime']
352
				);
353
			}
246 jpm 354
		}
248 jpm 355
 
356
		return $metadonnees;
246 jpm 357
	}
358
 
359
	/**
360
	 * Give (if possible) an extra lifetime to the given cache id
361
	 *
362
	 * @param string $id cache id
363
	 * @param int $extraLifetime
364
	 * @return boolean true if ok
365
	 */
248 jpm 366
	public function ajouterSupplementDureeDeVie($id, $supplement_duree_de_vie) {
367
		$augmentation = true;
368
		if ($metadonnees = $this->getMetadonneesFichier($id)) {
369
			if (time() > $metadonnees['expiration']) {
370
				$augmentation = false;
371
			} else {
372
				$metadonnees_nouvelle = array(
373
					'hash' => $metadonnees['hash'],
374
					'mtime' => time(),
375
					'expiration' => $metadonnees['expiration'] + $supplement_duree_de_vie,
376
					'tags' => $metadonnees['tags']
377
				);
378
				$augmentation = $this->setMetadonnees($id, $metadonnees_nouvelle);
379
			}
246 jpm 380
		}
248 jpm 381
		return $augmentation;
246 jpm 382
	}
383
 
384
	/**
385
	 * Get a metadatas record
386
	 *
387
	 * @param  string $id  Cache id
388
	 * @return array|false Associative array of metadatas
389
	 */
390
	protected function getMetadonneesFichier($id) {
391
		$metadonnees = false;
392
		if (isset($this->metadonnees[$id])) {
393
			$metadonnees = $this->metadonnees[$id];
394
		} else {
395
			if ($metadonnees = $this->chargerMetadonnees($id)) {
396
				$this->setMetadonnees($id, $metadonnees, false);
397
			}
398
		}
399
		return $metadonnees;
400
	}
401
 
402
	/**
403
	 * Set a metadatas record
404
	 *
405
	 * @param  string $id		Cache id
406
	 * @param  array  $metadatas Associative array of metadatas
407
	 * @param  boolean $save	 optional pass false to disable saving to file
408
	 * @return boolean True if no problem
409
	 */
410
	protected function setMetadonnees($id, $metadonnees, $sauvegarde = true) {
411
		if (count($this->metadonnees) >= $this->options['metadonnees_max_taille']) {
412
			$n = (int) ($this->options['metadonnees_max_taille'] / 10);
413
			$this->metadonnees = array_slice($this->metadonnees, $n);
414
		}
415
 
416
		$resultat = true;
417
		if ($sauvegarde) {
418
			$resultat = $this->sauverMetadonnees($id, $metadonnees);
419
		}
420
		if ($resultat == true) {
421
			$this->metadonnees[$id] = $metadonnees;
422
		}
423
		return $resultat;
424
	}
425
 
426
	/**
427
	 * Drop a metadata record
428
	 *
429
	 * @param  string $id Cache id
430
	 * @return boolean True if no problem
431
	 */
432
	protected function supprimerMetadonnees($id) {
433
		if (isset($this->metadonnees[$id])) {
434
			unset($this->metadonnees[$id]);
435
		}
436
		$fichier_meta = $this->getNomFichierMeta($id);
437
		return $this->supprimerFichier($fichier_meta);
438
	}
439
 
440
	/**
441
	 * Clear the metadatas array
442
	 *
443
	 * @return void
444
	 */
445
	protected function nettoyerMetadonnees() {
446
		$this->metadonnees = array();
447
	}
448
 
449
	/**
450
	 * Load metadatas from disk
451
	 *
452
	 * @param  string $id Cache id
453
	 * @return array|false Metadatas associative array
454
	 */
455
	protected function chargerMetadonnees($id) {
456
		$fichier = $this->getNomFichierMeta($id);
457
		if ($resultat = $this->getContenuFichier($fichier)) {
458
			$resultat = @unserialize($resultat);
459
		}
460
		return $resultat;
461
	}
462
 
463
	/**
464
	 * Save metadatas to disk
465
	 *
466
	 * @param  string $id		Cache id
467
	 * @param  array  $metadatas Associative array
468
	 * @return boolean True if no problem
469
	 */
470
	protected function sauverMetadonnees($id, $metadonnees) {
471
		$fichier = $this->getNomFichierMeta($id);
472
		$resultat = $this->setContenuFichier($fichier, serialize($metadonnees));
473
		return $resultat;
474
	}
475
 
476
	/**
477
	 * Make and return a file name (with path) for metadatas
478
	 *
479
	 * @param  string $id Cache id
480
	 * @return string Metadatas file name (with path)
481
	 */
482
	protected function getNomFichierMeta($id) {
483
		$chemin = $this->getChemin($id);
253 jpm 484
		$fichier_nom = $this->transformaterIdEnNomFichier('interne-meta---'.$id);
246 jpm 485
		return $chemin.$fichier_nom;
486
	}
487
 
488
	/**
489
	 * Check if the given filename is a metadatas one
490
	 *
491
	 * @param  string $fileName File name
492
	 * @return boolean True if it's a metadatas one
493
	 */
494
	protected function etreFichierMeta($fichier_nom) {
495
		$id = $this->transformerNomFichierEnId($fichier_nom);
253 jpm 496
		return (substr($id, 0, 21) == 'interne-meta---') ? true : false;
246 jpm 497
	}
498
 
499
	/**
500
	 * Remove a file
501
	 *
502
	 * If we can't remove the file (because of locks or any problem), we will touch
503
	 * the file to invalidate it
504
	 *
505
	 * @param  string $file Complete file path
506
	 * @return boolean True if ok
507
	 */
508
	protected function supprimerFichier($fichier) {
509
		$resultat = false;
510
		if (is_file($fichier)) {
511
			if ($resultat = @unlink($fichier)) {
512
				// TODO : ajouter un log
513
			}
514
		}
515
		return $resultat;
516
	}
517
 
518
	/**
519
	 * Clean some cache records (protected method used for recursive stuff)
520
	 *
521
	 * Available modes are :
522
	 * Zend_Cache::CLEANING_MODE_ALL (default)	=> remove all cache entries ($tags is not used)
523
	 * Zend_Cache::CLEANING_MODE_OLD			  => remove too old cache entries ($tags is not used)
524
	 * Zend_Cache::CLEANING_MODE_MATCHING_TAG	 => remove cache entries matching all given tags
525
	 *											   ($tags can be an array of strings or a single string)
526
	 * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
527
	 *											   ($tags can be an array of strings or a single string)
528
	 * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
529
	 *											   ($tags can be an array of strings or a single string)
530
	 *
531
	 * @param  string $dir  Directory to clean
532
	 * @param  string $mode Clean mode
533
	 * @param  array  $tags Array of tags
534
	 * @throws Zend_Cache_Exception
535
	 * @return boolean True if no problem
536
	 */
537
	protected function nettoyerFichiers($dossier, $mode = Cache::NETTOYAGE_MODE_TOUS, $tags = array()) {
538
		if (!is_dir($dossier)) {
539
			return false;
540
		}
541
		$resultat = true;
542
		$prefixe = $this->options['fichier_prefixe'];
543
		$glob = @glob($dossier.$prefixe.'--*');
544
		if ($glob === false) {
545
			// On some systems it is impossible to distinguish between empty match and an error.
546
			return true;
547
		}
548
		foreach ($glob as $fichier)  {
549
			if (is_file($fichier)) {
550
				$fichier_nom = basename($fichier);
551
				if ($this->etreFichierMeta($fichier_nom)) {
248 jpm 552
					// Pour le mode Cache::NETTOYAGE_MODE_TOUS, nous essayons de tous supprimer même les vieux fichiers méta
246 jpm 553
					if ($mode != Cache::NETTOYAGE_MODE_TOUS) {
554
						continue;
555
					}
556
				}
557
				$id = $this->transformerNomFichierEnId($fichier_nom);
248 jpm 558
				$metadonnees = $this->getMetadonneesFichier($id);
559
				if ($metadonnees === FALSE) {
560
					$metadonnees = array('expiration' => 1, 'tags' => array());
246 jpm 561
				}
562
				switch ($mode) {
248 jpm 563
					case Cache::NETTOYAGE_MODE_TOUS :
564
						if ($resultat_suppression = $this->supprimer($id)) {
565
							// Dans ce cas seulement, nous acception qu'il y ait un problème avec la suppresssion du fichier meta
566
							$resultat_suppression = $this->supprimerFichier($fichier);
246 jpm 567
						}
248 jpm 568
						$resultat = $resultat && $resultat_suppression;
246 jpm 569
						break;
248 jpm 570
					case Cache::NETTOYAGE_MODE_EXPIRATION :
571
						if (time() > $metadonnees['expiration']) {
572
							$resultat = $this->supprimer($id) && $resultat;
246 jpm 573
						}
574
						break;
248 jpm 575
					case Cache::NETTOYAGE_MODE_AVEC_LES_TAGS :
576
						$correspondance = true;
246 jpm 577
						foreach ($tags as $tag) {
248 jpm 578
							if (!in_array($tag, $metadonnees['tags'])) {
579
								$correspondance = false;
246 jpm 580
								break;
581
							}
582
						}
248 jpm 583
						if ($correspondance) {
584
							$resultat = $this->supprimer($id) && $resultat;
246 jpm 585
						}
586
						break;
248 jpm 587
					case Cache::NETTOYAGE_MODE_SANS_LES_TAGS :
588
						$correspondance = false;
246 jpm 589
						foreach ($tags as $tag) {
248 jpm 590
							if (in_array($tag, $metadonnees['tags'])) {
591
								$correspondance = true;
246 jpm 592
								break;
593
							}
594
						}
248 jpm 595
						if (!$correspondance) {
596
							$resultat = $this->supprimer($id) && $resultat;
246 jpm 597
						}
598
						break;
248 jpm 599
					case Cache::NETTOYAGE_MODE_AVEC_UN_TAG :
600
						$correspondance = false;
246 jpm 601
						foreach ($tags as $tag) {
248 jpm 602
							if (in_array($tag, $metadonnees['tags'])) {
603
								$correspondance = true;
246 jpm 604
								break;
605
							}
606
						}
248 jpm 607
						if ($correspondance) {
608
							$resultat = $this->supprimer($id) && $resultat;
246 jpm 609
						}
610
						break;
611
					default:
248 jpm 612
						trigger_error("Mode de nettoyage invalide pour la méthode nettoyer()", E_USER_WARNING);
246 jpm 613
						break;
614
				}
615
			}
248 jpm 616
			if ((is_dir($fichier)) and ($this->options['dossier_niveau'] > 0)) {
617
				// Appel récursif
618
				$resultat = $this->nettoyerFichiers($fichier.DS, $mode, $tags) && $resultat;
619
				if ($mode == Cache::NETTOYAGE_MODE_TOUS) {
620
					// Si mode == Cache::NETTOYAGE_MODE_TOUS, nous essayons de supprimer la structure aussi
621
					@rmdir($fichier);
246 jpm 622
				}
623
			}
624
		}
625
		return $resultat;
626
	}
627
 
248 jpm 628
	protected function analyserCache($dossier, $mode, $tags = array()) {
629
		if (!is_dir($dossier)) {
246 jpm 630
			return false;
631
		}
248 jpm 632
		$resultat = array();
633
		$prefixe = $this->options['fichier_prefixe'];
634
		$glob = @glob($dossier.$prefixe.'--*');
246 jpm 635
		if ($glob === false) {
636
			// On some systems it is impossible to distinguish between empty match and an error.
637
			return array();
638
		}
248 jpm 639
		foreach ($glob as $fichier)  {
640
			if (is_file($fichier)) {
641
				$nom_fichier = basename($fichier);
642
				$id = $this->transformerNomFichierEnId($nom_fichier);
643
				$metadonnees = $this->getMetadonneesFichier($id);
644
				if ($metadonnees === FALSE) {
246 jpm 645
					continue;
646
				}
248 jpm 647
				if (time() > $metadonnees['expiration']) {
246 jpm 648
					continue;
649
				}
650
				switch ($mode) {
651
					case 'ids':
248 jpm 652
						$resultat[] = $id;
246 jpm 653
						break;
654
					case 'tags':
248 jpm 655
						$resultat = array_unique(array_merge($resultat, $metadonnees['tags']));
246 jpm 656
						break;
657
					case 'matching':
248 jpm 658
						$correspondance = true;
246 jpm 659
						foreach ($tags as $tag) {
248 jpm 660
							if (!in_array($tag, $metadonnees['tags'])) {
661
								$correspondance = false;
246 jpm 662
								break;
663
							}
664
						}
248 jpm 665
						if ($correspondance) {
666
							$resultat[] = $id;
246 jpm 667
						}
668
						break;
669
					case 'notMatching':
248 jpm 670
						$correspondance = false;
246 jpm 671
						foreach ($tags as $tag) {
248 jpm 672
							if (in_array($tag, $metadonnees['tags'])) {
673
								$correspondance = true;
246 jpm 674
								break;
675
							}
676
						}
248 jpm 677
						if (!$correspondance) {
678
							$resultat[] = $id;
246 jpm 679
						}
680
						break;
681
					case 'matchingAny':
248 jpm 682
						$correspondance = false;
246 jpm 683
						foreach ($tags as $tag) {
248 jpm 684
							if (in_array($tag, $metadonnees['tags'])) {
685
								$correspondance = true;
246 jpm 686
								break;
687
							}
688
						}
248 jpm 689
						if ($correspondance) {
690
							$resultat[] = $id;
246 jpm 691
						}
692
						break;
693
					default:
248 jpm 694
						trigger_error("Mode invalide pour la méthode analyserCache()", E_USER_WARNING);
246 jpm 695
						break;
696
				}
697
			}
248 jpm 698
			if ((is_dir($fichier)) and ($this->options['dossier_niveau'] > 0)) {
699
				// Appel récursif
700
				$resultat_analyse_recursive = $this->analyserCache($fichier.DS, $mode, $tags);
701
				if ($resultat_analyse_recursive === false) {
702
					// TODO : ajoute un log
246 jpm 703
				} else {
248 jpm 704
					$resultat = array_unique(array_merge($resultat, $resultat_analyse_recursive));
246 jpm 705
				}
706
			}
707
		}
248 jpm 708
		return array_unique($resultat);
246 jpm 709
	}
710
 
711
	/**
712
	 * Make a control key with the string containing datas
713
	 *
714
	 * @param  string $data		Data
715
	 * @param  string $controlType Type of control 'md5', 'crc32' or 'strlen'
716
	 * @throws Zend_Cache_Exception
717
	 * @return string Control key
718
	 */
719
	protected function genererCleSecu($donnees, $type_de_controle) {
720
		switch ($type_de_controle) {
721
		case 'md5':
722
			return md5($donnees);
723
		case 'crc32':
724
			return crc32($donnees);
725
		case 'strlen':
726
			return strlen($donnees);
727
		case 'adler32':
728
			return hash('adler32', $donnees);
729
		default:
730
			trigger_error("Fonction de génération de clé de sécurité introuvable : $type_de_controle", E_USER_WARNING);
731
		}
732
	}
733
 
734
	/**
735
	 * Transform a cache id into a file name and return it
736
	 *
737
	 * @param  string $id Cache id
738
	 * @return string File name
739
	 */
740
	protected function transformaterIdEnNomFichier($id) {
741
		$prefixe = $this->options['fichier_prefixe'];
248 jpm 742
		$resultat = $prefixe.'---'.$id;
246 jpm 743
		return $resultat;
744
	}
745
 
746
	/**
747
	 * Make and return a file name (with path)
748
	 *
749
	 * @param  string $id Cache id
750
	 * @return string File name (with path)
751
	 */
752
	protected function getFichierNom($id) {
753
		$path = $this->getChemin($id);
754
		$fileName = $this->transformaterIdEnNomFichier($id);
755
		return $path . $fileName;
756
	}
757
 
758
	/**
759
	 * Return the complete directory path of a filename (including hashedDirectoryStructure)
760
	 *
761
	 * @param  string $id Cache id
762
	 * @param  boolean $decoupage if true, returns array of directory parts instead of single string
763
	 * @return string Complete directory path
764
	 */
765
	protected function getChemin($id, $decoupage = false) {
766
		$morceaux = array();
767
		$chemin = $this->options['stockage_chemin'];
768
		$prefixe = $this->options['fichier_prefixe'];
769
		if ($this->options['dossier_niveau'] > 0) {
770
			$hash = hash('adler32', $id);
771
			for ($i = 0 ; $i < $this->options['dossier_niveau'] ; $i++) {
248 jpm 772
				$chemin .= $prefixe.'--'.substr($hash, 0, $i + 1).DS;
246 jpm 773
				$morceaux[] = $chemin;
774
			}
775
		}
776
		return ($decoupage) ? 	$morceaux : $chemin;
777
	}
778
 
779
	/**
780
	 * Make the directory strucuture for the given id
781
	 *
782
	 * @param string $id cache id
783
	 * @return boolean true
784
	 */
785
	protected function lancerMkdirEtChmodRecursif($id) {
786
		$resultat = true;
787
		if ($this->options['dossier_niveau'] > 0) {
788
			$chemins = $this->getChemin($id, true);
789
			foreach ($chemins as $chemin) {
790
				if (!is_dir($chemin)) {
791
					@mkdir($chemin, $this->options['dossier_umask']);
792
					@chmod($chemin, $this->options['dossier_umask']); // see #ZF-320 (this line is required in some configurations)
793
				}
794
			}
795
		}
796
		return $resultat;
797
	}
798
 
799
	/**
800
	 * Test if the given cache id is available (and still valid as a cache record)
801
	 *
802
	 * @param  string  $id					 Cache id
803
	 * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
804
	 * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
805
	 */
806
	protected function testerExistenceCache($id, $ne_pas_tester_validiter_du_cache) {
807
		$resultat = false;
808
		if ($metadonnees = $this->getMetadonnees($id)) {
809
			if ($ne_pas_tester_validiter_du_cache || (time() <= $metadonnees['expiration'])) {
810
				$resultat =  $metadonnees['mtime'];
811
			}
812
		}
813
		return $resultat;
814
	}
815
 
816
	/**
817
	 * Return the file content of the given file
818
	 *
819
	 * @param  string $file File complete path
820
	 * @return string File content (or false if problem)
821
	 */
822
	protected function getContenuFichier($fichier) {
823
		$resultat = false;
824
		if (is_file($fichier)) {
825
			$f = @fopen($fichier, 'rb');
826
			if ($f) {
827
				if ($this->options['fichier_verrou']) @flock($f, LOCK_SH);
828
				$resultat = stream_get_contents($f);
829
				if ($this->options['fichier_verrou']) @flock($f, LOCK_UN);
830
				@fclose($f);
831
			}
832
		}
833
		return $resultat;
834
	}
835
 
836
	/**
837
	 * Put the given string into the given file
838
	 *
839
	 * @param  string $file   File complete path
840
	 * @param  string $string String to put in file
841
	 * @return boolean true if no problem
842
	 */
843
	protected function setContenuFichier($fichier, $chaine) {
844
		$resultat = false;
845
		$f = @fopen($fichier, 'ab+');
846
		if ($f) {
847
			if ($this->options['fichier_verrou']) @flock($f, LOCK_EX);
848
			fseek($f, 0);
849
			ftruncate($f, 0);
850
			$tmp = @fwrite($f, $chaine);
851
			if (!($tmp === FALSE)) {
852
				$resultat = true;
853
			}
854
			@fclose($f);
855
		}
856
		@chmod($fichier, $this->options['fichier_umask']);
857
		return $resultat;
858
	}
859
 
860
	/**
861
	 * Transform a file name into cache id and return it
862
	 *
863
	 * @param  string $fileName File name
864
	 * @return string Cache id
865
	 */
866
	protected function transformerNomFichierEnId($nom_de_fichier) {
867
		$prefixe = $this->options['fichier_prefixe'];
868
		return preg_replace('~^' . $prefixe . '---(.*)$~', '$1', $nom_de_fichier);
869
	}
870
}
871
?>