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 to create an instance of this class.
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
198: * with 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: *
258: * Used to generate the id for caching.
259: *
260: * @param string $name
261: * Name of option
262: * @param string $option
263: * Value of option (any variable)
264: */
265: public function addOption($name, $option) {
266: $this->_aIDOptions[$name] = $option;
267: }
268:
269: /**
270: * Returns information cache hit/miss and execution time if caching
271: * is enabled.
272: *
273: * @return string
274: * Information about cache if caching is enabled, otherwise nothing.
275: */
276: public function getInfo() {
277: if (!$this->_bEnableCaching) {
278: return;
279: }
280:
281: return $this->_sDebugMsg;
282: }
283:
284: /**
285: * Starts the cache process.
286: *
287: * @return bool|string
288: */
289: protected function _start() {
290: $id = $this->_sID;
291: $group = $this->_sGroup;
292:
293: // this is already cached return it from the cache so that the
294: // user can use the cache content and stop script execution
295: if ($content = $this->_fileCache->get($id, $group)) {
296: return $content;
297: }
298:
299: // WARNING: we need the output buffer - possible clashes
300: ob_start();
301: ob_implicit_flush(false);
302:
303: return '';
304: }
305:
306: /**
307: * Handles PEAR caching.
308: *
309: * The script will be terminated by calling die(), if any cached
310: * content is found.
311: *
312: * @param int $iPageStartTime [optional]
313: * Optional start time, e.g. start time of main script
314: */
315: public function start($iPageStartTime = NULL) {
316: if (!$this->_bEnableCaching) {
317: return;
318: }
319:
320: $this->_iStartTime = $this->_getMicroTime();
321:
322: // set cache object and unique id
323: $this->_initFileCache();
324:
325: // check if it's cached and start the output buffering if necessary
326: if ($content = $this->_start()) {
327: // raise beforeoutput event
328: $this->_raiseEvent('beforeoutput');
329:
330: $iEndTime = $this->_getMicroTime();
331: if ($this->_bHtmlComment) {
332: $time = sprintf("%2.4f", $iEndTime - $this->_iStartTime);
333: $exp = ($this->_iLifetime == 0? 'infinite' : date('Y-m-d H:i:s', time() + $this->_iLifetime));
334: $content .= sprintf($this->_sHtmlCommentTpl, 'HIT', $time . ' sec.', $exp);
335: if ($iPageStartTime != NULL && is_numeric($iPageStartTime)) {
336: $content .= '<!-- [' . sprintf("%2.4f", $iEndTime - $iPageStartTime) . '] -->';
337: }
338: }
339:
340: if ($this->_bDebug) {
341: $info = sprintf("HIT: %2.4f sec.", $iEndTime - $this->_iStartTime);
342: $info = sprintf($this->_sDebugTpl, $info);
343: $content = str_ireplace('</body>', $info . "\n</body>", $content);
344: }
345:
346: echo $content;
347:
348: // raise afteroutput event
349: $this->_raiseEvent('afteroutput');
350:
351: die();
352: }
353: }
354:
355: /**
356: * Handles ending of PEAR caching.
357: */
358: public function end() {
359: if (!$this->_bEnableCaching) {
360: return;
361: }
362:
363: $content = ob_get_contents();
364: ob_end_clean();
365:
366: $this->_fileCache->save($content, $this->_sID, $this->_sGroup);
367:
368: echo $content;
369:
370: if ($this->_bDebug) {
371: $this->_sDebugMsg .= "\n" . sprintf("MISS: %2.4f sec.\n", $this->_getMicroTime() - $this->_iStartTime);
372: $this->_sDebugMsg = sprintf($this->_sDebugTpl, $this->_sDebugMsg);
373: }
374: }
375:
376: /**
377: * Removes any cached content if exists.
378: *
379: * This is nesessary to delete cached articles, if they are changed on
380: * backend.
381: */
382: public function removeFromCache() {
383: // set cache object and unique id
384: $this->_initFileCache();
385: $this->_fileCache->remove($this->_sID, $this->_sGroup);
386: }
387:
388: /**
389: * Creates one-time a instance of PEAR cache output object and also
390: * the unique id, if propery $this->_oPearCache is not set.
391: */
392: protected function _initFileCache() {
393: if (is_object($this->_fileCache)) {
394: return;
395: }
396:
397: // create an output cache object mode - file storage
398: $this->_fileCache = new cFileCache($this->_aCacheOptions);
399:
400: // generate an ID from whatever might influence the script behaviour
401: $this->_sID = $this->_fileCache->generateID($this->_aIDOptions);
402: }
403:
404: /**
405: * Raises any defined event code by using eval().
406: *
407: * @param string $name
408: * Name of event to raise
409: */
410: protected function _raiseEvent($name) {
411: // skip if event does not exist
412: if (!isset($this->_aEventCode[$name]) && !is_array($this->_aEventCode[$name])) {
413: return;
414: }
415:
416: // loop array and execute each defined php-code
417: foreach ($this->_aEventCode[$name] as $code) {
418: eval($code);
419: }
420: }
421:
422: /**
423: * Returns microtime (UNIX timestamp), used to calculate time of execution.
424: *
425: * @return float
426: * Timestamp
427: */
428: protected function _getMicroTime() {
429: $mtime = explode(' ', microtime());
430: $mtime = $mtime[1] + $mtime[0];
431:
432: return $mtime;
433: }
434: }
435:
436: /**
437: * This class contains functions for the output cache handler in CONTENIDO.
438: *
439: * @package Core
440: * @subpackage Cache
441: */
442: class cOutputCacheHandler extends cOutputCache {
443:
444: /**
445: * Constructor to create an instance of this class.
446: *
447: * Does some checks and sets the configuration of cache object.
448: *
449: * @param array $aConf
450: * Configuration of caching as follows:
451: * - $a['excludecontenido'] bool
452: * don't cache output, if we have a CONTENIDO variable,
453: * e.g. on calling frontend preview from backend
454: * - $a['enable'] bool
455: * activate caching of frontend output
456: * - $a['debug'] bool
457: * compose debuginfo (hit/miss and execution time of caching)
458: * - $a['infotemplate'] string
459: * debug information template
460: * - $a['htmlcomment'] bool
461: * add a html comment including several debug messages to output
462: * - $a['lifetime'] int
463: * lifetime in seconds to cache output
464: * - $a['cachedir'] string
465: * directory where cached content is to store.
466: * - $a['cachegroup'] string
467: * cache group, will be a subdirectory inside cachedir
468: * - $a['cacheprefix'] string
469: * add prefix to stored filenames
470: * - $a['idoptions'] array
471: * several variables to create a unique id,
472: * if the output depends on them. e.g.
473: * array(
474: * 'uri' => $_SERVER['REQUEST_URI'],
475: * 'post' => $_POST, 'get' => $_GET
476: * )
477: * @param cDb $db
478: * CONTENIDO database object
479: * @param int $iCreateCode [optional]
480: * Flag of createcode state from table con_cat_art
481: */
482: public function __construct($aConf, $db, $iCreateCode = NULL) {
483: // check if caching is allowed on CONTENIDO variable
484: if ($aConf['excludecontenido'] == true) {
485: if (isset($GLOBALS['contenido'])) {
486: // CONTENIDO variable exists, set state and get out here
487: $this->_bEnableCaching = false;
488:
489: return;
490: }
491: }
492:
493: // set enable state of caching
494: if (is_bool($aConf['enable'])) {
495: $this->_bEnableCaching = $aConf['enable'];
496: }
497: if ($this->_bEnableCaching == false) {
498: return;
499: }
500:
501: // check if current article shouldn't be cached (by stese)
502: $sExcludeIdarts = getEffectiveSetting('cache', 'excludeidarts', false);
503: if ($sExcludeIdarts && strlen($sExcludeIdarts) > 0) {
504: $sExcludeIdarts = preg_replace("/[^0-9,]/", '', $sExcludeIdarts);
505: $aExcludeIdart = explode(',', $sExcludeIdarts);
506: if (in_array($GLOBALS['idart'], $aExcludeIdart)) {
507: $this->_bEnableCaching = false;
508:
509: return;
510: }
511: }
512:
513: $this->_oDB = $db;
514:
515: // set caching configuration
516: parent::__construct($aConf['cachedir'], $aConf['cachegroup']);
517: $this->debug($aConf['debug']);
518: $this->htmlComment($aConf['htmlcomment']);
519: $this->lifetime($aConf['lifetime']);
520: $this->infoTemplate($aConf['infotemplate']);
521: foreach ($aConf['idoptions'] as $name => $var) {
522: $this->addOption($name, $var);
523: }
524:
525: if (is_array($aConf['raiseonevent'])) {
526: $this->_aEventCode = $aConf['raiseonevent'];
527: }
528:
529: // check, if code is to create
530: $this->_bEnableCaching = !$this->_isCode2Create($iCreateCode);
531: if ($this->_bEnableCaching == false) {
532: $this->removeFromCache();
533: }
534: }
535:
536: /**
537: * Checks, if the create code flag is set.
538: * Output will be loaded from cache, if no code is to create.
539: * It also checks the state of global variable $force.
540: *
541: * @param mixed $iCreateCode
542: * State of create code (0 or 1).
543: * The state will be loaded from database if value is NULL
544: * @return bool
545: * True if code is to create, otherwise false.
546: */
547: protected function _isCode2Create($iCreateCode) {
548: if ($this->_bEnableCaching == false) {
549: return;
550: }
551:
552: // check content of global variable $force, get out if is's set to '1'
553: if (isset($GLOBALS['force']) && is_numeric($GLOBALS['force']) && $GLOBALS['force'] == 1) {
554: return true;
555: }
556:
557: if (is_null($iCreateCode)) {
558: // check if code is expired
559:
560: $oApiCatArtColl = new cApiCategoryArticleCollection('idart="' . $GLOBALS['idart'] . '" AND idcat="' . $GLOBALS['idcat'] . '"');
561: if ($oApiCatArt = $oApiCatArtColl->next()) {
562: $iCreateCode = $oApiCatArt->get('createcode');
563: unset($oApiCatArt);
564: }
565: unset($oApiCatArtColl);
566: }
567:
568: return ($iCreateCode == 1) ? true : false;
569: }
570:
571: }
572: