📊 1 مشاهدة
- تحرير مساحة عبر حذف الملفات المؤقتة والنسخ الاحتياطية القديمة والـ logs.
- تفعيل وضع صيانة مؤقت لمدة 24 ساعة لتجنب مشاكل الدلائل.
- مراقبة المساحة وإرسال تنبيهات.
الكود التالي يقوم بهذه المهام بشكل آمن.
1) الملف الرئيسي: wp-content/mu-plugins/disk-space-manager.php
يُنشأ داخل
mu-pluginsليعمل تلقائياً دون تفعيل.
php
<?php
/**
* Plugin Name: Disk Space Manager (24h Temporary Boost)
* Description: تحرير مساحة القرص مؤقتاً لمدة 24 ساعة عبر حذف الملفات المؤقتة والنسخ القديمة، مع وضع صيانة خفيف.
* Version: 1.0.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // منع الوصول المباشر
}
class WP_Disk_Space_Manager {
/** مفتاح الخيارات في قاعدة البيانات */
const OPTION_KEY = 'wp_dsm_state';
/** مدة التفعيل: 24 ساعة بالثواني */
const DURATION = DAY_IN_SECONDS;
public function __construct() {
// جدولة الحدث اليومي للتنظيف
add_action( 'wp_dsm_daily_cleanup', [ $this, 'run_cleanup' ] );
// Cron للتأكد من انتهاء الصلاحية وإيقاف الوضع المؤقت
add_action( 'wp_dsm_expire_check', [ $this, 'check_expiration' ] );
// لوحة تحكم بسيطة في الأدوات
add_action( 'admin_menu', [ $this, 'add_admin_page' ] );
// معالجة زر التفعيل
add_action( 'admin_post_wp_dsm_activate', [ $this, 'handle_activate' ] );
add_action( 'admin_post_wp_dsm_deactivate', [ $this, 'handle_deactivate' ] );
}
/**
* تفعيل وضع "زيادة المساحة المؤقتة" لمدة 24 ساعة
*/
public function activate_temporary_boost() {
$state = [
'active' => true,
'started_at' => time(),
'expires_at' => time() + self::DURATION,
];
update_option( self::OPTION_KEY, $state, false );
// جدولة التنظيف الفوري
$this->run_cleanup();
// جدولة فحص الانتهاء بعد 24 ساعة
if ( ! wp_next_scheduled( 'wp_dsm_expire_check' ) ) {
wp_schedule_single_event( time() + self::DURATION, 'wp_dsm_expire_check' );
}
// جدولة التنظيف اليومي إن لم يكن مجدولاً
if ( ! wp_next_scheduled( 'wp_dsm_daily_cleanup' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wp_dsm_daily_cleanup' );
}
}
/**
* إيقاف الوضع المؤقت
*/
public function deactivate_temporary_boost() {
$state = get_option( self::OPTION_KEY, [] );
$state['active'] = false;
update_option( self::OPTION_KEY, $state, false );
wp_clear_scheduled_hook( 'wp_dsm_expire_check' );
}
/**
* التحقق من انتهاء المدة وإيقاف الوضع تلقائياً
*/
public function check_expiration() {
$state = get_option( self::OPTION_KEY, [] );
if ( ! empty( $state['active'] ) && time() >= (int) $state['expires_at'] ) {
$this->deactivate_temporary_boost();
$this->log( 'انتهت مدة الـ 24 ساعة، تم إيقاف الوضع المؤقت.' );
}
}
/**
* عملية التنظيف الأساسية لتحرير المساحة
*/
public function run_cleanup() {
$freed = 0;
// 1) حذف الملفات المؤقتة في wp-content/uploads/temp
$freed += $this->delete_directory_contents(
WP_CONTENT_DIR . '/uploads/temp',
DAY_IN_SECONDS // أقدم من يوم
);
// 2) حذف ملفات الـ log القديمة
$freed += $this->delete_old_files( WP_CONTENT_DIR . '/logs', 7 * DAY_IN_SECONDS );
// 3) حذف النسخ الاحتياطية القديمة (older than 3 days)
$freed += $this->delete_old_files( WP_CONTENT_DIR . '/backups', 3 * DAY_IN_SECONDS );
// 4) تنظيف transients المنتهية في قاعدة البيانات
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '\_transient\_timeout\_%'
AND option_value < UNIX_TIMESTAMP()"
);
// 5) حذف الملفات المؤقتة للنظام داخل wp-content (إن وُجدت)
$freed += $this->delete_old_files( WP_CONTENT_DIR, 2 * DAY_IN_SECONDS, [ 'tmp', 'bak', 'old' ] );
$this->log( sprintf( 'تم تحرير ما يقارب %s ميجابايت.', round( $freed / MB_IN_BYTES, 2 ) ) );
return $freed;
}
/**
* حذف محتويات مجلد أقدم من مدة معينة
*/
private function delete_directory_contents( $dir, $age = 0 ) {
if ( ! is_dir( $dir ) ) {
return 0;
}
$freed = 0;
$items = @scandir( $dir );
if ( ! $items ) {
return 0;
}
foreach ( $items as $item ) {
if ( $item === '.' || $item === '..' ) {
continue;
}
$path = $dir . '/' . $item;
if ( is_file( $path ) && ( time() - filemtime( $path ) ) > $age ) {
$freed += filesize( $path );
@unlink( $path );
} elseif ( is_dir( $path ) ) {
$freed += $this->delete_directory_contents( $path, $age );
@rmdir( $path );
}
}
return $freed;
}
/**
* حذف ملفات بامتدادات معينة أقدم من مدة
*/
private function delete_old_files( $dir, $age, $extensions = [ 'log', 'tmp', 'bak', 'zip', 'gz', 'sql' ] ) {
if ( ! is_dir( $dir ) ) {
return 0;
}
$freed = 0;
foreach ( glob( $dir . '/*' ) as $file ) {
if ( ! is_file( $file ) ) {
continue;
}
$ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
if ( in_array( $ext, $extensions, true ) && ( time() - filemtime( $file ) ) > $age ) {
$freed += filesize( $file );
@unlink( $file );
}
}
return $freed;
}
/**
* تسجيل الحدث في ملف Log خاص
*/
private function log( $message ) {
$log_file = WP_CONTENT_DIR . '/dsm.log';
@file_put_contents(
$log_file,
'[' . current_time( 'mysql' ) . '] ' . $message . PHP_EOL,
FILE_APPEND
);
}
/**
* الحصول على معلومات المساحة الحرة
*/
public function get_disk_info() {
$free = @disk_free_space( ABSPATH );
$total = @disk_total_space( ABSPATH );
return [
'free' => $free,
'total' => $total,
'used' => $total - $free,
'percent' => $total > 0 ? round( ( ( $total - $free ) / $total ) * 100, 2 ) : 0,
'readable' => size_format( $free ) . ' حر من ' . size_format( $total ),
];
}
/**
* إضافة صفحة إدارية
*/
public function add_admin_page() {
add_management_page(
'Disk Space Manager',
'Disk Space Manager',
'manage_options',
'wp-disk-space-manager',
[ $this, 'render_admin_page' ]
);
}
/**
* واجهة الصفحة الإدارية
*/
public function render_admin_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$state = get_option( self::OPTION_KEY, [] );
$info = $this->get_disk_info();
$active = ! empty( $state['active'] );
$remaining = $active ? max( 0, (int) $state['expires_at'] - time() ) : 0;
?>
<div class="wrap">
<h1>Disk Space Manager</h1>
<h2>حالة القرص</h2>
<p><strong>المساحة:</strong> <?php echo esc_html( $info['readable'] ); ?></p>
<p><strong>نسبة الاستخدام:</strong> <?php echo esc_html( $info['percent'] ); ?>%</p>
<h2>الوضع المؤقت (24 ساعة)</h2>
<?php if ( $active ) : ?>
<p style="color:green;">✅ مفعّل — متبقٍ: <?php echo esc_html( human_time_diff( time(), $state['expires_at'] ) ); ?></p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'wp_dsm_deactivate' ); ?>
<input type="hidden" name="action" value="wp_dsm_deactivate">
<button class="button">إيقاف الآن</button>
</form>
<?php else : ?>
<p style="color:#a00;">غير مفعّل</p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'wp_dsm_activate' ); ?>
<input type="hidden" name="action" value="wp_dsm_activate">
<button class="button button-primary">تفعيل لمدة 24 ساعة</button>
</form>
<?php endif; ?>
<h2>تشغيل التنظيف يدوياً</h2>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'wp_dsm_activate' ); ?>
<input type="hidden" name="action" value="wp_dsm_activate">
<button class="button">تنظيف + تفعيل</button>
</form>
</div>
<?php
}
/**
* معالجة زر التفعيل
*/
public function handle_activate() {
if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( 'wp_dsm_activate' ) ) {
wp_die( 'غير مصرح.' );
}
$this->activate_temporary_boost();
wp_safe_redirect( admin_url( 'tools.php?page=wp-disk-space-manager&activated=1' ) );
exit;
}
/**
* معالجة زر الإيقاف
*/
public function handle_deactivate() {
if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( 'wp_dsm_deactivate' ) ) {
wp_die( 'غير مصرح.' );
}
$this->deactivate_temporary_boost();
wp_safe_redirect( admin_url( 'tools.php?page=wp-disk-space-manager&deactivated=1' ) );
exit;
}
}
// تهيئة الكلاس
new WP_Disk_Space_Manager();
2) شرح الكود خطوة بخطوة
🔹 الإعدادات الأساسية
OPTION_KEY: اسم الخيار في جدولwp_optionsلتخزين حالة الوضع المؤقت.DURATION:DAY_IN_SECONDS = 86400أي 24 ساعة.
🔹 التفعيل (activate_temporary_boost)
- يخزّن وقت البدء ووقت الانتهاء.
- يشغّل التنظيف فوراً.
- يجدول حدث
wp_dsm_expire_checkليعمل تلقائياً بعد 24 ساعة لإيقاف الوضع. - يجدول تنظيف يومي متكرر.
🔹 التنظيف (run_cleanup) — جوهر توفير المساحة
| العملية | الموقع | العمر المحذوف |
|---|---|---|
| ملفات مؤقتة | wp-content/uploads/temp | أقدم من يوم |
| Logs | wp-content/logs | أقدم من 7 أيام |
| نسخ احتياطية | wp-content/backups | أقدم من 3 أيام |
| Transients منتهية | قاعدة البيانات | فوراً |
ملفات .tmp/.bak/.old | wp-content | أقدم من يومين |
🔹 Cron Events
wp_dsm_daily_cleanup: تنظيف يومي تلقائي.wp_dsm_expire_check: إيقاف الوضع المؤقت عند انتهاء 24 ساعة.
🔹 الأمان
- التحقق من
ABSPATHلمنع الوصول المباشر. check_admin_referer+current_user_canلحماية النماذج.- استخدام
wp_safe_redirectوesc_htmlوesc_url.
🔹 الواجهة الإدارية
متاحة في: الأدوات → Disk Space Manager
- عرض المساحة الحرة/الإجمالية والنسبة.
- زر تفعيل/إيقاف الوضع المؤقت.
- مؤقت تنازلي للوقت المتبقي.
3) كود بديل: كرون خارجي مستقل (اختياري)
إذا أردت تشغيل التنظيف دون الدخول للوحة التحكم، أضف إلى wp-config.php:
php
define( 'DISABLE_WP_CRON', false ); // للسماح بالكرون الطبيعي
ثم أضف مهمة Cron في السيرفر:
bash
# كل 6 ساعات 0 */6 * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
4) ملاحظات مهمة
| النقطة | التفصيل |
|---|---|
| المساحة الحقيقية | إن لم تكفِ المساحة، يجب ترقية الباقة لدى الاستضافة — لا يمكن تجاوز حد القرص برمجياً. |
| الأمان | احتفظ بنسخة احتياطية قبل تفعيل الحذف التلقائي. |
| الصلاحيات | تأكد أن مستخدم PHP يملك صلاحية الكتابة على المجلدات. |
| الاختبار | جرّب أولاً على بيئة staging قبل الإنتاج. |
| القانون | حذف ملفات قد يخالف سياسة الاستضافة إن لم تكن مسؤولاً عنها. |
5) طريقة التثبيت
- أنشئ المجلد
wp-content/mu-plugins/إن لم يكن موجوداً. - احفظ الكود في الملف:
wp-content/mu-plugins/disk-space-manager.php. - تأكد من صلاحيات الملف
644. - اذهب إلى الأدوات → Disk Space Manager واضغط “تفعيل لمدة 24 ساعة”.
- راقب الملف
wp-content/dsm.logلمتابعة العمليات.
هل تريد أن أضيف:
- إشعار بريدي عند انخفاض المساحة تحت نسبة معينة؟
- حذف الصور المصغرة (thumbnails) غير المستخدمة؟
- تنظيف
wp_optionsمن الصفوف المهملة (orphaned rows)؟
النسخة الموسّعة: Disk Space Manager Pro
سأضيف لك الآن الطبعة الاحترافية الكاملة التي تتضمن:
- إشعارات بريدية ذكية عند تجاوز عتبات المساحة
- حذف الصور المصغّرة غير المستخدمة (Orphan Thumbnails)
- تنظيف صفوف
wp_optionsالمهملة (Orphaned Rows) - تنظيف
wp_postmetaوwp_commentmetaالمهملة - ضغط الصور الكبيرة تلقائياً
- تصدير تقرير CSV
- حماية ضد حلقة الحذف اللانهائية
- سجل مفصّل مع إعادة تدوير (Log Rotation)
- دعم WP-CLI
- REST API Endpoint
- Widget في لوحة القيادة
- Cron متعدد المهام
📁 هيكل الملفات
text
wp-content/mu-plugins/disk-space-manager/
├── disk-space-manager.php # الملف الرئيسي
├── includes/
│ ├── class-dsm-core.php # المحرك الأساسي
│ ├── class-dsm-cleanup.php # عمليات التنظيف
│ ├── class-dsm-notifier.php # الإشعارات
│ ├── class-dsm-db-cleaner.php # تنظيف قاعدة البيانات
│ ├── class-dsm-media-cleaner.php # تنظيف الوسائط
│ ├── class-dsm-logger.php # السجل
│ ├── class-dsm-admin.php # الواجهة الإدارية
│ ├── class-dsm-cli.php # WP-CLI
│ └── class-dsm-rest.php # REST API
└── assets/
├── admin.css
└── admin.js
1) الملف الرئيسي: disk-space-manager.php
php
<?php
/**
* Plugin Name: Disk Space Manager Pro
* Description: إدارة متقدمة لمساحة القرص — تنظيف تلقائي، إشعارات، WP-CLI، REST API، وزيادة مؤقتة لمدة 24 ساعة.
* Version: 2.0.0
* Author: Your Name
* Text Domain: dsm
* Requires PHP: 7.4
* Requires at least: 5.8
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/** ثوابت المسارات */
define( 'DSM_VERSION', '2.0.0' );
define( 'DSM_PATH', __DIR__ );
define( 'DSM_URL', content_url( 'mu-plugins/disk-space-manager' ) );
define( 'DSM_LOG_DIR', WP_CONTENT_DIR . '/dsm-logs' );
/** تحميل الكلاسات */
require_once DSM_PATH . '/includes/class-dsm-logger.php';
require_once DSM_PATH . '/includes/class-dsm-cleanup.php';
require_once DSM_PATH . '/includes/class-dsm-db-cleaner.php';
require_once DSM_PATH . '/includes/class-dsm-media-cleaner.php';
require_once DSM_PATH . '/includes/class-dsm-notifier.php';
require_once DSM_PATH . '/includes/class-dsm-core.php';
require_once DSM_PATH . '/includes/class-dsm-admin.php';
require_once DSM_PATH . '/includes/class-dsm-rest.php';
if ( defined( 'WP_CLI' ) && WP_CLI ) {
require_once DSM_PATH . '/includes/class-dsm-cli.php';
}
/** التهيئة */
DSM_Core::instance();
2) السجل: class-dsm-logger.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Logger {
const MAX_LOG_SIZE = 2 * MB_IN_BYTES; // 2 ميجا
const KEEP_FILES = 5; // نحتفظ بـ 5 ملفات مدوّرة
/** كتابة سطر في السجل */
public static function log( $message, $level = 'info' ) {
if ( ! is_dir( DSM_LOG_DIR ) ) {
wp_mkdir_p( DSM_LOG_DIR );
}
self::rotate_if_needed();
$line = sprintf(
"[%s] [%s] %s\n",
current_time( 'mysql' ),
strtoupper( $level ),
is_array( $message ) || is_object( $message )
? wp_json_encode( $message )
: $message
);
@file_put_contents( self::current_file(), $line, FILE_APPEND | LOCK_EX );
}
/** الملف الحالي */
public static function current_file() {
return DSM_LOG_DIR . '/dsm.log';
}
/** تدوير السجل عند تجاوز الحجم */
private static function rotate_if_needed() {
$file = self::current_file();
if ( ! file_exists( $file ) || filesize( $file ) < self::MAX_LOG_SIZE ) {
return;
}
// تدوير الأسماء
for ( $i = self::KEEP_FILES - 1; $i >= 1; $i-- ) {
$old = DSM_LOG_DIR . "/dsm.{$i}.log";
$new = DSM_LOG_DIR . '/dsm.' . ( $i + 1 ) . '.log';
if ( file_exists( $old ) ) {
@rename( $old, $new );
}
}
@rename( $file, DSM_LOG_DIR . '/dsm.1.log' );
}
/** قراءة آخر N سطر */
public static function tail( $lines = 100 ) {
$file = self::current_file();
if ( ! file_exists( $file ) ) {
return [];
}
$content = @file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
return array_slice( $content, -$lines );
}
/** تفريغ السجل */
public static function clear() {
@unlink( self::current_file() );
foreach ( glob( DSM_LOG_DIR . '/dsm.*.log' ) as $f ) {
@unlink( $f );
}
}
}
3) المحرك الأساسي: class-dsm-core.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Core {
const OPTION_KEY = 'dsm_state';
const OPTION_SCHED = 'dsm_schedules';
const DURATION = DAY_IN_SECONDS;
private static $instance = null;
/** Singleton */
public static function instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
// Cron Events
add_action( 'dsm_hourly_cleanup', [ $this, 'run_cleanup' ] );
add_action( 'dsm_expire_check', [ $this, 'check_expiration' ] );
add_action( 'dsm_disk_watch', [ 'DSM_Notifier', 'watch_disk' ] );
add_action( 'dsm_orphan_media_cleanup',[ 'DSM_Media_Cleaner', 'clean_orphan_thumbnails' ] );
add_action( 'dsm_db_cleanup', [ 'DSM_DB_Cleaner', 'run_all' ] );
// جدولة عند التهيئة
add_action( 'init', [ $this, 'schedule_events' ] );
// لوحة القيادة (Widget)
add_action( 'wp_dashboard_setup', [ $this, 'register_dashboard_widget' ] );
}
/** جدولة كل الأحداث */
public function schedule_events() {
// تنظيف كل ساعة
if ( ! wp_next_scheduled( 'dsm_hourly_cleanup' ) ) {
wp_schedule_event( time() + 300, 'hourly', 'dsm_hourly_cleanup' );
}
// مراقبة القرص كل 15 دقيقة (نستخدم hourly لأبسط)
if ( ! wp_next_scheduled( 'dsm_disk_watch' ) ) {
wp_schedule_event( time() + 120, 'hourly', 'dsm_disk_watch' );
}
// حذف الصور المصغرة غير المستخدمة يومياً
if ( ! wp_next_scheduled( 'dsm_orphan_media_cleanup' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'dsm_orphan_media_cleanup' );
}
// تنظيف قاعدة البيانات أسبوعياً
if ( ! wp_next_scheduled( 'dsm_db_cleanup' ) ) {
wp_schedule_event( time() + 2 * HOUR_IN_SECONDS, 'weekly', 'dsm_db_cleanup' );
}
}
/** إلغاء الجدولة عند إلغاء التنشيط (لو أردت لاحقاً تحويله لـ plugin) */
public static function unschedule_events() {
foreach ( [ 'dsm_hourly_cleanup', 'dsm_disk_watch', 'dsm_orphan_media_cleanup', 'dsm_db_cleanup', 'dsm_expire_check' ] as $hook ) {
wp_clear_scheduled_hook( $hook );
}
}
/** تنشيط الوضع المؤقت 24 ساعة */
public function activate_temporary_boost( $reason = 'manual' ) {
$state = [
'active' => true,
'started_at' => time(),
'expires_at' => time() + self::DURATION,
'reason' => $reason,
];
update_option( self::OPTION_KEY, $state, false );
DSM_Logger::log( "تم تفعيل الوضع المؤقت (24h) — السبب: {$reason}" );
// تنظيف فوري
$this->run_cleanup( true );
// جدولة الفحص
wp_clear_scheduled_hook( 'dsm_expire_check' );
wp_schedule_single_event( time() + self::DURATION, 'dsm_expire_check' );
// إشعار
DSM_Notifier::send( 'تفعيل الوضع المؤقت', sprintf(
"تم تفعيل الوضع المؤقت لمدة 24 ساعة.\nالسبب: %s\nالمساحة الحرة: %s",
$reason,
$this->get_disk_info()['readable']
) );
return $state;
}
/** إلغاء الوضع المؤقت */
public function deactivate_temporary_boost() {
$state = get_option( self::OPTION_KEY, [] );
if ( empty( $state['active'] ) ) {
return;
}
$state['active'] = false;
update_option( self::OPTION_KEY, $state, false );
wp_clear_scheduled_hook( 'dsm_expire_check' );
DSM_Logger::log( 'تم إيقاف الوضع المؤقت.' );
}
/** فحص الانتهاء */
public function check_expiration() {
$state = get_option( self::OPTION_KEY, [] );
if ( ! empty( $state['active'] ) && time() >= (int) $state['expires_at'] ) {
$this->deactivate_temporary_boost();
DSM_Notifier::send( 'انتهاء الوضع المؤقت', 'انتهت 24 ساعة وتم إيقاف الوضع المؤقت.' );
}
}
/** تشغيل التنظيف الكامل */
public function run_cleanup( $manual = false ) {
// منع التداخل
if ( get_transient( 'dsm_cleanup_lock' ) ) {
DSM_Logger::log( 'تم تخطي التنظيف — عملية قيد التنفيذ.', 'warning' );
return 0;
}
set_transient( 'dsm_cleanup_lock', 1, 10 * MINUTE_IN_SECONDS );
$start = microtime( true );
$total_freed = 0;
// 1. تنظيف الملفات
$total_freed += DSM_Cleanup::run_all();
// 2. تنظيف قاعدة البيانات (فقط إذا كان وضع مؤقت أو يدوي)
if ( $manual || $this->is_boost_active() ) {
DSM_DB_Cleaner::run_all();
}
$elapsed = round( microtime( true ) - $start, 2 );
DSM_Logger::log( sprintf(
'انتهى التنظيف: %s محرّرة، استغرق %ss',
size_format( $total_freed ),
$elapsed
) );
delete_transient( 'dsm_cleanup_lock' );
// تحديث معدل التنظيف
update_option( 'dsm_last_cleanup', [
'time' => time(),
'freed' => $total_freed,
'elapsed'=> $elapsed,
], false );
return $total_freed;
}
/** هل الوضع المؤقت نشط؟ */
public function is_boost_active() {
$state = get_option( self::OPTION_KEY, [] );
return ! empty( $state['active'] ) && time() < (int) $state['expires_at'];
}
/** معلومات القرص */
public function get_disk_info() {
$free = @disk_free_space( ABSPATH );
$total = @disk_total_space( ABSPATH );
$used = ( $total && $free ) ? ( $total - $free ) : 0;
$pct = $total > 0 ? round( ( $used / $total ) * 100, 2 ) : 0;
return [
'free' => $free,
'total' => $total,
'used' => $used,
'percent' => $pct,
'readable' => size_format( $free ) . ' حرة من ' . size_format( $total ),
'critical' => $pct >= 90,
'warning' => $pct >= 75 && $pct < 90,
];
}
/** إحصاءات قاعدة البيانات */
public function get_db_stats() {
global $wpdb;
return [
'posts' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" ),
'postmeta' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->postmeta}" ),
'options' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->options}" ),
'transients' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'" ),
'comments' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments}" ),
'commentmeta' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->commentmeta}" ),
'size_mb' => round( ( $wpdb->get_var( "SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = DATABASE()" ) ?: 0 ) / MB_IN_BYTES, 2 ),
];
}
/** Widget لوحة القيادة */
public function register_dashboard_widget() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_add_dashboard_widget(
'dsm_dashboard_widget',
'💾 Disk Space Manager',
[ $this, 'render_dashboard_widget' ]
);
}
public function render_dashboard_widget() {
$info = $this->get_disk_info();
$state = get_option( self::OPTION_KEY, [] );
$last = get_option( 'dsm_last_cleanup', [] );
?>
<ul style="list-style:none;padding:0;margin:0;">
<li>📊 <strong>الاستخدام:</strong> <?php echo esc_html( $info['percent'] ); ?>%</li>
<li>💿 <strong><?php echo esc_html( $info['readable'] ); ?></strong></li>
<li>
⚡ <strong>الوضع المؤقت:</strong>
<?php if ( ! empty( $state['active'] ) ) : ?>
<span style="color:green;">مفعّل — متبقٍ <?php echo esc_html( human_time_diff( time(), $state['expires_at'] ) ); ?></span>
<?php else : ?>
<span style="color:#a00;">غير مفعّل</span>
<?php endif; ?>
</li>
<?php if ( $last ) : ?>
<li>🧹 <strong>آخر تنظيف:</strong> <?php echo esc_html( human_time_diff( $last['time'] ) ); ?> — حرّر <?php echo esc_html( size_format( $last['freed'] ) ); ?></li>
<?php endif; ?>
</ul>
<p>
<a class="button button-primary" href="<?php echo esc_url( admin_url( 'tools.php?page=dsm-dashboard' ) ); ?>">فتح اللوحة</a>
<a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=dsm_quick_clean' ), 'dsm_quick_clean' ) ); ?>">تنظيف سريع</a>
</p>
<?php
}
}
4) التنظيف: class-dsm-cleanup.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Cleanup {
public static function run_all() {
$freed = 0;
$freed += self::temp_uploads();
$freed += self::old_logs();
$freed += self::old_backups();
$freed += self::system_temp_files();
$freed += self::cache_files();
$freed += self::expired_transients_db();
return $freed;
}
/** ملفات التحميل المؤقتة */
private static function temp_uploads() {
return self::purge_dir( WP_CONTENT_DIR . '/uploads/temp', DAY_IN_SECONDS );
}
/** logs أقدم من 7 أيام */
private static function old_logs() {
$freed = self::purge_dir( WP_CONTENT_DIR . '/logs', 7 * DAY_IN_SECONDS );
$freed += self::purge_by_ext( WP_CONTENT_DIR, [ 'log' ], 14 * DAY_IN_SECONDS, 1 ); // مستوى 1 فقط
return $freed;
}
/** نسخ احتياطية أقدم من 3 أيام */
private static function old_backups() {
$dirs = [
WP_CONTENT_DIR . '/backups',
WP_CONTENT_DIR . '/updraft',
WP_CONTENT_DIR . '/ai1wm-backups',
];
$freed = 0;
foreach ( $dirs as $d ) {
$freed += self::purge_by_ext( $d, [ 'zip', 'gz', 'sql', 'tar', 'bak' ], 3 * DAY_IN_SECONDS );
}
return $freed;
}
/** ملفات النظام المؤقتة في wp-content */
private static function system_temp_files() {
return self::purge_by_ext( WP_CONTENT_DIR, [ 'tmp', 'bak', 'old', 'swp' ], 2 * DAY_IN_SECONDS, 1 );
}
/** ملفات الكاش (WP Rocket، W3TC، وغيرها) */
private static function cache_files() {
$dirs = [
WP_CONTENT_DIR . '/cache',
WP_CONTENT_DIR . '/w3tc-config',
];
$freed = 0;
foreach ( $dirs as $d ) {
$freed += self::purge_dir( $d, 2 * DAY_IN_SECONDS );
}
return $freed;
}
/** Transients منتهية في قاعدة البيانات */
private static function expired_transients_db() {
global $wpdb;
$time = time();
$deleted = $wpdb->query( $wpdb->prepare(
"DELETE a, b FROM {$wpdb->options} a
LEFT JOIN {$wpdb->options} b ON b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 20))
WHERE a.option_name LIKE %s AND a.option_value < %d",
$wpdb->esc_like( '_transient_timeout_' ) . '%',
$time
) );
// احتياطي مبسط
$wpdb->query( $wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s AND option_value < %d",
$wpdb->esc_like( '_transient_timeout_' ) . '%',
$time
) );
DSM_Logger::log( "حذف Transients منتهية: {$deleted} صف." );
return 0; // لا نحسب حجم DB هنا
}
/** حذف كل محتوى مجلد أقدم من عمر محدد */
public static function purge_dir( $dir, $age = 0, $depth = 0, $max_depth = 5 ) {
if ( $depth > $max_depth || ! is_dir( $dir ) ) {
return 0;
}
$freed = 0;
$items = @scandir( $dir );
if ( ! $items ) {
return 0;
}
foreach ( $items as $item ) {
if ( $item === '.' || $item === '..' ) {
continue;
}
$path = $dir . '/' . $item;
// تجاهل ملفات مهمة
if ( in_array( $item, [ 'index.php', '.htaccess', 'advanced-cache.php', 'object-cache.php' ], true ) ) {
continue;
}
if ( is_file( $path ) && ( time() - filemtime( $path ) ) > $age ) {
$size = @filesize( $path );
if ( @unlink( $path ) ) {
$freed += $size;
}
} elseif ( is_dir( $path ) ) {
$freed += self::purge_dir( $path, $age, $depth + 1, $max_depth );
@rmdir( $path ); // يحذف فقط إن كان فارغاً
}
}
return $freed;
}
/** حذف ملفات بامتدادات أقدم من عمر محدد */
public static function purge_by_ext( $dir, $exts, $age, $depth = 0, $max_depth = 3 ) {
if ( $depth > $max_depth || ! is_dir( $dir ) ) {
return 0;
}
$freed = 0;
$items = @scandir( $dir );
if ( ! $items ) {
return 0;
}
foreach ( $items as $item ) {
if ( $item === '.' || $item === '..' ) {
continue;
}
$path = $dir . '/' . $item;
if ( is_dir( $path ) ) {
$freed += self::purge_by_ext( $path, $exts, $age, $depth + 1, $max_depth );
continue;
}
$ext = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );
if ( in_array( $ext, $exts, true ) && ( time() - filemtime( $path ) ) > $age ) {
$size = @filesize( $path );
if ( @unlink( $path ) ) {
$freed += $size;
}
}
}
return $freed;
}
}
5) منظّف قاعدة البيانات: class-dsm-db-cleaner.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_DB_Cleaner {
public static function run_all() {
self::orphan_postmeta();
self::orphan_commentmeta();
self::orphan_term_relationships();
self::expired_transients();
self::expired_user_activation_keys();
self::auto_drafts();
self::trashed_posts();
self::spam_comments();
self::trashed_comments();
self::optimize_tables();
}
/** صفوف postmeta يتيمة */
private static function orphan_postmeta() {
global $wpdb;
$n = $wpdb->query(
"DELETE pm FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE p.ID IS NULL"
);
DSM_Logger::log( "حذف postmeta يتيمة: {$n}" );
}
/** صفوف commentmeta يتيمة */
private static function orphan_commentmeta() {
global $wpdb;
$n = $wpdb->query(
"DELETE cm FROM {$wpdb->commentmeta} cm
LEFT JOIN {$wpdb->comments} c ON c.comment_ID = cm.comment_id
WHERE c.comment_ID IS NULL"
);
DSM_Logger::log( "حذف commentmeta يتيمة: {$n}" );
}
/** علاقات التصنيفات اليتيمة */
private static function orphan_term_relationships() {
global $wpdb;
$n = $wpdb->query(
"DELETE tr FROM {$wpdb->term_relationships} tr
LEFT JOIN {$wpdb->posts} p ON p.ID = tr.object_id
WHERE p.ID IS NULL"
);
DSM_Logger::log( "حذف term_relationships يتيمة: {$n}" );
}
/** Transients منتهية */
private static function expired_transients() {
global $wpdb;
$time = time();
$n = $wpdb->query( $wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s AND option_value < %d",
$wpdb->esc_like( '_transient_timeout_' ) . '%',
$time
) );
DSM_Logger::log( "حذف Transients منتهية: {$n}" );
}
/** مفاتيح تنشيط المستخدمين المنتهية */
private static function expired_user_activation_keys() {
global $wpdb;
$n = $wpdb->query(
"DELETE FROM {$wpdb->usermeta}
WHERE meta_key = 'activation_key'
AND meta_value = ''"
);
if ( $n ) {
DSM_Logger::log( "حذف activation keys فارغة: {$n}" );
}
}
/** المسودات التلقائية القديمة */
private static function auto_drafts() {
global $wpdb;
$ids = $wpdb->get_col(
"SELECT ID FROM {$wpdb->posts}
WHERE post_status = 'auto-draft'
AND post_type = 'post'
AND DATEDIFF(NOW(), post_date) > 7"
);
foreach ( $ids as $id ) {
wp_delete_post( $id, true );
}
DSM_Logger::log( 'حذف auto-drafts: ' . count( $ids ) );
}
/** المقالات في المهملات */
private static function trashed_posts() {
global $wpdb;
$ids = $wpdb->get_col(
"SELECT ID FROM {$wpdb->posts}
WHERE post_status = 'trash'
AND DATEDIFF(NOW(), post_modified) > 30"
);
foreach ( $ids as $id ) {
wp_delete_post( $id, true );
}
DSM_Logger::log( 'حذف مقالات من المهملات: ' . count( $ids ) );
}
/** تعليقات Spam */
private static function spam_comments() {
global $wpdb;
$ids = $wpdb->get_col(
"SELECT comment_ID FROM {$wpdb->comments} WHERE comment_approved = 'spam'"
);
foreach ( $ids as $id ) {
wp_delete_comment( $id, true );
}
DSM_Logger::log( 'حذف تعليقات Spam: ' . count( $ids ) );
}
/** تعليقات في المهملات */
private static function trashed_comments() {
global $wpdb;
$ids = $wpdb->get_col(
"SELECT comment_ID FROM {$wpdb->comments} WHERE comment_approved = 'trash'"
);
foreach ( $ids as $id ) {
wp_delete_comment( $id, true );
}
DSM_Logger::log( 'حذف تعليقات من المهملات: ' . count( $ids ) );
}
/** تحسين الجداول */
private static function optimize_tables() {
global $wpdb;
$tables = $wpdb->get_col( 'SHOW TABLES' );
foreach ( $tables as $t ) {
$wpdb->query( "OPTIMIZE TABLE `{$t}`" );
}
DSM_Logger::log( 'تم تحسين جميع الجداول.' );
}
}
6) منظّف الوسائط: class-dsm-media-cleaner.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Media_Cleaner {
/** حذف الصور المصغّرة اليتيمة (بدون مرفق أب) */
public static function clean_orphan_thumbnails() {
global $wpdb;
// جلب جميع المرفقات التي لا تحتوي على parent موجود
$orphans = $wpdb->get_col(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type = 'attachment'
AND post_parent > 0
AND post_parent NOT IN (SELECT ID FROM {$wpdb->posts})"
);
$count = 0;
foreach ( $orphans as $id ) {
$meta = wp_get_attachment_metadata( $id );
if ( ! empty( $meta['sizes'] ) ) {
$base = wp_upload_dir();
$path = trailingslashit( $base['basedir'] ) . dirname( get_post_meta( $id, '_wp_attached_file', true ) );
foreach ( $meta['sizes'] as $size ) {
@unlink( $path . '/' . $size['file'] );
$count++;
}
}
wp_delete_attachment( $id, true );
}
DSM_Logger::log( "تنظيف صور مصغّرة يتيمة: {$count} ملف." );
}
/** حذف الصور المصغّرة غير المستخدمة في السجل (unregistered sizes) */
public static function clean_unregistered_thumbnails() {
$sizes = wp_get_registered_image_subsizes();
$upload = wp_upload_dir();
$count = 0;
$query = new WP_Query( [
'post_type' => 'attachment',
'post_mime_type' => 'image',
'posts_per_page' => 500,
'fields' => 'ids',
] );
foreach ( $query->posts as $id ) {
$meta = wp_get_attachment_metadata( $id );
if ( empty( $meta['sizes'] ) ) {
continue;
}
$base = trailingslashit( $upload['basedir'] ) . dirname( get_post_meta( $id, '_wp_attached_file', true ) );
foreach ( $meta['sizes'] as $size_name => $size_data ) {
if ( ! isset( $sizes[ $size_name ] ) ) {
@unlink( $base . '/' . $size_data['file'] );
unset( $meta['sizes'][ $size_name ] );
$count++;
}
}
wp_update_attachment_metadata( $id, $meta );
}
DSM_Logger::log( "حذف صور مصغّرة غير مسجّلة: {$count}" );
return $count;
}
/** حجم مجلد الرفع */
public static function get_uploads_size() {
$dir = wp_upload_dir();
return self::dir_size( $dir['basedir'] );
}
/** حساب حجم مجلد بشكل تعاودي */
public static function dir_size( $dir ) {
$size = 0;
if ( ! is_dir( $dir ) ) {
return 0;
}
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS )
);
foreach ( $it as $file ) {
if ( $file->isFile() ) {
$size += $file->getSize();
}
}
return $size;
}
/** أكبر 10 ملفات */
public static function get_largest_files( $limit = 10 ) {
$dir = wp_upload_dir();
$files = [];
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir['basedir'], FilesystemIterator::SKIP_DOTS )
);
foreach ( $it as $file ) {
if ( $file->isFile() ) {
$files[] = [ 'path' => $file->getPathname(), 'size' => $file->getSize() ];
}
}
usort( $files, fn( $a, $b ) => $b['size'] <=> $a['size'] );
return array_slice( $files, 0, $limit );
}
}
7) الإشعارات: class-dsm-notifier.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Notifier {
const THRESHOLDS = [ 75, 85, 90, 95 ];
/** إرسال بريد */
public static function send( $subject, $message ) {
$admin_email = get_option( 'admin_email' );
$emails = get_option( 'dsm_notify_emails', $admin_email );
$emails = array_filter( array_map( 'trim', explode( ',', $emails ) ) );
$site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
$full_subject = sprintf( '[%s] %s', $site, $subject );
foreach ( $emails as $to ) {
wp_mail(
$to,
$full_subject,
$message . "\n\n— Disk Space Manager Pro",
[ 'Content-Type: text/plain; charset=UTF-8' ]
);
}
}
/** مراقبة القرص وإرسال التنبيهات */
public static function watch_disk() {
$info = DSM_Core::instance()->get_disk_info();
if ( ! $info['total'] ) {
return;
}
$last_level = (int) get_option( 'dsm_last_alert_level', 0 );
$current_level = 0;
foreach ( self::THRESHOLDS as $t ) {
if ( $info['percent'] >= $t ) {
$current_level = $t;
}
}
// إرسال فقط إذا ارتفع المستوى
if ( $current_level > $last_level ) {
$subject = "تحذير: مساحة القرص {$info['percent']}%";
$message = "المساحة المستخدمة: {$info['percent']}%\n";
$message .= "المساحة الحرة: {$info['readable']}\n\n";
$message .= "تم تفعيل الوضع المؤقت تلقائياً لمدة 24 ساعة.";
self::send( $subject, $message );
// تفعيل وضع مؤقت تلقائياً إذا تجاوزنا 90%
if ( $current_level >= 90 ) {
DSM_Core::instance()->activate_temporary_boost( "تجاوز {$current_level}% تلقائياً" );
}
update_option( 'dsm_last_alert_level', $current_level, false );
}
// إعادة الضبط عند الانخفاض
if ( $info['percent'] < 70 && $last_level > 0 ) {
update_option( 'dsm_last_alert_level', 0, false );
self::send( 'تم استعادة المساحة', 'انخفض استخدام القرص تحت 70%.' );
}
}
/** اختبار الإرسال */
public static function test_email() {
self::send( 'اختبار Disk Space Manager', 'هذه رسالة اختبارية من DSM Pro.' );
}
}
8) الواجهة الإدارية: class-dsm-admin.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Admin {
public function __construct() {
add_action( 'admin_menu', [ $this, 'menu' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'assets' ] );
// معالجات الأزرار
add_action( 'admin_post_dsm_activate', [ $this, 'handle_activate' ] );
add_action( 'admin_post_dsm_deactivate', [ $this, 'handle_deactivate' ] );
add_action( 'admin_post_dsm_clean_now', [ $this, 'handle_clean' ] );
add_action( 'admin_post_dsm_quick_clean', [ $this, 'handle_clean' ] );
add_action( 'admin_post_dsm_save_settings',[ $this, 'handle_save_settings' ] );
add_action( 'admin_post_dsm_clear_log', [ $this, 'handle_clear_log' ] );
add_action( 'admin_post_dsm_export_csv', [ $this, 'handle_export_csv' ] );
}
public function menu() {
add_management_page(
'Disk Space Manager',
'💾 Disk Space',
'manage_options',
'dsm-dashboard',
[ $this, 'render' ]
);
}
public function assets( $hook ) {
if ( 'tools_page_dsm-dashboard' !== $hook ) {
return;
}
wp_enqueue_style( 'dsm-admin', DSM_URL . '/assets/admin.css', [], DSM_VERSION );
wp_enqueue_script( 'dsm-admin', DSM_URL . '/assets/admin.js', [ 'jquery' ], DSM_VERSION, true );
}
public function render() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$core = DSM_Core::instance();
$info = $core->get_disk_info();
$state = get_option( DSM_Core::OPTION_KEY, [] );
$stats = $core->get_db_stats();
$last = get_option( 'dsm_last_cleanup', [] );
$log = array_reverse( DSM_Logger::tail( 30 ) );
$active = ! empty( $state['active'] );
?>
<div class="wrap dsm-wrap">
<h1>💾 Disk Space Manager Pro</h1>
<?php if ( isset( $_GET['done'] ) ) : ?>
<div class="notice notice-success is-dismissible"><p>تمت العملية بنجاح.</p></div>
<?php endif; ?>
<!-- بطاقات المعلومات -->
<div class="dsm-cards">
<div class="dsm-card <?php echo $info['critical'] ? 'critical' : ( $info['warning'] ? 'warning' : '' ); ?>">
<div class="dsm-card-label">القرص</div>
<div class="dsm-card-value"><?php echo esc_html( $info['percent'] ); ?>%</div>
<div class="dsm-progress"><span style="width:<?php echo esc_attr( $info['percent'] ); ?>%"></span></div>
<div class="dsm-card-sub"><?php echo esc_html( $info['readable'] ); ?></div>
</div>
<div class="dsm-card">
<div class="dsm-card-label">قاعدة البيانات</div>
<div class="dsm-card-value"><?php echo esc_html( $stats['size_mb'] ); ?> MB</div>
<div class="dsm-card-sub">
<?php echo esc_html( number_format_i18n( $stats['posts'] ) ); ?> posts —
<?php echo esc_html( number_format_i18n( $stats['postmeta'] ) ); ?> meta
</div>
</div>
<div class="dsm-card">
<div class="dsm-card-label">الوسائط</div>
<div class="dsm-card-value"><?php echo esc_html( size_format( DSM_Media_Cleaner::get_uploads_size() ) ); ?></div>
<div class="dsm-card-sub">wp-content/uploads</div>
</div>
<div class="dsm-card <?php echo $active ? 'ok' : ''; ?>">
<div class="dsm-card-label">الوضع المؤقت</div>
<div class="dsm-card-value"><?php echo $active ? 'نشط' : 'معطّل'; ?></div>
<?php if ( $active ) : ?>
<div class="dsm-card-sub">متبقٍ <?php echo esc_html( human_time_diff( time(), $state['expires_at'] ) ); ?></div>
<?php endif; ?>
</div>
</div>
<!-- أزرار العمل -->
<div class="dsm-actions">
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'dsm_clean_now' ); ?>
<input type="hidden" name="action" value="dsm_clean_now">
<button class="button button-primary">🧹 تنظيف الآن</button>
</form>
<?php if ( $active ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'dsm_deactivate' ); ?>
<input type="hidden" name="action" value="dsm_deactivate">
<button class="button">⏹ إيقاف الوضع المؤقت</button>
</form>
<?php else : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'dsm_activate' ); ?>
<input type="hidden" name="action" value="dsm_activate">
<button class="button">⚡ تفعيل 24 ساعة</button>
</form>
<?php endif; ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'dsm_export_csv' ); ?>
<input type="hidden" name="action" value="dsm_export_csv">
<button class="button">📥 تصدير CSV</button>
</form>
</div>
<!-- إعدادات -->
<h2>الإعدادات</h2>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'dsm_save_settings' ); ?>
<input type="hidden" name="action" value="dsm_save_settings">
<table class="form-table">
<tr>
<th>بريد التنبيهات</th>
<td>
<input type="text" name="dsm_notify_emails" class="regular-text"
value="<?php echo esc_attr( get_option( 'dsm_notify_emails', get_option( 'admin_email' ) ) ); ?>">
<p class="description">مفصولة بفواصل.</p>
</td>
</tr>
<tr>
<th>التنظيف التلقائي</th>
<td>
<label>
<input type="checkbox" name="dsm_auto_clean" value="1"
<?php checked( get_option( 'dsm_auto_clean', 1 ), 1 ); ?>>
تشغيل التنظيف التلقائي كل ساعة
</label>
</td>
</tr>
</table>
<?php submit_button( 'حفظ' ); ?>
</form>
<!-- أكبر الملفات -->
<h2>أكبر 10 ملفات</h2>
<table class="widefat striped">
<thead><tr><th>الملف</th><th>الحجم</th></tr></thead>
<tbody>
<?php foreach ( DSM_Media_Cleaner::get_largest_files( 10 ) as $f ) : ?>
<tr>
<td><code><?php echo esc_html( str_replace( ABSPATH, '', $f['path'] ) ); ?></code></td>
<td><?php echo esc_html( size_format( $f['size'] ) ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- السجل -->
<h2>آخر 30 حدث
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'dsm_clear_log' ); ?>
<input type="hidden" name="action" value="dsm_clear_log">
<button class="button button-small">تفريغ</button>
</form>
</h2>
<pre class="dsm-log"><?php echo esc_html( implode( "\n", $log ) ); ?></pre>
</div>
<?php
}
// ===== المعالجات =====
private function check( $nonce_action ) {
if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( $nonce_action ) ) {
wp_die( 'غير مصرح.' );
}
}
private function back() {
wp_safe_redirect( admin_url( 'tools.php?page=dsm-dashboard&done=1' ) );
exit;
}
public function handle_activate() {
$this->check( 'dsm_activate' );
DSM_Core::instance()->activate_temporary_boost( 'يدوي' );
$this->back();
}
public function handle_deactivate() {
$this->check( 'dsm_deactivate' );
DSM_Core::instance()->deactivate_temporary_boost();
$this->back();
}
public function handle_clean() {
$this->check( 'dsm_clean_now' );
DSM_Core::instance()->run_cleanup( true );
$this->back();
}
public function handle_save_settings() {
$this->check( 'dsm_save_settings' );
update_option( 'dsm_notify_emails', sanitize_text_field( $_POST['dsm_notify_emails'] ?? '' ), false );
update_option( 'dsm_auto_clean', isset( $_POST['dsm_auto_clean'] ) ? 1 : 0, false );
$this->back();
}
public function handle_clear_log() {
$this->check( 'dsm_clear_log' );
DSM_Logger::clear();
$this->back();
}
public function handle_export_csv() {
$this->check( 'dsm_export_csv' );
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="dsm-report-' . date( 'Y-m-d' ) . '.csv"' );
$out = fopen( 'php://output', 'w' );
fputs( $out, "\xEF\xBB\xBF" ); // BOM لدعم Excel العربي
fputcsv( $out, [ 'Item', 'Value' ] );
$info = DSM_Core::instance()->get_disk_info();
fputcsv( $out, [ 'Free', $info['free'] ] );
fputcsv( $out, [ 'Total', $info['total'] ] );
fputcsv( $out, [ 'Percent', $info['percent'] ] );
foreach ( DSM_Core::instance()->get_db_stats() as $k => $v ) {
fputcsv( $out, [ $k, $v ] );
}
fclose( $out );
exit;
}
}
new DSM_Admin();
9) WP-CLI: class-dsm-cli.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_CLI {
/**
* معلومات القرص
*
* ## EXAMPLES
* wp dsm info
*/
public function info() {
$info = DSM_Core::instance()->get_disk_info();
WP_CLI::line( '📊 استخدام القرص: ' . $info['percent'] . '%' );
WP_CLI::line( '💿 ' . $info['readable'] );
}
/**
* تفعيل الوضع المؤقت 24 ساعة
*/
public function boost() {
DSM_Core::instance()->activate_temporary_boost( 'WP-CLI' );
WP_CLI::success( 'تم تفعيل الوضع المؤقت لمدة 24 ساعة.' );
}
/**
* إيقاف الوضع المؤقت
*/
public function unboost() {
DSM_Core::instance()->deactivate_temporary_boost();
WP_CLI::success( 'تم إيقاف الوضع المؤقت.' );
}
/**
* تنظيف فوري
*/
public function clean() {
$freed = DSM_Core::instance()->run_cleanup( true );
WP_CLI::success( 'تم تحرير ' . size_format( $freed ) );
}
/**
* عرض السجل
*
* [--lines=<n>]
*/
public function log( $args, $assoc ) {
$lines = isset( $assoc['lines'] ) ? (int) $assoc['lines'] : 50;
foreach ( DSM_Logger::tail( $lines ) as $l ) {
WP_CLI::line( $l );
}
}
/**
* حذف الصور المصغّرة غير المسجّلة
*/
public function clean_thumbnails() {
$n = DSM_Media_Cleaner::clean_unregistered_thumbnails();
WP_CLI::success( "تم حذف {$n} صورة مصغّرة." );
}
}
WP_CLI::add_command( 'dsm', 'DSM_CLI' );
الاستخدام:
bash
wp dsm info wp dsm boost wp dsm clean wp dsm log --lines=100 wp dsm clean_thumbnails
10) REST API: class-dsm-rest.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_REST {
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register' ] );
}
public function register() {
register_rest_route( 'dsm/v1', '/status', [
'methods' => 'GET',
'callback' => [ $this, 'status' ],
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
] );
register_rest_route( 'dsm/v1', '/clean', [
'methods' => 'POST',
'callback' => [ $this, 'clean' ],
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
] );
register_rest_route( 'dsm/v1', '/boost', [
'methods' => 'POST',
'callback' => [ $this, 'boost' ],
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
] );
}
public function status() {
return [
'disk' => DSM_Core::instance()->get_disk_info(),
'db' => DSM_Core::instance()->get_db_stats(),
'boost'=> get_option( DSM_Core::OPTION_KEY, [] ),
];
}
public function clean() {
$freed = DSM_Core::instance()->run_cleanup( true );
return [ 'freed' => $freed, 'readable' => size_format( $freed ) ];
}
public function boost() {
return DSM_Core::instance()->activate_temporary_boost( 'REST' );
}
}
new DSM_REST();
اختبار:
bash
curl -X POST https://example.com/wp-json/dsm/v1/clean \ -H "X-WP-Nonce: YOUR_NONCE" \ --cookie "wordpress_logged_in_xxx=..."
11) ملفات الأصول
assets/admin.css
css
.dsm-wrap .dsm-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin: 20px 0;
}
.dsm-card {
background: #fff;
border: 1px solid #dcdcde;
border-radius: 8px;
padding: 16px;
box-shadow: 0 1px 2px rgba(0,0,0,.05);
}
.dsm-card.warning { border-color: #dba617; background: #fffbe6; }
.dsm-card.critical { border-color: #d63638; background: #ffeaea; }
.dsm-card.ok { border-color: #00a32a; background: #eaffea; }
.dsm-card-label { color: #646970; font-size: 12px; text-transform: uppercase; }
.dsm-card-value { font-size: 24px; font-weight: 700; margin: 6px 0; }
.dsm-card-sub { color: #646970; font-size: 12px; }
.dsm-progress {
height: 6px; background: #eee; border-radius: 3px; overflow: hidden; margin: 6px 0;
}
.dsm-progress span { display: block; height: 100%; background: #2271b1; }
.dsm-actions { margin: 16px 0; }
.dsm-actions form { margin-right: 8px; }
.dsm-log {
background: #1d2327; color: #f0f0f1; padding: 12px;
border-radius: 6px; max-height: 400px; overflow: auto;
font-size: 12px; line-height: 1.6;
}
assets/admin.js
javascript
jQuery(function($){
// تنبيه عند مغادرة الصفحة أثناء تنظيف
$('.dsm-actions form button').on('click', function(){
$(this).prop('disabled', true).text('جارٍ التنفيذ...');
});
});
🎯 مقارنة الميزات بين النسخة 1 و 2
| الميزة | v1 | v2 Pro |
|---|---|---|
| تنظيف ملفات أساسي | ✅ | ✅ |
| وضع 24 ساعة | ✅ | ✅ |
| تنظيف DB يتيمة | ❌ | ✅ |
| حذف صور مصغرة يتيمة | ❌ | ✅ |
| إشعارات بريدية ذكية | ❌ | ✅ |
| REST API | ❌ | ✅ |
| WP-CLI | ❌ | ✅ |
| تدوير السجل | ❌ | ✅ |
| Dashboard Widget | ❌ | ✅ |
| تصدير CSV | ❌ | ✅ |
| حماية ضد التداخل | بسيط | كامل |
| تفعيل تلقائي عند 90% | ❌ | ✅ |
🚀 التثبيت السريع
bash
cd wp-content/mu-plugins/ mkdir -p disk-space-manager/includes disk-space-manager/assets # انسخ كل ملف إلى مكانه الصحيح chmod -R 644 disk-space-manager/**/*.php chmod -R 755 disk-space-manager/
ثم افتح: الأدوات → 💾 Disk Space
هل تريد أن أضيف الآن:
- حذف الصور المكررة بالذكاء الاصطناعي (perceptual hash)؟
- ضغط تلقائي للصور بـ Imagick/WebP؟
- تكامل مع S3/Cloudflare R2 لنقل الوسائط بعيداً عن القرص؟
- Dashboard Report أسبوعي عبر البريد؟
هل يمكنك إضافة من الوظائف
Disk Space Manager Pro — الإصدار 3.0 (Ultimate)
سأضيف الآن حزمة الميزات المتقدمة التي طلبتها وأكثر:
🆕 الميزات الجديدة في v3.0
| # | الميزة | الفائدة |
|---|---|---|
| 1 | حذف الصور المكررة (Perceptual Hash) | توفير 10-40% من حجم الوسائط |
| 2 | ضغط تلقائي WebP/AVIF | تقليل حجم الصور بـ 60-80% |
| 3 | نقل الوسائط إلى S3/R2/B2 | تفريغ القرص المحلي |
| 4 | تقارير أسبوعية بالبريد | متابعة تلقائية |
| 5 | قواعد حذف ذكية (Smart Rules) | تحكم دقيق بالمحتوى |
| 6 | إصلاح قاعدة البيانات | اكتشاف وإصلاح التلف |
| 7 | حذف السجلات الضخمة | post_revisions, expired sessions |
| 8 | مراقبة النمو (Growth Tracking) | تنبؤ بموعد امتلاء القرص |
| 9 | وضع الطوارئ التلقائي | تفعيل تلقائي عند 95% |
| 10 | حذف الملفات اليتيمة في uploads | ملفات بدون سجل في DB |
| 11 | تكامل Cloudflare Cache Purge | تنظيف الكاش بعد التنظيف |
| 12 | إشعارات Slack/Discord/Telegram | قنوات متعددة |
| 13 | جدولة مرنة (Cron Builder) | أوقات مخصصة |
| 14 | نسخ احتياطي قبل الحذف | سلة مهملات آمنة |
| 15 | إحصائيات بيانية (Chart.js) | رسم بياني للنمو |
📁 الهيكل المحدّث
text
disk-space-manager/
├── disk-space-manager.php
├── includes/
│ ├── class-dsm-core.php
│ ├── class-dsm-cleanup.php
│ ├── class-dsm-db-cleaner.php
│ ├── class-dsm-media-cleaner.php
│ ├── class-dsm-notifier.php
│ ├── class-dsm-logger.php
│ ├── class-dsm-admin.php
│ ├── class-dsm-cli.php
│ ├── class-dsm-rest.php
│ ├── class-dsm-dedup.php ← جديد
│ ├── class-dsm-compressor.php ← جديد
│ ├── class-dsm-offloader.php ← جديد
│ ├── class-dsm-reporter.php ← جديد
│ ├── class-dsm-rules.php ← جديد
│ ├── class-dsm-db-repair.php ← جديد
│ ├── class-dsm-growth.php ← جديد
│ ├── class-dsm-emergency.php ← جديد
│ ├── class-dsm-orphan-files.php ← جديد
│ ├── class-dsm-channels.php ← جديد
│ ├── class-dsm-trash.php ← جديد
│ └── class-dsm-scheduler.php ← جديد
└── assets/
├── admin.css
├── admin.js
└── chart.min.js
1) حذف الصور المكررة: class-dsm-dedup.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* كشف الصور المكررة باستخدام Perceptual Hash (aHash)
* يكتشف حتى الصور المختلفة قليلاً (resize, recompress).
*/
class DSM_Dedup {
const META_HASH = '_dsm_phash';
const HASH_SIZE = 8; // 8x8 = 64-bit hash
/** حساب aHash لصورة */
public static function perceptual_hash( $file ) {
if ( ! function_exists( 'imagecreatefromstring' ) ) {
return false;
}
$data = @file_get_contents( $file );
if ( ! $data ) {
return false;
}
$img = @imagecreatefromstring( $data );
if ( ! $img ) {
return false;
}
// تصغير إلى 8x8
$small = imagecreatetruecolor( self::HASH_SIZE, self::HASH_SIZE );
imagecopyresampled(
$small, $img,
0, 0, 0, 0,
self::HASH_SIZE, self::HASH_SIZE,
imagesx( $img ), imagesy( $img )
);
// تحويل لتدرج رمادي وحساب المتوسط
$pixels = [];
$sum = 0;
for ( $y = 0; $y < self::HASH_SIZE; $y++ ) {
for ( $x = 0; $x < self::HASH_SIZE; $x++ ) {
$rgb = imagecolorat( $small, $x, $y );
$r = ( $rgb >> 16 ) & 0xFF;
$g = ( $rgb >> 8 ) & 0xFF;
$b = $rgb & 0xFF;
$gray = (int) ( ( $r + $g + $b ) / 3 );
$pixels[] = $gray;
$sum += $gray;
}
}
$avg = $sum / count( $pixels );
// بناء الـ hash الثنائي
$bits = '';
foreach ( $pixels as $p ) {
$bits .= ( $p >= $avg ) ? '1' : '0';
}
imagedestroy( $img );
imagedestroy( $small );
return $bits;
}
/** المسافة الهامنغ بين hashين */
public static function hamming( $a, $b ) {
if ( strlen( $a ) !== strlen( $b ) ) {
return PHP_INT_MAX;
}
$d = 0;
for ( $i = 0, $len = strlen( $a ); $i < $len; $i++ ) {
if ( $a[ $i ] !== $b[ $i ] ) {
$d++;
}
}
return $d;
}
/**
* البحث عن مجموعات التكرار
* @param int $threshold مسافة هامنغ (كلما قل، كلما كان أشد صرامة) — 5 مناسبة
*/
public static function find_duplicates( $threshold = 5, $limit = 2000 ) {
global $wpdb;
$rows = $wpdb->get_results( $wpdb->prepare(
"SELECT p.ID, pm.meta_value AS file
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_wp_attached_file'
WHERE p.post_type = 'attachment'
AND p.post_mime_type LIKE 'image/%'
ORDER BY p.ID DESC
LIMIT %d",
$limit
) );
$upload = wp_upload_dir();
$hashes = [];
foreach ( $rows as $r ) {
$path = $upload['basedir'] . '/' . $r->file;
if ( ! file_exists( $path ) ) {
continue;
}
$hash = get_post_meta( $r->ID, self::META_HASH, true );
if ( ! $hash ) {
$hash = self::perceptual_hash( $path );
if ( $hash ) {
update_post_meta( $r->ID, self::META_HASH, $hash );
}
}
if ( $hash ) {
$hashes[] = [
'id' => (int) $r->ID,
'hash' => $hash,
'file' => $r->file,
'size' => filesize( $path ),
];
}
}
// تجميع المكرر
$groups = [];
$used = [];
foreach ( $hashes as $i => $a ) {
if ( in_array( $a['id'], $used, true ) ) {
continue;
}
$group = [ $a ];
for ( $j = $i + 1, $n = count( $hashes ); $j < $n; $j++ ) {
$b = $hashes[ $j ];
if ( in_array( $b['id'], $used, true ) ) {
continue;
}
if ( self::hamming( $a['hash'], $b['hash'] ) <= $threshold ) {
$group[] = $b;
$used[] = $b['id'];
}
}
if ( count( $group ) > 1 ) {
$used[] = $a['id'];
$groups[] = $group;
}
}
return $groups;
}
/** حذف المكرر مع الإبقاء على الأكبر حجماً (أفضل جودة) */
public static function delete_duplicates( $groups ) {
$deleted = 0;
$freed = 0;
foreach ( $groups as $group ) {
// ترتيب حسب الحجم تنازلياً — الإبقاء على الأكبر
usort( $group, fn( $a, $b ) => $b['size'] <=> $a['size'] );
$keep = array_shift( $group );
foreach ( $group as $dup ) {
$size = $dup['size'];
if ( wp_delete_attachment( $dup['id'], true ) ) {
$deleted++;
$freed += $size;
}
}
}
DSM_Logger::log( "حذف صور مكررة: {$deleted} صورة، وُفّر " . size_format( $freed ) );
return [ 'deleted' => $deleted, 'freed' => $freed ];
}
}
2) ضغط الصور WebP/AVIF: class-dsm-compressor.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Compressor {
const META_COMPRESSED = '_dsm_compressed';
/**
* ضغط صورة وتحويلها إلى WebP
* @param int $attachment_id
* @param string $format webp|avif|jpg
* @param int $quality 1-100
*/
public static function compress( $attachment_id, $format = 'webp', $quality = 82 ) {
$file = get_attached_file( $attachment_id );
if ( ! $file || ! file_exists( $file ) ) {
return false;
}
$mime = get_post_mime_type( $attachment_id );
if ( ! in_array( $mime, [ 'image/jpeg', 'image/png', 'image/webp' ], true ) ) {
return false;
}
if ( get_post_meta( $attachment_id, self::META_COMPRESSED, true ) ) {
return false; // مضغوطة مسبقاً
}
$new_file = preg_replace( '/\.(jpe?g|png|webp)$/i', ".{$format}", $file );
$success = false;
// AVIF
if ( $format === 'avif' && function_exists( 'imageavif' ) ) {
$img = @imagecreatefromstring( file_get_contents( $file ) );
if ( $img ) {
$success = imageavif( $img, $new_file, $quality );
imagedestroy( $img );
}
}
// WebP
elseif ( $format === 'webp' && function_exists( 'imagewebp' ) ) {
$img = @imagecreatefromstring( file_get_contents( $file ) );
if ( $img ) {
$success = imagewebp( $img, $new_file, $quality );
imagedestroy( $img );
}
}
if ( ! $success ) {
return false;
}
// مقارنة الحجم
$old_size = filesize( $file );
$new_size = filesize( $new_file );
if ( $new_size >= $old_size ) {
@unlink( $new_file );
return false;
}
update_post_meta( $attachment_id, self::META_COMPRESSED, [
'format' => $format,
'old_size' => $old_size,
'new_size' => $new_size,
'saved' => $old_size - $new_size,
'time' => time(),
] );
DSM_Logger::log( sprintf(
'ضغط #%d: %s → %s (%s وُفّر)',
$attachment_id,
size_format( $old_size ),
size_format( $new_size ),
size_format( $old_size - $new_size )
) );
return [
'old' => $old_size,
'new' => $new_size,
'saved' => $old_size - $new_size,
'file' => $new_file,
];
}
/** ضغط دفعي للصور */
public static function bulk_compress( $limit = 50, $format = 'webp', $quality = 82 ) {
global $wpdb;
$ids = $wpdb->get_col( $wpdb->prepare(
"SELECT p.ID FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm
ON pm.post_id = p.ID AND pm.meta_key = %s
WHERE p.post_type = 'attachment'
AND p.post_mime_type IN ('image/jpeg','image/png')
AND pm.meta_id IS NULL
LIMIT %d",
self::META_COMPRESSED,
$limit
) );
$saved = 0;
foreach ( $ids as $id ) {
$r = self::compress( $id, $format, $quality );
if ( $r ) {
$saved += $r['saved'];
}
}
return $saved;
}
/** التحقق من دعم WebP/AVIF */
public static function capabilities() {
return [
'webp' => function_exists( 'imagewebp' ),
'avif' => function_exists( 'imageavif' ),
'gd' => extension_loaded( 'gd' ),
'imagick' => extension_loaded( 'imagick' ),
'imagick_webp' => extension_loaded( 'imagick' ) && in_array( 'WEBP', Imagick::queryFormats(), true ),
'imagick_avif' => extension_loaded( 'imagick' ) && in_array( 'AVIF', Imagick::queryFormats(), true ),
];
}
}
3) نقل الوسائط للسحابة: class-dsm-offloader.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* نقل الوسائط إلى S3 / Cloudflare R2 / Backblaze B2
* يتطلب AWS SDK أو استخدام cURL مباشر مع توقيع AWS SigV4
*/
class DSM_Offloader {
const OPTION = 'dsm_offload_settings';
/** الإعدادات الافتراضية */
public static function defaults() {
return [
'enabled' => false,
'provider' => 's3', // s3|r2|b2
'endpoint' => '',
'region' => 'us-east-1',
'bucket' => '',
'access_key'=> '',
'secret_key'=> '',
'prefix' => 'wp-media',
'delete_local' => false,
'cdn_url' => '',
];
}
public static function settings() {
return wp_parse_args( get_option( self::OPTION, [] ), self::defaults() );
}
/** نقل ملف واحد */
public static function upload_file( $local_path, $remote_key = null ) {
$s = self::settings();
if ( ! $s['enabled'] || ! $s['bucket'] ) {
return new WP_Error( 'dsm_offload_disabled', 'Offload معطّل.' );
}
if ( ! file_exists( $local_path ) ) {
return new WP_Error( 'dsm_file_missing', 'الملف غير موجود.' );
}
$remote_key = $remote_key ?: $s['prefix'] . '/' . basename( $local_path );
$body = file_get_contents( $local_path );
// توقيع AWS SigV4
$url = self::signed_url( 'PUT', $remote_key, $body, $s );
$response = wp_remote_request( $url, [
'method' => 'PUT',
'body' => $body,
'timeout' => 120,
'headers' => [
'Content-Type' => mime_content_type( $local_path ) ?: 'application/octet-stream',
],
] );
if ( is_wp_error( $response ) ) {
DSM_Logger::log( 'فشل الرفع: ' . $response->get_error_message(), 'error' );
return $response;
}
$code = wp_remote_retrieve_response_code( $response );
if ( $code >= 300 ) {
return new WP_Error( 'dsm_upload_failed', "HTTP {$code}" );
}
DSM_Logger::log( "تم رفع {$remote_key}" );
if ( $s['delete_local'] ) {
@unlink( $local_path );
}
return $remote_key;
}
/** توليد توقيع AWS SigV4 (اختصار) */
private static function signed_url( $method, $key, $body, $s ) {
$service = 's3';
$host = $s['provider'] === 'r2'
? str_replace( 'https://', '', $s['endpoint'] )
: "{$s['bucket']}.s3.{$s['region']}.amazonaws.com";
$base_url = $s['provider'] === 'r2'
? "https://{$host}/{$s['bucket']}"
: "https://{$host}";
$uri = '/' . ltrim( $key, '/' );
$t = time();
$date = gmdate( 'Ymd', $t );
$amz_date = gmdate( 'Ymd\THis\Z', $t );
$payload_hash = hash( 'sha256', $body );
$canonical_headers = "host:{$host}\nx-amz-content-sha256:{$payload_hash}\nx-amz-date:{$amz_date}\n";
$signed_headers = 'host;x-amz-content-sha256;x-amz-date';
$canonical_request = "{$method}\n{$uri}\n\n{$canonical_headers}\n{$signed_headers}\n{$payload_hash}";
$credential_scope = "{$date}/{$s['region']}/{$service}/aws4_request";
$string_to_sign = "AWS4-HMAC-SHA256\n{$amz_date}\n{$credential_scope}\n" . hash( 'sha256', $canonical_request );
$k_date = hash_hmac( 'sha256', $date, 'AWS4' . $s['secret_key'], true );
$k_region = hash_hmac( 'sha256', $s['region'], $k_date, true );
$k_service = hash_hmac( 'sha256', $service, $k_region, true );
$k_signing = hash_hmac( 'sha256', 'aws4_request', $k_service, true );
$signature = hash_hmac( 'sha256', $string_to_sign, $k_signing );
$auth = "AWS4-HMAC-SHA256 Credential={$s['access_key']}/{$credential_scope}, "
. "SignedHeaders={$signed_headers}, Signature={$signature}";
// تمرير الترويسات عبر query string مؤقت (بديل: استخدام عميل AWS رسمي)
return add_query_arg( [
'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
'X-Amz-Credential' => urlencode( "{$s['access_key']}/{$credential_scope}" ),
'X-Amz-Date' => $amz_date,
'X-Amz-SignedHeaders' => $signed_headers,
'X-Amz-Signature' => $signature,
], $base_url . $uri );
}
/** نقل دفعي لمرفقات */
public static function bulk_offload( $limit = 20 ) {
$ids = get_posts( [
'post_type' => 'attachment',
'posts_per_page' => $limit,
'fields' => 'ids',
'meta_query' => [
[ 'key' => '_dsm_offloaded', 'compare' => 'NOT EXISTS' ],
],
] );
$done = 0;
foreach ( $ids as $id ) {
$file = get_attached_file( $id );
if ( $file && file_exists( $file ) ) {
$key = self::upload_file( $file );
if ( ! is_wp_error( $key ) ) {
update_post_meta( $id, '_dsm_offloaded', $key );
$done++;
}
}
}
return $done;
}
/** رابط CDN لملف */
public static function cdn_url( $attachment_id ) {
$s = self::settings();
if ( empty( $s['cdn_url'] ) ) {
return false;
}
$key = get_post_meta( $attachment_id, '_dsm_offloaded', true );
return $key ? trailingslashit( $s['cdn_url'] ) . $key : false;
}
}
4) التقارير: class-dsm-reporter.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Reporter {
/** تقرير HTML كامل */
public static function weekly_html() {
$core = DSM_Core::instance();
$info = $core->get_disk_info();
$db = $core->get_db_stats();
$last = get_option( 'dsm_last_cleanup', [] );
$growth = DSM_Growth::summary();
ob_start(); ?>
<div style="font-family:Arial,sans-serif;max-width:640px;margin:auto;direction:rtl;">
<h2 style="color:#2271b1;">💾 تقرير Disk Space Manager</h2>
<p><?php echo esc_html( date_i18n( 'Y-m-d H:i' ) ); ?></p>
<table style="width:100%;border-collapse:collapse;">
<tr style="background:#f0f0f1;">
<th style="padding:8px;text-align:right;border:1px solid #ddd;">البند</th>
<th style="padding:8px;text-align:right;border:1px solid #ddd;">القيمة</th>
</tr>
<tr><td style="padding:8px;border:1px solid #ddd;">الاستخدام</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( $info['percent'] ); ?>%</td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">المساحة الحرة</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( $info['readable'] ); ?></td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">قاعدة البيانات</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( $db['size_mb'] ); ?> MB</td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">المقالات</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( number_format_i18n( $db['posts'] ) ); ?></td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">meta يتيمة</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( $db['postmeta'] ); ?></td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">آخر تنظيف</td><td style="padding:8px;border:1px solid #ddd;"><?php echo $last ? esc_html( human_time_diff( $last['time'] ) ) : '—'; ?></td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">نمو أسبوعي</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( size_format( $growth['weekly'] ?? 0 ) ); ?></td></tr>
<tr><td style="padding:8px;border:1px solid #ddd;">متوقع الامتلاء</td><td style="padding:8px;border:1px solid #ddd;"><?php echo esc_html( $growth['eta'] ?? '—' ); ?></td></tr>
</table>
<p style="margin-top:20px;">
<a href="<?php echo esc_url( admin_url( 'tools.php?page=dsm-dashboard' ) ); ?>"
style="background:#2271b1;color:#fff;padding:10px 20px;text-decoration:none;border-radius:4px;">
فتح اللوحة
</a>
</p>
</div>
<?php
return ob_get_clean();
}
/** إرسال تقرير أسبوعي بالبريد */
public static function send_weekly() {
add_filter( 'wp_mail_content_type', fn() => 'text/html' );
$s = get_option( 'dsm_notify_emails', get_option( 'admin_email' ) );
foreach ( array_filter( array_map( 'trim', explode( ',', $s ) ) ) as $to ) {
wp_mail( $to, '[تقرير] حالة القرص الأسبوعي', self::weekly_html() );
}
remove_filter( 'wp_mail_content_type', fn() => 'text/html' );
}
/** تقرير JSON للـ API */
public static function json() {
return [
'timestamp' => time(),
'disk' => DSM_Core::instance()->get_disk_info(),
'db' => DSM_Core::instance()->get_db_stats(),
'growth' => DSM_Growth::summary(),
];
}
/** تصدير CSV شامل */
public static function export_csv() {
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="dsm-full-' . date( 'Y-m-d' ) . '.csv"' );
$out = fopen( 'php://output', 'w' );
fputs( $out, "\xEF\xBB\xBF" );
fputcsv( $out, [ 'Section', 'Key', 'Value' ] );
$info = DSM_Core::instance()->get_disk_info();
foreach ( $info as $k => $v ) {
fputcsv( $out, [ 'disk', $k, $v ] );
}
foreach ( DSM_Core::instance()->get_db_stats() as $k => $v ) {
fputcsv( $out, [ 'db', $k, $v ] );
}
foreach ( DSM_Growth::summary() as $k => $v ) {
fputcsv( $out, [ 'growth', $k, is_array( $v ) ? wp_json_encode( $v ) : $v ] );
}
fclose( $out );
exit;
}
}
5) القواعد الذكية: class-dsm-rules.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* قواعد حذف مخصصة:
* - احذف المرفقات الأكبر من X ميغا
* - احذف المرفقات الأقدم من Y سنة
* - احذف أنواع MIME معينة
* - استثنِ فئات/وسوم
*/
class DSM_Rules {
const OPTION = 'dsm_rules';
public static function get() {
return get_option( self::OPTION, [] );
}
public static function save( $rules ) {
update_option( self::OPTION, $rules, false );
}
/**
* قاعدة نموذجية:
* [
* 'name' => 'حذف صور كبيرة',
* 'enabled' => true,
* 'min_size' => 5 * MB_IN_BYTES,
* 'max_age_days' => 365,
* 'mimes' => ['image/png'],
* 'exclude_ids' => [1,2,3],
* 'exclude_cats' => [5],
* 'action' => 'trash', // trash|delete|move
* 'target_dir' => '/archive'
* ]
*/
public static function apply_all() {
$report = [];
foreach ( self::get() as $rule ) {
if ( empty( $rule['enabled'] ) ) {
continue;
}
$report[ $rule['name'] ] = self::apply( $rule );
}
return $report;
}
public static function apply( $rule ) {
global $wpdb;
$mimes = ! empty( $rule['mimes'] ) ? (array) $rule['mimes'] : [];
$where = "p.post_type = 'attachment'";
if ( $mimes ) {
$placeholders = implode( ',', array_fill( 0, count( $mimes ), '%s' ) );
$where .= $wpdb->prepare( " AND p.post_mime_type IN ({$placeholders})", $mimes );
}
$rows = $wpdb->get_results(
"SELECT p.ID, p.post_mime_type FROM {$wpdb->posts} p WHERE {$where} LIMIT 1000"
);
$matched = [];
foreach ( $rows as $r ) {
if ( ! empty( $rule['exclude_ids'] ) && in_array( $r->ID, $rule['exclude_ids'], true ) ) {
continue;
}
$file = get_attached_file( $r->ID );
if ( ! $file || ! file_exists( $file ) ) {
continue;
}
$size = filesize( $file );
$age = ( time() - filemtime( $file ) ) / DAY_IN_SECONDS;
if ( ! empty( $rule['min_size'] ) && $size < $rule['min_size'] ) {
continue;
}
if ( ! empty( $rule['max_age_days'] ) && $age < $rule['max_age_days'] ) {
continue;
}
$matched[] = $r->ID;
}
$done = 0;
foreach ( $matched as $id ) {
switch ( $rule['action'] ?? 'trash' ) {
case 'delete':
wp_delete_attachment( $id, true );
$done++;
break;
case 'trash':
wp_trash_post( $id );
$done++;
break;
case 'move':
if ( ! empty( $rule['target_dir'] ) ) {
self::move_file( $id, $rule['target_dir'] );
$done++;
}
break;
}
}
DSM_Logger::log( "قاعدة {$rule['name']}: {$done} عنصر." );
return $done;
}
private static function move_file( $id, $dir ) {
$file = get_attached_file( $id );
if ( ! $file ) {
return;
}
$target = rtrim( $dir, '/' ) . '/' . basename( $file );
wp_mkdir_p( dirname( $target ) );
if ( @rename( $file, $target ) ) {
update_attached_file( $id, $target );
}
}
}
6) إصلاح قاعدة البيانات: class-dsm-db-repair.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_DB_Repair {
/** فحص الجداول */
public static function check_tables() {
global $wpdb;
$tables = $wpdb->get_col( 'SHOW TABLES' );
$results = [];
foreach ( $tables as $t ) {
$row = $wpdb->get_row( "CHECK TABLE `{$t}`" );
if ( $row && strtoupper( $row->Msg_type ) !== 'STATUS' ) {
$results[] = [
'table' => $t,
'type' => $row->Msg_type,
'text' => $row->Msg_text,
];
}
}
return $results;
}
/** إصلاح جدول */
public static function repair_table( $table ) {
global $wpdb;
$table = preg_replace( '/[^a-zA-Z0-9_]/', '', $table );
return $wpdb->get_row( "REPAIR TABLE `{$table}`" );
}
/** إصلاح كل الجداول */
public static function repair_all() {
global $wpdb;
$tables = $wpdb->get_col( 'SHOW TABLES' );
$repaired = [];
foreach ( $tables as $t ) {
$wpdb->query( "REPAIR TABLE `{$t}`" );
$wpdb->query( "OPTIMIZE TABLE `{$t}`" );
$repaired[] = $t;
}
DSM_Logger::log( 'إصلاح + تحسين ' . count( $repaired ) . ' جدول.' );
return $repaired;
}
/** حذف جداول يتيمة (غير مرتبطة بـ prefix) */
public static function find_orphan_tables() {
global $wpdb;
$all = $wpdb->get_col( 'SHOW TABLES' );
$prefix = $wpdb->prefix;
$orphans = [];
foreach ( $all as $t ) {
if ( strpos( $t, $prefix ) !== 0 ) {
$orphans[] = $t;
}
}
return $orphans;
}
/** تحويل الجداول إلى utf8mb4 */
public static function convert_to_utf8mb4() {
global $wpdb;
$tables = $wpdb->get_col( 'SHOW TABLES' );
foreach ( $tables as $t ) {
$wpdb->query( "ALTER TABLE `{$t}` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" );
}
DSM_Logger::log( 'تحويل utf8mb4 مكتمل لـ ' . count( $tables ) . ' جدول.' );
}
}
7) تتبع النمو: class-dsm-growth.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Growth {
const OPTION = 'dsm_growth_history';
const KEEP = 90; // احتفظ بـ 90 قياس
/** تسجيل قياس */
public static function record() {
$info = DSM_Core::instance()->get_disk_info();
$history = get_option( self::OPTION, [] );
$history[] = [
't' => time(),
'used' => $info['used'],
'free' => $info['free'],
'total' => $info['total'],
'percent'=> $info['percent'],
];
// قصر السجل
if ( count( $history ) > self::KEEP ) {
$history = array_slice( $history, -self::KEEP );
}
update_option( self::OPTION, $history, false );
}
/** ملخص النمو */
public static function summary() {
$h = get_option( self::OPTION, [] );
if ( count( $h ) < 2 ) {
return [ 'weekly' => 0, 'daily' => 0, 'eta' => '—' ];
}
$first = $h[0];
$last = end( $h );
$days = max( 1, ( $last['t'] - $first['t'] ) / DAY_IN_SECONDS );
$delta = $last['used'] - $first['used'];
$daily = $delta / $days;
$weekly = $daily * 7;
// متى سيمتلئ؟
$eta = '—';
if ( $daily > 0 && $last['free'] > 0 ) {
$days_left = (int) ( $last['free'] / $daily );
if ( $days_left < 3650 ) {
$eta = date_i18n( 'Y-m-d', time() + $days_left * DAY_IN_SECONDS );
}
}
return [
'daily' => $daily,
'weekly' => $weekly,
'eta' => $eta,
'samples'=> count( $h ),
];
}
/** بيانات الرسم البياني */
public static function chart_data() {
$h = get_option( self::OPTION, [] );
$labels = [];
$values = [];
foreach ( $h as $point ) {
$labels[] = date_i18n( 'm/d', $point['t'] );
$values[] = round( $point['percent'], 2 );
}
return [ 'labels' => $labels, 'values' => $values ];
}
}
8) وضع الطوارئ: class-dsm-emergency.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Emergency {
const THRESHOLD = 95;
const CLEAR_BELOW = 80;
/** يُنفّذ كل ساعة */
public static function check() {
$info = DSM_Core::instance()->get_disk_info();
if ( ! $info['total'] ) {
return;
}
$emergency = get_option( 'dsm_emergency_active', 0 );
if ( $info['percent'] >= self::THRESHOLD && ! $emergency ) {
self::activate();
} elseif ( $info['percent'] < self::CLEAR_BELOW && $emergency ) {
self::deactivate();
}
}
/** تفعيل وضع الطوارئ */
public static function activate() {
update_option( 'dsm_emergency_active', 1, false );
// 1. تنشيط الوضع المؤقت
DSM_Core::instance()->activate_temporary_boost( 'emergency_95' );
// 2. تنظيف شامل
DSM_Core::instance()->run_cleanup( true );
DSM_DB_Cleaner::run_all();
DSM_Orphan_Files::clean();
// 3. حذف السجلات الضخمة (revisions + auto-drafts)
self::purge_revisions();
self::purge_auto_drafts();
// 4. إشعار الطوارئ
DSM_Notifier::send(
'🚨 طوارئ: القرص ممتلئ 95%+',
"تم تفعيل وضع الطوارئ وتنظيف شامل.\n" . DSM_Core::instance()->get_disk_info()['readable']
);
DSM_Logger::log( 'تفعيل وضع الطوارئ.', 'warning' );
}
public static function deactivate() {
update_option( 'dsm_emergency_active', 0, false );
DSM_Logger::log( 'إيقاف وضع الطوارئ.' );
}
/** حذف المراجعات */
public static function purge_revisions() {
global $wpdb;
$n = $wpdb->query(
"DELETE FROM {$wpdb->posts} WHERE post_type = 'revision'"
);
DSM_Logger::log( "حذف مراجعات: {$n}" );
return $n;
}
/** حذف المسودات التلقائية */
public static function purge_auto_drafts() {
global $wpdb;
$n = $wpdb->query(
"DELETE FROM {$wpdb->posts} WHERE post_status = 'auto-draft'"
);
return $n;
}
}
9) الملفات اليتيمة: class-dsm-orphan-files.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
/**
* يكتشف ملفات موجودة في uploads لكن غير مسجلة في wp_posts
*/
class DSM_Orphan_Files {
public static function scan( $limit = 5000 ) {
global $wpdb;
$upload = wp_upload_dir();
$base = $upload['basedir'];
// جلب كل الملفات المسجلة
$registered = $wpdb->get_col(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_wp_attached_file'"
);
$registered = array_flip( $registered );
// فحص الملفات على القرص
$orphans = [];
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $base, FilesystemIterator::SKIP_DOTS )
);
$n = 0;
foreach ( $it as $file ) {
if ( ++$n > $limit ) {
break;
}
if ( ! $file->isFile() ) {
continue;
}
$rel = ltrim( str_replace( $base, '', $file->getPathname() ), '/' );
// تجاهل الصور المصغّرة (تنتهي بـ -WxH)
if ( preg_match( '/-\d+x\d+\.(jpe?g|png|gif|webp)$/i', $rel ) ) {
continue;
}
if ( ! isset( $registered[ $rel ] ) ) {
$orphans[] = [
'path' => $file->getPathname(),
'rel' => $rel,
'size' => $file->getSize(),
'age' => time() - $file->getMTime(),
];
}
}
return $orphans;
}
/** حذف اليتيمة (فقط الأقدم من يوم لتجنب حذف رفع حديث) */
public static function clean( $min_age = DAY_IN_SECONDS ) {
$orphans = self::scan();
$deleted = 0;
$freed = 0;
foreach ( $orphans as $o ) {
if ( $o['age'] < $min_age ) {
continue;
}
if ( @unlink( $o['path'] ) ) {
$deleted++;
$freed += $o['size'];
}
}
DSM_Logger::log( "حذف ملفات يتيمة: {$deleted}، وُفّر " . size_format( $freed ) );
return [ 'deleted' => $deleted, 'freed' => $freed ];
}
/** نقل لسلة المهملات بدلاً من الحذف */
public static function trash( $min_age = DAY_IN_SECONDS ) {
$orphans = self::scan();
$moved = 0;
$trash_dir = WP_CONTENT_DIR . '/dsm-trash/' . date( 'Y-m-d' );
wp_mkdir_p( $trash_dir );
foreach ( $orphans as $o ) {
if ( $o['age'] < $min_age ) {
continue;
}
$target = $trash_dir . '/' . basename( $o['rel'] );
if ( @rename( $o['path'], $target ) ) {
$moved++;
}
}
DSM_Logger::log( "نقل للسلة: {$moved} ملف." );
return $moved;
}
}
10) قنوات الإشعار: class-dsm-channels.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Channels {
const OPTION = 'dsm_channels';
public static function settings() {
return get_option( self::OPTION, [
'slack' => '',
'discord' => '',
'telegram' => [ 'token' => '', 'chat_id' => '' ],
'webhook' => '',
] );
}
/** إرسال لجميع القنوات المفعلة */
public static function broadcast( $message, $level = 'info' ) {
$s = self::settings();
$emoji = $level === 'error' ? '🚨' : ( $level === 'warning' ? '⚠️' : 'ℹ️' );
$text = "{$emoji} *Disk Space Manager*\n{$message}";
if ( ! empty( $s['slack'] ) ) {
wp_remote_post( $s['slack'], [
'body' => wp_json_encode( [ 'text' => $text ] ),
'headers' => [ 'Content-Type' => 'application/json' ],
'timeout' => 10,
] );
}
if ( ! empty( $s['discord'] ) ) {
wp_remote_post( $s['discord'], [
'body' => wp_json_encode( [ 'content' => $text ] ),
'headers' => [ 'Content-Type' => 'application/json' ],
'timeout' => 10,
] );
}
if ( ! empty( $s['telegram']['token'] ) && ! empty( $s['telegram']['chat_id'] ) ) {
wp_remote_get( sprintf(
'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s&parse_mode=Markdown',
$s['telegram']['token'],
$s['telegram']['chat_id'],
urlencode( $text )
), [ 'timeout' => 10 ] );
}
if ( ! empty( $s['webhook'] ) ) {
wp_remote_post( $s['webhook'], [
'body' => wp_json_encode( [ 'text' => $text, 'level' => $level ] ),
'headers' => [ 'Content-Type' => 'application/json' ],
'timeout' => 10,
] );
}
}
}
11) سلة المهملات الآمنة: class-dsm-trash.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Trash {
const DIR = 'dsm-trash';
const MAX_AGE = 7 * DAY_IN_SECONDS;
/** نقل ملف إلى السلة */
public static function move( $file ) {
if ( ! file_exists( $file ) ) {
return false;
}
$trash = WP_CONTENT_DIR . '/' . self::DIR . '/' . date( 'Y-m-d' );
wp_mkdir_p( $trash );
// اسم فريد لتفادي التعارض
$name = basename( $file );
$target = $trash . '/' . $name;
if ( file_exists( $target ) ) {
$target = $trash . '/' . uniqid() . '-' . $name;
}
return @rename( $file, $target );
}
/** تفريغ السلة (الأقدم من أسبوع) */
public static function purge_old() {
$dir = WP_CONTENT_DIR . '/' . self::DIR;
if ( ! is_dir( $dir ) ) {
return 0;
}
$freed = 0;
foreach ( glob( $dir . '/*', GLOB_ONLYDIR ) as $d ) {
$freed += self::purge_dir( $d, self::MAX_AGE );
@rmdir( $d );
}
DSM_Logger::log( 'تفريغ سلة المهملات: ' . size_format( $freed ) );
return $freed;
}
private static function purge_dir( $dir, $age ) {
$freed = 0;
foreach ( glob( $dir . '/*' ) as $f ) {
if ( is_file( $f ) && ( time() - filemtime( $f ) ) > $age ) {
$freed += filesize( $f );
@unlink( $f );
}
}
return $freed;
}
/** إحصاءات السلة */
public static function stats() {
$dir = WP_CONTENT_DIR . '/' . self::DIR;
$size = 0; $count = 0;
if ( is_dir( $dir ) ) {
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS )
);
foreach ( $it as $f ) {
if ( $f->isFile() ) {
$size += $f->getSize();
$count++;
}
}
}
return [ 'size' => $size, 'count' => $count ];
}
}
12) الجدولة المرنة: class-dsm-scheduler.php
php
<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSM_Scheduler {
const OPTION = 'dsm_schedules';
public static function defaults() {
return [
'cleanup' => [ 'enabled' => true, 'interval' => 'hourly', 'time' => null ],
'dedup' => [ 'enabled' => true, 'interval' => 'weekly', 'time' => '03:00' ],
'compress' => [ 'enabled' => true, 'interval' => 'daily', 'time' => '04:00' ],
'report' => [ 'enabled' => true, 'interval' => 'weekly', 'time' => '08:00' ],
'db_repair' => [ 'enabled' => false,'interval' => 'monthly','time' => '02:00' ],
'orphans' => [ 'enabled' => true, 'interval' => 'daily', 'time' => '05:00' ],
];
}
public static function get() {
return wp_parse_args( get_option( self::OPTION, [] ), self::defaults() );
}
public static function save( $data ) {
update_option( self::OPTION, $data, false );
}
/** إعادة جدولة كل الأحداث بناء على الإعدادات */
public static function reschedule_all() {
$schedules = self::get();
foreach ( $schedules as $task => $cfg ) {
$hook = 'dsm_task_' . $task;
wp_clear_scheduled_hook( $hook );
if ( empty( $cfg['enabled'] ) ) {
continue;
}
$interval = $cfg['interval'] ?? 'daily';
// استخدام توقيت مخصص
if ( ! empty( $cfg['time'] ) && in_array( $interval, [ 'daily', 'weekly', 'monthly' ], true ) ) {
$next = self::next_occurrence( $cfg['time'], $interval );
wp_schedule_event( $next, $interval, $hook );
} else {
wp_schedule_event( time() + 60, $interval, $hook );
}
}
}
/** وقت التنفيذ التالي */
private static function next_occurrence( $time, $interval ) {
[ $h, $m ] = array_map( 'intval', explode( ':', $time ) );
$today = strtotime( 'today' ) + $h * HOUR_IN_SECONDS + $m * MINUTE_IN_SECONDS;
$now = time();
if ( $today <= $now ) {
$map = [ 'daily' => DAY_IN_SECONDS, 'weekly' => WEEK_IN_SECONDS, 'monthly' => 30 * DAY_IN_SECONDS ];
$today += $map[ $interval ] ?? DAY_IN_SECONDS;
}
return $today;
}
/** إضافة فواصل مخصصة */
public static function register_intervals( $schedules ) {
$schedules['weekly'] = [ 'interval' => WEEK_IN_SECONDS, 'display' => 'أسبوعياً' ];
$schedules['monthly'] = [ 'interval' => 30 * DAY_IN_SECONDS, 'display' => 'شهرياً' ];
$schedules['quarterly']= [ 'interval' => 90 * DAY_IN_SECONDS, 'display' => 'كل 3 أشهر' ];
return $schedules;
}
}
add_filter( 'cron_schedules', [ 'DSM_Scheduler', 'register_intervals' ] );
13) تحديث الملف الرئيسي: disk-space-manager.php
php
<?php
/**
* Plugin Name: Disk Space Manager Pro Ultimate
* Description: إدارة كاملة للمساحة: تنظيف، ضغط، نقل سحابي، إصلاح DB، إشعارات متعددة، تقارير.
* Version: 3.0.0
* Requires PHP: 7.4
* Requires at least: 5.8
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
define( 'DSM_VERSION', '3.0.0' );
define( 'DSM_PATH', __DIR__ );
define( 'DSM_URL', content_url( 'mu-plugins/disk-space-manager' ) );
define( 'DSM_LOG_DIR', WP_CONTENT_DIR . '/dsm-logs' );
$files = [
// الأساس
'class-dsm-logger', 'class-dsm-cleanup', 'class-dsm-db-cleaner',
'class-dsm-media-cleaner', 'class-dsm-notifier', 'class-dsm-core',
'class-dsm-admin', 'class-dsm-rest', 'class-dsm-cli',
// الميزات المتقدمة
'class-dsm-dedup', 'class-dsm-compressor', 'class-dsm-offloader',
'class-dsm-reporter', 'class-dsm-rules', 'class-dsm-db-repair',
'class-dsm-growth', 'class-dsm-emergency', 'class-dsm-orphan-files',
'class-dsm-channels', 'class-dsm-trash', 'class-dsm-scheduler',
];
foreach ( $files as $f ) {
$path = DSM_PATH . "/includes/{$f}.php";
if ( file_exists( $path ) ) {
require_once $path;
}
}
// تهيئة
DSM_Core::instance();
// ربط مهام الجدولة بالتنفيذ
add_action( 'dsm_task_cleanup', fn() => DSM_Core::instance()->run_cleanup( true ) );
add_action( 'dsm_task_dedup', fn() => DSM_Dedup::delete_duplicates( DSM_Dedup::find_duplicates() ) );
add_action( 'dsm_task_compress', fn() => DSM_Compressor::bulk_compress( 50, 'webp', 82 ) );
add_action( 'dsm_task_report', [ 'DSM_Reporter', 'send_weekly' ] );
add_action( 'dsm_task_db_repair', [ 'DSM_DB_Repair', 'repair_all' ] );
add_action( 'dsm_task_orphans', fn() => DSM_Orphan_Files::trash( DAY_IN_SECONDS ) );
// مهام دورية
add_action( 'init', [ 'DSM_Scheduler', 'reschedule_all' ] );
add_action( 'dsm_hourly_cleanup', [ 'DSM_Growth', 'record' ] );
add_action( 'dsm_hourly_cleanup', [ 'DSM_Emergency', 'check' ] );
add_action( 'dsm_hourly_cleanup', [ 'DSM_Trash', 'purge_old' ] );
14) تحديث الواجهة الإدارية (تبويبات)
أضف هذا داخل DSM_Admin::render() بعد بطاقات المعلومات:
php
<?php
$tab = $_GET['tab'] ?? 'overview';
$tabs = [
'overview' => 'نظرة عامة',
'duplicates' => 'الصور المكررة',
'compress' => 'ضغط الصور',
'offload' => 'النقل السحابي',
'rules' => 'القواعد',
'database' => 'قاعدة البيانات',
'orphans' => 'الملفات اليتيمة',
'growth' => 'النمو',
'channels' => 'الإشعارات',
'schedules' => 'الجدولة',
'log' => 'السجل',
];
?>
<h2 class="nav-tab-wrapper">
<?php foreach ( $tabs as $k => $label ) : ?>
<a href="?page=dsm-dashboard&tab=<?php echo esc_attr( $k ); ?>"
class="nav-tab <?php echo $tab === $k ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $label ); ?>
</a>
<?php endforeach; ?>
</h2>
<div class="dsm-tab-content">
<?php
switch ( $tab ) {
case 'duplicates':
$groups = DSM_Dedup::find_duplicates();
echo '<h3>مجموعات مكررة: ' . count( $groups ) . '</h3>';
$total_saved = 0;
foreach ( $groups as $g ) {
echo '<div class="dsm-group"><table class="widefat">';
foreach ( $g as $item ) {
$total_saved += $item['size'];
echo '<tr><td>#' . $item['id'] . '</td><td>' . esc_html( $item['file'] ) . '</td><td>' . esc_html( size_format( $item['size'] ) ) . '</td></tr>';
}
echo '</table></div>';
}
echo '<p><strong>يمكن توفير: ' . esc_html( size_format( $total_saved ) ) . '</strong></p>';
echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
wp_nonce_field( 'dsm_delete_dups' );
echo '<input type="hidden" name="action" value="dsm_delete_dups">';
echo '<button class="button button-primary">حذف المكرر (الإبقاء على الأكبر)</button>';
echo '</form>';
break;
case 'compress':
$caps = DSM_Compressor::capabilities();
echo '<h3>إمكانيات الخادم</h3><ul>';
foreach ( $caps as $k => $v ) {
echo '<li>' . esc_html( $k ) . ': ' . ( $v ? '✅' : '❌' ) . '</li>';
}
echo '</ul>';
echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
wp_nonce_field( 'dsm_compress_now' );
echo '<input type="hidden" name="action" value="dsm_compress_now">';
echo '<button class="button button-primary">ضغط دفعي (50 صورة)</button>';
echo '</form>';
break;
case 'growth':
$chart = DSM_Growth::chart_data();
echo '<canvas id="dsm-growth-chart" height="80"></canvas>';
echo '<script>window.dsmChartData = ' . wp_json_encode( $chart ) . ';</script>';
$sum = DSM_Growth::summary();
echo '<p>النمو اليومي: ' . esc_html( size_format( $sum['daily'] ) ) . '</p>';
echo '<p>النمو الأسبوعي: ' . esc_html( size_format( $sum['weekly'] ) ) . '</p>';
echo '<p>متوقع الامتلاء: ' . esc_html( $sum['eta'] ) . '</p>';
break;
case 'channels':
$s = DSM_Channels::settings();
?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'dsm_save_channels' ); ?>
<input type="hidden" name="action" value="dsm_save_channels">
<table class="form-table">
<tr><th>Slack Webhook</th><td><input type="url" name="slack" class="regular-text" value="<?php echo esc_attr( $s['slack'] ); ?>"></td></tr>
<tr><th>Discord Webhook</th><td><input type="url" name="discord" class="regular-text" value="<?php echo esc_attr( $s['discord'] ); ?>"></td></tr>
<tr><th>Telegram Token</th><td><input type="text" name="tg_token" class="regular-text" value="<?php echo esc_attr( $s['telegram']['token'] ); ?>"></td></tr>
<tr><th>Telegram Chat ID</th><td><input type="text" name="tg_chat" class="regular-text" value="<?php echo esc_attr( $s['telegram']['chat_id'] ); ?>"></td></tr>
<tr><th>Custom Webhook</th><td><input type="url" name="webhook" class="regular-text" value="<?php echo esc_attr( $s['webhook'] ); ?>"></td></tr>
</table>
<?php submit_button(); ?>
</form>
<?php
break;
case 'schedules':
$schedules = DSM_Scheduler::get();
?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'dsm_save_schedules' ); ?>
<input type="hidden" name="action" value="dsm_save_schedules">
<table class="widefat">
<thead><tr><th>المهمة</th><th>مفعّل</th><th>التكرار</th><th>الوقت</th></tr></thead>
<tbody>
<?php foreach ( $schedules as $task => $cfg ) : ?>
<tr>
<td><?php echo esc_html( $task ); ?></td>
<td><input type="checkbox" name="tasks[<?php echo esc_attr( $task ); ?>][enabled]" value="1" <?php checked( ! empty( $cfg['enabled'] ) ); ?>></td>
<td>
<select name="tasks[<?php echo esc_attr( $task ); ?>][interval]">
<?php foreach ( [ 'hourly','daily','weekly','monthly' ] as $i ) : ?>
<option value="<?php echo esc_attr( $i ); ?>" <?php selected( $cfg['interval'], $i ); ?>><?php echo esc_html( $i ); ?></option>
<?php endforeach; ?>
</select>
</td>
<td><input type="time" name="tasks[<?php echo esc_attr( $task ); ?>][time]" value="<?php echo esc_attr( $cfg['time'] ?? '' ); ?>"></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php submit_button( 'حفظ وإعادة الجدولة' ); ?>
</form>
<?php
break;
default:
// overview
break;
}
?>
</div>
15) أوامر WP-CLI الجديدة
أضف داخل DSM_CLI:
php
/** حذف المكرر */
public function dedup() {
$groups = DSM_Dedup::find_duplicates();
$r = DSM_Dedup::delete_duplicates( $groups );
WP_CLI::success( "حذف {$r['deleted']} — وُفّر " . size_format( $r['freed'] ) );
}
/** ضغط الصور */
public function compress( $args, $assoc ) {
$limit = (int) ( $assoc['limit'] ?? 50 );
$format = $assoc['format'] ?? 'webp';
$q = (int) ( $assoc['quality'] ?? 82 );
$saved = DSM_Compressor::bulk_compress( $limit, $format, $q );
WP_CLI::success( 'وُفّر ' . size_format( $saved ) );
}
/** رفع للسحابة */
public function offload() {
$n = DSM_Offloader::bulk_offload( 20 );
WP_CLI::success( "تم نقل {$n} ملف." );
}
/** إصلاح DB */
public function repair() {
DSM_DB_Repair::repair_all();
WP_CLI::success( 'تم.' );
}
/** حذف ملفات يتيمة */
public function orphans() {
$r = DSM_Orphan_Files::clean();
WP_CLI::success( "حذف {$r['deleted']} — وُفّر " . size_format( $r['freed'] ) );
}
/** تقرير */
public function report() {
WP_CLI::line( wp_json_encode( DSM_Reporter::json(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ) );
}
الاستخدام:
bash
wp dsm dedup wp dsm compress --limit=100 --format=webp --quality=85 wp dsm offload wp dsm repair wp dsm orphans wp dsm report
16) جدول موجز لكل الميزات
| الميزة | الكلاس | الوظيفة |
|---|---|---|
| تنظيف ملفات | DSM_Cleanup | purge_dir / purge_by_ext |
| تنظيف DB | DSM_DB_Cleaner | 10 عمليات DELETE آمنة |
| وسائط | DSM_Media_Cleaner | thumbnails، أكبر الملفات |
| إشعارات | DSM_Notifier | بريد + عتبات |
| قنوات | DSM_Channels | Slack / Discord / Telegram |
| سجل | DSM_Logger | Rotation + tail |
| تكرار | DSM_Dedup | aHash + Hamming |
| ضغط | DSM_Compressor | WebP / AVIF |
| سحابة | DSM_Offloader | S3 / R2 / B2 SigV4 |
| تقارير | DSM_Reporter | HTML / JSON / CSV |
| قواعد | DSM_Rules | حجم/عمر/mime |
| إصلاح DB | DSM_DB_Repair | REPAIR + OPTIMIZE + utf8mb4 |
| نمو | DSM_Growth | رسم + ETA |
| طوارئ | DSM_Emergency | تلقائي عند 95% |
| ملفات يتيمة | DSM_Orphan_Files | فحص uploads vs DB |
| سلة | DSM_Trash | بديل آمن للحذف |
| جدولة | DSM_Scheduler | Cron Builder |
| REST | DSM_REST | 3 نقاط |
| CLI | DSM_CLI | 11 أمراً |
🎯 خارطة طريق مقترحة للإصدار 4.0
هل تريد في المرة القادمة إضافة:
- AI Content Detection — حذف الصور غير المستخدمة في أي مقال (فحص DOM).
- تكامل مع CDN (Cloudflare / BunnyCDN) — Purge Cache تلقائي.
- نسخ احتياطي قبل التنظيف إلى S3 ثم حذف.
- تعدد المواقع (Multisite) — تقرير شبكي موحّد.
- مراقبة Inodes بجانب المساحة (مهم لبعض الاستضافات).
- Virus Scan للملفات المرفوعة (ClamAV).
- Watermark ذكي على الصور بدلاً من حذفها.
- تنبؤ بالمساحة عبر ML (Linear Regression).
- PWA Dashboard لمتابعة المساحة من الجوال.
- Blockchain Audit Log لتوثيق عمليات الحذف.
هل تريد أن أُكمل بأي من هذه؟ أو أدمجها كلها في v4.0 بملف واحد منظم؟