📦 فصل ۱۰: 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
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’) |
بعد از ثبت CPT جدید، باید پیوندهای یکتا رو دوباره ذخیره کنی (تنظیمات → پیوندهای یکتا → ذخیره). یا توی activation hook این تابع رو فراخوانی کن.
🏷 Taxonomy های سفارشی
taxonomy یعنی دستهبندی. وردپرس بهصورت پیشفرض category و post_tag داره. میتونی برای CPT خودت 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' );
📋 متاباکسها
متاباکسها فیلدهای اختصاصی هستن که توی صفحه ویرایش نوشته نمایش داده میشن.
ساخت متاباکس
// ثبت متاباکس
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
💾 ذخیره دادهها
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 از save_post_book استفاده میکنیم تا فقط برای post type ما اجرا بشه. کارایی بهتری داره.
🎨 نمایش در فرانت
قالب 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(); ?>
🎯 مثالهای کامل
مثال ۱: ستون اختصاصی در لیست ادمین
// اضافه کردن ستون
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 برای محصولات کردی – چند متاباکس مختلف برای جزئیات، گالری، تغییرات نسخه و…
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، پیوندهای یکتا رو ریست کن