translated words. */ public static function labels(): array { return array( 'page_checked' => __( 'Public page checked', 'affstackdev-image-manager' ), 'page_blocked' => __( 'Page could not be checked', 'affstackdev-image-manager' ), 'page_retry' => __( 'Page retry saved', 'affstackdev-image-manager' ), 'catalog_checked' => __( 'Product inventory batch checked', 'affstackdev-image-manager' ), 'comments_checked' => __( 'Review inventory batch checked', 'affstackdev-image-manager' ), 'selection_ready' => __( 'Image selection saved', 'affstackdev-image-manager' ), 'image_step' => __( 'Image processing checkpoint saved', 'affstackdev-image-manager' ), 'complete' => __( 'Selected image action completed', 'affstackdev-image-manager' ), 'checking' => __( 'Image approved; public-page check pending', 'affstackdev-image-manager' ), 'review' => __( 'Comparison ready for review', 'affstackdev-image-manager' ), 'skipped' => __( 'Image retained without applying this action', 'affstackdev-image-manager' ), 'failed' => __( 'Image needs attention', 'affstackdev-image-manager' ), 'working' => __( 'Image work queued', 'affstackdev-image-manager' ), ); } /** * Reporting is best effort: it must never invalidate a successful file publication. * * @param string $event The checkpoint code (a key of labels()). * @param int $source The source row the checkpoint is about, or 0. */ public static function record( string $event, int $source = 0 ): void { if ( ! isset( self::labels()[ $event ] ) ) { return; } try { $job = Workflow::job(); if ( ! $job['active'] || $job['cancelled'] ) { return; } Reviewed_Store::update_option( self::OPTION, array( 'version' => 1, 'run_id' => (string) $job['id'], 'run_started' => (int) $job['started'], 'at' => time(), 'event' => $event, 'source_id' => max( 0, $source ), ), false ); } catch ( \Throwable $ignored ) { /* Image work remains authoritative. */ } } /** * All queries are bounded aggregates over known work; no source files or network reads. * * @param array $job The current run (Workflow::job()). * @param array $pages The page counts of the scan. * @param array $counts The source counts by state. * @param array|null $summary The last finished run's summary, or null. * @param bool $active Whether a run is active. * @param bool $scanning Whether the page scan is still running. * @param bool $paused Whether processing is paused. * @param array $health The background-processing check (Scheduler). * @return array The progress figures and words for the Optimize screen. */ public static function describe( array $job, array $pages, array $counts, ?array $summary, bool $active, bool $scanning, bool $paused, array $health ): array { global $wpdb; $now = time(); $scan = Scanner::state(); $checkpoint = (array) get_option( self::OPTION, array() ); $code = is_string( $checkpoint['event'] ?? null ) ? $checkpoint['event'] : ''; $stamp = is_numeric( $checkpoint['at'] ?? null ) ? (int) $checkpoint['at'] : 0; $matching = 1 === ( $checkpoint['version'] ?? null ) && is_string( $checkpoint['run_id'] ?? null ) && $checkpoint['run_id'] === (string) $job['id'] && is_numeric( $checkpoint['run_started'] ?? null ) && (int) $checkpoint['run_started'] === (int) $job['started'] && isset( self::labels()[ $code ] ) && $stamp > 0 && $stamp <= $now; $at = $matching ? $stamp : 0; $event = $matching ? self::labels()[ $code ] : ''; $run_active = (bool) $job['active']; $phase = ! $run_active ? ( $job['cancelled'] ? 'stopped' : ( $job['finished'] ? 'finished' : 'idle' ) ) : ( $paused ? 'paused' : ( $scanning ? 'discovering' : ( $summary && $summary['checking'] > 0 && ! $summary['waiting'] && ! $summary['working'] ? 'checking' : 'processing' ) ) ); $scanned = (int) ( $pages['scanned'] ?? 0 ); $blocked = (int) ( $pages['blocked'] ?? 0 ); $waiting = (int) ( $pages['waiting'] ?? 0 ); $scope_known = 'bulk' === $job['mode'] && ! empty( $job['snapshotted'] ); $out = array( 'version' => 1, 'id' => (string) $job['id'], 'mode' => (string) $job['mode'], 'phase' => $phase, 'active' => $run_active, 'other_work_pending' => $active && ! $run_active, 'started' => (int) $job['started'], 'finished' => (int) $job['finished'], 'selection_known' => $scope_known, 'selected' => $scope_known ? (int) $summary['total'] : null, 'completed' => $scope_known ? (int) $summary['done'] : null, 'outcomes' => $scope_known ? $summary : null, 'pages' => array( 'checked' => $scanned + $blocked, 'successful' => $scanned, 'blocked' => $blocked, 'waiting' => $waiting, 'found_so_far' => array_sum( $pages ), 'has_scan' => ! empty( $job['scan_first'] ) || in_array( $job['mode'], array( 'scan', 'optimize' ), true ), ), 'inventory_scans' => array( 'products_pending' => (int) $scan['catalog_page'] > 0, 'reviews_pending' => (int) $scan['comment_offset'] >= 0, ), 'checkpoint_at' => $at, 'checkpoint_label' => $event, 'wait_code' => '', 'wait_detail' => '', 'retry_at' => 0, 'compression' => 'bulk' === $job['mode'] && 'optimize' === ( $job['action'] ?? '' ) ? Policy::label( Policy::clean( (array) ( $job['recipe'] ?? array() ) )['mode'] ) : '', 'sizing' => 'bulk' === $job['mode'] && 'optimize' === ( $job['action'] ?? '' ) ? ( array( 'balanced' => __( 'Useful missing display sizes', 'affstackdev-image-manager' ), 'fit' => __( 'Sizes measured on your pages', 'affstackdev-image-manager' ), )[ $job['sizing'] ?? 'existing' ] ?? __( 'Existing sizes only', 'affstackdev-image-manager' ) ) : '', ); if ( $active && $paused ) { $out['wait_code'] = 'paused'; $out['wait_detail'] = __( 'Paused. Resume continues the saved work; original files remain unchanged.', 'affstackdev-image-manager' ); } elseif ( $active ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Observe current lock expiry and due worker queues for progress; cached leases or queue times can misreport stalled/running work. $lease = $wpdb->get_row( $wpdb->prepare( 'SELECT expires FROM %i WHERE name=%s', Repository::table( 'locks' ), 'worker' ), ARRAY_A ); if ( (int) ( $lease['expires'] ?? 0 ) >= $now ) { $out['wait_code'] = 'worker_reserved'; $out['wait_detail'] = __( 'Another request holds the worker reservation. It may still be processing or recovering; this screen will not start a second worker.', 'affstackdev-image-manager' ); } else { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Observe current lock expiry and due worker queues for progress; cached leases or queue times can misreport stalled/running work. $queue = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS n,MIN(available_at) AS due FROM %i WHERE state='waiting'", Repository::table( 'pages' ) ), ARRAY_A ) ?: array(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Observe current lock expiry and due worker queues for progress; cached leases or queue times can misreport stalled/running work. $images = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS n,MIN(available_at) AS due FROM %i WHERE state='queued' AND requested=1", Repository::table( 'sources' ) ), ARRAY_A ) ?: array(); $page_due = (int) ( $queue['due'] ?? 0 ); $image_due = (int) ( $images['due'] ?? 0 ); $page_delayed = $scanning && (int) ( $queue['n'] ?? 0 ) > 0 && $page_due > $now; $image_delayed = ! $scanning && (int) ( $images['n'] ?? 0 ) > 0 && $image_due > $now; if ( $page_delayed || $image_delayed ) { $out['retry_at'] = $page_delayed ? $page_due : $image_due; $out['wait_code'] = $page_delayed ? 'page_retry' : 'image_retry'; /* translators: %s: UTC retry date and time. */ $out['wait_detail'] = sprintf( __( 'A saved retry becomes eligible at %s UTC. Other eligible work may continue; do not restart the run.', 'affstackdev-image-manager' ), gmdate( 'Y-m-d H:i:s', $out['retry_at'] ) ); } elseif ( max( $at, (int) $job['started'], (int) get_option( 'afsd_im_last_resume', 0 ), (int) get_option( 'afsd_im_work_requested', 0 ) ) < $now - 300 ) { $out['wait_code'] = 'no_recent_checkpoint'; $out['wait_detail'] = __( 'No progress checkpoint has been recorded for at least five minutes. This is not proof of failure. Use Process here now once or review Tools and diagnostics; do not keep restarting the run.', 'affstackdev-image-manager' ); } elseif ( $scanning && ! $waiting && ( $out['inventory_scans']['products_pending'] || $out['inventory_scans']['reviews_pending'] ) ) { $out['wait_code'] = 'inventory_scan'; $out['wait_detail'] = __( 'Public page checks are finished. Checking the enabled product or review inventory before selecting images.', 'affstackdev-image-manager' ); } } } return $out; } }