هفت سال پیش، یک مشتری از من خواست که برایش یک سیستم ورود انبوه محصولات بسازم. فایل CSV داشت با هزار محصول و می‌خواست در چند دقیقه همه وارد شوند. من به‌جای استفاده از توابع استاندارد وردپرس، مستقیماً کوئری SQL نوشتم. سه ساعت بعد، سایت با خطای حافظه از دسترس خارج شد. وقتی با phpMyAdmin دیتابیس را باز کردم، جدول wp_posts پر بود از ردیف‌های ناقص و متادیتاهای یتیم. آن روز درس گرفتم که wp_insert_post فقط یک تابع ساده نیست؛ لایه‌ای از امنیت، اعتبارسنجی و اعمال hookهاست که SQL خام از آن بی‌بهره است. از آن روز، هیچ نوشته‌ای را با SQL خام نساخته‌ام و هیچ نوشته‌ای را هم بدون بررسی دسترسی حذف نکرده‌ام.

در این مقاله، تجربه‌ام از کار با توابع ایجاد و حذف نوشته را با شما به اشتراک می‌گذارم. اگر با مفاهیم پایه آشنا نیستید، وردپرس چیست و چگونه شروع کنیم و نحوه استفاده از توابع وردپرس در پروژه‌ها پیش‌نیاز است. این مقاله مکمل توابع وردپرس برای داده‌های نوشته است که روی خواندن تمرکز دارد؛ اینجا روی نوشتن، ویرایش و حذف.

چرا توابع استاندارد مهم‌اند؟

وردپرس در جدول wp_posts، تمام نوشته‌ها، برگه‌ها، CPTها، و ضمیمه‌ها را نگه می‌دارد. اگر این جدول را با کوئری خام تغییر دهید، سه لایه حیاتی را از دست می‌دهید:

  • لایه Hookها: توابع استاندارد، هوک‌هایی مثل save_post، wp_insert_post، و transition_post_status را فعال می‌کنند. این هوک‌ها، ستون اصلی اکوسیستم افزونه‌ها هستند. اگر با SQL خام بنویسید، افزونه‌های امنیتی، سئو، و کش شما از تغییر باخبر نمی‌شوند.
  • لایه اعتبارسنجی: wp_insert_post، پاک‌سازی محتوا، مدیریت slug، تعیین نویسنده، و مدیریت revisionها را خودکار انجام می‌دهد. اگر با SQL خام بنویسید، باید همه این‌ها را دستی پیاده کنید.
  • لایه کش: وردپرس کش داخلی دارد که با SQL خام، به‌روز نمی‌شود. راهنمای کامل در افزایش سرعت سایت وردپرسی و بهینه‌سازی کد وردپرس.

راهنمای جزئی این مباحث در ساختار هسته وردپرس و توابع داده‌های نوشته آمده است.

نوشتن با SQL خام در وردپرس، مثل رانندگی با چراغ خاموش است؛ ممکن است برسید، ولی نه سالم و نه بدون آسیب.

ساخت نوشته با wp_insert_post

تابع اصلی ساخت نوشته، wp_insert_post است. این تابع، هم نوشته جدید می‌سازد و هم نوشته موجود را به‌روزرسانی می‌کند:

$post_id = wp_insert_post( array(
    "post_title"    => "عنوان نوشته",
    "post_content"  => "محتوای نوشته",
    "post_excerpt"  => "خلاصه نوشته",
    "post_status"   => "publish",
    "post_type"     => "post",
    "post_author"   => get_current_user_id(),
    "post_category" => array( 5, 12 ),
    "tags_input"    => array( "وردپرس", "آموزش" ),
), true );

if ( is_wp_error( $post_id ) ) {
    error_log( $post_id->get_error_message() );
} else {
    // نوشته ساخته شد
}

پارامتر دوم true، باعث می‌شود در صورت خطا، WP_Error برگردد به‌جای 0. همیشه این پارامتر را بگذارید و is_wp_error را بررسی کنید. راهنمای کامل در توابع داده‌های نوشته.

پارامترهای پرکاربرد wp_insert_post:

پارامترتوضیح
post_titleعنوان نوشته
post_contentمحتوا (HTML مجاز)
post_excerptخلاصه
post_statuspublish، draft، private، pending
post_typepost، page، یا CPT سفارشی
post_authorشناسه نویسنده
post_nameنامک (slug)
post_dateتاریخ انتشار (فرمت MySQL)
post_categoryآرایه شناسه دسته‌ها
tags_inputآرایه برچسب‌ها
meta_inputآرایه متادیتا

ساخت نوشته با متادیتا

از وردپرس ۴.۴، می‌توانید متادیتا را هم در همان درخواست wp_insert_post پاس دهید:

$post_id = wp_insert_post( array(
    "post_title"  => "محصول جدید",
    "post_type"   => "product",
    "post_status" => "publish",
    "meta_input"  => array(
        "_price"     => 250000,
        "_sku"       => "SKU-2024-001",
        "_in_stock"  => "yes",
        "_gallery"   => array( 101, 102, 103 ),
    ),
), true );

مزیت این روش: تمام متادیتا در یک عملیات نوشته می‌شود و هوک‌ها یک بار اجرا می‌شوند. اگر بعد از ساخت نوشته، متادیتا را جداگانه اضافه کنید، هر متا یک نوشتن جداگانه است و در سایت‌های بزرگ، فشار محسوسی روی دیتابیس وارد می‌کند. راهنمای کامل در کار با متاباکس‌ها، توابع متادیتا، و کار با Options API.

به‌روزرسانی نوشته موجود

همان تابع wp_insert_post، اگر پارامتر ID داشته باشد، نوشته را به‌روزرسانی می‌کند:

wp_insert_post( array(
    "ID"           => $post_id,
    "post_title"   => "عنوان جدید",
    "post_content" => "محتوای جدید",
) );

نکته مهم: اگر می‌خواهید فقط یک فیلد را تغییر دهید، باید همه فیلدهای دیگر را هم بفرستید، چون wp_insert_post فیلدهای غایب را از دیتابیس نمی‌خواند و فیلدهای موجود را پاک می‌کند. راه‌حل بهتر: از wp_update_post استفاده کنید که همین تابع است ولی به‌عنوان wrapper تمیزتر به‌کار می‌رود:

wp_update_post( array(
    "ID"         => $post_id,
    "post_title" => "عنوان جدید",
) );

راهنمای کامل نوشته و برگه در توابع داده‌های نوشته.

ساخت برگه و CPT

برای ساخت برگه، همان wp_insert_post استفاده می‌شود ولی post_type را روی page می‌گذارید. برای CPT سفارشی، باید post_type را با نام CPT ثبت‌شده تطبیق دهید:

// ساخت برگه
wp_insert_post( array(
    "post_title"  => "درباره ما",
    "post_type"   => "page",
    "post_status" => "publish",
) );

// ساخت CPT سفارشی
wp_insert_post( array(
    "post_title"  => "پروژه اول",
    "post_type"   => "project",
    "post_status" => "publish",
) );

نکته: اگر CPT سفارشی دارید، نام آن باید در سراسر پروژه یکسان باشد. راهنمای کامل در ساخت نوع نوشته سفارشی در وردپرس و کار با CPT در وردپرس.

ساخت نوشته از فرم front-end

یکی از کاربردهای رایج، اجازه دادن به کاربران برای ساخت نوشته از front-end است. اینجا امنیت حیاتی است:

function myplugin_handle_frontend_submit() {
    if ( ! isset( $_POST["myplugin_nonce"] ) || 
         ! wp_verify_nonce( $_POST["myplugin_nonce"], "myplugin_submit" ) ) {
        wp_die( "درخواست نامعتبر" );
    }
    
    if ( ! is_user_logged_in() ) {
        wp_die( "برای ارسال نوشته باید وارد شوید" );
    }
    
    if ( ! current_user_can( "publish_posts" ) ) {
        wp_die( "شما اجازه ارسال ندارید" );
    }
    
    $title   = sanitize_text_field( wp_unslash( $_POST["post_title"] ) );
    $content = wp_kses_post( wp_unslash( $_POST["post_content"] ) );
    
    if ( empty( $title ) || empty( $content ) ) {
        wp_die( "عنوان و محتوا الزامی است" );
    }
    
    $post_id = wp_insert_post( array(
        "post_title"   => $title,
        "post_content" => $content,
        "post_status"  => "pending",
        "post_author"  => get_current_user_id(),
    ), true );
    
    if ( is_wp_error( $post_id ) ) {
        wp_die( "خطا در ساخت نوشته" );
    }
    
    wp_safe_redirect( get_permalink( $post_id ) );
    exit;
}
add_action( "template_redirect", "myplugin_handle_frontend_submit" );

سه اصل امنیتی: نانس، بررسی دسترسی، و پاک‌سازی ورودی. راهنمای کامل در نانس وردپرس، پاک‌سازی داده‌ها، اعتبارسنجی داده‌ها، و PHP امن در وردپرس.

حذف نوشته: wp_delete_post و wp_trash_post

برای حذف نوشته، سه تابع در اختیار دارید:

// انتقال به زباله‌دان (قابل بازیابی)
wp_trash_post( $post_id );

// حذف کامل (بدون بازگشت)
wp_delete_post( $post_id, true );

// حذف موقت (بدون انتقال به زباله‌دان، بدون پاک کردن متادیتا)
wp_delete_post( $post_id, false );

نکته مهم: پارامتر دوم wp_delete_post تعیین می‌کند آیا نوشته به‌طور کامل حذف شود (true) یا فقط به زباله‌دان منتقل شود (false). همیشه در پروژه‌های مشتری‌محور، از wp_trash_post استفاده کنید تا اگر مشتری پشیمان شد، قابل بازیابی باشد. راهنمای زباله‌دان در پاک‌سازی دیتابیس وردپرس و افزونه‌های بهینه‌سازی دیتابیس.

حذف نوشته با بررسی دسترسی

حذف نوشته، در هر پروژه‌ای باید با دو لایه محافظت شود:

function myplugin_handle_delete() {
    $post_id = isset( $_GET["post_id"] ) ? absint( $_GET["post_id"] ) : 0;
    
    if ( ! $post_id ) {
        wp_die( "شناسه نامعتبر" );
    }
    
    if ( ! isset( $_GET["_wpnonce"] ) || 
         ! wp_verify_nonce( $_GET["_wpnonce"], "delete_post_" . $post_id ) ) {
        wp_die( "درخواست نامعتبر" );
    }
    
    if ( ! current_user_can( "delete_post", $post_id ) ) {
        wp_die( "دسترسی غیرمجاز" );
    }
    
    wp_trash_post( $post_id );
    
    wp_safe_redirect( admin_url( "edit.php" ) );
    exit;
}
add_action( "admin_post_myplugin_delete", "myplugin_handle_delete" );

سه لایه امنیت: بررسی شناسه، نانس و بررسی دسترسی. نکته مهم: هیچ لینک حذفی نباید بدون نانس باشد، حتی برای ادمین. اگر یک مهاجم بتواند ادمین لاگین‌شده را فریب دهد که روی یک لینک خارجی کلیک کند، بدون نانس، نوشته حذف می‌شود. راهنمای کامل در حمله CSRF و نانس وردپرس.

حذف انبوه نوشته‌ها

در پروژه‌های واقعی، اغلب نیاز به حذف انبوه داریم. مثال: حذف تمام نوشته‌های پیش‌نویس قدیمی:

function myplugin_delete_old_drafts() {
    $args = array(
        "post_type"      => "post",
        "post_status"    => "draft",
        "date_query"     => array(
            array(
                "before" => "6 months ago",
            ),
        ),
        "posts_per_page" => 100,
        "fields"         => "ids",
    );
    
    $query = new WP_Query( $args );
    
    if ( ! empty( $query->posts ) ) {
        foreach ( $query->posts as $post_id ) {
            wp_delete_post( $post_id, true );
        }
    }
}
// این تابع باید در cron روزانه اجرا شود
add_action( "myplugin_daily_cleanup", "myplugin_delete_old_drafts" );

نکته: برای حذف انبوه، همیشه در cron استفاده کنید، نه در هر لود سایت. برای cron واقعی، راهنمای عیب‌یابی cron وردپرس و زمان‌بندی cron در وردپرس را ببینید. راهنمای بهینه‌سازی کوئری‌های انبوه در بهینه‌سازی کوئری‌ها.

هوک‌های مهم در ساخت و حذف

سه هوک حیاتی که در ساخت و حذف نوشته به‌کار می‌آیند:

save_post

پس از ذخیره یا به‌روزرسانی نوشته اجرا می‌شود. مناسب برای ذخیره متادیتا و پاک‌سازی کش:

add_action( "save_post", function( $post_id, $post, $update ) {
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( ! current_user_can( "edit_post", $post_id ) ) {
        return;
    }
    // ذخیره متادیتای اضافی
}, 10, 3 );

before_delete_post

پیش از حذف نوشته اجرا می‌شود. مناسب برای پاک‌سازی متادیتا یا داده مرتبط:

add_action( "before_delete_post", function( $post_id ) {
    // حذف داده مرتبط
} );

trashed_post و untrashed_post

هنگام انتقال به زباله‌دان و بازگشت از آن اجرا می‌شود. مناسب برای لاگ یا اعلان:

add_action( "trashed_post", function( $post_id ) {
    error_log( "نوشته {$post_id} به زباله‌دان منتقل شد" );
} );

راهنمای کامل هوک‌ها در هوک‌های وردپرس، استفاده صحیح از هوک‌ها، و هوک‌های محتوای نوشته.

حذف نوشته در ووکامرس

در ووکامرس، محصولات با wp_delete_post حذف می‌شوند ولی توصیه می‌شود از توابع ووکامرس استفاده کنید:

$product = wc_get_product( $product_id );
if ( $product ) {
    $product->delete( true ); // true = حذف کامل
}

راهنمای کامل در قالب مناسب ووکامرس، مدیریت سفارش‌های ووکامرس، و امنیت فروشگاه ووکامرس.

بازبینی نسخه‌ها (Revisions)

وردپرس به‌طور خودکار از هر نوشته، نسخه‌های قبلی را نگه می‌دارد. این ویژگی مفید است ولی در سایت‌های بزرگ، می‌تواند دیتابیس را سنگین کند:

// محدودسازی تعداد revisionها در wp-config.php
define( "WP_POST_REVISIONS", 5 );

// حذف نسخه‌های قدیمی
wp_delete_post_revision( $revision_id );

راهنمای کامل در تأثیر revisionها بر دیتابیس و پاک‌سازی دیتابیس وردپرس.

ساخت و حذف در مالتی‌سایت

در وردپرس مالتی‌سایت، wp_insert_post و wp_delete_post باید با switch_to_blog و restore_current_blog استفاده شوند:

switch_to_blog( $blog_id );
$post_id = wp_insert_post( array( "post_title" => "نوشته در سایت دیگر" ) );
restore_current_blog();

راهنمای کامل در وردپرس مالتی‌سایت.

اشتباهات رایج

جمع‌بندی

توابع ایجاد و حذف نوشته در وردپرس، چهار گروه اصلی دارند: ساخت (wp_insert_post)، به‌روزرسانی (wp_update_post)، انتقال به زباله‌دان (wp_trash_post)، و حذف کامل (wp_delete_post). سه اصل را در پایان تاکید می‌کنم: اول، همیشه با توابع استاندارد کار کنید، نه SQL خام. دوم، ساخت و حذف را با نانس و بررسی دسترسی محافظت کنید. سوم، هوک‌های save_post و before_delete_post را برای پاک‌سازی داده مرتبط جدی بگیرید.

اگر تجربه‌ای از یک باگ در ساخت یا حذف نوشته دارید که با تابع استاندارد حل شد، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 📝