wordpress的缓存插件cos-html-cache的源码解读

之前在“Blog的选择-Wordpress还是Typecho?”说过Wordpress很笨重,所以这里选择了Typecho。因为一些站需要用Wordpress,所以就得上缓存类插件,一般来说直接用WP Super Cache即可,稳定、易用、效率高。

不过,其实Wordpress还有一款效率更高的缓存类插件,那就是cosbeta(江东,东哥)开发的cos-html-cache,关于cos-html-cache,其原理和功能等方面更多的信息见这里

下面简单解读一下cos-html-cache的源码,我不是这款插件的开发者,不保证解读完全正确。

/* config */
// 是否创建首页的静态文件
define('IS_INDEX',true);// false = do not create home page cache 

/*end of config*/

define('COSVERSION','2.7.3');   // 定义插件版本

// 引入wp的文件操作函数,后面将用到其中的get_home_path
require_once(ABSPATH . 'wp-admin/includes/file.php');  
/* end of config */
$sm_locale = get_locale();  // 获取语言环境方便后面的本地化处理

// 得到语言环境对应的本地化翻译文件,当然现在只有中文的cosbeta-zh_CN.mo    
$sm_mofile = dirname(__FILE__) . "/cosbeta-$sm_locale.mo";  
// 加载本地化的翻译(http://codex.wordpress.org/Function_Reference/load_textdomain)
load_textdomain('cosbeta', $sm_mofile);  
// 得到博客网站的url (http://codex.wordpress.org/Option_Reference)
$cossithome = get_option('home');  
// 获取调用链接的完整url
$script_uri = rtrim( "http://".$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"]  ,"/"); 
$home_path = get_home_path();  // 得到wp所安装的绝对物理目录

define('SCRIPT_URI',$script_uri);   // 定义调用链接
define('CosSiteHome',$cossithome);  // 定义网站url
define('CosBlogPath', $home_path);  // 定义网站安装物理位置
// 定义插件生效标记
define("COSMETA","<!--this is a real static html file created at ".date("Y-m-d H:i:s").
                             " by cos-html-cache ".COSVERSION." -->");  

// 创建静态文件的函数
function CreateHtmlFile($FilePath,$Content){    
    // 去掉文件名中的一些非法符号
    $FilePath = preg_replace('/[ <>\'\"\r\n\t\(\)]/', '', $FilePath);  

    // if there is http:// $FilePath will return its bas path
    // 分隔文件路径,比如/home/xxx/xxx/xxx/xxx.html,
    // 如果是类似http://xxx.com/html/y2012/xxx.html,将传入/html/y2012/xxx.html
    // (http://cn2.php.net/manual/zh/function.explode.php)
    $dir_array = explode("/",$FilePath);  

    //split the FilePath
    $max_index = count($dir_array) ;
    $i = 0;
    $path = $_SERVER['DOCUMENT_ROOT']."/";  // 获取网站的根目录,比如/home/username/

    while( $i < $max_index ){
        $path .= "/".$dir_array[$i];    // 把子目录一级一级加到路径上
        $path = str_replace("//","/",$path);  // 如果有//则替换成/

        if( $dir_array[$i] == "" ){  // 如果这目录值为空则跳过去,这个判断放在循环的最前面可能更合适
            $i ++ ;
            continue;
        }
        // 上面的代码似乎可以写得更精练

        if( substr_count($path, '&') ) return true;  // 如果路径中有&符号,这不好处理,不管了
        if( substr_count($path, '?') ) return true;  // 有?也不管了
        if( !substr_count($path, '.htm') ){     // 如果不包含.htm,原来传了个路径进来
            //if is a directory
            // 如果路径不存在,贱之,并赋读写运行权,如果存在,那八成也是这里贱的
            if( !file_exists( $path ) ){        
                @mkdir( $path, 0777);
                @chmod( $path, 0777 );
            }
        }
        $i ++;  // 这个搞完,继续
    }

    if( is_dir( $path ) ){  //  如果上面折腾完后,发现是个目录,那就是说要创建index.html
        $path = $path."/index.html";
    }
    // 如果html页面没创建完整,那还是不管算了
    if ( !strstr( strtolower($Content), '</html>' ) ) return;   

    //if sql error ignore...
    $fp = @fopen( $path , "w+" );   // 好了开始写了,文件在就准备覆盖内容,不在就贱之
    if( $fp ){  // 说明有权限写
        @chmod($path, 0666 ) ;  // 给文件先赋个权限
        @flock($fp ,LOCK_EX );  // 锁定

        // write the file。
        fwrite( $fp , $Content );// 写静态文件内容
        @flock($fp, LOCK_UN);   // 解锁
        fclose($fp);            // 完事
     }
}

/* read the content from output buffer */
// 在用户登录后,才会有动静,先初始化为不管,这是个非常好的习惯,
// 为了安全,一般的看不懂的东西还是躲开算了
$is_buffer = false; 
// 如果调用的是/year/y2012/xxx.html或访问的url就是网站url
if( substr_count($_SERVER['REQUEST_URI'], '.htm') || ( SCRIPT_URI == CosSiteHome) ){   
    if( strlen( $_COOKIE['wordpress_logged_in_'.COOKIEHASH] ) < 4 ){    // 用户登录了,估计得干活了
        $is_buffer = true;
    }
    if(  substr_count($_SERVER['REQUEST_URI'], '?'))  $is_buffer = false;   // 参数调用,躲
    if(  substr_count($_SERVER['REQUEST_URI'], '../'))  $is_buffer = false;  // 太危险了,还是躲开好了
}

if( $is_buffer ){  // 真得得干活了
    ob_start('cos_cache_ob_callback');  // 开干
    register_shutdown_function('cos_cache_shutdown_callback');  // 高潮并结束,很快的
}

function cos_cache_ob_callback($buffer){    // 这里就是就会从wp的输出缓冲拿内容准备写到静态文件中
    // 下面一堆正则表达式,不是给一般人看的,我是一般人,所以不看了
    // 不过显然是在处理评论相关的信息
    $buffer = preg_replace('/(<\s*input[^>]+?(name=["\']author[\'"])[^>]+?value=(["\']))([^"\']+?)\3/i', '\1\3', $buffer); 

    $buffer = preg_replace('/(<\s*input[^>]+?value=)([\'"])[^\'"]+\2([^>]+?name=[\'"]author[\'"])/i', '\1""\3', $buffer);

    $buffer = preg_replace('/(<\s*input[^>]+?(name=["\']url[\'"])[^>]+?value=(["\']))([^"\']+?)\3/i', '\1\3', $buffer);

    $buffer = preg_replace('/(<\s*input[^>]+?value=)([\'"])[^\'"]+\2([^>]+?name=[\'"]url[\'"])/i', '\1""\3', $buffer);

    $buffer = preg_replace('/(<\s*input[^>]+?(name=["\']email[\'"])[^>]+?value=(["\']))([^"\']+?)\3/i', '\1\3', $buffer);

    $buffer = preg_replace('/(<\s*input[^>]+?value=)([\'"])[^\'"]+\2([^>]+?name=[\'"]email[\'"])/i', '\1""\3', $buffer);
    // 不是一般人看的东西结束    

     // 内容里没有插件做的标记,不能管
    if( !substr_count($buffer, '<!--cos-html-cache-safe-tag-->') ) return  $buffer;
    // 有密码保护的文章不能管
    if( substr_count($buffer, 'post_password') > 0 ) return  $buffer;//to check if post password protected  
    $wppasscookie = "wp-postpass_".COOKIEHASH;
    // 还是不能管
    if( strlen( $_COOKIE[$wppasscookie] ) > 0 ) return  $buffer;//to check if post password protected   
    /*  这些内容还是放在common.js.php里的,用js去处理吧
    $comment_author_url='';
$comment_author_email='';
$comment_author='';*/


    elseif( SCRIPT_URI == CosSiteHome) {// creat homepage   // 噢,首页,这个得管
        $fp = @fopen( CosBlogPath."index.bak" , "w+" ); // 先把内容弄进index.bak里
        if( $fp ){
            @flock($fp ,LOCK_EX );
            // write the file。
            fwrite( $fp , $buffer.COSMETA );    // 写,后面再加点标记说明静态文件创建成功
            @flock($fp, LOCK_UN);
            fclose($fp);
         }
        if(IS_INDEX)  // 如果真得要创建首页静态文件, 就把index.bak改成index.html
            @rename(CosBlogPath."index.bak",CosBlogPath."index.html");  
    }
    else    // 其它情况,试着创建静态文件,并在后面加个标记
        CreateHtmlFile($_SERVER['REQUEST_URI'],$buffer.COSMETA );
    return $buffer;
}

function cos_cache_shutdown_callback(){     // 把静态文件真得写到物理硬盘上去,高潮过程总是很短
    ob_end_flush();
    flush();
}

if( !function_exists('DelCacheByUrl') ){
    function DelCacheByUrl($url) {      // 通过url来删静态文件
        $url = CosBlogPath.str_replace( CosSiteHome,"",$url );  // 如果是完整url,就把网站给去掉
        $url = str_replace("//","/", $url );    // 把所有//替换成/
         if( file_exists( $url )){  // 文件存在
             // 如果是目录就先删里面的index.html,再删目录
             if( is_dir( $url )) {@unlink( $url."/index.html" );@rmdir($url);} 
             else @unlink( $url );  // 否则直接删静态文件
         }
    }
}

if( !function_exists('htmlCacheDel') ){
    // create single html
    function htmlCacheDel($post_ID) {   // 通过文章id来删静态文件
        if( $post_ID == "" ) return true;
        $uri = get_permalink($post_ID);
        DelCacheByUrl($uri );
    }
}

if( !function_exists('htmlCacheDelNb') ){
    // delete nabour posts
    // 如果把文章删了,那也得把邻居两文章的静态文章同时删除,避免上一篇下一篇出问题
    // 不过你看出来了么,对于文章id 大于被删文章的sql语句有点小问题,新版将会解决(2.7.4版中已解决)
    function htmlCacheDelNb($post_ID) {  
        if( $post_ID == "" ) return true;

        $uri = get_permalink($post_ID);
        DelCacheByUrl($uri );
        global $wpdb;
        $postRes=$wpdb->get_results("SELECT `ID`  FROM `" . $wpdb->posts . "` WHERE post_status = 'publish'   AND   post_type='post'   AND  ID < ".$post_ID." ORDER BY ID DESC LIMIT 0,1;");
        $uri1 = get_permalink($postRes[0]->ID);
        DelCacheByUrl($uri1 );
        $postRes=$wpdb->get_results("SELECT `ID`  FROM `" . $wpdb->posts . "` WHERE post_status = 'publish'  AND   post_type='post'    AND ID > ".$post_ID."  ORDER BY ID DESC  LIMIT 0,1;");
        if( $postRes[0]->ID != '' ){
              $uri2  = get_permalink($postRes[0]->ID);
              DelCacheByUrl($uri2 );
        }
    }
}

//create index.html
if( !function_exists('createIndexHTML') ){
    // 用户编辑、删除、新建了文章,首页就会有变动了,
    // 所以得把首页静态文件先改个名,免得还是直接访问静态文件,看不到效果
    function createIndexHTML($post_ID){    
        if( $post_ID == "" ) return true;
        //[menghao]@rename(ABSPATH."index.html",ABSPATH."index.bak");
        @rename(CosBlogPath."index.html",CosBlogPath."index.bak");//[menghao]
    }
}

if(!function_exists("htmlCacheDel_reg_admin")) {  // 把删除静态文件功能加到wp的管理菜单中
    /**
    * Add the options page in the admin menu
    */
    function htmlCacheDel_reg_admin() {
        if (function_exists('add_options_page')) {
            add_options_page('html-cache-creator', 'CosHtmlCache',8, basename(__FILE__), 'cosHtmlOption');
            //add_options_page($page_title, $menu_title, $access_level, $file).
        }
    }
}

add_action('admin_menu', 'htmlCacheDel_reg_admin');

if(!function_exists("cosHtmlOption")) {     // 这就是wp里cos-html-cache选项功能
function cosHtmlOption(){
    do_cos_html_cache_action();
?>
    <div class="wrap" style="padding:10px 0 0 10px;text-align:left">
    <form method="post" action="<?php echo $_SERVER["REQUEST_URI"]; ?>">
    <p>
    <?php _e("Click the button bellow to delete all the html cache files","cosbeta");?></p>
    <p><?php _e("Note:this will Not  delete data from your databases","cosbeta");?></p>
    <p><?php _e("If you want to rebuild all cache files, you should delete them first,and then the cache files will be built when post or page first visited","cosbeta");?></p>

    <p><b><?php _e("specify a post ID or Title to to delete the related cache file","cosbeta");?></b> <input type="text" id="cache_id" name="cache_id" value="" /> <?php _e("Leave blank if you want to delete all caches","cosbeta");?></p>
    <p><input type="submit" value="<?php _e("Delete Html Cache files","cosbeta");?>" id="htmlCacheDelbt" name="htmlCacheDelbt" onClick="return checkcacheinput(); " />
    </form>
    </div>

    <SCRIPT LANGUAGE="JavaScript">
    <!--
        function checkcacheinput(){
        document.getElementById('htmlCacheDelbt').value = 'Please Wait...';
        return true;
    }
    //-->
    </SCRIPT>
<?php
    }
}
/*
end of get url
*/
// deal with rebuild or delete
function do_cos_html_cache_action(){    // 完成wp中cos-html-cache选项中删除静态文件的操作
    if( !empty($_POST['htmlCacheDelbt']) ){
        @rename(CosBlogPath."index.html",CosBlogPath."index.bak");
        @chmod( CosBlogPath."index.bak", 0666 );
        global $wpdb;
        if( $_POST['cache_id'] * 1 > 0 ){
            //delete cache by id
             DelCacheByUrl(get_permalink($_POST['cache_id']));
             $msg = __('the post cache was deleted successfully: ID=','cosbeta').$_POST['cache_id'];
        }
        else if( strlen($_POST['cache_id']) > 2  ){
            $postRes=$wpdb->get_results("SELECT `ID`  FROM `" . $wpdb->posts . "` WHERE post_title like '%".$_POST['cache_id']."%' LIMIT 0,1 ");
            DelCacheByUrl( get_permalink( $postRes[0]->ID ) );
            $msg = __('the post cache was deleted successfully: Title=','cosbeta').$_POST['cache_id'];
        }
        else{
        $postRes=$wpdb->get_results("SELECT `ID`  FROM `" . $wpdb->posts . "` WHERE post_status = 'publish' AND ( post_type='post' OR  post_type='page' )  ORDER BY post_modified DESC ");
        foreach($postRes as $post) {
            DelCacheByUrl(get_permalink($post->ID));
            }
            $msg = __('HTML Caches were deleted successfully','cosbeta');
        }
    }
    if($msg)
    echo '<div class="updated"><strong><p>'.$msg.'</p></strong></div>';
}
$is_add_comment_is = true;  // 下面是处理文章评论,增加了js来处理,相关js在common.js.php中
/*
 * with ajax comments
 */
if ( !function_exists("cos_comments_js") ){
    function cos_comments_js($postID){
        global $is_add_comment_is;
        if( $is_add_comment_is ){
            $is_add_comment_is = false;
        ?>
        <script language="JavaScript" type="text/javascript" src="<?php echo CosSiteHome;?>/wp-content/plugins/cos-html-cache/common.js.php?hash=<?php echo COOKIEHASH;?>"></script>
        <script language="JavaScript" type="text/javascript">
        //<![CDATA[
        var hash = "<?php echo COOKIEHASH;?>";
        var author_cookie = "comment_author_" + hash;
        var email_cookie = "comment_author_email_" + hash;
        var url_cookie = "comment_author_url_" + hash; 
        var adminmail = "<?php  echo str_replace('@','{_}',get_option('admin_email'));?>";
        var adminurl = "<?php  echo  get_option('siteurl') ;?>";
        setCommForm();
        //]]>
        </script>
    <?php
        }
    }
}

function CosSafeTag(){  // 增加安全标记
    if   ( is_single() || (is_home() && IS_INDEX) )  {
        echo "<!--cos-html-cache-safe-tag-->";
    }
}
function clearCommentHistory(){ // 原因查看 http://www.storyday.com/html/y2009/2270_cos-html-cache-upgrade-to-272.html
global $comment_author_url,$comment_author_email,$comment_author;
$comment_author_url='';
$comment_author_email='';
$comment_author='';
}
//add_action('comments_array','clearCommentHistory');
add_action('get_footer', 'CosSafeTag'); // 让安全标记加在footer前
add_action('comment_form', 'cos_comments_js');  // 评论特殊处理一下

/* end of ajaxcomments*/
if(IS_INDEX)    add_action('publish_post', 'createIndexHTML'); // 有新文章对首页处理一下
add_action('publish_post', 'htmlCacheDelNb');  // 有新文章对相关静态文件处理一下

if(IS_INDEX)    add_action('delete_post', 'createIndexHTML');  // 删除了文章也对首页处理一下
add_action('delete_post', 'htmlCacheDelNb');  // 删除文件对相关静态文件处理一下

//if comments add
add_action('edit_post', 'htmlCacheDel');  // 文章及评论有变动都要更新静态文件
if(IS_INDEX) add_action('edit_post', 'createIndexHTML');  // 可能影响首页,反正也更新一下吧

解读结束。

通过阅读东哥关于cos-html-cache的说明,再通过以上解读,可以发现cos-html-cache真是一个有超级高效率的wordpress缓存类插件,并且对服务器的资源占用非常小。通过这款插件,可以说让自己的网站快变成一个纯静态页面的网站了。不用一下,真是太对不起cosbeta了。

需要特别提醒的是:

  1. 使用前请仔细阅读说明
  2. cos-html-cache只会对文章和首页产生静态html文件,这对于以文章为主的博客或cms,cos-html-cache是再适合不过了。对于那些以页面为主的,除了首页,就没什么效果了。
  3. 虽然有几年没更新了,但在wordpress 3.3、3.4和3.5下都能正常地工作。

标签: wordpress, cache, cos-html-cache, 缓存, 插件, 源码, 解读

已有 2 条评论

  1. Era

    看到您写的注释,很详细。 赞一个。
    不过我在使用的过程中遇到几个问题,不知道你是否可以解答。
    如果我的文章是直接入库,不是通过后台发布,那么首页不会重新生成静态文件。
    使用的是nginx的环境, 有安全标识,就是不能生成静态文件是怎么回事呢?

  2. Era

    大侠,如果想删除同一分类id下相邻的两篇文章应该怎么修改呢?

添加新评论