模板开发

CnLog 采用原生 PHP 模板引擎,通过 doAction() / addAction() 钩子机制实现插件扩展。模板位于 themes/default/ 目录。

一、系统概述

1.1 核心常量

常量说明示例值
CNLOG_ROOT系统根目录物理路径/var/www/html/
BLOG_URL站点根 URL(末尾带 /)https://blog.example.com/
THEME_PATH主题根目录物理路径/var/www/html/themes
CURRENT_THEME当前主题标识default
CONFIG_PATH配置文件目录物理路径/var/www/html/config/

1.2 安全要求

所有模板文件开头必须包含安全检查:

<?php if (!defined('CNLOG_ROOT')) { exit('error!'); } ?>

1.3 模板加载流程

用户请求 → index.php → 路由分发 → Controller
    → 设置模板变量
    → define THEME_PATH / CURRENT_THEME
    → require 模板文件
        → require header.php
        → 页面内容
        → require footer.php

二、目录结构

themes/default/
├── theme.php              # 主题元信息(返回 PHP 数组:name/version/author/description)
├── screenshot.png         # 主题预览截图(可选)
├── layout.php             # 全局布局(<head> + 导航栏 + 页脚 + 钩子挂载点)
├── assets/
│   └── style.css          # 主题样式表
├── images/                # 主题图片资源
├── index/                 # 前台页面模板
│   ├── index.php          # 首页
│   ├── apps.php           # 应用列表页
│   ├── detail.php         # 应用详情页
│   ├── download.php       # 下载页
│   └── docs.php           # 文档页
├── auth/                  # 认证页面
├── user/                  # 用户中心页面
└── payment/               # 支付页面

三、主题元信息 — theme.php

主题通过 theme.php 文件声明元信息,返回一个 PHP 数组:

<?php
return [
    'name'        => 'Cnlog 默认主题',
    'version'     => '1.0.0',
    'description' => '简洁现代的设计风格,支持响应式布局',
    'author'      => 'Cnlog Team',
    'author_url'  => 'https://cnlog.dev',
    'has_settings'=> true,
];
字段必填说明
name必填主题显示名称
version必填主题版本号
description可选主题描述
author可选作者名称
author_url可选作者主页
has_settings可选是否支持后台设置(默认 true)

四、主题设置 — 数据库持久化

主题设置通过后台 /admin/extension/theme-settings 管理,持久化存储在 app_theme_settings 表中(按主题标识 + 键名隔离)。前台 layout.php 启动时自动从数据库读取当前主题的设置。

4.1 默认设置项

键名类型说明
logo_typeradioLOGO 类型:text/image
logo_texttext文字 LOGO 内容
logo_imagetext+upload图片 LOGO URL(通过 /admin/upload/logo 上传)
nav_styleselect导航栏样式
color_schemeselect配色方案
show_sidebarswitch是否显示侧边栏
show_friendlinksswitch是否显示友情链接
footer_texttextarea页脚文字
custom_csstextarea自定义 CSS(追加到样式表末尾)

4.2 在模板中读取设置

// layout.php 顶部自动从数据库加载到 $themeSettings 数组
$logoType = $themeSettings['logo_type'] ?? 'text';
$logoText = $themeSettings['logo_text'] ?? site_config('site_name', 'Cnlog 应用中心');

五、页面模板详解

5.1 全局头部 — header.php

加载方式:layout.php 自动包含 <head> + 导航栏,通过 $content 变量注入页面内容

职责:初始化模板设置、输出 DOCTYPE 到 body、SEO meta 标签、CSS 加载、导航栏、搜索浮层。

变量类型说明
$site_namestring站点名称
$site_titlestring页面标题
$site_descriptionstring站点描述
$site_keywordsstring站点关键词
$configarray系统配置数组
$sortsarray分类列表
$currentPagestring当前页面标识

5.2 全局页脚 — footer.php

加载方式:layout.php 自动包含页脚部分,通过 doAction('footer_before') / doAction('footer_after') 提供钩子挂载点

职责:页脚钩子、版权信息、全局 JS 加载。

5.3 文章列表页 — log_list.php

路由:首页、分类页、标签页

变量类型说明
$logsarray文章列表数组
$page_urlstring/array分页 HTML
$pageint当前页码
$totalint文章总数
$categoryarray|null当前分类信息
$tagarray|null当前标签信息

5.4 单篇文章数据结构

键类型说明
idint文章 ID
titlestring文章标题
contentstring文章内容(HTML)
cover_imagestring封面图 URL
excerptstring摘要
created_atstring发布时间
viewsint浏览量
comment_countint评论数
category_idint分类 ID
category_namestring分类名称
author_nicknamestring作者昵称
is_topint置顶等级(0=否,1=首页,2=分类,3=全局)

5.5 文章详情页 — echo_log.php

路由:?r=article&id={id}

变量类型说明
$logarray当前文章详情(含 tags、like_count 等)
$neighborLogarray上一篇/下一篇
$commentsarray评论列表
$comments_countint评论数
$commentCsrfFieldstring评论表单 CSRF 字段
$reactionCsrfTokenstring点赞/收藏 CSRF Token

5.6 搜索页 — search.php

路由:?r=search&keyword={keyword}

变量类型说明
$keywordstring搜索关键词
$totalCountint结果总数

注意:搜索结果中的 $value['title'] 已包含高亮标签,不需要 htmlspecialchars() 转义。

5.7 个人中心 — profile.php / profile.clean.php

路由:?r=profile 或 ?r=profile&user_id={id}

变量类型说明
$userarray用户信息
$userArticlesarray用户文章列表
$favoriteArticlesarray收藏文章列表
$isSelfbool是否查看自己
$isLoggedInbool是否已登录

5.8 独立页面

登录、注册、找回密码、重置密码、邮箱验证页面不加载 header.php / footer.php,是完整的独立 HTML 页面。

文件路由说明
login.php?r=login用户登录
register.php?r=register用户注册
forgot_password.php?r=forgot_password找回密码
reset_password.php?r=resetPassword重置密码
email_verify.php?r=verifyEmailCode邮箱验证

六、侧边栏组件系统

6.1 组件注册机制

侧边栏组件通过 widget_{name} 命名的函数实现,由 renderSidebarWidgets() 统一调度渲染。

// 渲染侧边栏(自动读取后台配置)
renderSidebarWidgets();           // 自动识别当前页面类型
renderSidebarWidgets('profile');  // 强制指定页面类型

6.2 内置组件

组件名函数名说明缓存
bloggerwidget_blogger()博主信息—
searchwidget_search()搜索框—
recent_postswidget_recent_posts($limit=5)最新文章5 分钟
categorieswidget_categories()分类目录10 分钟
tagswidget_tags($limit=20)标签云10 分钟
linkswidget_links()友情链接10 分钟
recent_commentswidget_recent_comments($limit=5)最新评论5 分钟
archiveswidget_archives()文章归档15 分钟
hot_postswidget_hot_posts($limit=5)热门文章10 分钟
random_postswidget_random_posts($limit=5)随便看看—

6.3 自定义组件

if (!function_exists('widget_my_custom')) {
    function widget_my_custom() {
        ?>
        <div class="side">
            <h3>我的组件</h3>
            <p>自定义内容</p>
        </div>
        <?php
    }
}

在后台侧边栏配置中启用 my_custom 组件即可。每个组件渲染前后会自动触发 widget_{name}_before 和 widget_{name}_after 钩子。

七、URL 生成 — Url 类

URL 生成使用全局 url() 函数,自动适配伪静态/动态 URL(定义在 app/core/functions.php)。

方法参数返回示例
Url::log($id)文章 ID?r=article&id=1
Url::sort($sortId)分类 ID?r=category&id=2
Url::tag($tagId)标签 ID?r=tag&id=3
Url::guestbook()—?r=guestbook
Url::profile($userId, $tab, $page)用户ID, Tab, 页码?r=profile&user_id=1
Url::login()—?r=login
Url::register()—?r=register
Url::logout()—?r=logout
Url::rss()—?r=rss
Url::page($pageNum)页码分页 URL

注意:URL 格式取决于后台的伪静态配置。url() 函数根据 $GLOBALS['config'] 中的 rewrite_enabled 和 rewrite_suffix 自动适配。

八、钩子系统(模板开发视角)

8.1 核心机制

// 注册钩子
addAction('hook_name', 'my_callback_function', 10);
addAction('hook_name', function($param) { /* ... */ }, 10);

// 触发钩子(模板中)
doAction('hook_name');
doAction('hook_name', $param);
doAction('hook_name', ['key' => 'value']);

优先级:数字越小越先执行,默认为 10。

8.2 模板开发常用钩子

钩子名称位置说明
index_head</head> 前注入 CSS、meta 标签等
header_before<body> 后页面 body 开始后扩展
navbar导航栏内插入导航链接
header_after</header> 后头部后扩展
footer_before<footer> 前页脚前扩展
footer_after</footer> 后页脚后扩展
article_content_before文章内容前正文前扩展
article_content_after文章内容后正文后扩展
index_loglist_top列表顶部列表前扩展
index_loglist_bottom列表底部列表后扩展
sidebar_before侧边栏顶部所有组件前扩展
sidebar_after侧边栏底部所有组件后扩展

九、开发规范与注意事项

9.1 缩略图获取优先级

封面图 → 内容首图 → 默认图 default-cover.svg

9.2 CSRF 防护

所有 POST 表单必须包含 CSRF Token,模板中通过变量获取:

<?php echo $commentCsrfField; ?>  <!-- 评论表单 -->
<?php echo $csrfField; ?>          <!-- 独立页面 -->

9.3 前端资源加载

CSS 和 JS 建议通过 View::asset() 引用,自动附加版本号防止缓存:

<link rel="stylesheet" href="<?php echo View::asset('style.css'); ?>">
<script src="<?php echo View::asset('main.js'); ?>"></script>

9.4 创建自定义模板步骤

  1. 复制 themes/default/ 到新目录(如 themes/mytheme/)
  2. 修改 theme.php 填写主题信息
  3. 根据需要修改 layout.php 实现自定义布局和样式
  4. 在后台「扩展中心 → 主题管理」中启用新主题

9.5 安全注意事项

  • 所有模板文件开头必须包含 if (!defined('CNLOG_ROOT')) { exit('error!'); }
  • 输出用户输入内容时必须使用 htmlspecialchars() 转义
  • 搜索结果的标题和摘要已包含高亮标签,不需要额外转义
  • 表单必须包含 CSRF Token 字段