From 68ae5b9b1a1d157986611df00cea0a01169e8f89 Mon Sep 17 00:00:00 2001 From: peptiderepo <273788462+peptiderepo@users.noreply.github.com> Date: Sat, 13 Jun 2026 09:42:55 +0000 Subject: [PATCH] PHPCS compliance: fix pre-existing violations exposed by gating the lint step All violations were present in v0.1.0 and hidden by continue-on-error. Now that PHPCS gates the build (P2-B), they must be fixed. - Add file-level @package doc comment to all PHP files (file doc requirement) - Add short descriptions to @var and constructor docblocks throughout - Fix @param type spacing throughout (PHPCS alignment rules) - Expand phpcs:ignore lines to include NoCaching where DirectQuery is already suppressed (uninstall.php, cost-ledger, table-manager, collector) - Suppress EscapeOutput.ExceptionNotEscaped on RuntimeException messages (exception strings are not HTML output) - Suppress error_log_error_log on the probe-runner failure log - Replace date() with gmdate() in panel (timezone-aware per WP standard) - Convert inline associative arrays to multi-line in config (WP array style) - Fix double-arrow alignment in probe-runner counts and insert arrays - Fix double-space before ?? in provider constructors - Expand phpcs:ignore for reads to include MissingUnslash - Fix $counts++ to ++$counts (prefix increment per WP standard) - Fix V1/v1 capitalisation in interface doc long description Agent-Session: pr-vision rename v0.1.1 --- .../class-prv-ai-visibility-collector.php | 7 ++- .../interface-prv-data-collector.php | 3 +- includes/core/class-prv-activator.php | 1 + includes/core/class-prv-admin-page.php | 17 +++--- includes/core/class-prv-autoloader.php | 1 + .../core/class-prv-collector-registry.php | 7 ++- includes/core/class-prv-config.php | 61 +++++++++++++++---- includes/core/class-prv-cost-ledger.php | 7 ++- includes/core/class-prv-cron.php | 1 + includes/core/class-prv-deactivator.php | 1 + includes/core/class-prv-plugin.php | 1 + includes/core/class-prv-probe-result.php | 23 +++++-- includes/core/class-prv-probe-runner.php | 52 +++++++++------- includes/core/class-prv-table-manager.php | 3 +- .../panel/class-prv-ai-visibility-panel.php | 15 +++-- .../panel/interface-prv-dashboard-panel.php | 1 + .../providers/class-prv-citation-detector.php | 1 + .../providers/class-prv-gateway-client.php | 5 +- .../class-prv-openrouter-provider.php | 23 +++++-- .../class-prv-perplexity-provider.php | 23 +++++-- .../interface-prv-probe-provider.php | 1 + pr-vision.php | 13 ++-- uninstall.php | 5 +- 23 files changed, 189 insertions(+), 83 deletions(-) diff --git a/includes/collector/class-prv-ai-visibility-collector.php b/includes/collector/class-prv-ai-visibility-collector.php index d45bfe5..cb4fbe3 100644 --- a/includes/collector/class-prv-ai-visibility-collector.php +++ b/includes/collector/class-prv-ai-visibility-collector.php @@ -1,4 +1,5 @@ (string) $row['run_id'], + 'run_id' => (string) $row['run_id'], 'captured_at' => (string) $row['captured_at'], 'score' => $this->compute_score( (int) $row['cited_count'], diff --git a/includes/collector/interface-prv-data-collector.php b/includes/collector/interface-prv-data-collector.php index 8a2dd33..c77c647 100644 --- a/includes/collector/interface-prv-data-collector.php +++ b/includes/collector/interface-prv-data-collector.php @@ -1,10 +1,11 @@ self::MENU_SLUG, - 'prv_run_done' => 1, - 'prv_probed' => (int) $counts['probed'], - 'prv_skipped' => (int) ( $counts['skipped_budget'] + $counts['skipped_error'] ), + 'page' => self::MENU_SLUG, + 'prv_run_done' => 1, + 'prv_probed' => (int) $counts['probed'], + 'prv_skipped' => (int) ( $counts['skipped_budget'] + $counts['skipped_error'] ), ), admin_url( 'admin.php' ) ); @@ -183,10 +184,10 @@ class PRV_Admin_Page { if ( empty( $_GET['prv_run_done'] ) ) { return; } - // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $probed = (int) ( $_GET['prv_probed'] ?? 0 ); - // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $skipped = (int) ( $_GET['prv_skipped'] ?? 0 ); + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $probed = isset( $_GET['prv_probed'] ) ? absint( wp_unslash( $_GET['prv_probed'] ) ) : 0; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $skipped = isset( $_GET['prv_skipped'] ) ? absint( wp_unslash( $_GET['prv_skipped'] ) ) : 0; echo '

'; echo esc_html( diff --git a/includes/core/class-prv-autoloader.php b/includes/core/class-prv-autoloader.php index b48b1ce..62ea42b 100644 --- a/includes/core/class-prv-autoloader.php +++ b/includes/core/class-prv-autoloader.php @@ -1,4 +1,5 @@ 'bpc-157', 'label' => 'BPC-157' ), - array( 'slug' => 'tb-500', 'label' => 'TB-500' ), - array( 'slug' => 'mk-677', 'label' => 'MK-677' ), - array( 'slug' => 'cjc-1295', 'label' => 'CJC-1295' ), - array( 'slug' => 'ghrp-6', 'label' => 'GHRP-6' ), - array( 'slug' => 'ipamorelin', 'label' => 'Ipamorelin' ), - array( 'slug' => 'semaglutide', 'label' => 'Semaglutide' ), - array( 'slug' => 'tirzepatide', 'label' => 'Tirzepatide' ), - array( 'slug' => 'selank', 'label' => 'Selank' ), - array( 'slug' => 'semax', 'label' => 'Semax' ), - array( 'slug' => 'nad-plus', 'label' => 'NAD+' ), - array( 'slug' => 'aod-9604', 'label' => 'AOD-9604' ), + array( + 'slug' => 'bpc-157', + 'label' => 'BPC-157', + ), + array( + 'slug' => 'tb-500', + 'label' => 'TB-500', + ), + array( + 'slug' => 'mk-677', + 'label' => 'MK-677', + ), + array( + 'slug' => 'cjc-1295', + 'label' => 'CJC-1295', + ), + array( + 'slug' => 'ghrp-6', + 'label' => 'GHRP-6', + ), + array( + 'slug' => 'ipamorelin', + 'label' => 'Ipamorelin', + ), + array( + 'slug' => 'semaglutide', + 'label' => 'Semaglutide', + ), + array( + 'slug' => 'tirzepatide', + 'label' => 'Tirzepatide', + ), + array( + 'slug' => 'selank', + 'label' => 'Selank', + ), + array( + 'slug' => 'semax', + 'label' => 'Semax', + ), + array( + 'slug' => 'nad-plus', + 'label' => 'NAD+', + ), + array( + 'slug' => 'aod-9604', + 'label' => 'AOD-9604', + ), ); } diff --git a/includes/core/class-prv-cost-ledger.php b/includes/core/class-prv-cost-ledger.php index 8165bfa..a1c7fb9 100644 --- a/includes/core/class-prv-cost-ledger.php +++ b/includes/core/class-prv-cost-ledger.php @@ -1,4 +1,5 @@ get_month_to_date_usd(); + $cap = PRV_Config::get_monthly_budget_usd(); + $spent = $this->get_month_to_date_usd(); return ( $spent + $estimated_cost_usd ) <= $cap; } @@ -95,7 +96,7 @@ class PRV_Cost_Ledger { $table = PRV_Table_Manager::get_table_name(); - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $result = $wpdb->update( $table, array( 'cost_usd' => number_format( $cost_usd, 8, '.', '' ) ), diff --git a/includes/core/class-prv-cron.php b/includes/core/class-prv-cron.php index 9816ea5..dcf1f52 100644 --- a/includes/core/class-prv-cron.php +++ b/includes/core/class-prv-cron.php @@ -1,4 +1,5 @@ 0, - 'skipped_budget' => 0, - 'skipped_error' => 0, + 'probed' => 0, + 'skipped_budget' => 0, + 'skipped_error' => 0, ); foreach ( $peptides as $peptide ) { @@ -66,22 +71,23 @@ class PRV_Probe_Runner { $provider = $this->resolve_provider( $model ); if ( null === $provider || ! $provider->is_configured() ) { - $counts['skipped_error']++; + ++$counts['skipped_error']; continue; } // Budget pre-check. $estimated = $this->get_estimated_cost( $model ); if ( ! $this->ledger->can_afford( $estimated ) ) { - $counts['skipped_budget']++; + ++$counts['skipped_budget']; continue; } try { $result = $provider->probe( $query ); } catch ( \Exception $e ) { - error_log( sprintf( 'PRV: probe failed [%s / %s]: %s', $model, $peptide['slug'], $e->getMessage() ) ); - $counts['skipped_error']++; + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( sprintf( 'PRV: probe failed [%s / %s]: %s', $model, $peptide['slug'], $e->getMessage() ) ); + ++$counts['skipped_error']; continue; } @@ -90,7 +96,7 @@ class PRV_Probe_Runner { $this->ledger->update_row_cost( $row_id, $result->get_cost_usd() ); } - $counts['probed']++; + ++$counts['probed']; } } } @@ -101,11 +107,11 @@ class PRV_Probe_Runner { /** * Persist a single probe result to the database. * - * @param string $run_id UUID for this run. - * @param array{slug:string, label:string} $peptide Peptide config. - * @param string $model Model identifier. - * @param string $intent_tpl Raw intent template string. - * @param PRV_Probe_Result $result Probe result. + * @param string $run_id UUID for this run. + * @param array $peptide Peptide config with slug and label. + * @param string $model Model identifier. + * @param string $intent_tpl Raw intent template string. + * @param PRV_Probe_Result $result Probe result. * * @return int Inserted row ID, or 0 on failure. */ @@ -118,17 +124,17 @@ class PRV_Probe_Runner { $wpdb->insert( $table, array( - 'run_id' => $run_id, - 'captured_at' => current_time( 'mysql', true ), - 'peptide_slug' => $peptide['slug'], - 'peptide_label' => $peptide['label'], - 'model' => $model, - 'prompt_intent' => $intent_tpl, - 'cited' => $result->is_cited() ? 1 : 0, - 'our_position' => $result->get_our_position(), + 'run_id' => $run_id, + 'captured_at' => current_time( 'mysql', true ), + 'peptide_slug' => $peptide['slug'], + 'peptide_label' => $peptide['label'], + 'model' => $model, + 'prompt_intent' => $intent_tpl, + 'cited' => $result->is_cited() ? 1 : 0, + 'our_position' => $result->get_our_position(), 'source_domains' => wp_json_encode( $result->get_source_domains() ), - 'raw_excerpt' => $result->get_raw_excerpt(), - 'cost_usd' => number_format( $result->get_cost_usd(), 8, '.', '' ), + 'raw_excerpt' => $result->get_raw_excerpt(), + 'cost_usd' => number_format( $result->get_cost_usd(), 8, '.', '' ), ), array( '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s', '%s' ) ); diff --git a/includes/core/class-prv-table-manager.php b/includes/core/class-prv-table-manager.php index 020f52f..81592c4 100644 --- a/includes/core/class-prv-table-manager.php +++ b/includes/core/class-prv-table-manager.php @@ -1,4 +1,5 @@ prefix . self::TABLE_BASE; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); } } diff --git a/includes/panel/class-prv-ai-visibility-panel.php b/includes/panel/class-prv-ai-visibility-panel.php index dce78ec..3ccf5df 100644 --- a/includes/panel/class-prv-ai-visibility-panel.php +++ b/includes/panel/class-prv-ai-visibility-panel.php @@ -1,4 +1,5 @@ render_proxy_note(); $this->render_meta_bar( $last_run_at, $mtd_cost, $cap ); @@ -96,7 +97,7 @@ class PRV_Ai_Visibility_Panel implements PRV_Dashboard_Panel { $labels = array(); $scores = array(); foreach ( $trendline as $point ) { - $labels[] = esc_js( date( 'M j', strtotime( $point['captured_at'] ) ) ); + $labels[] = esc_js( gmdate( 'M j', (int) strtotime( $point['captured_at'] ) ) ); $scores[] = (float) $point['score']; } @@ -104,8 +105,10 @@ class PRV_Ai_Visibility_Panel implements PRV_Dashboard_Panel { $scores_json = wp_json_encode( $scores ); echo ''; + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo ''; echo '

'; diff --git a/includes/panel/interface-prv-dashboard-panel.php b/includes/panel/interface-prv-dashboard-panel.php index 4c06320..9375c6a 100644 --- a/includes/panel/interface-prv-dashboard-panel.php +++ b/includes/panel/interface-prv-dashboard-panel.php @@ -1,4 +1,5 @@ = 400 ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new \RuntimeException( sprintf( 'PR Vision gateway HTTP %d: %s', $status, substr( $raw_body, 0, 300 ) ) ); @@ -120,6 +122,7 @@ class PRV_Gateway_Client { remove_action( 'http_api_curl', $curl_filter, 99 ); } + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new \RuntimeException( sprintf( 'PR Vision: gateway failed after %d attempts. Last: %s', PRV_MAX_RETRIES, $last_error ) ); diff --git a/includes/providers/class-prv-openrouter-provider.php b/includes/providers/class-prv-openrouter-provider.php index f7fc425..72aebec 100644 --- a/includes/providers/class-prv-openrouter-provider.php +++ b/includes/providers/class-prv-openrouter-provider.php @@ -1,4 +1,5 @@ model = $model; - $this->gateway = $gateway ?? new PRV_Gateway_Client(); + $this->gateway = $gateway ?? new PRV_Gateway_Client(); $this->detector = $detector ?? new PRV_Citation_Detector(); } /** * {@inheritDoc} * - * @param string $query + * @param string $query The search query to probe. + * * @return PRV_Probe_Result * @throws \RuntimeException On permanent API failure. */ @@ -71,7 +81,10 @@ class PRV_OpenRouter_Provider implements PRV_Probe_Provider { $body = array( 'model' => $this->model, 'messages' => array( - array( 'role' => 'user', 'content' => $query ), + array( + 'role' => 'user', + 'content' => $query, + ), ), 'max_tokens' => 512, ); diff --git a/includes/providers/class-prv-perplexity-provider.php b/includes/providers/class-prv-perplexity-provider.php index 9ac205d..be564bb 100644 --- a/includes/providers/class-prv-perplexity-provider.php +++ b/includes/providers/class-prv-perplexity-provider.php @@ -1,4 +1,5 @@ gateway = $gateway ?? new PRV_Gateway_Client(); + $this->gateway = $gateway ?? new PRV_Gateway_Client(); $this->detector = $detector ?? new PRV_Citation_Detector(); } @@ -60,7 +67,7 @@ class PRV_Perplexity_Provider implements PRV_Probe_Provider { * Sends the query as a user message to perplexity/sonar via OpenRouter. * Parses the citations[] array from the OpenRouter response envelope. * - * @param string $query + * @param string $query The search query to probe. * * @return PRV_Probe_Result * @throws \RuntimeException On permanent API failure. @@ -69,9 +76,12 @@ class PRV_Perplexity_Provider implements PRV_Probe_Provider { $api_key = $this->resolve_api_key(); $body = array( - 'model' => self::MODEL, - 'messages' => array( - array( 'role' => 'user', 'content' => $query ), + 'model' => self::MODEL, + 'messages' => array( + array( + 'role' => 'user', + 'content' => $query, + ), ), 'max_tokens' => 512, ); @@ -135,11 +145,12 @@ class PRV_Perplexity_Provider implements PRV_Probe_Provider { /** * Retrieve the OpenRouter API key from wp-config constants. * - * @return string + * @return string The API key. * @throws \RuntimeException When the constant is not defined or empty. */ private function resolve_api_key(): string { if ( ! defined( 'PRV_OPENROUTER_API_KEY' ) || '' === PRV_OPENROUTER_API_KEY ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped throw new \RuntimeException( __( 'PRV_OPENROUTER_API_KEY constant is not defined. Add it to wp-config.php.', 'pr-vision' ) ); diff --git a/includes/providers/interface-prv-probe-provider.php b/includes/providers/interface-prv-probe-provider.php index dd6c24d..1598d1e 100644 --- a/includes/providers/interface-prv-probe-provider.php +++ b/includes/providers/interface-prv-probe-provider.php @@ -1,4 +1,5 @@ prefix . 'prv_ai_visibility'; -// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared +// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); /* ── 2. Delete all prv_ prefixed options ──────────────────────────── */ -// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery +// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'prv\_%'" );