افزونه وردپرس (WordPress Plugin) چطور نوشته می‌شود؟ این پرسشی است که هر توسعه‌دهنده‌ای پس از آشنایی با هوک‌ها و ساختار هسته‌ی وردپرس با آن روبه‌رو می‌شود. افزونه، یک بسته‌ی مستقل از کد است که قابلیت‌های جدید را بدون دستکاری هسته به وردپرس اضافه می‌کند و همین معماری، دلیل اصلی انعطاف‌پذیری بی‌نظیر این سیستم مدیریت محتوا است. نوشتن یک افزونه‌ی حرفه‌ای، فقط ایجاد یک فایل PHP و اتصال چند هوک نیست؛ بلکه طراحی یک قطعه‌ی نرم‌افزاری است که باید در برابر تغییرات نسخه، تداخل با سایر افزونه‌ها، تهدیدات امنیتی و بار ترافیکی مقاوم باشد.

وردپرس بیش از ۶۰ هزار افزونه در مخزن رسمی خود دارد و این آمار، نشان‌دهنده‌ی اهمیت معماری افزونه‌نویسی در اکوسیستم این پلتفرم است. هر افزونه، یک قرارداد بین توسعه‌دهنده و کاربر است: توسعه‌دهنده قابلیتی را فراهم می‌کند و کاربر انتظار دارد که این قابلیت بدون شکستن سایت، بدون تداخل با سایر افزونه‌ها و بدون ایجاد حفره‌ی امنیتی کار کند. رعایت این قرارداد، تفاوت بین یک افزونه‌ی آماتور و یک افزونه‌ی حرفه‌ای را مشخص می‌کند.

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

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

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

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

افزونه وردپرس دقیقاً چیست و چگونه کار می‌کند؟

افزونه وردپرس یک بسته‌ی مستقل از کد PHP، JavaScript، CSS و فایل‌های دیگر است که در پوشه‌ی wp-content/plugins قرار می‌گیرد و توسط هسته‌ی وردپرس بارگذاری می‌شود. مفهوم Plug-in در ویکی‌پدیا توضیح داده شده و ریشه‌ی آن به معماری نرم‌افزارهای ماژولار بازمی‌گردد. در وردپرس، افزونه‌ها به‌عنوان مکمل هسته عمل می‌کنند و قابلیت‌هایی را اضافه می‌کنند که در هسته وجود ندارد.

مکانیزم بارگذاری افزونه در وردپرس بر پایه‌ی یک فهرست فعال‌سازی است که در جدول wp_options با کلید active_plugins ذخیره می‌شود. وقتی وردپرس بارگذاری می‌شود، این فهرست را می‌خواند و فایل اصلی هر افزونه را با include یا require بارگذاری می‌کند. این فرآیند، قبل از هوک plugins_loaded انجام می‌شود، بنابراین کدی که در سطح فایل اصلی افزونه نوشته می‌شود، در همان لحظه اجرا می‌شود.

نکته‌ی مهم این است که افزونه‌ها در یک محیط مشترک با هسته و سایر افزونه‌ها اجرا می‌شوند. این یعنی یک افزونه‌ی نادرست می‌تواند کل سایت را از کار بیندازد. به همین دلیل، اصولی مثل پیشوندگذاری، جداسازی فضاهای نام (Namespace) و مدیریت خطا، در افزونه‌نویسی حیاتی هستند.

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

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

ساختار پایه و هدر افزونه

هر افزونه‌ی وردپرس، از یک فایل اصلی شروع می‌شود که حاوی هدر افزونه (Plugin Header) است. این هدر، یک بلوک کامنت PHP است که اطلاعات افزونه را به وردپرس معرفی می‌کند. بدون این هدر، وردپرس افزونه را شناسایی نمی‌کند.

<?php
/**
 * Plugin Name:       My Custom Plugin
 * Plugin URI:        https://example.com/my-custom-plugin
 * Description:       یک افزونه نمونه برای نمایش ساختار استاندارد.
 * Version:           1.0.0
 * Requires at least: 5.8
 * Requires PHP:      7.4
 * Author:            Your Name
 * Author URI:        https://example.com
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-custom-plugin
 * Domain Path:       /languages
 */

هدر افزونه، شامل چند فیلد کلیدی است. Plugin Name نام نمایشی افزونه است. Version نسخه‌ی فعلی است که در سیستم به‌روزرسانی استفاده می‌شود. Requires at least حداقل نسخه‌ی وردپرس مورد نیاز است. Requires PHP حداقل نسخه‌ی PHP است. Text Domain دامنه‌ی متنی برای ترجمه است. Domain Path مسیر فایل‌های ترجمه است.

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

my-custom-plugin/
├── my-custom-plugin.php
├── uninstall.php
├── readme.txt
├── includes/
│   ├── class-plugin.php
│   ├── class-loader.php
│   ├── class-activator.php
│   └── class-deactivator.php
├── admin/
│   ├── class-admin.php
│   ├── css/
│   └── js/
├── public/
│   ├── class-public.php
│   ├── css/
│   └── js/
└── languages/
    └── my-custom-plugin.pot

این ساختار، بر اساس الگوی پیشنهادی وردپرس و الگوهای رایج در جامعه‌ی توسعه‌دهندگان شکل گرفته است. جداسازی بخش مدیریت (Admin) از بخش عمومی (Public) و جدا کردن منطق در پوشه‌ی includes، نگهداری افزونه را به‌طور قابل‌توجهی ساده‌تر می‌کند.

فایل uninstall.php نقش ویژه‌ای دارد. این فایل، هنگام حذف افزونه از وردپرس اجرا می‌شود و مسئول پاک‌سازی داده‌های باقی‌مانده است. اگر این فایل وجود نداشته باشد، داده‌های افزونه در دیتابیس باقی می‌مانند و این می‌تواند به تجمع داده‌های زائد منجر شود.

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

ثبت هوک‌ها و مدیریت رویدادها

افزونه‌ها از طریق هوک‌ها با هسته‌ی وردپرس ارتباط برقرار می‌کنند. هوک‌ها، نقاط دخالت در چرخه‌ی اجرای وردپرس هستند و توسعه‌دهنده با اتصال کالبک‌ها به آن‌ها، رفتار سیستم را تغییر می‌دهد. برای درک پایه‌ای این مفهوم، مقاله هوک‌های وردپرس: قلب تپنده توسعه را مطالعه کنید.

در یک افزونه‌ی حرفه‌ای، ثبت هوک‌ها در یک نقطه‌ی متمرکز انجام می‌شود. رویکرد رایج، استفاده از یک کلاس Loader است که مسئول ثبت همه‌ی هوک‌هاست. این رویکرد، مدیریت و دیباگ را به‌طور قابل‌توجهی ساده‌تر می‌کند.

class My_Plugin_Loader {
    protected $actions = [];

    public function add_action($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
        $this->actions[] = [
            'hook' => $hook,
            'component' => $component,
            'callback' => $callback,
            'priority' => $priority,
            'accepted_args' => $accepted_args,
        ];
    }

    public function run() {
        foreach ($this->actions as $action) {
            add_action(
                $action['hook'],
                [$action['component'], $action['callback']],
                $action['priority'],
                $action['accepted_args']
            );
        }
    }
}

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

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

برای نوشتن کد تمیزتر در ثبت هوک‌ها، مقاله اکشن‌های وردپرس چگونه کد شما را تمیزتر می‌کنند؟ نکات کاربردی بیشتری ارائه می‌دهد.

کلاس‌بندی و معماری شی‌گرا

افزونه‌های حرفه‌ای، معمولاً بر پایه‌ی برنامه‌نویسی شی‌گرا (Object-Oriented Programming) نوشته می‌شوند. این رویکرد، جداسازی مسئولیت‌ها، تست‌پذیری و نگهداری را بهبود می‌دهد. در PHP مدرن، استفاده از فضاهای نام (Namespace) و خودبارگذاری (Autoloading) استاندارد است.

namespace My_Plugin;

class Plugin {
    protected $loader;

    public function __construct() {
        $this->loader = new Loader();
        $this->load_dependencies();
        $this->define_hooks();
    }

    protected function load_dependencies() {
        require_once MY_PLUGIN_PATH . 'includes/class-loader.php';
        require_once MY_PLUGIN_PATH . 'includes/class-activator.php';
        require_once MY_PLUGIN_PATH . 'admin/class-admin.php';
    }

    protected function define_hooks() {
        $admin = new Admin();
        $this->loader->add_action('admin_menu', $admin, 'register_menu');
        $this->loader->add_action('admin_enqueue_scripts', $admin, 'enqueue_assets');
    }

    public function run() {
        $this->loader->run();
    }
}

در این ساختار، هر کلاس مسئول یک دامنه‌ی مشخص است. کلاس Plugin نقش هماهنگ‌کننده دارد. کلاس Loader مسئول ثبت هوک‌هاست. کلاس Admin مسئول بخش مدیریت است. این جداسازی، به‌ویژه در پروژه‌های بزرگ، حیاتی است.

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

مفهوم Single Responsibility Principle در ویکی‌پدیا توضیح داده شده و پایه‌ی معماری کلاس‌های افزونه است. هر کلاس باید فقط یک مسئولیت داشته باشد. اگر کلاسی بیش از یک مسئولیت دارد، باید آن را به چند کلاس تقسیم کنید.

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

API تنظیمات و پنل مدیریت

اکثر افزونه‌ها نیاز به یک پنل تنظیمات دارند که کاربر بتواند رفتار افزونه را تنظیم کند. وردپرس یک API استاندارد به نام Settings API برای این کار فراهم کرده است. استفاده از این API، به‌جای ساخت فرم‌های سفارشی، امنیت و سازگاری را تضمین می‌کند.

class My_Plugin_Settings {
    public function register() {
        register_setting('my_plugin_options', 'my_plugin_settings', [
            'sanitize_callback' => [$this, 'sanitize_settings'],
            'default' => [
                'enabled' => false,
                'message' => '',
            ],
        ]);

        add_settings_section(
            'my_plugin_main',
            __('تنظیمات اصلی', 'my-custom-plugin'),
            [$this, 'render_section'],
            'my_plugin_options'
        );

        add_settings_field(
            'my_plugin_enabled',
            __('فعال‌سازی', 'my-custom-plugin'),
            [$this, 'render_enabled_field'],
            'my_plugin_options',
            'my_plugin_main'
        );
    }

    public function sanitize_settings($input) {
        return [
            'enabled' => !empty($input['enabled']),
            'message' => sanitize_text_field($input['message'] ?? ''),
        ];
    }
}

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

برای ساخت پنل مدیریت، از هوک admin_menu استفاده کنید. این هوک، به شما اجازه می‌دهد یک منوی جدید یا یک زیرمنو به پنل مدیریت اضافه کنید.

class My_Plugin_Admin {
    public function register_menu() {
        add_menu_page(
            __('تنظیمات افزونه', 'my-custom-plugin'),
            __('افزونه من', 'my-custom-plugin'),
            'manage_options',
            'my-plugin-settings',
            [$this, 'render_page'],
            'dashicons-admin-generic',
            80
        );
    }

    public function render_page() {
        if (!current_user_can('manage_options')) {
            return;
        }
        require_once MY_PLUGIN_PATH . 'admin/views/settings-page.php';
    }
}

در این کد، دو نکته‌ی مهم وجود دارد. اول، بررسی current_user_can('manage_options') که از دسترسی غیرمجاز جلوگیری می‌کند. دوم، جداسازی فایل نمایش از کلاس، که نگهداری را ساده‌تر می‌کند.

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

ذخیره‌سازی و مدیریت داده

افزونه‌ها به روش‌های مختلفی داده ذخیره می‌کنند. انتخاب روش مناسب، بسته به نوع داده و حجم آن متفاوت است. سه گزینه‌ی اصلی وجود دارد: گزینه‌های وردپرس (Options)، متادیتای پست (Post Meta) و جداول سفارشی.

گزینه‌های وردپرس برای ذخیره‌ی تنظیمات ساده و داده‌های کوچک مناسب است. از توابع get_option()، update_option() و delete_option() استفاده کنید. این داده‌ها در جدول wp_options ذخیره می‌شوند و به‌طور خودکار توسط وردپرس مدیریت می‌شوند.

// ذخیره گزینه
update_option('my_plugin_version', '1.0.0');

// خواندن گزینه
$version = get_option('my_plugin_version', '0.0.0');

// حذف گزینه
delete_option('my_plugin_version');

متادیتای پست برای ذخیره‌ی داده‌های مرتبط با یک پست یا کاربر مناسب است. از توابع get_post_meta()، update_post_meta()، get_user_meta() و update_user_meta() استفاده کنید. این داده‌ها در جداول wp_postmeta و wp_usermeta ذخیره می‌شوند.

// ذخیره متادیتای پست
update_post_meta($post_id, '_my_plugin_field', $value);

// خواندن متادیتا
$value = get_post_meta($post_id, '_my_plugin_field', true);

نکته‌ی مهم در استفاده از متادیتا، استفاده از پیشوند زیرخط (_) برای فیلدهای خصوصی است. این پیشوند، باعث می‌شود که فیلد در پنل مدیریت پیش‌فرض نمایش داده نشود.

جداول سفارشی برای ذخیره‌ی داده‌های حجیم یا داده‌هایی که ساختار پیچیده دارند، مناسب است. ایجاد جدول سفارشی، نیازمند استفاده از dbDelta() است که تفاوت ساختار جدول را تشخیص می‌دهد و آن را به‌روزرسانی می‌کند.

function my_plugin_create_table() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_plugin_data';
    $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,
        data longtext 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);
}

نکته‌ی حیاتی در ایجاد جدول سفارشی، استفاده از $wpdb->prefix است. این متغیر، پیشوند جدول‌های وردپرس را برمی‌گرداند و از تداخل با سایر سایت‌ها در نصب‌های چندگانه جلوگیری می‌کند.

برای درک عمیق‌تر نحوه‌ی مدیریت داده‌ها در وردپرس، مقاله آموزش مدیریت دیتابیس وردپرس را مطالعه کنید. همچنین مقاله بهینه‌سازی دیتابیس وردپرس چیست؟ نکات کارایی مهمی دارد.

امنیت در افزونه‌نویسی

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

حوزه‌ی اول، پاک‌سازی ورودی‌ها. هر داده‌ای که از کاربر دریافت می‌شود، باید پاک‌سازی شود. برای متن از sanitize_text_field()، برای ایمیل از sanitize_email()، برای URL از esc_url_raw()، برای اعداد از absint() و برای HTML از wp_kses_post() استفاده کنید.

$name = sanitize_text_field($_POST['name']);
$email = sanitize_email($_POST['email']);
$age = absint($_POST['age']);

حوزه‌ی دوم، فرار دادن خروجی‌ها. هر مقداری که در خروجی HTML قرار می‌گیرد، باید فرار داده شود. برای متن از esc_html()، برای ویژگی‌های HTML از esc_attr()، برای URL از esc_url() و برای JavaScript از esc_js() استفاده کنید.

echo '<div class="' . esc_attr($class) . '">';
echo '<p>' . esc_html($message) . '</p>';
echo '<a href="' . esc_url($link) . '">' . esc_html__('لینک', 'my-plugin') . '</a>';

حوزه‌ی سوم، بررسی سطح دسترسی. قبل از انجام هر عملیات حساس، بررسی کنید که کاربر جاری مجوز لازم را دارد. از current_user_can() برای بررسی سطح دسترسی و از wp_verify_nonce() برای بررسی منشأ درخواست استفاده کنید.

if (!current_user_can('manage_options')) {
    wp_die(esc_html__('دسترسی غیرمجاز', 'my-plugin'));
}

if (!isset($_POST['_wpnonce']) ||
    !wp_verify_nonce($_POST['_wpnonce'], 'my_plugin_action')) {
    wp_die(esc_html__('درخواست نامعتبر', 'my-plugin'));
}

حوزه‌ی چهارم، جلوگیری از SQL Injection. اگر از کوئری خام استفاده می‌کنید، همیشه از $wpdb->prepare() برای پارامترها استفاده کنید. هرگز رشته‌های کاربر را مستقیماً در کوئری قرار ندهید.

global $wpdb;

// نادرست
$results = $wpdb->get_results(
    "SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = $user_id"
);

// درست
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d",
        $user_id
    )
);

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

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

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

افزونه‌های حرفه‌ای، از همان ابتدا برای بین‌المللی‌سازی (Internationalization) طراحی می‌شوند. این یعنی همه‌ی رشته‌های متنی در کد، باید از توابع ترجمه استفاده کنند تا بتوان آن‌ها را به زبان‌های مختلف ترجمه کرد. در وردپرس، استاندارد بین‌المللی‌سازی با پیشوند i18n شناخته می‌شود و ترجمه با l10n.

// ترجمه ساده
$message = __('سلام دنیا', 'my-custom-plugin');

// ترجمه با فرار دادن HTML
echo esc_html__('تنظیمات ذخیره شد', 'my-custom-plugin');

// ترجمه با پارامتر
printf(
    esc_html__('شما %d پیام دارید', 'my-custom-plugin'),
    absint($count)
);

// ترجمه با تفکیک جمع و مفرد
printf(
    esc_html(_n('%d پیام', '%d پیام', $count, 'my-custom-plugin')),
    absint($count)
);

نکته‌ی مهم در بین‌المللی‌سازی، استفاده از دامنه‌ی متنی (Text Domain) یکسان در همه‌ی توابع ترجمه است. این دامنه، باید با مقدار Text Domain در هدر افزونه یکسان باشد. در غیر این صورت، ترجمه‌ها بارگذاری نمی‌شوند.

برای تولید فایل ترجمه، از ابزارهایی مثل WP-CLI یا Poedit استفاده کنید. دستور wp i18n make-pot همه‌ی رشته‌های قابل ترجمه را از کد استخراج می‌کند و در یک فایل .pot ذخیره می‌کند. سپس مترجم‌ها می‌توانند این فایل را به زبان خود ترجمه کنند.

برای درک عمیق‌تر مفاهیم بین‌المللی‌سازی، مقاله چگونه از وردپرس چندزبانه استفاده کنیم؟ را مطالعه کنید.

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

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

class My_Plugin_Assets {
    public function enqueue_admin_assets($hook) {
        if ('toplevel_page_my-plugin-settings' !== $hook) {
            return;
        }

        wp_enqueue_style(
            'my-plugin-admin',
            MY_PLUGIN_URL . 'admin/css/admin.css',
            [],
            MY_PLUGIN_VERSION
        );

        wp_enqueue_script(
            'my-plugin-admin',
            MY_PLUGIN_URL . 'admin/js/admin.js',
            ['jquery'],
            MY_PLUGIN_VERSION,
            true
        );
    }
}

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

برای فایل‌های عمومی (Frontend)، از هوک wp_enqueue_scripts استفاده کنید. همچنین می‌توانید از wp_localize_script() برای انتقال داده‌ها از PHP به JavaScript استفاده کنید.

wp_localize_script('my-plugin-public', 'myPluginData', [
    'ajaxUrl' => admin_url('admin-ajax.php'),
    'nonce' => wp_create_nonce('my_plugin_ajax'),
    'strings' => [
        'loading' => esc_html__('در حال بارگذاری...', 'my-custom-plugin'),
    ],
]);

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

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

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

class My_Plugin_Updater {
    protected $api_url = 'https://example.com/api/updates';

    public function check_for_updates($transient) {
        if (empty($transient->checked)) {
            return $transient;
        }

        $response = wp_remote_get(add_query_arg([
            'plugin' => 'my-custom-plugin',
            'version' => MY_PLUGIN_VERSION,
        ], $this->api_url));

        if (is_wp_error($response)) {
            return $transient;
        }

        $data = json_decode(wp_remote_retrieve_body($response));

        if (isset($data->version) && version_compare($data->version, MY_PLUGIN_VERSION, '>')) {
            $transient->response['my-custom-plugin/my-custom-plugin.php'] = (object) [
                'slug' => 'my-custom-plugin',
                'new_version' => $data->version,
                'package' => $data->download_url,
            ];
        }

        return $transient;
    }
}

برای انتشار در مخزن رسمی وردپرس، باید یک فایل readme.txt با فرمت مشخص ایجاد کنید. این فایل، شامل توضیحات، نصب، تغییرات نسخه‌ها و سوالات پرتکرار است. همچنین کد افزونه باید با استانداردهای کدنویسی وردپرس سازگار باشد.

نکته‌ی مهم در انتشار، رعایت مجوز GPL است. همه‌ی افزونه‌های وردپرس باید تحت مجوز GPL منتشر شوند. این مجوز، آزادی استفاده، تغییر و توزیع را تضمین می‌کند.

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

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

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

اشتباهپیامدراه‌حل
عدم پیشوندگذاری در نام توابعتداخل با سایر افزونه‌هااستفاده از فضای نام یا پیشوند یکتا
نوشتن کد در سطح فایل اصلیاجرا قبل از بارگذاری کامل وردپرساتصال به هوک plugins_loaded
عدم پاک‌سازی ورودی‌هاآسیب‌پذیری XSS و SQL Injectionاستفاده از توابع پاک‌سازی
عدم فرار دادن خروجی‌هاآسیب‌پذیری XSSاستفاده از توابع esc_*
عدم بررسی سطح دسترسیاجرای عملیات حساس توسط کاربر غیرمجازاستفاده از current_user_can()
عدم استفاده از nonceحمله CSRFاستفاده از wp_verify_nonce()
عدم پاک‌سازی در حذف افزونهتجمع داده‌های زائد در دیتابیساستفاده از uninstall.php

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

if (function_exists('some_new_function')) {
    some_new_function();
} else {
    // جایگزین
}

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

دیباگ و عیب‌یابی افزونه

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

ابزار اول، فعال کردن WP_DEBUG. این ثابت، خطاهای PHP را نمایش می‌دهد. برای فعال‌سازی، در فایل wp-config.php این خطوط را اضافه کنید:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

با این تنظیمات، خطاها در فایل wp-content/debug.log ثبت می‌شوند و نمایش داده نمی‌شوند. این کار، از نمایش خطاها به کاربران جلوگیری می‌کند.

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

ابزار سوم، لاگ‌گیری ساختاریافته. اگر افزونه‌ی شما در محیط تولید کار می‌کند، نمی‌توانید از var_dump() استفاده کنید. به‌جای آن، از error_log() یا یک سیستم لاگ‌گیری مثل Monolog استفاده کنید.

if (defined('WP_DEBUG') && WP_DEBUG) {
    error_log('My Plugin: ' . print_r($data, true));
}

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

برای درک عمیق‌تر روش‌های دیباگ، مقاله فعال‌سازی حالت Debug وردپرس و پیدا کردن خطاها را مطالعه کنید. همچنین مقاله چگونه خطای افزونه وردپرس را پیدا کنیم؟ نکات عملی بیشتری دارد.

نگاهی مهندسی به لایه‌های پیشرفته افزونه

از منظر یک مهندس ارشد، نوشتن افزونه‌ی وردپرس فقط کدنویسی نیست؛ طراحی یک سیستم است که باید در برابر تغییرات نسخه، بار ترافیکی، تهدیدات امنیتی و نیازهای آینده مقاوم باشد. چند مفهوم بنیادین را باید بازتعریف کنید.

مفهوم اول، جداسازی لایه‌ها. یک افزونه‌ی حرفه‌ای، حداقل از سه لایه تشکیل می‌شود: لایه‌ی ارائه (Presentation)، لایه‌ی منطق کسب‌وکار (Business Logic) و لایه‌ی دسترسی به داده (Data Access). هر لایه، مسئولیت مشخصی دارد و از لایه‌های دیگر مستقل است. این جداسازی، تست‌پذیری و نگهداری را به‌طور قابل‌توجهی بهبود می‌دهد.

// لایه داده
class My_Plugin_Repository {
    public function find_by_id($id) {
        global $wpdb;
        return $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}my_table WHERE id = %d",
            $id
        ));
    }
}

// لایه منطق
class My_Plugin_Service {
    protected $repository;

    public function __construct(My_Plugin_Repository $repository) {
        $this->repository = $repository;
    }

    public function get_processed_data($id) {
        $row = $this->repository->find_by_id($id);
        // پردازش داده
        return $processed;
    }
}

// لایه ارائه
class My_Plugin_Controller {
    protected $service;

    public function __construct(My_Plugin_Service $service) {
        $this->service = $service;
    }

    public function handle_request() {
        $data = $this->service->get_processed_data($_GET['id']);
        // نمایش داده
    }
}

مفهوم دوم، تزریق وابستگی (Dependency Injection). به‌جای اینکه کلاس‌ها وابستگی‌های خود را مستقیماً ایجاد کنند، آن‌ها را از بیرون دریافت می‌کنند. این رویکرد، تست‌پذیری را افزایش می‌دهد و وابستگی‌ها را شفاف می‌کند. در پروژه‌های بزرگ، استفاده از یک Container برای مدیریت وابستگی‌ها توصیه می‌شود.

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

class My_Plugin_Cache {
    protected $group = 'my_plugin';
    protected $ttl = 3600;

    public function remember($key, callable $callback) {
        $value = wp_cache_get($key, $this->group);
        if (false === $value) {
            $value = $callback();
            wp_cache_set($key, $value, $this->group, $this->ttl);
        }
        return $value;
    }
}

مفهوم چهارم، سازگاری با نسخه‌های مختلف. یک افزونه‌ی حرفه‌ای باید با چند نسخه‌ی وردپرس و PHP کار کند. این یعنی باید از توابعی استفاده کنید که در نسخه‌های قدیمی هم وجود دارند، یا با function_exists() بررسی کنید که تابع مورد نظر وجود دارد. همچنین باید از ویژگی‌های زبان PHP که در نسخه‌های قدیمی پشتیبانی نمی‌شوند، پرهیز کنید.

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

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

پرسش‌های پرتکرار درباره نوشتن افزونه وردپرس

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

حداقل فایل‌های یک افزونه چیست؟ حداقل، یک فایل PHP با هدر افزونه. اما یک افزونه‌ی حرفه‌ای شامل فایل اصلی، فایل‌های کلاس، فایل‌های CSS و JavaScript، فایل readme.txt و فایل uninstall.php است.

چگونه یک افزونه امن بنویسم؟ همیشه ورودی‌ها را پاک‌سازی کنید، خروجی‌ها را فرار دهید، سطح دسترسی کاربر را بررسی کنید و از nonce برای عملیات حساس استفاده کنید. همچنین از $wpdb->prepare() برای کوئری‌های خام استفاده کنید.

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

چگونه داده‌ها را در افزونه ذخیره کنم؟ برای تنظیمات ساده از Options API، برای داده‌های مرتبط با پست از Post Meta و برای داده‌های حجیم از جداول سفارشی استفاده کنید.

چگونه افزونه را ترجمه‌پذیر کنم؟ از توابع ترجمه مثل __()، _e()، esc_html__() و _n() استفاده کنید. دامنه‌ی متنی (Text Domain) باید با مقدار در هدر افزونه یکسان باشد. برای تولید فایل ترجمه از WP-CLI یا Poedit استفاده کنید.

چگونه افزونه را در مخزن رسمی منتشر کنم؟ باید یک فایل readme.txt با فرمت مشخص ایجاد کنید، کد را با استانداردهای وردپرس سازگار کنید، مجوز GPL را رعایت کنید و از طریق سایت وردپرس درخواست ثبت دهید.

چگونه افزونه را به‌روزرسانی کنم؟ برای افزونه‌های مخزن رسمی، وردپرس به‌طور خودکار به‌روزرسانی را انجام می‌دهد. برای افزونه‌های تجاری یا سفارشی، باید یک سیستم به‌روزرسانی اختصاصی با استفاده از هوک pre_set_site_transient_update_plugins پیاده‌سازی کنید.

چگونه با خطاهای افزونه مقابله کنم؟ ابتدا WP_DEBUG را فعال کنید تا خطاها را ببینید. سپس با غیرفعال کردن سایر افزونه‌ها، منبع تداخل را پیدا کنید. برای خطاهای پیچیده، از Query Monitor و لاگ‌گیری ساختاریافته استفاده کنید.

آیا می‌توانم از کتابخانه‌های خارجی در افزونه استفاده کنم؟ بله، اما با احتیاط. کتابخانه‌ها باید تحت مجوز GPL سازگار باشند. همچنین باید از تداخل با کتابخانه‌های سایر افزونه‌ها جلوگیری کنید. بهترین رویکرد، استفاده از Composer و فضای نام اختصاصی است.

آیا افزونه‌ها روی سرعت سایت تأثیر دارند؟ هر افزونه، کد اضافه‌ای است که باید بارگذاری و اجرا شود. اگر تعداد افزونه‌ها زیاد باشد یا کد ناکارآمد داشته باشند، می‌تواند بر سرعت تأثیر بگذارد. با بهینه‌سازی کوئری‌ها، کش کردن و بارگذاری هدفمند فایل‌ها، می‌توان این تأثیر را کاهش داد.

چگونه یک افزونه حرفه‌ای بسازم؟ از معماری شی‌گرا با جداسازی لایه‌ها استفاده کنید. هوک‌ها را در یک نقطه متمرکز ثبت کنید. امنیت را از همان ابتدا در نظر بگیرید. از Settings API استفاده کنید. کد را ترجمه‌پذیر بنویسید. تست خودکار بنویسید و از استانداردهای کدنویسی وردپرس پیروی کنید.

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

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