7) { return null; } if (is_array($value)) { $result = []; foreach (array_slice($value, 0, 40, true) as $key => $item) { if (in_array($key, ['css', 'src', 'url', 'content', 'fontFace'], true)) { continue; } $result[$key] = self::clean($item, $depth + 1); } return $result; } if (is_string($value)) { return sanitize_text_field(mb_substr($value, 0, 300)); } return is_scalar($value) || null === $value ? $value : null; } private static function files(): array { $roots = ['theme' => get_stylesheet_directory()]; if (get_template_directory() !== get_stylesheet_directory()) { $roots['parent'] = get_template_directory(); } $files = []; $truncated = false; $visited = 0; foreach ($roots as $origin => $directory) { $root = realpath($directory); if (!$root || is_link($directory)) { continue; } $walk = static function (string $dir, string $relative = '', int $depth = 0) use ( &$walk, &$files, &$truncated, &$visited, $root, $origin, ): void { if ($depth > 10 || count($files) >= 400 || $visited >= 5000) { $truncated = true; return; } $names = @scandir($dir); $names = is_array($names) ? $names : []; usort($names, static function ($a, $b) { $rank = static fn($name) => in_array( $name, ['style.css', 'theme.json', 'functions.php', 'index.php', 'header.php', 'footer.php'], true, ) ? 0 : (in_array($name, ['templates', 'parts', 'patterns'], true) ? 1 : 2); return $rank($a) <=> $rank($b) ?: strcmp($a, $b); }); foreach ($names as $name) { if (++$visited > 5000) { $truncated = true; break; } if ($name[0] === '.' || in_array($name, ['node_modules', 'vendor', 'cache'], true)) { continue; } $path = $dir . DIRECTORY_SEPARATOR . $name; if (is_link($path)) { continue; } $next = $relative . $name; if (is_dir($path)) { $walk($path, $next . '/', $depth + 1); continue; } $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); if ( !in_array($ext, ['css', 'scss', 'sass', 'less', 'json', 'php', 'html', 'htm'], true) ) { continue; } if (count($files) >= 400) { $truncated = true; break; } clearstatcache(true, $path); $stat = @stat($path); if (!$stat || $stat['nlink'] > 1 || $stat['size'] > 262144 || !is_file($path)) { $truncated = true; continue; } $resolved = realpath($path); if (!$resolved || !str_starts_with($resolved, $root . DIRECTORY_SEPARATOR)) { continue; } $files[$origin . '/' . $next] = [ 'absolute' => $resolved, 'origin' => $origin, 'path' => $next, 'kind' => $ext, 'size' => $stat['size'], ]; } }; $walk($root); } ksort($files); return [$files, $truncated]; } private static function entry(array $file, string $hash): array { $path = $file['path']; $entry = ['path' => $path, 'origin' => $file['origin'], 'kind' => $file['kind'], 'sha256' => $hash]; $entry['role'] = preg_match( '~(?:^|/)(header|footer|front-page|home|single|page|archive|search|404)(?:[.-]|$)~', $path, $role, ) ? $role[1] : (preg_match('~^(?:templates|parts|patterns)/~', $path) ? explode('/', $path)[0] : (str_ends_with($path, '.json') ? 'settings' : (preg_match('/\.(css|scss|sass|less)$/', $path) ? 'styles' : 'render'))); $content = @file_get_contents($file['absolute']); if (!is_string($content) || str_contains($content, "\0")) { return $entry; } if (in_array($file['kind'], ['css', 'scss', 'sass', 'less'], true)) { preg_match_all('/([^{}]+)\{([^{}]*)\}/s', $content, $matches, PREG_OFFSET_CAPTURE); $selectors = []; foreach (array_slice($matches[1], 0, 24) as $index => $match) { $selector = trim(preg_replace('~/\*.*?\*/~s', '', $match[0])); if (!$selector || str_starts_with($selector, '@')) { continue; } preg_match_all('/(?:^|;)\s*(--[\w-]+|[a-z-]+)\s*:/i', $matches[2][$index][0], $properties); $selectors[] = [ 'selector' => sanitize_text_field(mb_substr($selector, 0, 220)), 'line' => substr_count(substr($content, 0, $match[1]), "\n") + 1, 'properties' => array_slice(array_values(array_unique($properties[1])), 0, 16), ]; } $entry['selectors'] = $selectors; } preg_match_all( '~(?:@(?:import|use|forward)\s+|get_template_part\s*\(\s*|(?:require|include)(?:_once)?\s*\(?\s*)[\x27"]([a-zA-Z0-9_./-]{1,160})[\x27"]~', $content, $links, ); $entry['references'] = array_values(array_slice(array_unique($links[1]), 0, 16)); if (in_array($file['kind'], ['php', 'html', 'htm'], true)) { preg_match_all( '/\b(the_title|the_content|get_header|get_footer|get_template_part|wp_head|wp_footer|wp_nav_menu)\s*\(|