diff --git a/advanced-cache.php b/advanced-cache.php new file mode 100644 index 0000000..b94204f --- /dev/null +++ b/advanced-cache.php @@ -0,0 +1,254 @@ +$v) { + // test cookie makes to cache not work!!! + if ($n == 'wordpress_test_cookie') continue; + // wp 2.5 and wp 2.3 have different cookie prefix, skip cache if a post password cookie is present, also + if ( (substr($n, 0, 14) == 'wordpressuser_' || substr($n, 0, 10) == 'wordpress_' || substr($n, 0, 12) == 'wp-postpass_') && !$wp_ffpc_config['cache_loggedin'] ) { + return false; + } + } +} + +global $wp_ffpc_backend_status; +$wp_ffpc_backend_status = wp_ffpc_init( ); + +/* check alive status of backend */ +if ( !$wp_ffpc_backend_status ) + return false; + +/* use the full accessed URL string as key, same will be generated by nginx as well + we need a data and a meta key: data is string only with content, meta is not used in nginx */ +global $wp_ffpc_data_key; +$wp_ffpc_data_key = $wp_ffpc_config['prefix_data'] . $_SERVER['HTTP_HOST'] . $wp_ffpc_uri; +global $wp_ffpc_meta_key; +$wp_ffpc_meta_key = $wp_ffpc_config['prefix_meta'] . $_SERVER['HTTP_HOST'] . $wp_ffpc_uri; + +/* search for valid data entry */ +global $wp_ffpc_data; +$wp_ffpc_data = wp_ffpc_get ( $wp_ffpc_data_key ); + +/* search for valid meta entry */ +global $wp_ffpc_meta; +$wp_ffpc_meta = wp_ffpc_get ( $wp_ffpc_meta_key ); + +/* data is corrupted or empty */ +if ( !$wp_ffpc_data || !$wp_ffpc_meta ) { + wp_ffpc_start(); + return; +} + +/* 404 status cache */ +if ($wp_ffpc_meta['status'] == 404) { + header("HTTP/1.1 404 Not Found"); + flush(); + die(); +} + +/* server redirect cache */ +if ($wp_ffpc_meta['redirect_location']) { + header('Location: ' . $wp_ffpc_meta['redirect_location']); + flush(); + die(); +} + +/* page is already cached on client side (chrome likes to do this, anyway, it's quite efficient) */ +if (array_key_exists("HTTP_IF_MODIFIED_SINCE", $_SERVER) && !empty($wp_ffpc_meta['lastmodified']) ) { + $if_modified_since = strtotime(preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"])); + /* check is cache is still valid */ + if ( $if_modified_since >= $wp_ffpc_meta['lastmodified'] ) { + header("HTTP/1.0 304 Not Modified"); + flush(); + die(); + } +} + +/* data found & correct, serve it */ +header('Content-Type: ' . $wp_ffpc_meta['mime']); +/* don't allow browser caching of page */ +header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0'); +header('Pragma: no-cache'); +/* expire at this very moment */ +header('Expires: ' . gmdate("D, d M Y H:i:s", time() ) . " GMT"); + +/* if shortlinks were set */ +if (!empty ( $wp_ffpc_meta['shortlink'] ) ) + header('Link:<'. $wp_ffpc_meta['shortlink'] .'>; rel=shortlink'); + +/* if last modifications were set (for posts & pages) */ +if ( !empty($wp_ffpc_meta['lastmodified']) ) + header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $wp_ffpc_meta['lastmodified'] ). " GMT"); + +/* only set when not multisite, fallback to HTTP HOST */ +$wp_ffpc_pingback_url = (empty( $wp_ffpc_config['url'] )) ? $_SERVER['HTTP_HOST'] : $wp_ffpc_config['url']; + +/* pingback additional header */ +if ($wp_ffpc_config['pingback_status']) + header('X-Pingback: ' . $wp_ffpc_pingback_url . '/xmlrpc.php' ); + +/* for debugging */ +if ($wp_ffpc_config['debug']) + header('X-Cache-Engine: WP-FFPC with ' . $wp_ffpc_config['cache_type']); + +/* HTML data */ +echo $wp_ffpc_data; +flush(); +die(); + +/** + * FUNCTIONS + */ + +/** + * starts caching + * + */ +function wp_ffpc_start( ) { + ob_start('wp_ffpc_callback'); +} + +/** + * write cache function, called when page generation ended + */ +function wp_ffpc_callback($buffer) { + global $wp_ffpc_config; + global $wp_ffpc_data; + global $wp_ffpc_meta; + global $wp_ffpc_redirect; + global $wp_ffpc_meta_key; + global $wp_ffpc_data_key; + + /* no is_home = error */ + if (!function_exists('is_home')) + return $buffer; + + /* no
close tag = not HTML, don't cache */ + if (strpos($buffer, '') === false) + return $buffer; + + /* reset meta to solve conflicts */ + $wp_ffpc_meta = array(); + + /* WP is sending a redirect */ + if ($wp_ffpc_redirect) { + $wp_ffpc_meta['redirect_location'] = $wp_ffpc_redirect; + wp_ffpc_write(); + return $buffer; + } + + /* trim unneeded whitespace from beginning / ending of buffer */ + $buffer = trim($buffer); + + /* Can be a trackback or other things without a body. + We do not cache them, WP needs to get those calls. */ + if (strlen($buffer) == 0) + return ''; + + if ( is_home() ) + $wp_ffpc_meta['type'] = 'home'; + elseif (is_feed() ) + $wp_ffpc_meta['type'] = 'feed'; + elseif ( is_archive() ) + $wp_ffpc_meta['type'] = 'archive'; + elseif ( is_single() ) + $wp_ffpc_meta['type'] = 'single'; + else if ( is_page() ) + $wp_ffpc_meta['type'] = 'page'; + else + $wp_ffpc_meta['type'] = 'unknown'; + + /* check if caching is disabled for page type */ + $nocache_key = 'nocache_'. $wp_ffpc_meta['type']; + + if ( $wp_ffpc_config[$nocache_key] == 1 ) { + return $buffer; + } + + if ( is_404() ) + $wp_ffpc_meta['status'] = 404; + + /* feed is xml, all others forced to be HTML */ + if ( is_feed() ) + $wp_ffpc_meta['mime'] = 'text/xml;charset='; + else + $wp_ffpc_meta['mime'] = 'text/html;charset='; + + /* set mimetype */ + $wp_ffpc_meta['mime'] = $wp_ffpc_meta['mime'] . $wp_ffpc_config['charset']; + + /* get shortlink, if possible */ + if (function_exists('wp_get_shortlink')) + { + $shortlink = wp_get_shortlink( ); + if (!empty ( $shortlink ) ) + $wp_ffpc_meta['shortlink'] = $shortlink; + } + + /* try if post is available + if made with archieve, last listed post can make this go bad + */ + global $post; + if ( !empty($post) && ( $wp_ffpc_meta['type'] == 'single' || $wp_ffpc_meta['type'] == 'page' ) ) + { + /* get last modification data */ + if (!empty ( $post->post_modified_gmt ) ) + $wp_ffpc_meta['lastmodified'] = strtotime ( $post->post_modified_gmt ); + } + /* set meta */ + wp_ffpc_set ( $wp_ffpc_meta_key, $wp_ffpc_meta ); + /* set data */ + wp_ffpc_set ( $wp_ffpc_data_key, $buffer ); + + /* vital for nginx version */ + header("HTTP/1.1 200 OK"); + /* echoes HTML out */ + return $buffer; +} + +?> diff --git a/css/wp-ffpc.admin.css b/css/wp-ffpc.admin.css new file mode 100644 index 0000000..2ee1814 --- /dev/null +++ b/css/wp-ffpc.admin.css @@ -0,0 +1,53 @@ +dt { + margin-top: 2em; + font-weight:bold; +} + +dd { + +} +fieldset { + display:block; + margin:0 1% 0 1%; + clear:none; +} + +legend { + font-weight:bold; + padding:0; + margin:0; + font-size:130%; + padding-top:3em; +} + +.default { + display:block; + padding-left: 1em; + font-size:90%; + color:#666; +} + +.description { + display:block; +} + +.grid50 { + float:left; + display:block; + text-align:left; + margin:0 1% 0 1%; + width:48%; + overflow:auto; +} + +.clearcolumns { + clear:both; +} + +.error-msg { + color: #990000; +} + +.ok-msg { + color: #009900; +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..8e15cfb --- /dev/null +++ b/readme.txt @@ -0,0 +1,45 @@ +=== WP-FFPC === +Contributors: cadeyrn +Tags: cache, APC, memcached, full page cache +Requires at least: 2.7 +Tested up to: 3.3 +Stable tag: 0.1 + +Fast Full Page Cache, backend can be memcached or APC + + +== Description == +WP-FFPC is a full page cache plugin for WordPress. It can use APC or a memcached server as backend. +PHP has two extension for communication with a memcached server, named Memcache and Memcached. The plugin can utilize both. +A special function is that when used with the Memcache extension it produces memcached entries that can be read and served directly from nginx, making the cache insanely fast. + +The naming stands for Fast Full Page Cache. + +If used in WordPress Network, the configuration will only be available for network admins at the network admin panel, and will be system-wide and will be applied for every blog. + +Some parts were based on Hyper Cache plugin by Satollo (info@satollo.net). + + += Usage = +Configure the plugin at the admin section + +== Installation == +1. Upload contents of `wp-ffpc.zip` to the `/wp-content/plugins/` directory +2. Activate the plugin through the `Plugins` menu in WordPress (please use network wide activation if used in a WordPress Network) +3. Fine tune the settings in `Settings` -> `wp-ffpc` menu in WordPress. For WordPress Network, please visit the Network Admin panel, the options will be available at WP-FFPC menu entry. + +== Frequently Asked Questions == + + +== Screenshots == + + +== Upgrade Notice == + + +== Changelog == + += 0.1 = +2012.02.16 + +* first public release diff --git a/wp-ffpc-common.php b/wp-ffpc-common.php new file mode 100644 index 0000000..4a034bd --- /dev/null +++ b/wp-ffpc-common.php @@ -0,0 +1,180 @@ +addServer( $wp_ffpc_config['host'] , $wp_ffpc_config['port'] ); + $wp_ffpc_backend_status = $wp_ffpc_backend->getServerStatus( $wp_ffpc_config['host'] , $wp_ffpc_config['port'] ); + break; + + /* in case of Memcached */ + case 'memcached': + /* Memcached class does not exist, Memcached extension is not available */ + if (!class_exists('Memcached')) + return false; + if ($reg_backend) + global $wp_ffpc_backend; + $wp_ffpc_backend = new Memcached(); + $wp_ffpc_backend->addServer( $wp_ffpc_config['host'] , $wp_ffpc_config['port'] ); + $wp_ffpc_backend_status = array_key_exists( $wp_ffpc_config['host'] . ':' . $wp_ffpc_config['port'] , $wp_ffpc_backend->getStats() ); + break; + + /* cache type is invalid */ + default: + return false; + } + return $wp_ffpc_backend_status; +} + +/** + * clear cache element or flush cache + * + * @param $post_id [optional] : if registered with invalidation hook, post_id will be passed + */ +function wp_ffpc_clear ( $post_id = false ) { + global $wp_ffpc_config; + global $post; + + /* post invalidation enabled */ + if ( $wp_ffpc_config['invalidation_method'] ) + { + $path = substr ( get_permalink($post_id) , 7 ); + if (empty($path)) + return false; + $meta = $wp_ffpc_config['prefix-meta'] . $path; + $data = $wp_ffpc_config['prefix-data'] . $path; + } + + switch ($wp_ffpc_config['cache_type']) + { + /* in case of apc */ + case 'apc': + if ( $wp_ffpc_config['invalidation_method'] ) + { + apc_delete ( $meta ); + apc_delete ( $data ); + } + else + { + apc_clear_cache('user'); + apc_clear_cache('system'); + } + break; + + /* in case of Memcache */ + case 'memcache': + case 'memcached': + global $wp_ffpc_backend; + if ( $wp_ffpc_config['invalidation_method'] ) + { + $wp_ffpc_backend->delete( $meta ); + $wp_ffpc_backend->delete( $data ); + } + else + { + $wp_ffpc_backend->flush(); + } + break; + + /* cache type is invalid */ + default: + return false; + } + return true; +} + +/** + * sets a key-value pair in backend + * + * @param &$key store key, passed by reference for speed + * @param &$data store value, passed by reference for speed + * + */ +function wp_ffpc_set ( &$key, &$data ) { + global $wp_ffpc_config; + + switch ($wp_ffpc_config['cache_type']) + { + case 'apc': + /* use apc_store to overwrite data is existed */ + apc_store( $key , $data , $wp_ffpc_config['expire']); + break; + case 'memcache': + global $wp_ffpc_backend; + /* false to disable compression, vital for nginx */ + $wp_ffpc_backend->set ( $key, $data , false, $wp_ffpc_config['expire'] ); + break; + case 'memcached': + global $wp_ffpc_backend; + $wp_ffpc_backend->set ( $key, $data , $wp_ffpc_config['expire'] ); + break; + } +} + +/** + * gets cached element by key + * + * @param &$key: key of needed cache element + * + */ +function wp_ffpc_get( &$key ) { + global $wp_ffpc_config; + + switch ($wp_ffpc_config['cache_type']) + { + case 'apc': + return apc_fetch($key); + case 'memcache': + case 'memcached': + global $wp_ffpc_backend; + return $wp_ffpc_backend->get($key); + default: + return false; + } +} +?> diff --git a/wp-ffpc.php b/wp-ffpc.php new file mode 100644 index 0000000..46128a1 --- /dev/null +++ b/wp-ffpc.php @@ -0,0 +1,627 @@ +get_options(); + + /* check is backend is available */ + $alive = wp_ffpc_init( $this->options['cache_type'] ); + + /* don't register hooks if backend is dead */ + if ($alive) + { + /* init inactivation hooks */ + add_action('switch_theme', array( $this , 'invalidate'), 0); + add_action('edit_post', array( $this , 'invalidate'), 0); + add_action('publish_post', array( $this , 'invalidate'), 0); + add_action('delete_post', array( $this , 'invalidate'), 0); + + /* Capture and register if a redirect is sent back from WP, so the cache + can cache (or ignore) it. Redirects were source of problems for blogs + with more than one host name (eg. domain.com and www.domain.com) comined + with the use of Hyper Cache.*/ + add_filter('redirect_canonical', array( $this , 'redirect_canonical') , 10, 2); + } + + /* add admin styling */ + if( is_admin() ) + { + wp_enqueue_style( WP_FFPC_PARAM . '.admin.css' , WP_FFPC_URL . '/css/'. WP_FFPC_PARAM .'.admin.css', false, '0.1'); + } + + /* on activation */ + register_activation_hook(__FILE__ , array( $this , 'activate') ); + + /* on deactivation */ + register_deactivation_hook(__FILE__ , array( $this , 'deactivate') ); + + /* on uninstall */ + register_uninstall_hook(__FILE__ , array( $this , 'uninstall') ); + + /* init plugin in the admin section */ + /* if multisite, admin page will be on network admin section */ + if ( MULTISITE ) + add_action('network_admin_menu', array( $this , 'admin_init') ); + /* not network, will be in simple admin menu */ + else + add_action('admin_menu', array( $this , 'admin_init') ); + } + + /** + * activation hook: save default settings in order to eliminate bugs. + * + */ + function activate ( ) { + $this->save_settings( true ); + } + + /** + * init function for admin section + * + */ + function admin_init () { + /* register options */ + add_site_option( WP_FFPC_PARAM, $this->options , '' , 'no'); + + /* save parameter updates, if there are any */ + if ( isset($_POST[WP_FFPC_PARAM . '-save']) ) + { + $this->save_settings (); + $this->status = 1; + header("Location: admin.php?page=" . WP_FFPC_OPTIONS_PAGE . "&saved=true"); + } + + add_menu_page('Edit WP-FFPC options', __('WP-FFPC', WP_FFPC_PARAM ), 10, WP_FFPC_OPTIONS_PAGE , array ( $this , 'admin_panel' ) ); + } + + /** + * settings panel at admin section + * + */ + function admin_panel ( ) { + + /** + * security + */ + if( ! function_exists( 'current_user_can' ) || ! current_user_can( 'manage_options' ) ){ + die( ); + } + + /** + * if options were saved + */ + if ($_GET['saved']=='true' || $this->status == 1) : ?> +Settings saved.
WARNING: WP_CACHE is disabled, plugin will not work that way. Please add define( 'WP_CACHE', true ); into the beginning of wp-config.php