|null */ private ?array $quick_check_ids = null; /** @var list|null */ private ?array $full_check_ids = null; /** @var list|null */ private ?array $deep_code_check_ids = null; /** @var list|null */ private ?array $vendor_check_ids = null; /** @var list|null */ private ?array $rendered_check_ids = null; /** @var list|null */ private ?array $vendor_candidates = null; private ReportCatalogue $catalogue; private string $hook_suffix = ''; /** * @param string $registry_path Bundled registry path. */ public function __construct( string $plugin_file, string $version, string $registry_path, ReportCatalogue $catalogue ) { $this->plugin_file = $plugin_file; $this->version = $version; $this->registry_path = $registry_path; $this->catalogue = $catalogue; } public function register(): void { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } public function add_menu(): void { $this->hook_suffix = (string) add_menu_page( __( '320px Site Audit', '320px-site-audit' ), __( 'Site Audit', '320px-site-audit' ), 'manage_options', '320px-site-audit', array( $this, 'render' ), 'dashicons-search', 81 ); } public function enqueue_assets( string $hook_suffix ): void { if ( $this->hook_suffix !== $hook_suffix ) { return; } wp_enqueue_style( 'px320-site-audit-admin', plugins_url( 'assets/admin.css', $this->plugin_file ), array(), $this->version ); wp_enqueue_script( 'px320-site-audit-rendered-inspector', plugins_url( 'assets/rendered-inspector.js', $this->plugin_file ), array(), $this->version, true ); wp_enqueue_script( 'px320-site-audit-admin', plugins_url( 'assets/admin.js', $this->plugin_file ), array( 'px320-site-audit-rendered-inspector' ), $this->version, true ); $locale = determine_locale(); $quick_check_ids = $this->quick_check_ids(); $full_check_ids = $this->full_check_ids(); $deep_code_ids = $this->deep_code_check_ids(); $vendor_ids = $this->vendor_check_ids(); $rendered_ids = $this->rendered_check_ids(); $registry = new AuditRegistry( $this->registry_path ); $same_site_origin = ( new \Px320\SiteAudit\Crawl\SameSiteUrlPolicy( home_url( '/' ) ) )->origin(); $config = array( 'restRoot' => esc_url_raw( rest_url( RunController::NAMESPACE . '/runs' ) ), 'externalRoot' => esc_url_raw( rest_url( RunController::NAMESPACE . '/external' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'version' => $this->version, 'checkIds' => $quick_check_ids, 'plans' => array( 'quick' => array( 'checkIds' => $quick_check_ids ), 'full' => array( 'checkIds' => $full_check_ids ), 'deep_code' => array( 'checkIds' => $deep_code_ids ), 'vendor' => array( 'checkIds' => $vendor_ids ), 'rendered' => array( 'checkIds' => $rendered_ids ), ), 'sameSite' => array( 'origin' => $same_site_origin, 'disclosureVersion' => RunController::SAME_SITE_DISCLOSURE_VERSION, ), 'rendered' => array( 'url' => esc_url_raw( home_url( '/' ) ), 'origin' => $same_site_origin, 'disclosureVersion' => RunController::RENDERED_DISCLOSURE_VERSION, 'timeoutMs' => 20000, ), 'external' => array( 'disclosureVersion' => ExternalController::DISCLOSURE_VERSION, ), 'defaultLanguage' => 0 === strpos( $locale, 'ru_' ) ? 'ru' : 'en', 'catalogues' => $this->catalogue->all(), 'sourceUrls' => $registry->source_urls(), 'labels' => array( 'genericError' => __( 'The audit interface could not complete this request. Refresh the page and try again.', '320px-site-audit' ), 'preparing' => __( 'Preparing the checks…', '320px-site-audit' ), 'firstCheck' => __( 'Preparing the first check', '320px-site-audit' ), // translators: %s: human-readable check title. 'completedCheck' => __( 'Completed: %s', '320px-site-audit' ), // translators: 1: completed check count, 2: planned check count. 'progressCount' => __( '%1$d of %2$d checks completed', '320px-site-audit' ), // translators: %s: elapsed time on the current progress screen. 'sessionTime' => __( 'Time on this screen: %s', '320px-site-audit' ), 'cancelled' => __( 'Audit cancelled. Completed results remain saved in this plugin.', '320px-site-audit' ), 'failed' => __( 'The audit stopped because a check could not finish safely.', '320px-site-audit' ), 'resumeTitle' => __( 'An unfinished audit was found', '320px-site-audit' ), 'resumeCopy' => __( 'Continue where it stopped. Completed checks will not run again.', '320px-site-audit' ), 'resumeAction' => __( 'Continue audit', '320px-site-audit' ), 'completedTitle' => __( 'The latest report is ready', '320px-site-audit' ), 'completedCopy' => __( 'Open the saved report or start a new audit.', '320px-site-audit' ), 'viewReport' => __( 'Open report', '320px-site-audit' ), 'quickPreflightTitle' => __( 'What happens after you start', '320px-site-audit' ), 'quickPreflightKicker' => __( 'Before you start', '320px-site-audit' ), 'quickStart' => __( 'Start express check', '320px-site-audit' ), 'fullPreflightTitle' => __( 'The main audit will open public pages on this site', '320px-site-audit' ), 'fullPreflightKicker' => __( 'What the plugin will request', '320px-site-audit' ), 'fullStart' => __( 'Start the main site audit', '320px-site-audit' ), 'deepCodePreflightTitle' => __( 'Only the listed site code will be checked', '320px-site-audit' ), 'deepCodePreflightKicker' => __( 'Which code will be read', '320px-site-audit' ), 'deepCodeStart' => __( 'Start code audit', '320px-site-audit' ), 'vendorPreflightTitle' => __( 'Only the components you select', '320px-site-audit' ), 'vendorPreflightKicker' => __( 'Which plugins and themes will be read', '320px-site-audit' ), 'vendorStart' => __( 'Check the selected plugins and themes', '320px-site-audit' ), 'renderedPreflightTitle' => __( 'The homepage will open in a protected frame in this browser', '320px-site-audit' ), 'renderedPreflightKicker' => __( 'What this browser will open', '320px-site-audit' ), 'renderedStart' => __( 'Check the homepage display', '320px-site-audit' ), 'quickProgress' => __( 'Express WordPress check', '320px-site-audit' ), 'fullProgress' => __( 'Main site and search audit', '320px-site-audit' ), 'deepCodeProgress' => __( 'Custom-code check', '320px-site-audit' ), 'vendorProgress' => __( 'Selected plugin and theme check', '320px-site-audit' ), 'renderedProgress' => __( 'Homepage display check', '320px-site-audit' ), 'quickCheckLabel' => __( 'express checks', '320px-site-audit' ), 'fullCheckLabel' => __( 'site and search checks', '320px-site-audit' ), 'deepCodeCheckLabel' => __( 'custom-code checks', '320px-site-audit' ), 'vendorCheckLabel' => __( 'checks for selected plugins and themes', '320px-site-audit' ), 'renderedCheckLabel' => __( 'homepage display checks', '320px-site-audit' ), 'quickRequestCount' => '0', 'fullRequestCount' => __( 'Up to 500', '320px-site-audit' ), 'deepCodeRequestCount' => '0', 'vendorRequestCount' => '0', 'renderedRequestCount' => '1–17', 'quickRequestLabel' => __( 'external requests', '320px-site-audit' ), 'fullRequestLabel' => __( 'requests to this site', '320px-site-audit' ), 'deepCodeRequestLabel' => __( 'external requests', '320px-site-audit' ), 'vendorRequestLabel' => __( 'external requests', '320px-site-audit' ), 'renderedRequestLabel' => __( 'requests to this site', '320px-site-audit' ), 'quickNetworkTitle' => __( 'Nothing leaves this site', '320px-site-audit' ), 'quickNetworkCopy' => __( 'This plan makes no external request and does not need an account or email address.', '320px-site-audit' ), 'fullNetworkTitle' => __( 'Only this site will be opened', '320px-site-audit' ), 'fullNetworkCopy' => __( 'The plugin opens only the public addresses listed below on this site, including six bounded diagnostic requests. It stops if a redirect leads elsewhere.', '320px-site-audit' ), 'deepCodeNetworkTitle' => __( 'Source stays on this site', '320px-site-audit' ), 'deepCodeNetworkCopy' => __( 'The code is read only on this site. The report saves issue types and protected references, but not source code or text values.', '320px-site-audit' ), 'vendorNetworkTitle' => __( 'Selected source stays on this site', '320px-site-audit' ), 'vendorNetworkCopy' => __( 'Only checked installed plugins and themes are read. The audit makes no network request and stores no source code or literal values.', '320px-site-audit' ), 'renderedNetworkTitle' => __( 'This browser will open only this site', '320px-site-audit' ), 'renderedNetworkCopy' => __( 'The browser opens the public homepage and up to 16 stylesheets from this site without login data. Scripts and all external resources are blocked.', '320px-site-audit' ), 'quickModeState' => __( 'This site only · nothing is sent', '320px-site-audit' ), 'fullModeState' => __( 'Only public pages of this site', '320px-site-audit' ), 'deepCodeModeState' => __( 'Code stays on this site · nothing is sent', '320px-site-audit' ), 'vendorModeState' => __( 'Selected code stays on this site', '320px-site-audit' ), 'renderedModeState' => __( 'This browser opens this site only', '320px-site-audit' ), 'renderedUnavailable' => __( 'This browser could not inspect the public page. The report records the missing coverage and recommends the CLI continuation.', '320px-site-audit' ), 'defaultRunHint' => __( 'You can pause or close this tab. Completed checks remain saved.', '320px-site-audit' ), 'renderedRunHint' => __( 'This one browser attempt cannot pause. Cancel it or let it finish; an interrupted attempt must return here for a new confirmation.', '320px-site-audit' ), 'renderedInterruptedTitle' => __( 'The homepage check was interrupted', '320px-site-audit' ), 'renderedInterruptedCopy' => __( 'To try again, return to the start and confirm the homepage request once more. The incomplete attempt will be cancelled.', '320px-site-audit' ), 'renderedInterruptedAction' => __( 'Return to start', '320px-site-audit' ), 'vendorSelectionRequired' => __( 'Select between 1 and 10 available plugins or themes, then confirm the scope.', '320px-site-audit' ), // translators: 1: selected component count, 2: maximum component count. 'vendorSelectionCount' => __( '%1$d of %2$d components selected', '320px-site-audit' ), 'externalConfirmationRequired' => __( 'Review the external-request disclosure and check its confirmation box first.', '320px-site-audit' ), 'externalPluginRequired' => __( 'Choose exactly one plugin before running its WordPress.org checksum request.', '320px-site-audit' ), ), ); wp_add_inline_script( 'px320-site-audit-admin', 'window.px320SiteAuditConfig = ' . wp_json_encode( $config, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . ';', 'before' ); } public function render(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to view this audit.', '320px-site-audit' ) ); } $check_count = count( $this->quick_check_ids() ); $full_check_count = count( $this->full_check_ids() ); $deep_code_count = count( $this->deep_code_check_ids() ); $vendor_count = count( $this->vendor_check_ids() ); $rendered_count = count( $this->rendered_check_ids() ); $server_count = count( \Px320\SiteAudit\Server\ServerObservationSet::check_ids() ); $vendor_candidates = $this->vendor_candidates(); $checksum_plugins = ( new PluginChecksumInventory() )->candidates(); $same_site_origin = ( new \Px320\SiteAudit\Crawl\SameSiteUrlPolicy( home_url( '/' ) ) )->origin(); $cli_language = 0 === strpos( determine_locale(), 'ru_' ) ? 'ru' : 'en'; ?>
Site Audit

  • 0

wp 320px-audit server-scan --profile=auto --language=

wp 320px-audit preflight --output=/private/preflight.json
px320_plugin_dir="$(wp plugin path 320px-site-audit --dir)"
php "$px320_plugin_dir/cli/server-audit.php" --preflight=/private/preflight.json --root="$PWD" --output=/private/server.json --profile=auto
wp 320px-audit import /private/server.json --language=

api.wordpress.org

downloads.wordpress.org

www.wordfence.com

·

export PX320_WORDFENCE_TOKEN='…'
wp 320px-audit vulnerabilities --user=<administrator-id> --language=
wp 320px-audit vulnerabilities --user=<administrator-id> --language= --confirm-network

*/ private function quick_check_ids(): array { if ( null === $this->quick_check_ids ) { $registry = new AuditRegistry( $this->registry_path ); $checkers = new CheckerRegistry( $registry ); $this->quick_check_ids = $registry->local_implemented_for_profile( 'quick', $checkers->ids() ); } return $this->quick_check_ids; } /** * @return list */ private function full_check_ids(): array { if ( null === $this->full_check_ids ) { $registry = new AuditRegistry( $this->registry_path ); $checkers = new CheckerRegistry( $registry ); $analyzers = new SnapshotAnalyzerRegistry( new FindingFactory( $registry, new FindingContract() ) ); $same_site_ids = $registry->same_site_implemented_for_profile( 'full', $analyzers->ids() ); $local_ids = $registry->local_implemented_for_profile( 'full', $checkers->ids() ); $this->full_check_ids = array_merge( $same_site_ids, $local_ids ); } return $this->full_check_ids; } /** @return list */ private function deep_code_check_ids(): array { if ( null === $this->deep_code_check_ids ) { $registry = new AuditRegistry( $this->registry_path ); $this->deep_code_check_ids = $registry->local_implemented_for_profile( 'deep_code', CodeFindingAnalyzer::ids() ); } return $this->deep_code_check_ids; } /** @return list */ private function vendor_check_ids(): array { if ( null === $this->vendor_check_ids ) { $registry = new AuditRegistry( $this->registry_path ); $this->vendor_check_ids = $registry->local_implemented_for_profile( 'vendor', CodeFindingAnalyzer::ids() ); } return $this->vendor_check_ids; } /** @return list */ private function rendered_check_ids(): array { if ( null === $this->rendered_check_ids ) { $registry = new AuditRegistry( $this->registry_path ); $analyzer = new RenderedFindingAnalyzer( new FindingFactory( $registry, new FindingContract() ) ); $this->rendered_check_ids = $registry->browser_implemented_for_profile( 'rendered', $analyzer->ids() ); } return $this->rendered_check_ids; } /** @return list */ private function vendor_candidates(): array { if ( null === $this->vendor_candidates ) { $this->vendor_candidates = ( new VendorCodeScopeProvider( ( new LocalHashKey() )->derive(), $this->plugin_file ) )->candidates(); } return $this->vendor_candidates; } /** @param array{id:string,type:string,name:string,version:string,state:string,available:bool} $candidate Candidate. */ private function vendor_candidate_meta( array $candidate ): string { $type = 'plugin' === $candidate['type'] ? __( 'Plugin', '320px-site-audit' ) : __( 'Theme', '320px-site-audit' ); $state = array( 'active' => __( 'active', '320px-site-audit' ), 'network-active' => __( 'network active', '320px-site-audit' ), 'parent' => __( 'active parent', '320px-site-audit' ), 'inactive' => __( 'inactive', '320px-site-audit' ), )[ $candidate['state'] ] ?? __( 'installed', '320px-site-audit' ); $parts = array( $type, $state ); if ( '' !== $candidate['version'] ) { $parts[] = sprintf( /* translators: %s: installed component version. */ __( 'version %s', '320px-site-audit' ), $candidate['version'] ); } if ( ! $candidate['available'] ) { $parts[] = __( 'unavailable: linked or outside the WordPress content root', '320px-site-audit' ); } return implode( ' · ', $parts ); } }