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_loadedAfter all plugins have loaded
ik_headBefore the frontend </head>, for injecting CSS / meta
ik_header_afterAfter the frontend navigation bar
ik_footer_beforeBefore the frontend footer starts
ik_footer_scriptsBefore the frontend </body>, for injecting JS
ik_admin_headBefore the admin </head>
ik_admin_footer_scriptsBefore 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
});
Tip: Once your plugin is finished, just go to the "Plugin Management" page in the admin panel and enable it — no need to modify any configuration files.

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>
Note: The 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.phpRetrieve settings via config('home_about_*')
channel.php$homeChannels — Channels shown on the homepage
stats.phpRetrieve numbers via config('home_stat_*')
advantage.phpRetrieve advantage items via config('home_adv_*')
testimonials.phpRetrieve JSON via config('home_testimonials')
cta.phpRetrieve 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)
Tip: Once your theme is finished, place the theme folder into the 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.

HostWhat 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 hostsZero config — ships with .htaccess
Self-hosted NginxOne 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 All is enabled in the Apache configuration
  • The mod_rewrite module is loaded (a2enmod rewrite)
  • The .htaccess file 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

  1. Log in to the Cloud Shared Hosting management page
  2. Find the target host and click Manage
  3. Left navigation: Advanced Environment Settings → NGINX Settings
  4. Paste the configuration below into the text box
  5. 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;
}
Restore defaults: If a misconfiguration makes the site inaccessible, simply replace the content with location / { } to restore it.
Custom rules: If your project uses a special URL format (such as /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=16Content detail page
/case/16.html/detail.php?id=16Case detail page
/about.html/page.php?slug=aboutSingle page
/news.html/page.php?slug=newsChannel list
/news/page/2.html/list.php?slug=news&page=2List pagination
/product/phone.html/list.php?slug=product&cat=phoneProduct category
/product/16.html/product.php?id=16Product detail
/job/1.html/job_detail.php?id=1Job detail
/about/team.html/page.php?parent=about&slug=teamSecond-level page