1: <?php
2:
3: /**
4: * This file contains the output cache classes.
5: *
6: * @package Core
7: * @subpackage Cache
8: * @author Murat Purc <murat@purc.de>
9: * @copyright four for business AG <www.4fb.de>
10: * @license http://www.contenido.org/license/LIZENZ.txt
11: * @link http://www.4fb.de
12: * @link http://www.contenido.org
13: */
14:
15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
16:
17: /**
18: * This class contains functions for the CONTENIDO output cache.
19: *
20: * @package Core
21: * @subpackage Cache
22: */
23: class cOutputCache {
24:
25: /**
26: * File cache object.
27: *
28: * @var cFileCache
29: */
30: protected $_fileCache;
31:
32: /**
33: * Flag to activate caching.
34: *
35: * @var bool
36: */
37: protected $_bEnableCaching = false;
38:
39: /**
40: * Flag for output of debug informations.
41: *
42: * @var bool
43: */
44: protected $_bDebug = false;
45:
46: /**
47: * Flag to print html comment including some debug informations.
48: *
49: * @var bool
50: */
51: protected $_bHtmlComment = false;
52:
53: /**
54: * Start time of caching.
55: *
56: * @var int
57: */
58: protected $_iStartTime;
59:
60: /**
61: * Option array for generating cache identifier
62: * (e.g. $_GET,$_POST, $_COOKIE, ...).
63: *
64: * @var array
65: */
66: protected $_aIDOptions;
67:
68: /**
69: * Option array for pear caching.
70: *
71: * @var array
72: */
73: protected $_aCacheOptions;
74:
75: /**
76: * Handler array to store code, beeing executed on some hooks.
77: * We have actually two hooks:
78: * - 'beforeoutput': code to execute before doing the output
79: * - 'afteroutput' code to execute after output
80: *
81: * @var array
82: */
83: protected $_aEventCode;
84:
85: /**
86: * Unique identifier for caching.
87: *
88: * @var string
89: */
90: protected $_sID;
91:
92: /**
93: * Directory to store cached output.
94: *
95: * @var string
96: */
97: protected $_sDir = 'cache/';
98:
99: /**
100: * Subdirectory to store cached output.
101: *
102: * @var string
103: */
104: protected $_sGroup = 'default';
105:
106: /**
107: * Substring to add as prefix to cache-filename.
108: *
109: * @var string
110: */
111: protected $_sPrefix = 'cache_output_';
112:
113: /**
114: * Default lifetime of cached files.
115: *
116: * @var int
117: */
118: protected $_iLifetime = 3600;
119:
120: /**
121: * Used to store debug message.
122: *
123: * @var string
124: */
125: protected $_sDebugMsg = '';
126:
127: /**
128: * HTML code template used for debug message.
129: *
130: * @var string
131: */
132: protected $_sDebugTpl = '<div>%s</div>';
133:
134: /**
135: * HTML comment template used for generating some debug infos.
136: *
137: * @var string
138: */
139: protected $_sHtmlCommentTpl = '
140: <!--
141: CACHESTATE: %s
142: TIME: %s
143: VALID UNTIL: %s
144: -->
145: ';
146:
147: /**
148: * Constructor.
149: *
150: * @param string $cachedir [optional]
151: * Directory to cache files
152: * @param string $cachegroup [optional]
153: * Subdirectory to cache files
154: * @param string $cacheprefix [optional]
155: * Prefixname to add to cached files
156: */
157: public function __construct($cachedir = NULL, $cachegroup = NULL, $cacheprefix = NULL) {
158: // wherever you want the cache files
159: if (!is_null($cachedir)) {
160: $this->_sDir = $cachedir;
161: }
162:
163: // subdirectory where you want the cache files
164: if (!is_null($cachegroup)) {
165: $this->_sGroup = $cachegroup;
166: }
167:
168: // optional a filename prefix
169: if (!is_null($cacheprefix)) {
170: $this->_sPrefix = $cacheprefix;
171: }
172:
173: // config options are passed to the cache as an array
174: $this->_aCacheOptions = array(
175: 'cacheDir' => $this->_sDir,
176: 'fileNamePrefix' => $this->_sPrefix
177: );
178: }
179:
180: /**
181: * Get/Set the flag to enable caching.
182: *
183: * @param bool $enable [optional]
184: * True to enable caching or false
185: * @return mixed
186: * Enable flag or void
187: */
188: public function enable($enable = NULL) {
189: if (!is_null($enable) && is_bool($enable)) {
190: $this->_bEnableCaching = $enable;
191: } else {
192: return $this->_bEnableCaching;
193: }
194: }
195:
196: /**
197: * Get/Set the flag to debug cache object (prints out miss/hit state with
198: * execution time).
199: *
200: * @param bool $debug
201: * True to activate debugging or false.
202: * @return mixed
203: * Debug flag or void
204: */
205: public function debug($debug) {
206: if (!is_null($debug) && is_bool($debug)) {
207: $this->_bDebug = $debug;
208: } else {
209: return $this->_bDebug;
210: }
211: }
212:
213: /**
214: * Get/Set flag to print out cache info as html comment.
215: *
216: * @param bool $htmlcomment
217: * True debugging or false.
218: * @return void|string
219: * Htmlcomment flag or void
220: */
221: public function htmlComment($htmlcomment) {
222: if (!is_null($htmlcomment) && is_bool($htmlcomment)) {
223: $this->_bHtmlComment = $htmlcomment;
224: } else {
225: return $this->_bHtmlComment;
226: }
227: }
228:
229: /**
230: * Get/Set caching lifetime in seconds.
231: *
232: * @param int $seconds [optional]
233: * New Lifetime in seconds
234: * @return mixed
235: * Actual lifetime or void
236: */
237: public function lifetime($seconds = NULL) {
238: if ($seconds != NULL && is_numeric($seconds) && $seconds > 0) {
239: $this->_iLifetime = $seconds;
240: } else {
241: return $this->_iLifetime;
242: }
243: }
244:
245: /**
246: * Get/Set template to use on printing the chache info.
247: *
248: * @param string $template
249: * Template string including the '%s' format definition.
250: */
251: public function infoTemplate($template) {
252: $this->_sDebugTpl = $template;
253: }
254:
255: /**
256: * Add option for caching (e.g. $_GET,$_POST, $_COOKIE, ...).
257: * Used to generate the id for caching.
258: *
259: * @param string $name
260: * Name of option
261: * @param string $option
262: * Value of option (any variable)
263: */
264: public function addOption($name, $option) {
265: $this->_aIDOptions[$name] = $option;
266: }
267:
268: /**
269: * Returns information cache hit/miss and execution time if caching is
270: * enabled.
271: *
272: * @return string
273: * Information about cache if caching is enabled, otherwise nothing.
274: */
275: public function getInfo() {
276: if (!$this->_bEnableCaching) {
277: return;
278: }
279:
280: return $this->_sDebugMsg;
281: }
282:
283: /**
284: * Starts the cache process.
285: *
286: * @return bool|string
287: */
288: protected function _start() {
289: $id = $this->_sID;
290: $group = $this->_sGroup;
291:
292: // this is already cached return it from the cache so that the user
293: // can use the cache content and stop script execution
294: if ($content = $this->_fileCache->get($id, $group)) {
295: return $content;
296: }
297:
298: // WARNING: we need the output buffer - possible clashes
299: ob_start();
300: ob_implicit_flush(false);
301:
302: return '';
303: }
304:
305: /**
306: * Handles PEAR caching.
307: * The script will be terminated by calling die(), if any cached
308: * content is found.
309: *
310: * @param int $iPageStartTime [optional]
311: * Optional start time, e. g. start time of main script
312: */
313: public function start($iPageStartTime = NULL) {
314: if (!$this->_bEnableCaching) {
315: return;
316: }
317:
318: $this->_iStartTime = $this->_getMicroTime();
319:
320: // set cache object and unique id
321: $this->_initFileCache();
322:
323: // check if it's cached and start the output buffering if necessary
324: if ($content = $this->_start()) {
325: // raise beforeoutput event
326: $this->_raiseEvent('beforeoutput');
327:
328: $iEndTime = $this->_getMicroTime();
329: if ($this->_bHtmlComment) {
330: $time = sprintf("%2.4f", $iEndTime - $this->_iStartTime);
331: $exp = ($this->_iLifetime == 0? 'infinite' : date('Y-m-d H:i:s', time() + $this->_iLifetime));
332: $content .= sprintf($this->_sHtmlCommentTpl, 'HIT', $time . ' sec.', $exp);
333: if ($iPageStartTime != NULL && is_numeric($iPageStartTime)) {
334: $content .= '<!-- [' . sprintf("%2.4f", $iEndTime - $iPageStartTime) . '] -->';
335: }
336: }
337:
338: if ($this->_bDebug) {
339: $info = sprintf("HIT: %2.4f sec.", $iEndTime - $this->_iStartTime);
340: $info = sprintf($this->_sDebugTpl, $info);
341: $content = str_ireplace('</body>', $info . "\n</body>", $content);
342: }
343:
344: echo $content;
345:
346: // raise afteroutput event
347: $this->_raiseEvent('afteroutput');
348:
349: die();
350: }
351: }
352:
353: /**
354: * Handles ending of PEAR caching.
355: */
356: public function end() {
357: if (!$this->_bEnableCaching) {
358: return;
359: }
360:
361: $content = ob_get_contents();
362: ob_end_clean();
363:
364: $this->_fileCache->save($content, $this->_sID, $this->_sGroup);
365:
366: echo $content;
367:
368: if ($this->_bDebug) {
369: $this->_sDebugMsg .= "\n" . sprintf("MISS: %2.4f sec.\n", $this->_getMicroTime() - $this->_iStartTime);
370: $this->_sDebugMsg = sprintf($this->_sDebugTpl, $this->_sDebugMsg);
371: }
372: }
373:
374: /**
375: * Removes any cached content if exists.
376: * This is nesessary to delete cached articles, if they are changed on
377: * backend.
378: */
379: public function removeFromCache() {
380: // set cache object and unique id
381: $this->_initFileCache();
382: $this->_fileCache->remove($this->_sID, $this->_sGroup);
383: }
384:
385: /**
386: * Creates one-time a instance of PEAR cache output object and also the
387: * unique id,
388: * if propery $this->_oPearCache is not set.
389: */
390: protected function _initFileCache() {
391: if (is_object($this->_fileCache)) {
392: return;
393: }
394:
395: // create a output cache object mode - file storage
396: $this->_fileCache = new cFileCache($this->_aCacheOptions);
397:
398: // generate an ID from whatever might influence the script behaviour
399: $this->_sID = $this->_fileCache->generateID($this->_aIDOptions);
400: }
401:
402: /**
403: * Raises any defined event code by using eval().
404: *
405: * @param string $name
406: * Name of event to raise
407: */
408: protected function _raiseEvent($name) {
409: // check if event exists, get out if not
410: if (!isset($this->_aEventCode[$name]) && !is_array($this->_aEventCode[$name])) {
411: return;
412: }
413:
414: // loop array and execute each defined php-code
415: foreach ($this->_aEventCode[$name] as $code) {
416: eval($code);
417: }
418: }
419:
420: /**
421: * Returns microtime (UNIX timestamp), used to calculate time of execution.
422: *
423: * @return float
424: * Timestamp
425: */
426: protected function _getMicroTime() {
427: $mtime = explode(' ', microtime());
428: $mtime = $mtime[1] + $mtime[0];
429:
430: return $mtime;
431: }
432: }
433:
434: /**
435: * This class contains functions for the output cache handler in CONTENIDO.
436: *
437: * @package Core
438: * @subpackage Cache
439: */
440: class cOutputCacheHandler extends cOutputCache {
441:
442: /**
443: * Constructor of cOutputCacheHandler.
444: * Does some checks and sets the configuration of cache object.
445: *
446: * @param array $aConf
447: * Configuration of caching as follows:
448: * - $a['excludecontenido'] bool
449: * don't cache output, if we have a CONTENIDO variable,
450: * e. g. on calling frontend preview from backend
451: * - $a['enable'] bool
452: * activate caching of frontend output
453: * - $a['debug'] bool
454: * compose debuginfo (hit/miss and execution time of caching)
455: * - $a['infotemplate'] string
456: * debug information template
457: * - $a['htmlcomment'] bool
458: * add a html comment including several debug messages to output
459: * - $a['lifetime'] int
460: * lifetime in seconds to cache output
461: * - $a['cachedir'] string
462: * directory where cached content is to store.
463: * - $a['cachegroup'] string
464: * cache group, will be a subdirectory inside cachedir
465: * - $a['cacheprefix'] string
466: * add prefix to stored filenames
467: * - $a['idoptions'] array
468: * several variables to create a unique id,
469: * if the output depends on them. e. g.
470: * array(
471: * 'uri' => $_SERVER['REQUEST_URI'],
472: * 'post' => $_POST, 'get' => $_GET
473: * )
474: * @param cDb $db
475: * CONTENIDO database object
476: * @param int $iCreateCode [optional]
477: * Flag of createcode state from table con_cat_art
478: */
479: public function __construct($aConf, $db, $iCreateCode = NULL) {
480: // check if caching is allowed on CONTENIDO variable
481: if ($aConf['excludecontenido'] == true) {
482: if (isset($GLOBALS['contenido'])) {
483: // CONTENIDO variable exists, set state and get out here
484: $this->_bEnableCaching = false;
485:
486: return;
487: }
488: }
489:
490: // set enable state of caching
491: if (is_bool($aConf['enable'])) {
492: $this->_bEnableCaching = $aConf['enable'];
493: }
494: if ($this->_bEnableCaching == false) {
495: return;
496: }
497:
498: // check if current article shouldn't be cached (by stese)
499: $sExcludeIdarts = getEffectiveSetting('cache', 'excludeidarts', false);
500: if ($sExcludeIdarts && strlen($sExcludeIdarts) > 0) {
501: $sExcludeIdarts = preg_replace("/[^0-9,]/", '', $sExcludeIdarts);
502: $aExcludeIdart = explode(',', $sExcludeIdarts);
503: if (in_array($GLOBALS['idart'], $aExcludeIdart)) {
504: $this->_bEnableCaching = false;
505:
506: return;
507: }
508: }
509:
510: $this->_oDB = $db;
511:
512: // set caching configuration
513: parent::__construct($aConf['cachedir'], $aConf['cachegroup']);
514: $this->debug($aConf['debug']);
515: $this->htmlComment($aConf['htmlcomment']);
516: $this->lifetime($aConf['lifetime']);
517: $this->infoTemplate($aConf['infotemplate']);
518: foreach ($aConf['idoptions'] as $name => $var) {
519: $this->addOption($name, $var);
520: }
521:
522: if (is_array($aConf['raiseonevent'])) {
523: $this->_aEventCode = $aConf['raiseonevent'];
524: }
525:
526: // check, if code is to create
527: $this->_bEnableCaching = !$this->_isCode2Create($iCreateCode);
528: if ($this->_bEnableCaching == false) {
529: $this->removeFromCache();
530: }
531: }
532:
533: /**
534: * Checks, if the create code flag is set.
535: * Output will be loaded from cache, if no code is to create.
536: * It also checks the state of global variable $force.
537: *
538: * @param mixed $iCreateCode
539: * State of create code (0 or 1).
540: * The state will be loaded from database if value is NULL
541: * @return bool
542: * True if code is to create, otherwise false.
543: */
544: protected function _isCode2Create($iCreateCode) {
545: if ($this->_bEnableCaching == false) {
546: return;
547: }
548:
549: // check content of global variable $force, get out if is's set to '1'
550: if (isset($GLOBALS['force']) && is_numeric($GLOBALS['force']) && $GLOBALS['force'] == 1) {
551: return true;
552: }
553:
554: if (is_null($iCreateCode)) {
555: // check if code is expired
556:
557: $oApiCatArtColl = new cApiCategoryArticleCollection('idart="' . $GLOBALS['idart'] . '" AND idcat="' . $GLOBALS['idcat'] . '"');
558: if ($oApiCatArt = $oApiCatArtColl->next()) {
559: $iCreateCode = $oApiCatArt->get('createcode');
560: unset($oApiCatArt);
561: }
562: unset($oApiCatArtColl);
563: }
564:
565: return ($iCreateCode == 1) ? true : false;
566: }
567:
568: }
569: