30]); if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { return new CheckResult( 'core_integrity', 'skip', 0, 15, 'high', ['error' => 'Impossible de récupérer les checksums officiels'], 'Vérifiez la connectivité vers api.wordpress.org' ); } $body = json_decode(wp_remote_retrieve_body($response), true); if (!isset($body['checksums']) || !is_array($body['checksums'])) { return new CheckResult( 'core_integrity', 'skip', 0, 15, 'high', ['error' => 'Réponse invalide de l\'API WordPress'], 'Réessayez plus tard' ); } $modified = []; $added = []; $missing = []; // Default plugins/themes under wp-content/ are routinely removed or updated // independently of core, so checksum mismatches there are NOT integrity // problems. A real core-integrity check only covers wp-admin/, wp-includes/ // and the root WordPress files. We track wp-content deltas separately (info). $contentDeltas = 0; foreach ($body['checksums'] as $file => $expectedHash) { $isCore = str_starts_with($file, 'wp-admin/') || str_starts_with($file, 'wp-includes/') || !str_contains($file, '/'); // root files: wp-*.php, index.php, xmlrpc.php… $path = ABSPATH . $file; $exists = file_exists($path); $mismatch = $exists && md5_file($path) !== $expectedHash; if (!$isCore) { if (!$exists || $mismatch) { $contentDeltas++; } continue; } if (!$exists) { $missing[] = $file; } elseif ($mismatch) { $modified[] = $file; } } // Check for unexpected files in wp-admin and wp-includes $coreDirs = ['wp-admin', 'wp-includes']; foreach ($coreDirs as $dir) { if (!is_dir(ABSPATH . $dir)) { continue; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator(ABSPATH . $dir, \RecursiveDirectoryIterator::SKIP_DOTS) ); foreach ($iterator as $file) { $relative = str_replace(ABSPATH, '', wp_normalize_path($file->getPathname())); if (!isset($body['checksums'][$relative])) { $added[] = $relative; } } } $hasCritical = false; foreach ($modified as $file) { if (strpos($file, 'wp-includes/') === 0 || strpos($file, 'wp-admin/') === 0) { $hasCritical = true; break; } } $totalIssues = count($modified) + count($added) + count($missing); $score = $totalIssues === 0 ? 15 : 0; $status = $totalIssues === 0 ? 'pass' : 'fail'; $severity = $hasCritical ? 'critical' : 'high'; return new CheckResult( 'core_integrity', $status, $score, 15, $severity, [ // Comparison base — answers "on what version/locale was this checked?" 'wp_version' => $wpVersion, 'locale' => $locale, 'scope' => 'wp-admin, wp-includes, fichiers racine (wp-content exclu)', 'modified' => array_slice($modified, 0, 50), 'added' => array_slice($added, 0, 50), 'missing' => array_slice($missing, 0, 50), 'modified_count' => count($modified), 'added_count' => count($added), 'missing_count' => count($missing), // wp-content default plugins/themes differences (informational only) 'wp_content_deltas_ignored' => $contentDeltas, ], $totalIssues === 0 ? sprintf('Cœur WordPress %s intègre (wp-admin, wp-includes, racine)', $wpVersion) : sprintf('Cœur %s : %d fichier(s) modifié(s), %d ajouté(s), %d manquant(s)', $wpVersion, count($modified), count($added), count($missing)) ); } catch (\Throwable $e) { return new CheckResult( 'core_integrity', 'skip', 15, 15, 'info', ['error' => $e->getMessage()], 'Vérification ignorée (exception interne) : ' . $e->getMessage() ); } } }