چرا دادههای شما با wpdb::insert ذخیره نمیشود؟ راهنمای امنیت و دقت
متد wpdb::insert برای درج امن رکورد در پایگاه داده وردپرس؛ بررسی پارامترها، format، sanitize و اشتباهات رایج در افزونهنویسی حرفهای.
چرا درج امن داده حیاتی است؟
در توسعه افزونه وردپرس، بسیاری از عملیاتها نیازمند درج داده در جداول سفارشی است: ثبت لاگ رویداد، ذخیره رکورد سفارش، درج آمار بازدید، ثبت گزارش خطا و بسیاری موارد دیگر. اگر این درج بهدرستی انجام نشود، دو خطر جدی وجود دارد. خطر اول، SQL Injection است. اگر داده ورودی کاربر بدون escape در کوئری درج شود، مهاجم میتواند کوئری دلخواه اجرا کند، دادهها را بخواند یا حذف کند. خطر دوم، ذخیره نادرست داده است. اگر نوع داده اشتباه باشد، ممکن است داده معیوب در پایگاه داده ذخیره شود و در بازخوانی، مشکلات منطقی ایجاد کند. متدwpdb::insert این دو خطر را با یک رویکرد استاندارد و امن حل میکند.
متد wpdb::insert چیست؟
متدwpdb::insert() یک متد از کلاس wpdb در وردپرس است که در فایل wp-includes/wp-db.php تعریف شده است. این متد یک رکورد جدید در جدول مشخص درج میکند.
برخلاف کوئری مستقیم با $wpdb->query()، این متد بهصورت خودکار از prepared statement استفاده میکند و پارامترها را با format مناسب escape میکند. این رویکرد، امنیت درج را تضمین میکند.
نکته مهم این است که این متد تنها برای جداول وردپرس طراحی شده است. برای درج در جداول غیر وردپرس، باید احتیاط بیشتری کرد.
امضای متد و پارامترها
امضای این متد بهشکل زیر است:public function insert( $table, $data, $format = null ) {
// ...
}
پارامتر اول (table) نام جدول است که باید شامل prefix وردپرس باشد.
پارامتر دوم (data) آرایهای انجمنی است که کلیدها نام ستونها و مقادیر، دادههای درج هستند.
پارامتر سوم (format) آرایهای از formatها است که ترتیب آن با ترتیب مقادیر در آرایه data مطابقت دارد. مقادیر مجاز:
- %s: رشته
- %d: عدد صحیح
- %f: عدد اعشاری
اگر پارامتر format پاس داده نشود، وردپرس بهصورت خودکار بر پایه نوع داده PHP تصمیم میگیرد. اما این رویکرد ممکن است در برخی سناریوها نادرست عمل کند.
خروجی متد یک عدد صحیح است: تعداد رکوردهای درجشده (معمولاً ۱) یا مقدار false در صورت خطا.
نقش پارامتر format
پارامترformat نقش کلیدی در امنیت و دقت درج دارد. اگر format اشتباه باشد، ممکن است داده بهدرستی ذخیره نشود یا کوئری شکست بخورد.
نمونه درست:
global $wpdb;
$result = $wpdb->insert(
$wpdb->prefix . 'myplugin_log',
array(
'user_id' => get_current_user_id(),
'action' => 'login',
'message' => 'ورود موفق',
'ip_address' => $_SERVER['REMOTE_ADDR'],
'created_at' => current_time( 'mysql' ),
),
array( '%d', '%s', '%s', '%s', '%s' )
);
ترتیب format باید دقیقاً با ترتیب مقادیر در آرایه data مطابقت داشته باشد. اگر ترتیب اشتباه باشد، ممکن است داده در ستون اشتباهی درج شود.
نکته مهم: اگر پارامتر format پاس داده نشود، وردپرس بهصورت خودکار همه مقادیر را بهعنوان رشته (%s) درج میکند. این رفتار ممکن است برای اعداد نامناسب باشد. همیشه format را صریح تعریف کنید.
Sanitize پیش از درج
متدwpdb::insert از SQL Injection جلوگیری میکند اما از XSS محافظت نمیکند. اگر داده خام از کاربر را درج کنید، در زمان نمایش میتواند به XSS منجر شود.
الگوی صحیح:
global $wpdb;
$data = array(
'user_id' => absint( $_POST['user_id'] ),
'title' => isset( $_POST['title'] )
? sanitize_text_field( wp_unslash( $_POST['title'] ) )
: '',
'content' => isset( $_POST['content'] )
? wp_kses_post( wp_unslash( $_POST['content'] ) )
: '',
'email' => isset( $_POST['email'] )
? sanitize_email( wp_unslash( $_POST['email'] ) )
: '',
);
$format = array( '%d', '%s', '%s', '%s' );
$wpdb->insert( $wpdb->prefix . 'myplugin_items', $data, $format );
نکته مهم: تابع wp_unslash پیش از sanitize فراخوانی میشود تا کاراکترهای escape شده توسط PHP به حالت اصلی بازگردند. راهنمای توابع escape در صفحه esc_html آمده است.
مقدار بازگشتی و تفسیر آن
مقدار بازگشتی این متد میتواند سه حالت داشته باشد: -1: درج موفق یک رکورد
- 0: هیچ رکوردی درج نشد (نادر)
- false: خطا در درج
الگوی صحیح بررسی:
$result = $wpdb->insert( $table, $data, $format );
if ( false === $result ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf(
'Insert failed: %s | Last error: %s',
$table,
$wpdb->last_error
) );
}
return false;
}
$inserted_id = $wpdb->insert_id;
return $inserted_id;
نکته مهم: متد insert خودش مقدار شناسه درجشده را برنمیگرداند. برای دریافت شناسه، باید از خاصیت $wpdb->insert_id استفاده کنید. این خاصیت تنها پس از یک درج موفق معتبر است.
کاربردهای عملی در افزونه
درج لاگ رویداد با بررسی کامل:function myplugin_log_event( $action, $message, $user_id = 0 ) {
global $wpdb;
$table = $wpdb->prefix . 'myplugin_events';
$data = array(
'user_id' => absint( $user_id ),
'action' => sanitize_key( $action ),
'message' => sanitize_textarea_field( $message ),
'ip_address' => isset( $_SERVER['REMOTE_ADDR'] )
? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) )
: '',
'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] )
? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
: '',
'created_at' => current_time( 'mysql' ),
);
$format = array( '%d', '%s', '%s', '%s', '%s', '%s' );
$result = $wpdb->insert( $table, $data, $format );
if ( false === $result ) {
return false;
}
return $wpdb->insert_id;
}
درج داده از فرم AJAX با بررسی nonce و capability:
add_action( 'wp_ajax_myplugin_add_item', 'myplugin_add_item_handler' );
function myplugin_add_item_handler() {
check_ajax_referer( 'myplugin_add_item_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( array( 'message' => 'دسترسی غیرمجاز' ), 403 );
}
global $wpdb;
$title = isset( $_POST['title'] )
? sanitize_text_field( wp_unslash( $_POST['title'] ) )
: '';
if ( empty( $title ) ) {
wp_send_json_error( array( 'message' => 'عنوان الزامی است' ), 400 );
}
$data = array(
'user_id' => get_current_user_id(),
'title' => $title,
'status' => 'active',
'created_at' => current_time( 'mysql' ),
);
$format = array( '%d', '%s', '%s', '%s' );
$result = $wpdb->insert( $wpdb->prefix . 'myplugin_items', $data, $format );
if ( false === $result ) {
wp_send_json_error( array( 'message' => 'خطا در ذخیرهسازی' ), 500 );
}
wp_send_json_success( array(
'id' => $wpdb->insert_id,
'message' => 'با موفقیت ذخیره شد',
) );
}
راهنمای توابع استفادهشده در این بخش: check_ajax_referer، current_user_can، wp_send_json_success و wp_send_json_error.
طراحی جدول سفارشی
پیش از استفاده ازwpdb::insert، باید جدول سفارشی خود را طراحی کنید. الگوی استاندارد:
function myplugin_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'myplugin_events';
$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 DEFAULT 0,
action VARCHAR(50) NOT NULL,
message TEXT NULL,
ip_address VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY action (action),
KEY created_at (created_at)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
}
نکته مهم: استفاده از dbDelta امکان بهروزرسانی ساختار جدول در نسخههای بعدی افزونه را فراهم میکند. راهنمای register_activation_hook در صفحه register_activation_hook آمده است.
نکات امنیتی و اشتباهات رایج
اشتباه اول، نبود sanitize است. متدwpdb::insert از SQL Injection جلوگیری میکند اما از XSS محافظت نمیکند.
اشتباه دوم، نبود format است. اگر format پاس داده نشود، وردپرس همه داده را بهعنوان رشته درج میکند.
اشتباه سوم، اشتباه در ترتیب format است. ترتیب format باید دقیقاً با ترتیب آرایه data مطابقت داشته باشد.
اشتباه چهارم، نبود بررسی مقدار بازگشتی است. اگر متد false برگرداند، ممکن است خطای خاموش رخ داده باشد.
اشتباه پنجم، نبود لاگگیری است. در محیط توسعه، خطاهای درج باید لاگ شوند.
اشتباه ششم، نبود بررسی current_user_can در درج داده است. اگر درج از فرم یا AJAX انجام میشود، بررسی دسترسی الزامی است.
اشتباه هفتم، نبود بررسی nonce است. برای جلوگیری از CSRF، بررسی nonce ضروری است. راهنمای این تابع در صفحه wp_verify_nonce آمده است.
اشتباه هشتم، استفاده از نام جدول بدون prefix است. همیشه از $wpdb->prefix استفاده کنید تا با نصبهای مختلف وردپرس سازگار باشد.
تحلیل فنی پیشرفته
در نگاه مهندسی، متدwpdb::insert() یک نقطه معماری در لایه Data Access Object است که بر چند جنبه از سیستم اثر میگذارد. لایه اول لایه Prepared Statement است. این متد بهصورت خودکار پارامترها را escape و کوئری را آماده میکند.
لایه دوم لایه Type Binding است. پارامتر format امکان تعریف نوع دقیق هر ستون را فراهم میکند. این رویکرد از Type Juggling در PHP جلوگیری میکند.
لایه سوم لایه Atomicity است. درج یک رکورد در MySQL یک عملیات اتمیک است. اگر خطا رخ دهد، هیچ داده ناقصی ذخیره نمیشود.
لایه چهارم لایه Performance است. متد wpdb::insert از یک کوئری INSERT استفاده میکند که بهینه است. برای درج انبوه، بهتر است از INSERT INTO ... VALUES (multiple rows) استفاده کنید.
لایه پنجم لایه Observability است. مقدار بازگشتی و $wpdb->last_error امکان رهگیری خطاها را فراهم میکنند.
لایه ششم لایه Schema Evolution است. استفاده از dbDelta امکان بهروزرسانی ساختار جدول را بدون از دست دادن داده فراهم میکند.
لایه هفتم لایه Multisite است. در شبکههای Multisite، هر سایت prefix جدول مستقل دارد.
لایه هشتم لایه Testing است. تستهای واحد باید سناریوهای درج موفق، درج ناموفق، داده خالی و نوع داده اشتباه را پوشش دهند.
مفاهیم پایهای Prepared Statement در Prepared statement در ویکیپدیا توضیح داده شده است.
برای مطالعه بیشتر روی متدهای مرتبط، میتوانید به راهنمای wpdb::update، راهنمای wpdb::delete، راهنمای wpdb::prepare، راهنمای wpdb::get_results، راهنمای register_activation_hook، راهنمای current_user_can و راهنمای wp_verify_nonce مراجعه کنید.
پرسشهای پرتکرار
تفاوتwpdb::insert و wpdb::query چیست؟ اولی با prepared statement امن است و دومی نیازمند escape دستی.
چطور شناسه رکورد درجشده را دریافت کنیم؟ با $wpdb->insert_id.
اگر format را پاس ندهیم، چه اتفاقی میافتد؟ وردپرس همه داده را بهعنوان رشته درج میکند.
آیا این متد از XSS جلوگیری میکند؟ خیر، فقط از SQL Injection.
آیا میتوان چند رکورد را در یک فراخوانی درج کرد؟ خیر، برای درج انبوه باید از کوئری مستقیم استفاده کنید.
ادامه مسیر
متدwpdb::insert() ابزار استاندارد وردپرس برای درج امن داده در جداول سفارشی است. استفاده درست از آن یعنی sanitize داده پیش از درج، تعریف صریح format، بررسی مقدار بازگشتی و لاگگیری خطاها. اشتباههای کوچک در این متد اغلب به ذخیره نادرست داده یا حفرههای امنیتی منجر میشوند.
اگر این متد را در پروژهای واقعی به کار بردهاید و رفتار غیرمنتظرهای دیدهاید — بهخصوص در سناریوهای Multisite یا درج انبوه — تجربهتان میتواند راهگشای دیگران باشد. کدام بخش بیشترین زمان را از شما گرفت؟ دیدگاه خود را بنویسید.