factory = $factory; } /** * @param array $result Core inspection result. * @return array */ public function core( array $result, string $timestamp ): array { $problems = (int) $result['missing_count'] + (int) $result['mismatched_count'] + (int) $result['additional_count']; $partial = (int) $result['skipped_count'] > 0 || true === $result['lists_truncated']; $status = $problems > 0 ? 'fail' : ( $partial ? 'warning' : 'pass' ); $total = (int) $result['considered_count']; $observed = max( 0, $total - (int) $result['skipped_count'] ); $objects = array(); foreach ( array_merge( $result['missing'], $result['mismatched'] ) as $path ) { $objects[] = array( 'type' => 'core_file', 'reference' => (string) $path, ); } return $this->factory->build( // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Contract IDs are lowercase. 'wordpress.integrity', $status, array( 'service' => 'wordpress_org_core_checksums', 'queried_objects' => array( 'wordpress_core_release' => 1, 'expected_files' => $total, ), 'result_summary' => array( 'matched_files' => (int) $result['matched'], 'missing_files' => (int) $result['missing_count'], 'mismatched_files' => (int) $result['mismatched_count'], 'additional_files' => (int) $result['additional_count'], 'skipped_files' => (int) $result['skipped_count'], ), 'response_date' => gmdate( 'Y-m-d' ), 'limitations' => array( 'root_additional_files_not_enumerated', 'wp_content_excluded', 'official_release_files_only' ), ), $observed, $total, $partial ? 'partial' : 'complete', $timestamp, $objects ); } /** * @param array{id:string,slug:string,name:string,version:string,state:string,root:string} $plugin Selected local plugin. * @param array|null $result Plugin inspection or unsupported result. * @return array */ public function plugin( array $plugin, ?array $result, string $timestamp ): array { if ( null === $result ) { return $this->factory->build( 'components.integrity', 'not_tested', array( 'service' => 'wordpress_org_plugin_checksums', 'queried_objects' => array( 'selected_plugins' => 1 ), 'result_summary' => array( 'checksum_set' => 'unavailable' ), 'response_date' => gmdate( 'Y-m-d' ), 'limitations' => array( 'wordpress_org_checksum_set_unavailable', 'themes_not_supported' ), ), 0, 1, 'none', $timestamp, array( array( 'type' => 'plugin', 'reference' => $plugin['name'] . ' @ ' . $plugin['version'], ), ) ); } $problems = (int) $result['missing_count'] + (int) $result['mismatched_count'] + (int) $result['additional_count']; $partial = (int) $result['skipped_count'] > 0 || true === $result['lists_truncated']; $status = $problems > 0 ? 'fail' : ( $partial ? 'warning' : 'pass' ); $total = (int) $result['expected_count']; $observed = max( 0, $total - (int) $result['skipped_count'] ); return $this->factory->build( 'components.integrity', $status, array( 'service' => 'wordpress_org_plugin_checksums', 'queried_objects' => array( 'selected_plugins' => 1, 'expected_files' => $total, ), 'result_summary' => array( 'plugin_name' => $plugin['name'], 'plugin_version' => $plugin['version'], 'matched_files' => (int) $result['matched'], 'missing_files' => (int) $result['missing_count'], 'mismatched_files' => (int) $result['mismatched_count'], 'additional_files' => (int) $result['additional_count'], 'skipped_files' => (int) $result['skipped_count'], ), 'response_date' => gmdate( 'Y-m-d' ), 'limitations' => array( 'selected_plugin_only', 'official_wordpress_org_release_only', 'themes_not_supported' ), ), $observed, $total, $partial ? 'partial' : 'complete', $timestamp, array( array( 'type' => 'plugin', 'reference' => $plugin['name'] . ' @ ' . $plugin['version'], ), ) ); } /** * @param array $result Vulnerability match result. * @return array */ public function vulnerabilities( array $result, string $timestamp ): array { $partial = (int) $result['relevant_unsupported'] > 0 || true === $result['matches_truncated'] || true === $result['inventory_truncated']; $actionable = 0; $informational = 0; $objects = array(); foreach ( $result['matches'] as $match ) { if ( true === $match['informational'] ) { ++$informational; } else { ++$actionable; } $objects[] = array( 'type' => (string) $match['component_type'], 'reference' => (string) $match['component_name'] . ' @ ' . (string) $match['component_version'] . ' / ' . (string) $match['advisory_id'], ); } $status = $actionable > 0 ? 'fail' : ( $informational > 0 || $partial ? 'warning' : 'pass' ); $total = (int) $result['components_count']; $observed = min( $total, (int) $result['components_observed'] ); if ( (int) $result['relevant_unsupported'] > 0 ) { $observed = max( 0, $observed - 1 ); } return $this->factory->build( 'external.vulnerability_intelligence', $status, array( 'service' => 'wordfence_vulnerability_feed_v3', 'queried_objects' => array( 'locally_matched_components' => (int) $result['components_observed'], 'transmitted_inventory_items' => 0, ), 'result_summary' => array( 'feed_records' => (int) $result['feed_records'], 'matched_advisories' => (int) $result['matches_count'], 'actionable_matches' => $actionable, 'informational_matches' => $informational, 'matches' => $result['matches'], ), 'attribution' => array( 'provider' => 'Wordfence Intelligence', 'license' => self::WORDFENCE_LICENSE, 'license_url' => self::WORDFENCE_LICENSE_URL, ), 'response_date' => gmdate( 'Y-m-d' ), 'limitations' => array( 'wordfence_scanner_feed', 'local_version_range_match_not_exploitability', 'custom_components_may_be_uncovered', 'maximum_500_local_components' ), ), $observed, $total, $partial ? 'partial' : 'complete', $timestamp, $objects ); } /** * @return array */ public function error( string $check_id, string $service, string $timestamp ): array { return $this->factory->build( $check_id, 'error', array( 'service' => $service, 'queried_objects' => array( 'observed' => 0 ), 'result_summary' => array( 'state' => 'request_or_adapter_failed' ), 'response_date' => gmdate( 'Y-m-d' ), 'limitations' => array( 'no_remote_error_body_stored', 'no_pass_or_fail_conclusion' ), ), 0, 1, 'error', $timestamp ); } }