Back to Main Site

Hướng dẫn tự thiết kế Theme tùy biến cho PolyCMS — Khung mẫu phát triển

Last updated on Jul 10, 2026 3:26 AM

Tổng quan

Các giao diện (themes) PolyCMS kiểm soát toàn bộ diện mạo và bố cục frontend của blog. Hệ thống theme sử dụng một cơ chế phân cấp mẫu (template hierarchy) lấy cảm hứng từ WordPress, trong đó các mẫu chuyên biệt hơn sẽ ghi đè các mẫu chung.

Hướng dẫn này cung cấp một khung cấu trúc hoàn chỉnh để xây dựng một theme tùy chỉnh từ con số không.

Yêu cầu tiên quyết: Kiến thức về PHP, HTML/CSS và sự quen thuộc với hệ thống hooks của PolyCMS.

Cấu trúc thư mục Theme

Tất cả các theme đều nằm trong thư mục modules/polycms/themes/. Mỗi theme có cấu trúc như sau:

modules/polycms/themes/
└── my-theme/
    ├── style.css            # Khai báo thông tin theme (bắt buộc)
    ├── index.php            # Mẫu dự phòng mặc định (bắt buộc)
    ├── header.php           # Phần đầu trang
    ├── footer.php           # Phần chân trang
    ├── single.php           # Mẫu hiển thị bài viết chi tiết
    ├── page.php             # Mẫu hiển thị trang tĩnh
    ├── category.php         # Mẫu trang lưu trữ danh mục
    ├── tag.php              # Mẫu trang lưu trữ thẻ tag
    ├── archive.php          # Mẫu trang lưu trữ chung
    ├── search.php           # Mẫu trang kết quả tìm kiếm
    ├── 404.php              # Giao diện trang không tìm thấy
    ├── functions.php        # Các hàm bổ trợ của theme (tùy chọn)
    ├── assets/
    │   ├── css/
    │   │   └── theme.css
    │   ├── js/
    │   │   └── theme.js
    │   └── images/
    └── language/
        └── english/
            └── my_theme_lang.php

Cơ chế phân cấp Mẫu (Template Hierarchy)

Khi PolyCMS hiển thị một trang, hệ thống sẽ tìm kiếm các tệp mẫu theo thứ tự sau:

Bài viết chi tiết:   single.php → index.php
Trang tĩnh:         page.php → index.php
Danh mục:           category.php → archive.php → index.php
Thẻ tag:            tag.php → archive.php → index.php
Tìm kiếm:           search.php → index.php
Trang chủ:          homepage.php → index.php
Trang 404:          404.php → index.php

Tệp mẫu khớp đầu tiên tìm thấy sẽ được sử dụng. Tệp index.php đóng vai trò là mẫu dự phòng cuối cùng.

Khả năng tương thích MTBuilder trong Giao diện

If you want to reuse MTBuilder preset layouts, Template Parts, or custom blocks, design your theme scaffold to be MTBuilder-friendly from day one:

  1. Thiết lập một mẫu trang rộng toàn màn hình (full-width page template) cho các trang đích (không có khung container giới hạn hoặc sidebar bắt buộc).
  2. Luôn hiển thị nội dung trang/bài viết thông qua hàm lọc polycms_apply_filters('polycms_post_content', $content) để các khối visual block có thể được biên dịch chính xác.
  3. Nhúng đầy đủ các hàm neo polycms_do_action('polycms_head')polycms_do_action('polycms_footer') để cho phép tài nguyên của MTBuilder/plugin tải thành công.
  4. Tránh các định nghĩa CSS toàn cục quá mạnh tác động lên toàn bộ thẻ a, button, h1-h6, .row, và .col-*, vì chúng có thể phá vỡ bố cục giao diện của từng khối block.

Ví dụ: Mẫu trang rộng toàn màn hình cho trang đích MTBuilder

page-landing.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<?php include __DIR__ . '/header.php'; ?>

<main class="site-main landing-main">
    <?php
    // Xuất cấu trúc layout thô của MTBuilder ra giao diện
    echo polycms_apply_filters('polycms_post_content', $page->content);
    ?>
</main>

<?php include __DIR__ . '/footer.php'; ?>

Bố cục này là giải pháp lý tưởng khi bạn muốn kết hợp một khung theme tùy biến với các hàng, cột, phần và khối nội dung tương tác của MTBuilder.

Bước 1: Tạo tệp style.css (Khai báo Theme)

Tệp style.css tại thư mục gốc của theme đóng vai trò khai báo thông tin. PolyCMS sẽ đọc các tiêu đề chú thích ở đầu tệp để nhận diện theme.

style.css

/*
Theme Name: My Custom Theme
Theme URI: https://your-website.com/my-theme
Description: Một giao diện blog sạch sẽ và hiện đại cho PolyCMS.
Version: 1.0.0
Author: Tên Của Bạn
Author URI: https://your-website.com
Requires PolyCMS: 1.0.0
License: Commercial
*/

/* CSS của theme có thể viết trực tiếp tại đây hoặc trong assets/css/theme.css */

Bước 2: Tạo tệp header.php

Mẫu header sẽ được nhúng ở trên cùng của mọi trang.

header.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<!DOCTYPE html>
<html lang="<?php echo polycms_get_locale(); ?>">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <?php
    // Các thẻ SEO meta tự động sinh bởi PolyCMS
    if (isset($seo_data)) {
        echo polycms_render_meta_tags($seo_data);
    }
    if (isset($og_tags)) {
        echo $og_tags;
    }
    if (isset($twitter_tags)) {
        echo $twitter_tags;
    }
    ?>

    <!-- Theme CSS -->
    <link rel="stylesheet" href="<?php echo polycms_theme_url(); ?>/assets/css/theme.css">

    <?php
    // Cho phép các plugin chèn tài nguyên vào thẻ <head>
    polycms_do_action('polycms_head');
    ?>
</head>
<body class="polycms-frontend <?php echo isset($body_class) ? $body_class : ''; ?>">

<?php
// Hiển thị thanh quản trị admin bar nếu đã đăng nhập hệ thống
polycms_render_admin_bar();
?>

<!-- Giao diện Menu điều hướng -->
<nav class="site-nav">
    <div class="container">
        <a href="<?php echo polycms_blog_url(); ?>" class="site-logo">
            <?php echo polycms_get_option('blog_title', 'My Blog'); ?>
        </a>
        <div class="nav-menu">
            <?php echo polycms_render_menu('primary'); ?>
        </div>
    </div>
</nav>

Bước 3: Tạo tệp footer.php

footer.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>

<!-- Chân trang -->
<footer class="site-footer">
    <div class="container">
        <p>&copy; <?php echo date('Y'); ?> <?php echo polycms_get_option('blog_title', 'My Blog'); ?>. All rights reserved.</p>
    </div>
</footer>

<?php
// Cho phép các plugin chèn mã/thẻ trước thẻ đóng </body>
polycms_do_action('polycms_footer');
?>

<!-- Theme JS -->
<script src="<?php echo polycms_theme_url(); ?>/assets/js/theme.js"></script>
</body>
</html>

Bước 4: Tạo tệp index.php (Mẫu mặc định dự phòng)

index.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>

<?php include __DIR__ . '/header.php'; ?>

<main class="site-main">
    <div class="container">
        <h1><?php echo $title ?? 'Blog'; ?></h1>

        <?php if (!empty($posts)): ?>
            <div class="post-grid">
                <?php foreach ($posts as $post): ?>
                    <article class="post-card">
                        <?php if ($post->featured_image): ?>
                            <img src="<?php echo $post->featured_image; ?>"
                                 alt="<?php echo htmlspecialchars($post->title); ?>"
                                 class="post-thumbnail">
                        <?php endif; ?>
                        <div class="post-card-body">
                            <h2>
                                <a href="<?php echo polycms_post_url($post); ?>">
                                    <?php echo htmlspecialchars($post->title); ?>
                                </a>
                            </h2>
                            <div class="post-meta">
                                <time datetime="<?php echo $post->published_at; ?>">
                                    <?php echo date('M d, Y', strtotime($post->published_at)); ?>
                                </time>
                                <span class="reading-time"><?php echo $post->reading_time; ?> phút đọc</span>
                            </div>
                            <p><?php echo $post->excerpt; ?></p>
                        </div>
                    </article>
                <?php endforeach; ?>
            </div>

            <?php if (isset($pagination)): ?>
                <div class="pagination">
                    <?php echo $pagination; ?>
                </div>
            <?php endif; ?>
        <?php else: ?>
            <p>Không tìm thấy bài viết nào.</p>
        <?php endif; ?>
    </div>
</main>

<?php include __DIR__ . '/footer.php'; ?>

Bước 5: Tạo tệp single.php (Mẫu bài viết chi tiết)

single.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>

<?php include __DIR__ . '/header.php'; ?>

<main class="site-main">
    <article class="single-post">
        <div class="container">
            <!-- Tiêu đề bài viết -->
            <header class="post-header">
                <?php if (!empty($post->categories)): ?>
                    <div class="post-categories">
                        <?php foreach ($post->categories as $cat): ?>
                            <a href="<?php echo polycms_category_url($cat); ?>" class="category-badge">
                                <?php echo htmlspecialchars($cat->name); ?>
                            </a>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>

                <h1><?php echo htmlspecialchars($post->title); ?></h1>

                <div class="post-meta">
                    <time datetime="<?php echo $post->published_at; ?>">
                        <?php echo date('F d, Y', strtotime($post->published_at)); ?>
                    </time>
                    <span><?php echo $post->reading_time; ?> phút đọc</span>
                    <span><?php echo $post->views; ?> lượt xem</span>
                </div>
            </header>

            <!-- Ảnh đại diện bài viết -->
            <?php if ($post->featured_image): ?>
                <div class="featured-image">
                    <img src="<?php echo $post->featured_image; ?>"
                         alt="<?php echo htmlspecialchars($post->title); ?>">
                </div>
            <?php endif; ?>

            <!-- Nội dung bài viết -->
            <div class="post-content">
                <?php
                // Áp dụng bộ lọc nội dung (các plugin có thể sửa đổi nội dung tại đây)
                echo polycms_apply_filters('polycms_post_content', $post->content);
                ?>
            </div>

            <!-- Các thẻ tag -->
            <?php if (!empty($post->tags)): ?>
                <div class="post-tags">
                    <?php foreach ($post->tags as $tag): ?>
                        <a href="<?php echo polycms_tag_url($tag); ?>" class="tag">
                            #<?php echo htmlspecialchars($tag->name); ?>
                        </a>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>

            <!-- Bình luận -->
            <?php if ($post->allow_comments): ?>
                <section class="comments-section">
                    <?php echo polycms_render_comments($post->id); ?>
                </section>
            <?php endif; ?>

            <!-- Bài viết liên quan -->
            <?php if (!empty($related_posts)): ?>
                <section class="related-posts">
                    <h3>Bài viết liên quan</h3>
                    <div class="post-grid">
                        <?php foreach ($related_posts as $rp): ?>
                            <a href="<?php echo polycms_post_url($rp); ?>" class="related-card">
                                <strong><?php echo htmlspecialchars($rp->title); ?></strong>
                            </a>
                        <?php endforeach; ?>
                    </div>
                </section>
            <?php endif; ?>
        </div>
    </article>
</main>

<?php include __DIR__ . '/footer.php'; ?>

Bước 6: Tạo tệp category.php

category.php

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>

<?php include __DIR__ . '/header.php'; ?>

<main class="site-main">
    <div class="container">
        <header class="archive-header">
            <h1>Danh mục: <?php echo htmlspecialchars($category->name); ?></h1>
            <?php if ($category->description): ?>
                <p class="archive-description"><?php echo $category->description; ?></p>
            <?php endif; ?>
        </header>

        <?php if (!empty($posts)): ?>
            <div class="post-grid">
                <?php foreach ($posts as $post): ?>
                    <article class="post-card">
                        <h2><a href="<?php echo polycms_post_url($post); ?>"><?php echo htmlspecialchars($post->title); ?></a></h2>
                        <p><?php echo $post->excerpt; ?></p>
                    </article>
                <?php endforeach; ?>
            </div>
            <?php if (isset($pagination)) echo $pagination; ?>
        <?php else: ?>
            <p>Không có bài viết nào trong danh mục này.</p>
        <?php endif; ?>
    </div>
</main>

<?php include __DIR__ . '/footer.php'; ?>

Cài đặt tùy chọn Theme Options (Tùy chọn)

Đăng ký các trường cấu hình trực quan để admin có thể thay đổi giao diện mà không cần chỉnh sửa code.

functions.php

<?php
defined('BASEPATH') or exit('No direct script access allowed');

// Đăng ký theme options
polycms_add_action('polycms_theme_options', function() {
    return [
        [
            'key'   => 'primary_color',
            'label' => 'Màu sắc chủ đạo',
            'type'  => 'color',
            'default' => '#82b440'
        ],
        [
            'key'   => 'posts_per_page',
            'label' => 'Số bài viết trên mỗi trang',
            'type'  => 'number',
            'default' => 10
        ],
        [
            'key'   => 'show_sidebar',
            'label' => 'Hiển thị Sidebar',
            'type'  => 'select',
            'options' => ['yes' => 'Có', 'no' => 'Không'],
            'default' => 'yes'
        ]
    ];
});

Kết hợp Theme Template Parts với MTBuilder Template Parts

Trong phát triển theme, "template parts" thường ám chỉ các file PHP được tái sử dụng như:

  • partials/header-hero.php
  • partials/cta-banner.php
  • partials/footer-columns.php

Trong MTBuilder, Template Parts là các phần giao diện trực quan được lưu lại từ editor. Bạn có thể kết hợp cả hai cách tiếp cận:

  1. Sử dụng theme template parts cho các phần giao diện tĩnh/toàn cục (khung tiêu đề trang web, chân trang pháp lý, nhãn chứng nhận).
  2. Sử dụng MTBuilder Template Parts cho các phần trang đích tiếp thị động (các biến thể ảnh bìa hero, danh sách câu hỏi thường gặp FAQ, bảng giá, CTA chiến dịch).
  3. Giữ nguyên phân chia trách nhiệm: Theme quản lý cấu trúc khung bên ngoài; MTBuilder quản lý các khối hiển thị phục vụ mục đích chuyển đổi.

Nếu bạn cần các thành phần giao diện đặc thù, hãy đăng ký các khối block trong MTBuilder từ một plugin và sử dụng chúng trong các trang. Xem thêm Hướng dẫn tạo khối nội dung custom blocks cho MTBuilder.

Các hàm hỗ trợ thiết kế giao diện sẵn có

Hàm hỗ trợMô tả chức năng
polycms_blog_url()Đường dẫn trang chủ blog
polycms_post_url($post)Đường dẫn chi tiết bài viết
polycms_category_url($cat)Đường dẫn danh mục lưu trữ
polycms_tag_url($tag)Đường dẫn trang lưu trữ thẻ tag
polycms_theme_url()Đường dẫn đến thư mục theme đang kích hoạt
polycms_render_menu($location)Hiển thị menu điều hướng
polycms_render_admin_bar()Hiển thị thanh công cụ admin phía trên cùng
polycms_render_comments($post_id)Hiển thị danh sách và biểu mẫu bình luận
polycms_render_meta_tags($seo)Hiển thị các thẻ SEO meta
polycms_render_widgets($area)Hiển thị các widget trong một khu vực sidebar
polycms_get_option($key, $default)Lấy giá trị cấu hình blog
polycms_apply_filters($hook, $value)Áp dụng bộ lọc cho nội dung
polycms_do_action($hook)Thực thi một action hook

Để xem danh sách đầy đủ, vui lòng đọc Tài liệu Template Tags & Helper Functions.

Quy trình cài đặt

  1. Tạo thư mục my-theme/ trong modules/polycms/themes/.
  2. Thêm đầy đủ các tệp mẫu như mô tả ở trên.
  3. Truy cập mục Blog → Themes trong Perfex CRM.
  4. Tìm "My Custom Theme" và nhấp vào Kích hoạt (Activate).

Nguyên tắc khuyến nghị

  1. Luôn nhúng header.phpfooter.php trong mỗi tệp mẫu để đảm bảo giao diện đồng bộ.
  2. Bắt buộc sử dụng polycms_do_action('polycms_head')polycms_do_action('polycms_footer') để các plugin có thể hoạt động bình thường.
  3. Khử độc dữ liệu (escape) cho tất cả dữ liệu người dùng nhập bằng hàm htmlspecialchars() để chống tấn công XSS.
  4. Luôn gọi polycms_render_admin_bar() để ban quản trị có các lối tắt chỉnh sửa nhanh ngoài trang chủ.

Cần trợ giúp trong quá trình thiết kế Theme?

Nếu bạn cần hỗ trợ thiết kế giao diện tùy biến, muốn học cấu trúc phân cấp mẫu hoặc có bất kỳ câu hỏi nào về CSS/bố cục — đừng ngần ngại liên hệ với chúng tôi. Chúng tôi hỗ trợ hoàn toàn miễn phí cho mọi khách hàng PolyCMS.

Chỉ cần để lại bình luận trên trang của PolyXGO trên CodeCanyon kèm theo mô tả yêu cầu của bạn, đội ngũ phát triển của chúng tôi sẽ hướng dẫn bạn kèm theo code mẫu cụ thể.

Tài liệu liên quan