تابع wp_insert_post() در وردپرس مسئول ایجاد یک نوشته جدید در دیتابیس است و پایه تمام عملیات درج محتوا — از پنل مدیریت تا اسکریپت‌های خودکار و APIهای سفارشی — محسوب می‌شود. این تابع نه‌تنها رکورد جدول wp_posts را درج می‌کند، بلکه تمام پردازش‌های جانبی مثل اجرای hookها، ذخیره متادیتا و ثبت روابط taxonomy را نیز انجام می‌دهد.

تابع wp_insert_post یکی از پرکاربردترین توابع وردپرس برای ایجاد نوشته‌های جدید است. این تابع امکان تعیین عنوان، محتوا، وضعیت، نویسنده، post_type و ده‌ها فیلد دیگر را با رعایت اعتبارسنجی و اجرای هوک‌ها فراهم می‌کند. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، هوک‌های مرتبط، اشتباهات رایج و نکات امنیتی و عملکردی این تابع بررسی می‌شود. همچنین تفاوت آن با wp_update_post و wp_delete_post توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه فنی عمیق به این تابع مرور خواهد شد.

در پروژه‌هایی که نیاز به درج خودکار محتوا، import داده از سیستم خارجی یا ساخت نوشته بر اساس یک رویداد داشتند، این تابع همیشه یک نقش محوری داشته است. آنچه در نگاه اول ساده به‌نظر می‌رسد، در جزئیات خود مسائلی مثل تنظیم post_name، تعامل با hookها، اعتبارسنجی و مدیریت خطا دارد که بی‌توجهی به آن‌ها در سطح production گران تمام می‌شود.

چرا wp_insert_post اهمیت دارد

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

تابع wp_insert_post() در واقع لایه بالایی روی متد $wpdb->insert() است که وظیفه انجام مراحل زیر را دارد:

  • اعتبارسنجی و پاک‌سازی فیلدهای ورودی
  • تولید slug یکتا از عنوان
  • تنظیم تاریخ و ساعت
  • درج رکورد در جدول wp_posts
  • ذخیره روابط taxonomy
  • اجرای hookهای مرتبط

نکته کلیدی این است که برخلاف کوئری مستقیم SQL، این تابع تمام این مراحل را به‌شکل سازگار با اکوسیستم وردپرس انجام می‌دهد. برای درک جایگاه این تابع در چرخه کامل محتوا، مطلب تابع wp_update_post و تابع wp_delete_post را مطالعه کنید.

ساختار و امضای تابع wp_insert_post

امضای این تابع به شکل زیر است:

wp_insert_post( array $postarr, bool $wp_error = false, bool $fire_after_hooks = true ): int|WP_Error

پارامتر اول یک آرایه انجمنی از فیلدهای پست است. پارامتر دوم اگر true باشد، در صورت خطا شیء WP_Error برمی‌گرداند؛ در غیر این صورت مقدار 0 برمی‌گردد. پارامتر سوم از نسخه 5.6 اضافه شده و کنترل می‌کند که hookهای بعد از درج اجرا شوند یا خیر.

خروجی موفق، شناسه پست جدید است. این شناسه را باید برای عملیات بعدی مثل افزودن متادیتا یا اتصال taxonomy ذخیره کنید.

برای درک چرخه وضعیت پس از درج، مطلب تابع wp_transition_post_status را ببینید.

پارامترهای کلیدی و کاربرد هرکدام

آرایه ورودی می‌تواند شامل ده‌ها فیلد باشد. مهم‌ترین آن‌ها را مرور می‌کنیم:

پارامتر post_title

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

$post_id = wp_insert_post( array(
    'post_title'   => 'عنوان پست جدید',
    'post_content' => 'محتوای پست',
    'post_status'  => 'publish',
    'post_type'    => 'post',
) );

پارامتر post_content

محتوای اصلی نوشته. نکته مهم: وردپرس این مقدار را به‌طور کامل sanitize نمی‌کند چون محتوا ذاتاً شامل HTML است. اگر مقدار از ورودی کاربر می‌آید، خودتان مسئول wp_kses_post() هستید:

$content = wp_kses_post( $_POST['content'] );

برای آشنایی با الگوهای کامل sanitize، مطلب راهنمای Sanitization در وردپرس منبع جامعی است.

پارامتر post_status

وضعیت اولیه پست. مقادیر رایج: publish، draft، pending، private. برای درج محتوای برنامه‌نویسی، معمولاً draft انتخاب امن‌تری است چون امکان بازبینی می‌دهد:

wp_insert_post( array(
    'post_title'  => $title,
    'post_status' => 'draft',
) );

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

پارامتر post_type

نوع محتوا. مقدار پیش‌فرض post است اما می‌تواند page، product یا هر post type سفارشی ثبت‌شده باشد:

wp_insert_post( array(
    'post_type'  => 'product',
    'post_title' => 'محصول جدید',
) );

برای آشنایی کامل با ثبت post type سفارشی، مطلب تابع register_post_type را ببینید.

پارامتر post_author

شناسه نویسنده. اگر مقدار معتبر نباشد، وردپرس از کاربر جاری استفاده می‌کند. همیشه با توابعی مثل get_user_by یا تابع get_users اعتبارسنجی کنید.

پارامتر post_name

slug پست. اگر خالی بماند، وردپرس از عنوان یک slug یکتا می‌سازد. برای کنترل دستی:

wp_insert_post( array(
    'post_name' => 'my-custom-slug',
) );

برای درک نحوه یکتاسازی slug، مطلب تابع wp_unique_post_slug را مطالعه کنید.

پارامتر post_date و post_date_gmt

تاریخ انتشار. باید به فرمت MySQL یعنی YYYY-MM-DD HH:MM:SS باشد. اگر خالی بماند، زمان فعلی سرور استفاده می‌شود:

wp_insert_post( array(
    'post_date' => current_time( 'mysql' ),
) );

پارامتر post_category و tags_input

برای اتصال دسته‌بندی و برچسب در همان مرحله درج:

wp_insert_post( array(
    'post_category' => array( 4, 7 ),
    'tags_input'    => 'wordpress, security',
) );

برای taxonomyهای سفارشی، از پارامتر tax_input با ساختار آرایه استفاده کنید. برای الگوهای دقیق‌تر، مطلب تابع wp_set_object_terms را ببینید.

پارامتر meta_input

از نسخه 4.4 وردپرس اضافه شده و اجازه می‌دهد متادیتا را در همان مرحله درج پست ذخیره کنید:

wp_insert_post( array(
    'post_title' => $title,
    'meta_input' => array(
        'price'     => 100000,
        'in_stock'  => 1,
    ),
) );

برای ثبت متادیتا با ساختار نوع‌دار، مطلب تابع register_meta را مطالعه کنید.

پارامتر comment_status و ping_status

کنترل باز یا بسته بودن دیدگاه‌ها و پینگ‌بک:

wp_insert_post( array(
    'comment_status' => 'open',
    'ping_status'    => 'closed',
) );

هوک‌های مرتبط با درج پست

هنگام فراخوانی این تابع، چندین hook به‌ترتیب اجرا می‌شوند:

  • wp_insert_post_empty_content: اگر محتوا خالی باشد
  • pre_post_update: قبل از درج
  • wp_insert_post_data: قبل از ذخیره برای تغییر داده‌ها
  • save_post: بعد از ذخیره موفق
  • save_post_{post_type}: نسخه مخصوص هر post type
  • wp_insert_post: بعد از درج (نام hook با نام تابع یکسان است)

یکی از کاربردهای رایج، ذخیره متادیتای سفارشی هنگام درج پست است. مطلب هوک save_post توضیحات دقیقی در این زمینه دارد.

نکته مهم: اگر درون hook save_post دوباره wp_insert_post فراخوانی کنید، ممکن است حلقه بی‌پایان ایجاد شود. برای جلوگیری، از یک flag موقت یا تابع remove_action استفاده کنید. مطلب تابع remove_filter نیز در این زمینه کاربردی است.

نمونه‌های عملی در پروژه واقعی

درج خودکار پست از یک منبع خارجی

$response = wp_remote_get( 'https://api.example.com/articles' );
$items = json_decode( wp_remote_retrieve_body( $response ), true );
foreach ( $items as $item ) {
    $post_id = wp_insert_post( array(
        'post_title'   => sanitize_text_field( $item['title'] ),
        'post_content' => wp_kses_post( $item['body'] ),
        'post_status'  => 'draft',
        'post_type'    => 'post',
        'meta_input'   => array( 'source_url' => esc_url_raw( $item['url'] ) ),
    ), true );
    if ( is_wp_error( $post_id ) ) {
        error_log( $post_id->get_error_message() );
    }
}

نکته مهم در این الگو: استفاده از true به‌عنوان پارامتر دوم برای دسترسی به خطاها به‌صورت ساختاریافته.

درج محصول ووکامرس

در ووکامرس، پس از درج پست با post_type => 'product'، باید متادیتای خاص محصول مثل قیمت و موجودی نیز ذخیره شود. مطلب تابع update_post_meta الگوی این کار را نشان می‌دهد.

درج پست بر اساس فرم فرانت‌اند

if ( ! isset( $_POST['my_nonce'] ) || ! wp_verify_nonce( $_POST['my_nonce'], 'submit_post' ) ) {
    wp_die( 'درخواست نامعتبر' );
}
if ( ! current_user_can( 'publish_posts' ) ) {
    wp_die( 'دسترسی غیرمجاز' );
}
$post_id = wp_insert_post( array(
    'post_title'   => sanitize_text_field( $_POST['title'] ),
    'post_content' => wp_kses_post( $_POST['content'] ),
    'post_status'  => 'pending',
), true );

استفاده از Nonce در وردپرس و بررسی capability دو اصل ضروری در این الگو هستند.

درج پست و سپس به‌روزرسانی آن

در برخی سناریوها پس از درج، نیاز به به‌روزرسانی فوری دارید. از تابع wp_update_post استفاده کنید. برای مدیریت متادیتا در ادامه، تابع get_post_meta و تابع delete_post_meta ابزارهای اصلی هستند.

درج انبوه با WP-CLI

برای درج هزاران پست، اجرا از طریق وب به timeout منجر می‌شود. مطلب راهنمای WP-CLI الگوهای این کار را پوشش می‌دهد.

ترکیب با wpdb برای کوئری‌های کمکی

برای خواندن داده پس از درج، استفاده از متد wpdb::get_results راهکار متداول است. اگر به کوئری دستی نیاز دارید، ابتدا با متد wpdb::prepare مقادیر را امن کنید.

اشتباهات رایج در استفاده از wp_insert_post

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

نبود sanitize روی ورودی کاربر

اگر عنوان یا محتوا را بدون sanitize_text_field() و wp_kses_post() ذخیره کنید، یک XSS جدی ایجاد می‌شود. این اشتباه در پروژه‌های فرانت‌اند بسیار رایج است.

نبود nonce در فرم‌های فرانت‌اند

هر فرمی که در فرانت‌اند پست ایجاد می‌کند، باید nonce داشته باشد. بدون آن، مهاجم می‌تواند با یک درخواست جعلی، پست ناخواسته درج کند.

نبود capability check

پیش از فراخوانی، باید با current_user_can( 'publish_posts' ) یا capability متناسب با post_type بررسی شود. برای مطالعه کامل نقش‌ها، مطلب Capability و نقش‌های کاربری سفارشی را ببینید.

نبود بررسی خروجی

اگر درج شکست بخورد، تابع مقدار 0 یا WP_Error برمی‌گرداند. اگر این خروجی بررسی نشود، ممکن است پیام موفقیت نادرست به کاربر نشان داده شود.

درج مستقیم به‌صورت publish

درج پست با post_status => 'publish' بدون بازبینی، خطر انتشار محتوای ناخواسته را افزایش می‌دهد. توصیه استاندارد، درج به‌صورت draft و سپس بازبینی است.

نبود مدیریت خطا در حلقه‌های انبوه

در درج انبوه هزاران پست، اگر یکی شکست بخورد، حلقه متوقف نمی‌شود اما خطا نادیده گرفته می‌شود. باید هر خطا را لاگ کنید و در صورت نیاز ادامه دهید. مطلب راهنمای WP-CLI الگوهای مدیریت خطا را نشان می‌دهد.

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

تست‌هایی مثل «عنوان خالی»، «محتوای خالی»، «post_type نامعتبر»، «کاربر بدون دسترسی» و «کاربر غیرمجاز» را حتماً بنویسید.

امنیت و عملکرد در wp_insert_post

این تابع به‌طور داخلی از prepared statement استفاده می‌کند و در برابر SQL Injection مقاوم است. اما نکات زیر باید رعایت شوند:

  • عنوان و متادیتا را با توابع sanitize پاک کنید
  • محتوا را با wp_kses_post() محدود کنید
  • سطح دسترسی کاربر را با capability بررسی کنید
  • nonce را در فرم‌های فرانت‌اند و AJAX قرار دهید
  • در درج انبوه، محدودیت تعداد را رعایت کنید

برای مطالعه جامع مباحث امنیتی، مطلب SQL Injection Prevention در وردپرس مرجع اصلی است.

از نظر عملکرد، هر فراخوانی این تابع باعث چندین کوئری می‌شود:

  1. درج رکورد در wp_posts
  2. درج متادیتا در wp_postmeta (در صورت وجود meta_input)
  3. درج روابط taxonomy در wp_term_relationships
  4. به‌روزرسانی شمارش termها
  5. اجرای hookهای پس از درج

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

  • استفاده از wp_insert_post با پارامتر سوم false برای غیرفعال کردن برخی hookها در سناریوهای خاص
  • اجرای درج در بازه‌های زمانی کم‌ترافیک
  • استفاده از WP-CLI برای درج انبوه

برای مطالعه الگوهای بهینه‌سازی، مطلب بهینه‌سازی کوئری‌های وردپرس با کدنویسی توصیه می‌شود.

پرسش‌های پرتکرار درباره wp_insert_post

تفاوت wp_insert_post با wp_update_post چیست؟

wp_insert_post() برای ایجاد پست جدید استفاده می‌شود و شناسه جدید برمی‌گرداند، درحالی‌که wp_update_post() یک پست موجود را ویرایش می‌کند و به کلید ID نیاز دارد.

آیا wp_insert_post باعث ثبت revision می‌شود؟

در حالت درج اولیه، revision ثبت نمی‌شود چون هنوز نسخه قبلی وجود ندارد. revision‌ها فقط در به‌روزرسانی‌های بعدی ایجاد می‌شوند. برای مدیریت این رفتار مطلب مدیریت Post Revisions را ببینید.

آیا می‌توان با این تابع متادیتا هم ذخیره کرد؟

بله، از پارامتر meta_input که از نسخه 4.4 اضافه شده است. اما برای ساختارهای پیچیده‌تر، معمولاً پس از درج، از تابع update_post_meta استفاده می‌شود.

آیا اگر post_name را خالی بگذاریم، وردپرس خودش slug می‌سازد؟

بله، وردپرس از عنوان پست یک slug یکتا می‌سازد. اگر عنوان فارسی باشد، ممکن است slug شامل کاراکترهای غیرلاتین شود. برای کنترل دقیق، مطلب تابع wp_unique_post_slug را ببینید.

آیا wp_insert_post روی Multisite کار می‌کند؟

بله، اما فقط روی سایت جاری. برای درج در سایت‌های دیگر، باید با switch_to_blog جابه‌جا شوید و پس از پایان با restore_current_blog بازگردید. برای مطالعه مدیریت Multisite، مطلب مدیریت Multisite وردپرس مفید است.

آیا می‌توان پست را بدون slug ذخیره کرد؟

خیر، وردپرس همیشه یک slug می‌سازد. اگر post_name را عدد بگذارید، همان ذخیره می‌شود. برای حذف کامل slug، باید ساختار permalink تغییر کند که توصیه نمی‌شود.

چرا wp_insert_post مقدار 0 برمی‌گرداند؟

معمولاً به سه دلیل: محتوای خالی، post_type نامعتبر یا خطای دیتابیس. اگر پارامتر دوم را true بگذارید، به‌جای 0 یک شیء WP_Error با جزئیات دریافت می‌کنید.

نگاه فنی عمیق به wp_insert_post

در سطح معماری، wp_insert_post() یک عملیات پیچیده چندمرحله‌ای است. این تابع در ابتدا یک سری اعتبارسنجی انجام می‌دهد، سپس داده‌ها را فیلتر می‌کند، در ادامه خود درج را انجام می‌دهد و در نهایت hookهای پس از درج را اجرا می‌کند. کل این زنجیره در یک scope واحد اجرا می‌شود که در صورت خطا در هر مرحله، بقیه مراحل متوقف می‌شوند.

نکته ظریف اول، رفتار تابع در مورد پارامتر post_date است. اگر این مقدار خالی بماند، وردپرس از current_time('mysql') استفاده می‌کند که بر اساس تنظیمات timezone سایت محاسبه می‌شود. اما مقدار post_date_gmt در همان مرحله محاسبه و ذخیره می‌شود. اگر این محاسبه اشتباه انجام شود، می‌تواند به نمایش نادرست تاریخ در برخی سرویس‌های خارجی منجر شود.

مسئله دوم، ترتیب اجرای hookهاست. hook wp_insert_post_data پیش از درج اجرا می‌شود و می‌تواند مقادیر را تغییر دهد. اگر توسعه‌دهنده‌ای درون این hook یک wp_insert_post دیگر فراخوانی کند، می‌تواند به یک حلقه ناپایدار منجر شود. یکی از روش‌های استاندارد جلوگیری، استفاده از doing_action() برای بررسی زمینه اجراست.

مسئله سوم، رفتار meta_input در نسخه‌های مختلف وردپرس است. این قابلیت از 4.4 اضافه شده، اما در 4.4 و 4.5 رفتار متفاوتی در اعتبارسنجی داشت. اگر پروژه‌ای در چند نسخه وردپرس اجرا می‌شود، بهتر است به‌جای اتکا به meta_input، پس از درج از update_post_meta استفاده کنید.

در نهایت، در پروژه‌های Enterprise توصیه می‌کنم یک Domain Service بنویسید که عملیات درج پست و تمام side effectهای آن (متادیتا، taxonomy، اعلان‌ها) را در یک معماری متمرکز اجرا کند. این کار از پخش شدن منطق در hookهای مختلف جلوگیری می‌کند و تست‌پذیری را به‌شدت افزایش می‌دهد. برای مطالعه بیشتر در مورد الگوهای ساختاری، مباحث توابع وردپرس برای کوئری سفارشی و بهینه‌سازی پیشرفته دیتابیس مفید هستند.

اگر در پروژه‌ای با مشکل درج ناقص یا رفتار غیرمنتظره در hookها مواجه شده‌اید، برای ما جالب است بدانید کدام راهکار عملاً به حل مسئله کمک کرده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد. برای مطالعه بیشتر در مورد لایه دیتابیس، زبان SQL در ویکی‌پدیا نقطه شروع خوبی است.