find_all( array( 'status' => 'publish' ) ); $due_peptides = array(); $overdue_peptides = array(); foreach ( $peptides as $dto ) { $last_verified = get_post_meta( $dto->id, '_pr_last_source_verified', true ); $velocity = get_post_meta( $dto->id, '_pr_verification_velocity', true ) ?: 'medium'; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found // Empty verified date = treat as overdue. if ( empty( $last_verified ) ) { update_post_meta( $dto->id, '_pr_verification_status', 'overdue' ); $overdue_peptides[] = $dto; continue; } // Delegate to compute_status() — keeps logic testable without DB calls. $status = self::compute_status( $last_verified, $velocity ); update_post_meta( $dto->id, '_pr_verification_status', $status ); if ( 'due' === $status ) { $due_peptides[] = $dto; } elseif ( 'overdue' === $status ) { $overdue_peptides[] = $dto; } } // Log the scan result. self::log_scan( count( $peptides ), count( $due_peptides ), count( $overdue_peptides ) ); // Send digest email if configured and there are items needing review. if ( ! empty( $due_peptides ) || ! empty( $overdue_peptides ) ) { self::send_digest_email( $due_peptides, $overdue_peptides ); } } /** * Compute verification status for a single peptide. * * Extracted for direct unit-testability without database or WP-cron dependencies. * * @param string $last_verified YYYY-MM-DD date of last source verification. Empty = overdue. * @param string $velocity Velocity tier: high | medium | low. * @return string Status: current | due | overdue. */ public static function compute_status( string $last_verified, string $velocity ): string { if ( empty( $last_verified ) ) { return 'overdue'; } $days_since = ( time() - strtotime( $last_verified ) ) / DAY_IN_SECONDS; $threshold = match ( $velocity ) { 'high' => (int) get_option( 'pr_core_high_velocity_threshold', self::HIGH_VELOCITY_THRESHOLD ), 'low' => self::LOW_VELOCITY_THRESHOLD, default => (int) get_option( 'pr_core_default_threshold', self::DEFAULT_THRESHOLD_DAYS ), }; return $days_since < ( $threshold * 0.9 ) ? 'current' : ( $days_since < $threshold ? 'due' : 'overdue' ); } /** * Log scan summary to WP option, keeping last N entries. * * @param int $total Total peptides scanned. * @param int $due Count of due peptides. * @param int $overdue Count of overdue peptides. * @return void */ private static function log_scan( int $total, int $due, int $overdue ): void { $log = get_option( self::SCAN_LOG_OPTION_KEY, array() ); if ( ! is_array( $log ) ) { $log = array(); } $log[] = array( 'timestamp' => current_time( 'mysql' ), 'total' => $total, 'due' => $due, 'overdue' => $overdue, ); // Keep only the last MAX_LOG_ENTRIES. if ( count( $log ) > self::MAX_LOG_ENTRIES ) { $log = array_slice( $log, -self::MAX_LOG_ENTRIES ); } update_option( self::SCAN_LOG_OPTION_KEY, $log ); } /** * Send digest email to configured recipient(s). * * @param array $due_peptides Peptides due for review. * @param array $overdue_peptides Peptides overdue for review. * @return void */ private static function send_digest_email( array $due_peptides, array $overdue_peptides ): void { $recipients = get_option( 'pr_core_verification_email', '' ); if ( empty( $recipients ) ) { return; } // Parse comma-separated emails. $emails = array_map( 'trim', explode( ',', $recipients ) ); $emails = array_filter( $emails, static function ( $email ) { return is_email( $email ); } ); if ( empty( $emails ) ) { return; } $subject = sprintf( '[Peptide Repo] Verification digest: %d due, %d overdue', count( $due_peptides ), count( $overdue_peptides ) ); $body = self::build_email_body( $due_peptides, $overdue_peptides ); wp_mail( $emails, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) ); } /** * Build HTML email body for digest. * * @param array $due_peptides Peptides due for review. * @param array $overdue_peptides Peptides overdue for review. * @return string HTML email body. */ private static function build_email_body( array $due_peptides, array $overdue_peptides ): string { $body = '

Verification Digest

'; if ( ! empty( $overdue_peptides ) ) { $body .= '

Overdue for review (' . count( $overdue_peptides ) . ')

    '; foreach ( $overdue_peptides as $dto ) { $edit_url = get_edit_post_link( $dto->id ); $body .= sprintf( '
  • %s
  • ', esc_url( $edit_url ), esc_html( $dto->title ) ); } $body .= '
'; } if ( ! empty( $due_peptides ) ) { $body .= '

Due for review (' . count( $due_peptides ) . ')

    '; foreach ( $due_peptides as $dto ) { $edit_url = get_edit_post_link( $dto->id ); $body .= sprintf( '
  • %s
  • ', esc_url( $edit_url ), esc_html( $dto->title ) ); } $body .= '
'; } $body .= '

View verification dashboard

'; return $body; } }