تابع wp_update_post() در وردپرس مسئول به‌روزرسانی یک نوشته موجود در دیتابیس است و پایه‌ی هر عملیات ویرایشی روی محتوا — از پنل مدیریت تا اسکریپت‌های خودکار و APIهای سفارشی — محسوب می‌شود. این تابع در واقع لایه‌ای امن و استاندارد برای به‌روزرسانی رکورد جدول wp_posts فراهم می‌کند.

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

در پروژه‌های واقعی که به به‌روزرسانی خودکار قیمت، تغییر وضعیت انبوه یا سینک محتوا با سیستم خارجی نیاز داشتند، این تابع بارها در کانون کد قرار گرفته است. آنچه در نگاه اول یک فراخوانی ساده به‌نظر می‌رسد، در جزئیات خود مسائل مهمی مثل save_post، revision، sanitize و nonce دارد که بی‌توجهی به آن‌ها در سطح production گران تمام می‌شود.

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

وردپرس به‌عنوان یک CMS (Content Management System) برای مدیریت چرخه عمر محتوا طراحی شده است. بخش اعظم این چرخه به ویرایش و به‌روزرسانی نوشته‌ها اختصاص دارد. اگر توسعه‌دهنده‌ای بدون درک عمیق این تابع شروع به کار کند، به سرعت با مشکلاتی مثل از دست رفتن revision، اجرای ناخواسته هوک‌ها، یا نشت داده‌های حساس مواجه می‌شود.

تابع wp_update_post() در حقیقت یک abstraction روی متد $wpdb->update() است که در کنار آن، تمام پردازش‌های جانبی وردپرس مثل sanitize کردن فیلدها، اجرای hookهای save_post و wp_insert_post، و ثبت revision را نیز انجام می‌دهد. به همین دلیل استفاده از این تابع همیشه بر نوشتن کوئری دستی روی جدول wp_posts ترجیح دارد.

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

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

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

wp_update_post( array|object $postarr = array(), bool $wp_error = false ): int|WP_Error

پارامتر اول یک آرایه یا شیء پست است که شامل فیلدهای موردنظر برای به‌روزرسانی است. در این آرایه، کلید ID الزامی است و اگر حذف شود، وردپرس خطا برمی‌گرداند. پارامتر دوم اگر true باشد، در صورت بروز خطا یک شیء WP_Error برمی‌گرداند؛ در غیر این صورت مقدار 0 برگردانده می‌شود که ردیابی خطا را سخت‌تر می‌کند.

خروجی موفق، شناسه پست به‌روزرسانی‌شده است. این شناسه با شناسه ورودی یکسان است، اما در برخی سناریوهای خاص — مثلاً وقتی revision جدید ایجاد می‌شود — می‌تواند مقدار متفاوتی داشته باشد.

برای درک عمیق‌تر چرخه حیات یک پست، مطالعه مطلب تابع wp_transition_post_status توصیه می‌شود، چون در کنار این تابع نقش کلیدی در تغییر وضعیت دارد.

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

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

پارامتر ID

الزامی‌ترین فیلد این آرایه. اگر مقدار ID صفر باشد یا پست متناظر وجود نداشته باشد، تابع مقدار 0 برمی‌گرداند و هیچ تغییری در دیتابیس اعمال نمی‌شود:

$result = wp_update_post( array(
    'ID'           => 123,
    'post_title'   => 'عنوان جدید',
), true );
if ( is_wp_error( $result ) ) {
    error_log( $result->get_error_message() );
}

استفاده از پارامتر دوم true در محیط‌های production بسیار توصیه می‌شود چون به شما اجازه می‌دهد خطاها را به‌صورت ساختاریافته لاگ کنید.

پارامتر post_title و post_content

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

$title = sanitize_text_field( $_POST['post_title'] );
$content = wp_kses_post( $_POST['post_content'] );
wp_update_post( array(
    'ID'           => $post_id,
    'post_title'   => $title,
    'post_content' => $content,
) );

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

پارامتر post_status

وضعیت پست شامل مقادیری مثل publish، draft، pending، private و trash است. تغییر این مقدار باعث اجرای hookهای تغییر وضعیت می‌شود و اگر پست به publish منتقل شود، hookهای مربوط به انتشار نیز اجرا می‌شوند:

wp_update_post( array(
    'ID'          => $post_id,
    'post_status' => 'publish',
) );

برای درک کامل این فرآیند، مطلب تابع wp_publish_post را ببینید.

پارامتر post_author

شناسه کاربری نویسنده. اگر این مقدار را به شناسه کاربری نامعتبر تغییر دهید، وردپرس ممکن است آن را نادیده بگیرد. همیشه با get_user_by() یا get_users() صحت شناسه را بررسی کنید. برای الگوهای دریافت کاربران مطلب تابع get_users در وردپرس کاربردی است.

پارامتر post_date و post_modified

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

wp_update_post( array(
    'ID'          => $post_id,
    'post_date'   => current_time( 'mysql' ),
) );

پارامتر post_name (slug)

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

پارامترهای مربوط به دسته‌بندی و برچسب

پارامترهای post_category و tags_input به‌ترتیب آرایه‌ای از شناسه دسته‌ها و رشته‌ای از برچسب‌های جداشده با کاما هستند. این پارامترها هنگام به‌روزرسانی، روابط taxonomies را بازنویسی می‌کنند:

wp_update_post( array(
    'ID'            => $post_id,
    'post_category' => array( 4, 7 ),
    'tags_input'    => 'wordpress, security',
) );

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

هوک‌های مرتبط با به‌روزرسانی پست

یکی از مهم‌ترین دلایلی که باید همیشه از wp_update_post() به جای کوئری مستقیم استفاده کنید، اجرای hookهاست. مهم‌ترین hookهایی که در طول این فرآیند فعال می‌شوند:

  • pre_post_update: بلافاصله قبل از اجرای به‌روزرسانی
  • wp_insert_post_data: قبل از ذخیره، برای تغییر داده‌ها
  • save_post: بعد از ذخیره موفق
  • save_post_{post_type}: نسخه مخصوص هر post type
  • post_updated: بعد از به‌روزرسانی موفق
  • transition_post_status: در تغییر وضعیت

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

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

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

در ادامه چند الگو که در پروژه‌های واقعی بارها به کار رفته است را مرور می‌کنیم:

به‌روزرسانی انبوه وضعیت نوشته‌ها

$post_ids = get_posts( array(
    'post_type'   => 'post',
    'post_status' => 'draft',
    'numberposts' => -1,
    'fields'      => 'ids',
) );
foreach ( $post_ids as $id ) {
    wp_update_post( array(
        'ID'          => $id,
        'post_status' => 'publish',
    ) );
}

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

به‌روزرسانی خودکار قیمت محصول

در فروشگاه‌های ووکامرس، قیمت محصول در متادیتا نگهداری می‌شود و باید با update_post_meta() تغییر کند، نه wp_update_post. مطلب تابع update_post_meta جزئیات را توضیح می‌دهد.

به‌روزرسانی از یک سرویس خارجی

$remote = wp_remote_get( 'https://api.example.com/posts/123' );
$body = json_decode( wp_remote_retrieve_body( $remote ), true );
if ( ! empty( $body['id'] ) ) {
    wp_update_post( array(
        'ID'           => (int) $body['id'],
        'post_title'   => sanitize_text_field( $body['title'] ),
        'post_content' => wp_kses_post( $body['body'] ),
    ), true );
}

حذف پست پس از به‌روزرسانی ناموفق

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

ترکیب با wpdb::update برای کوئری‌های مستقیم

در برخی موارد که به به‌روزرسانی جدول‌های سفارشی نیاز دارید، از متد wpdb::update استفاده کنید. برای حفظ امنیت، پیش از آن با متد wpdb::prepare مقادیر را امن کنید.

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

در بازبینی کدبیس افزونه‌های متعدد، این اشتباهات تکراری دیده شده است:

نبود بررسی ID

اگر ID وجود نداشته باشد، تابع مقدار 0 برمی‌گرداند و این خطا در بسیاری از پروژه‌ها نادیده گرفته می‌شود. همیشه نتیجه را با is_wp_error() یا مقایسه با صفر بررسی کنید.

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

فیلد post_content به‌طور پیش‌فرض اجازه HTML دارد. اگر بدون wp_kses_post() ذخیره شود، می‌تواند یک XSS جدی ایجاد کند. همیشه sanitize سطح ورودی انجام دهید.

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

هر فرمی که در فرانت‌اند یا پنل سفارشی به‌روزرسانی انجام می‌دهد، باید nonce داشته باشد. مطلب Nonce در وردپرس توضیحات کامل را دارد.

نبود capability check

پیش از فراخوانی، باید با current_user_can( 'edit_post', $post_id ) سطح دسترسی بررسی شود. بدون این کنترل، هر کاربری می‌تواند محتوای دلخواه را تغییر دهد.

ایجاد حلقه در hookهای save_post

اگر درون save_post دوباره wp_update_post صدا زده شود، حلقه بی‌پایان رخ می‌دهد. با اجرای remove_action() قبل از فراخوانی یا استفاده از flag، از این مشکل جلوگیری کنید.

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

تست‌های «پست ناموجود»، «کاربر بدون دسترسی»، «ورودی خالی» و «post_type نامعتبر» را حتماً بنویسید. این سناریوها در محیط production به‌سرعت خودشان را نشان می‌دهند.

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

این تابع به‌صورت داخلی از $wpdb->update استفاده می‌کند که خودش روی prepared statement ساخته شده است. بنابراین در برابر SQL Injection تا حد زیادی مقاوم است. اما نکات زیر باید رعایت شوند:

  • مقادیر آرایه باید قبل از ارسال sanitize شوند
  • سطح دسترسی کاربر باید با capability بررسی شود
  • nonce باید در فرم‌های سفارشی استفاده شود
  • پیش از به‌روزرسانی فیلدهای slug، ریدایرکت‌های لازم تنظیم شوند

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

از نظر عملکرد، هر فراخوانی این تابع باعث اجرای چندین کوئری می‌شود: یک کوئری UPDATE روی wp_posts، به‌روزرسانی متادیتا، به‌روزرسانی taxonomy relationships و ثبت revision. در سایت‌های پربازدید، به‌روزرسانی انبوه هزاران پست با این تابع می‌تواند ساعت‌ها طول بکشد. راهکار استاندارد، اجرای این عملیات در محیط staging یا از طریق WP-CLI است.

همچنین اگر می‌خواهید پس از به‌روزرسانی، داده‌ها را بخوانید، استفاده از متد wpdb::get_results راهکار متداول است. و اگر نیاز به درج پست جدید دارید، مطلب wp_insert_post را ببینید.

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

تابع wp_update_post چه تفاوتی با wp_insert_post دارد؟

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

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

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

آیا می‌توان با این تابع نویسنده پست را تغییر داد؟

بله، با تنظیم post_author روی شناسه کاربر جدید. اما توجه کنید که تغییر نویسنده ممکن است روی دسترسی‌های بعدی اثر بگذارد.

آیا برای به‌روزرسانی متادیتا هم باید از این تابع استفاده کرد؟

خیر. برای متادیتا از تابع update_post_meta استفاده کنید. کوئری جداگانه و سبک‌تری است.

آیا اگر post_type را در آرایه تغییر دهم، نوع پست عوض می‌شود؟

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

آیا wp_update_post روی پست‌های سفارشی هم کار می‌کند؟

بله، این تابع برای هر post type ثبت‌شده کار می‌کند. برای جزئیات ثبت post type سفارشی، مطلب تابع register_post_type را مطالعه کنید.

نگاه مهندسی سطح بالا

در سطح معماری، wp_update_post() را باید یک نقطه ورود حساس به دیتابیس در نظر گرفت. این تابع درون خود از wp_insert_post() استفاده می‌کند و همین موضوع باعث می‌شود بخش عمده‌ای از منطق sanitize، تولید slug یکتا و اجرای hookها در آن تکرار شود.

نکته ظریفی که در کدبیس وردپرس وجود دارد این است که اگر مقدار ID در آرایه ارسال شود، وردپرس مسیر «به‌روزرسانی» را طی می‌کند؛ اما اگر شناسه نامعتبر باشد، مقادیر post_status ممکن است به draft تغییر داده شوند. این behavior به‌صورت مستند نیست و در پروژه‌های بزرگ می‌تواند باعث پنهان شدن باگ‌ها شود.

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

در نهایت، در پروژه‌های Enterprise توصیه می‌کنم به‌جای اتکای کامل به این تابع، یک لایه Domain Service بنویسید که عملیات به‌روزرسانی را در یک transaction منطقی اجرا کند. در این حالت اگر یک فیلد به‌روزرسانی نشد، می‌توانید بقیه تغییرات را rollback کنید. برای آشنایی با الگوهای ساختاری در پروژه‌های بزرگ، مطلب کلاس WP_Query و توابع وردپرس برای داده پست را مطالعه کنید.

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