اولین افزونهای که نوشتم، یک فایل PHP دویست خطی بود که با functions.php قالب شروع شد. هر بار که قالب عوض میشد، آن قابلیت هم از بین میرفت. همانجا فهمیدم هر چیزی که به «کارکرد» سایت مربوط است و نه «ظاهر» آن، جایش در افزونه است، نه قالب.
از آن موقع افزونههای زیادی نوشتهام؛ بعضی برای پروژههای مشتریها و بعضی را منتشر کردهام، مثل افزونه تبدیل عکس به WebP، گالری زنده محصولات ووکامرس و افزونه مدیریت سریع انبار. این مقاله چکیده چیزهایی است که در این مسیر یاد گرفتهام و برای کسی نوشته شده که PHP را در حد پایه میداند و میخواهد اولین افزونه درستوحسابیاش را بسازد.
اول بپرسید: واقعاً افزونه لازم است؟
قبل از اینکه کد بنویسید، ببینید مشکل با تنظیمات یا یک افزونه معتبر موجود حل میشود یا نه. افزونه سفارشی یعنی مسئولیت نگهداری؛ با هر بهروزرسانی وردپرس و ووکامرس باید مطمئن شوید هنوز کار میکند. من وقتی افزونه اختصاصی مینویسم که یا راهحل آماده وجود ندارد، یا راهحلهای موجود آنقدر سنگیناند که برای یک نیاز کوچک دهها قابلیت اضافه بار میکنند.
ساختار حداقلی
یک افزونه در سادهترین حالت یک پوشه در wp-content/plugins و یک فایل PHP با سربرگ استاندارد است:
<?php
/**
* Plugin Name: My Shop Tweaks
* Description: تغییرات اختصاصی فروشگاه
* Version: 1.0.0
* Requires PHP: 7.4
* Text Domain: my-shop-tweaks
*/
defined( 'ABSPATH' ) || exit;
خط آخر جلوی اجرای مستقیم فایل از طریق آدرس را میگیرد. کوچک است، اما در هر فایل PHP افزونه باید باشد.
وقتی افزونه بزرگتر شد، ساختاری که من استفاده میکنم این است: فایل اصلی فقط ثابتها را تعریف میکند و کلاس اصلی را بارگذاری میکند؛ پوشه includes برای کلاسها، پوشه assets برای CSS و JS، و پوشه languages برای ترجمه. کد مربوط به پیشخوان را از کد سمت کاربر جدا کنید و فقط در جای خودش بارگذاری کنید.
هوکها؛ زبان مشترک با وردپرس
تمام قدرت افزونهنویسی در هوکهاست. اکشنها جاییاند که کاری انجام میدهید، فیلترها جاییاند که مقداری را تغییر میدهید و برمیگردانید.
یک مثال واقعی: مشتریای میخواست متن دکمه «افزودن به سبد خرید» برای محصولات یک دسته خاص «رزرو» باشد:
add_filter( 'woocommerce_product_add_to_cart_text', function ( $text, $product ) {
if ( has_term( 'reservable', 'product_cat', $product->get_id() ) ) {
return __( 'رزرو', 'my-shop-tweaks' );
}
return $text;
}, 10, 2 );
سه نکته در همین چند خط هست. فیلتر همیشه باید مقداری برگرداند، حتی وقتی شرط برقرار نیست. پارامتر چهارم add_filter تعداد آرگومانهاست و اگر فراموشش کنید، فقط اولی را میگیرید. و رشته با تابع ترجمه نوشته شده تا بعداً قابل ترجمه باشد.
برای پیدا کردن هوک مناسب، بهترین منبع خود کد است. در فایلهای قالب ووکامرس یا هسته وردپرس دنبال do_action و apply_filters بگردید. افزونه Query Monitor هم نشان میدهد در هر صفحه چه هوکهایی اجرا شدهاند.
صفحه تنظیمات
تقریباً هر افزونهای بالاخره صفحه تنظیمات میخواهد. Settings API وردپرس شاید در نگاه اول پرحرف به نظر برسد، اما ذخیره، اعتبارسنجی و nonce را خودش مدیریت میکند. بارها افزونههایی دیدهام که فرم تنظیمات را دستی ساختهاند و فراموش کردهاند nonce یا بررسی دسترسی بگذارند.
تنظیمات را در یک گزینه آرایهای ذخیره کنید، نه ده گزینه جدا. این کار جدول wp_options را تمیز نگه میدارد و پاکسازی هنگام حذف افزونه هم راحتتر است. اگر گزینهای در هر بارگذاری صفحه لازم نیست، autoload آن را خاموش کنید.
امنیت؛ جایی که بیشتر افزونهها کم میآورند
بیشتر آسیبپذیریهایی که در گزارشهای امنیتی افزونهها میبینیم، از چند اشتباه تکراری میآیند. من قبل از انتشار هر نسخه، کد را با این سؤالها مرور میکنم:
آیا ورودی پاکسازی شده؟ هر چیزی که از $_GET، $_POST یا درخواست REST میآید، با sanitize_text_field، absint یا تابع مناسب دیگری پاکسازی شود.
آیا خروجی escape شده؟ هر چیزی که چاپ میشود، با esc_html، esc_attr یا esc_url. حتی اگر فکر میکنید داده از جای امنی آمده است.
آیا دسترسی بررسی شده؟ هر عملیاتی که چیزی را تغییر میدهد باید current_user_can را چک کند. وجود کاربر واردشده کافی نیست؛ مشترک سایت هم کاربر واردشده است.
آیا nonce هست؟ برای هر فرم و هر درخواست AJAX. nonce جلوی حمله CSRF را میگیرد.
آیا کوئریها آمادهسازی شدهاند؟ هر کوئری مستقیم به پایگاهداده با $wpdb->prepare.
اگر فقط یک بخش از این مقاله را جدی بگیرید، همین بخش باشد. توصیههای امنیتی سطح سایت را هم در امنیت وردپرس نوشتهام.
REST API و AJAX
برای ارتباط جاوااسکریپت با سرور، این روزها REST API را به admin-ajax ترجیح میدهم. مسیرها مشخصترند، پارامترها اعتبارسنجی میشوند و callback دسترسی (permission_callback) اجباری است. در افزونه مدیریت سریع انبار، ذخیره موجودی دقیقاً با یک مسیر REST انجام میشود که فقط برای کاربرانی با دسترسی مدیریت محصولات باز است.
ترجمه و راستچین
همه رشتهها را با توابع __() و esc_html__() و دامنه متنی افزونه بنویسید، حتی اگر افزونه فقط برای مخاطب فارسی است. روزی که بخواهید نسخه انگلیسی منتشر کنید یا افزونه را در مارکت بفروشید، از خودتان تشکر میکنید.
برای CSS، به جای left و right از خصوصیات منطقی مثل margin-inline-start استفاده کنید تا افزونه هم در راستچین و هم در چپچین درست نمایش داده شود.
سازگاری با ووکامرس
اگر افزونه با ووکامرس کار میکند، دو نکته مهم دارد. اول، مستقیم با post_meta محصول و سفارش کار نکنید؛ از متدهای کلاس محصول و سفارش استفاده کنید. ووکامرس ذخیرهسازی سفارشها را به جداول اختصاصی (HPOS) منتقل کرده و کدی که سفارش را مثل یک نوشته معمولی میخواند، روی فروشگاههای جدید کار نمیکند. دوم، سازگاری با HPOS را در افزونه اعلام کنید تا ووکامرس هشدار ندهد.
عملکرد
افزونه شما روی سایت کس دیگری اجرا میشود که احتمالاً بیست افزونه دیگر هم دارد. اسکریپت و استایل را فقط در صفحاتی بارگذاری کنید که لازم است. کوئری سنگین را در هر بارگذاری صفحه اجرا نکنید؛ نتیجه را با transient ذخیره کنید. و کارهای طولانی، مثل پردازش صدها تصویر، را به پسزمینه بسپارید. در افزونه WebP، تبدیل تصاویر قدیمی به همین دلیل دستهدسته و در پسزمینه انجام میشود تا سایت در حین کار کند نشود.
فعالسازی، غیرفعالسازی و حذف
register_activation_hook برای ساخت جدول یا مقداردهی اولیه است. غیرفعالسازی نباید دادهای را پاک کند؛ کاربر ممکن است فقط برای عیبیابی افزونه را موقتاً غیرفعال کرده باشد. پاکسازی واقعی در فایل uninstall.php انجام میشود که فقط هنگام حذف افزونه اجرا میشود.
تست و دیباگ
WP_DEBUG و WP_DEBUG_LOG را روی محیط توسعه همیشه روشن نگه دارید. افزونهای که روی سایت شما بیصدا کار میکند، ممکن است روی سایتی با نسخه PHP متفاوت دهها هشدار تولید کند. قبل از انتشار، روی قدیمیترین و جدیدترین نسخه PHP که اعلام کردهاید پشتیبانی میکنید تست کنید، و یک بار هم با یک قالب پیشفرض وردپرس.
محیط توسعه و نسخهبندی
افزونه را مستقیم روی سایت اصلی ننویسید. من روی سیستم خودم یک وردپرس محلی دارم، با Local یا داکر، و هر افزونه یک مخزن Git جدا دارد. هر تغییری کامیت میشود و برای هر نسخه منتشرشده یک تگ میگذارم. وقتی مشتری میگوید «از دیروز فلان چیز کار نمیکند»، میتوانم دقیقاً ببینم دیروز چه چیزی عوض شده است.
ویرایشگر کد هم مهم است. VS Code یا PhpStorm با افزونههایی که توابع وردپرس را میشناسند، خیلی از اشتباههای تایپی را قبل از اجرا نشان میدهند. PHP_CodeSniffer با استاندارد کدنویسی وردپرس هم ابزار خوبی است؛ لازم نیست همه قواعدش را رعایت کنید، اما هشدارهای امنیتیاش، مثل خروجی escapeنشده، را جدی بگیرید.
یک عادت دیگر که به من کمک کرده: برای هر افزونه یک فایل یادداشت دارم که هوکهای مهم، تصمیمهای طراحی و دلیلشان را مینویسم. شش ماه بعد که برمیگردم، لازم نیست دوباره بفهمم چرا فلان کار را آنطور انجام دادهام.
آپدیت افزونه بدون شکستن سایت مشتری
وقتی افزونه شما روی سایتهای دیگر نصب است، هر تغییر در ساختار داده باید مهاجرت داشته باشد. شماره نسخه پایگاهداده را در یک گزینه ذخیره کنید و هنگام بارگذاری افزونه، اگر نسخه ذخیرهشده قدیمیتر بود، مهاجرت لازم را اجرا کنید. هیچوقت فرض نکنید کاربر از نسخه قبلی بهروزرسانی کرده؛ ممکن است سه نسخه را یکجا رد کرده باشد.
انتشار
برای انتشار در مخزن وردپرس، فایل readme.txt با فرمت استاندارد لازم است و کد بررسی میشود. اگر قصد فروش دارید، مارکتهای ایرانی مثل ژاکت گزینه رایجیاند؛ افزونه مدیریت سریع انبار من هم همانجا عرضه میشود. هر جا منتشر میکنید، برای بهروزرسانیها نسخهبندی معنادار داشته باشید و تغییرات هر نسخه را بنویسید.
اگر وقت ساختش را ندارید
همه اینها یاد گرفتنی است، اما اگر یک نیاز مشخص در فروشگاه یا سایتتان دارید و میخواهید سریع و درست حل شود، توسعه افزونه اختصاصی وردپرس را ببینید. برای پروژههای بزرگتر، درباره انتخاب بین وردپرس و کدنویسی کامل در وردپرس یا کدنویسی اختصاصی نوشتهام.
افزونه برای چند سایت
اگر افزونه را برای چند مشتری استفاده میکنید، تنظیمات مخصوص هر سایت را در کد ثابت ننویسید. هر چیزی که بین سایتها فرق دارد، از آدرس API تا متن پیامها، باید از صفحه تنظیمات قابلتغییر باشد. این کار در ابتدا کمی وقت بیشتری میگیرد، اما بعداً به جای نگهداری چند نسخه جدا، یک افزونه دارید.
سؤالهای رایج
کد را در functions.php بنویسم یا افزونه؟
اگر به ظاهر قالب مربوط است، functions.php قالب فرزند. اگر به کارکرد سایت مربوط است و با عوض شدن قالب باید بماند، افزونه. برای قطعهکدهای کوچک میتوانید یک افزونه «اختصاصی سایت» بسازید و همه را آنجا جمع کنید.
برای ساخت افزونه باید شیگرایی بلد باشم؟
برای افزونه کوچک نه. اما وقتی کد از چند صد خط گذشت، کلاسها نظم و جلوگیری از تداخل نامها را خیلی راحتتر میکنند. اگر کلاس نمینویسید، دستکم همه توابع را با پیشوند یکتا نامگذاری کنید.
چطور بفهمم افزونهام سایت را کند کرده؟
Query Monitor را نصب کنید و صفحه را با افزونه فعال و غیرفعال مقایسه کنید. تعداد کوئریها، زمان اجرا و فایلهای بارگذاریشده را ببینید.
