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