foreach ( $term_order as $term_id ) { if ( isset( $terms_by_id[ "$term_id" ] ) ) { $return[] = $terms_by_id[ "$term_id" ]; unset( $terms_by_id[ "$term_id" ] ); } } foreach ( $terms_by_id as $term_id => $term ) { $return[] = $term; } return $return; } /** * Get first menu taxonomy "leaf". * * @param int $post_id Post ID. * * @return bool|\WP_Term|\WP_Error|null */ public function get_menu_item_menu_leaf( $post_id ) { // Get first menu taxonomy "leaf". $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); foreach ( $term_ids as $term_id ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach $children = get_term_children( $term_id, self::MENU_TAX ); if ( ! $children ) { break; } } if ( ! isset( $term_id ) ) { return false; } return get_term( $term_id, self::MENU_TAX ); } /** * Get a list of the labels linked to a menu item. * * @param int $post_id Post ID. * * @return void */ public function list_labels( $post_id = 0 ) { $post = get_post( $post_id ); echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), '' ); } /** * Get a list of the labels linked to a menu item, with links to manage them. * * @param int $post_id Post ID. * * @return void */ public function list_admin_labels( $post_id = 0 ) { $post = get_post( $post_id ); $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); if ( ! empty( $labels ) ) { $out = array(); foreach ( $labels as $label ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach $out[] = sprintf( '%s', esc_url( add_query_arg( array( 'post_type' => self::MENU_ITEM_POST_TYPE, 'taxonomy' => self::MENU_ITEM_LABEL_TAX, 'term' => $label->slug, ), 'edit.php' ) ), esc_html( sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) ) ); } echo implode( _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. } else { esc_html_e( 'No Labels', 'jetpack-classic-theme-helper' ); } } /** * Update post meta with the price defined in meta box. * * @param int $post_id Post ID. * @param string $price Price. * * @return int|bool */ public function set_price( $post_id = 0, $price = '' ) { return update_post_meta( $post_id, 'nova_price', $price ); } /** * Get the price of a menu item. * * @param int $post_id Post ID. * * @return bool|string */ public function get_price( $post_id = 0 ) { return get_post_meta( $post_id, 'nova_price', true ); } /** * Echo the price of a menu item. * * @param int $post_id Post ID. * * @return void */ public function display_price( $post_id = 0 ) { echo esc_html( $this->get_price( $post_id ) ); } /* Menu Item Loop Markup */ /** * Get markup for a menu item. * Note: Does not support nested loops. * * @param null|string $field The field to get the value for. * * @return array */ public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $this->menu_item_loop_markup; } /** * Sets up the loop markup. * Attached to the 'template_include' *filter*, * which fires only during a real blog view (not in admin, feeds, etc.) * * @param string $template Template File. * * @return string Template File. VERY Important. */ public function setup_menu_item_loop_markup__in_filter( $template ) { add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); return $template; } /** * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku * Attached to the 'loop_start' action. * * @param WP_Query $query Post query. * * @return void */ public function start_menu_item_loop( $query ) { if ( ! $this->is_menu_item_query( $query ) ) { return; } $this->menu_item_loop_last_term_id = false; $this->menu_item_loop_current_term = false; add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); } /** * Outputs the Menu Item Loop Markup * Attached to the 'the_post' action. * * @param WP_Post $post Post object. * * @return void */ public function menu_item_loop_each_post( $post ) { $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); if ( false === $this->menu_item_loop_current_term || null === $this->menu_item_loop_current_term || is_wp_error( $this->menu_item_loop_current_term ) ) { return; } if ( false === $this->menu_item_loop_last_term_id ) { // We're at the very beginning of the loop $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section $this->menu_item_loop_header(); // Output the menu's header } elseif ( is_object( $this->menu_item_loop_current_term ) && $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id ) { // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section $this->menu_item_loop_header(); // Output the menu's header } if ( is_object( $this->menu_item_loop_current_term ) ) { $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; } } /** * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku * Attached to the 'loop_end' action. * * @param WP_Query $query Post query. * * @return void */ public function stop_menu_item_loop( $query ) { if ( ! $this->is_menu_item_query( $query ) ) { return; } remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); $this->menu_item_loop_close_element( 'menu' ); // End the last menu section } /** * Outputs the Menu Group Header * * @return void */ public function menu_item_loop_header() { $this->menu_item_loop_open_element( 'menu_header' ); $this->menu_item_loop_open_element( 'menu_title' ); echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter $this->menu_item_loop_close_element( 'menu_title' ); if ( $this->menu_item_loop_current_term->description ) : $this->menu_item_loop_open_element( 'menu_description' ); echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter $this->menu_item_loop_close_element( 'menu_description' ); endif; $this->menu_item_loop_close_element( 'menu_header' ); } /** * Outputs a Menu Item Markup element opening tag * * @param string $field - Menu Item Markup settings field. * * @return void */ public function menu_item_loop_open_element( $field ) { $markup = $this->get_menu_item_loop_markup(); /** * Filter a menu item's element opening tag. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu item's element opening tag. * @param string $field Menu Item Markup settings field. * @param array $markup Array of markup elements for the menu item. * @param false|object $term Taxonomy term for current menu item. */ echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. 'jetpack_nova_menu_item_loop_open_element', '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", $field, $markup, $this->menu_item_loop_current_term ); } /** * Outputs a Menu Item Markup element closing tag * * @param string $field - Menu Item Markup settings field. * * @return void */ public function menu_item_loop_close_element( $field ) { $markup = $this->get_menu_item_loop_markup(); /** * Filter a menu item's element closing tag. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu item's element closing tag. * @param string $field Menu Item Markup settings field. * @param array $markup Array of markup elements for the menu item. * @param false|object $term Taxonomy term for current menu item. */ echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. 'jetpack_nova_menu_item_loop_close_element', '\n", $field, $markup, $this->menu_item_loop_current_term ); } /** * Returns a Menu Item Markup element's class attribute. * * @param string $class Class name. * * @return string HTML class attribute with leading whitespace. */ public function menu_item_loop_class( $class ) { if ( ! $class ) { return ''; } /** * Filter a menu Item Markup element's class attribute. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu Item Markup element's class attribute. * @param string $class Menu Item Class name. * @param false|object $term Taxonomy term for current menu item. */ return apply_filters( 'jetpack_nova_menu_item_loop_class', ' class="' . esc_attr( $class ) . '"', $class, $this->menu_item_loop_current_term ); } } } foreach ( $term_order as $term_id ) { if ( isset( $terms_by_id[ "$term_id" ] ) ) { $return[] = $terms_by_id[ "$term_id" ]; unset( $terms_by_id[ "$term_id" ] ); } } foreach ( $terms_by_id as $term_id => $term ) { $return[] = $term; } return $return; } /** * Get first menu taxonomy "leaf". * * @param int $post_id Post ID. * * @return bool|\WP_Term|\WP_Error|null */ public function get_menu_item_menu_leaf( $post_id ) { // Get first menu taxonomy "leaf". $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); foreach ( $term_ids as $term_id ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach $children = get_term_children( $term_id, self::MENU_TAX ); if ( ! $children ) { break; } } if ( ! isset( $term_id ) ) { return false; } return get_term( $term_id, self::MENU_TAX ); } /** * Get a list of the labels linked to a menu item. * * @param int $post_id Post ID. * * @return void */ public function list_labels( $post_id = 0 ) { $post = get_post( $post_id ); echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), '' ); } /** * Get a list of the labels linked to a menu item, with links to manage them. * * @param int $post_id Post ID. * * @return void */ public function list_admin_labels( $post_id = 0 ) { $post = get_post( $post_id ); $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); if ( ! empty( $labels ) ) { $out = array(); foreach ( $labels as $label ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach $out[] = sprintf( '%s', esc_url( add_query_arg( array( 'post_type' => self::MENU_ITEM_POST_TYPE, 'taxonomy' => self::MENU_ITEM_LABEL_TAX, 'term' => $label->slug, ), 'edit.php' ) ), esc_html( sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) ) ); } echo implode( _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. } else { esc_html_e( 'No Labels', 'jetpack-classic-theme-helper' ); } } /** * Update post meta with the price defined in meta box. * * @param int $post_id Post ID. * @param string $price Price. * * @return int|bool */ public function set_price( $post_id = 0, $price = '' ) { return update_post_meta( $post_id, 'nova_price', $price ); } /** * Get the price of a menu item. * * @param int $post_id Post ID. * * @return bool|string */ public function get_price( $post_id = 0 ) { return get_post_meta( $post_id, 'nova_price', true ); } /** * Echo the price of a menu item. * * @param int $post_id Post ID. * * @return void */ public function display_price( $post_id = 0 ) { echo esc_html( $this->get_price( $post_id ) ); } /* Menu Item Loop Markup */ /** * Get markup for a menu item. * Note: Does not support nested loops. * * @param null|string $field The field to get the value for. * * @return array */ public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable return $this->menu_item_loop_markup; } /** * Sets up the loop markup. * Attached to the 'template_include' *filter*, * which fires only during a real blog view (not in admin, feeds, etc.) * * @param string $template Template File. * * @return string Template File. VERY Important. */ public function setup_menu_item_loop_markup__in_filter( $template ) { add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); return $template; } /** * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku * Attached to the 'loop_start' action. * * @param WP_Query $query Post query. * * @return void */ public function start_menu_item_loop( $query ) { if ( ! $this->is_menu_item_query( $query ) ) { return; } $this->menu_item_loop_last_term_id = false; $this->menu_item_loop_current_term = false; add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); } /** * Outputs the Menu Item Loop Markup * Attached to the 'the_post' action. * * @param WP_Post $post Post object. * * @return void */ public function menu_item_loop_each_post( $post ) { $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); if ( false === $this->menu_item_loop_current_term || null === $this->menu_item_loop_current_term || is_wp_error( $this->menu_item_loop_current_term ) ) { return; } if ( false === $this->menu_item_loop_last_term_id ) { // We're at the very beginning of the loop $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section $this->menu_item_loop_header(); // Output the menu's header } elseif ( is_object( $this->menu_item_loop_current_term ) && $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id ) { // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section $this->menu_item_loop_header(); // Output the menu's header } if ( is_object( $this->menu_item_loop_current_term ) ) { $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; } } /** * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku * Attached to the 'loop_end' action. * * @param WP_Query $query Post query. * * @return void */ public function stop_menu_item_loop( $query ) { if ( ! $this->is_menu_item_query( $query ) ) { return; } remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); $this->menu_item_loop_close_element( 'menu' ); // End the last menu section } /** * Outputs the Menu Group Header * * @return void */ public function menu_item_loop_header() { $this->menu_item_loop_open_element( 'menu_header' ); $this->menu_item_loop_open_element( 'menu_title' ); echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter $this->menu_item_loop_close_element( 'menu_title' ); if ( $this->menu_item_loop_current_term->description ) : $this->menu_item_loop_open_element( 'menu_description' ); echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter $this->menu_item_loop_close_element( 'menu_description' ); endif; $this->menu_item_loop_close_element( 'menu_header' ); } /** * Outputs a Menu Item Markup element opening tag * * @param string $field - Menu Item Markup settings field. * * @return void */ public function menu_item_loop_open_element( $field ) { $markup = $this->get_menu_item_loop_markup(); /** * Filter a menu item's element opening tag. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu item's element opening tag. * @param string $field Menu Item Markup settings field. * @param array $markup Array of markup elements for the menu item. * @param false|object $term Taxonomy term for current menu item. */ echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. 'jetpack_nova_menu_item_loop_open_element', '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", $field, $markup, $this->menu_item_loop_current_term ); } /** * Outputs a Menu Item Markup element closing tag * * @param string $field - Menu Item Markup settings field. * * @return void */ public function menu_item_loop_close_element( $field ) { $markup = $this->get_menu_item_loop_markup(); /** * Filter a menu item's element closing tag. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu item's element closing tag. * @param string $field Menu Item Markup settings field. * @param array $markup Array of markup elements for the menu item. * @param false|object $term Taxonomy term for current menu item. */ echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. 'jetpack_nova_menu_item_loop_close_element', '\n", $field, $markup, $this->menu_item_loop_current_term ); } /** * Returns a Menu Item Markup element's class attribute. * * @param string $class Class name. * * @return string HTML class attribute with leading whitespace. */ public function menu_item_loop_class( $class ) { if ( ! $class ) { return ''; } /** * Filter a menu Item Markup element's class attribute. * * @module custom-content-types * * @since 4.4.0 * * @param string $tag Menu Item Markup element's class attribute. * @param string $class Menu Item Class name. * @param false|object $term Taxonomy term for current menu item. */ return apply_filters( 'jetpack_nova_menu_item_loop_class', ' class="' . esc_attr( $class ) . '"', $class, $this->menu_item_loop_current_term ); } } }