كود PHP WordPress لزيادة مساحة التخزين مؤقتاً لمدة 24 ساعة

📊 1 مشاهدة
  1. تحرير مساحة عبر حذف الملفات المؤقتة والنسخ الاحتياطية القديمة والـ logs.
  2. تفعيل وضع صيانة مؤقت لمدة 24 ساعة لتجنب مشاكل الدلائل.
  3. مراقبة المساحة وإرسال تنبيهات.

الكود التالي يقوم بهذه المهام بشكل آمن.


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)

  1. يخزّن وقت البدء ووقت الانتهاء.
  2. يشغّل التنظيف فوراً.
  3. يجدول حدث wp_dsm_expire_check ليعمل تلقائياً بعد 24 ساعة لإيقاف الوضع.
  4. يجدول تنظيف يومي متكرر.

🔹 التنظيف (run_cleanup) — جوهر توفير المساحة

العمليةالموقعالعمر المحذوف
ملفات مؤقتةwp-content/uploads/tempأقدم من يوم
Logswp-content/logsأقدم من 7 أيام
نسخ احتياطيةwp-content/backupsأقدم من 3 أيام
Transients منتهيةقاعدة البياناتفوراً
ملفات .tmp/.bak/.oldwp-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) طريقة التثبيت

  1. أنشئ المجلد wp-content/mu-plugins/ إن لم يكن موجوداً.
  2. احفظ الكود في الملف: wp-content/mu-plugins/disk-space-manager.php.
  3. تأكد من صلاحيات الملف 644.
  4. اذهب إلى الأدوات → Disk Space Manager واضغط “تفعيل لمدة 24 ساعة”.
  5. راقب الملف 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

الميزةv1v2 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


هل تريد أن أضيف الآن:

  1. حذف الصور المكررة بالذكاء الاصطناعي (perceptual hash)؟
  2. ضغط تلقائي للصور بـ Imagick/WebP؟
  3. تكامل مع S3/Cloudflare R2 لنقل الوسائط بعيداً عن القرص؟
  4. 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_Cleanuppurge_dir / purge_by_ext
تنظيف DBDSM_DB_Cleaner10 عمليات DELETE آمنة
وسائطDSM_Media_Cleanerthumbnails، أكبر الملفات
إشعاراتDSM_Notifierبريد + عتبات
قنواتDSM_ChannelsSlack / Discord / Telegram
سجلDSM_LoggerRotation + tail
تكرارDSM_DedupaHash + Hamming
ضغطDSM_CompressorWebP / AVIF
سحابةDSM_OffloaderS3 / R2 / B2 SigV4
تقاريرDSM_ReporterHTML / JSON / CSV
قواعدDSM_Rulesحجم/عمر/mime
إصلاح DBDSM_DB_RepairREPAIR + OPTIMIZE + utf8mb4
نموDSM_Growthرسم + ETA
طوارئDSM_Emergencyتلقائي عند 95%
ملفات يتيمةDSM_Orphan_Filesفحص uploads vs DB
سلةDSM_Trashبديل آمن للحذف
جدولةDSM_SchedulerCron Builder
RESTDSM_REST3 نقاط
CLIDSM_CLI11 أمراً

🎯 خارطة طريق مقترحة للإصدار 4.0

هل تريد في المرة القادمة إضافة:

  1. AI Content Detection — حذف الصور غير المستخدمة في أي مقال (فحص DOM).
  2. تكامل مع CDN (Cloudflare / BunnyCDN) — Purge Cache تلقائي.
  3. نسخ احتياطي قبل التنظيف إلى S3 ثم حذف.
  4. تعدد المواقع (Multisite) — تقرير شبكي موحّد.
  5. مراقبة Inodes بجانب المساحة (مهم لبعض الاستضافات).
  6. Virus Scan للملفات المرفوعة (ClamAV).
  7. Watermark ذكي على الصور بدلاً من حذفها.
  8. تنبؤ بالمساحة عبر ML (Linear Regression).
  9. PWA Dashboard لمتابعة المساحة من الجوال.
  10. Blockchain Audit Log لتوثيق عمليات الحذف.

هل تريد أن أُكمل بأي من هذه؟ أو أدمجها كلها في v4.0 بملف واحد منظم؟

Scroll to Top

🤖 موصى به لك بناءً على اهتماماتك