~/icsd.ir — bash
SYSTEM_ONLINE

📦 فصل ۱۰: Custom Post Type و متاباکس

Custom Post Type (CPT) قابلیتیه که اجازه می‌ده ساختار محتوایی جدید بسازی - مثل محصول، نمونه‌کار، رویداد و... این فصل دقیقاً همون چیزیه که در قالب ICSD انجام دادی!

Custom Post Type (CPT) قابلیتیه که اجازه می‌ده ساختار محتوایی جدید بسازی – مثل محصول، نمونه‌کار، رویداد و… این فصل دقیقاً همون چیزیه که در قالب ICSD انجام دادی!

🤔 CPT چیست؟

وردپرس به‌صورت پیش‌فرض چند نوع پست داره: post, page, attachment, revision, nav_menu_item. می‌تونی نوع‌های جدید هم بسازی.

کِی به CPT نیاز داریم؟

  • وقتی محتوای ساختاریافته داریم که با «نوشته» سازگار نیست
  • وقتی می‌خوایم آرشیو جدا با URL مخصوص داشته باشه
  • وقتی فیلدهای اختصاصی نیاز داریم
  • وقتی دسته‌بندی متفاوت می‌خوایم

مثال‌های واقعی

نوع سایت CPT های احتمالی
شرکت نرم‌افزاری محصول، خدمت، نمونه‌کار، تیم
املاک ملک، آژانس، مشاور
رستوران منو، دسر، نوشیدنی
سایت آموزشی دوره، استاد، دانشجو
سایت رویداد رویداد، اسپانسر

📝 ثبت CPT

register_post_type

functions.php یا plugin
function register_book_cpt() {
    $labels = array(
        'name'                  => 'کتاب‌ها',
        'singular_name'         => 'کتاب',
        'menu_name'             => 'کتاب‌ها',
        'add_new'               => 'افزودن کتاب',
        'add_new_item'          => 'افزودن کتاب جدید',
        'edit_item'             => 'ویرایش کتاب',
        'new_item'              => 'کتاب جدید',
        'view_item'             => 'نمایش کتاب',
        'view_items'            => 'نمایش کتاب‌ها',
        'search_items'          => 'جستجوی کتاب',
        'not_found'             => 'کتابی یافت نشد',
        'all_items'             => 'همه کتاب‌ها',
        'archives'              => 'آرشیو کتاب‌ها',
        'featured_image'        => 'تصویر جلد',
        'set_featured_image'    => 'انتخاب تصویر جلد',
    );

    $args = array(
        'labels'              => $labels,
        'public'              => true,                    // قابل دسترسی عمومی
        'has_archive'         => true,                    // داره صفحه آرشیو؟
        'show_in_rest'        => true,                    // در REST API؟
        'show_in_nav_menus'   => true,                    // در منو قابل اضافه؟
        'menu_icon'           => 'dashicons-book',        // آیکن
        'menu_position'       => 5,                       // جایگاه در منو
        'hierarchical'        => false,                   // مثل برگه (والد/فرزند)
        'capability_type'     => 'post',                  // capability ها
        'rewrite'             => array(
            'slug'       => 'books',                      // /books/title/
            'with_front' => false,
        ),
        'supports'            => array(
            'title',
            'editor',
            'thumbnail',
            'excerpt',
            'custom-fields',
            'revisions',
            'page-attributes',
            'comments',
            'author',
        ),
    );

    register_post_type( 'book', $args );
}
add_action( 'init', 'register_book_cpt' );

پارامترهای مهم

پارامتر توضیح
public قابل دسترسی برای کاربران
has_archive صفحه آرشیو داره؟
show_in_rest پشتیبانی از Gutenberg و API
menu_icon آیکن (از dashicons یا base64)
hierarchical true = مثل page، false = مثل post
supports فیلدهایی که داره
rewrite URL سفارشی
capability_type دسترسی (‘post’ یا ‘page’)
⚠️

flush_rewrite_rules

بعد از ثبت CPT جدید، باید پیوندهای یکتا رو دوباره ذخیره کنی (تنظیمات → پیوندهای یکتا → ذخیره). یا توی activation hook این تابع رو فراخوانی کن.

🏷 Taxonomy های سفارشی

taxonomy یعنی دسته‌بندی. وردپرس به‌صورت پیش‌فرض category و post_tag داره. می‌تونی برای CPT خودت taxonomy جدید بسازی.

register_taxonomy

register_taxonomy
function register_book_taxonomies() {
    
    // ژانر کتاب (سلسله‌مراتبی - مثل category)
    register_taxonomy( 'book_genre', 'book', array(
        'labels' => array(
            'name'          => 'ژانرها',
            'singular_name' => 'ژانر',
            'menu_name'     => 'ژانرها',
            'all_items'     => 'همه ژانرها',
            'edit_item'     => 'ویرایش ژانر',
            'add_new_item'  => 'افزودن ژانر',
        ),
        'hierarchical'      => true,        // مثل category
        'show_ui'           => true,
        'show_admin_column' => true,        // در لیست ادمین نمایش بده
        'show_in_rest'      => true,
        'rewrite'           => array( 'slug' => 'genre' ),
    ) );
    
    // نویسنده (افقی - مثل tag)
    register_taxonomy( 'book_author', 'book', array(
        'labels' => array(
            'name'          => 'نویسنده‌ها',
            'singular_name' => 'نویسنده',
        ),
        'hierarchical'      => false,       // مثل tag
        'show_ui'           => true,
        'show_admin_column' => true,
        'show_in_rest'      => true,
        'rewrite'           => array( 'slug' => 'author' ),
    ) );
}
add_action( 'init', 'register_book_taxonomies' );

📋 متاباکس‌ها

متاباکس‌ها فیلدهای اختصاصی هستن که توی صفحه ویرایش نوشته نمایش داده می‌شن.

ساخت متاباکس

add_meta_box
// ثبت متاباکس
function add_book_metaboxes() {
    add_meta_box(
        'book_details',           // ID
        '📚 جزئیات کتاب',         // عنوان
        'render_book_metabox',    // callback
        'book',                   // post type
        'normal',                 // context: normal, side, advanced
        'high'                    // priority: high, default, low
    );
}
add_action( 'add_meta_boxes', 'add_book_metaboxes' );

// محتوای متاباکس
function render_book_metabox( $post ) {
    // nonce برای امنیت
    wp_nonce_field( 'save_book_meta', 'book_meta_nonce' );
    
    // مقادیر فعلی
    $isbn       = get_post_meta( $post->ID, '_book_isbn', true );
    $pages      = get_post_meta( $post->ID, '_book_pages', true );
    $price      = get_post_meta( $post->ID, '_book_price', true );
    $year       = get_post_meta( $post->ID, '_book_year', true );
    $is_featured = get_post_meta( $post->ID, '_book_featured', true );
    ?>
    <style>
        .book-meta { padding: 10px 0; }
        .book-meta .row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 12px; }
        .book-meta label { display: block; font-weight: 600; margin-bottom: 6px; }
        .book-meta input[type="text"], 
        .book-meta input[type="number"] { width: 100%; padding: 8px; }
    </style>
    
    <div class="book-meta">
        <div class="row">
            <div>
                <label>ISBN</label>
                <input type="text" name="book_isbn" 
                    value="<?php echo esc_attr( $isbn ); ?>"
                    placeholder="978-XXX-XXX-X">
            </div>
            <div>
                <label>تعداد صفحات</label>
                <input type="number" name="book_pages" 
                    value="<?php echo esc_attr( $pages ); ?>">
            </div>
        </div>
        
        <div class="row">
            <div>
                <label>قیمت (تومان)</label>
                <input type="number" name="book_price" 
                    value="<?php echo esc_attr( $price ); ?>">
            </div>
            <div>
                <label>سال انتشار</label>
                <input type="number" name="book_year" 
                    value="<?php echo esc_attr( $year ); ?>"
                    min="1300" max="1500">
            </div>
        </div>
        
        <div>
            <label>
                <input type="checkbox" name="book_featured" value="1"
                    <?php checked( $is_featured, '1' ); ?>>
                کتاب ویژه (در صفحه اصلی نمایش بده)
            </label>
        </div>
    </div>
    <?php
}

گزینه‌های context

  • normal: در ستون اصلی (کنار editor)
  • side: در سایدبار راست
  • advanced: پایین normal

💾 ذخیره داده‌ها

save_post hook
function save_book_meta( $post_id ) {
    
    // ۱. بررسی nonce
    if ( ! isset( $_POST['book_meta_nonce'] ) || 
         ! wp_verify_nonce( $_POST['book_meta_nonce'], 'save_book_meta' ) ) {
        return;
    }
    
    // ۲. جلوگیری از autosave
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    
    // ۳. بررسی دسترسی
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }
    
    // ۴. ذخیره فیلدها
    $fields = array(
        'book_isbn'  => 'sanitize_text_field',
        'book_pages' => 'absint',
        'book_price' => 'absint',
        'book_year'  => 'absint',
    );
    
    foreach ( $fields as $field => $sanitize_func ) {
        if ( isset( $_POST[ $field ] ) ) {
            $value = call_user_func( $sanitize_func, $_POST[ $field ] );
            update_post_meta( $post_id, '_' . $field, $value );
        }
    }
    
    // checkbox
    update_post_meta( $post_id, '_book_featured', 
        isset( $_POST['book_featured'] ) ? '1' : '0' );
}
add_action( 'save_post_book', 'save_book_meta' );
💡

save_post_{post_type}

به جای save_post از save_post_book استفاده می‌کنیم تا فقط برای post type ما اجرا بشه. کارایی بهتری داره.

🎨 نمایش در فرانت

قالب single-book.php

single-book.php
<?php get_header(); ?>

<main class="container">
<?php while ( have_posts() ) : the_post(); 
    $isbn  = get_post_meta( get_the_ID(), '_book_isbn', true );
    $pages = get_post_meta( get_the_ID(), '_book_pages', true );
    $price = get_post_meta( get_the_ID(), '_book_price', true );
    $year  = get_post_meta( get_the_ID(), '_book_year', true );
    
    $genres  = get_the_terms( get_the_ID(), 'book_genre' );
    $authors = get_the_terms( get_the_ID(), 'book_author' );
?>
    <article class="single-book">
        <div class="book-cover">
            <?php the_post_thumbnail( 'large' ); ?>
        </div>
        
        <div class="book-info">
            <h1><?php the_title(); ?></h1>
            
            <?php if ( $authors && ! is_wp_error( $authors ) ) : ?>
                <p class="authors">
                    نویسنده: 
                    <?php foreach ( $authors as $author ) : ?>
                        <a href="<?php echo get_term_link( $author ); ?>">
                            <?php echo esc_html( $author->name ); ?>
                        </a>
                    <?php endforeach; ?>
                </p>
            <?php endif; ?>
            
            <table class="book-details">
                <?php if ( $isbn ) : ?>
                    <tr><td>ISBN</td><td><?php echo esc_html( $isbn ); ?></td></tr>
                <?php endif; ?>
                <?php if ( $pages ) : ?>
                    <tr><td>صفحات</td><td><?php echo number_format_i18n( $pages ); ?></td></tr>
                <?php endif; ?>
                <?php if ( $year ) : ?>
                    <tr><td>سال انتشار</td><td><?php echo esc_html( $year ); ?></td></tr>
                <?php endif; ?>
                <?php if ( $price ) : ?>
                    <tr><td>قیمت</td><td><?php echo number_format_i18n( $price ); ?> تومان</td></tr>
                <?php endif; ?>
            </table>
            
            <div class="book-content">
                <?php the_content(); ?>
            </div>
        </div>
    </article>
<?php endwhile; ?>
</main>

<?php get_footer(); ?>

🎯 مثال‌های کامل

متوسط

مثال ۱: ستون اختصاصی در لیست ادمین

admin columns
// اضافه کردن ستون
add_filter( 'manage_book_posts_columns', function( $columns ) {
    $new = array();
    $new['cb']     = $columns['cb'];
    $new['title']  = $columns['title'];
    $new['cover']  = 'جلد';
    $new['author'] = 'نویسنده';
    $new['price']  = 'قیمت';
    $new['date']   = $columns['date'];
    return $new;
} );

// محتوای ستون
add_action( 'manage_book_posts_custom_column', function( $col, $post_id ) {
    switch ( $col ) {
        case 'cover':
            echo get_the_post_thumbnail( $post_id, array( 50, 60 ) );
            break;
        case 'author':
            $authors = get_the_terms( $post_id, 'book_author' );
            if ( $authors ) {
                $names = wp_list_pluck( $authors, 'name' );
                echo esc_html( implode( ', ', $names ) );
            }
            break;
        case 'price':
            $price = get_post_meta( $post_id, '_book_price', true );
            echo $price ? number_format_i18n( $price ) . ' تومان' : '—';
            break;
    }
}, 10, 2 );

پیشرفته

مثال ۲: متاباکس متعدد با تب

مثل کاری که در ICSD theme برای محصولات کردی – چند متاباکس مختلف برای جزئیات، گالری، تغییرات نسخه و…

multiple metaboxes
function register_book_metaboxes() {
    add_meta_box( 'book_details', '📖 جزئیات', 
        'render_details_box', 'book', 'normal', 'high' );
    
    add_meta_box( 'book_chapters', '📑 فصل‌ها', 
        'render_chapters_box', 'book', 'normal', 'default' );
    
    add_meta_box( 'book_reviews', '⭐ نظرات منتقدان', 
        'render_reviews_box', 'book', 'normal', 'default' );
    
    add_meta_box( 'book_promo', '🎯 تنظیمات نمایش', 
        'render_promo_box', 'book', 'side', 'high' );
}
add_action( 'add_meta_boxes', 'register_book_metaboxes' );

📝 خلاصه فصل

  • CPT برای ساختار محتوایی جدید – با register_post_type
  • Taxonomy برای دسته‌بندی – hierarchical (دسته) یا flat (برچسب)
  • متاباکس برای فیلدهای اختصاصی – با add_meta_box
  • ذخیره با save_post_{type} و sanitize کردن داده‌ها
  • فایل single-{type}.php برای نمایش فرانت
  • بعد از ثبت CPT، پیوندهای یکتا رو ریست کن

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.