days = $days; $this->compress = $compress; } /** * Returns the module identifier. * * @return string Module name. */ public function get_module(): string { return 'archive'; } /** * Returns the zone identifier. * * @return string Zone name. */ public function get_zone(): string { return 'archive'; } /** * Returns the total number of archive-eligible postmeta rows. * * @return int Total row count. */ protected function count_source(): int { global $wpdb; $cutoff = $this->get_cutoff(); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_status = 'trash' AND p.post_modified_gmt < %s", $cutoff ) ); } /** * Migrates one batch of eligible postmeta rows to the archive table. * * @param int $offset Starting row offset. * @return int Number of rows processed. */ protected function migrate_batch( int $offset ): int { global $wpdb; $cutoff = $this->get_cutoff(); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT pm.meta_id, pm.post_id, pm.meta_key, pm.meta_value, p.post_type FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_status = 'trash' AND p.post_modified_gmt < %s ORDER BY pm.meta_id ASC LIMIT %d OFFSET %d", $cutoff, self::BATCH_SIZE, $offset ), ARRAY_A ); if ( empty( $rows ) ) { return 0; } $entries = array(); foreach ( $rows as $row ) { $entries[] = array( 'post_id' => $row['post_id'], 'post_type' => $row['post_type'], 'meta_key' => $row['meta_key'], 'meta_value' => $row['meta_value'], 'meta_id' => $row['meta_id'], ); } TMDO_Zone_Archive::archive_batch( $entries, $this->compress ); return count( $rows ); } /** * Verifies that archive table has rows when eligible source rows exist. * * @return bool True if verification passes. */ public function verify_counts(): bool { global $wpdb; $source = $this->count_source(); $table = TMDO_Zone_Archive::table(); $target = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from TMDO_Zone_Archive::table() // Archive may have more rows than current source (trashed posts may have // been permanently deleted after archival). So we just check target > 0 // when source is 0, or target >= some threshold. if ( 0 === $source ) { return true; } return $target >= $source; } // ── Private helpers ─────────────────────────────────────────────────── /** * Returns the cutoff datetime string for archival eligibility. * * @return string MySQL datetime string. */ private function get_cutoff(): string { return gmdate( 'Y-m-d H:i:s', time() - ( $this->days * DAY_IN_SECONDS ) ); } }