| @@ -26,11 +26,11 @@ | ||
| 26 | 26 | public const VERSION = '1.0.0'; |
| 27 | 27 | |
| 28 | 28 | public function ui_metadata(): array { |
| 29 | 29 | return array( |
| 30 | - 'label' => 'Database', | |
| 30 | + 'label' => __( 'Database', 'xspeed' ), | |
| 31 | 31 | 'icon' => 'Trash2', |
| 32 | - 'description' => 'Scan + clean WordPress bloat (revisions, spam, transients, orphan meta) and optimize tables.', | |
| 32 | + 'description' => __( 'Scan + clean WordPress bloat (revisions, spam, transients, orphan meta) and optimize tables.', 'xspeed' ), | |
| 33 | 33 | 'custom_panel' => 'DatabaseCleanerPanel', |
| 34 | 34 | ); |
| 35 | 35 | } |
| 36 | 36 | |
| @@ -45,21 +45,94 @@ | ||
| 45 | 45 | 'hourly' => 'Hourly', |
| 46 | 46 | 'daily' => 'Daily', |
| 47 | 47 | 'weekly' => 'Weekly', |
| 48 | 48 | ), |
| 49 | - 'label' => 'Auto-Cleanup Schedule', | |
| 50 | - 'description' => 'How often to run cleanup automatically. Manual means cleanup only runs when you press the button.', | |
| 49 | + 'label' => __( 'Auto-Cleanup Schedule', 'xspeed' ), | |
| 50 | + 'description' => __( 'How often to run cleanup automatically. Manual means cleanup only runs when you press the button.', 'xspeed' ), | |
| 51 | 51 | ), |
| 52 | 52 | 'included_types' => array( |
| 53 | 53 | 'type' => 'list', |
| 54 | 54 | 'default' => array(), |
| 55 | 55 | 'item_type' => 'string', |
| 56 | - 'label' => 'Auto-Cleanup Types', | |
| 57 | - 'description' => 'Which cleanup categories run on the schedule above. Leave empty to keep auto-cleanup disabled even if a schedule is set.', | |
| 56 | + 'label' => __( 'Auto-Cleanup Types', 'xspeed' ), | |
| 57 | + 'description' => __( 'Which cleanup categories run on the schedule above. Leave empty to keep auto-cleanup disabled even if a schedule is set.', 'xspeed' ), | |
| 58 | 58 | ), |
| 59 | + 'limit_revisions' => array( | |
| 60 | + 'type' => 'bool', | |
| 61 | + 'default' => false, | |
| 62 | + 'label' => __( 'Limit Post Revisions', 'xspeed' ), | |
| 63 | + 'description' => __( 'Cap how many revisions WordPress keeps for each post. Cleanup removes revisions that already exist; this stops them piling up again.', 'xspeed' ), | |
| 64 | + ), | |
| 65 | + 'revisions_to_keep' => array( | |
| 66 | + 'type' => 'int', | |
| 67 | + 'default' => 5, | |
| 68 | + 'min' => 0, | |
| 69 | + 'max' => 500, | |
| 70 | + 'label' => __( 'Revisions to Keep', 'xspeed' ), | |
| 71 | + 'description' => __( 'Used when Limit Post Revisions is on. 0 turns revisions off completely. Older revisions are trimmed the next time each post is saved; to remove the backlog now, clean Post Revisions above.', 'xspeed' ), | |
| 72 | + ), | |
| 59 | 73 | ); |
| 60 | 74 | } |
| 61 | 75 | |
| 76 | + /** | |
| 77 | + * WP_POST_REVISIONS as wp-config.php (or an earlier plugin) set it, or | |
| 78 | + * null when nobody did. Read in boot(), on plugins_loaded: after that, | |
| 79 | + * core defines it as `true` itself, so defined() stops telling the two | |
| 80 | + * cases apart. | |
| 81 | + * | |
| 82 | + * Not the schema's `constants` pinning (#398): that tests defined() at | |
| 83 | + * request time, when core's own default makes it always true. | |
| 84 | + * | |
| 85 | + * @var bool|int|null | |
| 86 | + */ | |
| 87 | + private static $configured_revisions = null; | |
| 88 | + | |
| 89 | + /** | |
| 90 | + * @param int $num Revisions WordPress would keep; -1 means all. | |
| 91 | + * @param \WP_Post $post | |
| 92 | + * @return int | |
| 93 | + */ | |
| 94 | + public static function filter_revisions_to_keep( $num, $post ) { | |
| 95 | + // A wp-config.php value is the site owner's decision; leave it alone. | |
| 96 | + if ( null !== self::$configured_revisions ) { | |
| 97 | + return $num; | |
| 98 | + } | |
| 99 | + // Core already answered 0 for post types without revision support. | |
| 100 | + if ( ! $post instanceof \WP_Post || ! post_type_supports( $post->post_type, 'revisions' ) ) { | |
| 101 | + return $num; | |
| 102 | + } | |
| 103 | + $opts = Settings_Manager::get( self::SLUG ); | |
| 104 | + if ( empty( $opts['limit_revisions'] ) ) { | |
| 105 | + return $num; | |
| 106 | + } | |
| 107 | + return max( 0, (int) ( $opts['revisions_to_keep'] ?? 5 ) ); | |
| 108 | + } | |
| 109 | + | |
| 110 | + public function ui_notices(): array { | |
| 111 | + if ( null === self::$configured_revisions ) { | |
| 112 | + return array(); | |
| 113 | + } | |
| 114 | + $value = self::$configured_revisions; | |
| 115 | + if ( true === $value ) { | |
| 116 | + $shown = 'true'; | |
| 117 | + } elseif ( false === $value ) { | |
| 118 | + $shown = 'false'; | |
| 119 | + } else { | |
| 120 | + $shown = (string) (int) $value; | |
| 121 | + } | |
| 122 | + return array( | |
| 123 | + array( | |
| 124 | + 'tone' => 'info', | |
| 125 | + 'title' => __( 'The revision limit is set outside xSpeed', 'xspeed' ), | |
| 126 | + 'body' => sprintf( | |
| 127 | + /* translators: %s: value of WP_POST_REVISIONS, e.g. 5 or false. */ | |
| 128 | + __( 'WP_POST_REVISIONS is set to %s, usually in wp-config.php, so Limit Post Revisions below has no effect. Remove that definition to manage the limit here.', 'xspeed' ), | |
| 129 | + $shown | |
| 130 | + ), | |
| 131 | + ), | |
| 132 | + ); | |
| 133 | + } | |
| 134 | + | |
| 62 | 135 | public function rest_routes(): array { |
| 63 | 136 | // Schema POST/GET come from the base; we add three custom routes. |
| 64 | 137 | $default = parent::rest_routes(); |
| 65 | 138 | return array_merge( |
| @@ -89,8 +162,9 @@ | ||
| 89 | 162 | array( |
| 90 | 163 | 'name' => 'xspeed db', |
| 91 | 164 | 'callback' => array( $this, 'cli_handler' ), |
| 92 | 165 | 'shortdesc' => 'Scan or clean WordPress bloat. Use --types=a,b to clean specific categories.', |
| 166 | + 'ai_hint' => 'Find and remove database bloat (post revisions, spam comments, expired transients, orphaned metadata). Always `scan` first to see what would go; `clean` is destructive and requires confirmation.', | |
| 93 | 167 | 'synopsis' => array( |
| 94 | 168 | array( |
| 95 | 169 | 'type' => 'positional', |
| 96 | 170 | 'name' => 'action', |
| @@ -107,8 +181,10 @@ | ||
| 107 | 181 | ); |
| 108 | 182 | } |
| 109 | 183 | |
| 110 | 184 | public function boot(): void { |
| 185 | + self::$configured_revisions = defined( 'WP_POST_REVISIONS' ) ? constant( 'WP_POST_REVISIONS' ) : null; | |
| 186 | + add_filter( 'wp_revisions_to_keep', array( __CLASS__, 'filter_revisions_to_keep' ), 10, 2 ); | |
| 111 | 187 | add_action( Database_Cleaner::CRON_HOOK, array( Database_Cleaner::class, 'cron_tick' ) ); |
| 112 | 188 | add_action( 'update_option_xspeed_module_database', array( $this, 'on_settings_change' ), 10, 2 ); |
| 113 | 189 | add_action( 'add_option_xspeed_module_database', array( $this, 'on_settings_added' ), 10, 2 ); |
| 114 | 190 | } |
| @@ -117,8 +193,16 @@ | ||
| 117 | 193 | wp_clear_scheduled_hook( Database_Cleaner::CRON_HOOK ); |
| 118 | 194 | } |
| 119 | 195 | |
| 120 | 196 | public function on_settings_change( $old, $new ): void { |
| 197 | + // A revision-limit save must not reschedule cleanup: apply_schedule() | |
| 198 | + // restarts the timer, which pulled a weekly run forward on every save. | |
| 199 | + $old = is_array( $old ) ? $old : array(); | |
| 200 | + if ( is_array( $new ) | |
| 201 | + && ( $old['schedule'] ?? null ) === ( $new['schedule'] ?? null ) | |
| 202 | + && ( $old['included_types'] ?? null ) === ( $new['included_types'] ?? null ) ) { | |
| 203 | + return; | |
| 204 | + } | |
| 121 | 205 | $schedule = is_array( $new ) ? (string) ( $new['schedule'] ?? 'manual' ) : 'manual'; |
| 122 | 206 | $types = is_array( $new ) && isset( $new['included_types'] ) && is_array( $new['included_types'] ) ? $new['included_types'] : array(); |
| 123 | 207 | Database_Cleaner::apply_schedule( $schedule, $types ); |
| 124 | 208 | } |