$c['title'], 'existingRegions' => $c['existing'], 'proposed' => array_map(fn(int $i) => $c['els'][$i]['name'], $c['regions']), 'images' => array_map(fn(array $i) => ['src' => $i['src'], 'hasAlt' => $i['hasAlt'], 'inRegion' => $i['inRegion'], 'tagged' => $i['tagged']], $c['images']), 'refs' => $c['refs'], 'seo' => $c['seo'], 'navs' => array_values(array_map( fn(array $n) => ['menu' => $n['menu'] ?? ($allNavs ? $n['name'] : $n['slot']), 'links' => count($n['links']), 'tagged' => $n['menu'] !== null, 'sig' => md5(implode("\n", array_column($n['links'], 'url')))], array_filter($c['navs'], fn(array $n) => $n['menu'] !== null || ($allNavs ? $n['name'] : $n['slot']) !== null) )), ]; } /** Every navigation block in a page with its byte offsets, links and any data-neo-menu name (used to replace menus in place). */ public static function navs(string $html): array { return self::navList($html, self::elements($html)); } /** * Insert the requested markers into a page. * * @param array $options Booleans: content, images, seo, menus; allNavs also names navs that are not clearly header or footer menus. * @return array|null ['html' => string, 'changes' => string[], 'menus' => [name => links]], or null when the page needs no change. */ public static function apply(string $html, string $editableClass, array $options): ?array { $c = self::scan($html, $editableClass, !empty($options['content'])); $edits = []; $changes = []; if (!empty($options['content'])) { foreach ($c['regions'] as $i) { $edit = self::classEdit($html, $c['els'][$i], $editableClass); if ($edit) { $edits[] = $edit; $changes[] = "Made <{$c['els'][$i]['name']}> editable"; } } } if (!empty($options['images'])) { foreach ($c['images'] as $img) { if (!$img['inRegion'] && !$img['tagged'] && $img['taggable']) { $edits[] = [$img['insertAt'], ' data-neo-image']; $changes[] = !empty($img['placeholder']) ? 'Made image placeholder editable' . ($img['label'] !== '' ? ': ' . $img['label'] : '') : 'Made image editable: ' . $img['src']; } } } if (!empty($options['seo']) && ($seo = self::seoEdit($html, $c))) { $edits[] = $seo['edit']; array_push($changes, ...$seo['changes']); } $menus = []; if (!empty($options['menus'])) { $named = array_filter(array_column($c['navs'], 'menu')); foreach ($c['navs'] as $nav) { $name = !empty($options['allNavs']) ? $nav['name'] : $nav['slot']; // One nav per name per page, and never over a menu the page already marks by hand. if ($nav['menu'] === null && $name !== null && !isset($menus[$name]) && !in_array($name, $named, true)) { $edits[] = [$nav['insertAt'], ' data-neo-menu="' . $name . '"']; $changes[] = 'Marked ' . $name . ' navigation as a menu (' . count($nav['links']) . ' links)'; $menus[$name] = $nav['links']; } } } if (!$edits) { return null; } usort($edits, fn(array $a, array $b) => $b[0] <=> $a[0]); foreach ($edits as [$offset, $text]) { $html = substr($html, 0, $offset) . $text . substr($html, $offset); } return ['html' => $html, 'changes' => $changes, 'menus' => $menus]; } /** Navigation blocks: