Completed
Pull Request — master (#44)
by
unknown
01:59
created

includes/wp-api-menus-v2.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * WP REST API Menu routes
4
 *
5
 * @package WP_API_Menus
6
 */
7
8
if ( ! defined( 'ABSPATH' ) ) {
9
    exit; // Exit if accessed directly
10
}
11
12
if ( ! class_exists( 'WP_REST_Menus' ) ) :
13
14
15
    /**
16
     * WP REST Menus class.
17
     *
18
     * WP API Menus support for WP API v2.
19
     *
20
     * @package WP_API_Menus
21
     * @since 1.2.0
22
     */
23
    class WP_REST_Menus {
24
25
26
	    /**
27
	     * Get WP API namespace.
28
	     *
29
	     * @since 1.2.0
30
	     * @return string
31
	     */
32
        public static function get_api_namespace() {
33
            return 'wp/v2';
34
        }
35
36
37
	    /**
38
	     * Get WP API Menus namespace.
39
	     *
40
	     * @since 1.2.1
41
	     * @return string
42
	     */
43
	    public static function get_plugin_namespace() {
44
		    return 'wp-api-menus/v2';
45
	    }
46
47
48
        /**
49
         * Register menu routes for WP API v2.
50
         *
51
         * @since  1.2.0
52
         */
53
        public function register_routes() {
54
55
            register_rest_route( self::get_plugin_namespace(), '/menus', array(
56
                array(
57
                    'methods'  => WP_REST_Server::READABLE,
58
                    'callback' => array( $this, 'get_menus' ),
59
                )
60
            ) );
61
62
            register_rest_route( self::get_plugin_namespace(), '/menus/(?P<id>\d+)', array(
63
                array(
64
                    'methods'  => WP_REST_Server::READABLE,
65
                    'callback' => array( $this, 'get_menu' ),
66
                    'args'     => array(
67
                        'context' => array(
68
                        'default' => 'view',
69
                        ),
70
                    ),
71
                )
72
            ) );
73
74
            register_rest_route( self::get_plugin_namespace(), '/menu-locations', array(
75
                array(
76
                    'methods'  => WP_REST_Server::READABLE,
77
                    'callback' => array( $this, 'get_menu_locations' ),
78
                )
79
            ) );
80
81
            register_rest_route( self::get_plugin_namespace(), '/menu-locations/(?P<location>[a-zA-Z0-9_-]+)', array(
82
                array(
83
                    'methods'  => WP_REST_Server::READABLE,
84
                    'callback' => array( $this, 'get_menu_location' ),
85
                )
86
            ) );
87
        }
88
89
90
        /**
91
         * Get menus.
92
         *
93
         * @since  1.2.0
94
         * @return array All registered menus
95
         */
96
        public static function get_menus() {
97
98
            $rest_url = trailingslashit( get_rest_url() . self::get_plugin_namespace() . '/menus/' );
99
            $wp_menus = wp_get_nav_menus();
100
101
            $i = 0;
102
            $rest_menus = array();
103 View Code Duplication
            foreach ( $wp_menus as $wp_menu ) :
104
105
                $menu = (array) $wp_menu;
106
107
                $rest_menus[ $i ]                = $menu;
108
                $rest_menus[ $i ]['ID']          = $menu['term_id'];
109
                $rest_menus[ $i ]['name']        = $menu['name'];
110
                $rest_menus[ $i ]['slug']        = $menu['slug'];
111
                $rest_menus[ $i ]['description'] = $menu['description'];
112
                $rest_menus[ $i ]['count']       = $menu['count'];
113
114
                $rest_menus[ $i ]['meta']['links']['collection'] = $rest_url;
115
                $rest_menus[ $i ]['meta']['links']['self']       = $rest_url . $menu['term_id'];
116
117
                $i ++;
118
            endforeach;
119
120
            return apply_filters( 'rest_menus_format_menus', $rest_menus );
121
        }
122
123
124
        /**
125
         * Get a menu.
126
         *
127
         * @since  1.2.0
128
         * @param  $request
129
         * @return array Menu data
130
         */
131
        public function get_menu( $request ) {
132
133
            $id             = (int) $request['id'];
134
            $rest_url       = get_rest_url() . self::get_api_namespace() . '/menus/';
135
            $wp_menu_object = $id ? wp_get_nav_menu_object( $id ) : array();
136
            $wp_menu_items  = $id ? wp_get_nav_menu_items( $id ) : array();
137
138
            $rest_menu = array();
139
140 View Code Duplication
            if ( $wp_menu_object ) :
141
142
                $menu = (array) $wp_menu_object;
143
                $rest_menu['ID']          = abs( $menu['term_id'] );
144
                $rest_menu['name']        = $menu['name'];
145
                $rest_menu['slug']        = $menu['slug'];
146
                $rest_menu['description'] = $menu['description'];
147
                $rest_menu['count']       = abs( $menu['count'] );
148
149
                $rest_menu_items = array();
150
                foreach ( $wp_menu_items as $item_object ) {
151
	                $rest_menu_items[] = $this->format_menu_item( $item_object );
152
                }
153
154
                $rest_menu_items = $this->nested_menu_items($rest_menu_items, 0);
155
156
                $rest_menu['items']                       = $rest_menu_items;
157
                $rest_menu['meta']['links']['collection'] = $rest_url;
158
                $rest_menu['meta']['links']['self']       = $rest_url . $id;
159
160
            endif;
161
162
            return apply_filters( 'rest_menus_format_menu', $rest_menu );
163
        }
164
165
166
        /**
167
         * Handle nested menu items.
168
         *
169
         * Given a flat array of menu items, split them into parent/child items
170
         * and recurse over them to return children nested in their parent.
171
         *
172
         * @since  1.2.0
173
         * @param  $menu_items
174
         * @param  $parent
175
         * @return array
176
         */
177
        private function nested_menu_items( &$menu_items, $parent = null ) {
178
179
            $parents = array();
180
            $children = array();
181
182
            // Separate menu_items into parents & children.
183
            array_map( function( $i ) use ( $parent, &$children, &$parents ){
184
                if ( $i['id'] != $parent && $i['parent'] == $parent ) {
185
                    $parents[] = $i;
186
                } else {
187
                    $children[] = $i;
188
                }
189
            }, $menu_items );
190
191
            foreach ( $parents as &$parent ) {
192
193
                if ( $this->has_children( $children, $parent['id'] ) ) {
194
                    $parent['children'] = $this->nested_menu_items( $children, $parent['id'] );
195
                }
196
            }
197
198
            return $parents;
199
        }
200
201
202
        /**
203
         * Check if a collection of menu items contains an item that is the parent id of 'id'.
204
         *
205
         * @since  1.2.0
206
         * @param  array $items
207
         * @param  int $id
208
         * @return array
209
         */
210
        private function has_children( $items, $id ) {
211
            return array_filter( $items, function( $i ) use ( $id ) {
212
                return $i['parent'] == $id;
213
            } );
214
        }
215
216
217
        /**
218
         * Get menu locations.
219
         *
220
         * @since 1.2.0
221
         * @param  $request
222
         * @return array All registered menus locations
223
         */
224 View Code Duplication
        public static function get_menu_locations( $request ) {
225
226
            $locations        = get_nav_menu_locations();
227
            $registered_menus = get_registered_nav_menus();
228
	        $rest_url         = get_rest_url() . self::get_api_namespace() . '/menu-locations/';
229
            $rest_menus       = array();
230
231
            if ( $locations && $registered_menus ) :
232
233
                foreach ( $registered_menus as $slug => $label ) :
234
235
	                // Sanity check
236
	                if ( ! isset( $locations[ $slug ] ) ) {
237
		                continue;
238
	                }
239
240
	                $rest_menus[ $slug ]['ID']                          = $locations[ $slug ];
241
                    $rest_menus[ $slug ]['label']                       = $label;
242
                    $rest_menus[ $slug ]['meta']['links']['collection'] = $rest_url;
243
                    $rest_menus[ $slug ]['meta']['links']['self']       = $rest_url . $slug;
244
245
                endforeach;
246
247
            endif;
248
249
            return $rest_menus;
250
        }
251
252
253
        /**
254
         * Get menu for location.
255
         *
256
         * @since 1.2.0
257
         * @param  $request
258
         * @return array The menu for the corresponding location
259
         */
260
        public function get_menu_location( $request ) {
261
262
            $params     = $request->get_params();
263
            $location   = $params['location'];
264
            $locations  = get_nav_menu_locations();
265
266
            if ( ! isset( $locations[ $location ] ) ) {
267
	            return array();
268
            }
269
270
            $wp_menu = wp_get_nav_menu_object( $locations[ $location ] );
271
            $menu_items = wp_get_nav_menu_items( $wp_menu->term_id );
272
273
			/**
274
			 * wp_get_nav_menu_items() outputs a list that's already sequenced correctly.
275
			 * So the easiest thing to do is to reverse the list and then build our tree
276
			 * from the ground up
277
			 */
278
			$rev_items = array_reverse ( $menu_items );
279
			$rev_menu  = array();
280
			$cache     = array();
281
282
			foreach ( $rev_items as $item ) :
283
284
				$formatted = array(
285
					'ID'          => abs( $item->ID ),
286
					'order'       => (int) $item->menu_order,
287
					'parent'      => abs( $item->menu_item_parent ),
288
					'title'       => $item->title,
289
					'url'         => $item->url,
290
					'attr'        => $item->attr_title,
291
					'target'      => $item->target,
292
					'classes'     => implode( ' ', $item->classes ),
293
					'xfn'         => $item->xfn,
294
					'description' => $item->description,
295
					'object_id'   => abs( $item->object_id ),
296
					'object'      => $item->object,
297
					'type'        => $item->type,
298
					'type_label'  => $item->type_label,
299
					'children'    => array(),
300
				);
301
302
				if ( array_key_exists( $item->ID , $cache ) ) {
303
					$formatted['children'] = array_reverse( $cache[ $item->ID ] );
304
				}
305
306
            	$formatted = apply_filters( 'rest_menus_format_menu_item', $formatted );
307
308
				if ( $item->menu_item_parent != 0 ) {
309
310
					if ( array_key_exists( $item->menu_item_parent , $cache ) ) {
311
						array_push( $cache[ $item->menu_item_parent ], $formatted );
312
					} else {
313
						$cache[ $item->menu_item_parent ] = array( $formatted, );
314
					}
315
316
				} else {
317
318
					array_push( $rev_menu, $formatted );
319
				}
320
321
			endforeach;
322
323
			return array_reverse ( $rev_menu );
324
        }
325
326
327
        /**
328
         * Returns all child nav_menu_items under a specific parent.
329
         *
330
         * @since   1.2.0
331
         * @param int   $parent_id      The parent nav_menu_item ID
332
         * @param array $nav_menu_items Navigation menu items
333
         * @param bool  $depth          Gives all children or direct children only
334
         * @return array	returns filtered array of nav_menu_items
335
         */
336 View Code Duplication
        public function get_nav_menu_item_children( $parent_id, $nav_menu_items, $depth = true ) {
337
338
            $nav_menu_item_list = array();
339
340
            foreach ( (array) $nav_menu_items as $nav_menu_item ) :
341
342
                if ( $nav_menu_item->menu_item_parent == $parent_id ) :
343
344
                    $nav_menu_item_list[] = $this->format_menu_item( $nav_menu_item, true, $nav_menu_items );
345
346
                    if ( $depth ) {
347
                        if ( $children = $this->get_nav_menu_item_children( $nav_menu_item->ID, $nav_menu_items ) ) {
348
                            $nav_menu_item_list = array_merge( $nav_menu_item_list, $children );
349
                        }
350
                    }
351
352
                endif;
353
354
            endforeach;
355
356
            return $nav_menu_item_list;
357
        }
358
359
360
        /**
361
         * Format a menu item for REST API consumption.
362
         *
363
         * @since  1.2.0
364
         * @param  object|array $menu_item  The menu item
365
         * @param  bool         $children   Get menu item children (default false)
366
         * @param  array        $menu       The menu the item belongs to (used when $children is set to true)
367
         * @return array	a formatted menu item for REST
368
         */
369 View Code Duplication
        public function format_menu_item( $menu_item, $children = false, $menu = array() ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
370
371
            $item = (array) $menu_item;
372
373
            $menu_item = array(
374
                'id'          => abs( $item['ID'] ),
375
                'order'       => (int) $item['menu_order'],
376
                'parent'      => abs( $item['menu_item_parent'] ),
377
                'title'       => $item['title'],
378
                'url'         => $item['url'],
379
                'attr'        => $item['attr_title'],
380
                'target'      => $item['target'],
381
                'classes'     => implode( ' ', $item['classes'] ),
382
                'xfn'         => $item['xfn'],
383
                'description' => $item['description'],
384
                'object_id'   => abs( $item['object_id'] ),
385
                'object'      => $item['object'],
386
                'object_slug' => get_post( $item['object_id'] )->post_name,
387
                'type'        => $item['type'],
388
                'type_label'  => $item['type_label'],
389
            );
390
391
            if ( $children === true && ! empty( $menu ) ) {
392
	            $menu_item['children'] = $this->get_nav_menu_item_children( $item['ID'], $menu );
393
            }
394
395
            return apply_filters( 'rest_menus_format_menu_item', $menu_item );
396
        }
397
398
399
    }
400
401
402
endif;
403