SCHWEIS
Server: LiteSpeed
System: Linux premium103.web-hosting.com 4.18.0-553.44.1.lve.el8.x86_64 #1 SMP Thu Mar 13 14:29:12 UTC 2025 x86_64
User: aaasepid (956)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //proc/self/cwd/new/wp-content/plugins/extendify/app/QuickEdit/Services/NavRefTagger.php
<?php

namespace Extendify\QuickEdit\Services;

defined('ABSPATH') || die('No direct access.');

use Extendify\Config;

// Surfaces the wp_navigation ref + per-item index so the front-end can
// route nav-item edits to WPNavigationController. Without this, nav items
// inside ref-based navigations 409 on save (the parsed core/navigation has
// empty innerBlocks; items live in a separate wp_navigation CPT).
class NavRefTagger
{
    // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound -- 7.0 floor: no const visibility
    const ATTR_REF = 'data-extendify-quick-edit-nav-ref';
    // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound -- 7.0 floor: no const visibility
    const ATTR_ITEM_INDEX = 'data-extendify-quick-edit-nav-item-index';

    // Stack so nested navigations (submenus) get their own counter.
    private static $navStack = [];

    public static function init()
    {
        add_action('template_redirect', [self::class, 'reset']);
        add_filter('pre_render_block', [self::class, 'onPreRenderBlock'], 10, 2);
        // Priority 9 runs before TagTemplateParts (10) so the agent's first-tag
        // matcher isn't thrown off by our attr on the nav wrapper.
        add_filter('render_block', [self::class, 'onRenderBlock'], 9, 2);
    }

    public static function reset()
    {
        self::$navStack = [];
    }

    public static function onRenderBlock(string $html, array $block): string
    {
        if (is_admin() || !is_string($html) || $html === '') {
            return $html;
        }
        if (!is_user_logged_in() || !current_user_can(Config::$requiredCapability)) {
            return $html;
        }

        $name = $block['blockName'] ?? '';

        if ($name === 'core/navigation') {
            $ref = (int) ($block['attrs']['ref'] ?? 0);
            $html = self::tagFirstElement(
                $html,
                self::ATTR_REF,
                $ref > 0 ? (string) $ref : ''
            );
            // Pop after tagging; inner items have all rendered by now.
            if (!empty(self::$navStack)) {
                array_pop(self::$navStack);
            }
            return $html;
        }

        if ($name === 'core/navigation-link' || $name === 'core/navigation-submenu') {
            if (empty(self::$navStack)) {
                return $html;
            }
            $top = count(self::$navStack) - 1;
            $idx = self::$navStack[$top]['counter'];
            self::$navStack[$top]['counter']++;
            $html = self::tagFirstElement($html, self::ATTR_ITEM_INDEX, (string) $idx);
            return $html;
        }

        return $html;
    }

    // Push the frame BEFORE the navigation's render_callback runs so
    // inner items can find it on render_block.
    public static function onPreRenderBlock($pre, array $parsed_block)
    {
        if (($parsed_block['blockName'] ?? '') !== 'core/navigation') {
            return $pre;
        }
        $ref = (int) ($parsed_block['attrs']['ref'] ?? 0);
        self::$navStack[] = [
            'ref' => $ref,
            'counter' => 0,
        ];
        return $pre; // never short-circuit
    }

    private static function tagFirstElement(string $html, string $attr, string $value): string
    {
        if ($value === '') {
            return $html;
        }
        $tp = new \WP_HTML_Tag_Processor($html);
        if ($tp->next_tag()) {
            if ($tp->get_attribute($attr) !== null) {
                return $html;
            }
            $tp->set_attribute($attr, $value);
            return $tp->get_updated_html();
        }
        return $html;
    }
}