<?php

if (!class_exists('Bootstrap_Navwalker')) :

    class Bootstrap_Navwalker extends Walker_Nav_Menu
    {
        /**
         * Whether the items_wrap contains schema microdata or not.
         *
         * @since 4.2.0
         * @var boolean
         */
        private $has_schema = false;

        /**
         * Ensure the items_wrap argument contains microdata.
         *
         * @since 4.2.0
         */
        public function __construct()
        {
            if (!has_filter('wp_nav_menu_args', array($this, 'add_schema_to_navbar_ul'))) {
                add_filter('wp_nav_menu_args', array($this, 'add_schema_to_navbar_ul'));
            }
        }

        /**
         * Starts the list before the elements are added.
         *
         * @param string $output Used to append additional content (passed by reference).
         * @param int $depth Depth of menu item. Used for padding.
         * @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
         * @see Walker_Nav_Menu::start_lvl()
         *
         * @since WP 3.0.0
         *
         */
        public function start_lvl(&$output, $depth = 0, $args = null)
        {
            if (isset($args->item_spacing) && 'discard' === $args->item_spacing) {
                $t = '';
                $n = '';
            } else {
                $t = "\t";
                $n = "\n";
            }
            $indent = str_repeat($t, $depth);
            // Default class to add to the file.
            $classes = array('dropdown-menu');

            if ($depth > 0) {
                $classes[] = 'submenu';
            }

            /**
             * Filters the CSS class(es) applied to a menu list element.
             *
             * @param array $classes The CSS classes that are applied to the menu `<ul>` element.
             * @param stdClass $args An object of `wp_nav_menu()` arguments.
             * @param int $depth Depth of menu item. Used for padding.
             * @since WP 4.8.0
             *
             */
            $class_names = join(' ', apply_filters('nav_menu_submenu_css_class', $classes, $args, $depth));
            $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

            /*
             * The `.dropdown-menu` container needs to have a labelledby
             * attribute which points to it's trigger link.
             *
             * Form a string for the labelledby attribute from the the latest
             * link with an id that was added to the $output.
             */
            $labelledby = '';
            // Find all links with an id in the output.
            preg_match_all('/(<a.*?id=\"|\')(.*?)\"|\'.*?>/im', $output, $matches);
            // With pointer at end of array check if we got an ID match.
            if (end($matches[2])) {
                // Build a string to use as aria-labelledby.
                $labelledby = 'aria-labelledby="' . esc_attr(end($matches[2])) . '"';
            }
            $output .= "{$n}{$indent}<ul$class_names $labelledby>{$n}";
        }

        /**
         * Starts the element output.
         *
         * @param string $output Used to append additional content (passed by reference).
         * @param WP_Nav_Menu_Item $item Menu item data object.
         * @param int $depth Depth of menu item. Used for padding.
         * @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
         * @param int $id Current item ID.
         * @see Walker_Nav_Menu::start_el()
         *
         * @since WP 3.0.0
         * @since WP 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
         *
         */
        public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0)
        {
            if (isset($args->item_spacing) && 'discard' === $args->item_spacing) {
                $t = '';
                $n = '';
            } else {
                $t = "\t";
                $n = "\n";
            }
            $indent = ($depth) ? str_repeat($t, $depth) : '';

            if (false !== strpos($args->items_wrap, 'itemscope') && false === $this->has_schema) {
                $this->has_schema = true;
                $args->link_before = '<span itemprop="name">' . $args->link_before;
                $args->link_after .= '</span>';
            }

            $classes = empty($item->classes) ? array() : (array)$item->classes;

            // Updating the CSS classes of a menu item in the WordPress Customizer preview results in all classes defined
            // in that particular input box to come in as one big class string.
            $split_on_spaces = function ($class) {
                return preg_split('/\s+/', $class);
            };
            $classes = $this->flatten(array_map($split_on_spaces, $classes));

            /*
             * Initialize some holder variables to store specially handled item
             * wrappers and icons.
             */
            $linkmod_classes = array();
            $icon_classes = array();

            /*
             * Get an updated $classes array without linkmod or icon classes.
             *
             * NOTE: linkmod and icon class arrays are passed by reference and
             * are maybe modified before being used later in this function.
             */
            $classes = $this->separate_linkmods_and_icons_from_classes($classes, $linkmod_classes, $icon_classes, $depth);

            $a_classes = array();

            // Join any icon classes plucked from $classes into a string.
            $icon_class_string = join(' ', $icon_classes);

            /**
             * Filters the arguments for a single nav menu item.
             *
             * @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
             * @param WP_Nav_Menu_Item $item Menu item data object.
             * @param int $depth Depth of menu item. Used for padding.
             *
             * @since WP 4.4.0
             *
             * @var WP_Nav_Menu_Args
             */
            $args = apply_filters('nav_menu_item_args', $args, $item, $depth);

            // Add .dropdown or .active classes where they are needed.
            if ($this->has_children) {
                $classes[] = 'dropdown';
            }

            if (in_array('current-menu-item', $classes, true) || in_array('current-menu-parent', $classes, true)) {
                $a_classes[] = ' active';
            }

            // Add some additional default classes to the item.
            $classes[] = 'menu-item-' . $item->ID;
            $classes[] = 'nav-item';

            // Allow filtering the classes.
            $classes = apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth);

            // Form a string of classes in format: class="class_names".
            $class_names = join(' ', $classes);
            $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

            /**
             * Filters the ID applied to a menu item's list item element.
             *
             * @param string $menu_id The ID that is applied to the menu item's `<li>` element.
             * @param WP_Nav_Menu_Item $item The current menu item.
             * @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
             * @param int $depth Depth of menu item. Used for padding.
             * @since WP 3.0.1
             * @since WP 4.1.0 The `$depth` parameter was added.
             *
             */
            $id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth);
            $id = $id ? ' id="' . esc_attr($id) . '"' : '';

            $output .= $indent . '<li ' . $id . $class_names . '>';

            // Initialize array for holding the $atts for the link item.
            $atts = array();
            $atts['title'] = !empty($item->attr_title) ? $item->attr_title : '';
            $atts['target'] = !empty($item->target) ? $item->target : '';
            if ('_blank' === $item->target && empty($item->xfn)) {
                $atts['rel'] = 'noopener noreferrer';
            } else {
                $atts['rel'] = !empty($item->xfn) ? $item->xfn : '';
            }

            // If the item has_children add atts to <a>.
            if ($this->has_children && $depth >= 0) {
                $atts['href'] = '#';
                $atts['data-bs-toggle'] = 'dropdown';
                $atts['aria-expanded'] = 'false';
                $atts['class'] = 'dropdown-toggle nav-link';
                $atts['id'] = 'menu-item-dropdown-' . $item->ID;
            } else {
                if (true === $this->has_schema) {
                    $atts['itemprop'] = 'url';
                }

                $atts['href'] = !empty($item->url) ? $item->url : '#';
                // For items in dropdowns use .dropdown-item instead of .nav-link.
                if ($depth > 0) {
                    $atts['class'] = 'dropdown-item';
                } else {
                    $atts['class'] = 'nav-link';
                }
            }

            $atts['class'] = implode('', array_merge([$atts['class']], $a_classes));

            $atts['aria-current'] = $item->current ? 'page' : '';

            // Update atts of this item based on any custom linkmod classes.
            $atts = $this->update_atts_for_linkmod_type($atts, $linkmod_classes);

            // Allow filtering of the $atts array before using it.
            $atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args, $depth);

            // Build a string of html containing all the atts for the item.
            $attributes = '';
            foreach ($atts as $attr => $value) {
                if (!empty($value)) {
                    $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
                    $attributes .= ' ' . $attr . '="' . $value . '"';
                }
            }

            // Set a typeflag to easily test if this is a linkmod or not.
            $linkmod_type = $this->get_linkmod_type($linkmod_classes);

            // START appending the internal item contents to the output.
            $item_output = isset($args->before) ? $args->before : '';

            /*
             * This is the start of the internal nav item. Depending on what
             * kind of linkmod we have we may need different wrapper elements.
             */
            if ('' !== $linkmod_type) {
                // Is linkmod, output the required element opener.
                $item_output .= $this->linkmod_element_open($linkmod_type, $attributes);
            } else {
                // With no link mod type set this must be a standard <a> tag.
                $item_output .= '<a' . $attributes . '>';
            }

            /*
             * Initiate empty icon var, then if we have a string containing any
             * icon classes form the icon markup with an <i> element. This is
             * output inside of the item before the $title (the link text).
             */
            $icon_html = '';
            if (!empty($icon_class_string)) {
                // Append an <i> with the icon classes to what is output before links.
                $icon_html = '<i class="' . esc_attr($icon_class_string) . '" aria-hidden="true"></i> ';
            }

            /** This filter is documented in wp-includes/post-template.php */
            $title = apply_filters('the_title', $item->title, $item->ID);

            /**
             * Filters a menu item's title.
             *
             * @param string $title The menu item's title.
             * @param WP_Nav_Menu_Item $item The current menu item.
             * @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
             * @param int $depth Depth of menu item. Used for padding.
             * @since WP 4.4.0
             *
             */
            $title = apply_filters('nav_menu_item_title', $title, $item, $args, $depth);

            // If the .sr-only class was set apply to the nav items text only.
            if (in_array('sr-only', $linkmod_classes, true)) {
                $title = $this->wrap_for_screen_reader($title);
                $keys_to_unset = array_keys($linkmod_classes, 'sr-only', true);
                foreach ($keys_to_unset as $k) {
                    unset($linkmod_classes[$k]);
                }
            }

            // Put the item contents into $output.
            $item_output .= isset($args->link_before) ? $args->link_before . $icon_html . $title . $args->link_after : '';

            /*
             * This is the end of the internal nav item. We need to close the
             * correct element depending on the type of link or link mod.
             */
            if ('' !== $linkmod_type) {
                // Is linkmod, output the required closing element.
                $item_output .= $this->linkmod_element_close($linkmod_type);
            } else {
                // With no link mod type set this must be a standard <a> tag.
                $item_output .= '</a>';
            }

            $item_output .= isset($args->after) ? $args->after : '';

            // END appending the internal item contents to the output.
            $output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
        }

        /**
         * Menu fallback.
         *
         * If this function is assigned to the wp_nav_menu's fallback_cb variable
         * and a menu has not been assigned to the theme location in the WordPress
         * menu manager the function will display nothing to a non-logged in user,
         * and will add a link to the WordPress menu manager if logged in as an admin.
         *
         * @param array $args passed from the wp_nav_menu function.
         * @return string|void String when echo is false.
         */
        public static function fallback($args)
        {
            if (!current_user_can('edit_theme_options')) {
                return;
            }

            // Initialize var to store fallback html.
            $fallback_output = '';

            // Menu container opening tag.
            $show_container = false;
            if ($args['container']) {
                /**
                 * Filters the list of HTML tags that are valid for use as menu containers.
                 *
                 * @param array $tags The acceptable HTML tags for use as menu containers.
                 *                    Default is array containing 'div' and 'nav'.
                 * @since WP 3.0.0
                 *
                 */
                $allowed_tags = apply_filters('wp_nav_menu_container_allowedtags', array('div', 'nav'));
                if (is_string($args['container']) && in_array($args['container'], $allowed_tags, true)) {
                    $show_container = true;
                    $class = $args['container_class'] ? ' class="menu-fallback-container ' . esc_attr($args['container_class']) . '"' : ' class="menu-fallback-container"';
                    $id = $args['container_id'] ? ' id="' . esc_attr($args['container_id']) . '"' : '';
                    $fallback_output .= '<' . $args['container'] . $id . $class . '>';
                }
            }

            // The fallback menu.
            $class = $args['menu_class'] ? ' class="menu-fallback-menu ' . esc_attr($args['menu_class']) . '"' : ' class="menu-fallback-menu"';
            $id = $args['menu_id'] ? ' id="' . esc_attr($args['menu_id']) . '"' : '';
            $fallback_output .= '<ul' . $id . $class . '>';
            $fallback_output .= '<li class="nav-item"><a href="' . esc_url(admin_url('nav-menus.php')) . '" class="nav-link" title="' . esc_attr__('Add a menu', TXTDOMAIN) . '">' . esc_html__('Add a menu', TXTDOMAIN) . '</a></li>';
            $fallback_output .= '</ul>';

            // Menu container closing tag.
            if ($show_container) {
                $fallback_output .= '</' . $args['container'] . '>';
            }

            // if $args has 'echo' key and it's true echo, otherwise return.
            if (array_key_exists('echo', $args) && $args['echo']) {
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                echo $fallback_output;
            } else {
                return $fallback_output;
            }
        }

        /**
         * Filter to ensure the items_Wrap argument contains microdata.
         *
         * @param array $args The nav instance arguments.
         * @return array $args The altered nav instance arguments.
         * @since 4.2.0
         *
         */
        public function add_schema_to_navbar_ul($args)
        {
            if (isset($args['items_wrap'])) {
                $wrap = $args['items_wrap'];
                if (strpos($wrap, 'SiteNavigationElement') === false) {
                    $args['items_wrap'] = preg_replace('/(>).*>?\%3\$s/', ' itemscope itemtype="http://www.schema.org/SiteNavigationElement"$0', $wrap);
                }
            }
            return $args;
        }

        /**
         * Find any custom linkmod or icon classes and store in their holder
         * arrays then remove them from the main classes array.
         *
         * Supported linkmods: .disabled, .dropdown-header, .dropdown-divider, .sr-only
         * Supported iconsets: Font Awesome 4/5, Glypicons
         *
         * NOTE: This accepts the linkmod and icon arrays by reference.
         *
         * @param array $classes an array of classes currently assigned to the item.
         * @param array $linkmod_classes an array to hold linkmod classes.
         * @param array $icon_classes an array to hold icon classes.
         * @param integer $depth an integer holding current depth level.
         *
         * @return array  $classes         a maybe modified array of classnames.
         * @since 4.0.0
         *
         */
        private function separate_linkmods_and_icons_from_classes($classes, &$linkmod_classes, &$icon_classes, $depth)
        {
            // Loop through $classes array to find linkmod or icon classes.
            foreach ($classes as $key => $class) {
                /*
                 * If any special classes are found, store the class in it's
                 * holder array and and unset the item from $classes.
                 */
                if (preg_match('/^disabled|^sr-only/i', $class)) {
                    // Test for .disabled or .sr-only classes.
                    $linkmod_classes[] = $class;
                    unset($classes[$key]);
                } elseif (preg_match('/^dropdown-header|^dropdown-divider|^dropdown-item-text/i', $class) && $depth > 0) {
                    /*
                     * Test for .dropdown-header or .dropdown-divider and a
                     * depth greater than 0 - IE inside a dropdown.
                     */
                    $linkmod_classes[] = $class;
                    unset($classes[$key]);
                } elseif (preg_match('/^fa-(\S*)?|^fa(s|r|l|b)?(\s?)?$/i', $class)) {
                    // Font Awesome.
                    $icon_classes[] = $class;
                    unset($classes[$key]);
                } elseif (preg_match('/^glyphicon-(\S*)?|^glyphicon(\s?)$/i', $class)) {
                    // Glyphicons.
                    $icon_classes[] = $class;
                    unset($classes[$key]);
                }
            }

            return $classes;
        }

        /**
         * Return a string containing a linkmod type and update $atts array
         * accordingly depending on the decided.
         *
         * @param array $linkmod_classes array of any link modifier classes.
         *
         * @return string                empty for default, a linkmod type string otherwise.
         * @since 4.0.0
         *
         */
        private function get_linkmod_type($linkmod_classes = array())
        {
            $linkmod_type = '';
            // Loop through array of linkmod classes to handle their $atts.
            if (!empty($linkmod_classes)) {
                foreach ($linkmod_classes as $link_class) {
                    if (!empty($link_class)) {

                        // Check for special class types and set a flag for them.
                        if ('dropdown-header' === $link_class) {
                            $linkmod_type = 'dropdown-header';
                        } elseif ('dropdown-divider' === $link_class) {
                            $linkmod_type = 'dropdown-divider';
                        } elseif ('dropdown-item-text' === $link_class) {
                            $linkmod_type = 'dropdown-item-text';
                        }
                    }
                }
            }
            return $linkmod_type;
        }

        /**
         * Update the attributes of a nav item depending on the limkmod classes.
         *
         * @param array $atts array of atts for the current link in nav item.
         * @param array $linkmod_classes an array of classes that modify link or nav item behaviors or displays.
         *
         * @return array                 maybe updated array of attributes for item.
         * @since 4.0.0
         *
         */
        private function update_atts_for_linkmod_type($atts = array(), $linkmod_classes = array())
        {
            if (!empty($linkmod_classes)) {
                foreach ($linkmod_classes as $link_class) {
                    if (!empty($link_class)) {
                        /*
                         * Update $atts with a space and the extra classname
                         * so long as it's not a sr-only class.
                         */
                        if ('sr-only' !== $link_class) {
                            $atts['class'] .= ' ' . esc_attr($link_class);
                        }
                        // Check for special class types we need additional handling for.
                        if ('disabled' === $link_class) {
                            // Convert link to '#' and unset open targets.
                            $atts['href'] = '#';
                            unset($atts['target']);
                        } elseif ('dropdown-header' === $link_class || 'dropdown-divider' === $link_class || 'dropdown-item-text' === $link_class) {
                            // Store a type flag and unset href and target.
                            unset($atts['href']);
                            unset($atts['target']);
                        }
                    }
                }
            }
            return $atts;
        }

        /**
         * Wraps the passed text in a screen reader only class.
         *
         * @param string $text the string of text to be wrapped in a screen reader class.
         * @return string      the string wrapped in a span with the class.
         * @since 4.0.0
         *
         */
        private function wrap_for_screen_reader($text = '')
        {
            if ($text) {
                $text = '<span class="sr-only">' . $text . '</span>';
            }
            return $text;
        }

        /**
         * Returns the correct opening element and attributes for a linkmod.
         *
         * @param string $linkmod_type a sting containing a linkmod type flag.
         * @param string $attributes a string of attributes to add to the element.
         *
         * @return string              a string with the openign tag for the element with attribibutes added.
         * @since 4.0.0
         *
         */
        private function linkmod_element_open($linkmod_type, $attributes = '')
        {
            $output = '';
            if ('dropdown-item-text' === $linkmod_type) {
                $output .= '<span class="dropdown-item-text"' . $attributes . '>';
            } elseif ('dropdown-header' === $linkmod_type) {
                /*
                 * For a header use a span with the .h6 class instead of a real
                 * header tag so that it doesn't confuse screen readers.
                 */
                $output .= '<span class="dropdown-header h6"' . $attributes . '>';
            } elseif ('dropdown-divider' === $linkmod_type) {
                // This is a divider.
                $output .= '<div class="dropdown-divider"' . $attributes . '>';
            }
            return $output;
        }

        /**
         * Return the correct closing tag for the linkmod element.
         *
         * @param string $linkmod_type a string containing a special linkmod type.
         *
         * @return string              a string with the closing tag for this linkmod type.
         * @since 4.0.0
         *
         */
        private function linkmod_element_close($linkmod_type)
        {
            $output = '';
            if ('dropdown-header' === $linkmod_type || 'dropdown-item-text' === $linkmod_type) {
                /*
                 * For a header use a span with the .h6 class instead of a real
                 * header tag so that it doesn't confuse screen readers.
                 */
                $output .= '</span>';
            } elseif ('dropdown-divider' === $linkmod_type) {
                // This is a divider.
                $output .= '</div>';
            }
            return $output;
        }

        /**
         * Flattens a multidimensional array to a simple array.
         *
         * @param array $array a multidimensional array.
         *
         * @return array a simple array
         */
        public function flatten($array)
        {
            $result = array();
            foreach ($array as $element) {
                if (is_array($element)) {
                    array_push($result, ...$this->flatten($element));
                } else {
                    $result[] = $element;
                }
            }
            return $result;
        }

    }

endif;