80 phút
Theme Development
Template Hierarchy
Front Page:
front-page.php → home.php → index.php
Single Post:
single-post.php → single.php → singular.php → index.php
Page:
page-{slug}.php → page-{id}.php → page.php → singular.php → index.php
Category:
category-{slug}.php → category-{id}.php → category.php → archive.php → index.php
Tag, Author, Date, Custom Post Type
Similar hierarchy to category
Cấu trúc theme cơ bản
my-theme/
├── style.css # Required
├── functions.php
├── index.php # Required
├── header.php
├── footer.php
├── sidebar.php
├── single.php
├── page.php
├── archive.php
├── search.php
├── 404.php
├── screenshot.png
├── assets/
│ ├── css/
│ ├── js/
│ └── images/
├── template-parts/
│ ├── content.php
│ ├── content-single.php
│ └── content-page.php
└── inc/
├── customizer.php
├── template-tags.php
└── custom-functions.php
header.php
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header class="site-header">
<div class="container">
<div class="site-branding">
<?php if (has_custom_logo()): ?>
<?php the_custom_logo(); ?>
<?php else: ?>
<a href="<?php echo esc_url(home_url('/')); ?>">
<?php bloginfo('name'); ?>
</a>
<?php endif; ?>
</div>
<nav class="site-nav">
<?php
wp_nav_menu([
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'menu',
'fallback_cb' => false,
]);
?>
</nav>
</div>
</header>
footer.php
<footer class="site-footer">
<div class="container">
<p>© <?php echo date('Y'); ?> <?php bloginfo('name'); ?>.
<?php esc_html_e('All rights reserved.', 'my-theme'); ?>
</p>
<?php
wp_nav_menu([
'theme_location' => 'footer',
'container' => false,
'menu_class' => 'footer-menu',
'depth' => 1,
]);
?>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>
index.php
<?php get_header(); ?>
<main class="site-main container">
<?php if (have_posts()): ?>
<div class="posts-grid">
<?php while (have_posts()): the_post(); ?>
<?php get_template_part('template-parts/content', get_post_type()); ?>
<?php endwhile; ?>
</div>
<?php
the_posts_pagination([
'mid_size' => 2,
'prev_text' => __('« Previous', 'my-theme'),
'next_text' => __('Next »', 'my-theme'),
]);
?>
<?php else: ?>
<p><?php esc_html_e('No posts found.', 'my-theme'); ?></p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
template-parts/content.php
<article id="post-<?php the_ID(); ?>" <?php post_class('post-card'); ?>>
<?php if (has_post_thumbnail()): ?>
<a href="<?php the_permalink(); ?>" class="post-thumbnail">
<?php the_post_thumbnail('medium_large'); ?>
</a>
<?php endif; ?>
<div class="post-content">
<header class="entry-header">
<?php the_title('<h2 class="entry-title"><a href="' . esc_url(get_permalink()) . '">', '</a></h2>'); ?>
<div class="entry-meta">
<time datetime="<?php echo esc_attr(get_the_date('c')); ?>">
<?php echo esc_html(get_the_date()); ?>
</time>
<span class="author">by <?php the_author_posts_link(); ?></span>
</div>
</header>
<div class="entry-summary">
<?php the_excerpt(); ?>
</div>
<a href="<?php the_permalink(); ?>" class="read-more">
<?php esc_html_e('Read more', 'my-theme'); ?> →
</a>
</div>
</article>
single.php
<?php get_header(); ?>
<main class="site-main container">
<?php while (have_posts()): the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('single-post'); ?>>
<header class="entry-header">
<?php the_title('<h1 class="entry-title">', '</h1>'); ?>
<div class="entry-meta">
<time datetime="<?php echo esc_attr(get_the_date('c')); ?>">
<?php echo esc_html(get_the_date()); ?>
</time>
<span>by <?php the_author_posts_link(); ?></span>
<?php if (has_category()): ?>
<span class="categories">
<?php the_category(', '); ?>
</span>
<?php endif; ?>
</div>
</header>
<?php if (has_post_thumbnail()): ?>
<div class="post-thumbnail">
<?php the_post_thumbnail('large'); ?>
</div>
<?php endif; ?>
<div class="entry-content">
<?php the_content(); ?>
<?php
wp_link_pages([
'before' => '<nav class="page-links">' . __('Pages:', 'my-theme'),
'after' => '</nav>',
]);
?>
</div>
<footer class="entry-footer">
<?php the_tags('<div class="tags">', ', ', '</div>'); ?>
</footer>
</article>
<?php
the_post_navigation([
'prev_text' => '<span class="nav-label">Previous</span><span class="nav-title">%title</span>',
'next_text' => '<span class="nav-label">Next</span><span class="nav-title">%title</span>',
]);
if (comments_open() || get_comments_number()) {
comments_template();
}
?>
<?php endwhile; ?>
</main>
<?php get_footer(); ?>
Functions.php essentials
<?php
// Prevent direct access
if (!defined('ABSPATH')) exit;
// Theme constants
define('MY_THEME_VERSION', '1.0.0');
define('MY_THEME_DIR', get_template_directory());
define('MY_THEME_URI', get_template_directory_uri());
// Include files
require_once MY_THEME_DIR . '/inc/template-tags.php';
require_once MY_THEME_DIR . '/inc/customizer.php';
/**
* Theme setup
*/
function my_theme_setup() {
load_theme_textdomain('my-theme', MY_THEME_DIR . '/languages');
add_theme_support('title-tag');
add_theme_support('post-thumbnails');
add_theme_support('custom-logo', [
'height' => 100,
'width' => 400,
'flex-height' => true,
'flex-width' => true,
]);
add_theme_support('html5', ['search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script']);
add_theme_support('responsive-embeds');
add_theme_support('align-wide');
add_theme_support('editor-styles');
// Image sizes
add_image_size('my-theme-featured', 1200, 600, true);
add_image_size('my-theme-thumbnail', 600, 400, true);
register_nav_menus([
'primary' => __('Primary Menu', 'my-theme'),
'footer' => __('Footer Menu', 'my-theme'),
]);
}
add_action('after_setup_theme', 'my_theme_setup');
/**
* Enqueue scripts and styles
*/
function my_theme_scripts() {
wp_enqueue_style(
'my-theme-style',
get_stylesheet_uri(),
[],
MY_THEME_VERSION
);
wp_enqueue_style(
'my-theme-main',
MY_THEME_URI . '/assets/css/main.css',
['my-theme-style'],
MY_THEME_VERSION
);
wp_enqueue_script(
'my-theme-script',
MY_THEME_URI . '/assets/js/main.js',
[],
MY_THEME_VERSION,
true
);
if (is_singular() && comments_open() && get_option('thread_comments')) {
wp_enqueue_script('comment-reply');
}
}
add_action('wp_enqueue_scripts', 'my_theme_scripts');
/**
* Register widget areas
*/
function my_theme_widgets_init() {
register_sidebar([
'name' => __('Sidebar', 'my-theme'),
'id' => 'sidebar-1',
'description' => __('Add widgets here.', 'my-theme'),
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
]);
register_sidebar([
'name' => __('Footer Column 1', 'my-theme'),
'id' => 'footer-1',
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
]);
}
add_action('widgets_init', 'my_theme_widgets_init');
Template tags
<?php // app/inc/template-tags.php ?>
/**
* Display post meta
*/
function my_theme_post_meta() {
printf(
'<div class="entry-meta">
<time datetime="%1$s">%2$s</time>
<span class="author">%3$s</span>
</div>',
esc_attr(get_the_date('c')),
esc_html(get_the_date()),
sprintf(
'<a href="%1$s">%2$s</a>',
esc_url(get_author_posts_url(get_the_author_meta('ID'))),
esc_html(get_the_author())
)
);
}
/**
* Get the post thumbnail URL
*/
function my_theme_get_thumbnail_url($size = 'full') {
if (!has_post_thumbnail()) {
return get_theme_file_uri('/assets/images/placeholder.jpg');
}
return get_the_post_thumbnail_url(get_the_ID(), $size);
}
/**
* Breadcrumbs
*/
function my_theme_breadcrumbs() {
if (is_front_page()) return;
echo '<nav class="breadcrumbs" aria-label="Breadcrumb"><ol>';
echo '<li><a href="' . esc_url(home_url('/')) . '">Home</a></li>';
if (is_single()) {
$categories = get_the_category();
if (!empty($categories)) {
echo '<li><a href="' . esc_url(get_category_link($categories[0]->term_id)) . '">'
. esc_html($categories[0]->name) . '</a></li>';
}
echo '<li aria-current="page">' . esc_html(get_the_title()) . '</li>';
} elseif (is_page()) {
echo '<li aria-current="page">' . esc_html(get_the_title()) . '</li>';
} elseif (is_category()) {
echo '<li aria-current="page">' . esc_html(single_cat_title('', false)) . '</li>';
}
echo '</ol></nav>';
}
Custom Post Types và Taxonomies
/**
* Register custom post type: Portfolio
*/
function my_theme_register_cpt() {
register_post_type('portfolio', [
'labels' => [
'name' => __('Portfolio', 'my-theme'),
'singular_name' => __('Project', 'my-theme'),
'add_new' => __('Add New', 'my-theme'),
'add_new_item' => __('Add New Project', 'my-theme'),
'edit_item' => __('Edit Project', 'my-theme'),
'view_item' => __('View Project', 'my-theme'),
'search_items' => __('Search Projects', 'my-theme'),
'not_found' => __('No projects found', 'my-theme'),
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-portfolio',
'menu_position' => 20,
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
'rewrite' => ['slug' => 'projects'],
'show_in_rest' => true, // Gutenberg support
]);
register_taxonomy('portfolio_category', 'portfolio', [
'labels' => [
'name' => __('Project Categories', 'my-theme'),
'singular_name' => __('Project Category', 'my-theme'),
],
'hierarchical' => true,
'show_in_rest' => true,
'rewrite' => ['slug' => 'project-category'],
]);
register_taxonomy('portfolio_tag', 'portfolio', [
'labels' => [
'name' => __('Project Tags', 'my-theme'),
'singular_name' => __('Project Tag', 'my-theme'),
],
'hierarchical' => false,
'show_in_rest' => true,
]);
}
add_action('init', 'my_theme_register_cpt');
Customizer
<?php // app/inc/customizer.php ?>
function my_theme_customize_register($wp_customize) {
// Add section
$wp_customize->add_section('my_theme_options', [
'title' => __('Theme Options', 'my-theme'),
'priority' => 30,
]);
// Footer text setting
$wp_customize->add_setting('footer_text', [
'default' => '© ' . date('Y') . ' ' . get_bloginfo('name'),
'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
]);
$wp_customize->add_control('footer_text', [
'label' => __('Footer Text', 'my-theme'),
'section' => 'my_theme_options',
'type' => 'text',
]);
// Primary color
$wp_customize->add_setting('primary_color', [
'default' => '#0073aa',
'sanitize_callback' => 'sanitize_hex_color',
]);
$wp_customize->add_control(
new WP_Customize_Color_Control($wp_customize, 'primary_color', [
'label' => __('Primary Color', 'my-theme'),
'section' => 'colors',
])
);
}
add_action('customize_register', 'my_theme_customize_register');
/**
* Output customizer CSS
*/
function my_theme_customizer_css() {
$primary = get_theme_mod('primary_color', '#0073aa');
?>
<style>
:root {
--primary-color: <?php echo esc_attr($primary); ?>;
}
a, button, .button {
color: var(--primary-color);
}
</style>
<?php
}
add_action('wp_head', 'my_theme_customizer_css');
Bài tập thực hành
Hãy tạo complete theme với custom post type!