Completed
Pull Request — master (#27)
by
unknown
02:13
created

WP_REST_Menus::has_children()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 1
Metric Value
cc 1
eloc 3
c 6
b 1
f 1
nc 1
nop 2
dl 0
loc 5
rs 9.4285
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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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
         * @return array
53
         */
54
        public function register_routes() {
55
56
            register_rest_route( self::get_plugin_namespace(), '/menus', array(
57
                array(
58
                    'methods'  => WP_REST_Server::READABLE,
59
                    'callback' => array( $this, 'get_menus' ),
60
                )
61
            ) );
62
63
            register_rest_route( self::get_plugin_namespace(), '/menus/(?P<id>\d+)', array(
64
                array(
65
                    'methods'  => WP_REST_Server::READABLE,
66
                    'callback' => array( $this, 'get_menu' ),
67
                    'args'     => array(
68
                        'context' => array(
69
                        'default' => 'view',
70
                        ),
71
                    ),
72
                )
73
            ) );
74
75
76
            register_rest_route( self::get_plugin_namespace(), '/menu-html/(?P<menu_id>[a-zA-Z0-9_-]+)', array(
77
                array(
78
                    'methods'  => WP_REST_Server::READABLE,
79
                    'callback' => array( $this, 'get_menu_html' ),
80
                    'args'     => array(
81
                        'context' => array(
82
                        'default' => 'view',
83
                        ),
84
                    ),
85
                )
86
            ) );
87
88
            register_rest_route( self::get_plugin_namespace(), '/menu-locations', array(
89
                array(
90
                    'methods'  => WP_REST_Server::READABLE,
91
                    'callback' => array( $this, 'get_menu_locations' ),
92
                )
93
            ) );
94
95
            register_rest_route( self::get_plugin_namespace(), '/menu-locations/(?P<location>[a-zA-Z0-9_-]+)', array(
96
                array(
97
                    'methods'  => WP_REST_Server::READABLE,
98
                    'callback' => array( $this, 'get_menu_location' ),
99
                )
100
            ) );
101
102
            register_rest_route( self::get_plugin_namespace(), '/menu-html-location/(?P<location>[a-zA-Z0-9_-]+)', array(
103
                array(
104
                    'methods'  => WP_REST_Server::READABLE,
105
                    'callback' => array( $this, 'get_menu_html_location' ),
106
                )
107
            ) );
108
109
        }
110
111
112
        /**
113
         * Get menus.
114
         *
115
         * @since  1.2.0
116
         * @return array All registered menus
117
         */
118
        public static function get_menus() {
119
120
            $rest_url = trailingslashit( get_rest_url() . self::get_plugin_namespace() . '/menus/' );
121
            $rest_url_base = get_rest_url() . self::get_plugin_namespace();
122
            $wp_menus = wp_get_nav_menus();
123
124
            $i = 0;
125
            $rest_menus = array();
126 View Code Duplication
            foreach ( $wp_menus as $wp_menu ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
127
128
                $menu = (array) $wp_menu;
129
130
                $rest_menus[ $i ]                = $menu;
131
                $rest_menus[ $i ]['ID']          = $menu['term_id'];
132
                $rest_menus[ $i ]['name']        = $menu['name'];
133
                $rest_menus[ $i ]['slug']        = $menu['slug'];
134
                $rest_menus[ $i ]['description'] = $menu['description'];
135
                $rest_menus[ $i ]['count']       = $menu['count'];
136
137
                $rest_menus[ $i ]['meta']['links']['collection'] = $rest_url;
138
                $rest_menus[ $i ]['meta']['links']['self']       = $rest_url . $menu['term_id'];
139
                $rest_menus[ $i ]['meta']['links']['html']       = $rest_url_base . '/menu-html/' . $menu['term_id'];
140
141
                $i ++;
142
            endforeach;
143
144
            return apply_filters( 'rest_menus_format_menus', $rest_menus );
145
        }
146
147
148
        /**
149
         * Get a menu.
150
         *
151
         * @since  1.2.0
152
         * @param  $request
153
         * @return array Menu data
154
         */
155
        public function get_menu( $request ) {
156
157
            $id             = (int) $request['id'];
158
            $rest_url       = get_rest_url() . self::get_plugin_namespace() . '/menus/';
159
            $wp_menu_object = $id ? wp_get_nav_menu_object( $id ) : array();
160
            $wp_menu_items  = $id ? wp_get_nav_menu_items( $id ) : array();
161
162
            $rest_menu = array();
163
164 View Code Duplication
            if ( $wp_menu_object ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
165
166
                $menu = (array) $wp_menu_object;
167
                $rest_menu['ID']          = abs( $menu['term_id'] );
168
                $rest_menu['name']        = $menu['name'];
169
                $rest_menu['slug']        = $menu['slug'];
170
                $rest_menu['description'] = $menu['description'];
171
                $rest_menu['count']       = abs( $menu['count'] );
172
173
                $rest_menu_items = array();
174
                foreach ( $wp_menu_items as $item_object ) {
175
	                $rest_menu_items[] = $this->format_menu_item( $item_object );
176
                }
177
178
                $rest_menu_items = $this->nested_menu_items($rest_menu_items, 0);
179
180
                $rest_menu['items']                       = $rest_menu_items;
181
                $rest_menu['meta']['links']['collection'] = $rest_url;
182
                $rest_menu['meta']['links']['self']       = $rest_url . $id;
183
184
            endif;
185
186
            return apply_filters( 'rest_menus_format_menu', $rest_menu );
187
        }
188
189
        /**
190
         * Get a menu rendered in html.
191
         *
192
         * @since  1.x.0
193
         * @param  $request
194
         * @return array Menu data
195
         */
196
        public function get_menu_html( $request ) {
197
198
            $menu_id        = $request['menu_id'];
199
            $rest_url_base  = get_rest_url() . self::get_plugin_namespace();
200
            $wp_menu_object = $menu_id ? wp_get_nav_menu_object( $menu_id) : array();
201
            $wp_menu_items  = $menu_id ? wp_get_nav_menu_items( $menu_id ) : array();
0 ignored issues
show
Unused Code introduced by
$wp_menu_items is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
202
203
			$rest_menu = array();
204
205 View Code Duplication
            if ( $wp_menu_object ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
206
207
                $menu = (array) $wp_menu_object;
208
                $rest_menu['ID']          = abs( $menu['term_id'] );
209
                $rest_menu['name']        = $menu['name'];
210
                $rest_menu['slug']        = $menu['slug'];
211
                $rest_menu['description'] = $menu['description'];
212
                $rest_menu['count']       = abs( $menu['count'] );
213
214
                ob_start();
215
           			wp_nav_menu( array( 'menu' => $menu_id ) );
216
           		$rest_menu['html']=ob_get_clean();
217
218
           		$rest_menu['meta']['links']['collection'] = $rest_url_base . '/menus/';
219
                $rest_menu['meta']['links']['self']       = $rest_url_base . '/menu-html/' . $menu_id;
220
221
            endif;
222
223
            return apply_filters( 'rest_menus_format_menu', $rest_menu );
224
        }
225
226
227
228
        /**
229
         * Handle nested menu items.
230
         *
231
         * Given a flat array of menu items, split them into parent/child items
232
         * and recurse over them to return children nested in their parent.
233
         *
234
         * @since  1.2.0
235
         * @param  $menu_items
236
         * @param  $parent
237
         * @return array
238
         */
239
        private function nested_menu_items( &$menu_items, $parent = null ) {
240
241
            $parents = array();
242
            $children = array();
243
244
            // Separate menu_items into parents & children.
245
            array_map( function( $i ) use ( $parent, &$children, &$parents ){
246
                if ( $i['id'] != $parent && $i['parent'] == $parent ) {
247
                    $parents[] = $i;
248
                } else {
249
                    $children[] = $i;
250
                }
251
            }, $menu_items );
252
253
            foreach ( $parents as &$parent ) {
254
255
                if ( $this->has_children( $children, $parent['id'] ) ) {
256
                    $parent['children'] = $this->nested_menu_items( $children, $parent['id'] );
257
                }
258
            }
259
260
            return $parents;
261
        }
262
263
264
        /**
265
         * Check if a collection of menu items contains an item that is the parent id of 'id'.
266
         *
267
         * @since  1.2.0
268
         * @param  array $items
269
         * @param  int $id
270
         * @return array
271
         */
272
        private function has_children( $items, $id ) {
273
            return array_filter( $items, function( $i ) use ( $id ) {
274
                return $i['parent'] == $id;
275
            } );
276
        }
277
278
279
        /**
280
         * Get menu locations.
281
         *
282
         * @since 1.2.0
283
         * @param  $request
284
         * @return array All registered menus locations
285
         */
286
        public static function get_menu_locations( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
287
288
            $locations        = get_nav_menu_locations();
289
            $registered_menus = get_registered_nav_menus();
290
	        $rest_url         = get_rest_url() . self::get_plugin_namespace() . '/menu-locations/';
291
	        $rest_url_base    = get_rest_url() . self::get_plugin_namespace();
292
            $rest_menus       = array();
293
294 View Code Duplication
            if ( $locations && $registered_menus ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
295
296
                foreach ( $registered_menus as $slug => $label ) :
297
298
	                // Sanity check
299
	                if ( ! isset( $locations[ $slug ] ) ) {
300
		                continue;
301
	                }
302
303
	                $rest_menus[ $slug ]['ID']                          = $locations[ $slug ];
304
                    $rest_menus[ $slug ]['label']                       = $label;
305
                    $rest_menus[ $slug ]['meta']['links']['collection'] = $rest_url;
306
                    $rest_menus[ $slug ]['meta']['links']['self']       = $rest_url . $slug;
307
                    $rest_menus[ $slug ]['meta']['links']['html']       = $rest_url_base . '/menu-html-location/' . $slug;
308
309
                endforeach;
310
311
            endif;
312
313
            return $rest_menus;
314
        }
315
316
317
        /**
318
         * Get menu for location.
319
         *
320
         * @since 1.2.0
321
         * @param  $request
322
         * @return array The menu for the corresponding location
323
         */
324
        public function get_menu_location( $request ) {
325
326
            $params     = $request->get_params();
327
            $location   = $params['location'];
328
            $locations  = get_nav_menu_locations();
329
330
            if ( ! isset( $locations[ $location ] ) ) {
331
	            return array();
332
            }
333
334
            $wp_menu = wp_get_nav_menu_object( $locations[ $location ] );
335
            $menu_items = wp_get_nav_menu_items( $wp_menu->term_id );
336
337
			/**
338
			 * wp_get_nav_menu_items() outputs a list that's already sequenced correctly.
339
			 * So the easiest thing to do is to reverse the list and then build our tree
340
			 * from the ground up
341
			 */
342
			$rev_items = array_reverse ( $menu_items );
343
			$rev_menu = array();
344
			$cache = array();
345
			foreach ( $rev_items as $item ) :
346
				$formatted = array(
347
					'ID'          => abs( $item->ID ),
348
					'order'       => (int) $item->menu_order,
349
					'parent'      => abs( $item->menu_item_parent ),
350
					'title'       => $item->title,
351
					'url'         => $item->url,
352
					'attr'        => $item->attr_title,
353
					'target'      => $item->target,
354
					'classes'     => implode( ' ', $item->classes ),
355
					'xfn'         => $item->xfn,
356
					'description' => $item->description,
357
					'object_id'   => abs( $item->object_id ),
358
					'object'      => $item->object,
359
					'type'        => $item->type,
360
					'type_label'  => $item->type_label,
361
					'children'    => array(),
362
				);
363
				// Pickup my children
364
				if ( array_key_exists ( $item->ID , $cache ) ) {
365
					$formatted['children'] = array_reverse ( $cache[ $item->ID ] );
366
				}
367
368
            	$formatted = apply_filters( 'rest_menus_format_menu_item', $formatted );
369
370
				if ( $item->menu_item_parent != 0 ) {
371
					// Wait for parent to pick me up
372
					if ( array_key_exists ( $item->menu_item_parent , $cache ) ) {
373
						array_push( $cache[ $item->menu_item_parent ], $formatted );
374
					} else {
375
						$cache[ $item->menu_item_parent ] = array( $formatted, );
376
					}
377
				} else {
378
					array_push( $rev_menu, $formatted );
379
				}
380
			endforeach;
381
			return array_reverse ( $rev_menu );
382
        }
383
384
385
        /**
386
         * Get menu rendered in html for location.
387
         *
388
         * @since 1.x.0
389
         * @param  $request
390
         * @return array The menu for the corresponding location
391
         */
392
        public function get_menu_html_location( $request ) {
393
394
            $params     = $request->get_params();
395
            $location   = $params['location'];
396
            $locations  = get_nav_menu_locations();
397
398
            //Check if location exists and return empty array if it doesn't
399
            if ( ! isset( $locations[ $location ] ) ) {
400
	            return array();
401
            }
402
403
            $rest_url_base  = get_rest_url() . self::get_plugin_namespace();
404
            $wp_menu_object = get_term( $locations[$location], 'nav_menu' );
405
            $rest_menu = array();
406
407 View Code Duplication
            if ( $wp_menu_object ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
408
409
                $menu = (array) $wp_menu_object;
410
                $rest_menu['ID']          	= abs( $menu['term_id'] );
411
                $rest_menu['name']        	= $menu['name'];
412
                $rest_menu['slug']        	= $menu['slug'];
413
                $rest_menu['location_slug'] = $location;
414
                $rest_menu['description'] 	= $menu['description'];
415
                $rest_menu['count']       	= abs( $menu['count'] );
416
417
                ob_start();
418
					wp_nav_menu( array( 'theme_location' => $location ) );
419
				$rest_menu['html']=ob_get_clean();
420
421
           		$rest_menu['meta']['links']['collection'] = $rest_url_base . '/menu-locations/';
422
                $rest_menu['meta']['links']['self']       = $rest_url_base . '/menu-html-location/' . $location;
423
424
            endif;
425
426
			return $rest_menu;
427
        }
428
429
430
        /**
431
         * Returns all child nav_menu_items under a specific parent.
432
         *
433
         * @since   1.2.0
434
         * @param int   $parent_id      The parent nav_menu_item ID
435
         * @param array $nav_menu_items Navigation menu items
436
         * @param bool  $depth          Gives all children or direct children only
437
         * @return  array   returns filtered array of nav_menu_items
438
         */
439 View Code Duplication
        public function get_nav_menu_item_children( $parent_id, $nav_menu_items, $depth = true ) {
0 ignored issues
show
Duplication introduced by
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...
440
441
            $nav_menu_item_list = array();
442
443
            foreach ( (array) $nav_menu_items as $nav_menu_item ) :
444
445
                if ( $nav_menu_item->menu_item_parent == $parent_id ) :
446
447
                    $nav_menu_item_list[] = $this->format_menu_item( $nav_menu_item, true, $nav_menu_items );
448
449
                    if ( $depth ) {
450
                        if ( $children = $this->get_nav_menu_item_children( $nav_menu_item->ID, $nav_menu_items ) ) {
451
                            $nav_menu_item_list = array_merge( $nav_menu_item_list, $children );
452
                        }
453
                    }
454
455
                endif;
456
457
            endforeach;
458
459
            return $nav_menu_item_list;
460
        }
461
462
463
        /**
464
         * Format a menu item for REST API consumption.
465
         *
466
         * @since  1.2.0
467
         * @param  object|array $menu_item  The menu item
468
         * @param  bool         $children   Get menu item children (default false)
469
         * @param  array        $menu       The menu the item belongs to (used when $children is set to true)
470
         * @return array   a formatted menu item for REST
471
         */
472 View Code Duplication
        public function format_menu_item( $menu_item, $children = false, $menu = array() ) {
0 ignored issues
show
Duplication introduced by
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...
473
474
            $item = (array) $menu_item;
475
476
            $menu_item = array(
477
                'id'          => abs( $item['ID'] ),
478
                'order'       => (int) $item['menu_order'],
479
                'parent'      => abs( $item['menu_item_parent'] ),
480
                'title'       => $item['title'],
481
                'url'         => $item['url'],
482
                'attr'        => $item['attr_title'],
483
                'target'      => $item['target'],
484
                'classes'     => implode( ' ', $item['classes'] ),
485
                'xfn'         => $item['xfn'],
486
                'description' => $item['description'],
487
                'object_id'   => abs( $item['object_id'] ),
488
                'object'      => $item['object'],
489
                'type'        => $item['type'],
490
                'type_label'  => $item['type_label'],
491
            );
492
493
            if ( $children === true && ! empty( $menu ) ) {
494
	            $menu_item['children'] = $this->get_nav_menu_item_children( $item['ID'], $menu );
495
            }
496
497
            return apply_filters( 'rest_menus_format_menu_item', $menu_item );
498
        }
499
500
501
    }
502
503
504
endif;
505