]*\bdata-aiwest-layout-item\s*=\s*(["\'])1\1[^>]*>/i', '/<\/article>\s*/i', ]; return preg_replace($patterns, '', $content); } public static function inventory($content) { $content = self::decompose((string) $content); preg_match_all('/]*>(.*?)<\/h\1>/is', $content, $headings, PREG_SET_ORDER); preg_match_all('/]*\bhref\s*=\s*(["\'])(.*?)\1/is', $content, $links, PREG_SET_ORDER); $heading_list = []; foreach ($headings as $heading) { $heading_list[] = strtolower($heading[1] . ':' . trim(wp_strip_all_tags($heading[2]))); } $link_list = []; foreach ($links as $link) $link_list[] = html_entity_decode(trim($link[2]), ENT_QUOTES, 'UTF-8'); return [ 'text_hash' => hash('sha256', self::visible_text($content)), 'headings_hash' => hash('sha256', wp_json_encode($heading_list)), 'links_hash' => hash('sha256', wp_json_encode($link_list)), 'headings' => count($heading_list), 'links' => count($link_list), 'lists' => preg_match_all('/<(?:ul|ol)\b/i', $content), 'tables' => preg_match_all('/ preg_match_all('/' . '' . $html . ''; } /** Keep a Gutenberg heading block comment attached to the H2 it opens. */ protected static function section_parts($content) { $offsets = self::heading_offsets($content, 2); if (!$offsets) return [$content]; $parts = []; $cursor = 0; foreach ($offsets as $offset) { if ($offset > $cursor) $parts[] = substr($content, $cursor, $offset - $cursor); $cursor = $offset; } if ($cursor < strlen($content)) $parts[] = substr($content, $cursor); return array_values(array_filter($parts, static function ($part) { return $part !== ''; })); } protected static function componentize_h3_groups($html, $role) { $offsets = self::heading_offsets($html, 3); if (count($offsets) < 2) return $html; $offset = $offsets[0]; $prefix = substr($html, 0, $offset); $items = []; foreach ($offsets as $index => $item_offset) { $end = $offsets[$index + 1] ?? strlen($html); $items[] = substr($html, $item_offset, $end - $item_offset); } $grid = '' . ''; foreach ($items as $item) { $grid .= '' . '' . $item . ''; } return $prefix . $grid . ''; } protected static function heading_offsets($content, $level) { preg_match_all('/]*>/i', $content, $matches, PREG_OFFSET_CAPTURE); $offsets = []; foreach ($matches[0] ?? [] as $heading) { $offset = intval($heading[1]); $prefix = substr($content, 0, $offset); if (preg_match('/).)*-->\s*$/is', $prefix, $comment, PREG_OFFSET_CAPTURE)) $offset = intval($comment[0][1]); $offsets[$offset] = true; } $offsets = array_keys($offsets); sort($offsets, SORT_NUMERIC); return $offsets; } protected static function classify($html, $index, $profile) { preg_match('/]*>(.*?)<\/h2>/is', $html, $heading_match); $heading = self::lower(trim(wp_strip_all_tags($heading_match[1] ?? ''))); $h3_count = preg_match_all('/]*>\s*(?:<[^>]+>\s*)*[0-9]+[\.)\-:]/i', $html); if (preg_match('/\b(faq|frequently asked|questions and answers|common questions)\b/u', $heading)) return 'faq'; if (preg_match('/\b(request|contact|start now|get started|download|subscribe|early access|donate|join)\b/u', $heading)) return 'cta'; if (preg_match('/\b(related|resources|explore|further reading|see also)\b/u', $heading)) return 'related'; if (preg_match('/\b(vs\.?|versus|comparison|compare|differences?)\b/u', $heading) || preg_match('/= 2 || preg_match('/\b(how .* works?|steps?|process|workflow|getting started)\b/u', $heading)) return 'steps'; if ($h3_count >= 2) return 'cards'; if ($index === 0 && $profile === 'landing') return 'hero'; return ($index % 2 === 0) ? 'content' : 'soft'; } protected static function visible_text($html) { $text = html_entity_decode(wp_strip_all_tags((string) $html, true), ENT_QUOTES, 'UTF-8'); $text = preg_replace('/\s+/u', ' ', trim($text)); return $text; } protected static function lower($text) { return function_exists('mb_strtolower') ? mb_strtolower((string) $text, 'UTF-8') : strtolower((string) $text); } }