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