Documentation
A plugin development and theme design guide for developers.
Plugin Development
YikaiCMS uses a WordPress-style Action / Filter hook mechanism for its plugin system, letting you extend functionality without modifying the core code.
Directory Structure
Each plugin is a folder under plugins/ that contains at least two files:
plugins/
my-plugin/
plugin.json # Plugin description file (required)
main.php # Entry file (required)
admin.php # Admin management page (optional)
plugin.json
Describes the plugin's metadata, which the system uses to display the plugin in the admin list.
{
"name": "My Plugin",
"version": "1.0.0",
"description": "This is the plugin's feature description.",
"author": "Your Name",
"requires_php": "8.0",
"requires_cms": "1.0.0"
}
name — The plugin name shown in the admin panel
requires_php — Minimum required PHP version
requires_cms — Minimum required CMS version
Hook System
In main.php, register hook callbacks via add_action() and add_filter().
Available Action Hooks
| Hook Name | Trigger Location |
|---|---|
plugins_loaded | After all plugins have loaded |
ik_head | Before the frontend </head>, for injecting CSS / meta |
ik_header_after | After the frontend navigation bar |
ik_footer_before | Before the frontend footer starts |
ik_footer_scripts | Before the frontend </body>, for injecting JS |
ik_admin_head | Before the admin </head> |
ik_admin_footer_scripts | Before the admin </body> |
API Functions
// Register a callback (lower priority runs earlier, default 10) add_action(string $hook, callable $callback, int $priority = 10): void // Trigger a hook do_action(string $hook, mixed ...$args): void // Register a filter add_filter(string $hook, callable $callback, int $priority = 10): void // Apply filters and return the processed value apply_filters(string $hook, mixed $value, mixed ...$args): mixed
Admin Management Page
If your plugin needs an admin settings interface, create admin.php and access it via /admin/plugin_page.php?plugin=PluginDirectoryName.
<?php
// plugins/my-plugin/admin.php
if (!defined('ROOT_PATH')) {
exit('Access Denied');
}
$pageTitle = 'My Plugin Settings';
require_once ROOT_PATH . '/admin/includes/header.php';
?>
<div class="max-w-3xl">
<h2 class="text-xl font-bold mb-4">Plugin Settings</h2>
<!-- Your settings form -->
</div>
<?php require_once ROOT_PATH . '/admin/includes/footer.php'; ?>
In main.php you can add a "Management Page" entry link to the plugin card:
add_action('ik_admin_footer_scripts', function () {
if (basename($_SERVER['SCRIPT_NAME']) !== 'plugin.php') return;
echo '<script>
(function(){
var card = document.getElementById("plugin-my-plugin");
if (!card) return;
var info = card.querySelector(".text-xs.text-gray-400");
if (info) {
var a = document.createElement("a");
a.href = "/admin/plugin_page.php?plugin=my-plugin";
a.className = "text-primary hover:underline ml-4";
a.textContent = "Open Management Page";
info.appendChild(a);
}
})();
</script>';
});
Full Example: Back-to-Top Plugin
A minimal frontend plugin that displays a "Back to Top" button in the bottom-right corner of the page.
plugins/back-to-top/plugin.json
{
"name": "Back to Top",
"version": "1.0.0",
"description": "Displays a back-to-top button in the bottom-right corner of frontend pages.",
"author": "Yikai CMS",
"requires_php": "8.0",
"requires_cms": "1.0.0"
}
plugins/back-to-top/main.php
<?php
if (!defined('ROOT_PATH')) {
exit('Access Denied');
}
add_action('ik_footer_scripts', function () {
?>
<div id="backToTop" onclick="window.scrollTo({top:0,behavior:'smooth'})"
style="position:fixed;bottom:30px;right:30px;z-index:9999;
width:44px;height:44px;border-radius:50%;
background:#3b82f6;color:#fff;display:none;
align-items:center;justify-content:center;
cursor:pointer;box-shadow:0 2px 12px rgba(59,130,246,0.4)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.5">
<path d="M18 15l-6-6-6 6"/>
</svg>
</div>
<script>
(function(){
var btn = document.getElementById('backToTop');
window.addEventListener('scroll', function(){
btn.style.display = window.scrollY > 400 ? 'flex' : 'none';
});
})();
</script>
<?php
});
Theme Design
The YikaiCMS theme system is based on a file-override mechanism: place template files into the theme directory and the system automatically loads the theme version first, falling back to the default templates in includes/ for any files you haven't overridden.
Directory Structure
themes/
my-theme/
theme.json # Theme description (required)
assets/
images/
screenshot.png # Preview image (shown in admin)
css/
style.css # Theme custom styles (optional)
layouts/
header.php # Header layout
footer.php # Footer layout
blocks/
banner.php # Homepage carousel block
about.php # Homepage "about us" block
channel.php # Homepage channel content block
stats.php # Statistics block
advantage.php # Advantages showcase block
testimonials.php # Customer testimonials block
cta.php # Call-to-action block
partials/
breadcrumb.php # Breadcrumb navigation
page-hero.php # Page top hero banner
pagination.php # Pagination component
article-card.php # Article card
product-card.php # Product card
case-card.php # Case study card
job-card.php # Job listing card
All files are optional; you only need to override the files you want to customize.
theme.json
{
"name": "My Theme",
"description": "A clean, elegant corporate website template",
"version": "1.0.0",
"author": "Your Name",
"screenshot": "assets/images/screenshot.png",
"supports": [
"banner", "about", "stats",
"testimonials", "advantage", "cta", "channel"
]
}
screenshot — Preview image path (relative to the theme root); a 16:10 ratio is recommended, ideally 1280×800px
supports — Declares the homepage block types the theme supports
Layout Files
layouts/header.php and layouts/footer.php are the site-wide shared header and footer.
Basic structure of header.php
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo e($pageTitle ? $pageTitle . ' - ' : '');
echo e(config('site_name')); ?></title>
<link rel="stylesheet" href="/assets/css/tailwind.css">
<!-- Include the theme's custom CSS -->
<link rel="stylesheet" href="<?php echo theme_asset('css/style.css'); ?>">
<?php do_action('ik_head'); ?>
</head>
<body>
<!-- Navigation bar HTML -->
<?php do_action('ik_header_after'); ?>
Basic structure of footer.php
<?php do_action('ik_footer_before'); ?>
<footer>
<!-- Footer HTML -->
</footer>
<?php do_action('ik_footer_scripts'); ?>
</body>
</html>
do_action() calls must be kept in the layout files, otherwise plugin hooks will not take effect.
Homepage Blocks
Files under blocks/ correspond to the individual blocks of the homepage and are loaded by index.php in the configured order. Each block can use the following predefined variables directly:
| Block File | Available Variables |
|---|---|
banner.php | $banners — Carousel array |
about.php | Retrieve settings via config('home_about_*') |
channel.php | $homeChannels — Channels shown on the homepage |
stats.php | Retrieve numbers via config('home_stat_*') |
advantage.php | Retrieve advantage items via config('home_adv_*') |
testimonials.php | Retrieve JSON via config('home_testimonials') |
cta.php | Retrieve copy via config('home_cta_*') |
Shared Components (Partials)
Files under partials/ are reusable UI components loaded via require on list and detail pages:
// Load the breadcrumb (looked up in the theme directory first, falling back to includes/partials/)
require theme_path('partials/breadcrumb.php');
// Load the product card
require theme_path('partials/product-card.php');
The variables available inside a component depend on the calling context; for example, $item (a single product's data) is available in product-card.php.
Common Functions
| Function | Description |
|---|---|
config($key, $default) | Read a system setting |
e($string) | HTML-escaped output |
__($key) | Multilingual translation |
theme_path($file) | Get the absolute path of a theme template, with automatic fallback |
theme_asset($file) | Get the URL of a theme static asset |
currentTheme() | The slug of the currently active theme |
channelUrl($channel) | Generate a channel URL |
thumbnail($path, $size) | Generate a thumbnail URL |
parseShortcodes($html) | Parse shortcodes within content |
do_action($hook) | Trigger a hook (must be kept in layouts) |
themes/ directory, then go to the "Theme Management" page in the admin panel to preview and enable it.
Deployment Configuration
YikaiCMS supports URL rewriting (dynamic URLs → static-looking .html URLs) for better SEO. Since v1.12.9 the built-in single-entry router is fully compatible with WordPress rewrite rules—most environments need just one step, or none at all.
| Host | What to do |
|---|---|
| BT / aaPanel (Nginx) | Put one line in the rewrite box: include …/deploy/nginx-baota.conf; (see the Nginx section below; do not use the "wordpress" preset alone) |
| Alibaba Cloud / shared virtual host (Nginx) | Paste one line of try_files (below) |
| Yikai Panel (local Windows) | Zero config — built-in YikaiCMS rules for Nginx; uses the bundled .htaccess on Apache |
| Apache / most shared hosts | Zero config — ships with .htaccess |
| Self-hosted Nginx | One try_files line; full rules for static pass-through (below) |
Nginx Rewrite Configuration
✓ Recommended: include the bundled rules (with security blocks)
Panels like BT / aaPanel: put just this one line in the site's rewrite box (replace the path with your site directory). The bundled deploy/nginx-baota.conf contains both the routing rules and the sensitive-directory blocks, and it is updated automatically on upgrade.
include /www/wwwroot/your-site-dir/deploy/nginx-baota.conf;
⚠ Do not use the "wordpress" preset alone
It is a single try_files rule that serves any file that exists on disk first — and a SQLite site keeps its database at storage/database.sqlite inside the site directory, so anyone could download it. After configuring, open /storage/database.sqlite in a browser: it must return 403 or 404.
Writing the server block by hand? Add these two blocks (the first one blocks sensitive directories and must come first):
location ~* ^/(config|storage|vendor|includes|install/sql|bin|migrations|recipes)(/|$) {
return 403;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Advanced: full rules (static HTML pass-through, best performance)
With root access, use the full rules: generated .html pages are served directly by Nginx, bypassing PHP. Merge into the server block (replacing the rules above):
location ~ ^/detail/(\d+)\.html$ { rewrite ^ /detail.php?id=$1 last; }
location ~ ^/case/(\d+)\.html$ { rewrite ^ /detail.php?id=$1 last; }
location ~ ^/case/([a-z0-9_-]+)\.html$ { rewrite ^ /detail.php?slug=$1 last; }
location ~ ^/list/(\d+)\.html$ { rewrite ^ /list.php?id=$1 last; }
location ~ ^/list/(\d+)/page/(\d+)\.html$ { rewrite ^ /list.php?id=$1&page=$2 last; }
location ~ ^/([a-z0-9_-]+)/page/(\d+)\.html$ { rewrite ^ /list.php?slug=$1&page=$2 last; }
location ~ ^/product/(\d+)\.html$ { rewrite ^ /product.php?id=$1 last; }
location ~ ^/product/([a-z0-9_-]+)/page/(\d+)\.html$ { rewrite ^ /list.php?slug=product&cat=$1&page=$2 last; }
location ~ ^/product/([a-z0-9_-]+)\.html$ { rewrite ^ /list.php?slug=product&cat=$1 last; }
location ~ ^/job/(\d+)\.html$ { rewrite ^ /job_detail.php?id=$1 last; }
location ~ ^/([a-z0-9_-]+)/([a-z0-9_-]+)\.html$ { rewrite ^ /page.php?parent=$1&slug=$2 last; }
location ~ ^/([a-z0-9_-]+)\.html$ { rewrite ^ /page.php?slug=$1 last; }
location / { try_files $uri $uri/ /index.php?$query_string; }
Apache Rewrite Configuration
In an Apache environment, YikaiCMS ships with a .htaccess file that takes effect automatically after installation, with no extra configuration needed. Make sure Apache has the mod_rewrite module enabled.
If rewriting doesn't work, check the following:
AllowOverride Allis enabled in the Apache configuration- The
mod_rewritemodule is loaded (a2enmod rewrite) - The
.htaccessfile exists in the website root directory
Alibaba Cloud Enhanced Shared Hosting
Alibaba Cloud Enhanced Linux shared hosting uses an Nginx engine and does not support .htaccess; you must configure the rewrite rules separately in the hosting control panel.
Configuration Steps
- Log in to the Cloud Shared Hosting management page
- Find the target host and click Manage
- Left navigation: Advanced Environment Settings → NGINX Settings
- Paste the configuration below into the text box
- Click Save Settings
✓ Recommended: minimal rules (with sensitive-directory blocks)
Replace the panel's default location / {} and location ~ /\.ht {deny all;} blocks with the content below. The first two blocks protect storage/ (where the database lives) and other directories — do not leave them out. Afterwards /storage/database.sqlite must return 403 or 404. A more complete version (also blocking PHP in uploads) is in the package at deploy/aliyun-nginx-minimal.txt.
location ~ ^/(config|storage|vendor|includes|install/sql|bin|migrations|recipes)/ {
deny all;
}
location ~ /\.(git|env|htaccess|htpasswd) {
deny all;
}
location / {
if (!-e $request_filename) {
rewrite ^ /index.php last;
}
}
Advanced: full rules (only for static HTML pass-through)
This host only supports limited directives inside a location block, so all rules go in a single location / (keep the two directory-blocking blocks above; replace only the location / block):
location / {
rewrite ^/detail/(\d+)\.html$ /detail.php?id=$1 last;
rewrite ^/case/(\d+)\.html$ /detail.php?id=$1 last;
rewrite ^/case/([a-z0-9_-]+)\.html$ /detail.php?slug=$1 last;
rewrite ^/list/(\d+)\.html$ /list.php?id=$1 last;
rewrite ^/list/(\d+)/page/(\d+)\.html$ /list.php?id=$1&page=$2 last;
rewrite ^/([a-z0-9_-]+)/page/(\d+)\.html$ /list.php?slug=$1&page=$2 last;
rewrite ^/product/(\d+)\.html$ /product.php?id=$1 last;
rewrite ^/product/([a-z0-9_-]+)/page/(\d+)\.html$ /list.php?slug=product&cat=$1&page=$2 last;
rewrite ^/product/([a-z0-9_-]+)\.html$ /list.php?slug=product&cat=$1 last;
rewrite ^/job/(\d+)\.html$ /job_detail.php?id=$1 last;
rewrite ^/([a-z0-9_-]+)/([a-z0-9_-]+)\.html$ /page.php?parent=$1&slug=$2 last;
rewrite ^/([a-z0-9_-]+)\.html$ /page.php?slug=$1 last;
try_files $uri $uri/ /index.php?$args;
}
location / { } to restore it.
/works/16.html), just add the corresponding rewrite rule before try_files. More specific rules must come before more general ones.
URL Mapping Table
| Rewritten URL | Actual Request | Description |
|---|---|---|
/detail/16.html | /detail.php?id=16 | Content detail page |
/case/16.html | /detail.php?id=16 | Case detail page |
/about.html | /page.php?slug=about | Single page |
/news.html | /page.php?slug=news | Channel list |
/news/page/2.html | /list.php?slug=news&page=2 | List pagination |
/product/phone.html | /list.php?slug=product&cat=phone | Product category |
/product/16.html | /product.php?id=16 | Product detail |
/job/1.html | /job_detail.php?id=1 | Job detail |
/about/team.html | /page.php?parent=about&slug=team | Second-level page |