2 /************************************************************************
3 * Mailer v0.2.1-FINAL Start: 10/22/2009 *
4 * =================== Last change: 10/22/2009 *
6 * -------------------------------------------------------------------- *
7 * File : cachesystem.class.php *
8 * -------------------------------------------------------------------- *
9 * Short description : CacheSystem class *
10 * -------------------------------------------------------------------- *
11 * Kurzbeschreibung : CacheSystem-Klasse *
12 * -------------------------------------------------------------------- *
15 * $Tag:: 0.2.1-FINAL $ *
17 * -------------------------------------------------------------------- *
18 * Copyright (c) 2003 - 2009 by Roland Haeder *
19 * Copyright (c) 2009 - 2011 by Mailer Developer Team *
20 * For more information visit: http://www.mxchange.org *
22 * This program is free software; you can redistribute it and/or modify *
23 * it under the terms of the GNU General Public License as published by *
24 * the Free Software Foundation; either version 2 of the License, or *
25 * (at your option) any later version. *
27 * This program is distributed in the hope that it will be useful, *
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
30 * GNU General Public License for more details. *
32 * You should have received a copy of the GNU General Public License *
33 * along with this program; if not, write to the Free Software *
34 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, *
36 ************************************************************************/
38 // Some security stuff...
39 if (!defined('__SECURITY')) {
46 var $statusCode = 'init';
48 // Full-qualified filename
51 // Resource to cache file
54 // Data array from cache
57 // Version data from cache
58 var $version = array();
62 var $rebuilt = array();
65 var $extension = '.cache';
66 var $status = array();
67 var $readable = array();
71 function CacheSystem () {
72 // Construct full path
73 $this->fullPath = getPath() . getCachePath();
75 // Failed is the default
76 $this->setStatusCode('failed');
78 // Check if path exists
79 if (isDirectory($this->fullPath)) {
80 // Is there a .htaccess file?
81 if (isFileReadable($this->fullPath . '.htaccess')) {
83 $this->setStatusCode('done');
85 // Stop! Set a .htaccess file first
86 $this->setStatusCode('htaccess');
91 // Checks validity of cache file and if content is given
92 function loadCacheFile ($cacheName) {
93 // Remember cache file
94 $this->name = $cacheName;
96 // Construct FQFN (full qualified file name)
97 $this->fqfn = $this->fullPath . $cacheName . $this->extension;
99 // Check if file exists and if version matches
100 if (!isset($this->status[$cacheName])) {
101 // Pre-fetch cache here if found
102 if ($this->isCacheReadable()) $this->getArrayFromCache();
104 //* DEBUG: */ debugOutput('cacheName='.$cacheName.',isCacheReadable='.intval($this->isCacheReadable()).',is_writeable='.intval(is_writeable($this->fqfn)).',extensionMatches='.intval($this->extensionVersionMatches('cache')));
105 $this->status[$cacheName] = ($this->isCacheReadable() && (is_writeable($this->fqfn)) && ($this->extensionVersionMatches('cache')));
107 //* DEBUG: */ debugOutput('cacheName='.$cacheName.',status='.intval($this->status[$cacheName]));
110 return $this->status[$cacheName];
113 // Initializes the cache file
115 // This will destory an existing cache file!
116 if ($this->getStatusCode() == 'done') {
118 $this->resetCacheReadStatus();
121 if ($this->isCacheReadable()) changeMode($this->fqfn, 0666);
122 $this->pointer = fopen($this->fqfn, 'w') or debug_report_bug(__METHOD__, __LINE__, 'Cannot write to cache ' . $this->fqfn . ' !');
125 $this->writeLine('<?php');
127 // Cannot create file
128 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
133 * Writes a line to the pointer and adds a \n (new-line) to the end
137 function writeLine ($line) {
138 // Is the pointer a valid resource?
139 if (is_resource($this->pointer)) {
141 fwrite($this->pointer, $line . "\n");
143 // Something bad happened
144 debug_report_bug(__METHOD__, __LINE__, 'Pointer type is ' . gettype($this->pointer) . ', expected is resource.');
148 // Reset the read status
149 function resetCacheReadStatus () {
150 unset($this->readable[$this->name]);
151 unset($GLOBALS['file_readable'][$this->fqfn]);
152 unset($this->status[$this->name]);
155 function addRow ($data) {
156 // Is the pointe rvalid?
157 if (is_resource($this->pointer)) {
158 // Write every array element to cache file
159 foreach ($data as $k => $v) {
160 // Write global cache array for immediate access
161 if ((substr($k, 0, 4) == 'ext_') && (isset($data['ext_name'])) && (isset($data['ext_id']))) {
162 if ($k == 'ext_name') {
163 $GLOBALS['cache_array']['extension'][$k][$data['ext_id']] = $v;
165 $GLOBALS['cache_array']['extension'][$k][$data['ext_name']] = $v;
167 if (($k == 'ext_keep') && ($v == 'Y')) {
168 $GLOBALS['cache_array']['always_active'][$data['ext_name']] = $v;
170 } elseif ($this->name == 'config') {
172 $GLOBALS['cache_array']['config'][$data['config']][$k] = $v;
173 } elseif ($this->name == 'filter') {
175 $GLOBALS['cache_array']['filter']['chains'][$data['filter_name']][$data['filter_function']] = $data['filter_active'];
176 $GLOBALS['cache_array']['filter']['counter'][$data['filter_name']][$data['filter_function']] = $data['filter_counter'];
177 $GLOBALS['cache_array']['filter']['loaded'][$data['filter_name']][$data['filter_function']] = true;
178 } elseif ($this->name == 'modules') {
180 $GLOBALS['cache_array']['modules'][$k][$data['module']] = $v;
181 } elseif ($this->name == 'admin') {
183 if ($k == 'admin_id') {
184 $GLOBALS['cache_array']['admin'][$k][$data['login']] = $v;
186 $GLOBALS['cache_array']['admin'][$k][$data['admin_id']] = $v;
188 } elseif ($this->name == 'admin_acls') {
189 // Access control lines
190 $GLOBALS['cache_array']['admin_acls'][$k][$data['admin_id']][] = $v;
191 } elseif ($this->name == 'refdepths') {
193 $GLOBALS['cache_array']['refdepths'][$k][$data['id']] = $v;
194 } elseif ($this->name == 'refsystem') {
196 $GLOBALS['cache_array']['refsystem'][$k][$data['id']] = $v;
197 } elseif ($this->name == 'revision') {
199 $GLOBALS['cache_array']['revision'][$k][0] = $v;
200 } elseif ($this->name == 'themes') {
202 if ($k == 'theme_path') {
203 $GLOBALS['cache_array']['themes'][$k][$data['id']] = $v;
205 $GLOBALS['cache_array']['themes'][$k][$data['theme_path']] = $v;
207 } elseif ($this->name == 'imprint') {
209 $GLOBALS['cache_array']['imprint'][$k][$data['imprint_id']] = $v;
210 } elseif (is_array($v)) {
211 // Serialize and BASE64-encode the array
212 $v = base64_encode(serialize($v));
214 // Finialize the cache and close it
218 $this->removeCacheFile(true);
220 // Unsupported/unhandled cache detected
221 debug_report_bug(__METHOD__, __LINE__, 'Unsupported cache ' . $this->name . ' detected.');
224 // Write cache line to file
225 $this->writeLine($this->rewriteEntry($k, $v));
228 // Cannot create file
229 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
233 function finalize () {
234 // Quit function when no pointer is set
235 if (is_resource($this->pointer)) {
236 // Add default depency
237 $this->storeExtensionVersion('cache');
240 $this->writeLine('?>');
242 // Close file add destroy handler
243 fclose($this->pointer);
245 // Reset readable status
246 $this->resetCacheReadStatus();
249 if ($this->isCacheReadable()) changeMode($this->fqfn, 0666);
251 // Remove pointer and status
252 unset($this->status[$this->name]);
253 $this->pointer = false;
254 //* DEBUG: */ debugOutput(__METHOD__ . '(<font color="#0000aa">' . __LINE__.'</font>): '.$this->name.' - FINALIZED!');
258 function getArrayFromCache () {
259 // Is the cache already loaded?
260 if (isset($this->data[$this->name])) {
261 // Return it's content!
262 return $this->data[$this->name];
265 // Is the cache file there?
266 if ($this->isCacheReadable()) {
268 include($this->fqfn);
270 // Is there an array?
271 if (isset($this->data[$this->name])) {
272 // Cache version found?
273 if (isset($this->version[$this->name])) {
274 // Check it here if cache matches ext-cache version
275 if (!$this->extensionVersionMatches('cache')) {
277 logDebugMessage(__METHOD__, __LINE__, 'Invalid cache data ' . $this->name . ' detected.');
278 $this->removeCacheFile();
282 // Return cache if detected
283 if (isset($this->data[$this->name])) {
284 return $this->data[$this->name];
287 logDebugMessage(__METHOD__, __LINE__, 'Possible damaged cache ' . $this->name . ' detected.');
288 $this->removeCacheFile();
292 // Cache file not found or not readable
293 debug_report_bug(__METHOD__, __LINE__, $this->name);
294 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): ' . getMaskedMessage('CACHE_CANNOT_LOAD', $this->fqfn));
297 $this->removeCacheFile();
300 // Always return an empty array if we have trouble or no data
304 // Destroy an existing cache file
305 function removeCacheFile ($force = false) {
307 $this->resetCacheReadStatus();
310 //* DEBUG: */ logDebugMessage(__METHOD__, __LINE__, sprintf("%s should be removed.", $this->name));
312 // Is the cache file not yet rebuilt?
313 if ((!isset($this->rebuilt[$this->name])) && ($this->isCacheReadable())) {
314 // Only run in regular output mode
315 if ((!isHtmlOutputMode()) && ($force === false)) {
316 // Debug message if allowed
317 if (isDebugModeEnabled()) {
319 logDebugMessage(__METHOD__, __LINE__, 'Not removing cache ' . $this->name . ' in output_mode=' . getScriptOutputMode());
329 // Debug-mode enabled?
330 if (isDebugModeEnabled()) {
331 // Log removal of cache
332 logDebugMessage(__METHOD__, __LINE__, 'removing cache: ' . $this->name);
335 // Remove cache file from system
336 //* DEBUG: */ debug_report_bug(__METHOD__, __LINE__, 'About to remove ' . basename($this->fqfn) . '!');
337 removeFile($this->fqfn);
340 $this->resetCacheReadStatus();
342 // Is the file there?
343 if (!$this->isCacheReadable()) {
344 // The cache does no longer exist so kill the content
345 unset($this->data[$this->name]);
346 unset($this->version[$this->name]);
347 $this->rebuilt[$this->name] = true;
350 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): ' . getMaskedMessage('CACHE_CANNOT_UNLINK', $this->fqfn));
356 function removeEntry ($search, $data, $array) {
357 if ($this->status[$this->name] == 'done') {
358 // Load cache into dummy array
359 $dummy = $this->getArrayFromCache();
361 // Search for key in array
362 $key = array_search($data, $dummy[$search]);
364 // Key (hopefully) found?
365 foreach ($array as $a) {
366 // So we can remove all elements as requested
367 unset($dummy[$a][$key]);
370 // Flush array to cache file
374 $this->writeArray($dummy);
380 // Cannot write to cache!
381 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
385 function writeArray ($array) {
386 if (is_resource($this->pointer)) {
387 foreach ($array as $k => $v) {
389 // Multi line(s) found
391 foreach ($v as $k2 => $v2) {
392 // Put every array element in a row...
393 $LINE .= $this->rewriteEntry($k, $v2);
397 $LINE = $this->rewriteEntry($k, $v);
401 $this->writeLine($LINE);
404 // Cannot write array!
405 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
410 function replaceEntry ($search, $replace, $search_key, $array) {
411 if ($this->status[$this->name] == 'done') {
412 // Load cache into dummy array
413 $dummy = $this->getArrayFromCache();
415 // Check if $dummy is valid (prevents some errors)
416 if ((is_array($dummy)) && (isset($dummy[$search])) && (is_array($dummy[$search]))) {
417 // Search for key in array
418 $key_found = array_key_exists($search_key, $dummy[$search]);
419 if ($key_found == true) {
421 // Key (hopefully) found?
422 foreach ($dummy as $a => $v) {
423 // So we can update all entries
426 $dummy[$a][$search_key] = $replace;
430 // Flush array to cache file
434 $this->writeArray($dummy);
441 // Cannot write to cache!
442 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
446 // Writes the version of given extension to the cache file
447 function storeExtensionVersion ($ext_name) {
448 // Valid cache pointer?
449 if (is_resource($this->pointer)) {
450 // Write only if extension is installed
451 if (isExtensionInstalled($ext_name)) {
452 // Get extension version
453 $ext_ver = getExtensionVersion($ext_name);
455 // Add the extension version to object (DO NOT REMOVE IT! Endless loop...)
456 $this->version[$this->name][$ext_name] = $ext_ver;
458 // Write cache line to file
459 $this->writeLine($this->rewriteEntry($ext_name, $ext_ver, 'version', true));
461 //* DEBUG: */ debugOutput(__METHOD__ . '(<font color="#0000aa">' . __LINE__ . '</font>): '.$this->name.' - '.$ext_name.'='.$ext_ver);
463 // Cannot create file
464 addFatalMessage(__METHOD__, __LINE__, '(<font color="#0000aa">' . __LINE__ . '</font>): {--CACHE_PROBLEMS_DETECTED');
468 // Checks wether versions from cache and extension matches
469 function extensionVersionMatches ($ext_name) {
471 if (!isset($GLOBALS[__METHOD__][$ext_name])) {
472 // Does never match by default
473 $GLOBALS[__METHOD__][$ext_name] = false;
475 // Compare only if installed
476 if (isExtensionInstalled($ext_name)) {
477 // Get extension version
478 $ext_ver = getExtensionVersion($ext_name);
481 if (isset($this->version[$this->name][$ext_name])) {
483 $GLOBALS[__METHOD__][$ext_name] = ((isset($this->version[$this->name][$ext_name])) && ($this->version[$this->name][$ext_name] == $ext_ver));
484 } elseif ($this->isCacheReadable()) {
485 // No cache version found
486 logDebugMessage(__METHOD__, __LINE__, 'Cache ' . $this->name . ' has missing version entry for extension ' . $ext_name . '! Purging cache...');
488 // Remove the cache file
489 $this->removeCacheFile(true);
492 // Not installed, does always match
493 $GLOBALS[__METHOD__][$ext_name] = true;
496 // Cache entry found, log debug message
497 //* DEBUG: */ logDebugMessage(__METHOD__, __LINE__, 'ext_name=' . $ext_name . ', matches=' . intval($GLOBALS[__METHOD__][$ext_name]));
501 return $GLOBALS[__METHOD__][$ext_name];
504 // Rewrit the entry so it can be stored in cache file
505 // @TODO Add support for more types which break in last else-block
506 function rewriteEntry ($key, $value, $prefix = 'data', $single = false) {
507 // Default is not single entry
510 // Add only for single array entry?
511 if ($single === true) {
518 // String or non-string? ;-)
519 if (is_string($value)) {
521 $line = '$this->' . $prefix . "['" . $this->name . "']['" . $key . "']" . $extender . " = '" . escapeQuotes($value) . "';";
522 } elseif (is_null($value)) {
524 $line = '$this->' . $prefix . "['" . $this->name . "']['" . $key . "']" . $extender . ' = null;';
525 } elseif (is_bool($value)) {
527 if ($value === true) {
529 $line = '$this->' . $prefix . "['" . $this->name . "']['" . $key . "']" . $extender . ' = true;';
532 $line = '$this->' . $prefix . "['" . $this->name . "']['" . $key . "']" . $extender . ' = false;';
534 } elseif (isset($value[0])) {
535 // These lines needs fixing
536 debug_report_bug(__METHOD__, __LINE__, 'Invalid entry with [0] found. key=' . $key);
539 $line = '$this->' . $prefix . "['" . $this->name . "']['" . $key . "']" . $extender . ' = ' . $value . ';';
546 // Getter for cache status code
547 function getStatusCode () {
548 return $this->statusCode;
551 // Setter for cache status code
552 function setStatusCode ($status) {
553 $this->statusCode = $status;
556 // Checks wether the current cache file is readable
557 function isCacheReadable () {
558 // Array entry found?
559 if (!isset($this->readable[$this->name])) {
560 // Not found, so create it
561 $this->readable[$this->name] = isFileReadable($this->fqfn);
565 return $this->readable[$this->name];
568 // Cloning not allowed
569 function __clone () {
570 // Please do not clone this class
571 debug_report_bug(__METHOD__, __LINE__, 'Cloning of this class is not allowed.');