# BeitaBlog 主题开发文档

本文面向 BeitaBlog 2.0 及以上版本的主题开发。主题只负责前台展示，后台、安装器、公共函数和数据库逻辑不应放进主题目录。

## 主题加载流程

前台入口文件位于根目录：

- `index.php` 渲染 `home`
- `article.php` 渲染 `article`
- `category.php` 渲染 `category`
- `archive.php` 渲染 `archive`
- `404.php` 渲染 `404`

入口文件会调用 `renderThemeRoute($route)`。系统会先读取后台选择的主题，再按下面顺序寻找模板：

1. 当前主题
2. 当前主题的父主题
3. `default` 默认主题

如果主题缺少某个页面，系统会自动回退；如果后台要切换主题，则必须保证最终能解析出完整页面集合。

## 目录结构

推荐目录：

```text
themes/my_theme/
  theme.json
  bootstrap.php
  home.php
  article.php
  category.php
  archive.php
  404.php
  assets/
    css/theme.css
    js/theme.js
    preview.svg
  partials/
    head.php
    footer.php
```

说明：

- `theme.json` 是主题清单，后台读取主题名称、版本、预览图、继承关系。
- `bootstrap.php` 是主题公共入口，适合放渲染包装、局部片段、资源注入 helper。
- `assets/` 下放 CSS、JS、图片、字体等静态资源。
- `partials/` 下放公共片段，建议只做 head/footer 等低风险扩展。

## theme.json 字段

最小示例：

```json
{
  "name": "My Theme",
  "description": "自定义主题",
  "version": "1.0.0",
  "author": "Your Name",
  "extends": "default",
  "screenshot": "assets/preview.svg",
  "assets": {
    "styles": ["assets/css/theme.css"],
    "scripts": ["assets/js/theme.js"]
  },
  "selectable": true,
  "development_template": false
}
```

字段说明：

- `name`：后台显示的主题名称。
- `description`：后台主题列表说明。
- `version`：主题自身版本号，建议语义化版本。
- `author`：作者或团队。
- `extends`：父主题 key，通常填写 `default`。
- `screenshot`：后台预览图相对路径。
- `assets.styles`：默认注入的 CSS 文件列表，路径相对当前主题目录。
- `assets.scripts`：默认注入的 JS 文件列表，路径相对当前主题目录。
- `selectable`：是否允许后台切换到该主题。
- `development_template`：是否为开发模板。模板主题建议为 `true`，正式主题必须改为 `false`。

## 两种开发方式

继承式开发适合大多数主题：

```php
<?php
require_once __DIR__ . '/bootstrap.php';

starterThemeRender('home.php', [
    'route' => 'home',
    'body_class' => 'theme-my-home',
]);
```

这种方式会先渲染父主题页面，再注入当前主题 CSS、JS、partials 和 body class。优点是功能稳定，评论、付费、小说、404、分页等逻辑不会被遗漏。

完整重写适合深度定制：

```php
<?php
$settings = getAllSettings();
$articles = getArticles(1, 10);
?>
<!doctype html>
<html lang="zh-CN">
...
```

完整重写时必须自己处理页面数据、权限判断、评论、付费文章、小说内容、导航、备案信息、播放器等功能。除非明确需要，建议先继承再逐页替换。

## 常用公共函数

主题中可直接调用公共函数：

- `getAllSettings()` 获取全站设置。
- `getFrontendNavigationItems()` 获取前台导航。
- `renderFrontendNavigationItems($items)` 输出导航 HTML。
- `getArticles($page, $pageSize)` 获取文章列表。
- `getArticle($id)` 获取文章详情。
- `getCategories()` 获取分类。
- `getHomeUrl()` 获取首页地址。
- `getArticleUrl($id)` 获取文章地址。
- `getCategoryUrl($id)` 获取分类地址。
- `getArchiveUrl()` 获取归档地址。
- `getThemeAssetUrl($path, $themeKey)` 获取主题资源 URL。
- `generateThemeCSS($settings)` 输出后台配置驱动的 CSS 变量。
- `renderThemeBootScript($storageKey, $defaultMode)` 输出明暗主题启动脚本。
- `renderThemeToggleButton($storageKey)` 输出明暗主题按钮。
- `renderHtmlPlayerBlock($settings)` 输出 HTML 播放器。
- `e($value)` HTML 转义输出。

输出用户输入、文章标题、分类名、评论等内容时必须使用 `e()`。文章正文等已由系统处理的 HTML 内容可按现有默认主题方式输出。

## 路由注意事项

首页 `home.php`：

- 负责文章列表、置顶/分页/加载更多等展示。
- 建议保留搜索、分类入口、分页链接的可访问性。

文章页 `article.php`：

- 必须处理普通文章、独立页面、付费文章、密码文章、小说。
- 如果不想重写小说阅读逻辑，检测到 `$contentType === 'novel'` 时可沿用父主题或单独 include 小说模板。

分类页 `category.php`：

- 需要处理分类不存在时的 404。
- 页面 URL 和分页 URL 应优先使用系统 helper。

归档页 `archive.php`：

- 适合展示站点统计、月份归档、标签云。
- 查询自定义数据时，表名必须使用 `tbName('articles')` 这类方式，不要硬编码表前缀。

404 页 `404.php`：

- 可读取 `$not_found_code`、`$not_found_title`、`$not_found_message`、`$not_found_hint`。
- 外层入口会负责状态码，模板只负责展示。

## 开发模板使用步骤

1. 复制 `themes/starter_template` 为 `themes/my_theme`。
2. 修改 `theme.json`，至少更新 `name`、`version`、`author`、`selectable`、`development_template`。
3. 将 `theme.json` 中 `selectable` 改为 `true`，`development_template` 改为 `false`。
4. 将 CSS 中的 `.theme-starter_template` 改为 `.theme-my_theme`。
5. 在后台切换主题，先检查首页、文章页、分类页、归档页、404 页。
6. 再逐步替换具体页面结构。

## 安全约定

- 不要在主题中写数据库账号、支付密钥、SMTP 密码。
- 不要直接读取 `common/config.php` 内容。
- 不要在主题中执行上传、删除、恢复数据库等后台操作。
- 不要拼接未过滤的 SQL；必须使用 PDO 预处理和 `tbName()`。
- 不要将 `$_GET`、`$_POST`、`$_COOKIE` 原样输出到 HTML。
- 不要在模板里绕过文章状态、密码文章、付费文章、小说购买权限。

## 发布前检查

- `theme.json` 是合法 JSON。
- 预览图路径存在。
- 首页、文章页、分类页、归档页、404 页都能打开。
- 文章页覆盖普通文章、付费文章、密码文章、小说。
- 移动端导航和阅读页可用。
- 浏览器控制台没有 JavaScript 报错。
- `php -l themes/my_theme/*.php` 无语法错误。
