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