国内著名CMS: PHPCMS 整站源代码分析讲解

首先我对 PHPCMS 的 头程序文件开始讲解 : include/common.inc.php 这个文件是程序启动的核心文件.

[php]
<?php
/**
代码讲解分析: 逆雪寒. 2007 - 12 - 20
*/
$mtime = explode(' ', microtime());
$phpcms_starttime = $mtime[1] + $mtime[0];
/**
计算脚本开始运行的时间。很常见的写法。最后结算还在程序运行的终点位置。讲到了在给各位说。
*/
unset($LANG, $_REQUEST, $HTTP_ENV_VARS, $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $HTTP_COOKIE_VARS);
/**
把 $HTTP_ENV_VARS $HTTP_POST_VARS 全局变量设置为 NULL 因为php4.1.0以上默认以 $_POST 来替代。unset后防止程序运行在低版本会出现安全等问题。比如变量注入
*/
set_magic_quotes_runtime(0);
//地球人都知道。关了字符窜入库自动转意 比如 my name is on'x 转成 my name is on \'x 为了最大的程序性能所以我们关掉吧~哈哈
define('IN_PHPCMS', TRUE);
/**
程序入口标记。为什么需要这个东西呢。就好比你进一个小区,保安大哥哥肯定要你签字或是挂个鸟牌证明你是从正门进来的。
防止你翻墙进入去偷东西搞破坏。这个入口标记也有这个功能。 在这里定义了一个常量 IN_PHPCMS 为 TRUE ,然后我在其他程序文件里面检查这个标记。如果不存在或不为真,
那么就基本可以肯定你这个家伙是个小偷来的。 人可能翻墙进入。但程序怎么翻墙呢。只要在php.ini文件里面激活 allow_url_fopen选项,include() 就可以包含 URL 地址了。你想下如果你有个 放密码的PHP文件。如果给人家include 了以后。你怕不怕。
*/
define('PHPCMS_ROOT', str_replace("\\", '/', substr(dirname(__FILE__), 0, -8)));
/**
为了程序产品的跨平台。自动获取程序的安装目录路径 ,定义为 PHPCMS_ROOT 常量。方便以后程序使用。建议大家都这样做哦
*/
require PHPCMS_ROOT.'/include/global.func.php';
/**
包含 全局函数 global.func.php 文件。里面放了些程序全局都有需要用的函数.大家看到了吧。 常量 PHPCMS_ROOT 已经发挥作用。 这样程序给人家发布到了 二级目录下。也不需要人家手动改整站根目录路径了。
自己好好理解下。。。。。
*/
$search_arr = array("/ union /i","/ select /i","/ update /i","/ outfile /i","/ or /i");
/**
("/ union /i") 这个是正则的写法?不懂正则的自己百度找教程来学。 这里不详细说了
*/
$replace_arr = array(' union ',' select ',' update ',' outfile ',' or ');
/**
看这个意思很明了。 union :连接两条SQL语句。 outfile : 主要用来导出数据库资料到其他介质上。
干啥字要定义这两个变量呢? 在 global.func.php 文件(前面已经加载)里面有个函数 strip_sql() 各位可以看下。
function strip_sql($string)
{
global $search_arr,$replace_arr;
return is_array($string) ? array_map('strip_sql', $string) : preg_replace($search_arr, $replace_arr, $string);
}
函数里面定义了这两个变量为 global全局变量。 那么函数里就可以直接使用了。 先讲解下这个函数吧。 顾名思义这个函数是过滤字符窜里面的SQL语句使得关键的SQL语句单词失效。
主要过滤那些SQL语句呢。 主要是这几个关键字: union select update outfile or 等。因为这几个SQL字是极度容易在$_GET传输中给截注.
这个函数写得实在精妙。 is_array 来判断 $string 是否为数组。如果 是 就 array_map() 函数来递归过滤 $string数组里面的每一个单元. 每个单元是一个字符窜吧?
当递归调用 strip_sql() 以后 is_array()判断 $string 肯定为假了,因为$string已经不是数组而是字符窜。所以 执行 preg_replace()函数。这个函数很好理解了。就是替换了。
*/
$_POST = strip_sql($_POST);
$_GET = strip_sql($_GET);
$_COOKIE = strip_sql($_COOKIE);
/**
使用strip_sql()函数来过滤 $_POST $_GET $_COOKIE;
一般不是开源的站。很少过SQL关键字过滤。不过这个也是冒很大风险的。
*/
unset($search_arr, $replace_arr);
/**
unset 使用过但以后不需要的变量。这个是很好的习惯。第一不会浪费内存。如果变量存了大量的数据字节,而后你的程序是一直不需要用的。那么
就会很浪费内存。拉底程序性能 . 哈哈。真很书面。反正是好习惯我们都要学习的拉。
*/
$magic_quotes_gpc = get_magic_quotes_gpc(); // get_magic_quotes_gpc()检测gpc是否系统自动转意。 gpc 是什么呢? GET POST COOKIE 来来去去就这几个东西罗。会返回 真或假
if(!$magic_quotes_gpc)
{
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
}
/**
判断一下系统是否打开了自动对gpc进行转意这个选择。如果是的话,就不需要我们自动转意了。如果不是那么还是要老百姓的手段。自己动手丰衣足食。
来看下也是在global.func.php 文件里面定义的这个函数: 其实是一个封装好的php的 addslashes() 函数的函数。PHP都自己有了为什么还要自己封装成函数呢?
理由很简单。为了以后的扩展更改容易罗。如果我们一开始就全部用 addslashes() 这个函数来对 ' 进行转意的话。那么以后随着程序的发展。我可能想多过滤个 ^ * ( )之类的
那如何是好呢?所以为了以后孩子的成长。我们还是最好封起来吧。 记住:以后有可能会边的东西。最好都封装成模块。函数 。类。 这样程序的灵活度就上去了。
function new_addslashes($string)
{
if(!is_array($string)) return addslashes($string);
foreach($string as $key => $val) $string[$key] = new_addslashes($val);
return $string;
}
这个函数也是写得贼好。也是同时考虑过滤 字符窜或数组,也是使用了 传归。看下就应该明白了吧。这个不用说了。我们要学下这个思路这个方法方式哦。这样才能进步。
哈哈。我们要懂模仿。
*/
@extract($_POST, EXTR_OVERWRITE);
@extract($_GET, EXTR_OVERWRITE);
/**
嘿。 extract 前面加个 @鸡蛋做什么呢??抑制错误的。还不懂的话。自己百度了。
为什么用extract()函数呢. 平时我们程序 是不是要常使用 $_POST $_GET来获取传递的变量呀。是不是感觉贼麻烦呀。
比如 $_POST['xx'] 这样接受是挺好。但写多了很麻烦是吧。我是感觉麻烦。我现在想直接就 $xx就可以获取传递过来的东西。那怎么办呢。
就用了 extract()函数来实现这么一个技巧。 这个技巧在discuz 论坛上也有应用。
*/
unset($_POST, $_GET);
/**
unset() 好处不用说了吧。 释放 $_POST $_GET 数组 ,因为已经不需要他们了。
*/
?>

明天放假了.今天在写点罗.放假没空写了.要陪老婆,大家看了有什么不明白的.可以跟帖问.我懂的我会回答.谢谢
[/php]

继续::

2007-12-21

吃完中午开始分析了点代码.时间不多.
[php]
/**
代码讲解分析: 逆雪寒. 2007 - 12 - 21
*/
require PHPCMS_ROOT.'/config.inc.php';
/**
加栽整站的配置参数文件。一般的程序都会有这个文件。做什么的呢?比如一些数据库连接地址。用户名,密码等。需要用到的参数都定义在这个文件里面。这样以后配置变了。我们只要改动下这个文件里面的
变量值就好。是不是很方便呢。呵呵. 在这里说下 require() 这个加载函数。 require 和 include 都是用来加载其他PHP文件用的。但他们是有区别的。 require 函数:是"预解释"函数。就是程序一加载,就执行了require函数。而include 呢。是个过程加载函数。我们可以在逻辑里比如: if 里面使用include 来动态的加载其他程序片段。而require 就不行。*/
require PHPCMS_ROOT.'/languages/'.$CONFIG['language'].'/phpcms.lang.php';
/**
顾名思义: 这个就是加载语言包了。PHP的国际化目前做得最多的。就是直接用PHP文件来实现。 在 phpcms.lang.php 文件里面定义程序中要用到的中文信息。然后在程序一开始就加载。那里程序里面
就可以使用这个文件里面的变量和一切。那么就简单了。模板上就不需要直接写中文信息了。直接用这个文件里面定义的变量等来替换。从而实现国际化。over!!!最好自己打开这个语言文件再加上自己思考下。就知道。原来如此简单。
*/
define('PHPCMS_PATH', $CONFIG['rootpath']);
define('PHPCMS_CACHEDIR', $CONFIG['cachedir']);
/**
$CONFIG['rootpath'] 这个就是全局配置文件 config.inc.php 文件里面数据库信息。等全部配置信息。在这里把他们定义为 常量。 为什么需要定义为常量呢。因为作者感觉这样写爽罗。呵呵。其实因为后面
用到这两个变量多。所以干脆定义为常量。方便使用。再多说一个技巧: $CONFIG['rootpath'] 其实也可以写成  $CONFIG[rootpath] 但是最好不要这样。为什么呢。因为PHP引擎会先判断 rootpath 是不是常量。如果不是才会认定    $CONFIG[rootpath] 是数组。 这样性能上就差了一点点了。 再多说一个技巧: 为什么程序多数都用 '' 单引号呢而不用 "" 双引号呢。因为这样效率好, "" 双引号。
php引擎还会先检查里面是否有变量,如果有就解释。而 '' 单引号不会做这一步的检查。而直接就当成字符窜了。所以效率上也会有一点点影响哦。
*/
$CONFIG['enablephplog'] ? set_error_handler('phpcms_error') : error_reporting(E_ERROR | E_WARNING | E_PARSE);
/**
$CONFIG['enablephplog'] 是否开启错误日志设置。这个设置在全局配置文件里面.config.inc.php 。 这里使用了 三目运算符   偶最喜欢用了。一些简短的逻辑判断。可以使用 ? : ; 来实现比较简洁
set_error_handler() 这个函数就大有来头了。php4里面的典型自定义程序出错后行为的一个函数。十分好用。怎么用呢? set_error_handler(函数) 的参数也是一个函数。这个函数。反映了程序出错后行为的。
phpcms_error 函数存在 global.func.php 全局函数里面。

function phpcms_error($errno, $errmsg, $filename, $linenum, $vars)
{
$filename = str_replace(PHPCMS_ROOT, '.', $filename);
$filename = str_replace("\\", '/', $filename); //  把win平台的 \\ 换成  /兼容常见系统的路径
if(!defined('E_STRICT')) define('E_STRICT', 2048);
$dt = date('Y-m-d H:i:s');
$errortype = array (  
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parsing Error',
E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core Error',
E_CORE_WARNING => 'Core Warning',
E_COMPILE_ERROR => 'Compile Error',
E_COMPILE_WARNING => 'Compile Warning',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Runtime Notice'
);
$user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE);
$err = "<errorentry>\n";
$err .= "\t<datetime>" . $dt . "</datetime>\n";
$err .= "\t<errornum>" . $errno . "</errornum>\n";
$err .= "\t<errortype>" . $errortype[$errno] . "</errortype>\n";
$err .= "\t<errormsg>" . $errmsg . "</errormsg>\n";
$err .= "\t<scriptname>" . $filename . "</scriptname>\n";
$err .= "\t<scriptlinenum>" . $linenum . "</scriptlinenum>\n";
if (in_array($errno, $user_errors))
{
$err .= "\t<vartrace>" . wddx_serialize_value($vars, "Variables") . "</vartrace>\n";
}
$err .= "</errorentry>\n\n";
echo $err;
error_log($err, 3, PHPCMS_ROOT.'/data/php_error_log.xml');
chmod(PHPCMS_ROOT.'/data/php_error_log.xml', 0777);
}
就是这个鸟蛋。 现在我们慢慢来干掉他。呵呵  这个自定义出错信息函数默认带有四个参数。 第一个参数 $errno 是程序出错的等级。 第二参数是程序出错的界面信息。第三是出现错误的程序文件名。
第四是 第几行出现错误。第五个参数。要不要都行是当前变量状态的快照.看吧。我们有这些信息后。想定义怎么样的错误信息给客户看都很容易了是吧?但现在我们是要生成错误日志呢?这里phpcms 作者是动态生成一个XML文件来做错误日志的。不错不错. 他使用了 in_array() 函数来实现(因为比较简单,自己理解下) 只记录 E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE 这三个级别的错误日志信息。\n 是文本换行符  \t是制表符. 这里他使用了一个比较漂亮而不常用的函数 wddx_serialize_value () wddx 其实也是一种 xml 。 wddx_serialize_value() 这个函数就是把一般变量以XML格式输出。这样我们就不用自己模拟写xml了。方便吧。呵呵 第一个参数就是: 要格式输出的变量,第二个参数是输出的xml的介绍信息. 下面就是 error_log() 函数。这个函数十分有用了。就是生成错误日志XML文件。不需要我们fopen 了。方便吧。它还有很多功能。详细的看手册。chmod 设置日志文件的权限是 可读可写可执行。  在php5中。我习惯使用 extends Exception 来定义自己的出错信息。所以很少用 set_error_handle(). 如果没开启日志功能。那么 error_reporting(E_ERROR | E_WARNING | E_PARSE) 就运行了。把一般出错信息先出过来。
*/
if($CONFIG['sessionsavepath']) session_save_path($CONFIG['sessionsavepath']);
/**
定义session 的存储路径,session 其实 也是cookie 不过 session 是实现在服务器端的。安全但负载重点。这样做的好处?效率很好。如果你在虚拟主机的话。大家的session cookie 都放在了php.ini里面设置的默认地方。文件夹臃肿就会慢罗。是吧。第二就是安全罗。 记得一定要定义在 session_start()函数之前
*/
session_start();
if(function_exists('date_default_timezone_set')) date_default_timezone_set($CONFIG['timezone']);
/**
php5开始有时区的概念了。记得就行
*/
header('Content-type: text/html; charset='.$CONFIG['charset']);
/**
设置页面编码.  php编码有: 页面编码。数据库编码。文件内码。如果三码相同就一般不会出现乱码. 文件内码是什么呢?每个文件都有自己的内部编码。一般都用UTF8比较爽。怎么改变文件内码?你用DW也行  UE 也行。随便。 数据库编码那肯定是要指定的了。mysql5开始也有字符集模式这个最好也设置这样可以兼容更多平台。
页面编码:<meta http-equiv="Content-Type" c />  这句就是。一般的HTML头文件都有。那 还需要header('Content-type: text/html; charset='.$CONFIG['charset']);吗?  其实需要的。因为有些自己写的提示层呀。或是文件里没指定页面编码的。就很容易出现乱码那么我们就防范于未然。 header 一个编码过去。那就OK了。多好。
*/
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown'))
{
$PHP_IP = getenv('HTTP_CLIENT_IP');
}
elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown'))
{
$PHP_IP = getenv('HTTP_X_FORWARDED_FOR');
}
elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown'))
{
$PHP_IP = getenv('REMOTE_ADDR');
}
elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown'))
{
$PHP_IP = $_SERVER['REMOTE_ADDR'];
}
preg_match("/[\d\.]{7,15}/", $PHP_IP, $ipmatches);
$PHP_IP = $ipmatches[0] ? $ipmatches[0] : 'unknown';
/**
函数 getenv() 是获取环境变量。  环境变量: HTTP_CLIENT_IP 是获取客户端的IP 。但有可能人家是通过代理来访问你的程序的呢。那么这时候就要用 环境变量:
HTTP_X_FORWARDED_FOR 了。 包括  getenv('REMOTE_ADDR')  $_SERVER['REMOTE_ADDR']  都是获取人家  IP的。反正碰罗。碰到那个能获取就大工告成。
*/
$PHP_TIME = time();
$PHP_SELF = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : (isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : $_SERVER['ORIG_PATH_INFO']);
/**
获取当前运行的脚本名:  刚开始看是不是有点乱呢。 咋没用if else 呢。 看这样的东西。我们最好从右看到左。这样比较好明白点。$_SERVER['SCRIPT_NAME'] $_SERVER['PHP_SELF'] $_SERVER['ORIG_PATH_INFO'] 这三个服务器全局变量都是获取 当前脚本名的。主要看服务器当前环境了。那个存在的就获取那个。
isset() 函数 十分有用。 测试一个变量是否已经定义。 注: $a= NULL ; isset($a) 这样会返回false的哦。 注意 isset 和empty 两个函数的用法。用得不好会出大问题的。自己看手册。  
*/
$PHP_QUERYSTRING = $_SERVER['QUERY_STRING'];
$PHP_DOMAIN = $_SERVER['SERVER_NAME'];
$PHP_REFERER = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
$PHP_SCHEME = $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://'; //测试服务器是否启动了ssl 连接如果是的话。就用https://安全连接来进行通行
$PHP_PORT = $_SERVER['SERVER_PORT'] == '80' ? '' : ':'.$_SERVER['SERVER_PORT'];
$PHP_SITEURL = $PHP_SCHEME.$PHP_DOMAIN.$PHP_PORT.PHPCMS_PATH;
$PHP_URL = $PHP_SCHEME.$PHP_DOMAIN.$PHP_PORT.$PHP_SELF.($PHP_QUERYSTRING ? '?'.$PHP_QUERYSTRING : '');
/**
获取当前脚本的URL
*/
$db_file = $db_class = 'db_'.$CONFIG['database'];
if(!defined('IN_ADMIN')) //如果不是在后台。 常量 IN_ADMIN 是后台标志
{
if($CONFIG['dbiscache']) $db_file .= '_cache';
if($CONFIG['phpcache'] == '2') // 如果在config.inc.php 里面开启了缓存
{
$cachefileid = md5($PHP_SELF.'?'.$PHP_QUERYSTRING); //把脚本名和后面的get信息 md5加密,以此来生成下面的缓存目录和缓存文件
$cachefiledir = PHPCMS_ROOT.'/data/phpcache/'.substr($cachefileid, 0, 2).'/'; //缓存目录
$cachefile = $cachefiledir.$cachefileid.'.html'; //缓存文件: xxx.html 格式 
if(file_exists($cachefile) && ($PHP_TIME < @filemtime($cachefile) + $CONFIG['phpcacheexpires']))
{ //如果缓存文件存在和缓存没有过期效,那么就返回缓存文件名
require $cachefile;
exit;
}
}
if($PHP_QUERYSTRING && preg_match("/^(.*)\.(htm|html|shtm|shtml)$/", $PHP_QUERYSTRING, $urlvar)) //获取传递过来的变量。有什么用的呢?请看下面解释
{
parse_str(str_replace(array('/', '-', ' '), array('&', '=', ''), $urlvar[1]));
}
}
/**
上面这部分相对复杂了点。但没关系。慢慢讲解. 首先缓存只针对前台.所以我们一开始就判断.这个脚本是运行在前台的而不是在后台 !defined('IN_ADMIN') 来判断.
然后呢.再看客户配置 config.inc.php文件是否开启了缓存. ==2 就是开启了. .接着开始用一系列的规则来找出缓寸的文件名和目录: 脚本名:xx.php和后续传递的参数 ?xx=ee&bb=jj 他两的字符窜的MD5 .以这个md5窜来定义出了缓存目录.和缓存文件 .接着再判断这个缓存文件是否存在和是否没过缓存有效期.如果没有就返回这个缓存文件的名字.
然后到主菜了. 最后一个if逻辑是做什么的呢? 不知道大家有没见过 这样的网址:http://www.beihai.com/dd.php/xx-23/cc-22.html  他们其实都算是伪静态.优化URL用的.咋看起来还很象静态.爽. 但你可能想.这样的地址.我们写PHP程序的.怎么获取get 变量呢?  最后if 就是解答这个问题的. 先剥离url来获取  传递的字符窜.然后 str_replace 来把 '/' '-' 替换成标准的 '&' '=' 好象:  http://www.beihai.com/dd.php&xx=23&cc=22  看这样你应该看明白了吧.然后用 parse_str() 函数来把xx  $xx=23 cc  $cc=22 php真是什么都给你想到了.强.看明白了吧.OK.过了.
*/
[/php]

恩.终于把common.inc.php 这个文件大概讲解完了. 这个文件里面包含了很多东西.都是些挺不错的思想.大家应该好好学习.这样我们写出来的PHP程序会更加强壮.
:lol: ,偶现在晚上都在边陪老婆边看电影边弄linux 的C,还是学习 阶段 所以时间有点紧.白天在公司挤点时间出来分析代码罗.

对于phpcms 我也是第一次接触.以前没装过也没用过.现在也没详细用过.所以我看到代码讲什么我就讲什么.没具体讲PHPCMS的应用等.希望理解.

如果我分析代码分析得不合理.请指出.功能进步学习.谢谢

[php]
require PHPCMS_ROOT.'/include/'.$db_file.'.class.php'; // 包含数据库操作类,下章详说
require PHPCMS_ROOT.'/include/tag.func.php';  //遇到再说
require PHPCMS_ROOT.'/include/extension.inc.php'; //遇到再说
$db = new $db_class;   // 实例化数据库类
$db->connect($CONFIG['dbhost'], $CONFIG['dbuser'], $CONFIG['dbpw'], $CONFIG['dbname'], $CONFIG['pconnect']); //连接数据库@_@
$db->iscache = $CONFIG['dbiscache']; //是否开启SQL缓存
$db->expires = $CONFIG['dbexpires']; //缓存时间
if(!cache_read('table.php'))
{
require_once PHPCMS_ROOT.'/include/cache.func.php';
cache_all(); //生成所有缓存
}
/**
cache_read() 函数 读缓存文件函数存在 global.func.php 里面.上菜先:
function cache_read($file, $mode = 'i')
{
$cachefile = PHPCMS_CACHEDIR.$file;
if(!file_exists($cachefile)) return array();
return $mode == 'i' ? include $cachefile : file_get_contents($cachefile);
}
就这么简单.文本缓存,在一些大的开源的PHP项目中经常见到.主要是为了减轻数据库的负荷的. 比如在程序启动文件里面,就把一些后台配置的常用信息缓存到php文件里面.然后在以后的程序就可以直接使用而不用每次都访问数据库了.但对经常要更新的信息.最好不要用文本缓存这形式,因为PHP文件内置的文件锁flock()不是很好用.大系统中多用户同时写访问的时候有可能会把缓存文件破坏.大系统建议使用 memcached  mysql5.1 分区  mysql 主从 来实现负载均衡 @=@ 废话太多了. 这个函数很简单.自己看下就明白了.如果缓存和模式变量 $mode 是否为 i 是就include 不是就 把文件以字符窜形式读到内存中.
如果 cache_read()找不到缓存文件'table.php'就会返回false,那么就 加栽 cache.func.php  文件.它里面是些创建缓存的一些函数. 然后呢执行 cache_all()函数生成所有的常用信息缓存.
关于phpcms 的缓存更详细包括生成原理.打算在弄完启动文件common.inc.php 后再开篇写个详细的.  
*/
$CACHE = cache_read('common.php');
/**
加载 common.php 缓存文件里面的变量(数据) 这样我们不用从数据库读了每次.是吧
common.php 文件里面是什么来的呢?上菜:
<?php
return array (
'module' =>
array (
'phpcms' =>
array (
'module' => 'phpcms',
'name' => 'phpcms',
'iscore' => '1',
'iscopy' => '0',
'isshare' => '0',
'moduledir' => '',
'linkurl' => '',
),
'member' =>
array (
'module' => 'member',
'name' => '会员',
'iscore' => '1',
'iscopy' => '0',
'isshare' => '0',
'moduledir' => 'member',
'linkurl' => '/phpcms/member/',
),
'article' =>
array (
'module' => 'article',
'name' => '文章',
'iscore' => '0',
'iscopy' => '1',
'isshare' => '0',
'moduledir' => 'article',
'linkurl' => '',
)
?>
看到了吧.这个就是全部从数据库里面生成的文本缓存信息.我们不用每次都连接数据库读数据库.而只要访问里面的数组就可以得到一些配置信息.
这个就是文本缓存的作用了,至于怎么会生成这个文本缓存文件的.我会另外开一篇来介绍。
*/
$MODULE = $CACHE['module']; //缓存中的数据
$CHANNEL = $CACHE['channel'];
$PHPCMS = $CACHE['phpcms'];
$FIELD = $CACHE['field'];
unset($CACHE, $ipmatches, $CONFIG['timezone'], $CONFIG['cachedir'], $CONFIG['dbhost'], $CONFIG['dbuser'], $CONFIG['dbpw'], $CONFIG['pconnect'], $CONFIG['dbiscache'], $CONFIG['dbexpires']);
/**
unset 掉不需要用的变量.
*/
if($PHPCMS['enablebanip'] && ip_banned($PHP_IP)) showmessage($LANG['administrator_banned_this_IP']);
/**
$PHPCMS['enablebanip'] 是什么.不用说应该知道了吧.这个就是后台里面设置是否开启过滤IP访问的功能.(因为我没用过 phpcms,我是按照代码猜的,不对的请指出)从这里就看出了文本缓存也有他的作用的。 ip_banned()函数是什么呢.上菜再说:
function ip_banned($ip)
{
global $PHP_TIME; //前面定义过的.当前的时间
$ipbanneds = cache_read('banip.php');
if(!is_array($ipbanneds)) return FALSE;
foreach($ipbanneds as $v)
{
if($v['overtime'] < $PHP_TIME) return FALSE;
if($ip == $v['ip'] || preg_match("/^".str_replace('.', '[.]', $v['ip'])."$/", $ip)) return TRUE;
}
}
里面也用到了 cache_read() 这个函数,还是读banip.php 这个文件.banip.php这个文件里面存着你在后台甚至的要过滤的IP列表.
里面的逻辑比较简单.自己消化下了.不明白跟帖问
showmessage() 函数是提示出错信息封装好的一个函数. 国家化的 $LANG['administrator_banned_this_IP']这个看到了吧.这个就是读语言包里面的.这样我们就可以出好多个语言版本的程序拉.
*/
$TEMP = $MOD = $CHA = $CATEGORY = $CAT = array();
$ftp = $enableftp = $tags = $html = 0;
/**
初始化变量.这个是好习惯我们要模仿.
*/
if(!isset($mod))
{
$mod = 'phpcms'; //phpcms 是默认加载的模块
}
elseif($mod != 'phpcms')
{
isset($MODULE[$mod]) or exit($LANG['module_not_exists']); // 从缓存中读加载的模块是否开启
/**
这个写法,我十分喜欢,平时也用. xx && dd ; xx and dd ;与运算要同时两边都为真整个公式才为真,就是利用这个原理.  xx || dd ; xx or dd 或运算只要一个条件满足就不会执行下一个条件而继续执行下去. 这样写是不是很酷.  
*/
$MOD = cache_read($mod.'_setting.php'); //开始加载这个模块的一些常用配置数值。 phpcms 对应的每个模块都有一个缓存配置文件。@@ 怪不得速度那么快
@include PHPCMS_ROOT.'/languages/'.(defined('IN_ADMIN') ? $CONFIG['adminlanguage'].'/'.$mod.'_admin.lang.php' : $CONFIG['language'].'/'.$mod.'.lang.php');
/**
加载想对应的模块语言包.
*/
}
if(!isset($forward)) $forward = $PHP_REFERER; //记录前一个URL地址。估计以后下面程序有需要用这个变量
$dosubmit = isset($dosubmit) ? 1 : 0; //记录是否有表单提交过.也是以后有用
$channelid = isset($channelid) ? intval($channelid) : 0; //记录当前频道的id 如果$channelid 没有 isset 那么就为 0. intval() 十分有用。数字和数字的比较加减速度会快很多。记得哦
$skindir = PHPCMS_PATH.'templates/'.$CONFIG['defaulttemplate'].'/skins/'.$CONFIG['defaultskin']; //加载默认phpcms皮肤
if($PHPCMS['enablegzip'] && function_exists('ob_gzhandler'))
{
($CONFIG['phpcache'] || defined('SHOWJS')) ? ob_start() : ob_start('ob_gzhandler');
}
else
{
$PHPCMS['enablegzip'] = 0;
ob_start();
}
/**
$PHPCMS['enablegzip'] 这个变量就是存在于 phpcms_setting.php 文件里。上面已经说过了。每个模块都有相对应的模块配置缓存文件(是从数据库copy过来的信息) 这个变量标致 是否开启 压缩传输。
压缩传输,听名字就知道。就是把数据按照一定的算法压缩小罗。然后再传送到客户端。这样就可以在有限的带宽中传输更大的数据拉。当然速度快了不少。压缩的数据到了你的浏览器,它就自动解压缩,老版本的一些浏览器不支持解压缩哦。不过现在还有谁用很久的浏览器呢。用法很简单的:看上面就知道:
首先判断下,看客户老大们是否在后台选择了这个模块的压缩传输(如果是的话。自然的已经加载到了相对应的文本缓存文件里面拉) 标致:$PHPCMS['enablegzip'] 判断 回调函数 ob_gzhandler 是否开启, ob_gzhandler 其实不算是个函数。看手册说明。 就这么简单。它只是一个专门给 ob_start() 做回调使用的一个参数函数。详细请看下手册。别偷懒哦,在程序开头ob_start('ob_gzhandler')就算是开始压缩传输了;判断完了 如果为真。就继续下面的代码:

($CONFIG['phpcache'] || defined('SHOWJS')) ? ob_start() : ob_start('ob_gzhandler');
看代码phpcms 是这样的: 如果用户在后台开启了压缩传输。而用户又开启了 页面缓存。那么就默认不使用压缩传输了。我也不知道为什么这样设计。我测试了下。后台开启压缩传输。又同时又使用页面缓存。没发现有什么问题。@@
如果没开启压缩传输,那么我们就ob_start(); 使用session 之前必须要 ob_start() ; 而且在ob_start() 之前不能有任何的 头文件发送和输出。比如:echo header等要不会出错的哦。
*/
$_userid = 0;
$_username = '';
$_groupid = 3;
$_arrgroupid = array();
$phpcms_auth = getcookie('auth');
/**
$_userid,$_username,$_groupid 这几个记录用户信息的变量初始化,不初始化危险就太大了。@@ 如果给人家$_GET一个 _userid 变量过来。那么就会把我们这个变量覆盖。但是我们如果给这几个变量一个值,
那么按照就近原则。就算你GET个变量过来。你也一样改不了我原来的变量值。大家好好自己想下。就会明白了。
getcookie() 这个自定义函数在 global.func.php文件里定义的。上菜:
function getcookie($var)
{
global $CONFIG;
$var = $CONFIG['cookiepre'].$var;
return isset($_COOKIE[$var]) ? $_COOKIE[$var] : FALSE;
}
这个函数用来提取我们设置的cookie 值. $CONFIG['cookiepre'] config.inc.php 文件里面设置,cookie 名的前缀. 函数很简单。一看就明白不说了。
*/
if($phpcms_auth)
{
$phpcms_auth_key = md5($PHPCMS['authkey'].$_SERVER['HTTP_USER_AGENT']);
list($_userid, $_password, $_answer) = $phpcms_auth ? explode("\t", phpcms_auth($phpcms_auth, 'DECODE')) : array(0, '', '');
/**
list() = array(); 用户大家自己试下。 意会下
phpcms_auth() 是加密和解密 函数, 因为cookie 是存在于客户端。十分危险呀。 你看连用户的密码也存在cookie 不加密能行吗。但是呢加密后又要能解密。因为用户名和用户密码我们往下操作要
获取的。 这个函数存在于 global.func.php 文件里面。大家想了解这个算法的自己去看下吧。挺简单的。 其实就是围绕着 $phpcms_auth_key 这个变量来加密解密和discuz 的cookie 机制差不。
$phpcms_auth_key = md5($PHPCMS['authkey'].$_SERVER['HTTP_USER_AGENT']); 看$PHPCMS['authkey'], 估计后台有个 cookie 加密值让你填,然后以这个值和 $_SERVER['HTTP_USER_AGENT'](系统信息)
*/
$_userid = intval($_userid);
if($_userid < 0) $_userid = 0; //读出的cookie 的用户id 如果是 小于0
if($_userid) //如果 cookie 保存的这个uid 存在,那么开始按照这个ID来查数据库用户表 来取出用户信息
{
$memberinfo = $db->get_one("SELECT username,password,groupid,arrgroupid,email,chargetype,begindate,enddate,money,point,credit,newmessages FROM ".TABLE_MEMBER." WHERE userid=$_userid LIMIT 0,1");
/**
phpcms 封装好的数据库类,下篇开讲这个大家就大概看行了。 大家看下 select sql语句。 也可以学习下。 首先最好不要使用 select * from xx 的 * 形式,除非你想获取所有字段的记录。只罗列你要的字段。这样在数据量大的查询中。速度明显上去。 常量: TABLE_MEMBER 定义了表名。这样做有什么好处呢?想都知道了,为了以后变更表名方便而定义为常量。这个东西那里来的。估计在一个文件里面定义好的。遇到了再讲吧懒得找了。
*/
if($memberinfo && $memberinfo['password'] == $_password) //用查询出来的密码和 cookie 中存在的密码想对比.为了在效率: 在比较前 先判断查询是否成功先。很多phper往往忽略。
{
if($memberinfo['groupid'] == 2) //如果用户属于的组的ID 为 2 那么这个用户是被管理员禁止访问的了。
{
mkcookie('auth', ''); // 清除cookie
showmessage($LANG['userid_banned_by_administrator']); //提示出错菜单
}
@extract($memberinfo, EXTR_PREFIX_ALL, ''); //又来这招,应该明白了吧各位老大:把字段 变成 我们能直接使用的变量
unset($memberinfo, $_password, $_answer);
$_arrgroupid = $_arrgroupid ? array_filter(explode(',', $_arrgroupid)) : array(); //把 字段为 arrgroupid 值为 FALSE 过滤掉。array_filter()不带回调参数的用法,请看手册。
}
else
{
mkcookie('auth', '');
}
/**
经过上面的读cookie 和查数据库用户信息后。当确定这个用户信息是合法以后。就会自动登陆了。比如phpchina论坛。当你登陆后没注销。下次访问的时候还是登陆状态。就是这个原理。记得模仿哦
这里详细解释下 mkcookie ()函数 上菜:
function mkcookie($var, $value = '', $time = 0)
{
global $CONFIG,$PHP_TIME;
$time = $time > 0 ? $time : (empty($value) ? $PHP_TIME - 3600 : 0);
$s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;
$var = $CONFIG['cookiepre'].$var;
return setcookie($var, $value, $time, $CONFIG['cookiepath'], $CONFIG['cookiedomain'], $s);
}
$time 为cookie 的存活时间: 如果为 0 就是关闭浏览器 cookie 就自动失效 , $PHP_TIME 在前面定义了:当前时间。 $PHP_TIME -3600 减去3600秒。就是一个小时前的意思,那肯定是设置cookie 失效的意思了。
$s 变量是 获取 是否开启SSL安全传输的标致。 cookie 有一个参数是ssl传输的。如果服务器已经opensll 了那么我们肯定不能浪费这么好的安全资源了。
$var cookie名的前缀,主要防止混淆。
$CONFIG['cookiedomain'] 这个家伙在 config.inc.php里面已经配置的了。定义为: '/' 意思就是说 在当前域 的所有目录的PHP程序都能访问这个COOKIE ,还有限制目录访问COOKIE 的弄法。具体请看 setcookie () 函数手册上说明。
*/

}
}
unset($db_class, $db_file, $phpcms_auth, $phpcms_auth_key, $memberinfo);
[/php]

下章我就分析 PHPCMS 的数据库操作类文件和 PHPCMS的文本缓存机制.:victory: 希望大家继续支持哦第一

这个讲 phpcms 的数据库类 和 phpcms 的文本缓存的实现.看了看

都是很简单的东西.大家看着我注释慢慢看吧.慢慢理解,最好能装了PHPCMS 在来看.因为这样可以看下它的数据库结构信息.可以帮助理解.

不明白的继续问吧.

首先是数据库类,phpcms 的数据库 分mysql 和mssql 版本. MSSQL 版本的我就不说了. 他们主要的sql 语句不同点就在于我们分页常用到的 limit 语句.所以.在 mssql 数据库类驱动里.他做了个挺好的封装.让MYSQL 和MSSQL在SQL语句方面的差异性就很小了.可以说PHPCMS你可以随便转换数据库只要在代码中换下数据库类驱动就行.
[php]
<?php
/**
mysql数据库类。写得比较简单。也没什么好说的。大家自己看下理解下。
然后就可以跳过了。
*/
defined('IN_PHPCMS') or exit('Access Denied');
/**
这个东西是不是很熟呀。对了。在上一章已经讲过了。也已经在上一章的common.inc.php 启动文件里面定义了 IN_PHPCMS 所以在以下的PHP文件里都检测下是否是人为”跳墙“进来的。是就中断
*/
/**
* Mysql 数据库类,支持Cache功能
*/
class db_mysql
{
/**
* MySQL 连接标识
* @var resource
*/
var $connid;
/**
* 整型变量用来计算被执行的sql语句数量
* @var int
*/
var $querynum = 0;
/**
* 数据库连接,返回数据库连接标识符
* @param string 数据库服务器主机
* @param string 数据库服务器帐号
* @param string 数据库服务器密码
* @param string 数据库名
* @param bool 是否保持持续连接,1为持续连接,0为非持续连接
* @return link_identifier
*/
function connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect = 0)
{
global $CONFIG;
$func = $pconnect == 1 ? 'mysql_pconnect' : 'mysql_connect';
/**
mysql_pconnect() 为常连接。它和mysql_connect 的区别是 前者在多进程的WEB服务器上效率比较好。但也有瑕疵就是在有关事务和数据表锁方面。详情请查看自己的手册。
*/
if(!$this->connid = @$func($dbhost, $dbuser, $dbpw))
{
$this->halt('Can not connect to MySQL server');
}
// 当mysql版本为4.1以上时,启用数据库字符集设置
if($this->version() > '4.1' && $CONFIG['dbcharset'])
{
mysql_query("SET NAMES '".$CONFIG['dbcharset']."'" , $this->connid);
}
// 当mysql版本为5.0以上时,设置sql mode,mysql5数据库带了字符集模式。设置下就好
if($this->version() > '5.0')
{
mysql_query("SET sql_mode=''" , $this->connid);
}
if($dbname)
{
if([email=!@mysql_select_db($dbname]!@mysql_select_db($dbname[/email] , $this->connid))
{
$this->halt('Cannot use database '.$dbname);
}
}
return $this->connid;
}
/**
* 选择数据库
* @param string 数据库名
*/
function select_db($dbname)
{
return mysql_select_db($dbname , $this->connid);
}
/**
* 执行sql语句
* @param string sql语句
* @param string 默认为空,可选值为 CACHE UNBUFFERED
* @param int Cache以秒为单位的生命周期
* @return resource
*/
function query($sql , $type = '' , $expires = 3600, $dbname = '')
{
$func = $type == 'UNBUFFERED' ? 'mysql_unbuffered_query' : 'mysql_query';
/**
mysql_unbuffered_query 效率更好。节省内存 看手册
*/
if(!($query = $func($sql , $this->connid)) && $type != 'SILENT')
{
$this->halt('MySQL Query Error', $sql);
}
$this->querynum++;
return $query;
}
/**
* 执行sql语句,只得到一条记录
* @param string sql语句
* @param string 默认为空,可选值为 CACHE UNBUFFERED
* @param int Cache以秒为单位的生命周期
* @return array
*/
function get_one($sql, $type = '', $expires = 3600, $dbname = '')
{
$query = $this->query($sql, $type, $expires, $dbname);
$rs = $this->fetch_array($query);
$this->free_result($query);
return $rs ;
}
/**
* 从结果集中取得一行作为关联数组
* @param resource 数据库查询结果资源
* @param string 定义返回类型
* @return array
*/
function fetch_array($query, $result_type = MYSQL_ASSOC)
{
return mysql_fetch_array($query, $result_type);
}
/**
* 取得前一次 MySQL 操作所影响的记录行数
* @return int
*/
function affected_rows()
{
return mysql_affected_rows($this->connid);
}
/**
* 取得结果集中行的数目
* @return int
*/
function num_rows($query)
{
return mysql_num_rows($query);
}
/**
* 返回结果集中字段的数目
* @return int
*/
function num_fields($query)
{
return mysql_num_fields($query);
}
/**
* @return array
*/
function result($query, $row)
{
return @mysql_result($query, $row);
}
function free_result($query)
{
return mysql_free_result($query);
}
/**
* 取得上一步 INSERT 操作产生的 ID
* @return int
*/
function insert_id()
{
return mysql_insert_id($this->connid);
}
/**
* @return array
*/
function fetch_row($query)
{
return mysql_fetch_row($query);
}
/**
* @return string
*/
function version()
{
return mysql_get_server_info($this->connid);
}
function close()
{
return mysql_close($this->connid);
}
/**
* @return string
*/
function error()
{
return @mysql_error($this->connid);
}
/**
* @return int
*/
function errno()
{
return intval(@mysql_errno($this->connid)) ;
}
/**
mysql_errno() 函数也挺好使的哦。自己试下
*/
/**
* 显示mysql错误信息
*/
function halt($message = '', $sql = '')
{
exit("MySQL Query:$sql <br> MySQL Error:".$this->error()." <br> MySQL Errno:".$this->errno()." <br> Message:$message");
}
}
?>
[/php]

PHPCMS的文本缓存实现:

[php]
<?php
defined('IN_PHPCMS') or exit('Access Denied');
/**
这个文件里面全是有关生成文本缓存的函数。文本缓存是个好东西。一般的项目,我们用不着内存缓存 : memcached ,文本搞定。
原理是这样的: 我们在后台是不是可以设置很多有关网站的参数。而这些参数很多都是固定的。就不变化的。都存到咱的数据库上。而我们程序那里呢
每次都要访问数据库读出参数来进行我们程序中的操作。首先数据库查询是个很耗硬盘IO资源的一个东西,所以文本缓存刚好能减轻数据库那边的承重。
我们在程序开始就把数据库里面的配置都转化为数组 等 放到 php文件里面。这样我们可以直接访问php文件而不用每次都访问数据库了。 php文本缓存其实成了
我们程序和数据库的一个中间件。 所以我们自己写自己的文本缓存的时候其实要实现的很简单: 读数据库 -> 写到PHP文件 -> 程序中include
来吧。开始文本缓存学习
*/
function cache_all() //生成所有缓存的总操作函数
{
cache_table(); //生成所有的数据库表名,表名是根据数据库里面当前的表明而生成。请看这个函数的详细分析
require_once PHPCMS_CACHEDIR.'table.php'; //包含表常量
cache_common();
cache_member_group();
$modules = cache_module();
$channelids = cache_channel(0);
$keyids = array_merge($modules, $channelids);
foreach($keyids as $keyid)
{
$catids = cache_categorys($keyid);
if(is_array($catids))
{
foreach($catids as $catid)
{
cache_category($catid);
}
}
}
cache_type(0);
return TRUE;
}
function cache_common()
{
global $db;
$query = $db->query("SELECT module,name,iscore,iscopy,isshare,moduledir,moduledomain FROM ".TABLE_MODULE." WHERE disabled=0"); //查询所有能用的模块
while($r = $db->fetch_array($query))
{
$r['linkurl'] = '';
if($r['module'] != 'phpcms' && $r['iscopy'] == 0) $r['linkurl'] = linkurl($r['moduledomain'] ? dir_path($r['moduledomain']) : $r['moduledir'].'/');
//如果模块存在目录的就取它目录地址
unset($r['moduledomain']);
$key = $r['module'];
$data[$key] = $r;
}
$CACHE['module'] = $data; //存到缓存数组,等一下一起把 $CACHE 数组写到文本里去
$data = array();
$query = $db->query("SELECT channelid,module,channelname,channeldir,channeldomain,channelpic,introduce,style,islink,linkurl,cat_html_urlruleid,item_html_urlruleid,special_html_urlruleid,cat_php_urlruleid,item_php_urlruleid,special_php_urlruleid FROM ".TABLE_CHANNEL." WHERE disabled=0 ORDER by listorder"); //罗列能用的频道列表
while($r = $db->fetch_array($query))
{
$r['linkurl'] = linkurl($r['linkurl']);
$key = $r['channelid'];
$data[$key] = $r;
}
$CACHE['channel'] = $data; //存到缓存数组
$data = array();
$r = $db->get_one("SELECT setting FROM ".TABLE_MODULE." WHERE module='phpcms'");
$CACHE['phpcms'] = unserialize($r['setting']);
//查询 phpcms这个模块的设置信息,大家可以看下数据库这个表内容。setting 字段里面的信息是经过serialize 函数窜行化的。
//所以取出的内容要unserialize 反窜行.我是挺喜欢使用serialize 函数的。他可以实现把一个数组存到数据库或把一个对象存到数据库。或是拿来GET传递都行。
//太强了。大家可以试用下。可能你项目某个地方需要用到哦。
$fields = array();
$result = $db->query("SELECT * FROM ".TABLE_FIELD." ORDER BY fieldid"); //下栽模块的信息,请自己看下这个表的数据就明白
while($r = $db->fetch_array($result))
{
$tablename = $r['tablename'];
$fields[$tablename] .= ','.$r['name'];
}
$CACHE['field'] = $fields;
cache_write('common.php', $CACHE); //开始把$CACHE 数组写到 common.php 这个文本缓存里。大家可以自己去打开这个文件看下内容。一切了然
return $CACHE;
}
function cache_update($action='')//更新文本缓存。最好在后台操作使用。因为PHP的文件flock 文件锁在某些平台使用不是很好。会出现多用户同写一个文件从而破坏缓存文件
{
global $db;
$data=array();
switch($action)
{
case 'keylink';
$query=$db->query("SELECT linktext,linkurl FROM ".TABLE_KEYLINK." where passed=1");
while($r=$db->fetch_array($query)){
$data[]=$r;
}
break;
case 'reword';
$query = $db->query("SELECT word,replacement FROM ".TABLE_REWORD." where passed=1");
while($r = $db->fetch_array($query))
{
$data[]=$r;
}
break;
default:
$actions = array('keylink','reword');
array_map('cache_update', $actions);
return TRUE;
}
cache_write('cache_'.$action.'.php', $data);
return $data;
}
function cache_table()
{
global $db,$CONFIG;
$query = $db->query("SHOW TABLES FROM `".$CONFIG['dbname']."`");
/**
显示数据库里面的所有表名
*/
while($r = $db->fetch_row($query))
{
$table = $r[0];
if(preg_match("/^".$CONFIG['tablepre']."/i", $table)) //寻找表前缀等于 $CONFIG['tablepre'] (在config.inc.php里设置) @@表前缀还有这个作用 嘿嘿
{
$tablename = str_replace($CONFIG['tablepre'], 'table_', $table);
$data[$tablename] = $table; // $data['table_xx'] = xx; 形式 只能意会下了
}
}
$db->free_result($query); //$db->free_result() 这个类方法其实是掉用了函数:mysql_free_result() 函数 主要是为了清除数据库大的查询而占用的内存。还是有必要的哦
if(!is_dir(PHPCMS_CACHEDIR)) // 常量 PHPCMS_CACHEDIR 在 common.inc.php 里面定义的。大家不记得了去看看吧。是存放phpcms 缓存目录的路径,这里意思是:如果缓存目录不存在
{
dir_create(PHPCMS_CACHEDIR); //如果缓存目录不存在那么就创建
dir_create($CONFIG['templatescachedir']); //创建编译后的PHP模板目录,有关phpcms模板引擎编写。在下一章合适就开讲
/**
dir_create() 函数为创建 目录函数。PHPCMS自己封装的,刚看了下。phpcms 挺强。这个函数还可以通过ftp 来创建目录。这样就可以解决一些 开启了安全模式下的服务器对于创建目录等出现的问题
因为涉及到PHP FTP 知识。所以打算讲解到下面再说。
*/
}
cache_write('table.php', $data , 'constant'); //很多朋友说找不到phpcms 表常量在那里定义的。就是在这里。
/**
cache_write() 函数在global.func.php里面定义的。是把 已经从数据库取出来的数组信息写到 PHP文本上去。@@文本缓存关键的一步 废话少说上菜:
function cache_write($file, $string, $type = 'array')
{
if(is_array($string)) //检测 $string 内容是字符窜的呢还是数组的,是数组的那就继续 ..
{
$type = strtolower($type);
if($type == 'array')//然后再判断这个函数的模式标致 ,是否为数组模式,默认为数组模式
{
$string = "<?php\n return ".var_export($string,TRUE).";\n?>";
//这个太关键了。因为我们把数据库的信息写到文本上去的时候。是以符合PHP语法的格式写进去的。为什么呢?@@ 十分废话,因为如果不是以PHP格式写到文件里面去
那么这个PHP文件怎么能给我们include 进程序运行调用呢? 呵呵。 知道这一点就真的明白文本缓存的实现了。忒简单。 这里使用了个小技巧:使用了 var_export() 函数
这个函数会返回一个变量的字符窜形式。这个函数太有帮助了。如果没有这个函数,我们还要自己想办法实现呢。自己写一次文本缓存就明白了。会碰到这个问题的。 '\n' 这个是
文本文件的换行。初学者 别把<br> 和 '\n' 搞混罗。 一个是html 的 一个是文本文件的
}
elseif($type == 'constant') //以内容形式
{
$data='';
foreach($string as $key => $value) $data .= "define('".strtoupper($key)."','".addslashes($value)."');\n";
$string = "<?php\n".$data."\n?>";
如果以内容形式的话。就不是写数组到文本里面了。而是把内容都定义成常量。
}
}
$strlen = file_put_contents(PHPCMS_CACHEDIR.$file, $string);//file_put_contents()函数 是PHP5才支持的 效率最好。建议使用
chmod(PHPCMS_CACHEDIR.$file, 0777); 设置目录 为可读可写可执行
return $strlen; //返回写到文本的字节数
}

再说多一个读 缓存文件的操作函数 :上菜
function cache_read($file, $mode = 'i')
{
$cachefile = PHPCMS_CACHEDIR.$file;
if(!file_exists($cachefile)) return array();
return $mode == 'i' ? include $cachefile : file_get_contents($cachefile);
}
读缓存其实就是 include php缓存文件。 讲完走人
*/
return $data;
}
/**
phpcms 的所有数据库表名 都用根据数据库当前的表名来用常量来进行定义。我认为这样设计不是很好。不够灵活:比如如果我们更改数据库的一个表名的话。
那么会出现找不到表的错误信息。而且想要修复还很麻烦。就是说不能随便更改表名了。不推荐大家这样写。我们可以把表名都定义在一个PHP文件里面。这样我们以后
要改某个表名,就很方便了。
*/
function cache_module($module = '')
{
global $db;
if($module)
{
$r = $db->get_one("SELECT setting,module,name,iscopy,moduledir,moduledomain FROM ".TABLE_MODULE." WHERE module='$module'"); //模块具体信息
if($r['setting'])
{
$setting = unserialize($r['setting']); //讲过了反窜行。因为里面信息是窜行化后再存到数据库的
}
$setting['name'] = $r['name'];
$setting['moduledir'] = $r['moduledir'];
$setting['moduledomain'] = $r['moduledomain'];
$setting['linkurl'] = '';
if($r['module'] != 'phpcms' && $r['iscopy'] == 0)
{
$setting['linkurl'] = linkurl($r['moduledomain'] ? dir_path($r['moduledomain']) : $r['moduledir'].'/');
cache_categorys($module);
}
unset($r['moduledomain']);
cache_write($module.'_setting.php', $setting);
return $setting;
}
else
{
$query = $db->query("SELECT module FROM ".TABLE_MODULE." WHERE disabled=0 ORDER by moduleid");
while($r = $db->fetch_array($query))
{
cache_module($r['module']);
$modules[] = $r['module'];
}
return $modules;
}
}
function cache_channel($channelid=0)
{
global $db;
if($channelid)
{
$data = $db->get_one("SELECT * FROM ".TABLE_CHANNEL." WHERE channelid=$channelid");
if($data && !$data['islink'])
{
if($data['setting'])
{
$setting = unserialize($data['setting']);
unset($data['setting']);
$data = is_array($setting) ? array_merge($data, $setting) : $data;
}
$data['linkurl'] = linkurl($data['linkurl']);
cache_write('channel_'.$channelid.'.php', $data);
cache_categorys($channelid);
return $data;
}
}
else
{
$query = $db->query("SELECT channelid FROM ".TABLE_CHANNEL." WHERE islink=0 AND disabled=0 ORDER by channelid");
while($r = $db->fetch_array($query))
{
cache_channel($r['channelid']);
$channelids[] = $r['channelid'];
}
return $channelids;
}
}
function cache_categorys($keyid)
{
global $db,$PHPCMS,$CHANNEL;
$urlpre = '';
if(is_numeric($keyid))
{
$keyid = intval($keyid);
$module = $CHANNEL[$keyid]['module'];
$sql = " channelid=$keyid ";
}
else
{
$sql = " module='$keyid' ";
}
$catids = $data = array();
$query = $db->query("SELECT module,channelid,catid,catname,style,introduce,catpic,islink,catdir,linkurl,parentid,arrparentid,parentdir,child,arrchildid,items,itemordertype,itemtarget,ismenu,islist,ishtml,htmldir,prefix,urlruleid,item_prefix,item_html_urlruleid,item_php_urlruleid FROM ".TABLE_CATEGORY." WHERE $sql ORDER by listorder,catid");
while($r = $db->fetch_array($query))
{
$r['linkurl'] = str_replace($PHPCMS['index'].'.'.$PHPCMS['fileext'], '', $r['linkurl']);
$r['linkurl'] = $urlpre ? preg_replace("|^".$urlpre."|", '', $r['linkurl']) : linkurl($r['linkurl']);
$catid = $r['catid'];
$data[$catid] = $r;
$catids[] = $catid;
}
if($data) cache_write('categorys_'.$keyid.'.php', $data); //写缓存罗。
return $catids;
}
function cache_category($catid)
{
global $db,$PHPCMS;
if(!$catid) return FALSE;
$data = $db->get_one("SELECT * FROM ".TABLE_CATEGORY." WHERE catid=$catid");
$setting = unserialize($data['setting']);
unset($data['setting']);
$data = is_array($setting) ? array_merge($data, $setting) : $data;
$data['linkurl'] = linkurl(str_replace($PHPCMS['index'].'.'.$PHPCMS['fileext'], '', $data['linkurl']));
cache_write('category_'.$catid.'.php', $data);
return $data;
}
function cache_type($keyid=0)
{
global $db;
if($keyid)
{
$result = $db->query("SELECT * FROM ".TABLE_TYPE." WHERE keyid='$keyid'");
$data = array();
while($r = $db->fetch_array($result))
{
$r['introduce'] = $r['introduce']? $r['introduce']:' ';
$data[$r['typeid']] = $r;
}
if($data)
{
cache_write('type_'.$keyid.'.php', $data);
}
return $data;
}
else
{
$modules = array();
$query = $db->query("SELECT module FROM ".TABLE_MODULE." WHERE disabled=0 ORDER by moduleid");
while($r = $db->fetch_array($query))
{
$modules[] = $r['module'];
}
$channelids = array();
$query = $db->query("SELECT channelid FROM ".TABLE_CHANNEL." WHERE islink=0 AND disabled=0 ORDER by channelid");
while($r = $db->fetch_array($query))
{
$channelids[] = $r['channelid'];
}
$modulechannels = array_merge($modules,$channelids);
foreach($modulechannels as $m)
{
$result = $db->query("SELECT * FROM ".TABLE_TYPE." WHERE keyid='$m'");
$TYPE = array();
while($r = $db->fetch_array($result))
{
$r['introduce'] = $r['introduce']? $r['introduce']:' ';
$TYPE[$r['typeid']] = $r;
}
cache_write('type_'.$m.'.php',$TYPE);
}
return $modulechannels;
}
}
function cache_member_group()
{
global $db;
$query = $db->query("SELECT * FROM ".TABLE_MEMBER_GROUP." ORDER BY groupid"); //用户组信息
while($r = $db->fetch_array($query))
{
$groupid = $r['groupid'];
cache_write('member_group_'.$groupid.'.php', $r);
$data[$groupid] = $r;
}
cache_write('member_group.php', $data); //明白了吧。写缓存罗
return $data;
}
function cache_banip()
{
global $db,$PHP_TIME;
$result = $db->query("SELECT ip,overtime FROM ".TABLE_BANIP." WHERE ifban=1 and overtime>=$PHP_TIME order by id desc ");
while($r = $db->fetch_array($result))
{
$data[] = array('ip'=>$r['ip'],'overtime'=>$r['overtime']);
}
$db->free_result($result);
cache_write('banip.php', $data);
return $data;
}
?>
[/php]

[php]
<?php
/**
函数 template函数是在global.func.php 里面定义的。 在前面的phpcms 的首页 index.php 里就见到了。 用法: include template() 用法很熟, 呵呵其实和 dz 的模板引擎一样的用法。 但DZ的模板引擎比 PHPCMS 的简单很多,因为没有用到模板的标签技术。 大家有空可以研究下DZ的模板引擎。这里不说。 好分析下上面这个 模板的主要函数吧。 他的作用是返回编译好的模板文件路径。也就是把模板 X.html(模板文件) 用正则替换成 x.php(编译后的PHP文件).然后使用 include 函数。懂了吧! php的模板引擎都一个鸟样。 然后剩下的就是正则的东西了。等下再说。
*/
function template($module = 'phpcms', $template = 'index')
{
global $CONFIG;
$compiledtplfile = $CONFIG['templatescachedir'].$module.'_'.$template.'.tpl.php';
/**
因为phpcms是分模块来存放模板文件。所以 template 函数有两个参数: 第一个就是模块目录名,第二个就是此模块里面的模板文件名.
$CONFIG['templatescachedir'] 这个是放编译后php文件存放的目录。在config.inc.php 站点配置文件里面定义的自己去看。 这样就取得了模板编译后的php文件路径。
*/
if($CONFIG['templaterefresh']) //$CONFIG['templaterefresh'] 在 config.inc.php里面配置了。默认是1 。是更新模板开关。如果你设置为0 那么模板更新了。程序也不会更新。
{
$tplfile = PHPCMS_ROOT.'/templates/'.$CONFIG['defaulttemplate'].'/'.$module.'/'.$template.'.html';
/**
和上面那句意思差不多。$CONFIG['defaulttemplate'] 是默认模板目录 。这句是获取你要的那个模块和里面的那个模板文件的路径(@@获取没编译前的模板文件)
*/
if(!file_exists($compiledtplfile) || @filemtime($tplfile) > @filemtime($compiledtplfile))
{
/**
我把文件编译成了php文件。那么模板改变了。 php文件总得也改变吧。要不你修改了模板后。站还是以前那个样子没变那有什么意思呢。
首先判断模板编译文件是否存在。如果不存在那么后边那个条件不用判断了。 因为编译文件都不存在。程序肯定运行不了拉。(因为其实我们主要是运行编译后的那个php文件,模板文件是html的运行个P呀)
或 后边那个 @filemtime($tplfile) > @filemtime($compiledtplfile) 很容易就明白: 函数 filetime() 判断文件最近修改的时间,返回Unix 时间戳。 如果模板文件的修改时间 大于 编译文件。 那么证明 模板文件 在 编译文件生成后 还进行了修改。那么我们是不是还要在更新次编译文件呀 ,那是肯定的拉。 所以继续执行下去。
*/
require_once PHPCMS_ROOT.'/include/template.func.php'; // 加载编译函数
template_refresh($tplfile, $compiledtplfile);// 这个就是模板的 编译启动函数 ,带动一系列的模板编译函数来最终生成模板编译文件。
}
}
return $compiledtplfile; // 返回 模板编译后的PHP文件路径。
}

defined('IN_PHPCMS') or exit('Access Denied');
function template_compile($module,$template) //和下面那个一样是编译模板启动函数。不过两函数的参数不一样,按照上下文意思。这个函数是为了配合批量编译模板而写的。第一个是模块目录名,第二是模板文件名,解释同下。请看下面那个
{
global $CONFIG;
$content = file_get_contents(PHPCMS_ROOT.'/templates/'.$CONFIG['defaulttemplate'].'/'.$module.'/'.$template.'.html');
$content = template_parse($content);
$compiledtplfile = $CONFIG['templatescachedir'].$module.'_'.$template.'.tpl.php';
$strlen = file_put_contents($compiledtplfile, $content);
@chmod($compiledtplfile, 0777);
return $strlen;
}
function template_refresh($tplfile,$compiledtplfile) //模板编译启动函数。 参数 第一个是 模板文件名 第二个是 编译后的php文件名
{
$str = file_get_contents($tplfile); //使用了php5 的最爽函数:file_get_contents() 获取文件的内容 。
$str = template_parse($str); /*然后 使用 template_parse() 函数来对文件内容进行替换。比如把一些我们自己定义的语句:{if xx > xx} 正则替换成 <?php if(xx > xx){?>具体看下面*/
$strlen = file_put_contents($compiledtplfile, $str);//编译完成后。把内容写到我们的 那个所谓的编译PHP文件。
@chmod($compiledtplfile, 0777); //别忘了设置下权限。
return $strlen; //返回 写到编译文件里的内容字大小节数,下面我们看下 template_parse() 函数
}
function template_module($module)//这个很有用。批量编译某模块目录下的模板文件
{
global $CONFIG;
$files = glob(PHPCMS_ROOT.'/templates/'.$CONFIG['defaulttemplate'].'/'.$module.'/*.html');
/*
glob 函数 取得 在此路径下的所有 *.html 以html为扩展名的文件列表。 具体看手册。
**/
if(is_array($files))
{
foreach($files as $tpl)
{ //开始批量
$template = str_replace('.html', '', basename($tpl));
// 获取模板文件名。以次来做编译后的PHP文件名
template_compile($module, $template); //这个函数上面讲过了。看上面
}
}
return TRUE;
}
function template_cache() //这个是比上面那个更大批量的生成。因为 $MODULE 里面的所有模块 $MODULE 存在于缓存文件模板。前面已经说了。自己看吧
{
global $MODULE;
foreach($MODULE as $module=>$m)
{
template_module($module);
}
return TRUE;
}
/**
哇,别给它吓到。其实都是些简单的正则。只要知道他们是干什么的就好办了。 在模板里面我们使用了些自己定义的标签呀。语句呀。这些东西不是PHP标准语法。所以根本不可能运行。
那么怎么办呢。通过正则对我们自己定义的语法 。转变成标准的PHP语法。然后写到我们的 PHP文件里。所以下面正则都是对我们自己定义的语法进行编译。
下面讲解下正则。按照本人水平解释。大概的解释了下。不过不懂正则的请自己百度学下。有不对的地方。大家可以讨论下。@@ 还没懂 preg_replace() 函数的同学兄弟朋友姐妹。请自己看手册。
*/
function template_parse($str)
{
$str = preg_replace("/([\n\r]+)\t+/s","[url=file://\\1]\\1",$str[/url]);
// 用 \n\r 过滤掉 tab制表符, [url=file://\\1]\\1[/url] 是逆向引用了第一个括号里面\n换行\r换页匹配的文本 (@@解释好要口,最好自己看下正则知识 /s为修正符。自己百度吧)
$str = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}",$str);
// 以 {xx} 来替换 <!--{xx}--> {xx}在下面肯定还要进行第二次正则替换,要不是不能在PHP里面运行的。 .+? 和 .+ 一个是懒惰 一个是贪婪。 看名字就知道。不知道的 百度吧: 正则贪婪。
$str = preg_replace("/\{template\s+(.+)\}/","\n<?php include template([url=file://\\1]\\1[/url]); ?>\n",$str);
/*把模板里面的 {template 'xx','jj'} 编译成PHP标准写法:<?php include template('xx','jj') ?> 大家可能一看就明白了: include template() 这个在那里见过。对了。这个在PHP里也可以运行的。因为 template() 函数不是定义了吗。*/
$str = preg_replace("/\{include\s+(.+)\}/","\n<?php include [url=file://\\1]\\1[/url]; ?>\n",$str);
/* 模板里面的 {include xx.php} 编译成 PHP文件里的 <?php include xx.php?>**/
$str = preg_replace("/\{php\s+(.+)\}/","\n<?php [url=file://\\1?]\\1?>\n",$str[/url]);
/* 模板里面的 {php xxxx} 编译成 <?php xxxx?> 大家也应该明白了。 xxxx 肯定是PHP的标准语法拉。 所以phpcms模板语句: {php } 就是用来给你在模板里写要运行的PHP语句。在smarty 里也有这功能**/
$str = preg_replace("/\{if\s+(.+?)\}/","<?php if([url=file://\\1]\\1[/url]) { ?>",$str);
/* 这个就更简单了。 把模板里面的{if xxxx} 编译成 <?php if(){?> 看这样一步一步的把一些自己定义的语句编译成PHP的标准语法。这个就叫模板引擎了。**/
$str = preg_replace("/\{else\}/","<?php } else { ?>",$str);
/* {else } 转 <?php } else {?>**/
$str = preg_replace("/\{elseif\s+(.+?)\}/","<?php } elseif ([url=file://\\1]\\1[/url]) { ?>",$str);
/* {elseif } 转 <?php } elseif {?>**/
$str = preg_replace("/\{\/if\}/","<?php } ?>",$str);
/* {/if} 转 <?php }?> phpcms 模板语法有: {/if}的哦。大家别忘了,要不 php肯定运行不了**/
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/","<?php if(is_array([url=file://\\1]\\1[/url])) foreach([url=file://\\1]\\1[/url] AS [url=file://\\2]\\2[/url]) { ?>",$str);
/* 下面就是循环了。模板里用{loop xx jj} 其实编译成了PHP的 foreach(xx AS jj) 这样大家都会用了吧**/
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/","\n<?php if(is_array([url=file://\\1]\\1[/url])) foreach([url=file://\\1]\\1[/url] AS [url=file://\\2]\\2[/url] => [url=file://\\3]\\3[/url]) { ?>",$str);
/* 这句和上面也差不多。不过是多了个取出数组的标名 {loop xx jj yy} 成 foreach(xx as jj=> yy)**/
$str = preg_replace("/\{\/loop\}/","\n<?php } ?>\n",$str);
/* 循环结束别忘了 {/loop} 对应PHP的 <?php }?>**/
$str = preg_replace("/\{tag_([^}]+)\}/e", "get_tag('\\1')", $str);
/* {tag_xx} 替换为 get_tag('xx') get_tag() 函数是自己定义的函数,因为phpcms 的模板引擎应用了标签功能。这个函数就是为了调用标签的。**/
$str = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(([^{}]*)\))\}/","<?php echo [url=file://\\1;?]\\1;?>",$str[/url]);
/* {xxx(jj)} 这么个奇怪的东西。因为奇怪所以我找了下PHPCMS的模板文件。找了几个文件都没发现这个怪物。大家有谁找到的说下我去看下。怕是我理解错了正则。先谢了**/
$str = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(([^{}]*)\))\}/","<?php echo [url=file://\\1;?]\\1;?>",$str[/url]);
/* {$xxx(wwsd)} 专换成 <?php echo xxx(wwsd)?> 当然了 xxx() 是程序中定义过的函数**/
$str = preg_replace("/\{([url=file://\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/]\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/","<?php[/url] echo [url=file://\\1;?]\\1;?>",$str[/url]);
/* 把{$xxjj} 转成 <?php echo $xxjj?> 当然了是把变量输出**/
$str = preg_replace("/\{([url=file://\\$[a-zA-Z0-9_\[\]\]\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}/es[/url]", "addquote('<?php echo [url=file://\\1;?]\\1;?>')",$str[/url]);
/* 主要是把{$xxx['jj']} 转成 <?php echo $xxx['jj']?> addquote() 函数自己定义的看下面,二次过滤。有代验证,头昏了看太久的黄色字。我昏**/
$str = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "<?php echo [url=file://\\1;?]\\1;?>",$str[/url]);
/* {XXJJ} <?php echo XXJJ?> XXJJ 是我们定义的常量**/
$str = "<?php defined('IN_PHPCMS') or exit('Access Denied'); ?>".$str;
/* 最后别忘了在自己的每个编译后的文件里加上这个。以前讲过了不明白找前面例子**/
return $str; //最后返回过滤完成的内容
}
function get_tag($tagname) //这个函数在 上面这个编译函数里面看到了。 其实就是获取对应标签的内容,头有点昏,下节再说标签吧。
{
global $tags,$html,$CONFIG;
if(!$tags) require PHPCMS_ROOT.'/templates/'.$CONFIG['defaulttemplate'].'/tags.php';
if(!$html) require PHPCMS_ROOT.'/templates/'.$CONFIG['defaulttemplate'].'/html.php';
if(!isset($tags[$tagname])) return '{tag_'.$tagname.'}';
$code = isset($html[$tagname]) ? 'tag_read('.$html[$tagname].')' : $tags[$tagname];
return "<?php echo $code;?>";
}
function addquote($var)
{
return str_replace("[url=]\\\[/url]"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
}
?>
[/php]


下一节讲 phpcms 的模板标签功能,讲得不好.大侠就别笑我了