'plugin-transients', 'label' => __( 'Expired cached data', 'templately' ), 'group' => 'cache', 'scope' => 'records', 'retention_kind' => RetentionKind::INTRINSIC, 'destructive' => true, 'schedulable' => true, ]; } public function estimate( Context $context ): TaskResult { return TaskResult::empty()->add( count( $this->expired_keys() ), 0 ); } public function run( Context $context ): TaskResult { $expired = $this->expired_keys(); if ( $context->is_dry_run() ) { return TaskResult::empty()->add( count( $expired ), 0 ); } $result = TaskResult::empty(); foreach ( $expired as $key ) { if ( delete_transient( $key ) ) { $result->add( 1, 0 ); } } return $result; } /** * Keys whose stored timeout has already passed. * * Scoped to the plugin's own `_templately_` entries — reclaiming another * plugin's cache is explicitly out of scope, and WordPress's own * `delete_expired_transients()` is site-wide. * * @return string[] */ private function expired_keys(): array { $expired = []; $now = time(); foreach ( Transients::get_templately_transients() as $row ) { $key = Transients::option_name_to_transient_key( $row->option_name ); $timeout = get_option( '_transient_timeout_' . $key, false ); if ( false === $timeout ) { // No timeout row = never expires. Not ours to reclaim on a schedule. continue; } if ( (int) $timeout < $now ) { $expired[] = $key; } } return $expired; } }