0byt3m1n1
Path:
/
data
/
applications
/
aps.bak
/
b2evolution
/
3.3.1-5
/
standard
/
htdocs
/
inc
/
_core
/
model
/
[
Home
]
File: _pagecache.class.php
<?php /** * This file implements the PageCache class, which caches HTML pages genereated by the app. * * This file is part of the evoCore framework - {@link http://evocore.net/} * See also {@link http://sourceforge.net/projects/evocms/}. * * @copyright (c)2003-2009 by Francois PLANQUE - {@link http://fplanque.net/} * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. * * {@internal License choice * - If you have received this file as part of a package, please find the license.txt file in * the same folder or the closest folder above for complete license terms. * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) * then you must choose one of the following licenses before using the file: * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php * }} * * {@internal Open Source relicensing agreement: * }} * * @package evocore * * @version $Id: _pagecache.class.php,v 1.7 2009/03/08 23:57:40 fplanque Exp $ }}} * */ if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); /** * Page Cache. * * @package evocore */ class PageCache { /** * How old can a cached object get before we consider it outdated */ var $max_age_seconds = 300; // 5 minutes for now /** * After how many bytes should we output sth live while collecting cache content: */ var $output_chunk_size = 2000; /** * By default we consider caching not to be enabled */ var $is_enabled = false; /** * */ var $ads_collcache_path; /** * Filename of cache for current page */ var $cache_filepath; /** * Progressively caching the content of the current page: */ var $cached_page_content = ''; /** * Are we currently recording cache contents for this page? */ var $is_collecting = false; /** * Constructor * * @param Blog to use, can be NULL */ function PageCache( $Blog = NULL ) { global $Debuglog; global $Settings; global $cache_path; if( is_null($Blog) ) { // Cache for "other" "genereic" "special" pages: $this->ads_collcache_path = $cache_path.'general/'; if( ! $Settings->get('general_cache_enabled') ) { // We do NOT want caching for this collection $Debuglog->add( 'General cache not enabled.', 'cache' ); } else { $this->is_enabled = true; } } else { // Cache for a specific Blog/Collection: // We need to set this even if cache is not enabled (yet) bc it's used for creating: $this->ads_collcache_path = $cache_path.'c'.$Blog->ID.'/'; if( ! $Blog->get_setting('cache_enabled') ) { // We do NOT want caching for this collection $Debuglog->add( 'Cache not enabled for this blog.', 'cache' ); } else { $this->is_enabled = true; } } } /** * Get path to file for current URL * * @todo fp> We may need to add some keys like the locale or the charset, I'm not sure. */ function get_af_filecache_path() { global $Debuglog; global $ReqHost, $ReqURI; // We want the cache for the current URL if( empty( $this->cache_filepath ) ) { $ReqAbsUrl = $ReqHost.$ReqURI; // echo $ReqAbsUrl; $Debuglog->add( 'URL being cached: '.$ReqAbsUrl, 'cache' ); $this->cache_filepath = $this->gen_filecache_path( $ReqAbsUrl ); $Debuglog->add( 'Cache file: '.$this->cache_filepath, 'cache' ); } return $this->cache_filepath; } /** * Generate path for caching $url. * @param string URL * @return string */ function gen_filecache_path( $url ) { $url_hash = md5($url); // fp> is this teh fastest way to hash this into something not too obvious to guess? // echo $url_hash; return $this->ads_collcache_path.$url_hash.'.page'; } /** * Invalidate a particular page from the cache * * @param URL of the page to be invalidated */ function invalidate( $url ) { global $Debuglog; // echo 'Invalidating:'.$url; $Debuglog->add( 'Invalidating:'.$url, 'cache' ); // What would be the cache file for the current URL? $af_cache_file = $this->gen_filecache_path( $url ); @unlink( $af_cache_file ); } /** * @return boolean true if cache has been successfully created */ function cache_create() { // Create by using the filemanager's default chmod. TODO> we may not want to make these publicly readable if( ! mkdir_r( $this->ads_collcache_path, NULL ) ) { return false; } // Clear contents of folder, if any: cleardir_r( $this->ads_collcache_path ); return true; } /** * Delete all cache files */ function cache_delete() { rmdir_r( $this->ads_collcache_path ); } /** * Check if cache contents are available, otherwise start collecting output to be cached * * @return true if we found and have echoed content from the cache */ function check() { global $Debuglog; global $disp; global $Messages; if( ! $this->is_enabled ) { // We do NOT want caching for this page $Debuglog->add( 'Cache not enabled. No lookup nor caching performed.', 'cache' ); return false; } if( $disp == '404' ) { // We do NOT want caching for 404 pages (illimited possibilities!) $Debuglog->add( 'Never cache 404s!', 'cache' ); return false; } if( is_logged_in() ) { // We do NOT want caching when a user is logged in (private data) $Debuglog->add( 'Never cache pages for/from logged in members!', 'cache' ); return false; } if( $Messages->count('all') ) { // There are some messages do be displayed. That means the user has done some action. // We do want to display those messages. // There may also be more... like a "comment pending review" etc... // DO NOT CACHE and do not present a cached page. $Debuglog->add( 'Not caching because we have messages!', 'cache' ); return false; } // TODO: fp> If the user has submitted a comment, we might actually want to invalidate the cache... if( $this->retrieve() ) { // We could retrieve: return true; } $this->is_collecting = true; $Debuglog->add( 'Collecting started', 'cache' ); ob_start( array( & $this, 'output_handler'), $this->output_chunk_size ); return false; } /** * Retrieve and output cache for current URL. * * @return boolean true if we could retrieve */ function retrieve() { global $Debuglog; global $ReqHost, $ReqURI; global $servertimenow; // What would be the cache file for the current URL? $af_cache_file = $this->get_af_filecache_path(); /* // fstat() is interesting because it gives the last access time... use that for purging... if( $fh = @fopen( $af_cache_file, 'r', false ) ) { $fstat = fstat( $fh ); pre_dump( $fstat ); fclose( $fh ); } */ $lines = @file( $af_cache_file, false ); // fp> note we are using empty() so that we detect both the case where there is no file and the case wher ethe file // might have ended up empty because PHP crashed while writing to it or sth like that... if( ! empty($lines) ) { // We have data in the cache! $Debuglog->add( 'Retrieving from cache!', 'cache' ); // Check that the format of the file if OK. $sep = trim($lines[2]); unset($lines[2]); if( $sep != '' ) { $Debuglog->add( 'Cached file format not recognized, aborting retrieve.', 'cache' ); return false; } // Retrieved cached URL: $retrieved_url = trim($lines[0]); unset($lines[0]); if( $retrieved_url != $ReqHost.$ReqURI ) { $Debuglog->add( 'Cached file URL ['.$retrieved_url.'] does not match current URL, aborting retrieve.', 'cache' ); return false; } // timestamp of cache generation: $retrieved_ts = trim($lines[1]); unset($lines[1]); $cache_age = $servertimenow-$retrieved_ts; $Debuglog->add( 'Cache age: '.floor($cache_age/60).' min '.($cache_age % 60).' sec', 'cache' ); if( $cache_age > $this->max_age_seconds ) { // Cache has expired return false; } // Go through headers $i = 2; while( $headerline = trim($lines[++$i]) ) { header( $headerline ); unset($lines[$i]); } unset($lines[$i]); // SEND CONTENT! echo implode('',$lines); return true; } } /** * This is called every x bytes to provide real time output */ function output_handler( $buffer ) { $this->cached_page_content .= $buffer; return $buffer; } /** * We are going to output personal data and we want to abort collecting the data for the cache. */ function abort_collect() { global $Debuglog; if( ! $this->is_collecting ) { // We are not collecting anyway return; } $Debuglog->add( 'Aborting cache data collection...', 'cache' ); ob_end_flush(); // We are no longer collecting... $this->is_collecting = false; } /** * End collecting output to be cached */ function end_collect() { global $Debuglog; if( ! $this->is_collecting ) { // We are not collecting return; } ob_end_flush(); // echo ' *** cache end *** '; // echo $this->cached_page_content; // What would be the cache file for the current URL? $af_cache_file = $this->get_af_filecache_path(); // fp> 'x' mode should either give an exclusive write lock or fail // fp> TODO: this here should be ok, but it would be even better with locking the file when we start collecting cache if( ! $fh = @fopen( $af_cache_file.'.tmp', 'x', false ) ) { $Debuglog->add( 'Could not open cache file!', 'cache' ); } else { // Put the URL of the page we are caching into the cache. You can never be to paranoid! // People can change their domain names, folder structures, etc... AND you cannot trust the hash to give a // different file name in 100.00% of the cases! Serving a page for a different URL would be REEEEEALLLY BAAAAAAD! global $ReqHost, $ReqURI; $file_head = $ReqHost.$ReqURI."\n"; // Put the time of the page generation into the file (btw this is the time of when we started this script) global $servertimenow; $file_head .= $servertimenow."\n"; $file_head .= "\n"; // We need to write the content type! global $content_type_header; if( !empty($content_type_header) ) { $file_head .= $content_type_header."\n"; } $file_head .= "\n"; fwrite( $fh, $file_head.$this->cached_page_content ); fclose( $fh ); // Now atomically replace old cache with new cache (at least on Linux) if( ! @rename( $af_cache_file.'.tmp', $af_cache_file ) ) { // Rename failed, we are probably on windows PHP <= 5.2.5... http://bugs.php.net/bug.php?id=44805 // we have to split this: $Debuglog->add( 'Renaming of cache file failed. (Windows?)', 'cache' ); // Kill cache: unlink( $af_cache_file ); // Now, some other process might start to try caching (and will likely give up since the .tmp file already exists) if( ! @rename( $af_cache_file.'.tmp', $af_cache_file ) ) { // Hide errors bc another PHP process could have beaten us to writing a new file there // Anyways, we still could not rename, let's drop the .tmp file: unlink( $af_cache_file.'.tmp' ); } else { $Debuglog->add( 'Cache updated... after unlink+rename!', 'cache' ); } } else { $Debuglog->add( 'Cache updated!', 'cache' ); } } } } ?>