نوشته و برگه برای وبلاگ و صفحه‌های ثابت کافی‌اند، ولی وقتی سایت کتاب‌فروشی، نمونه‌کار، رویداد یا فهرست اعضای تیم دارد، چیدن همه‌چیز در «نوشته» مثل ریختن همه‌ی وسایل خانه در یک کمد است. پست‌تایپ سفارشی (Custom Post Type) یک کمد جدا با برچسب خودش در پیشخوان می‌دهد و تاکسونومی سفارشی (Custom Taxonomy) دسته‌بندی مخصوص همان کمد.

در این مقاله پست‌تایپ «کتاب» و تاکسونومی «ژانر» را از صفر می‌سازیم. اگر با قلاب‌ها آشنا نیستید، اول قلاب‌های وردپرس را بخوانید؛ همه‌ی ثبت‌ها با قلاب init انجام می‌شود.

کجا کد بنویسیم#

کد پست‌تایپ را در یک افزونه‌ی کوچک بگذارید، نه در functions.php. داده‌ی سایت نباید به قالب گره بخورد. وگرنه با تعویض قالب، همه‌ی کتاب‌ها از پیشخوان غیب می‌شوند (در دیتابیس هستند ولی دیده نمی‌شوند). ساختار افزونه در ساخت افزونه‌ی وردپرس از صفر آمده است.

ثبت پست‌تایپ#

PHP
<?php
/**
 * Plugin Name: Demo Books
 */
defined( 'ABSPATH' ) || exit;

function demo_register_book(): void {
	register_post_type( 'demo_book', array(
		'labels'       => array(
			'name'          => 'کتاب‌ها',
			'singular_name' => 'کتاب',
			'add_new_item'  => 'افزودن کتاب',
			'edit_item'     => 'ویرایش کتاب',
		),
		'public'       => true,
		'has_archive'  => 'books',
		'rewrite'      => array( 'slug' => 'book' ),
		'menu_icon'    => 'dashicons-book',
		'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
		'show_in_rest' => true,
	) );
}
add_action( 'init', 'demo_register_book' );

نکته‌های مهم:

  • نام داخلی (demo_book) حداکثر ۲۰ نویسه، فقط حروف کوچک لاتین، عدد و زیرخط، با پیشوند افزونه تا با دیگران تداخل نکند. بعد از انتشار، تغییرش جدا شدن نوشته‌ها از نوع را به‌دنبال دارد.
  • has_archive صفحه‌ی فهرست همه‌ی کتاب‌ها را در آدرس /books می‌سازد.
  • supports فقط چیزهایی را فعال کنید که لازم است.
  • show_in_rest برای ویرایشگر بلوک و REST API لازم است؛ درباره‌ی API در REST API وردپرس خواهید خواند.

ثبت تاکسونومی#

PHP
<?php
function demo_register_genre(): void {
	register_taxonomy( 'demo_genre', array( 'demo_book' ), array(
		'labels'       => array(
			'name'          => 'ژانرها',
			'singular_name' => 'ژانر',
		),
		'hierarchical' => true,   // مثل دسته‌بندی؛ false یعنی مثل برچسب
		'public'       => true,
		'rewrite'      => array( 'slug' => 'genre' ),
		'show_in_rest' => true,
	) );
}
add_action( 'init', 'demo_register_genre' );

hierarchical => true ساختار درختی (والد و فرزند) می‌دهد و false ساختار آزاد و برچسب‌مانند. تاکسونومی را می‌شود به چند پست‌تایپ وصل کرد؛ کافی است نام‌ها را در آرایه‌ی دوم بیاورید.

رفع ۴۰۴ با تازه‌سازی پیوندهای یکتا#

وردپرس قواعد بازنویسی را در دیتابیس ذخیره می‌کند. بعد از ثبت پست‌تایپ تازه، آدرس‌ها تا وقتی قواعد تازه نشوند ۴۰۴ می‌دهند. دستی: از پیشخوان، تنظیمات، پیوندهای یکتا و زدن «ذخیره». در افزونه، هنگام فعال‌سازی:

PHP
<?php
function demo_activate(): void {
	demo_register_book();
	demo_register_genre();
	flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'demo_activate' );

ابتدا ثبت‌ها را صدا بزنید و بعد تازه‌سازی کنید. هرگز flush_rewrite_rules() را روی هر بارگذاری صفحه اجرا نکنید؛ عملیات سنگینی است.

چه وقت پست‌تایپ نسازیم#

هر پست‌تایپ جدید یعنی یک فهرست دیگر برای مدیریت، یک قالب دیگر برای طراحی و قواعد آدرس بیشتر. اگر تفاوت محتوا فقط در یک برچسب است («خبر» و «مقاله»)، دسته‌بندی کافی است. اگر فقط یک دو فیلد اضافه می‌خواهید، شاید همان نوشته با فیلد سفارشی کافی باشد. پست‌تایپ را وقتی بسازید که این سه پرسش جواب «بله» داشته باشد: ساختار صفحه‌ی مدیریت فرق دارد؟ آدرس و قالب نمایش جدا می‌خواهد؟ در آینده خروجی API جدا لازم می‌شود؟

افزودن فیلد سفارشی به کتاب#

کتاب معمولاً نویسنده دارد. ساده‌ترین راه یک جعبه‌ی کنار ویرایشگر با nonce و بررسی دسترسی است:

PHP
<?php
function demo_book_box(): void {
	add_meta_box( 'demo_author_box', 'نویسنده', 'demo_book_box_html', 'demo_book' );
}
add_action( 'add_meta_boxes', 'demo_book_box' );

function demo_book_box_html( WP_Post $post ): void {
	wp_nonce_field( 'demo_save_author', 'demo_author_nonce' );
	$value = get_post_meta( $post->ID, '_demo_author', true );
	echo '<input type="text" name="demo_author" value="' . esc_attr( $value ) . '" class="widefat">';
}

function demo_save_author( int $post_id ): void {
	if ( ! isset( $_POST['demo_author_nonce'] )
		|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['demo_author_nonce'] ) ), 'demo_save_author' ) ) {
		return;
	}
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}
	if ( isset( $_POST['demo_author'] ) ) {
		update_post_meta( $post_id, '_demo_author', sanitize_text_field( wp_unslash( $_POST['demo_author'] ) ) );
	}
}
add_action( 'save_post_demo_book', 'demo_save_author' );

ورودی با sanitize_text_field پاک‌سازی و هنگام نمایش با esc_attr escape می‌شود. پیشوند زیرخط در نام فیلد (_demo_author) آن را از فهرست فیلدهای سفارشی پیشخوان پنهان می‌کند.

نمایش در قالب#

وردپرس برای پست‌تایپ فایل‌های مخصوص را به این ترتیب جست‌وجو می‌کند:

صفحهفایل قالب
فهرست همه‌ی کتاب‌هاarchive-demo_book.php
یک کتابsingle-demo_book.php
فهرست یک ژانرtaxonomy-demo_genre.php

اگر فایل مخصوص نبود، به archive.php و single.php برمی‌گردد. در قالب‌های بلوکی همین نقش را قالب‌های ساخته‌شده در ویرایشگر سایت بازی می‌کنند (قالب‌های بلوکی و ویرایشگر سایت).

پرس‌وجو از کتاب‌ها#

PHP
<?php
$books = new WP_Query( array(
	'post_type'      => 'demo_book',
	'posts_per_page' => 6,
	'tax_query'      => array(
		array(
			'taxonomy' => 'demo_genre',
			'field'    => 'slug',
			'terms'    => 'novel',
		),
	),
) );

if ( $books->have_posts() ) {
	while ( $books->have_posts() ) {
		$books->the_post();
		echo '<h3><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></h3>';
	}
	wp_reset_postdata();
}

wp_reset_postdata() را بعد از حلقه‌ی دوم فراموش نکنید؛ وگرنه بقیه‌ی صفحه با داده‌ی اشتباه کار می‌کند.

خلاصه#

  • پست‌تایپ برای نوع محتوایی است که ساختار و صفحه‌ی مدیریت جدا می‌خواهد؛ تاکسونومی دسته‌بندی همان.
  • ثبت‌ها را با قلاب init و در یک افزونه انجام دهید، نه در قالب.
  • نام داخلی کوتاه و پیشوندی باشد و بعد از انتشار عوض نشود.
  • پس از ثبت، پیوندهای یکتا را تازه کنید تا ۴۰۴ از بین برود.
  • show_in_rest را برای ویرایشگر بلوک و API فعال کنید.
  • در قالب از فایل‌های archive- و single- و برای پرس‌وجو از WP_Query استفاده کنید.