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