gmdate( 'Y-m-d', time() + 366 * DAY_IN_SECONDS ) ) ) ) { throw new \InvalidArgumentException( 'Choose a valid review date from today through the next year, or leave it blank.' ); } return $value; } public static function record( Store $store, array $finding ): ?array { if ( 'finding' !== ( $finding['_kind'] ?? '' ) || 'excepted' !== ( $finding['_status'] ?? '' ) || ! FindingGuide::id( $finding ) ) { return null; } $key = Finding::exceptionKey( $finding ); $row = $store->get( 'ex_' . substr( $key, 0, 60 ) ); return $row && 'exception' === ( $row['_kind'] ?? '' ) && 'active' === ( $row['_status'] ?? '' ) && ( $row['finding_id'] ?? '' ) === $finding['_id'] && is_string( $row['key'] ?? null ) && hash_equals( $key, $row['key'] ) ? $row : null; } public static function save( Store $store, string $findingId, string $key, int $revision, string $date ): void { $actor = get_current_user_id(); $site = get_current_blog_id(); if ( ! WorkflowAuthority::administrator() || $actor < 1 || ! preg_match( '/^f_[a-f0-9]{60}$/D', $findingId ) || ! preg_match( '/^[a-f0-9]{64}$/D', $key ) || $revision < 1 ) { throw new \RuntimeException( 'Refresh this accepted finding before changing its review date.' ); } $date = self::date( $date ); $lock = $store->lock( 'exception_review_' . substr( $key, 0, 40 ), 30 ); if ( ! $lock ) { throw new \RuntimeException( 'Another review-date update is in progress. Reload and retry.' ); } try { $finding = $store->get( $findingId ); $row = $finding ? self::record( $store, $finding ) : null; if ( ! $row || $revision !== (int) $row['_revision'] || ! hash_equals( $key, $row['key'] ) ) { throw new \RuntimeException( 'The finding or accepted evidence changed. Reload before changing its review date.' ); } if ( ! WorkflowAuthority::administrator() || $actor !== get_current_user_id() || $site !== get_current_blog_id() || ! $store->ownsLock( 'exception_review_' . substr( $key, 0, 40 ), $lock ) ) { throw new \RuntimeException( 'Review-date authority changed. Reload and retry.' ); } $latest = $store->get( $findingId ); if ( $latest !== $finding ) { throw new \RuntimeException( 'The finding changed before the review date could be saved.' ); } $original = $row; $row['review_on'] = $date; global $wpdb; $table = preg_quote( $wpdb->prefix . 'avai_objects', '/' ); $checking = false; $guard = static function () use ( $store, $original, $finding, $actor, $site, $key, $lock, &$checking ): void { if ( $checking ) { throw new \RuntimeException( 'A nested review-date mutation interrupted this action.' ); } $checking = true; try { if ( ! WorkflowAuthority::administrator() || ! $store->ownsLock( 'exception_review_' . substr( $key, 0, 40 ), $lock ) || $store->get( $original['_id'] ) !== $original || $store->get( $finding['_id'] ) !== $finding ) { throw new \RuntimeException( 'Review access, evidence or its lock changed before the date could be saved.' ); } // No permission hook or database read may run after this final context pin. if ( $actor !== get_current_user_id() || $site !== get_current_blog_id() ) { throw new \RuntimeException( 'The administrator or site changed at the review-date write boundary.' ); } } finally { $checking = false; } }; $query = static function ( $sql ) use ( $table, $original, $guard ) { if ( is_string( $sql ) && str_contains( $sql, $original['_id'] ) && preg_match( '/^\s*UPDATE\s+`?' . $table . '`?\s/i', $sql ) ) { $guard(); } return $sql; }; add_filter( 'query', $query, PHP_INT_MAX ); try { $guard(); $store->put( 'exception', $row['_id'], $row, 'active', $revision ); } finally { remove_filter( 'query', $query, PHP_INT_MAX ); } $store->activity( 'exception_review_date_saved', array( 'finding_id' => $findingId, 'actor' => $actor, 'review_on' => $date ) ); } finally { $store->unlock( 'exception_review_' . substr( $key, 0, 40 ), $lock ); } } public static function field( string $id, string $value = '' ): void { echo '
' . esc_html__( 'A local reminder appears when this date arrives. It does not expire the exception, change protection, start a scan or send an email. Clear the date to remove the reminder.', 'shieldlume-security' ) . '
'; } public static function render( Store $store, array $finding ): void { $row = self::record( $store, $finding ); if ( ! $row ) { return; } $date = ''; try { $date = self::date( is_string( $row['review_on'] ?? null ) ? $row['review_on'] : '', false ); } catch ( \Throwable $error ) { /* An invalid saved date never changes protection or invents a due date. */ } echo '' . esc_html__( 'This accepted finding is due for review. Its exception remains in place until you explicitly remove it or its evidence changes.', 'shieldlume-security' ) . '
'; } echo '' . esc_html__( 'Saved reason:', 'shieldlume-security' ) . ' ' . esc_html( is_string( $row['reason'] ?? null ) ? $row['reason'] : '' ) . '
' . esc_html__( 'Accepted by administrator ID:', 'shieldlume-security' ) . ' ' . (int) ( $row['actor'] ?? 0 ) . ' · ' . esc_html( is_string( $row['created_at'] ?? null ) ? $row['created_at'] : '' ) . '
'; Admin::formStart( 'exception_review', 'findings' ); echo ''; self::field( 'review-' . $finding['_id'], $date ); submit_button( __( 'Save review date', 'shieldlume-security' ), 'secondary', 'submit', false ); echo '' . esc_html__( 'These are local reminders. Exceptions and protection settings have not changed.', 'shieldlume-security' ) . '
' . esc_html__( 'Review all accepted findings', 'shieldlume-security' ) . '
' . esc_html__( 'Shows up to five current matches from the earliest 100 dated exceptions. Older changed evidence may no longer match a current finding.', 'shieldlume-security' ) . '