简介:这是一份基于 PHPCMS V9.6.6 深度优化的精简增强版源码包,重点解决老版本在 PHP8、MySQL8 及 HTTPS 环境下的兼容问题,并移除 PHPSSO认证、Flash依赖、视频库与在线升级等过时功能,适合需要快速搭建安全、现代后台的企业站或二次开发人员。资源共180个文件,以138个php核心逻辑、35个html模板页面及htaccess配置为主,压缩包仅436KB,结构精简为/CMS目录,便于部署维护。后台与前台界面全面重写,登录、锁屏、内容管理均采用响应式设计,所有上传场景统一升级为HTML5,并集成UEditor 4.16.1,支持水印、微信图文导入、Word粘贴、远程图片自动下载压缩等实用能力。新增二维码生成、IP地理定位、面包屑导航、JSON统一返回、安全路径处理等工具函数,字段系统拓展至站点、栏目、单页自定义字段及组图、关联字段等模式,关键词提取已对接讯飞、百度及本地API。目前已有53人学习下载,适合希望摆脱旧版技术栈、快速获得高兼容性CMS内核的开发者直接使用。 做了这么多年 PHP 老项目维护,我一直觉得“老”和“烂”是两回事。就拿 PHPCMS V9.6.6 来说,这套系统在当年确实风光过,但放到现在,直接装到 PHP 8 环境里基本就是一堆白屏和 Fatal error。可问题是,很多企业站、地方门户、行业信息站至今还在用这套老系统跑,数据量大、历史页面多、客户又不愿意掏重构的钱,那就只能走“精简增强”这条路。我自己手里就接过好几个这类的单子,需求高度一致:PHP8 下能跑、移动端能传图、编辑器别太古董、把用不上的 SSO 和视频模块拆干净。
这篇文章就是想把这套改造思路完整复盘一遍,给同样被困在 PHPCMS V9.6.6 维护坑里的朋友一个可落地、不劝退的参考方案。
1. 为什么还有人啃 V9.6.6 这块老骨头
先说难听的话:但凡能重构,没人愿意在新项目里选 PHPCMS V9.6.6。但现实情况是,市面上有大量存量站点,后台几百篇乃至上万篇内容,模板是当年美工一笔一笔调出来的,搜索收录权重也都在,客户预算可能只够“续命”,不够“投胎”。这种时候,我们做的不是重写,而是在保留原有业务和数据的前提下,把系统的运行底座换成现代环境。
我经手的一个客户,站点是本地行业协会的信息门户,文章、政策、行业动态加起来接近 8 万条,后台管理员还有十几个。原来的服务器还是 PHP 5.2 时代的环境,连 PHP 7 都升不上去,安全补丁根本没法打,后台三天两头被扫描。客户的要求很明确:“能用就行,别丢数据,别让我重新教编辑怎么发文章。” 这就是典型的不该重构、只该改造的场景。
那为什么选 V9.6.6 而不是其他版本?因为 V9 系列是 PHPCMS 生命周期里撑得最久、模板资源最多的一代,.NET 版和早期版本都没法比。而 9.6.6 作为最后的补丁版,修复了之前不少已知问题,虽然网上也能下到各种精简版,但自己动手改一套最稳妥,至少你知道每一行改了什么。
这套改造最终要达成四个目标:一是能在 PHP 8.1 下稳定跑,不报死错误;二是后台上传图片和附件不再依赖 Flash,手机浏览器也能传;三是默认的编辑器换成 UEditor,至少编辑体验跟得上时代;四是把单点登录(SSO)模块和视频模块彻底摘掉,减少依赖、缩小攻击面。
2. PHP8+ 适配:老代码与新时代的兼容方案
2.1 V9.6.6 在 PHP8 下跑不起来的真实原因
很多人以为老系统升级 PHP 版本,无非就是改几个报错。真动起手来你会发现,V9.6.6 在 PHP 8 下的问题不是一个一个蹦的,而是成片成片地爆。它当初是给 PHP 5.2 / 5.3 写的代码,PHP 8 等于把地基都给换了。
我自己踩下来,最典型的四类问题:
- mysql_函数族被移除*。V9.6.6 的底层数据库类大量使用
mysql_connect、mysql_fetch_array,这些函数在 PHP 7.0 就没了,到 PHP 8 更是直接白屏罪魁祸首。 - each() 函数被移除。老代码里到处是
while (list($key, $value) = each($array)),PHP 8 里each已经不存在,直接报 Fatal error。 - 动态属性弃用。PHP 8.2 开始,给没有显式声明属性的对象动态赋值会抛 Deprecated,到 8.x 后期甚至成了致命问题。PHPCMS 里不少模型类图省事,$this->xxx 直接动态挂属性。
- preg_replace() 的 /e 修饰符被移除。模板解析、搜索关键字高亮这些地方如果还在用 /e,PHP 7 废掉后到 PHP 8 就是直接错误。
如果只是把 php.ini 里的display_errors关掉,确实能挡住一部分报错画面,但 Fatal error 关不住,而且代码里逻辑坏了,页面上展示的也是残缺内容。所以正确路线是:先把代码修到能在 PHP8 下跑通,再考虑关错误显示。
2.2 我的迁移步骤:先建独立环境再动手
我改造时没有直接在现网服务器上动刀,而是用 Docker 建了一套 PHP 8.1 + MySQL 5.7 的独立环境,从原服务器导了一份数据库和完整文件下来,在本地反复试。这个环节千万别省,原因很直接:老系统没有一个固定的“标准配置”,每个站点的模板、插件、自定义标签都不一样,只有实际跑起来才知道哪个文件有问题。
具体步骤大致是这样:
- 完整备份原站所有文件和数据,备份文件不要只放同一台服务器,我习惯同时下载到本地。
- 用 PHP 8.1 容器跑原版代码,打开
error_reporting(E_ALL),把首页、列表页、内容页、后台登录页逐个打开,收集所有报错。 - 跟着报错顺序,一类一类修。先修数据库连接层,再修每类函数替换,最后修模板层和扩展包。
- 每修完一批,就重新跑一遍前台页面的回归清单,确保没有改坏。
- 全部修完并在本地跑满 48 小时后,再把改造版本同步到测试服务器,最后才部署到生产环境。
这套流程听着麻烦,但实际是最省时间的路径。如果在生产环境上边测边改,碰到一个 Fatal error 页面就挂一次,编辑部的同事会直接跑到你工位来。
2.3 几类典型报错的处理示例
数据库层:V9.6.6 的/phpcms/libs/classes/db_factory.class.php和mysql.class.php里,老代码全是mysql_*风格。最省事的改法不是逐行改函数名,而是写一个兼容函数层,把mysql_connect等映射到mysqli_版本。比如:
if (!function_exists('mysql_connect')) { function mysql_connect($host, $user, $password) { global $mysqli_link; $mysqli_link = mysqli_connect($host, $user, $password); return $mysqli_link; } function mysql_select_db($database, $link = null) { global $mysqli_link; $link = $link ?: $mysqli_link; return mysqli_select_db($link, $database); } function mysql_query($sql, $link = null) { global $mysqli_link; $link = $link ?: $mysqli_link; return mysqli_query($link, $sql); } function mysql_fetch_array($result) { return mysqli_fetch_array($result); } function mysql_fetch_assoc($result) { return mysqli_fetch_assoc($result); } function mysql_num_rows($result) { return mysqli_num_rows($result); } function mysql_insert_id($link = null) { global $mysqli_link; $link = $link ?: $mysqli_link; return mysqli_insert_id($link); } function mysql_error($link = null) { global $mysqli_link; $link = $link ?: $mysqli_link; return mysqli_error($link); } function mysql_real_escape_string($string, $link = null) { global $mysqli_link; $link = $link ?: $mysqli_link; return mysqli_real_escape_string($link, $string); } }这段兼容层可以放在入口文件里,比如index.php或phpcms/base.php中通过require引入。需要注意,老代码里mysql_query调用时有时候会不传连接参数,所以在兼容函数里通过global $mysqli_link兜底,能避免改业务代码的时候漏掉某个函数调用。
each() 替换:老代码里长得像这样的:
while (list($key, $value) = each($list)) { // ... }改成 foreach 最直接:
foreach ($list as $key => $value) { // ... }如果循环内还有each的指针用法,比如配合current、next,那就要小心一些,按数组指针逻辑逐段改造,不能粗暴替换。但绝大多数场景就是普通遍历,foreach 无缝替换。
动态属性问题:最简单的是在类定义里补上属性声明。比如/phpcms/model/content_model.class.php这类模型类,把常用字段先public $xxx;声明出来。另一个方案是给对应类加#[AllowDynamicProperties]属性,但这是偷懒做法,我一般只在第三方插件里用,核心模型能补声明就补声明,毕竟后面维护的人也是咱们自己人。
preg_replace /e 问题:模板引擎里处理标签替换时,老代码会这么写:
$content = preg_replace("/\{(\$[\w\d\[\]\'\"]+)\}/e", "\$this->template_parse(\\1)", $content);PHP 7 之后 /e 被废,必须改成preg_replace_callback:
$content = preg_replace_callback('/\{(\$[\w\d\[\]\'\"]+)\}/', function($matches) { return $this->template_parse($matches[1]); }, $content);这套适配做完,V9.6.6 在 PHP 8.1 下能正常跑起来,但注意不要盲目上 PHP 8.2 或 8.3。越新的版本对动态属性和隐式转换管得越严,V9.6.6 这种老骨架没必要追新,稳定优先。
3. 去SSO、卸掉视频模块:减法改造的完整路径
3.1 为什么选择去掉SSO
PHPCMS V9 的 SSO 单点登录模块本来是为了多站点同步登录设计的,例如总站和子站共享用户登录态。但在绝大多数单站点业务里,这玩意儿不但没用,还会带来一堆额外开销:用户登录时要请求同步接口,接口地址失效或者域名变了,登录逻辑就异常;更烦的是它拉长了登录链路,排查问题都得多看好几个文件。
我改造的那个客户站点就是纯单站,后台管理员只有十几个,前台用户注册量也很少,SSO 纯属负担。而且 SSO 相关的同步脚本是历史遗留漏洞的重灾区,删掉之后安全面会显著缩小。
3.2 去掉SSO的实操路径
我先确认了核心入口,PHPCMS V9.6.6 的会员相关逻辑主要在/phpcms/modules/member/目录,其中 login 和 register 控制器在初始化时会载入 SSO 客户端类。
实际操作时,我把这些位置逐个处理:
- 在
member/index.php构造方法里,删除或注释掉对sync_client的初始化调用,保留正常的session_start和用户数据读取逻辑。 - 删除
/phpcms/modules/member/classes/下的 SSO 客户端类引用。 - 打开后台模板中会员中心的头部和登录页模板,去掉加载同步脚本的
<script>标签,这些标签通常指向index.php?m=member&c=index&a=...的同步请求。 - 检查
api/目录下是否有老版本的 SSO 接口文件,确认没有其他模块调用后一并移除。 - 清理数据库里和 SSO 相关的配置项,比如
v9_sso开头的表,以及v9_setting里跟同步相关的配置记录。
改完之后,登录流程变成纯粹的本地校验:用户提交用户名密码,PHP 查member表,校验密码,写入 session。登录退出都不会再发外部请求,简单直接,出问题也好查。
3.3 卸掉视频模块:不只是删个目录
视频模块在 V9.6.6 里是一个独立功能包,路径一般是/phpcms/modules/video/。如果站点没用它,留着不光是代码冗余,后台菜单多一块、数据库多几张表,而且老视频模块涉及上传播放逻辑,很容易被扫描工具盯上。
删除时我按以下顺序操作:
- 在后台“模块管理”里先卸载视频模块,让它从系统模块列表中移除。
- 删除
/phpcms/modules/video/目录以及模板目录里对应的video模板文件夹。 - 删除数据库里视频模块的数据表,我通常先查
SHOW TABLES LIKE '%video%',确认是视频模块专用表再 drop。 - 清理后台菜单表
menu里的视频模块菜单记录,避免后台出现“无模块”的空白菜单项。 - 检查内容模型里是否绑定了视频字段,如果有,要把相关字段从模型里解除。
这一步做完了,整个系统的路由、菜单、数据表都会清爽很多,后台登录之后的加载速度也明显快。
3.4 减法后的收益
不夸张地说,去掉 SSO 和视频模块之后,站点后台登录从原来可能触发 3 到 4 个外部请求变成了 1 个本地请求,前台请求链路也短了。安全上更明显,少了两个历史代码包,扫描器能打的点少了很多。几个改造单子里,客户最直接的感受就是“后台不卡了、登录不转圈了”。
4. H5上传与UEditor整合:把上传入口搬到移动端时代
4.1 原版上传组件为什么在移动端废了
V9.6.6 自带的附件上传组件是基于 Flash 的,PC 浏览器里还能忍,但到了手机浏览器,Flash 直接不工作,点上传按钮毫无反应。现在很多编辑直接用手机登录后台发图文,这个环节不解决,整个后台在移动端就是半残废状态。同时,UEditor 原装自带的上传也是 Flash 附件方式,需要一并替换为 H5 方案。
UEditor 本身是百度开源的老编辑器,但它的 PHP 上传接口设计得还算独立,和 PHPCMS 的附件表对接并不复杂。很多 PHPCMS 二开项目早就这么干了,核心思路是:前端用 UEditor 的 H5 上传能力,后端把上传请求转到 PHPCMS 的附件处理逻辑。
4.2 H5上传改造:前端代码思路与后端兼容
后端接口我保留了 PHPCMS 原有的upload_json.php逻辑,接收$_FILES后调用附件处理函数,生成缩略图、水印、返回附件 ID 和 URL,这样改造后原有内容模型的图片字段、缩略图字段都不需要动。
前端我用原生 XMLHttpRequest 2 写一个统一的上传入口,支持多图选择和进度显示。核心代码思路如下:
function uploadFile(file, url, callback) { var formData = new FormData(); formData.append('file', file); formData.append('module', 'content'); formData.append('catid', catid); var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function () { if (xhr.status === 200) { var res = JSON.parse(xhr.responseText); callback(res); } else { alert('上传失败,服务器返回 ' + xhr.status); } }; xhr.upload.onprogress = function (e) { if (e.lengthComputable) { var percent = Math.round(e.loaded / e.total * 100); // 更新进度条 } }; xhr.send(formData); }这里有个小坑:PHPCMS 的附件上传接口对module、catid参数有依赖,直接传空字符串可能导致附件表归类错误,所以前端一定要把这些参数带上。
另外,老接口对上传文件大小默认限制在 2MB 左右,如果编辑要传高清图、PDF,就要同步修改php.ini的upload_max_filesize、post_max_size,并修改 PHPCMS 后台“附件设置”里的允许大小和扩展名白名单。别只看百度编辑器配置,PHP 侧的upload_max_filesize是最容易漏的。
4.3 UEditor整合:版本选择与目录配置
UEditor 我选的是经典的 1.4.3 PHP 版,虽然它也不再更新,但胜在稳定、文档多、踩坑记录全。整合时主要按这几步:
- 下载
ueditor/目录,放到站点根目录,比如/statics/ueditor/。 - 在后台内容模型里把默认编辑器字段替换为 UEditor,通常就是改内容模型的字段类型和后台模板里的加载脚本。
- 配置
ueditor/php/config.json,把图片上传接口指向 PHPCMS 的上传入口,或者直接使用 UEditor 自带的controller.php,再在controller.php里引入 PHPCMS 初始化文件,复用附件表和权限校验。 - 在编辑器初始化时,设置
serverUrl指向正确的后端入口,同时打开图片在线管理、涂鸦、截图上传这些能力,这些正好都是 H5 友好的功能。
我习惯直接改 UEditor 的controller.php,在顶部引入 PHPCMS 的phpcms/base.php,然后上传成功后把附件 ID 写入attachment表,这样后台上传管理里能看到编辑器上传的文件,不产生“上传了但管理后台查不到”的孤儿附件。
4.4 改造后的上传链路与常见问题
改造完之后的完整链路是:编辑器或列表页上传按钮触发 H5 文件选择 → FormData 提交到上传接口 → PHPCMS 校验权限和格式 → 保存附件记录、生成缩略图和水印 → 返回 JSON 给前端 → 前端把 URL 填入编辑器或表单。
实际运行中,我遇过最多的三类问题,都在这里提前说:
- 上传成功但编辑器不显示图片。通常是返回 JSON 格式不对,UEditor 要求固定格式如
{"state":"SUCCESS","url":"...","title":"...","original":"..."},PHPCMS 原接口的返回字段名可能不一致,需要做一层字段映射。 - 手机传图方向不对。手机拍的照片带着 EXIF 信息,PC 浏览器显示会歪。可以在上传接口里用 PHP 的
exif_read_data读取方向并调用imagejpeg重新校正,或者在前端用 canvas 压缩时顺带纠正方向。 - 上传 .mp4 被拦。默认扩展名白名单里没有视频格式,如果确实要传视频,在后台上传设置里把
mp4、webm加进去。但要注意,老系统的播放器对 mp4 支持也不算好,如果只是临时需求,建议让客户直接传外链视频。
5. 上线前的验证清单与几个容易翻车的边角料
5.1 从后台到前台的核心回归用例
改造完成不代表就能上线,我一般会列一个回归清单,前后台都过一遍。后台重点关注:管理员登录、退出、栏目管理、内容发布、图片上传、附件管理、模板更新、缓存更新、会员管理。前台重点关注:首页、栏目列表页、内容详情页、搜索页、伪静态规则、会员登录注册、评论/留言(如果站点有)。
这里给你一个我在项目里反复用到的最小回归确认表,照着点一遍基本能覆盖 80% 的问题:
- 后台登录是否走通,退出后 session 是否彻底清掉。
- 后台发布一篇文章,插入一张图片,保存后到前台看是否正确输出。
- 用手机浏览器登录后台,上传一张大图,确认是否不依赖 Flash。
- 用 UEditor 编辑器发布一段带格式、带图片的正文,确认前端样式正常。
- 把站点默认页、栏目页、详情页各打开几个,确认没有 PHP 报错和样式丢失。
- 测试搜索功能,搜索一个包含特殊字符的关键词,确认不再报错。
- 测试后台“更新缓存”和“生成静态页”功能,确认模板缓存目录可写、生成逻辑正常。
5.2 那些不报错但会出问题的配置
平时最容易翻车的反而是“不报错但行为怪异”的配置。PHP 8 环境下,有几个点一定要提前检查:
session.save_path是否可写。老系统部署经常忽略这个,一旦目录不可写,前台匿名用户可能正常,后台登录直接失败,而且日志里不一定有明显错误。date.timezone是否设置。不设置时 PHP 8 会抛警告,更关键的是老代码发布时间戳会错 8 小时,导致新发布的文章显示时间不对。- 模板缓存目录权限。PHPCMS 的模板缓存通常在
/caches/template/,权限不对时后台刚保存内容,前台还是老页面,特别容易让人误以为修改没生效。 - 伪静态规则。V9.6.6 自带
.htaccess或者 nginx rewrite 规则,很多环境切到 PHP 8 后,URL 重写规则没同步,导致栏目页、详情页 404。这个问题和 PHP 版本无关,但升级过程中最容易忽略。 - 数据库连接字符集。老配置里如果没强制
SET NAMES utf8,PHP 8 下 mysqli 默认字符集可能不对,中文字段出现乱码,但页面不报错,非常隐蔽。建议启动时强制mysqli_set_charset($link, 'utf8')。
5.3 部署时的几个安全习惯
不是纯粹的 PHPCMS 改造,但既然碰上了老系统,顺手把安全底子补一补值得做:
- 后台路径从
/index.php?m=admin这种默认入口改掉,或者至少把admin目录改名。 - 关掉后台的目录列表,避免
/caches/等目录被扫描。 - 删除
/install/目录,避免重装风险。 - 把数据库备份文件放在 web 根目录外,或者至少设置访问权限禁止下载。
- 后台强制使用 HTTPS,老系统的表单提交用的是相对路径,一般不影响,套一层证书后登录密码就不再裸奔。
6. 我个人做这套改造时的一些操作习惯
回到开头那句话,老项目改造最重要的不是技术多炫,而是别把客户正在用的东西弄坏。我踩过几次坑之后,慢慢养成了一些固定习惯,分享给准备动手的人。
第一,永远先做减法,再做加法,最后升环境。先把 SSO、视频模块这些多余的东西摘掉,减少代码量,再换编辑器、改上传,最后升 PHP 版本时,涉及的文件数量已经少了很多,排查面也小。反过来先升级再做减法,报错会混在一起,很难定位。
第二,本地环境要模拟生产环境,不只是 PHP 版本一致,MySQL 版本、nginx/apache 规则、上传目录权限都要尽量一致。PHPCMS 在 Apache 和 nginx 下的伪静态规则不一样,容易在本地明明好好的、部署到线上就 404。有条件的话,用 Docker Compose 把一套和线上差不多的环境固定下来,改起来心里有底。
第三,每一次改动都留一个“可回滚点”。我习惯每完成一个功能点就打个 git 标签或者做一份增量备份,这样到时候出了问题,能快速回到上一个稳定状态,而不是从头开始查。老系统不比新框架,没有自动迁移、没有完善的测试,人工回滚能力就是保命符。
第四,控制改造范围,别顺手“优化”业务逻辑。很多人一打开老代码就手痒,这个 SQL 可以优化、那个函数写得烂,想顺手全改了。我的建议是别。改造目标是让 V9.6.6 在 PHP8 下稳定运行、补齐 H5 上传、换好编辑器、去掉多余模块,而不是把它改成一个新系统。范围一旦扩大,测试量跟着翻倍,交付日期基本就别想了。
最后说一句实在的:PHPCMS V9.6.6 这套东西虽然老旧,但只要你摸清了它的脾气,整一次“精简增强”也没那么可怕。这套改造方案完整走下来,站点的运行环境整体前进了好几代,后台编辑体验也有了质的提升,客户那边基本不会再有“后台卡、登录飘、传图失败”的抱怨。对维护老项目的朋友来说,这就是实实在在的价值。
本文还有配套的精品资源,点击获取