(转)dedecms代码详解 很全面
dedecms代码研究(1)开篇
dedecms 相信大家一定都知道这个cms 系统,功能比较强大,有比较完善的内容发布,还有内容静态化系
统,还有就是它有自己独特的标签系统和模板系统。而模板系统也是其他cms系统比较难模仿的的东西,这个
东西还是需要一点开发功力和技巧的。
本系列文章就研究一下dedecms的这套系统,挖掘一下看看里面有什么好东西。
建议大家先了解一下dedecms的功能。自己先动手用一下,对系统功能有个大概了解。
本文先带领大家了解一下dedecms的代码和功能架构。
其实,dedecms在架构上没什么应用架构模式可言,就是最简单的PHP 应用而已。访问不同的PHP 文件,
管理不同的系统功能。看目录大家都能猜出来各个目录干什么用的。include目录放系统的一些公用函数和类,
plus目录放插件,templates目录放模板,dede 目录是管理后台目录。
我们打开include 目录,看看里面都有什么好东西。
calendar 一个选时间的js
captcha 一个验证码,还是开源组件
code 没啥意思,翻页的文字
data 里面是一些系统用到的资源,比如声音,分词库,字体,图片等
dialog 里面估计是一些AJAX弹出窗口的内容部分。以后碰到了再说
inc 里面一些单独的功能类函数,以后研究一下为什么要单独放
payment 支付接口
taglib 好东西,是dedecms的标签存放的地方,打开看看,里面一堆文件,貌似就是dedecms的模板标
签啦
tpllib 模板库?暂时搞不明白,希望随着研究深入,能弄明白
其他 include 目录下的文件估计都是一些最基本的功能文件啦,比如常用函数,模板系统之类的东西了。用
到再说吧
打开dede目录(就是dedecms的管理目录)看看,哟嗬,里面的东西还挺多,看名字就知道了,都是各种
功能管理文件,一个功能一个文件。最原始的网站开发模式做出来的。我们姑且称之为高效吧。毕竟PHPwind
和discuz之类也是用类似的方法开发的。
整个程序大体就这些东西啦。功能就不讲了,建议不熟悉dedecms的朋友自己看一下dedecms的后台管
理功能,这样能有助于我们更好理解和分析它。
另外提一点,我们的这次代码分析,主要分析的是其页面生成、显示、模板处理、标签处理部分,这套
系统也是dedecms比较引以为傲的东西,之后呢,还会分析一些笔者觉得比较酷的功能代码。
最后,希望这次代码分析旅程能让大家有所收获。
sourcejoy 之dedecms代码研究(2)从index开始
现在继续,今天讲的主要是dedecms的入口代码。
2
先打开index.php看看里面是什么吧。打开根目录下的index.php嗯,映入眼帘的是一个if语句。
检查/data/common.inc.php是否存在。如果不存在就跳转到安装界面。
我们来到/data/看看这个目录和common.inc.php。
打开/data/,里面很多目录和文件,有上传的临时目录,模板缓存,压缩的临时目录,各种数据库里保存
的系统配置信息的缓存文件以及其他的一些东西,就一个字乱~
好吧,我们打开common.inc.php。哦,原来就是保存了数据库连接的相关变量而已。说白了,就是dedecms
的数据库连接配置文件,估计是安装完系统生成的。所以/index.php 会检查它是否存在。
我们回到/index.php中继续往下看,第二个if语句,判断GET请求“upcache”是否存在,存在就更新首
页缓存,不存在就直接301 跳转到index.html,也就是dedecms的静态首页(记住,dedecms的前台页面都是系
统生成的静态页面)。
想想接下来我们要研究什么?对,就是GET请求“upcache”存在的时候,更新首页缓存这小段代码啦。
代码如下:
require_once (dirname(__FILE__) . "/include/common.inc.php");
require_once DEDEINC."/arc.partview.class.php";
$GLOBALS['_arclistEnv'] = 'index';
$row = $dsql->GetOne("Select * From `#@__homepageset`");
$row['templet'] = MfTemplet($row['templet']);
$pv = new PartView();
$pv->SetTemplet($cfg_basedir . $cfg_templets_dir . "/" . $row['templet']);
$pv->SaveToHtml(dirname(__FILE__).'/index.html');
include(dirname(__FILE__).'/index.html');
exit();
先是加载/include/common.inc.php,估计是一些常用函数和加载其他系统函数和类的文件。
接下来又加载了DEDEINC."/arc.partview.class.php",注意DEDEINC 这个常量,我们知道经过前期对
dedecms 目录结构观察,arc.partview.class.php 是在/include 目录下的,而加载/include/common.inc.php 却没有用
DEDEINC 这个常量,这说明,common.inc.php 里面定义了DEDEINC 这个常量,所以后面得以使用,也印证
了common.inc.php 大体作用就是系统运行基本部分,都在这里面啦。我们不急着进common.inc.php,继续把
index.php 的更新缓存代码看完。
第三句,设置了一个全局变量:$GLOBALS['_arclistEnv'] = 'index';
第四行,获取一个表'#@__homepageset'的所有记录,当然如果你看下数据库,里面没有
“#@__homepageset”这个表,我们判断,是数据库操作相关函数把表名用表名前缀替换了一下前半部分,这
个都不重要了,重要的是,通过这句,获取了首页的相关配置信息,我们打开数据库里面的dede_homepageset
表,晕,就一条记录,俩字段,基本猜到了,一个是首页模板名称,一个是生成的静态文件的位置。
回来继续分析前面的代码
$row['templet'] = MfTemplet($row['templet']);
通过MfTemplet函数好像把$row['templet']进行了某些转换。我们记下MfTemplet函数,以待后面挖掘。
接下来,就是new 了一个PartView 类,看这个名字,我们就知道了前面加载arc.partview.class.php 的作
用啦。
至于这个PartView类有什么作用,我们继续看代码。看了下面两行,我想大家应该都明白啦
$pv->SetTemplet($cfg_basedir . $cfg_templets_dir . "/" . $row['templet']);
3
$pv->SaveToHtml(dirname(__FILE__).'/index.html');
创建个视图对象(PartView类的实例,我们姑且叫视图对象),设置模板,通过SaveToHtml方法,把最
后生成的页面写到指定位置。
首页生成完毕,接下来就是把生成的静态文件通过include的形式显示出来,然后exit中断页面解析。
至此,/index.php 就分析完啦。
它先是通过/data/common.inc.php,判断是否安装了dedecms或者说判断是否定义了数据库配置信息,好
为后面操作打下基础。
然后判断是否有GET 请求"upcache",如果有就加载/include/common.inc.php 初始化系统,然后调用
partview类的相关方法来生成静态首页文件,最后显示出来。
这么来看dedecms也没有太多的秘密嘛~
不过呢,我们这篇文章也遗留下了几个问题:
1)加载了/include/common.inc.php,里面做了哪些工作?
2)/include/arc.partview.class.php到底是干什么的,/include/下还有很多arc开头的文件都是干什么的?
3)MfTemplet这个函数到底对模板文件路径这个字符串做了什么操作?
4)partview类的相关方法都有什么秘密?
带着这几个疑问,我们将结束本文,后面的文章将将这些谜题一一揭开。
sourcejoy 之dedecms代码研究(3)partview的迷惑
上次,我们从dedecms的index.php文件中了解到了很多信息,也提出了一些问题,本文开始就带着前面的问题,
继续我们的dedecms之旅吧。
先回顾一下之前我们在index.php文件研究中总结的东西。
首先加载common.inc.php,接下来组织模板,生成静态页面并跳到静态页面。
接下来,我们就先来看看common.inc.php 里面都有什么吧。打开/include/common.inc.php 里面的注释已
经说地比较清楚了。我们大概说说结构。
先是定义一堆常量。然后是做一些安全措施,对PHP 的系统环境进行一些设置,代码里面的注释已经写
地很清楚了。
接下来是把dedecms的系统配置参数文件包含进来:require_once(DEDEDATA."/config.cache.inc.php");
看文件名字,我们猜测这个配置文件可能是数据库里面的配置信息的缓存。
接下来加载了数据库配置信息文件:require_once(DEDEDATA.'/common.inc.php');
这个文件,不是根据数据库中信息生成的缓存,而是dedecms安装的时候生成的。前一篇文章我们说过
index.php 文件开始,检测dedecms是否安装,就是看这个文件是否存在的。
再接下来,整理了很多目录,比如:站点根目录、模板目录,插件目录、数据目录等,还整理了很多变
量。最后加载了数据库操作类dedesql.class.php和常用函数文件common.func.php
4
嗯,common.inc.php的谜底揭开,里面没什么好玩的东西啦,都是最基本的东西。
接下来我们就得看看arc.partview.class.php吧,这里面可是dedecms关键呢
加载了channelunit.class.php,typelink.class.php,ftp.class.php
下面就是partview类的定义啦
因为index.php 中使用partview类的SetTemplet方法和SaveToHtml方法,所以,我们为了能更简单地深
入,就从这两个方法着手。
我们先看看partview的构造函数。
创建了一个DedeTagParse类的实例,看名字是标签解析类哦。然后设置了几个参数。
接下来,new了一个TypeLink 类,设置了一堆参数。搞的很云里雾里的。
看看 SetTemplet吧。啊,这个还算简单。
先,使用DedeTagParse类的LoadTemplet方法载入模板。
再,设置一些Fields数组的元素
最后,调用ParseTemplet方法。
ParseTemplet方法里面弄了一堆$GLOBALS 数组的元素,然后调用了MakeOneTag函数。费解啊
再看看SaveToHtml方法吧,前面就是建目录,最后用DedeTagParse的SaveTo 方法保存到文件。
呃~不给力啊。
只能回头想想,都看到了什么~
嗯,为了能生成首页,搞了个很搞不懂的partview类,然后里面调用了貌似万能的DedeTagParse方法,解析模
板,生成静态文件。
仅此而已。
里面还夹杂了其他函数和类,但不管怎么样这个DedeTagParse是重点,下次得重点分析了。
今天就到这吧,鸟儿的~太乱了,一点章法都没有~
sourcejoy 之dedecms代码研究(4)继续徘徊partview
之前,我们像掉进沼泽一样,看到无尽的变量,数组元素,莫名其面的东西摆在我们面前。今天,我们继续艰难前行,想办法走
出partview类的泥潭。
上一篇,我们胡乱分析了partview类,完全搞不懂干什么的,里面弄了一堆变量,最清晰的我们只是知道几个生成首页的关键地
方调用了DedeTagParse 类的LoadTemplet 方法和SaveTo 方法。而在partview 类定义的文件头部,包含了几个文件,我们就
避开partview,先来看看这几个包含的文件吧。
require_once(DEDEINC.'/channelunit.class.php');
require_once(DEDEINC.'/typelink.class.php');
require_once(DEDEINC.'/ftp.class.php');
5
ftp.class.php,不用说,就是ftp 相关操作类吧,我们之前看partview 代码的时候,了解到,在生成静态文件的时候,使用了ftp
相关方法,貌似就是可以远程写文件滴。至于怎么操作FTP 的,其实就是封装了php 函数库中ftp 开头的相关函数而已,代码很
简单,不说了。
typelink.class.php,我们也在partview 代码里面见过的,打开看看吧。大概看了一下代码,里面是type 的链接相关的东西,每
个方法都声称一个指定type 的链接html 字符串。
其实,我有点隐约感觉到在dedecms 中,type 就是指栏目,不知道是不是这样。
再来看看channelunit.class.php 吧。
里面是 ChannelUnit 类的定义,而且我们发现,这个ChannelUnit 类没有被使用过。所以先不去看他。我们注意到,这里面还加
载了两个文件:
require_once(DEDEINC."/dedetag.class.php");
require_once(DEDEINC."/channelunit.func.php");
dedetag.class.php,打开看看,嗯,很复杂,但我们发现用于解析模板和生成文件的DedeTagParse 类在里面,呵呵,先记住,
以后慢慢研究。
channelunit.func.php 里面都什么函数呢?打开一看,嗯,一堆变量,几个获取这种信息的函数,在我们浏览过程中,发现了两
个函数:MfTemplet 和MakeOneTag
我们知道,在index.php 中就用了MfTemplet 函数,回头打开index.php 看看怎么调用的:
$row['templet'] = MfTemplet($row['templet']);
我们之前了解过,$row['templet']保存的是default/index.htm 这个值,就是模板文件路径。接下来我们看看MfTemplet 函数都做
了什么吧。
//模板目录规则
function MfTemplet($tmpdir)
{
$tmpdir = str_replace("{style}",$GLOBALS['cfg_df_style'],$tmpdir);
$tmpdir = ereg_replace("/{1,}","/",$tmpdir);
return $tmpdir;
}
注释里面写的是“模板目录规则”,再看看代码,哦,仿佛明白了一点儿了,就是替换模板路径里面的{style}为全局变量
$GLOBALS['cfg_df_style']中的值。应该跟使用不同模板套系有关吧。意义不是很大就不再继续研究了。
我们看另一个函数MakeOneTag,这个在partview类的ParseTemplet 方法中,此方法看名字就是解析模板,而方法的大部分代
码都是在处理变量,看不大懂干什么的,最后一句调用了MakeOneTag 函数。貌似主要解析模板就是靠这个函数了。使用如下:
MakeOneTag($this->dtp,$this);
第一个参数是DedeTagParse类的实力,第二个参数就是partview类实例的句柄啦。
我们看看channelunit.func.php 中这个函数是干什么的吧。
嗯,只能大概看,因为好多东西,我们都不清楚啊,郁闷了。
这里面遍历了/include/taglib/下所有有lib 后缀的文件,并把文件路径加入数组,然后对DedeTagParse 类的CTag 进行了遍历,
6
由于我们没有研究DedeTagParse 类,所以这块暂时不懂呢,不过也算小有进展了。看来还得回到partview里面去重新看看了。
构造函数没什么特别的,就是创建了DedeTagParse 类实例,进行了一些设置而已。我们知道index.php 创建partview实例后执
行了SetTemplet 方法,我们再看看SetTemplet 方法吧。
这里面调用了DedeTagParse类实例的LoadTemplet 方法,看来我们就得从这里入手,去抽丝剥茧啦。
分析不下去了~
留几个疑问下次再说。
1)DedeTagParse 类LoadTemplet 方法说开去。
2)MakeOneTag 到底在搞什么。
看来只有彻底先把DedeTagParse 类LoadTemplet 方法搞懂才能进一步啊,目前还是一头雾水。
sourcejoy 之dedecms代码研究(5)从DedeTagParse开始
前面,我们一直在dedecms 的外围,被各种全局变量和各种调用所迷惑,我们抓住了一个关键的线索DedeTagParse 类,研究
明白它,就可以弄清楚很多东西了。
看看这个 NB 的DedeTagParse 类吧。
嗯,先看构造函数,没什么特别的,就是设置了一堆初始化参数。
接下来就找LoadTemplet 方法吧。
找到后,我们发现LoadTemplet 方法其实是指向LoadTemplate 方法的,无语啊,难道作者英文就差到此等地步?
看看那个LoadTemplate 方法吧。
里面先用 SetDefault 方法设置了几个初始变量:
$this->SourceString = '';
$this->CTags = '';
$this->Count=-1;
然后判断模板文件是否存在。然后针对不同情况对$this->SourceString 赋值,并调用$this->ParseTemplet();方法。
这块的代码看出来,作者开发功力有待改进啊,都5.6 了,代码重构还如此糟糕,唉~为什么不能把$this->ParseTemplet();这句
放在if 外面呢?
文件不存在时候,很简单,就是把“文件不存在”这句话放到$this->SourceString 中,然后调用$this->ParseTemplet();。
文件存在的时候,也很简单,fgets 读取文件内容(麻烦,为啥不用file_get_contents 呢),然后,又是一个if,通过
$this->LoadCache($filename)返回值判断是否有缓存,如果返回true 说明读取到缓存的模板了,就返回空字符串(怎么可以这样
呢?返回值也太不负责了吧),如果返回false 就调用$this->ParseTemplet();重新解析模板。
LoadTemplate 大致就是这些,无非是读取模板文件内容,然后看是否有缓存,有就不解析模板,没有就解析模板,仅此而已。
我们接下来看看$this->LoadCache 方法吧,找到方法定义的部分,呀喝,代码还不少。
7
先是通过$this->IsCache 判断是否允许缓存(这个属性是在DedeTagParse 类实例化的时候设定的,跟dedecms 的系统配置中
是否加模板缓存的参数$cfg_tplcache 有关,这个在DedeTagParse 类的构造函数中有所体现,由于安装dedecms 后,默认系统
配置为true,所以这里默认就为true 啦),如果为false 的话,$this->LoadCache 就返回false 而不继续向下走了,在LoadTemplate
方法中就会根据这个返回值来决定解析模板。
过了$this->IsCache 这关,程序继续。下面就是找当前模板文件对应的缓存了。
dedecms 的文件缓存有点特别,我们找到模板缓存目录(data/tplcache),观察一下就会发现,很多名字有点相同的文件,仔细
看看还能找出点规律来。我们从代码来印证一下吧。
上面说到LoadCache 方法中,我们过了$this->IsCache 这关,后面就是找模板文件了,我们看看后面的代码:
$cdir = dirname($filename);
$cachedir = DEDEROOT.$cfg_tplcache_dir;
$ckfile = str_replace($cdir,'',$filename).substr(md5($filename),0,16).'.inc';
$ckfullfile = $cachedir.'/'.$ckfile;
$ckfullfile_t = $cachedir.'/'.$ckfile.'.txt';
前 3 句是拼缓存文件名字的,方法是取不带目录的模板文件名然后md5 进行hash,然后把hash 出来的字符串取前16个字符,
后面加上“inc”后缀就成了。
第 4 句是取得完整的缓存文件名。
第 5 距好像是另一个文件名字,就是在缓存文件名字后面再加个后缀“.txt”
上面就得到了两个文件名字,但是我们不知道第二个文件名字干什么用的,再继续往下看咯啊的代码吧。
$this->CacheFile = $ckfullfile;
$this->TempMkTime = filemtime($filename);
if(!file_exists($ckfullfile)||!file_exists($ckfullfile_t))
{
return false;
}
第 1 句就指定了当前模板的缓存文件
第 2 句读取了文件的最后修改时间,设置了一个什么时间的属性,现在还不大明白。
接下来的if 语句就是如果找不到模板的两个缓存文件(就是上面组合出来的两个文件),就返回false 让LoadTemplate 方法解
析模板去。
我们假设模板的缓存文件都有,继续看代码。下面代码段写了注释,就是检测模板最后更新时间,代码很简单,就是打开我们前
面说的那个$ckfullfile_t 变量指定的txt 文件,读内容,然后把内容和缓存修改时间比较,原来.txt 文件是用来保存缓存文件的保存
时间的。如果时间不一致则就返回false 让LoadTemplate 方法解析模板去。
我们假定缓存有效,那么就可以继续了。
缓存有效就会把缓存文件包含进来。
这块就要根据缓存文件来具体分析了,所以,我们这里假定载入的是index.htm 模板吧,在tplcache 里面找到index.htm 开头,
8
后缀为inc 的文件,打开。
我们这里节选一部分:
$z[0]=Array("global","",236,264);
$z[0][4]['name']="cfg_soft_lang";
$z[1]=Array("global","",277,303);
$z[1][4]['name']="cfg_webname";
$z[2]=Array("global","",347,377);
$z[2][4]['name']="cfg_description";
$z[3]=Array("global","",414,441);
$z[3][4]['name']="cfg_keywords";
……
再回到我们的LoadCache 方法里面。
前面说到include 了模板缓存文件,然后,下面的if 语句判断的是缓存文件里面的信息数组“$z”是否正常,如果正常就进行来
个foreach 循环,这个foreach 很重要。我们来看看代码。
foreach($z as $k=>$v){
$this->Count++;
$ctag = new DedeTAg();
$ctag->CAttribute = new DedeAttribute();
$ctag->IsReplace = FALSE;
$ctag->TagName = $v[0];
$ctag->InnerText = $v[1];
$ctag->StartPos = $v[2];
$ctag->EndPos = $v[3];
$ctag->TagValue = '';
$ctag->TagID = $k;
if(isset($v[4]) && is_array($v[4])){
$i = 0;
foreach($v[4] as $k=>$v){
$ctag->CAttribute->Count++;
$ctag->CAttribute->Items[$k]=$v;
}
}
$this->CTags[$this->Count] = $ctag;
}
这是个遍历缓存信息数组,然后每个$z数组的元素,都生成一个DedeTAg 对象,并把$z数组元素的一些信息赋给DedeTAg 对
象,我们经过看这段源代码,发现,$z数组元素中,0 是标签名称(TagName),1 是内部文本(InnerText),2 是开始位置(StartPos),
3 是结束位置(EndPos)。新的DedeTag 对象的tagID 就是数组下标。
这里面还有个循环,是循环$z 数组每个元素的第四个子元素。然后,然后把相关值赋到当前DedeTAg 对象的DedeAttribute 对
9
象中。
看到这里,我们似乎明白点东西了。
1)tplcache 里面存的并不是解析好的模板,而是一堆信息数组。
2)信息数组里保存的都是一个模板页里面包含的所有标签的信息。
3)上面的循环其实是把缓存里面的标签信息读取并写入DedeTAg 对象,然后保存到当前DedeParse 类的CTags 数组中,到目
前DedeParse 的实例得到了模板内容(在$this->SourceString 中),模板上所有标签信息(在$this->CTags
中)。
好了,经过上面的一番操作,LoadCache 方法就这些了,缓存读取完成,这样就可以安心回到LoadTemplate 方法里面去继续分
析了。
sourcejoy 之dedecms代码研究(6)ParseTemplet算法分析
国庆放假三天开始生病,病好了就开始疯狂地忙碌,我晕,通过昨天的努力总算可以继续更新了。今天讲的是dedecms 最关键
的东西,模板分析啦。
先看看一个 dedecms 标签,大家心里有个数:
{dede:arclist row=10 orderby=pubdate type='image.' imgwidth='143' imgheight='106'}
<li><a href="[field:arcurl/]">[field:image/]<span class="title">[field:title/]</span></a></li>
{/dede:arclist}
参考上面标签我们就可以进一步分析啦。
这里假定,你已经了解了dedecms 的标签形式,标签格式,和标签种类。
下面我们展开分析
先看方法前面初始化一些最基本的变量:
1)标签起始符号和结束符号。如:“{”和"}"
$TagStartWord = $this->TagStartWord;
$TagEndWord = $this->TagEndWord;
2)设置临时变量,用于临时存储查找到的新标签在模板中的起始位置和结束位置。
$sPos = 0; $ePos = 0;
3)设定完整标签起始字符串和结束字符串。比如:“{dede:”这种形式
$FullTagStartWord = $TagStartWord.$this->NameSpace.":";
$sTagEndWord = $TagStartWord."/".$this->NameSpace.":";
$eTagEndWord = "/".$TagEndWord;
这里值得注意的是结束部分分两种,一种是类似于{aa:ff /}单体结构标签,一种是类似于{aa:fff}{/aa:fff}符合结构标签
4)获取标签其实字符串({dede:)长度和整个模板的长度
$tsLen = strlen($FullTagStartWord);
$sourceLen=strlen($this->SourceString);
上面就是初始变量设置部分啦。
接下来是个小判断,如果整个模板的长度不大于标签起始字符串的长度加3,就退出。
if( $sourceLen <= ($tsLen + 3) ){
return;
}
为什么要加3(也就是模板长度最少应该是标签起始字符串长度加4)呢?
10
我们看看我们能写出的最短标签:
{dede:a/}
冒号后面是可能出现的最短字符串,就是3 个,所以这里如果小于3 就连最起码的一个标签都无法完整,所以要做这个判断,至
于等于嘛,我个人认为是没必要的。
好继续往下看下面两句:
$cAtt = new DedeAttributeParse();
$cAtt->charToLow = $this->CharToLow;
创建了一个DedeAttributeParse 类,并设定了CharToLow属性,这个类看名字应该是标签属性分析类,charToLow就是是否把
字符串自动转化为小写。
接下来就是一个长长的for 循环了,遍历模板字符串的每个字符进行分析,提取模板中的标签。
for($i=0; $i < $sourceLen; $i++)
下面我们就来看看这个for 循环里面是怎么分析的吧
先定义一个临时变量,存储当前找到的标签的名字
$tTagName = '';
下面是一个判断,注释写得很清楚,但我们现在还看不懂,所以先知道有这么个判断就行啦
//如果不进行此判断,将无法识别相连的两个标记
if($i-1 >= 0){
$ss = $i-1;
}else{
$ss = 0;
}
设定了一个变量$ss,后面留意一下就是了。
下面就是查找标签了
$sPos = strpos($this->SourceString,$FullTagStartWord,$ss);
$isTag = $sPos;
找到在模板字符串中从$ss 指定的位置开始,第一个类似“{dede:”这种标签头的位置,并把$isTag 变量设置为strpos 的返回值,
这是个偷懒的写法,应该明确指出查到标签了,就是true,而不是任意字符。
我们看到这里用到了$ss,作用是设定查找的起始位置。
我们继续往下看吧
下面一个 if 语句好像是对第一个字符开始就是标签的情况下的一种补充?
搞不懂了,本来就能找到的,加这句什么意思呢?多余哦,这个肯定有更好方法的。不多说这句了。
在下来的if 就是如果没找到标签就不循环了,不解释。
再下来,一个子循环
for($j=($sPos+$tsLen);$j<($sPos+$tsLen+$this->TagMaxLen);$j++)
$tsLen 我们之前说了,是标签头(类似{dede:)长度
那这个for 的解释就是遍历从标签头的下一个字符开始到标签最大长度位置结束这中间的所有字符,看来是要找标签名字啦
再看看for 循环里面,很简单的几句,就是找出标签的名字,如何找出来的呢?
if($j>($sourceLen-1)){
break;
}else if( ereg("[/ /t/r/n]",$this->SourceString[$j]) || $this->SourceString[$j] == $this->TagEndWord ){
break;
}else{
$tTagName .= $this->SourceString[$j];
11 http://www.pprar.com
}
这个for 里面的if 语句,两种情况下名字结束,一种是字符位置到模板的字后一个位置,另一种是发现了空格、断行、tab 符、/
等或找到了标签结束符(如:"}")
通过这个for 循环,标签的名字就弄出来了,保存在变量$tTagName 中。
下面是一个极其长的if 语句啦,判断$tTagName 变量是否为空,如果是空则跳出循环(标签出错了嘛),不过跳出前还
设置$i,有什么用?看不懂。
接下来重点就是找到标签名字的情况啦。
先是设置几个变量
$i = $sPos+$tsLen;
$endPos = -1;
$fullTagEndWordThis = $sTagEndWord.$tTagName.$TagEndWord;
把循环模板字符串的指针$i 跳到标签名字开始的地方。然后设置变量$endPos 为-1,组合出一种标签结束符({/dede:xxx})
接下来是查找三个位置:$eTagEndWord(/})、$FullTagStartWord({dede:)、$fullTagEndWordThis({/dede:xxx})
$e1 = strpos($this->SourceString,$eTagEndWord, $i);
$e2 = strpos($this->SourceString,$FullTagStartWord, $i);
$e3 = strpos($this->SourceString,$fullTagEndWordThis,$i);
$e1 就是在标签名字找到后第一个"/}"出现的位置,$e2 就是第一个“{dede:”出现的位置,$e3 就是第一个{/dede:xxx}出现的位
置。这里注意,获取$e3 值的时候,$fullTagEndWordThis 是以当前找到的标签为名字的结束字符串。
在下面几句是统一$e1 $e2 $e3 的值,使这三个变量如果找到要找的标签字符串就保存位置,找不到就保存-1
$e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
$e1 = ($e1=='' ? '-1' : $e1);
$e2 = ($e2=='' ? '-1' : $e2);
$e3 = ($e3=='' ? '-1' : $e3);
接下来就要根据这三个值进行一些处理啦。处理什么呢?我们先看看这段代码吧:
//not found '{/tag:'
if($e3==-1) {
$endPos = $e1;
$elen = $endPos + strlen($eTagEndWord);
}
//not found '/}'
else if($e1==-1) {
$endPos = $e3;
$elen = $endPos + strlen($fullTagEndWordThis);
}
//found '/}' and found '{/dede:'
else{
//if '/}' more near '{dede:'、'{/dede:' , end tag is '/}', else is '{/dede:'
if($e1 < $e2 && $e1 < $e3 ){
$endPos = $e1;
$elen = $endPos + strlen($eTagEndWord);
}else{
12
$endPos = $e3;
$elen = $endPos + strlen($fullTagEndWordThis);
}
}
我们知道,dedecms 标签结束有两种方式,一种是(/})这种方式,还有一种是({/dede:xxx}),除此之外没有他选,如果没有
这两种结束,只能说明一个问题,模板内的标签不完整。这个if 语句做了一个假设,就是两种标签结束方式一定是有一种存在的。
if 的第一个分支,假设$e3 为-1,也就是(/})这种方式存在,所以设置了标签结束符位置变量$endPos 为变量$e1 的值,而此时,
标签最终结束位置就知道了,是$endPos 加上(/})的长度。
if 语句的第二个分支和第一个类似,只是假定找到了({/dede:xxx})。
if 语句的else 部分,是假定两个都找到了(有这种可能吗?),那么就要进一步分析啦,如果(/})这种结束符出现的位
置比下个标签起始位置靠前,而且还比$e3 的结束符({/dede:xxx})位置靠前,说明当前找到的(/})就是当前标签的结
束符;否则一定是({/dede:xxx})这种啦。
上面通过$e1 $e2 $e3 的变量设置和一个if 语句,最终是要得到两个变量:$endPos 和$elen,当前标签结束符开始的位
置和结束位置。
下面又是一个if 语句,很简单,通过endPos 是否为-1 判断当前标签是否正确结束。如果没有正确结束则打印一段文字,然后就
退出循环。这块设计的是否可以再好点呢,比如把这块出错的标签替换为一个错误信息,或在做模板分析前,统一检查语法正确
性,以保证更快速分析模板。
再继续往下看,又是设置了两个变量。
$i = $elen;
$ePos = $endPos;
由于找到当前循环要找的标签,所以,设置主循环for 的循环变量$i 到下个标签的起始位置。
设置当前标签的结束符起始位置$ePos。
当前标签的开始位置和结束位置都确定了,接下来就可以分析标签的属性了,我们继续。
$attStr = '';
$innerText = '';
$startInner = 0;
三个变量,我们了解到,标签内部有两种东西,一种是属性字符串,还有一种是内容字符串。$startInner 变量指示内容字符串是
否开始(奇怪为什么不用布尔值呢)。
下面一个 for 循环开始提取这些字符串,从标签名称后面到结束符开始之前的部分。
for($j=($sPos+$tsLen);$j < $ePos;$j++)
看看循环里面是怎么提取属性字符串和内容字符串的。
if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="//") ){
$startInner=1;
continue;
}
if($startInner==0){
$attStr .= $this->SourceString[$j];
}else{
$innerText .= $this->SourceString[$j];
}
嗯,用了两个if 语句,第一个语句是用来判断内容字符串是否开始的。第二个if 语句根据内容字符串开始指示符判断,分别读取
13
内容字符串和属性字符串。
个人认为,通过特殊标识符截字更快一些。
这里面还有个问题就是,是否内容字符串开始是如何判断的呢?
我们看看第一个if
if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="//") )
$startInner==0 这句就是做个过滤,当读取内容字符串的时候就不会再走这个if 了,关键是&&后面括号里面的内容。
如果当前字符为标签结束符$TagEndWord(})而且结束符的前一个字符不是反斜杠的时候,就是属性部分结束了,如果是反斜
杠说明是一些模板内容之类的了。
通过上面的for 循环我们就提取出了当前标签的属性和内容,接下来就开始分析属性和内容啦
$cAtt->SetSource($attStr);
if($cAtt->cAttributes->GetTagName()!=''){
$this->Count++;
$CDTag = new DedeTag();
$CDTag->TagName = $cAtt->cAttributes->GetTagName();
$CDTag->StartPos = $sPos;
$CDTag->EndPos = $i;
$CDTag->CAttribute = $cAtt->cAttributes;
$CDTag->IsReplace = FALSE;
$CDTag->TagID = $this->Count;
$CDTag->InnerText = $innerText;
$this->CTags[$this->Count] = $CDTag;
}
通过属性分析类来进行分析啦,然后创建DedeTag 标签类实例(就是创建一个标签对象),然后把当前标签的属性都放进这个
标签对象。
包括标签名称、起始位置、结束位置、属性数组、内部字符串等。
然后,帮这个新的标签对象放到DedeTagParse 类的CTags 数组中。
这样一个标签就分析完了,也结束了一次最外层的for 循环。原来每循环一次只能分析出一个标签,有多少个标签就有可能循环
多少次。
整个模板分析结束后,如果允许缓存再调用SaveCache 方法,把当前模板的标签信息保存到缓存文件或者叫中间信息文件。
模板分析就讲完啦,这样该有的信息就都有了,我们又可以回到LoadTemplate 方法继续啦。
sourcejoy 之dedecms代码研究(7)MakeOneTag
最近太忙了,忙得网站不能更新,书也好久没看了。今天有空就继续完成没有完成的事业。今天就接上一篇文章继续讲dedecms
的静态页面生成过程。
简单回顾一下,前面我们首页动态文件index.php 调用arc.partview.class.php 来初始化首页模板,arc.partview.class.php 通过
SetTemplet 调用DedeTagParse,在设置首页模板的时候,解析了模板,并生成模板的缓存或者说序列化文件。虽然模板被解析
了,但是这种解析的只是把所有标签在模板中的位置以及他们的参数记录下来,并没有填充数据,接下来估计就要干这些事情了。
我们回到partview的SetTemplet 方法,从$this->dtp->LoadTemplet($temp);这句往下看吧。
14
下面if($this->TypeID > 0)这个if 语句是设置当前页面的“面包屑导航”和标题。如果你使用过dedecms,对dedecms系统的操
作比较熟悉,应该了解这个if里面的$this->Fields['title']这种数组元素,在页面模板中很多地方都是用了fields 数组呢。
设置了两个变量,接下来就调用ParseTemplet 解析模板啦。
这个 ParseTemplet 很简单,设置了一大堆全局变量,一看就知道了,页面里面用的什么channelid,topid 之类的东西,有兴趣
的朋友可以自己细致研究一下。我们注意到ParseTemplet方法最后一句,MakeOneTag($this->dtp,$this); 因为前面的东西都是
没什么实质性的,这个应该很关键了。找到这个函数看看吧。
先看看 MakeOneTag 函数的参数,前两个参数都是引用传递的,第一个参数是传入DedeTagParse 的实例,第二个参数是传入
partview的实例。
开始用一个循环获取系统所拥有的所有标签列表
$dh = dir(DEDEINC.'/taglib');
while($filename = $dh->read()){
if(ereg("/.lib/.",$filename)){
$alltags[] = str_replace('.lib.php','',$filename);
}
}
$dh->Close();
代码很简单,就是遍历/include/taglib 目录,根据文件规则取文件名,形成一个所有标签的数组。
接下来就是一个大的foreach 数组了,遍历在partview 类中实例化并解析了当前模板的DedeTagParse 的实例的CTags 属性,
我们通过前面对DedeTagParse 的分析知道CTags 实际上是模板中所有使用的标签及其参数的集合而已,这里就开始遍历这些
模板上的标签进行赋值,替换之类的啦。
我们继续看这个foreach 都干了什么。
先获取循环中当前标签的名字。
接下来是针对标签名为field 的标签的操作。
如果是field 标签的时候,先获取标签的name 属性,当名字是array的时候则通过DedeTagParse 的Assign 方法把partview的
Fields 数组赋给标签名对应的值属性(详情看Assign 的代码,由于比较简单这里略过)。如果标签name 属性不是array,则把
partview 的Fields 数组中指定名字的值赋给标签对应的值。下面的else 是进行一些其他判断也很简单,然后field 标签就解析完
了,这样我们就知道了field 标签就是相当于变量,此处就是给这些变量赋值,解析完之后,就继续循环分析下个标签了。
接下来的两个if 是做标签名字的兼容性了,我们看到arclist有一堆标签名字~
再接下来就是看看模板中的标签是否有对应的系统标签了,如果有,就载入对应的文件,调用对应的函数,把函数返回值赋值给
模板标签对应的值。
这样,整个模板的标签就完成赋值了。
至于系统中那些标签(/include/taglib/下的那些标签解析文件)其实就是一个函数,里面读取数据库数据,组织数据,输
出而已。
这节就到这里了,主要讲了分析好的模板标签如何绑定最终数据的。其实比较简单,因为模板分析的时候已经得到了当
前模板所使用的全部标签,接下来只要遍历标签,调用不同的标签函数,获取函数返回数据就行了。
有些细心的朋友会发现,到这里其实还没有完,数据什么的都得到了,接下来呢?是如何生成静态文件的?
这就是下一节要讲的东西啦。
15
附:dedecms(v5.6)系统目录
a /* 生成HTML的目录*/
|data /* 程序生成常用数据保存目录*/
| |admin /* 保存系统后台常规配置,例如作者、快速导航、来源,以文本格式存放*/
| |backupdata /* 数据库备份存放目录,可以在系统后台设置处修改*/
| |cache /* 系统缓存*/
| |enums /* 联动类别生成的缓存和js文件*/
| |js /* 栏目js调用生成的js文件*/
| |mark /* 图片水印设置目录*/
| |module /* 系统后台那些模块安装包存放的目录,通常文件名称加密过*/
| |rss /* 生成 RSSmap 存放的文件目录*/
| |sessions /* 系统 sessions存放目录,登陆后就会生成个session,目录需可写*/
| |textdata /* 文本数据,系统后台保存为文本数据存放目录*/
| |tplcache /* 模板缓存目录,这个缓存一般是那些动态页*/
| |ziptmp /* 压缩缓存目录*/
|dede /* 系统后台管理目录*/
| |img
| |inc
| |js
| |templets /* 系统后台的模板存放目录*/
|images
| |js
| |swfupload
| | |images
| | |plugins
|include /* 系统核心类库、函数存放目录*/
| |calendar /* 日历控件,就是日期编辑框跳出来的那个日历框*/
| |code /* datalistcp动态分页类的编码语言包*/
| |data /* 系统核心设置的数据,比如词库默认验证码*/
| |dialog /* 系统对话框存放目录,编辑器上面选择的类似于缩略图、插入图片文件*
| | |img
| |FCKeditor /* 编辑器存放目录,用的是开源编辑器FCK */
| | |editor
| | | |css
| | | | |images
| | | |dialog
| | | | |common
| | | | |fck_about
| | | | |fck_codes
| | | | |fck_flash
| | | | |fck_image
| | | | |fck_link
16
| | | |dtd
| | | |images
| | | | |smiley
| | | |js
| | | |lang
| | | |skins
| | | | |images
| |inc /* 这里存放一些用才引入的比较长的函数*/
| |taglib /* 就是那些用在模板中的标签存放的目录了*/
| | |channel /* 解析一些特殊字段的扩展函数库*/
| | |help /* 基本的标签说明*/
| |tpllib /* 动态模板标签,一般用在动态调用页面上面的标签*/
|install /* 安装目录*/
| |images
| |templates
|member /* 会员中心目录*/
| |images
| | |pay
| |inc /* 会员中心*/
| |js
| |paycenter /* 支付接口配置*/
| | |alipay
| | |cbpayment
| | |nps
| | |tenpay
| | |yeepay
| |space /* 会员中心的空间模板*/
| | |coffee
| | | |images
| | |company
| | | |images
| | |flower
| | | |images
| | |gray
| | | |images
| | |lxblog
| | | |images
| | |lxbrown
| | | |images
| | |lxvista
| | | |images
| | |person
| | | |blue
| | | |common
17
| | | | |css
| | | | |images
| | | |images
| | |pwblue
| | | |images
| | |pwglight
| | | |images
| | |pwlake
| | | |images
| | |pwpink
| | | |images
| |templets
|plus /* 系统插件存放目录*/
| |guestbook /* 留言板插件*/
| | |images
| |img
| | |face
| |paycenter
| | |alipay
| | |cbpayment
| | |nps
| | |tenpay
| | |yeepay
| |task
|special /* 专题存放目录*/
|templets /* 织梦模板存放目录*/
| |default /* 系统默认模板目录*/
| |images
| | |mood
| | |photo
| |js
| |style /* 默认模板存放的CSS样式*/
| |plus /* 插件页面的模板文件存放目录*/
| |system /* 系统核心底层模板文件夹*/
| |wap /* 那个wap模块的样式目录了*/
|uploads /* 文件上传存放目录*/
| |allimg /* 图片*/
| |flink /* 友情链接图片*/
| |litimg /* 缩略图*/
| |media /* 多媒体存放目录*/
| |soft /* 软件*/
| |userup /* 用户文件存放,例如头像*/