DaoKecms 应用中心

DaoKecms 开发者文档

面向第三方开发者的模板与插件开发调用规范。所有约定均与当前版本源码一致,按本文开发可确保资源顺利通过安装校验并上架应用中心。开发交流与上架事宜请联系官方。

环境要求:PHP 7.4 及以上(推荐 8.x)、MySQL / MariaDB。DaoKecms 不依赖 Composer 与 Node,模板与插件均为纯 PHP 目录结构。

1开发前必读

DaoKecms 由两部分组成,开发者只需要关心资源本身的开发,上架与分发由应用中心完成:

部分说明开发者相关点
主程序(CMS)内容管理核心,含后台与前台渲染框架模板、插件都安装在主程序内运行
应用中心(本站)模板 / 插件的分发市场,含授权中心资源打包后提交到这里上架分发

两套体系的目录对应关系:

tuanyucms/                      主程序
├── templates/{slug}/          模板目录(模板开发的地方)
├── plugins/{slug}/             插件目录(插件开发的地方)
└── market/                     应用中心(独立部署,与官网放在一起)
    ├── source/templates/{slug}/   上架用模板源(打包发市场)
    ├── source/plugins/{slug}/    上架用插件源(打包发市场)
    └── tools/publish.php         上架打包工具

slug 命名规则(模板与插件通用):仅允许 a-z A-Z 0-9 _ -,全局唯一,是资源在市场与站点内的唯一标识。建议使用简短有辨识度的英文单词,如 corporate、data_export。

本地开发时直接把资源放在主程序 templates/、plugins/ 目录下调试;开发完成后放入 market/source/ 并登记清单,用打包工具生成 zip 上架(见第 5 章)。

2模板开发

模板决定站点的前台外观与交互。一套模板就是一个目录:若干 PHP 页面 + 静态资源 + 元数据清单。

2.1 目录结构与必需文件

模板目录 templates/{slug}/,安装校验要求以下 7 个文件必需存在,缺失任何一个 zip 都会被拒绝安装:

文件说明
index.php首页模板
header.php页头(全站共用,由框架自动加载)
footer.php页脚(全站共用,由框架自动加载)
article.php文章详情页
category.php分类列表页
page.php单页(关于我们等)
404.php404 页

其余页面按需提供,前台按 mod 参数(页面类型)取 {mod}.php 渲染,可选 mod 共 10 个:

mod页面mod页面
index首页(必需)cps导购列表页
category分类页(必需)cpsdetail导购详情页
article文章页(必需)search搜索结果页
page单页(必需)message留言页
tag标签聚合页like点赞列表页

未提供的页面自动回退到默认模板 templates/default/ 下的同名文件,不会报错。

单页型模板的 stub 写法

很多模板(发布页、表白页等)只有一个视觉首页,所有 mod 都渲染同一页面。此时把每个可选 mod 文件写成一行的 stub 即可:

<?php
/** {模板名} v1.0 · 文件:tag.php */
require __DIR__ . '/index.php';

即 tag.php、search.php、like.php 等全部转给 index.php 渲染,用户从任何入口进来看到的都是你的首页。

必需的 7 个文件即使内容是 stub 也必须存在(404.php 除外——它本身就是必需项,不可省略)。本地内置模板可以缺省 404.php 回退默认模板,但上架市场的 zip 会被校验拒绝。

2.2 渲染机制:不要手动 include 页头页脚

框架渲染顺序固定为:

header.php  →  {mod}.php  →  footer.php

也就是说,页面模板文件(如 article.php)内不要再写 include 'header.php',否则页头页脚会重复输出两次。

框架在各 mod 分支已备好数据,模板内直接使用变量:

mod可用变量
全部$site_name 站点名、$pages 单页列表、$categories 栏目、$page 当前页码、$pageSize 每页条数
index$banners 轮播、$articles 最新文章、$news、$stats
category$cat 当前栏目、$list 文章列表、$total 总数
article$art 文章、$prev / $next 上下篇、$related 相关推荐
page$alias 别名、$pg 单页数据
search$keyword 搜索词、$list、$total

2.3 template.json 元数据

模板根目录的 template.json 是安装与市场展示的元数据来源,字段如下:

{
  "slug": "mytpl",                    // 唯一标识,上架 zip 必填;仅字母数字下划线
  "title": "我的企业官网模板",         // 显示名
  "desc": "响应式企业官网模板……",      // 一句话描述
  "version": "1.0.0",
  "author": "你的名字",
  "preview": "preview.svg",           // 预览图文件名(目录内,支持 svg/png/jpg)
  "features": ["响应式", "多端适配"]   // 特性标签(数组)
}
本地内置模板的 slug 由目录名决定可省略;上架市场的 zip 内 template.json 必须含 slug 字段,否则安装端直接拒绝。

2.4 模板函数速查

模板中可直接调用的框架函数(均在主程序加载阶段就绪):

路径与资源

函数说明
template_path($file)当前模板文件的绝对路径,缺失自动回退默认模板
template_url($file)当前模板静态资源网址(CSS / JS / 图片引用用它)
url_asset($path)相对路径统一转可访问网址(上传图、市场源自动改写)
tpl_root()站点根目录绝对路径

配置与输出

函数说明
C($key, $default)读配置,空值回退默认值
e($str)HTML 转义输出。一切动态值输出前必须过它
post($key) / get($key)取请求参数
str_cut($str, $len)去标签截断(列表摘要用)
fmt_date($ts, $format)日期格式化

内容取数

函数说明
get_articles($where, $params, $limit, $order)通用文章列表(只取已发布)
get_articles_by_cat($catId, $limit, $offset)分类文章(含定时上下架)
count_articles_by_cat($catId)分类文章总数
get_article($id)单篇文章(含可见性校验)
get_article_prev_next($catId, $id, $dir)上一篇 / 下一篇
get_categories() / get_nav_categories()全部分类 / 导航分类
get_banners() / get_links() / get_ads($position)轮播 / 友链 / 广告位
get_page_by_alias($alias) / get_pages()单页取数
get_goods($platform, $limit, $order)导购商品列表
get_tags() / get_articles_by_tag(...)标签聚合

网址生成

函数生成
url_index()首页
url_article($id)文章页
url_category($id, $page)分类页
url_page($alias)单页
url_search($kw, $page)搜索页
url_tag($slug, $page)标签页

SEO 与分页

函数说明
seo_title() / seo_keywords() / seo_description()读当前页 SEO(有回退链)
breadcrumb($items)面包屑,传 ['名称' => '网址'],网址为空输出纯文本
pagination($total, $page, $pageSize, $baseUrl, $show)分页条,网址用 {page} 占位符

2.5 后台配置页 admin-config.php

想让站长不改代码就能调整模板(换标题、换图、开关栏目),就提供一个后台配置页。文件名固定为 admin-config.php,放在模板根目录,首行必须是网关守卫:

<?php
/** {模板名} 后台配置页 */
if (!defined('DK_TPL_CFG_INC')) { http_response_code(403); exit('Access denied'); }

/* 保存(POST) */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    /* 文件上传:走 save_upload() */
    if (!empty($_FILES['mytpl_logo_file']['name'])
        && $_FILES['mytpl_logo_file']['error'] === UPLOAD_ERR_OK) {
        $up = save_upload('mytpl_logo_file', 'uploads');
        if ($up !== '') set_config('mytpl_logo', $up);
    }
    /* 文本字段 */
    set_config('mytpl_title', trim((string) post('mytpl_title')));
    /* 复杂结构存 JSON */
    set_config('mytpl_links', json_encode($links, JSON_UNESCAPED_UNICODE));
    /* 开关:checkbox 未勾选时必须显式存 '0' */
    set_config('mytpl_show_title', post('mytpl_show_title') ? '1' : '0');
}

/* 读值渲染表单 */
$title = (string) C('mytpl_title', '');
$links = json_decode((string) C('mytpl_links', ''), true);

规则要点:

前台模板文件用同款 C('mytpl_title', '默认值') 读取配置即可。

2.6 SEO 与静态资源

最重要的一个坑:渲染顺序是 header 先于页面模板执行,页面模板里再调 set_seo() 已经晚了——<head> 里的标题早已输出。需要按页面定制 SEO 时,一律在 header.php 顶部按 $mod 分支前置计算:
<?php
/* header.php 顶部 */
$active_mod = $mod ?? 'index';
$seo_t = $GLOBALS['_seo']['title'] ?? null;   // 先取框架默认
$seo_k = seo_keywords();
$seo_d = seo_description();

if ($active_mod === 'index') {                // 再按 mod 覆盖
    $seo_t = $mytplBrand . ' - ' . $mytplSlogan;
    $seo_k = '企业建站,官网模板,' . $mytplBrand;
    $seo_d = '一站式企业官网解决方案';
} elseif ($active_mod === 'article' && isset($art)) {
    $seo_t = str_cut($art['title'], 60);
    $seo_d = str_cut(strip_tags($art['content']), 120);
}
?>
<title><?php echo e($seo_t ?: $site_name); ?></title>

CSS / JS 版本号

静态资源一律经 template_url() 引用,并带文件修改时间做版本号,改版后浏览器自动刷新缓存:

<link rel="stylesheet" href="<?php echo template_url('static/style.css'); ?>?v=<?php echo (int) (@filemtime(template_path('static/style.css')) ?: 1); ?>">

输出安全

2.7 在模板里调用插件(防白屏守卫)

插件可能被站长停用,模板直接调用插件函数会致命错误。标准守卫写法:

<?php
/* header.php:插件核心函数不存在时,手动 require 插件 helpers */
if (!function_exists('adslot') && function_exists('plugin_dir')) {
    $asDir = plugin_dir('adslot');          // 目标插件目录(插件不存在返回空串)
    if ($asDir !== '' && is_file($asDir . '/helpers/adslot-core.php')) {
        require_once $asDir . '/helpers/adslot-core.php';
    }
    unset($asDir);
}
?>
<?php echo function_exists('adslot_head_html') ? adslot_head_html() : ''; ?>
插件自治渲染页(虚拟页)不经过框架注入,广告位等浮层需要在模板 header.php / footer.php 里显式调 adslot_head_html() / adslot_foot_html()(带上面的守卫)。

3插件开发

插件用于扩展功能:挂内容钩子、加前台虚拟页、挂后台管理页、建自己的数据表。一个插件 = 一个目录 + 一个 JSON 清单。

3.1 目录结构与 plugin.json

插件目录 plugins/{slug}/,主入口默认 main.php(可经清单 main 字段改名),清单文件 plugin.json 字段全表:

字段类型说明
slugstring唯一标识,仅 a-zA-Z0-9_-,必填
titlestring显示名(截断 100 字)
descstring描述(截断 255 字)
versionstring版本号,默认 1.0.0
authorstring作者(截断 100 字)
mainstring主入口文件名,默认 main.php,须匹配 ^[a-zA-Z0-9_\-/]+\.php$
hooksstring[]声明使用的钩子名(供后台展示,如 ["head","footer"])
schemastring建表 SQL 文件相对路径(如 schema.sql,规则见 3.6)
require.phpstring最低 PHP 版本,如 "7.4"
require.cmsstring最低 DaoKecms 版本
featuresstring[]特性标签(市场页展示)
builtinbool仅官方内置插件使用,第三方勿填

完整示例:

{
  "slug": "data_export",
  "title": "数据导出",
  "desc": "一键导出文章与栏目数据。",
  "version": "1.0.0",
  "author": "你的名字",
  "main": "main.php",
  "hooks": ["footer", "admin_menu"],
  "schema": "schema.sql",
  "require": { "php": "7.4", "cms": "1.0.0" },
  "features": ["CSV 导出", "后台一键操作"]
}

3.2 入口与生命周期

框架每个请求会对所有已启用插件 require_once plugins/{slug}/main.php(按后台排序加载,单个插件抛异常不会拖垮整站)。main.php 的顶层代码就是注册点:

<?php
/** 数据导出插件入口 */
defined('ROOT') || exit('Access denied');

plugin_on('footer', 'myplg_footer_html');       // 挂钩子:返回字符串
plugin_mod('datadown', 'myplg_page');            // 注册虚拟页 ?mod=datadown
myplg_ensure_tables();                           // 幂等建表(推荐 CREATE TABLE IF NOT EXISTS)

function myplg_footer_html() {
    return '<div id="myplg-foot">...</div>';    // 返回字符串,勿 echo
}
function myplg_page($mod) { ... 自行输出并 exit; }
  • 函数名一律带插件 slug 前缀(如 myplg_),避免与其他插件 / 框架函数撞名。
  • 需要区分前台后台时,用 defined('DK_ADMIN') 或检查当前脚本路径判断。
  • plugin_dir($slug) 取插件绝对路径、plugin_url($slug, $file) 取插件资源网址。

3.3 钩子清单

注册:plugin_on($name, $cb);触发:plugin_fire($name, $payload)(返回各回调返回值数组)。当前框架提供的钩子:

钩子名触发时机回调约定
head前台 </head> 之前返回字符串追加进 head(不要 echo)
footer前台 </body> 之前同上
admin_menu后台菜单构建(预留扩展位)返回 [键 => [标题, 完整网址]]
article_saved后台文章新增 / 更新后payload:['id'=>.., 'action'=>'create'|'update', 'data'=>..]
article_deleted后台文章删除后payload:['id'=>.., 'ids'=>[..]]
/* 钩子回调示例:文章保存后同步 */
plugin_on('article_saved', function ($payload) {
    if (($payload['action'] ?? '') !== 'create') return;
    DB::insert('myplg_index', [
        'aid'  => (int) $payload['id'],
        'title' => (string) ($payload['data']['title'] ?? ''),
    ]);
});

3.4 前台虚拟页 ?mod=xxx

插件可注册前台独立页面(框架内置 mod 之外的地址),用户访问 ?mod=datadown 时插件完全接管输出:

plugin_mod('datadown', 'myplg_page');   // mod 名仅小写字母数字下划线

function myplg_page($mod) {
    /* 需要模板壳可 include,并自行 exit */
    include template_path('header.php');
    echo '<div class="container">插件页面内容</div>';
    include template_path('footer.php');
    exit;
}
虚拟页不经过框架的钩子注入流程,广告位等全站浮层不会自动出现,需要的话在页面里显式调用(带 3.3 的守卫写法)。

3.5 后台管理页

插件目录内的 admin.php、admin_config.php、admin_setting.php 及其余 admin_*.php(不含 admin_*_api.php)会被后台「插件管理」自动发现,出现「管理」按钮。页面经统一网关渲染:

admin/plugin_page.php?slug=data_export&f=admin.php

网关已做:登录校验、POST CSRF 校验、插件启用状态校验、文件名防穿越。页面内规则:

  • 入口网址用 plugin_admin_page_url('data_export', 'admin.php') 生成,不要手拼。
  • 所有 POST 表单必须带:<input type="hidden" name="_csrf" value="<?php echo e(csrf_token()); ?>">。
  • 可定义 $pageTitle 覆盖页标题(默认取 plugin.json 的 title)。
  • 子页分发参数用 p(&p=slots),f 被网关占用。
<?php
/** 插件后台页样例(admin.php,分发器模式) */
defined('ROOT') || exit('Access denied');
$selfUrl = plugin_admin_page_url('data_export', 'admin.php');
$p = get('p', 'list');
if ($p === 'list')        require __DIR__ . '/admin_list.php';
elseif ($p === 'export') require __DIR__ . '/admin_export.php';

3.6 数据表

表名必须以 {数据库前缀}{插件slug}_ 开头,这是卸载清理与隔离的依据。建表 SQL 写在清单 schema 指向的文件里(如 schema.sql),安装 / 覆盖升级时自动执行:

-- schema.sql:仅允许 CREATE TABLE / ALTER TABLE,表名前缀用 {{prefix}} 占位
CREATE TABLE IF NOT EXISTS `{{prefix}}data_export_logs` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `aid` INT UNSIGNED NOT NULL DEFAULT 0,
  `created_at` INT UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_aid` (`aid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

框架数据库类常用方法:

方法说明
DB::row($sql, $params) / DB::rows(...)单行 / 多行(原生 SQL,表名自己拼 DB_PREFIX)
DB::value($sql, $params)单值
DB::insert($table, $data)插入,返回自增 id。表名不带头(框架自动拼前缀)
DB::update($table, $data, $where, $wparams)更新,返回影响行数
DB::delete($table, $where, $wparams)删除
DB::count($table, $where, $wparams)计数
所有 SQL 参数一律走占位符(?)传参,禁止字符串拼接用户输入。DB::insert/update/delete/count 传不带前缀的短表名,DB::row/rows/query/value 传完整 SQL 时才自己拼前缀。

3.7 启用、停用与卸载

  • 启用前自动语法预检:后台对整个插件目录跑 php -l,有语法错误直接拒绝启用——发布前请自查。
  • 停用:status=0,主入口不再被加载;模板等处直接调插件函数会白屏,所以调用方要按 2.7 的守卫写法防御。
  • 熔断:站点存在 plugins/.disabled 文件时所有插件一律不加载(排障开关)。
  • 卸载:删除插件目录与注册行;勾选清理数据时 DROP 所有 {前缀}{slug}_ 开头的表——所以表名前缀必须严格,否则清理不到或误删别人数据。

4内置插件调用

系统随主程序分发六个内置插件:广告位(adslot)、用户中心(member)、VIP 会员(vip)、下载站(download)、接口对接 CPS(cps_connect)、APP 应用下载导航(appnav)。内置插件在 plugin.json 声明 "builtin": true,首次访问自动登记且默认停用,站长在后台按需启用;可停用、不可卸载,数据表始终保留(建表由插件自愈函数保证,不依赖安装流程)。模板调用它们的函数前,先按 2.7 的守卫写法确认函数存在。

本手册详述其中三个最常被模板调用的插件(adslot / appnav / cps_connect);member、vip、download 的前台函数(如 member_uid()、vip_is_vip())用法见各插件目录内的 README。另有网址导航 nav_grabber(4.4)为市场分发插件,调用方式一并收录。

4.1 广告位 adslot

全站广告统一管理:模板只声明广告位标识,投放内容全部由站长在后台配置,换广告、换图、调时间窗都不需要改模板。支持 8 种广告形式:

形式模板要改吗说明
横幅 Banner插一行图片 / GIF / 多图轮播,最常用横幅位
文字链插一行纯文字链接,可设颜色加粗,适合友情推广位
图文混排插一行缩略图 + 标题 + 描述,适合推荐位
自定义代码插一行粘贴第三方代码(联盟、AdSense 等),万能兜底
弹窗不用改可设延迟、触发方式、每日次数、关闭后 N 天不再出现
底部固定条不用改底部常驻浮层,可关闭并记住状态
信息流混排列表混入文章列表,样式与列表内容一致
文中插入包一层函数正文第 N 段或第 N 张图之后插入,可重复插入

弹窗与底部固定条由页面底部钩子全站自动注入,后台建好即生效,模板无需任何改动。

模板调用

<?php adslot('home_banner'); ?>                             // 直接输出(最常用)
<?php echo adslot_html('home_banner'); ?>                 // 返回字符串,便于判断空
<?php echo adslot_inject($art['content'], 'art_inline'); ?> // 文中插入:输出处理后的正文

// 信息流:取出广告内容,自行混进文章列表(插入间隔读后台配置)
$feeds = adslot_feed('list_feed');
$slot  = adslot_slot_by_code('list_feed');
$every = $slot ? max(1, (int) as_cfg(adslot_config($slot), 'every', 5)) : 5;
foreach ($list as $i => $a) {
    // … 原有列表项输出 …
    if ($feeds && ($i + 1) % $every === 0) {
        $item = array_shift($feeds);
        echo adslot_render_item($item, $slot, ['mode' => 'feed']);
    }
}
函数 / 方法说明
adslot($code)按广告位标识直接输出 HTML
adslot_html($code)同上但返回字符串;位置无内容时返回空,不留空白色块
adslot_inject($content, $code)文中插入:传正文,返回处理后的正文
adslot_feed($code)取信息流广告内容数组,由模板混排
adslot_slot_by_code($code)取广告位配置;as_cfg(adslot_config($slot), 'every', 5) 读混排间隔
adslot_render_item($item, $slot, $opts)渲染单条广告内容(信息流混排用)
adslot_head_html() / adslot_foot_html()虚拟页专用:在插件自治渲染页的页头 / 页尾各调一次(见下)
window.adslotRefresh(root)JS:AJAX 追加列表内容后重新绑定曝光、轮播与浮层

虚拟页接入

?mod=nav 这类插件自治渲染页不经过框架在 </head> 与 </body> 前的头尾注入,只靠钩子会出现「有广告没样式」。插件提供两个「同请求只输出一次」的函数,模板在页头页尾各调一次即可,与钩子自动去重:

// header.php 的 </head> 前
<?php echo function_exists('adslot_head_html') ? adslot_head_html() : ''; ?>
// footer.php 的 </body> 前
<?php echo function_exists('adslot_foot_html') ? adslot_foot_html() : ''; ?>

内置 default 模板已占用的标识

站长后台按标识新建广告位即可直接投放,模板无需改动:

标识位置
home_banner / home_mid_banner / home_bottom_banner首页首屏下方、中部、页尾通栏
list_top_banner / list_feed / list_side_banner列表页顶部通栏、信息流混排、侧栏
art_top_banner / art_inline / art_side_banner文章页正文上方、文中插入、侧栏

自定义模板的标识可自定,建议同样带模板前缀(如 mytpl_side)避免撞名。曝光按广告进入可视区域计一次,点击走 ?mod=adslot&act=click 计数后 302 跳转,跳转带开放协议白名单。

4.2 APP 应用下载导航 appnav

为应用商店 / 下载导航类模板提供分类与应用数据:分类管理、应用发布(图标 / 名称 / 简介 / 版本 / 多下载链接)、广告位(顶部横幅 + 分类间穿插)、点击统计。本插件不渲染前台页面——只提供 appnav_*() 数据函数,模板取数后自行渲染;前台仅注册一个计数跳转入口 ?mod=appnavgo(点击先计数再 302 到真实地址,真实地址不外露,后台改地址不用改模板)。配套官方模板 apphub(手机端优先)可直接参考。

分类与列表

<?php if (function_exists('appnav_cats')): ?>
<?php
$cats = appnav_cats();                 // 启用中的分类(sort DESC),每行含 app_count
$apps = appnav_apps([                  // 应用列表,每行自动带 links 数组
    'cat_id' => 0,                     // 按分类筛选(0=全部)
    'kw'     => '',                    // 按名称/简介搜索
    'order'  => 'sort',                // sort 权重(默认)| hot 点击量 | new 最新
    'per'    => 24, 'page' => 1,       // 每页数量(≤100)
]);
$app = appnav_app($id);                // 单个应用(含 links)
// 应用行:id / cat_id / name / icon / intro / version / size / hits / sort / status / links
// links 行:id / app_id / title(按钮文字)/ url / hits / sort
?>
<?php endif; ?>

按分类分组(导航首页形态)

$sections = appnav_apps_by_cat(8);      // 每分类取 8 个;0=不限;跳过空分类
foreach ($sections as $s) {
    // $s['cat'] 分类行;$s['apps'] 应用行数组(含 links)
}

下载按钮(计数跳转)

foreach ($app['links'] as $l) {
    echo '<a href="' . e(appnav_go_url($l['id'])) . '">' . e($l['title']) . '</a>';
}
// 点击 → ?mod=appnavgo&lid=xx → 记日志 + 应用/链接双计数 → 302 到真实地址
// 只配一条链接时 links 只有一行,模板自然只渲染一个下载按钮

广告位

$topAds = appnav_ads('top');           // 顶部横幅(多张时模板可轮播)
$midAds = appnav_ads('between');       // 分类间穿插广告(sort DESC)
// 广告行:id / title / image / url / position / cat_id / hits / sort / status
// between 行的 cat_id:>0 = 指定穿插在该分类区块之后;0 = 按顺序穿插
echo '<a href="' . e(appnav_ad_url($ad['id'])) . '"><img src="' . e($ad['image']) . '"></a>';
函数说明
appnav_cats()启用中的分类列表,每行含 app_count 应用数
appnav_apps($opts)应用列表(cat_id / kw / order / per / page),每行含 links
appnav_app($id)单个应用(含 links)
appnav_count_apps($opts)应用总数(搜索分页用,参数同 appnav_apps)
appnav_apps_by_cat($per)按分类分组取应用(导航首页形态)
appnav_ads('top' | 'between')广告位内容;between 行的 cat_id 决定穿插位置
appnav_go_url($linkId) / appnav_ad_url($adId)计数跳转网址:点击先计数再 302,真实地址不外露
appnav_hits_h($n)次数人性化(如 1.2万)
appnav_stat()概览统计:应用数 / 广告数 / 点击量 / 今日等
插件停用后以上函数不存在,模板调用前先 function_exists('appnav_cats') 判断或按 2.7 守卫写法处理,停用时显示空态引导即可。下载 / 广告链接入库前经协议白名单校验(拒绝 javascript: 等),图标与广告图走系统统一上传。

4.3 接口对接(CPS)cps_connect

电商 CPS 变现的对接工具插件:配置造梦开放平台的 Bearer Token、接口路径与同步热词,一键同步热销榜(high / hot)、热词比价商品、本地生活活动,并管理商品缓存与同步日志。

本插件是后台工具型插件,没有供模板调用的前台函数。前台 CPS 频道(?mod=cps 导购列表、?mod=cpsdetail 商品详情)由系统核心 includes/cps_api.php 与 includes/functions.php 提供,模板直接用第 2.4 节的 get_goods() 等函数取数即可。插件停用只关闭后台同步入口,前台频道照常展示已缓存数据。

4.4 网址导航 nav_grabber(市场分发插件)

为导航站、网址大全、工具集类模板提供分类与链接管理,可从目标网址抓取标题、简介、图标与关键词,数据存插件私有表,不占用系统友链表。前台详情页固定为 ?mod=nav&id=X。本插件不再随主程序内置分发,从后台「插件管理 → 插件中心」在线安装。模板调用:

$cats    = ng_get_cats(true);              // 全部启用分类(含 item_count 链接数)
$cat     = ng_get_cat_by_slug('tools');    // 按别名取分类
$items   = ng_get_items($cat['id'], 24);   // 取分类下的链接
$results = ng_search_items('GitHub', 30);  // 按名称 / 简介 / 标签搜索
$detail  = ng_detail_url($id);             // 详情页地址
$logo    = $item['logo'] ?: ng_logo_fallback($item['name']); // 无图标时首字母占位图
if (!ng_safe_url($url)) { /* 拒绝不安全的提交地址 */ }
函数说明
ng_get_cats($onlyEnabled)全部分类,传 true 只取启用的,每项含 item_count
ng_get_cat_by_slug($slug)按别名取分类
ng_get_items($catId, $limit)取分类下启用链接(按排序权重)
ng_search_items($kw, $limit)按名称 / 简介 / 标签搜索
ng_detail_url($id)生成前台详情页网址
ng_logo_fallback($name)无图标时按名称首字母生成占位图地址
ng_safe_url($url)网址安全校验(表单提交地址先过它)

插件停用后以上函数不存在,模板调用前先 function_exists('ng_get_cats') 判断,或按 2.7 守卫写法手动 require 插件核心文件。配套演示模板 navhub 含首页、分类页、搜索页、详情页与 404 页,可直接参考。

5上架应用中心

5.1 上架流程

资源从开发到出现在市场的完整链路:

  1. 本地开发调试:资源放主程序 templates/{slug}/ 或 plugins/{slug}/,切后台模板 / 启用插件实测。
  2. 放入市场源:完整拷贝到 market/source/templates/{slug}/(或 source/plugins/{slug}/)。
  3. 登记清单:在 market/tools/manifest.php 的对应数组里登记 slug 条目(标题、描述、分类、详情富文本、价格等)。
  4. 打包上架:运营方执行打包工具生成 zip 并入库:
    php market/tools/publish.php            # 全部资源重新打包上架
    php market/tools/publish.php tpl        # 仅模板
    php market/tools/publish.php tpl corporate,gov   # 指定 slug
    php market/tools/publish.php --list     # 只查看清单
    php market/tools/publish.php --dry      # 试运行不落库
  5. 审核发布:上架后经运营后台审核,通过即在市场前台展示,站长从后台「模板市场 / 插件中心」一键在线安装。

manifest.php 条目字段(市场侧资料,与包内 JSON 清单二者缺一回退):

字段说明
title / desc / version / author / features基本信息,未填时回退包内 template.json / plugin.json
category分类,必须在受控分类表内(模板:企业官网 / 政府机构 / 教育校园 / 资讯门户 / 资源下载 / 电商导购 / 深色科技 / 其他;插件另有分类表),否则归「其他」
content详情页富文本,仅允许 h3 / h4 / p / ul / li / strong / table 等基础标签
price / require_level价格与授权等级。require_level 非空即付费资源(见 5.3)
featured / is_top官方精选 / 置顶推荐
meta_title / meta_desc / meta_kw详情页 SEO
demo_url / status演示网址(默认隐藏)/ 上下架状态(默认 online)

5.2 打包规则(安装端强校验)

  • zip 内以 {slug}/ 为唯一根目录(也支持外层多包一层目录,安装端会自动定位清单文件)。
  • 插件包必须含清单声明的主入口文件;模板包必须含 7 个必需页面文件与含 slug 的 template.json。
  • 扩展名白名单:php / html / css / js / json / txt / md / xml / sql / png / jpg / jpeg / gif / svg / webp / ico / eot / ttf / woff / woff2 / map。
  • 单文件 ≤ 5MB,单包 ≤ 20MB;隐藏文件(. 开头)与 .git 等版本库目录自动剔除。
  • 解压全程路径穿越防护;PHP 文件在安装 / 启用前会做全量语法预检。

5.3 免费与付费资源

清单里 require_level 留空即免费资源,站长一键安装无任何限制。

require_level 非空即付费资源,打包时会自动在入口文件注入域名授权校验(license-guard.php):

  • 站长安装后,资源首次运行时向应用中心核验「当前站点域名是否已登记授权」。
  • 核验结果带签名缓存 15 天,断网宽限 45 天;前台访客不受任何影响(完全静默)。
  • 未授权仅在站长后台「模板管理 / 插件管理」页显示风险徽标,并提供「验证授权」按钮联网复查;站长可到授权中心免费登记域名。
付费资源同样先正常分发安装——体验完整、风险提示只给站长看,这是 DaoKecms 付费生态的默认策略。开发者无需在代码里做任何授权逻辑,打包工具全权处理。

5.4 上架自检清单

  • slug 合规(仅字母数字下划线连字符),且市场内无重名资源。
  • 必需文件齐全:模板 7 件套 + 含 slug 的 template.json;插件 main.php + plugin.json。
  • 全部 PHP 文件本地 php -l 通过(否则安装 / 启用被拒)。
  • 配置键、函数名、数据表均带自己的 slug 前缀,无冲突。
  • 模板页面没有手动 include header / footer;SEO 分支写在 header.php;动态输出全部 e()。
  • 插件 POST 表单带 _csrf;SQL 全占位符传参;删除数据禁止 _ / % 模糊匹配。
  • 预览图就位(目录内 preview.svg 或 preview.png,缺失会生成占位图)。
  • manifest.php 已登记条目,分类正确,详情富文本只用了允许标签。