تابع register_activation_hook() در وردپرس نقطه ورود رسمی برای اجرای کد در زمان فعال‌سازی یک افزونه است و به‌عنوان یکی از پایه‌ای‌ترین توابع چرخه عمر افزونه، امکان ساخت جداول سفارشی، تنظیم گزینه‌های اولیه و پاک‌سازی کش را فراهم می‌کند. بدون این تابع، افزونه در اولین بار فعال‌سازی در وضعیت نیمه‌آماده باقی می‌ماند.

تابع register_activation_hook وردپرس یکی از پرکاربردترین توابع چرخه عمر افزونه برای اجرای کد در زمان فعال‌سازی است. این تابع امکان ساخت جدول سفارشی، ثبت گزینه‌های پیش‌فرض، پاک‌سازی rewrite rules و تنظیم نقش‌های کاربری را فراهم می‌کند و پایه راه‌اندازی حرفه‌ای افزونه محسوب می‌شود. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، اشتباهات رایج و نکات امنیتی این تابع بررسی می‌شود. همچنین تفاوت آن با register_deactivation_hook توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه فنی عمیق به این تابع مرور خواهد شد.

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

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

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

در این مرحله، افزونه باید تمام اقدامات لازم برای شروع به کار را انجام دهد:

  • ساخت جداول سفارشی در دیتابیس
  • ثبت گزینه‌های پیش‌فرض
  • تنظیم نقش‌ها و دسترسی‌های سفارشی
  • پاک‌سازی rewrite rules برای post typeهای سفارشی
  • تنظیم cron jobهای دوره‌ای
  • بررسی سازگاری با نسخه PHP و وردپرس

برای درک کامل چرخه عمر، مطالب تابع register_deactivation_hook و تابع register_post_type را مطالعه کنید.

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

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

register_activation_hook( string $file, callable $callback ): void

پارامتر اول مسیر فایل اصلی افزونه است که معمولاً با __FILE__ پاس داده می‌شود. پارامتر دوم یک callback است که در زمان فعال‌سازی اجرا می‌شود. خروجی این تابع void است.

مثال عملی:

register_activation_hook( __FILE__, 'myplugin_activate' );

function myplugin_activate() {
    // کد اجرا شده در زمان فعال‌سازی
}

پارامترها و کاربرد __FILE__

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

// درست
register_activation_hook( __FILE__, 'myplugin_activate' );

// اشتباه، مسیر دستی
register_activation_hook( '/plugins/my-plugin/my-plugin.php', 'myplugin_activate' );

مقدار __FILE__ در هر فایل، مسیر کامل آن فایل است. به همین دلیل اگر register_activation_hook را در فایل غیراصلی فراخوانی کنید، hook اجرا نمی‌شود چون وردپرس انتظار دارد مسیر، همان فایل اصلی افزونه باشد.

کجا باید فراخوانی شود؟

توصیه استاندارد این است که register_activation_hook در فایل اصلی افزونه فراخوانی شود، نه در فایل‌های داخلی. این الگو از خطاهای مسیر جلوگیری می‌کند.

کاربردهای رایج در پروژه واقعی

ساخت جدول سفارشی

رایج‌ترین کاربرد این تابع، ساخت جدول سفارشی است:

function myplugin_activate() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'myplugin_logs';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        user_id bigint(20) unsigned NOT NULL,
        action varchar(255) NOT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        KEY user_id (user_id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
}

register_activation_hook( __FILE__, 'myplugin_activate' );

استفاده از dbDelta به‌جای کوئری مستقیم، مدیریت تغییرات ساختار را ساده‌تر می‌کند. برای مطالعه الگوهای امنیتی، مطلب SQL Injection Prevention در وردپرس را ببینید.

ثبت گزینه‌های پیش‌فرض

function myplugin_activate() {
    if ( false === get_option( 'myplugin_settings' ) ) {
        add_option( 'myplugin_settings', array(
            'enabled' => true,
            'mode'    => 'auto',
        ) );
    }
}

register_activation_hook( __FILE__, 'myplugin_activate' );

نکته مهم: قبل از add_option، با get_option بررسی کنید که گزینه از قبل وجود نداشته باشد. این کار از بازنویسی تنظیمات کاربر جلوگیری می‌کند.

پاک‌سازی rewrite rules

اگر افزونه post type سفارشی یا rewrite rule دارد، باید در زمان فعال‌سازی قوانین rewrite را پاک‌سازی کند:

function myplugin_activate() {
    myplugin_register_post_types();
    flush_rewrite_rules();
}

نکته مهم: flush_rewrite_rules یک عملیات سنگین است و نباید در هر بار بارگذاری اجرا شود. فقط در فعال‌سازی یا غیرفعال‌سازی. برای مطالعه بیشتر، مطلب تابع flush_rewrite_rules را ببینید.

ساخت نقش کاربری سفارشی

function myplugin_activate() {
    add_role( 'myplugin_manager', __( 'مدیر افزونه', 'my-plugin' ), array(
        'read'                   => true,
        'edit_posts'             => true,
        'myplugin_manage_settings' => true,
    ) );
}

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

زمان‌بندی cron job دوره‌ای

function myplugin_activate() {
    if ( ! wp_next_scheduled( 'myplugin_daily_cleanup' ) ) {
        wp_schedule_event( time(), 'daily', 'myplugin_daily_cleanup' );
    }
}
register_activation_hook( __FILE__, 'myplugin_activate' );

برای مطالعه دقیق cron، مطلب تابع wp_schedule_event راهنماست.

بررسی سازگاری نسخه

function myplugin_activate() {
    if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
        deactivate_plugins( plugin_basename( __FILE__ ) );
        wp_die( esc_html__( 'افزونه به PHP 7.4 یا بالاتر نیاز دارد.', 'my-plugin' ) );
    }
}
register_activation_hook( __FILE__, 'myplugin_activate' );

این الگو از فعال شدن افزونه روی نسخه ناسازگار PHP جلوگیری می‌کند. مطلب تابع plugin_basename راهنماست.

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

افزونه کامل با فعال‌سازی و ساخت جدول

// my-plugin.php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'MYPLUGIN_VERSION', '1.0.0' );

define( 'MYPLUGIN_DIR', plugin_dir_path( __FILE__ ) );

define( 'MYPLUGIN_URL', plugin_dir_url( __FILE__ ) );

require_once MYPLUGIN_DIR . 'includes/class-installer.php';

register_activation_hook( __FILE__, array( 'MyPlugin_Installer', 'activate' ) );
register_deactivation_hook( __FILE__, array( 'MyPlugin_Installer', 'deactivate' ) );

برای مطالعه الگوی کامل ساختاردهی، مطالب تابع plugin_dir_path، تابع plugin_dir_url و تابع register_deactivation_hook را ببینید.

کلاس Installer با متد activate

class MyPlugin_Installer {

    public static function activate() {
        self::check_requirements();
        self::create_tables();
        self::set_default_options();
        self::register_roles();
        flush_rewrite_rules();
    }

    private static function create_tables() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'myplugin_data';
        $charset_collate = $wpdb->get_charset_collate();
        $sql = "CREATE TABLE $table_name (...) $charset_collate;";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

    private static function set_default_options() {
        add_option( 'myplugin_version', MYPLUGIN_VERSION );
    }

    private static function register_roles() {
        add_role( 'myplugin_manager', __( 'مدیر افزونه', 'my-plugin' ), array( 'read' => true ) );
    }

    private static function check_requirements() {
        if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
            deactivate_plugins( plugin_basename( __FILE__ ) );
            wp_die( esc_html__( 'نسخه PHP نامعتبر', 'my-plugin' ) );
        }
    }
}

ارتقاء نسخه با فعال‌سازی مجدد

public static function activate() {
    $current_version = get_option( 'myplugin_version', '0' );
    if ( version_compare( $current_version, MYPLUGIN_VERSION, '<' ) ) {
        self::run_migrations( $current_version );
        update_option( 'myplugin_version', MYPLUGIN_VERSION );
    }
}

بررسی فعال‌سازی از طریق WP-CLI

در محیط production، می‌توان از WP-CLI برای فعال‌سازی و اجرای hook استفاده کرد:

wp plugin activate my-plugin

مطلب راهنمای WP-CLI الگوهای این کار را پوشش می‌دهد.

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

نبود flush rewrite rules

اگر افزونه post type سفارشی یا rewrite rule دارد، بدون flush در زمان فعال‌سازی، لینک‌های این post type کار نمی‌کنند و به 404 منجر می‌شوند. همیشه در پایان activate فراخوانی flush_rewrite_rules را قرار دهید.

اجرای flush در هر بار بارگذاری

برعکس اشتباه قبلی، اجرای flush_rewrite_rules در hook init یا admin_init باعث افت شدید کارایی می‌شود چون در هر بازدید یک کوئری سنگین اجرا می‌شود. فقط در زمان فعال‌سازی.

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

اگر ساخت جدول بدون بررسی انجام شود و جدول از قبل وجود داشته باشد، خطای MySQL رخ می‌دهد. راهکار درست استفاده از dbDelta است که خودش بررسی می‌کند. یا قبل از اجرا، با SHOW TABLES LIKE بررسی کنید.

نبود بررسی نسخه PHP و وردپرس

اگر افزونه روی نسخه ناسازگار PHP فعال شود، خطای Fatal رخ می‌دهد. همیشه در ابتدای activate، سازگاری را بررسی کنید و در صورت ناسازگاری، افزونه را غیرفعال کنید.

نبود خروجی از تابع

در متد activate، خروجی HTML یا echo اضافه نکنید چون در فرآیند فعال‌سازی، خروجی‌های اضافه به خطای headers already sent منجر می‌شوند. مطلب رفع خطای Cannot modify header information را ببینید.

نبود بررسی در کلاس‌های استاتیک

اگر callback شما یک متد استاتیک است، همیشه با class_exists بررسی کنید که کلاس وجود دارد قبل از ثبت hook:

if ( class_exists( 'MyPlugin_Installer' ) ) {
    register_activation_hook( __FILE__, array( 'MyPlugin_Installer', 'activate' ) );
}

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

تست‌هایی مثل «فعال‌سازی مجدد افزونه»، «ارتقاء از نسخه قدیمی»، «محیط با PHP قدیمی» و «جدول از قبل موجود» را حتماً بنویسید.

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

این تابع به‌تنهایی امنیت را تهدید نمی‌کند، اما callback آن در محیط فعال‌سازی اجرا می‌شود که حساس است. نکات مهم:

  • تمام عملیات دیتابیس را با $wpdb->prepare یا dbDelta انجام دهید
  • خروجی HTML اضافه نکنید
  • داده حساس را در لاگ ثبت نکنید
  • در ساخت جدول، از نام‌گذاری پیشوندی استفاده کنید
  • عملیات طولانی را به‌صورت async انجام دهید

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

از نظر عملکرد، این تابع فقط یک بار در زمان فعال‌سازی اجرا می‌شود و هزینه‌ای در بارگذاری روزانه سایت ندارد. اما عملیات داخل آن می‌تواند سنگین باشد:

  1. ساخت جدول بزرگ در دیتابیس می‌تواند ثانیه‌ها طول بکشد
  2. flush rewrite rules یک کوئری سنگین است
  3. درج داده‌های پیش‌فرض می‌تواند کند باشد

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

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

تفاوت register_activation_hook با register_deactivation_hook چیست؟

register_activation_hook() کد را در زمان فعال‌سازی افزونه اجرا می‌کند و برای راه‌اندازی استفاده می‌شود، در حالی که register_deactivation_hook() در زمان غیرفعال‌سازی برای پاک‌سازی اجرا می‌شود.

آیا این تابع در هر بار بارگذاری سایت اجرا می‌شود؟

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

آیا می‌توان چند callback ثبت کرد؟

خیر، هر افزونه فقط یک activation hook دارد. اگر چند تابع نیاز دارید، آن‌ها را در یک callback مرکزی فراخوانی کنید.

آیا این تابع در Multisite رفتار خاصی دارد؟

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

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

خیر، در فرآیند فعال‌سازی نمی‌توانید خروجی HTML اضافه کنید. برای نمایش پیام، از wp_die یا Transient استفاده کنید و در بارگذاری بعدی پیشخوان، پیام را نمایش دهید.

آیا در فعال‌سازی می‌توان به API خارجی متصل شد؟

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

آیا در فعال‌سازی می‌توان جدول‌های موجود را تغییر داد؟

بله، dbDelta به‌طور خودکار ساختار جداول را با کوئری شما همگام می‌کند. اما برای تغییرات پیچیده‌تر مثل تغییر نوع ستون، باید با احتیاط اقدام کنید.

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

در سطح معماری، register_activation_hook() یک رکورد در آرایه سراسری $wp_filter['activate_' . $file] ثبت می‌کند که در زمان فعال‌سازی با do_action اجرا می‌شود. وردپرس از hook اختصاصی به نام activate_{plugin_basename} استفاده می‌کند.

نکته ظریف اول، مسئله priority است. اگر چند افزونه فعال باشند و بخواهید مطمئن شوید افزونه شما اول اجرا می‌شود، می‌توانید از priority استفاده کنید. اما به‌طور پیش‌فرض، priority پیش‌فرض 10 است.

نکته دوم، تعامل با Transient و Object Cache است. اگر در فعال‌سازی، مقادیر را با set_transient ذخیره کنید، در اولین بار بارگذاری، این مقادیر موجود هستند. اما اگر cache بر اساس زمان پاک شود، ممکن است مقادیر قدیمی باقی بمانند. توصیه می‌شود در فعال‌سازی، wp_cache_flush را فراخوانی کنید. مطلب رفتار wp_cache_flush توضیحات کامل را دارد.

مسئله سوم، مسئله transaction در دیتابیس است. اگر در فعال‌سازی چند عملیات دیتابیس انجام دهید و یکی از آن‌ها شکست بخورد، بقیه عملیات باقی می‌مانند چون وردپرس به‌طور پیش‌فرض از transaction استفاده نمی‌کند. برای حل این مشکل، باید خودتان با $wpdb->query( 'START TRANSACTION' ) transaction مدیریت کنید.

در نهایت، در پروژه‌های Enterprise توصیه می‌شود یک کلاس Installer اختصاصی بسازید که مسئول چک کردن پیش‌نیازها، اجرای migrations و ثبت نسخه باشد. این الگو از تکرار منطق در افزونه‌های مختلف جلوگیری می‌کند و تست‌پذیری را بالا می‌برد. برای مطالعه بیشتر، مباحث تابع plugin_basename، تابع plugin_dir_path و تابع register_deactivation_hook مفید هستند. برای مطالعه بیشتر درباره خود وردپرس، WordPress در ویکی‌پدیا نقطه شروع خوبی است.

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