Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 793
0.00% covered (danger)
0.00%
0 / 49
CRAP
0.00% covered (danger)
0.00%
0 / 1
Nova_Restaurant
0.00% covered (danger)
0.00%
0 / 792
0.00% covered (danger)
0.00%
0 / 49
31152
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_register_cpt
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 site_supports_nova
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 register_taxonomies
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
12
 register_post_types
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
6
 updated_messages
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
6
 enqueue_nova_styles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 change_default_title
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 add_to_dashboard
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
6
 is_menu_item_query
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
 sort_menu_item_queries_by_menu_order
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 sort_menu_item_queries_by_menu_taxonomy
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
 add_admin_menus
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
6
 set_custom_font_icon
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 current_screen_load
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 admin_notices
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 no_title_sorting
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setup_menu_item_columns
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 menu_item_columns
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 menu_item_column_callback
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
42
 get_menu_by_post_id
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 maybe_reorder_menu_items
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
182
 edit_menu_items_page_load
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
6
 handle_menu_item_actions
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 1
812
 show_menu_titles_in_menu_item_list
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 1
72
 add_many_new_items_page_load
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 enqueue_many_items_scripts
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 process_form_request
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
240
 add_many_new_items_page
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
2
 register_menu_item_meta_boxes
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 menu_item_price_meta_box
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 add_post_meta
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 get_menus
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 get_menu_item_menu_leaf
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 list_labels
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 list_admin_labels
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
 set_price
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_price
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 display_price
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_menu_item_loop_markup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setup_menu_item_loop_markup__in_filter
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 start_menu_item_loop
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 menu_item_loop_each_post
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 stop_menu_item_loop
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 menu_item_loop_header
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 menu_item_loop_open_element
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 menu_item_loop_close_element
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 menu_item_loop_class
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Manage restaurant menus from your WordPress site,
4 * via a new "nova" CPT.
5 *
6 * Put the following code in your theme's Food Menu Page Template to customize the markup of the menu.
7 *
8 * if ( class_exists( 'Nova_Restaurant' ) ) {
9 *  Nova_Restaurant::init( array(
10 *      'menu_tag'               => 'section',
11 *      'menu_class'             => 'menu-items',
12 *      'menu_header_tag'        => 'header',
13 *      'menu_header_class'      => 'menu-group-header',
14 *      'menu_title_tag'         => 'h1',
15 *      'menu_title_class'       => 'menu-group-title',
16 *      'menu_description_tag'   => 'div',
17 *      'menu_description_class' => 'menu-group-description',
18 *  ) );
19 * }
20 *
21 * @todo
22 * - Bulk/Quick edit response of Menu Item rows is broken.
23 * - Drag and Drop reordering.
24 *
25 * @package automattic/jetpack-classic-theme-helper
26 */
27
28namespace Automattic\Jetpack\Classic_Theme_Helper;
29
30use Automattic\Jetpack\Assets;
31use Automattic\Jetpack\Roles;
32use WP_Post;
33use WP_Query;
34
35if ( ! class_exists( __NAMESPACE__ . '\Nova_Restaurant' ) ) {
36
37    /**
38     * Create the new Nova CPT.
39     */
40    class Nova_Restaurant {
41        const MENU_ITEM_POST_TYPE = 'nova_menu_item';
42        const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label';
43        const MENU_TAX            = 'nova_menu';
44
45        /**
46         * Version number used when enqueuing all resources (css and js).
47         *
48         * @var string
49         */
50        public $version = '20210303';
51
52        /**
53         * Default markup for the menu items.
54         *
55         * @var array
56         */
57        protected $default_menu_item_loop_markup = array(
58            'menu_tag'               => 'section',
59            'menu_class'             => 'menu-items',
60            'menu_header_tag'        => 'header',
61            'menu_header_class'      => 'menu-group-header',
62            'menu_title_tag'         => 'h1',
63            'menu_title_class'       => 'menu-group-title',
64            'menu_description_tag'   => 'div',
65            'menu_description_class' => 'menu-group-description',
66        );
67
68        /**
69         * Array of markup for the menu items.
70         *
71         * @var array
72         */
73        protected $menu_item_loop_markup = array();
74
75        /**
76         * Last term ID of a loop of menu items.
77         *
78         * @var bool|int
79         */
80        protected $menu_item_loop_last_term_id = false;
81
82        /**
83         * Current term ID of a loop of menu items.
84         *
85         * @var bool|int|\WP_Term
86         */
87        protected $menu_item_loop_current_term = false;
88
89        /**
90         * Whether the CPT and its utilities have already been registered for this request.
91         *
92         * The post type and taxonomies are registered globally and the hooks below are global too, so this is
93         * shared by every instance: a second instance must not attach a duplicate set. Private so that a
94         * subclass declaring a property of the same name doesn't hit a visibility fatal.
95         *
96         * @var bool
97         */
98        private static $registered = false;
99
100        /**
101         * Initialize class.
102         *
103         * @param array $menu_item_loop_markup Array of markup for the menu items.
104         *
105         * @return self
106         */
107        public static function init( $menu_item_loop_markup = array() ) {
108            static $instance = false;
109
110            if ( ! $instance ) {
111                $instance = new Nova_Restaurant();
112            }
113
114            if ( $menu_item_loop_markup ) {
115                $instance->menu_item_loop_markup = wp_parse_args( $menu_item_loop_markup, $instance->default_menu_item_loop_markup );
116            }
117
118            return $instance;
119        }
120
121        /**
122         * Constructor.
123         * Hook into WordPress to create CPT and utilities if needed.
124         */
125        public function __construct() {
126            /*
127             * WordPress.com Simple does not load the theme before `init` on REST requests; it replays the
128             * theme's `after_setup_theme` and `init` callbacks on the two hooks below instead. Theme support
129             * is undeclared until then, so check again as each replay finishes.
130             *
131             * The replayed callbacks keep their own priority and are hooked after ours, hence the offsets:
132             * last on the setup replay, and after a default-priority `init` but before post likes and sharing
133             * enumerate the registered post types at 20.
134             */
135            add_action( 'restapi_theme_after_setup_theme', array( $this, 'maybe_register_cpt' ), PHP_INT_MAX );
136            add_action( 'restapi_theme_init', array( $this, 'maybe_register_cpt' ), 15 );
137
138            $this->maybe_register_cpt();
139        }
140
141        /**
142         * Register the CPT and its utilities, if the site supports them.
143         */
144        public function maybe_register_cpt() {
145            if ( ! $this->site_supports_nova() ) {
146                return;
147            }
148
149            // Loop markup is per-instance state, so every instance needs it, registering or not. Registration
150            // can also run after `init()` has stored a caller's markup, so don't clobber that either.
151            if ( ! $this->menu_item_loop_markup ) {
152                $this->menu_item_loop_markup = $this->default_menu_item_loop_markup;
153            }
154
155            if ( self::$registered ) {
156                return;
157            }
158
159            self::$registered = true;
160
161            $this->register_taxonomies();
162            $this->register_post_types();
163            add_action( 'admin_menu', array( $this, 'add_admin_menus' ) );
164            add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_nova_styles' ) );
165            add_action( 'admin_head', array( $this, 'set_custom_font_icon' ) );
166
167            // Always sort menu items correctly
168            add_action( 'parse_query', array( $this, 'sort_menu_item_queries_by_menu_order' ) );
169            add_filter( 'posts_results', array( $this, 'sort_menu_item_queries_by_menu_taxonomy' ), 10, 2 );
170
171            add_action( 'wp_insert_post', array( $this, 'add_post_meta' ) );
172
173            // Only output our Menu Item Loop Markup on a real blog view.  Not feeds, XML-RPC, admin, etc.
174            add_filter( 'template_include', array( $this, 'setup_menu_item_loop_markup__in_filter' ) );
175
176            add_filter( 'enter_title_here', array( $this, 'change_default_title' ) );
177            add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) );
178            add_filter( 'dashboard_glance_items', array( $this, 'add_to_dashboard' ) );
179        }
180
181        /**
182         * Should this Custom Post Type be made available?
183         *
184         * @return bool
185         */
186        public function site_supports_nova() {
187            // If we're on WordPress.com, and it has the menu site vertical.
188            if ( function_exists( 'site_vertical' ) && 'nova_menu' === site_vertical() ) { // @phan-suppress-current-line PhanUndeclaredFunction -- only calling if it exists.
189                return true;
190            }
191
192            // Else, if the current theme requests it.
193            if ( current_theme_supports( self::MENU_ITEM_POST_TYPE ) ) {
194                return true;
195            }
196
197            // Otherwise, say no unless something wants to filter us to say yes.
198            /**
199             * Allow something else to hook in and enable this CPT.
200             *
201             * @module custom-content-types
202             *
203             * @since 2.6.0
204             *
205             * @param bool false Whether or not to enable this CPT.
206             * @param string $var The slug for this CPT.
207             */
208            return (bool) apply_filters( 'jetpack_enable_cpt', false, self::MENU_ITEM_POST_TYPE );
209        }
210
211        /* Setup */
212
213        /**
214         * Register Taxonomies and Post Type
215         */
216        public function register_taxonomies() {
217            if ( ! taxonomy_exists( self::MENU_ITEM_LABEL_TAX ) ) {
218                register_taxonomy(
219                    self::MENU_ITEM_LABEL_TAX,
220                    self::MENU_ITEM_POST_TYPE,
221                    array(
222                        'labels'       => array(
223                            /* translators: this is about a food menu */
224                            'name'                       => __( 'Menu Item Labels', 'jetpack-classic-theme-helper' ),
225                            /* translators: this is about a food menu */
226                            'singular_name'              => __( 'Menu Item Label', 'jetpack-classic-theme-helper' ),
227                            /* translators: this is about a food menu */
228                            'search_items'               => __( 'Search Menu Item Labels', 'jetpack-classic-theme-helper' ),
229                            'popular_items'              => __( 'Popular Labels', 'jetpack-classic-theme-helper' ),
230                            /* translators: this is about a food menu */
231                            'all_items'                  => __( 'All Menu Item Labels', 'jetpack-classic-theme-helper' ),
232                            /* translators: this is about a food menu */
233                            'edit_item'                  => __( 'Edit Menu Item Label', 'jetpack-classic-theme-helper' ),
234                            /* translators: this is about a food menu */
235                            'view_item'                  => __( 'View Menu Item Label', 'jetpack-classic-theme-helper' ),
236                            /* translators: this is about a food menu */
237                            'update_item'                => __( 'Update Menu Item Label', 'jetpack-classic-theme-helper' ),
238                            /* translators: this is about a food menu */
239                            'add_new_item'               => __( 'Add New Menu Item Label', 'jetpack-classic-theme-helper' ),
240                            /* translators: this is about a food menu */
241                            'new_item_name'              => __( 'New Menu Item Label Name', 'jetpack-classic-theme-helper' ),
242                            'separate_items_with_commas' => __( 'For example, spicy, favorite, etc. <br /> Separate Labels with commas', 'jetpack-classic-theme-helper' ),
243                            'add_or_remove_items'        => __( 'Add or remove Labels', 'jetpack-classic-theme-helper' ),
244                            'choose_from_most_used'      => __( 'Choose from the most used Labels', 'jetpack-classic-theme-helper' ),
245                            'items_list_navigation'      => __( 'Menu item label list navigation', 'jetpack-classic-theme-helper' ),
246                            'items_list'                 => __( 'Menu item labels list', 'jetpack-classic-theme-helper' ),
247                        ),
248                        'no_tagcloud'  => __( 'No Labels found', 'jetpack-classic-theme-helper' ),
249                        'hierarchical' => false,
250                    )
251                );
252            }
253
254            if ( ! taxonomy_exists( self::MENU_TAX ) ) {
255                register_taxonomy(
256                    self::MENU_TAX,
257                    self::MENU_ITEM_POST_TYPE,
258                    array(
259                        'labels'        => array(
260                            /* translators: this is about a food menu */
261                            'name'                  => __( 'Menu Sections', 'jetpack-classic-theme-helper' ),
262                            /* translators: this is about a food menu */
263                            'singular_name'         => __( 'Menu Section', 'jetpack-classic-theme-helper' ),
264                            /* translators: this is about a food menu */
265                            'search_items'          => __( 'Search Menu Sections', 'jetpack-classic-theme-helper' ),
266                            /* translators: this is about a food menu */
267                            'all_items'             => __( 'All Menu Sections', 'jetpack-classic-theme-helper' ),
268                            /* translators: this is about a food menu */
269                            'parent_item'           => __( 'Parent Menu Section', 'jetpack-classic-theme-helper' ),
270                            /* translators: this is about a food menu */
271                            'parent_item_colon'     => __( 'Parent Menu Section:', 'jetpack-classic-theme-helper' ),
272                            /* translators: this is about a food menu */
273                            'edit_item'             => __( 'Edit Menu Section', 'jetpack-classic-theme-helper' ),
274                            /* translators: this is about a food menu */
275                            'view_item'             => __( 'View Menu Section', 'jetpack-classic-theme-helper' ),
276                            /* translators: this is about a food menu */
277                            'update_item'           => __( 'Update Menu Section', 'jetpack-classic-theme-helper' ),
278                            /* translators: this is about a food menu */
279                            'add_new_item'          => __( 'Add New Menu Section', 'jetpack-classic-theme-helper' ),
280                            /* translators: this is about a food menu */
281                            'new_item_name'         => __( 'New Menu Sections Name', 'jetpack-classic-theme-helper' ),
282                            'items_list_navigation' => __( 'Menu section list navigation', 'jetpack-classic-theme-helper' ),
283                            'items_list'            => __( 'Menu section list', 'jetpack-classic-theme-helper' ),
284                        ),
285                        'rewrite'       => array(
286                            'slug'         => 'menu',
287                            'with_front'   => false,
288                            'hierarchical' => true,
289                        ),
290                        'hierarchical'  => true,
291                        'show_tagcloud' => false,
292                        'query_var'     => 'menu',
293                    )
294                );
295            }
296        }
297
298        /**
299         * Register our Post Type.
300         */
301        public function register_post_types() {
302            if ( post_type_exists( self::MENU_ITEM_POST_TYPE ) ) {
303                return;
304            }
305
306            register_post_type(
307                self::MENU_ITEM_POST_TYPE,
308                array(
309                    'description'          => __( "Items on your restaurant's menu", 'jetpack-classic-theme-helper' ),
310
311                    'labels'               => array(
312                        /* translators: this is about a food menu */
313                        'name'                  => __( 'Menu Items', 'jetpack-classic-theme-helper' ),
314                        /* translators: this is about a food menu */
315                        'singular_name'         => __( 'Menu Item', 'jetpack-classic-theme-helper' ),
316                        /* translators: this is about a food menu */
317                        'menu_name'             => __( 'Food Menus', 'jetpack-classic-theme-helper' ),
318                        /* translators: this is about a food menu */
319                        'all_items'             => __( 'Menu Items', 'jetpack-classic-theme-helper' ),
320                        /* translators: this is about a food menu */
321                        'add_new'               => __( 'Add One Item', 'jetpack-classic-theme-helper' ),
322                        /* translators: this is about a food menu */
323                        'add_new_item'          => __( 'Add Menu Item', 'jetpack-classic-theme-helper' ),
324                        /* translators: this is about a food menu */
325                        'edit_item'             => __( 'Edit Menu Item', 'jetpack-classic-theme-helper' ),
326                        /* translators: this is about a food menu */
327                        'new_item'              => __( 'New Menu Item', 'jetpack-classic-theme-helper' ),
328                        /* translators: this is about a food menu */
329                        'view_item'             => __( 'View Menu Item', 'jetpack-classic-theme-helper' ),
330                        /* translators: this is about a food menu */
331                        'search_items'          => __( 'Search Menu Items', 'jetpack-classic-theme-helper' ),
332                        /* translators: this is about a food menu */
333                        'not_found'             => __( 'No Menu Items found', 'jetpack-classic-theme-helper' ),
334                        /* translators: this is about a food menu */
335                        'not_found_in_trash'    => __( 'No Menu Items found in Trash', 'jetpack-classic-theme-helper' ),
336                        'filter_items_list'     => __( 'Filter menu items list', 'jetpack-classic-theme-helper' ),
337                        'items_list_navigation' => __( 'Menu item list navigation', 'jetpack-classic-theme-helper' ),
338                        'items_list'            => __( 'Menu items list', 'jetpack-classic-theme-helper' ),
339                    ),
340                    'supports'             => array(
341                        'title',
342                        'editor',
343                        'thumbnail',
344                        'excerpt',
345                    ),
346                    'rewrite'              => array(
347                        'slug'       => 'item',
348                        'with_front' => false,
349                        'feeds'      => false,
350                        'pages'      => false,
351                    ),
352                    'register_meta_box_cb' => array( $this, 'register_menu_item_meta_boxes' ),
353
354                    'public'               => true,
355                    'show_ui'              => true, // set to false to replace with custom UI
356                    'menu_position'        => 20, // below Pages
357                    'capability_type'      => 'page',
358                    'map_meta_cap'         => true,
359                    'has_archive'          => false,
360                    'query_var'            => 'item',
361                )
362            );
363        }
364
365        /**
366         * Update messages for the Menu Item admin.
367         *
368         * @param array $messages Existing post update messages.
369         *
370         * @return array $messages Updated post update messages.
371         */
372        public function updated_messages( $messages ) {
373            global $post;
374
375            $messages[ self::MENU_ITEM_POST_TYPE ] = array(
376                0  => '', // Unused. Messages start at index 1.
377                1  => sprintf(
378                    /* translators: this is about a food menu. Placeholder is a link to the food menu. */
379                    __( 'Menu item updated. <a href="%s">View item</a>', 'jetpack-classic-theme-helper' ),
380                    esc_url( get_permalink( $post->ID ) )
381                ),
382                2  => esc_html__( 'Custom field updated.', 'jetpack-classic-theme-helper' ),
383                3  => esc_html__( 'Custom field deleted.', 'jetpack-classic-theme-helper' ),
384                /* translators: this is about a food menu */
385                4  => esc_html__( 'Menu item updated.', 'jetpack-classic-theme-helper' ),
386                5  => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
387                    ? sprintf(
388                        /* translators: %s: date and time of the revision */
389                        esc_html__( 'Menu item restored to revision from %s', 'jetpack-classic-theme-helper' ),
390                        wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling.
391                    )
392                    : false,
393                6  => sprintf(
394                    /* translators: this is about a food menu. Placeholder is a link to the food menu. */
395                    __( 'Menu item published. <a href="%s">View item</a>', 'jetpack-classic-theme-helper' ),
396                    esc_url( get_permalink( $post->ID ) )
397                ),
398                /* translators: this is about a food menu */
399                7  => esc_html__( 'Menu item saved.', 'jetpack-classic-theme-helper' ),
400                8  => sprintf(
401                    /* translators: this is about a food menu */
402                    __( 'Menu item submitted. <a target="_blank" href="%s">Preview item</a>', 'jetpack-classic-theme-helper' ),
403                    esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
404                ),
405                9  => sprintf(
406                    /* translators: this is about a food menu. 1. Publish box date format, see https://php.net/date 2. link to the food menu. */
407                    __( 'Menu item scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview item</a>', 'jetpack-classic-theme-helper' ),
408                    /* translators: Publish box date format, see https://php.net/date */
409                    date_i18n( __( 'M j, Y @ G:i', 'jetpack-classic-theme-helper' ), strtotime( $post->post_date ) ),
410                    esc_url( get_permalink( $post->ID ) )
411                ),
412                10 => sprintf(
413                    /* translators: this is about a food menu. Placeholder is a link to the food menu. */
414                    __( 'Menu item draft updated. <a target="_blank" href="%s">Preview item</a>', 'jetpack-classic-theme-helper' ),
415                    esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) )
416                ),
417            );
418
419            return $messages;
420        }
421
422        /**
423         * Nova styles and scripts.
424         *
425         * @param string $hook Page hook.
426         *
427         * @return void
428         */
429        public function enqueue_nova_styles( $hook ) {
430            global $post_type;
431            $pages = array( 'edit.php', 'post.php', 'post-new.php' );
432
433            if ( in_array( $hook, $pages, true ) && $post_type === self::MENU_ITEM_POST_TYPE ) {
434                wp_enqueue_style( 'nova-style', plugins_url( 'css/nova.css', __FILE__ ), array(), $this->version );
435            }
436
437            wp_enqueue_style( 'nova-font', plugins_url( 'css/nova-font.css', __FILE__ ), array(), $this->version );
438        }
439
440        /**
441         * Change â€˜Enter Title Here’ text for the Menu Item.
442         *
443         * @param string $title Default title placeholder text.
444         *
445         * @return string
446         */
447        public function change_default_title( $title ) {
448            if ( self::MENU_ITEM_POST_TYPE === get_post_type() ) {
449                /* translators: this is about a food menu */
450                $title = esc_html__( "Enter the menu item's name here", 'jetpack-classic-theme-helper' );
451            }
452
453            return $title;
454        }
455
456        /**
457         * Add to Dashboard At A Glance
458         *
459         * @return void
460         */
461        public function add_to_dashboard() {
462            $number_menu_items = wp_count_posts( self::MENU_ITEM_POST_TYPE );
463
464            $roles = new Roles(); // @phan-suppress-current-line PhanUndeclaredClassMethod -- declared at top of file.
465            if ( current_user_can( $roles->translate_role_to_cap( 'administrator' ) ) ) { // @phan-suppress-current-line PhanUndeclaredClassMethod
466                $number_menu_items_published = sprintf(
467                    '<a href="%1$s">%2$s</a>',
468                    esc_url(
469                        get_admin_url(
470                            get_current_blog_id(),
471                            'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE
472                        )
473                    ),
474                    sprintf(
475                        /* translators: Placehoder is a number of items. */
476                        _n(
477                            '%1$d Food Menu Item',
478                            '%1$d Food Menu Items',
479                            (int) $number_menu_items->publish,
480                            'jetpack-classic-theme-helper'
481                        ),
482                        number_format_i18n( $number_menu_items->publish )
483                    )
484                );
485            } else {
486                $number_menu_items_published = sprintf(
487                    '<span>%1$s</span>',
488                    sprintf(
489                        /* translators: Placehoder is a number of items. */
490                        _n(
491                            '%1$d Food Menu Item',
492                            '%1$d Food Menu Items',
493                            (int) $number_menu_items->publish,
494                            'jetpack-classic-theme-helper'
495                        ),
496                        number_format_i18n( $number_menu_items->publish )
497                    )
498                );
499            }
500
501            echo '<li class="nova-menu-count">' . $number_menu_items_published . '</li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we escape things above.
502        }
503
504        /**
505         * If the WP query for our menu items.
506         *
507         * @param WP_Query $query WP Query.
508         *
509         * @return bool
510         */
511        public function is_menu_item_query( $query ) {
512            if (
513                ( isset( $query->query_vars['taxonomy'] ) && self::MENU_TAX === $query->query_vars['taxonomy'] )
514            ||
515                ( isset( $query->query_vars['post_type'] ) && self::MENU_ITEM_POST_TYPE === $query->query_vars['post_type'] )
516            ) {
517                return true;
518            }
519
520            return false;
521        }
522
523        /**
524         * Custom sort the menu item queries by menu order.
525         *
526         * @param WP_Query $query WP Query.
527         *
528         * @return void
529         */
530        public function sort_menu_item_queries_by_menu_order( $query ) {
531            if ( ! $this->is_menu_item_query( $query ) ) {
532                return;
533            }
534
535            $query->query_vars['orderby'] = 'menu_order';
536            $query->query_vars['order']   = 'ASC';
537
538            // For now, just turn off paging so we can sort by taxonmy later
539            // If we want paging in the future, we'll need to add the taxonomy sort here (or at least before the DB query is made)
540            $query->query_vars['posts_per_page'] = -1;
541        }
542
543        /**
544         * Custom sort the menu item queries by menu taxonomies.
545         *
546         * @param WP_Post[] $posts Array of post objects.
547         * @param WP_Query  $query The WP_Query instance.
548         *
549         * @return WP_Post[]
550         */
551        public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) {
552            if ( ! $posts ) {
553                return $posts;
554            }
555
556            if ( ! $this->is_menu_item_query( $query ) ) {
557                return $posts;
558            }
559
560            $grouped_by_term = array();
561
562            foreach ( $posts as $post ) {
563                $term = $this->get_menu_item_menu_leaf( $post->ID );
564                if ( ! $term || is_wp_error( $term ) ) {
565                    $term_id = 0;
566                } else {
567                    $term_id = $term->term_id;
568                }
569
570                if ( ! isset( $grouped_by_term[ "$term_id" ] ) ) {
571                    $grouped_by_term[ "$term_id" ] = array();
572                }
573
574                $grouped_by_term[ "$term_id" ][] = $post;
575            }
576
577            $term_order = get_option( 'nova_menu_order', array() );
578
579            $return = array();
580            foreach ( $term_order as $term_id ) {
581                if ( isset( $grouped_by_term[ "$term_id" ] ) ) {
582                    $return = array_merge( $return, $grouped_by_term[ "$term_id" ] );
583                    unset( $grouped_by_term[ "$term_id" ] );
584                }
585            }
586
587            foreach ( $grouped_by_term as $term_id => $posts ) {
588                $return = array_merge( $return, $posts );
589            }
590
591            return $return;
592        }
593
594        /**
595         * Add new "Add many items" submenu, custom colunmns, and custom bulk actions.
596         *
597         * @return void
598         */
599        public function add_admin_menus() {
600            $hook = add_submenu_page(
601                'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE,
602                __( 'Add Many Items', 'jetpack-classic-theme-helper' ),
603                __( 'Add Many Items', 'jetpack-classic-theme-helper' ),
604                'edit_pages',
605                'add_many_nova_items',
606                array( $this, 'add_many_new_items_page' )
607            );
608
609            add_action( "load-$hook", array( $this, 'add_many_new_items_page_load' ) );
610
611            add_action( 'current_screen', array( $this, 'current_screen_load' ) );
612
613            /*
614            * Adjust 'Add Many Items' submenu position
615            * We're making changes to the menu global, but no other choice unfortunately.
616            * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
617            */
618            if ( isset( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ) ) {
619                $submenu_item = array_pop( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] );
620                $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ][11] = $submenu_item;
621                ksort( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] );
622            }
623            // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited
624
625            $this->setup_menu_item_columns();
626
627            Assets::register_script(
628                'nova-menu-checkboxes',
629                '../../dist/custom-post-types/js/menu-checkboxes.js',
630                __FILE__,
631                array(
632                    'in_footer'  => true,
633                    'enqueue'    => false,
634                    'textdomain' => 'jetpack-classic-theme-helper',
635                )
636            );
637        }
638
639        /**
640         * Custom Nova Icon CSS
641         *
642         * @return void
643         */
644        public function set_custom_font_icon() {
645            ?>
646        <style type="text/css">
647        #menu-posts-nova_menu_item .wp-menu-image::before {
648            font-family: 'nova-font' !important;
649            content: '\e603' !important;
650        }
651        </style>
652            <?php
653        }
654
655        /**
656         * Load Nova menu management tools on the CPT admin screen.
657         *
658         * @return void
659         */
660        public function current_screen_load() {
661            $screen = get_current_screen();
662            if ( 'edit-nova_menu_item' !== $screen->id ) {
663                return;
664            }
665
666            $this->edit_menu_items_page_load();
667            add_filter( 'admin_notices', array( $this, 'admin_notices' ) );
668        }
669
670        /* Edit Items List */
671
672        /**
673         * Display a notice in wp-admin after items have been changed.
674         *
675         * @return void
676         */
677        public function admin_notices() {
678            if ( isset( $_GET['nova_reordered'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
679                printf(
680                    '<div class="updated"><p>%s</p></div>',
681                    /* translators: this is about a food menu */
682                    esc_html__( 'Menu Items re-ordered.', 'jetpack-classic-theme-helper' )
683                );
684            }
685        }
686
687        /**
688         * Do not allow sorting by title.
689         *
690         * @param array $columns An array of sortable columns.
691         *
692         * @return array $columns.
693         */
694        public function no_title_sorting( $columns ) {
695            if ( isset( $columns['title'] ) ) {
696                unset( $columns['title'] );
697            }
698            return $columns;
699        }
700
701        /**
702         * Set up custom columns for our Nova menu.
703         *
704         * @return void
705         */
706        public function setup_menu_item_columns() {
707            add_filter( sprintf( 'manage_edit-%s_sortable_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'no_title_sorting' ) );
708            add_filter( sprintf( 'manage_%s_posts_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_columns' ) );
709
710            add_action( sprintf( 'manage_%s_posts_custom_column', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_column_callback' ), 10, 2 );
711        }
712
713        /**
714         * Add custom columns to the Nova menu item list.
715         *
716         * @param array $columns An array of columns.
717         *
718         * @return array $columns.
719         */
720        public function menu_item_columns( $columns ) {
721            unset( $columns['date'], $columns['likes'] );
722
723            $columns['thumbnail'] = __( 'Thumbnail', 'jetpack-classic-theme-helper' );
724            $columns['labels']    = __( 'Labels', 'jetpack-classic-theme-helper' );
725            $columns['price']     = __( 'Price', 'jetpack-classic-theme-helper' );
726            $columns['order']     = __( 'Order', 'jetpack-classic-theme-helper' );
727
728            return $columns;
729        }
730
731        /**
732         * Display custom data in each new custom column we created.
733         *
734         * @param string $column  The name of the column to display.
735         * @param int    $post_id The current post ID.
736         *
737         * @return void
738         */
739        public function menu_item_column_callback( $column, $post_id ) {
740            $screen = get_current_screen();
741
742            switch ( $column ) {
743                case 'thumbnail':
744                    echo get_the_post_thumbnail( $post_id, array( 50, 50 ) );
745                    break;
746                case 'labels':
747                    $this->list_admin_labels( $post_id );
748                    break;
749                case 'price':
750                    $this->display_price( $post_id );
751                    break;
752                case 'order':
753                    $url = admin_url( $screen->parent_file );
754
755                    $up_url = add_query_arg(
756                        array(
757                            'action'  => 'move-item-up',
758                            'post_id' => (int) $post_id,
759                        ),
760                        wp_nonce_url( $url, 'nova_move_item_up_' . $post_id )
761                    );
762
763                    $down_url  = add_query_arg(
764                        array(
765                            'action'  => 'move-item-down',
766                            'post_id' => (int) $post_id,
767                        ),
768                        wp_nonce_url( $url, 'nova_move_item_down_' . $post_id )
769                    );
770                    $menu_item = get_post( $post_id );
771                    $menu      = $this->get_menu_by_post_id( $post_id );
772                    $term_id   = is_object( $menu ) ? $menu->term_id : '';
773                    ?>
774                    <input type="hidden" class="menu-order-value" name="nova_order[<?php echo (int) $post_id; ?>]" value="<?php echo esc_attr( (string) $menu_item->menu_order ); ?>" />
775                    <input type="hidden" class='nova-menu-term' name="nova_menu_term[<?php echo (int) $post_id; ?>]" value="<?php echo esc_attr( (string) $term_id ); ?>">
776
777                    <span class="hide-if-js">
778                    &nbsp; &nbsp; &mdash; <a class="nova-move-item-up" data-post-id="<?php echo (int) $post_id; ?>" href="<?php echo esc_url( $up_url ); ?>">up</a>
779                    <br />
780                    &nbsp; &nbsp; &mdash; <a class="nova-move-item-down" data-post-id="<?php echo (int) $post_id; ?>" href="<?php echo esc_url( $down_url ); ?>">down</a>
781                    </span>
782                    <?php
783                    break;
784            }
785        }
786
787        /**
788         * Get menu item by post ID.
789         *
790         * @param int $post_id Post ID.
791         *
792         * @return bool|\WP_Term
793         */
794        public function get_menu_by_post_id( $post_id = null ) {
795            if ( ! $post_id ) {
796                return false;
797            }
798
799            $terms = get_the_terms( $post_id, self::MENU_TAX );
800
801            if ( ! is_array( $terms ) ) {
802                return false;
803            }
804
805            return array_pop( $terms );
806        }
807
808        /**
809         * Fires on a menu edit page. We might have drag-n-drop reordered
810         */
811        public function maybe_reorder_menu_items() {
812            // make sure we clicked our button.
813            if (
814                empty( $_REQUEST['menu_reorder_submit'] )
815                || __( 'Save New Order', 'jetpack-classic-theme-helper' ) !== $_REQUEST['menu_reorder_submit']
816            ) {
817                return;
818            }
819
820            // make sure we have the nonce.
821            if (
822                empty( $_REQUEST['drag-drop-reorder'] )
823                || ! wp_verify_nonce( sanitize_key( $_REQUEST['drag-drop-reorder'] ), 'drag-drop-reorder' )
824            ) {
825                return;
826            }
827
828            // make sure we have data to work with.
829            if ( empty( $_REQUEST['nova_menu_term'] ) || empty( $_REQUEST['nova_order'] ) ) {
830                return;
831            }
832
833            $term_pairs  = array_map( 'absint', $_REQUEST['nova_menu_term'] );
834            $order_pairs = array_map( 'absint', $_REQUEST['nova_order'] );
835
836            foreach ( $order_pairs as $id => $menu_order ) {
837                $id = absint( $id );
838                unset( $order_pairs[ $id ] );
839                if ( $id < 0 ) {
840                    continue;
841                }
842
843                $post = get_post( $id );
844                if ( ! $post ) {
845                    continue;
846                }
847
848                // save a write if the order hasn't changed
849                if ( (int) $menu_order !== $post->menu_order ) {
850                    $args = array(
851                        'ID'         => $id,
852                        'menu_order' => $menu_order,
853                    );
854                    wp_update_post( $args );
855                }
856
857                // save a write if the term hasn't changed
858                if (
859                    is_object( $this->get_menu_by_post_id( $id ) ) &&
860                    (int) $term_pairs[ $id ] !== $this->get_menu_by_post_id( $id )->term_id
861                ) {
862                    wp_set_object_terms( $id, $term_pairs[ $id ], self::MENU_TAX );
863                }
864            }
865
866            $redirect = add_query_arg(
867                array(
868                    'post_type'      => self::MENU_ITEM_POST_TYPE,
869                    'nova_reordered' => '1',
870                ),
871                admin_url( 'edit.php' )
872            );
873            wp_safe_redirect( $redirect );
874            exit( 0 );
875        }
876
877        /**
878         * Handle changes to menu items.
879         * (process actions, update data, enqueue necessary scripts).
880         *
881         * @return void
882         */
883        public function edit_menu_items_page_load() {
884            if ( isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we process the form and check nonces in handle_menu_item_actions.
885                $this->handle_menu_item_actions();
886            }
887
888            $this->maybe_reorder_menu_items();
889
890            Assets::register_script(
891                'nova-drag-drop',
892                '../../dist/custom-post-types/js/nova-drag-drop.js',
893                __FILE__,
894                array(
895                    'dependencies' => array(
896                        'jquery',
897                        'jquery-ui-sortable',
898                    ),
899                    'in_footer'    => true,
900                    'enqueue'      => true,
901                    'textdomain'   => 'jetpack-classic-theme-helper',
902                )
903            );
904
905            wp_localize_script(
906                'nova-drag-drop',
907                '_novaDragDrop',
908                array(
909                    'nonce'       => wp_create_nonce( 'drag-drop-reorder' ),
910                    'nonceName'   => 'drag-drop-reorder',
911                    'reorder'     => __( 'Save New Order', 'jetpack-classic-theme-helper' ),
912                    'reorderName' => 'menu_reorder_submit',
913                )
914            );
915            add_action( 'the_post', array( $this, 'show_menu_titles_in_menu_item_list' ) );
916        }
917
918        /**
919         * Process actions to move menu items around.
920         *
921         * @return void
922         */
923        public function handle_menu_item_actions() {
924            if ( isset( $_GET['action'] ) ) {
925                $action = (string) wp_unslash( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we check for nonces below, and check against specific strings in switch statement.
926            } else {
927                return;
928            }
929
930            switch ( $action ) {
931                case 'move-item-up':
932                case 'move-item-down':
933                    $reorder = false;
934
935                    if ( empty( $_GET['post_id'] ) ) {
936                        break;
937                    }
938
939                    $post_id = (int) $_GET['post_id'];
940
941                    $term = $this->get_menu_item_menu_leaf( $post_id );
942
943                    // Get all posts in that term.
944                    $query = new WP_Query(
945                        array(
946                            'taxonomy' => self::MENU_TAX,
947                            'term'     => $term->slug,
948                        )
949                    );
950
951                    $order = array();
952                    foreach ( $query->posts as $post ) {
953                        $order[] = $post->ID;
954                    }
955
956                    if ( 'move-item-up' === $action ) {
957                        check_admin_referer( 'nova_move_item_up_' . $post_id );
958
959                        $first_post_id = $order[0];
960                        if ( $post_id === $first_post_id ) {
961                            break;
962                        }
963
964                        foreach ( $order as $menu_order => $order_post_id ) {
965                            if ( $post_id !== $order_post_id ) {
966                                continue;
967                            }
968
969                            $swap_post_id             = $order[ $menu_order - 1 ];
970                            $order[ $menu_order - 1 ] = $post_id;
971                            $order[ $menu_order ]     = $swap_post_id;
972
973                            $reorder = true;
974                            break;
975                        }
976                    } else {
977                        check_admin_referer( 'nova_move_item_down_' . $post_id );
978
979                        $last_post_id = end( $order );
980                        if ( $post_id === $last_post_id ) {
981                            break;
982                        }
983
984                        foreach ( $order as $menu_order => $order_post_id ) {
985                            if ( $post_id !== $order_post_id ) {
986                                continue;
987                            }
988
989                            $swap_post_id             = $order[ $menu_order + 1 ];
990                            $order[ $menu_order + 1 ] = $post_id;
991                            $order[ $menu_order ]     = $swap_post_id;
992
993                            $reorder = true;
994                        }
995                    }
996
997                    if ( $reorder ) {
998                        foreach ( $order as $menu_order => $id ) {
999                            wp_update_post( compact( 'id', 'menu_order' ) );
1000                        }
1001                    }
1002
1003                    break;
1004                case 'move-menu-up':
1005                case 'move-menu-down':
1006                    $reorder = false;
1007
1008                    if ( empty( $_GET['term_id'] ) ) {
1009                        break;
1010                    }
1011
1012                    $term_id = (int) $_GET['term_id'];
1013
1014                    $terms = $this->get_menus();
1015
1016                    $order = array();
1017                    foreach ( $terms as $term ) {
1018                        $order[] = $term->term_id;
1019                    }
1020
1021                    if ( 'move-menu-up' === $action ) {
1022                        check_admin_referer( 'nova_move_menu_up_' . $term_id );
1023
1024                        $first_term_id = $order[0];
1025                        if ( $term_id === $first_term_id ) {
1026                            break;
1027                        }
1028
1029                        foreach ( $order as $menu_order => $order_term_id ) {
1030                            if ( $term_id !== $order_term_id ) {
1031                                continue;
1032                            }
1033
1034                            $swap_term_id             = $order[ $menu_order - 1 ];
1035                            $order[ $menu_order - 1 ] = $term_id;
1036                            $order[ $menu_order ]     = $swap_term_id;
1037
1038                            $reorder = true;
1039                            break;
1040                        }
1041                    } else {
1042                        check_admin_referer( 'nova_move_menu_down_' . $term_id );
1043
1044                        $last_term_id = end( $order );
1045                        if ( $term_id === $last_term_id ) {
1046                            break;
1047                        }
1048
1049                        foreach ( $order as $menu_order => $order_term_id ) {
1050                            if ( $term_id !== $order_term_id ) {
1051                                continue;
1052                            }
1053
1054                            $swap_term_id             = $order[ $menu_order + 1 ];
1055                            $order[ $menu_order + 1 ] = $term_id;
1056                            $order[ $menu_order ]     = $swap_term_id;
1057
1058                            $reorder = true;
1059                        }
1060                    }
1061
1062                    if ( $reorder ) {
1063                        update_option( 'nova_menu_order', $order );
1064                    }
1065
1066                    break;
1067                default:
1068                    return;
1069            }
1070
1071            $redirect = add_query_arg(
1072                array(
1073                    'post_type'      => self::MENU_ITEM_POST_TYPE,
1074                    'nova_reordered' => '1',
1075                ),
1076                admin_url( 'edit.php' )
1077            );
1078            wp_safe_redirect( $redirect );
1079            exit( 0 );
1080        }
1081
1082        /**
1083         * Add menu title rows to the list table
1084         *
1085         * @param \WP_Post $post The Post object.
1086         *
1087         * @return void
1088         */
1089        public function show_menu_titles_in_menu_item_list( $post ) {
1090            global $wp_list_table;
1091
1092            static $last_term_id = false;
1093
1094            $term = $this->get_menu_item_menu_leaf( $post->ID );
1095
1096            $term_id = $term instanceof \WP_Term ? $term->term_id : null;
1097
1098            if ( false !== $last_term_id && $last_term_id === $term_id ) {
1099                return;
1100            }
1101
1102            if ( $term_id === null ) {
1103                $last_term_id = null;
1104                $term_name    = '';
1105                $parent_count = 0;
1106            } else {
1107                $last_term_id = $term->term_id;
1108                $term_name    = $term->name;
1109                $parent_count = 0;
1110                $current_term = $term;
1111                while ( $current_term->parent ) {
1112                    ++$parent_count;
1113                    $current_term = get_term( $current_term->parent, self::MENU_TAX );
1114                }
1115            }
1116
1117            $non_order_column_count = $wp_list_table->get_column_count() - 1;
1118
1119            $screen = get_current_screen();
1120
1121            $url = admin_url( $screen->parent_file );
1122
1123            $up_url = add_query_arg(
1124                array(
1125                    'action'  => 'move-menu-up',
1126                    'term_id' => (int) $term_id,
1127                ),
1128                wp_nonce_url( $url, 'nova_move_menu_up_' . $term_id )
1129            );
1130
1131            $down_url = add_query_arg(
1132                array(
1133                    'action'  => 'move-menu-down',
1134                    'term_id' => (int) $term_id,
1135                ),
1136                wp_nonce_url( $url, 'nova_move_menu_down_' . $term_id )
1137            );
1138
1139            ?>
1140            <tr class="no-items menu-label-row" data-term_id="<?php echo esc_attr( (string) $term_id ); ?>">
1141                <td class="colspanchange" colspan="<?php echo (int) $non_order_column_count; ?>">
1142                    <h3>
1143                    <?php
1144                        echo str_repeat( ' &mdash; ', (int) $parent_count ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- nothing to escape here.
1145
1146                    if ( $term instanceof \WP_Term ) {
1147                        echo esc_html( sanitize_term_field( 'name', $term_name, (int) $term_id, self::MENU_TAX, 'display' ) );
1148                        edit_term_link( __( 'edit', 'jetpack-classic-theme-helper' ), '<span class="edit-nova-section"><span class="dashicon dashicon-edit"></span>', '</span>', $term );
1149
1150                    } else {
1151                        esc_html_e( 'Uncategorized', 'jetpack-classic-theme-helper' );
1152                    }
1153                    ?>
1154                    </h3>
1155                </td>
1156                <td>
1157                    <?php if ( $term instanceof \WP_Term ) { ?>
1158                    <a class="nova-move-menu-up" title="<?php esc_attr_e( 'Move menu section up', 'jetpack-classic-theme-helper' ); ?>" href="<?php echo esc_url( $up_url ); ?>"><?php echo esc_html_x( 'UP', 'indicates movement (up or down)', 'jetpack-classic-theme-helper' ); ?></a>
1159                    <br />
1160                    <a class="nova-move-menu-down" title="<?php esc_attr_e( 'Move menu section down', 'jetpack-classic-theme-helper' ); ?>" href="<?php echo esc_url( $down_url ); ?>"><?php echo esc_html_x( 'DOWN', 'indicates movement (up or down)', 'jetpack-classic-theme-helper' ); ?></a>
1161                    <?php } ?>
1162                </td>
1163            </tr>
1164            <?php
1165        }
1166
1167        /* Edit Many Items */
1168
1169        /**
1170         * Handle form submissions that aim to add many menu items at once.
1171         * (process posted data and enqueue necessary script).
1172         *
1173         * @return void
1174         */
1175        public function add_many_new_items_page_load() {
1176            if (
1177                isset( $_SERVER['REQUEST_METHOD'] )
1178                && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) )
1179            ) {
1180                $this->process_form_request();
1181                exit( 0 );
1182            }
1183
1184            $this->enqueue_many_items_scripts();
1185        }
1186
1187        /**
1188         * Enqueue script to create many items at once.
1189         *
1190         * @return void
1191         */
1192        public function enqueue_many_items_scripts() {
1193
1194            Assets::register_script(
1195                'nova-many-items',
1196                '../../dist/custom-post-types/js/many-items.js',
1197                __FILE__,
1198                array(
1199                    'in_footer'  => true,
1200                    'enqueue'    => true,
1201                    'textdomain' => 'jetpack-classic-theme-helper',
1202                )
1203            );
1204        }
1205
1206        /**
1207         * Process form request to create many items at once.
1208         *
1209         * @return void
1210         */
1211        public function process_form_request() {
1212            if ( ! isset( $_POST['nova_title'] ) || ! is_array( $_POST['nova_title'] ) ) {
1213                return;
1214            }
1215
1216            $is_ajax = ! empty( $_POST['ajax'] );
1217
1218            if ( $is_ajax ) {
1219                check_ajax_referer( 'nova_many_items' );
1220            } else {
1221                check_admin_referer( 'nova_many_items' );
1222            }
1223
1224            /*
1225            * $_POST is already slashed
1226            * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1227            */
1228            foreach ( array_keys( $_POST['nova_title'] ) as $key ) : // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we sanitize below.
1229                $post_details = array(
1230                    'post_status'  => 'publish',
1231                    'post_type'    => self::MENU_ITEM_POST_TYPE,
1232                    'post_content' => ! empty( $_POST['nova_content'] ) && ! empty( $_POST['nova_content'][ $key ] )
1233                        ? sanitize_text_field( $_POST['nova_content'][ $key ] )
1234                        : '',
1235                    'post_title'   => isset( $_POST['nova_title'][ $key ] )
1236                        ? sanitize_title( $_POST['nova_title'][ $key ] )
1237                        : '',
1238                    'tax_input'    => array(
1239                        self::MENU_ITEM_LABEL_TAX => isset( $_POST['nova_labels'][ $key ] )
1240                            ? sanitize_meta( self::MENU_ITEM_LABEL_TAX, $_POST['nova_labels'][ $key ], 'term' )
1241                            : null,
1242                        self::MENU_TAX            => isset( $_POST['nova_menu_tax'] )
1243                            ? sanitize_meta( self::MENU_TAX, $_POST['nova_menu_tax'], 'term' )
1244                            : null,
1245                    ),
1246                );
1247
1248                $post_id = wp_insert_post( $post_details );
1249                if ( ! $post_id || is_wp_error( $post_id ) ) {
1250                    continue;
1251                }
1252
1253                $this->set_price(
1254                    $post_id,
1255                    isset( $_POST['nova_price'][ $key ] )
1256                        ? sanitize_meta( 'nova_price', $_POST['nova_price'][ $key ], 'post' )
1257                        : ''
1258                );
1259                // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1260
1261                if ( $is_ajax ) :
1262                    $post            = get_post( $post_id );
1263                    $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1264                    setup_postdata( $post );
1265
1266                    ?>
1267                <td><?php the_title(); ?></td>
1268                <td class="nova-price"><?php $this->display_price(); ?></td>
1269                <td><?php $this->list_labels( $post_id ); ?></td>
1270                <td><?php the_content(); ?></td>
1271                    <?php
1272                endif;
1273
1274            endforeach;
1275
1276            if ( $is_ajax ) {
1277                exit( 0 );
1278            }
1279
1280            wp_safe_redirect( admin_url( 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ) );
1281            exit( 0 );
1282        }
1283
1284        /**
1285         * Admin page contents for adding many menu items at once.
1286         *
1287         * @return void
1288         */
1289        public function add_many_new_items_page() {
1290            ?>
1291        <div class="wrap">
1292            <h2><?php esc_html_e( 'Add Many Items', 'jetpack-classic-theme-helper' ); ?></h2>
1293
1294            <p>
1295            <?php
1296            echo wp_kses(
1297                __( 'Use the <kbd>TAB</kbd> key on your keyboard to move between columns and the <kbd>ENTER</kbd> or <kbd>RETURN</kbd> key to save each row and move on to the next.', 'jetpack-classic-theme-helper' ),
1298                array(
1299                    'kbd' => array(),
1300                )
1301            );
1302            ?>
1303            </p>
1304
1305            <form method="post" action="" enctype="multipart/form-data">
1306                <p>
1307                    <h3><?php esc_html_e( 'Add to section:', 'jetpack-classic-theme-helper' ); ?>
1308                    <?php
1309                    wp_dropdown_categories(
1310                        array(
1311                            'id'           => 'nova-menu-tax',
1312                            'name'         => 'nova_menu_tax',
1313                            'taxonomy'     => self::MENU_TAX,
1314                            'hide_empty'   => false,
1315                            'hierarchical' => true,
1316                        )
1317                    );
1318                    ?>
1319                </h3></p>
1320
1321                <table class="many-items-table wp-list-table widefat">
1322                    <thead>
1323                        <tr>
1324                            <th scope="col"><?php esc_html_e( 'Name', 'jetpack-classic-theme-helper' ); ?></th>
1325                            <th scope="col" class="nova-price"><?php esc_html_e( 'Price', 'jetpack-classic-theme-helper' ); ?></th>
1326                            <th scope="col">
1327                                <?php
1328                                echo wp_kses(
1329                                    __( 'Labels: <small>spicy, favorite, etc. <em>Separate Labels with commas</em></small>', 'jetpack-classic-theme-helper' ),
1330                                    array(
1331                                        'small' => array(),
1332                                        'em'    => array(),
1333                                    )
1334                                );
1335                                ?>
1336                            </th>
1337                            <th scope="col"><?php esc_html_e( 'Description', 'jetpack-classic-theme-helper' ); ?></th>
1338                        </tr>
1339                    </thead>
1340                    <tbody>
1341                        <tr>
1342                            <td><input type="text" name="nova_title[]" aria-required="true" /></td>
1343                            <td class="nova-price"><input type="text" name="nova_price[]" /></td>
1344                            <td><input type="text" name="nova_labels[]" /></td>
1345                            <td><textarea name="nova_content[]" cols="20" rows="1"></textarea>
1346                        </tr>
1347                    </tbody>
1348                    <tbody>
1349                        <tr>
1350                            <td><input type="text" name="nova_title[]" aria-required="true" /></td>
1351                            <td class="nova-price"><input type="text" name="nova_price[]" /></td>
1352                            <td><input type="text" name="nova_labels[]" /></td>
1353                            <td><textarea name="nova_content[]" cols="20" rows="1"></textarea>
1354                        </tr>
1355                    </tbody>
1356                    <tfoot>
1357                        <tr>
1358                            <th><a class="button button-secondary nova-new-row"><span class="dashicon dashicon-plus"></span> <?php esc_html_e( 'New Row', 'jetpack-classic-theme-helper' ); ?></a></th>
1359                            <th class="nova-price"></th>
1360                            <th></th>
1361                            <th></th>
1362                        </tr>
1363                    </tfoot>
1364                </table>
1365
1366                <p class="submit">
1367                    <input type="submit" class="button-primary" value="<?php esc_attr_e( 'Add These New Menu Items', 'jetpack-classic-theme-helper' ); ?>" />
1368                    <?php wp_nonce_field( 'nova_many_items' ); ?>
1369                </p>
1370            </form>
1371        </div>
1372            <?php
1373        }
1374
1375        /* Edit One Item */
1376
1377        /**
1378         * Create admin meta box to save price for a menu item,
1379         * and add script to add extra checkboxes to the UI.
1380         *
1381         * @return void
1382         */
1383        public function register_menu_item_meta_boxes() {
1384            wp_enqueue_script( 'nova-menu-checkboxes' );
1385
1386            add_meta_box(
1387                'menu_item_price',
1388                __( 'Price', 'jetpack-classic-theme-helper' ),
1389                array( $this, 'menu_item_price_meta_box' ),
1390                array(),
1391                'side',
1392                'high'
1393            );
1394        }
1395
1396        /**
1397         * Meta box to edit the price of a menu item.
1398         *
1399         * @param WP_Post $post The post object.
1400         *
1401         * @return void
1402         */
1403        public function menu_item_price_meta_box( $post ) {
1404            printf(
1405                '<label for="nova-price-%1$s" class="screen-reader-text">%2$s</label><input type="text" id="nova-price-%1$s" class="widefat" name="nova_price[%1$s]" value="%3$s" />',
1406                (int) $post->ID,
1407                esc_html__( 'Price', 'jetpack-classic-theme-helper' ),
1408                esc_attr( $this->get_price( (int) $post->ID ) )
1409            );
1410        }
1411
1412        /**
1413         * Save the price of a menu item.
1414         *
1415         * @param int $post_id Post ID.
1416         */
1417        public function add_post_meta( $post_id ) {
1418            if ( ! isset( $_POST['nova_price'][ $post_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post.
1419                return;
1420            }
1421
1422            $this->set_price(
1423                $post_id,
1424                sanitize_meta( 'nova_price', wp_unslash( $_POST['nova_price'][ $post_id ] ), 'post' ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post.
1425            );
1426        }
1427
1428        /* Data */
1429
1430        /**
1431         * Get ordered array of menu items.
1432         *
1433         * @param array $args Optional argumments.
1434         *
1435         * @return array
1436         */
1437        public function get_menus( $args = array() ) {
1438            $args             = wp_parse_args(
1439                $args,
1440                array(
1441                    'hide_empty' => false,
1442                )
1443            );
1444            $args['taxonomy'] = self::MENU_TAX;
1445
1446            $terms = get_terms( $args );
1447            if ( ! $terms || is_wp_error( $terms ) ) {
1448                return array();
1449            }
1450
1451            $terms_by_id = array();
1452            foreach ( $terms as $term ) {
1453                $terms_by_id[ "{$term->term_id}" ] = $term;
1454            }
1455
1456            $term_order = get_option( 'nova_menu_order', array() );
1457
1458            $return = array();
1459            foreach ( $term_order as $term_id ) {
1460                if ( isset( $terms_by_id[ "$term_id" ] ) ) {
1461                    $return[] = $terms_by_id[ "$term_id" ];
1462                    unset( $terms_by_id[ "$term_id" ] );
1463                }
1464            }
1465
1466            foreach ( $terms_by_id as $term_id => $term ) {
1467                $return[] = $term;
1468            }
1469
1470            return $return;
1471        }
1472
1473        /**
1474         * Get first menu taxonomy "leaf".
1475         *
1476         * @param int $post_id Post ID.
1477         *
1478         * @return bool|\WP_Term|\WP_Error|null
1479         */
1480        public function get_menu_item_menu_leaf( $post_id ) {
1481            // Get first menu taxonomy "leaf".
1482            $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) );
1483
1484            foreach ( $term_ids as $term_id ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach
1485                $children = get_term_children( $term_id, self::MENU_TAX );
1486                if ( ! $children ) {
1487                    break;
1488                }
1489            }
1490
1491            if ( ! isset( $term_id ) ) {
1492                return false;
1493            }
1494
1495            return get_term( $term_id, self::MENU_TAX );
1496        }
1497
1498        /**
1499         * Get a list of the labels linked to a menu item.
1500         *
1501         * @param int $post_id Post ID.
1502         *
1503         * @return void
1504         */
1505        public function list_labels( $post_id = 0 ) {
1506            $post = get_post( $post_id );
1507            echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), '' );
1508        }
1509
1510        /**
1511         * Get a list of the labels linked to a menu item, with links to manage them.
1512         *
1513         * @param int $post_id Post ID.
1514         *
1515         * @return void
1516         */
1517        public function list_admin_labels( $post_id = 0 ) {
1518            $post   = get_post( $post_id );
1519            $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX );
1520            if ( ! empty( $labels ) ) {
1521                $out = array();
1522                foreach ( $labels as $label ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach
1523                    $out[] = sprintf(
1524                        '<a href="%s">%s</a>',
1525                        esc_url(
1526                            add_query_arg(
1527                                array(
1528                                    'post_type' => self::MENU_ITEM_POST_TYPE,
1529                                    'taxonomy'  => self::MENU_ITEM_LABEL_TAX,
1530                                    'term'      => $label->slug,
1531                                ),
1532                                'edit.php'
1533                            )
1534                        ),
1535                        esc_html(
1536                            sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' )
1537                        )
1538                    );
1539                }
1540
1541                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.
1542            } else {
1543                esc_html_e( 'No Labels', 'jetpack-classic-theme-helper' );
1544            }
1545        }
1546
1547        /**
1548         * Update post meta with the price defined in meta box.
1549         *
1550         * @param int    $post_id Post ID.
1551         * @param string $price   Price.
1552         *
1553         * @return int|bool
1554         */
1555        public function set_price( $post_id = 0, $price = '' ) {
1556            return update_post_meta( $post_id, 'nova_price', $price );
1557        }
1558
1559        /**
1560         * Get the price of a menu item.
1561         *
1562         * @param int $post_id Post ID.
1563         *
1564         * @return bool|string
1565         */
1566        public function get_price( $post_id = 0 ) {
1567            return get_post_meta( $post_id, 'nova_price', true );
1568        }
1569
1570        /**
1571         * Echo the price of a menu item.
1572         *
1573         * @param int $post_id Post ID.
1574         *
1575         * @return void
1576         */
1577        public function display_price( $post_id = 0 ) {
1578            echo esc_html( $this->get_price( $post_id ) );
1579        }
1580
1581        /* Menu Item Loop Markup */
1582
1583        /**
1584         * Get markup for a menu item.
1585         * Note: Does not support nested loops.
1586         *
1587         * @param null|string $field The field to get the value for.
1588         *
1589         * @return array
1590         */
1591        public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
1592            return $this->menu_item_loop_markup;
1593        }
1594
1595        /**
1596         * Sets up the loop markup.
1597         * Attached to the 'template_include' *filter*,
1598         * which fires only during a real blog view (not in admin, feeds, etc.)
1599         *
1600         * @param string $template Template File.
1601         *
1602         * @return string Template File.  VERY Important.
1603         */
1604        public function setup_menu_item_loop_markup__in_filter( $template ) {
1605            add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) );
1606
1607            return $template;
1608        }
1609
1610        /**
1611         * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku
1612         * Attached to the 'loop_start' action.
1613         *
1614         * @param WP_Query $query Post query.
1615         *
1616         * @return void
1617         */
1618        public function start_menu_item_loop( $query ) {
1619            if ( ! $this->is_menu_item_query( $query ) ) {
1620                return;
1621            }
1622
1623            $this->menu_item_loop_last_term_id = false;
1624            $this->menu_item_loop_current_term = false;
1625
1626            add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) );
1627            add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) );
1628        }
1629
1630        /**
1631         * Outputs the Menu Item Loop Markup
1632         * Attached to the 'the_post' action.
1633         *
1634         * @param WP_Post $post Post object.
1635         *
1636         * @return void
1637         */
1638        public function menu_item_loop_each_post( $post ) {
1639            $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID );
1640
1641            if (
1642                false === $this->menu_item_loop_current_term
1643                || null === $this->menu_item_loop_current_term
1644                || is_wp_error( $this->menu_item_loop_current_term )
1645            ) {
1646                return;
1647            }
1648
1649            if ( false === $this->menu_item_loop_last_term_id ) {
1650                // We're at the very beginning of the loop
1651
1652                $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section
1653                $this->menu_item_loop_header(); // Output the menu's header
1654            } elseif (
1655                is_object( $this->menu_item_loop_current_term ) &&
1656                $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id
1657            ) {
1658                // We're not at the very beginning but still need to start a new menu section.  End the previous menu section first.
1659
1660                $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section
1661                $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section
1662                $this->menu_item_loop_header(); // Output the menu's header
1663            }
1664            if ( is_object( $this->menu_item_loop_current_term ) ) {
1665
1666                $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id;
1667            }
1668        }
1669
1670        /**
1671         * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku
1672         * Attached to the 'loop_end' action.
1673         *
1674         * @param WP_Query $query Post query.
1675         *
1676         * @return void
1677         */
1678        public function stop_menu_item_loop( $query ) {
1679            if ( ! $this->is_menu_item_query( $query ) ) {
1680                return;
1681            }
1682
1683            remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) );
1684            remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) );
1685            remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) );
1686
1687            $this->menu_item_loop_close_element( 'menu' ); // End the last menu section
1688        }
1689
1690        /**
1691         * Outputs the Menu Group Header
1692         *
1693         * @return void
1694         */
1695        public function menu_item_loop_header() {
1696            $this->menu_item_loop_open_element( 'menu_header' );
1697                $this->menu_item_loop_open_element( 'menu_title' );
1698                    echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter
1699                $this->menu_item_loop_close_element( 'menu_title' );
1700            if ( $this->menu_item_loop_current_term->description ) :
1701                $this->menu_item_loop_open_element( 'menu_description' );
1702                    echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter
1703                $this->menu_item_loop_close_element( 'menu_description' );
1704            endif;
1705            $this->menu_item_loop_close_element( 'menu_header' );
1706        }
1707
1708        /**
1709         * Outputs a Menu Item Markup element opening tag
1710         *
1711         * @param string $field - Menu Item Markup settings field.
1712         *
1713         * @return void
1714         */
1715        public function menu_item_loop_open_element( $field ) {
1716            $markup = $this->get_menu_item_loop_markup();
1717            /**
1718             * Filter a menu item's element opening tag.
1719             *
1720             * @module custom-content-types
1721             *
1722             * @since 4.4.0
1723             *
1724             * @param string       $tag    Menu item's element opening tag.
1725             * @param string       $field  Menu Item Markup settings field.
1726             * @param array        $markup Array of markup elements for the menu item.
1727             * @param false|object $term   Taxonomy term for current menu item.
1728             */
1729            echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class.
1730                'jetpack_nova_menu_item_loop_open_element',
1731                '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n",
1732                $field,
1733                $markup,
1734                $this->menu_item_loop_current_term
1735            );
1736        }
1737
1738        /**
1739         * Outputs a Menu Item Markup element closing tag
1740         *
1741         * @param string $field - Menu Item Markup settings field.
1742         *
1743         * @return void
1744         */
1745        public function menu_item_loop_close_element( $field ) {
1746            $markup = $this->get_menu_item_loop_markup();
1747            /**
1748             * Filter a menu item's element closing tag.
1749             *
1750             * @module custom-content-types
1751             *
1752             * @since 4.4.0
1753             *
1754             * @param string       $tag    Menu item's element closing tag.
1755             * @param string       $field  Menu Item Markup settings field.
1756             * @param array        $markup Array of markup elements for the menu item.
1757             * @param false|object $term   Taxonomy term for current menu item.
1758             */
1759            echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used.
1760                'jetpack_nova_menu_item_loop_close_element',
1761                '</' . tag_escape( $markup[ "{$field}_tag" ] ) . ">\n",
1762                $field,
1763                $markup,
1764                $this->menu_item_loop_current_term
1765            );
1766        }
1767
1768        /**
1769         * Returns a Menu Item Markup element's class attribute.
1770         *
1771         * @param  string $class Class name.
1772         *
1773         * @return string HTML class attribute with leading whitespace.
1774         */
1775        public function menu_item_loop_class( $class ) {
1776            if ( ! $class ) {
1777                return '';
1778            }
1779
1780            /**
1781             * Filter a menu Item Markup element's class attribute.
1782             *
1783             * @module custom-content-types
1784             *
1785             * @since 4.4.0
1786             *
1787             * @param string       $tag    Menu Item Markup element's class attribute.
1788             * @param string       $class  Menu Item Class name.
1789             * @param false|object $term   Taxonomy term for current menu item.
1790             */
1791            return apply_filters(
1792                'jetpack_nova_menu_item_loop_class',
1793                ' class="' . esc_attr( $class ) . '"',
1794                $class,
1795                $this->menu_item_loop_current_term
1796            );
1797        }
1798    }
1799
1800}