PluginProbe
Responsive Menu – Create Mobile-Friendly Menu / 4.7.4
Responsive Menu – Create Mobile-Friendly Menu v4.7.4
4.7.4 4.7.3 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.10 4.1.11 4.1.12 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 All 91 releases
responsive-menu / v4.0.0 / inc / helpers / custom-functions.php

custom-functions.php in Responsive Menu – Create Mobile-Friendly Menu 4.7.4, at v4.0.0/inc/helpers/custom-functions.php

708 lines 21.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * RMP features custom functions.
4 *
5 * @version 4.0.0
6 *
7 * @package responsive-menu
8 */
9
10 // namespace RMP\Features\Inc\Helpers;
11
12 /**
13 * Function to check the input is checked or not.
14 *
15 * @version 4.0.0
16 *
17 * @param string $actual_value
18 *
19 * @return string|null
20 */
21 function is_rmp_option_checked( $actual_value, $options, $key ) {
22 if ( empty( $options[ $key ] ) ) {
23 return;
24 }
25
26 $checked_value = $options[ $key ];
27 if ( $actual_value == $checked_value ) {
28 return 'checked';
29 }
30
31 return;
32 }
33
34 /**
35 * Function to return the value from option
36 *
37 * @version 4.0.0
38 *
39 * @param string $key
40 * @param array $options
41 *
42 * @return string|array
43 */
44 function rmp_get_value( $options, $key ) {
45 if ( empty( $options[ $key ] ) ) {
46 return;
47 }
48
49 return $options[ $key ];
50 }
51
52 function rmp_get_list_of_pages() {
53 $posts = get_posts(
54 array(
55 'numberposts' => -1,
56 'post_type' => 'any',
57 )
58 );
59
60 $all_pages = array();
61
62 foreach ( $posts as $post ) {
63 $all_pages[ $post->ID ] = $post->post_title;
64 }
65 wp_reset_postdata();
66 return $all_pages;
67 }
68
69 /**
70 * Return the form to select a dashicon
71 *
72 * @since 1.5.2
73 * @return string
74 */
75 function rmp_dashicon_selector() {
76 foreach ( rmp_all_dash_icons() as $code => $class ) {
77 $bits = explode( '-', $code );
78 $type = $bits[0]; ?>
79 <div class="<?php echo esc_attr( $type ); ?> font-icon">
80 <input class="radio" id="<?php echo esc_attr( $class ); ?>" type="radio" rel="<?php echo '&#x' . esc_attr( $bits[1] ); ?>" name="icon" value="dashicons <?php echo esc_attr( $class ); ?>" />
81 <label rel="<?php echo '&#x' . esc_attr( $bits[1] ); ?>" for="<?php echo esc_attr( $class ); ?>" title="<?php echo esc_attr( $class ); ?>" ></label>
82 </div>
83 <?php
84 }
85 }
86
87
88 /**
89 * Function to return the all menu ids of published menu.
90 *
91 * @since 4.0.0
92 * @return array $menu_ids;
93 */
94 function get_all_rmp_menu_ids() {
95 $args = array(
96 'post_type' => 'rmp_menu',
97 'posts_per_page' => -1,
98 'post_status' => 'publish',
99 );
100
101 $all_menus = get_posts( $args );
102 $menu_ids = array();
103
104 if ( ! empty( $all_menus ) ) {
105 foreach ( $all_menus as $menu ) {
106 setup_postdata( $menu );
107 $menu_ids[] = $menu->ID;
108 }
109 }
110 wp_reset_postdata();
111 return $menu_ids;
112 }
113
114 /**
115 * Function to return the all published menu list.
116 *
117 * @since 4.0.0
118 * @return array;
119 */
120 function rmp_get_all_menus() {
121 $args = array(
122 'post_type' => 'rmp_menu',
123 'posts_per_page' => -1,
124 'post_status' => 'publish',
125 );
126
127 $all_menus = get_posts( $args );
128 $menus = array();
129
130 if ( ! empty( $all_menus ) ) {
131 foreach ( $all_menus as $menu ) {
132 setup_postdata( $menu );
133 $menus[ $menu->ID ] = $menu->post_title;
134 }
135 }
136 wp_reset_postdata();
137 return $menus;
138 }
139
140
141
142 /**
143 * Get image alt text by image URL
144 *
145 * @param String $image_url
146 *
147 * @return Bool | String
148 */
149 function rmp_image_alt_by_url( $image_url ) {
150 global $wpdb;
151
152 if ( empty( $image_url ) ) {
153 return '';
154 }
155
156 $query_arr = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid='%s';", strtolower( $image_url ) ) );
157 $image_id = ( ! empty( $query_arr ) ) ? $query_arr[0] : 0;
158
159 return get_post_meta( $image_id, '_wp_attachment_image_alt', true );
160 }
161
162 /**
163 * Return the menu items.
164 */
165 function rmp_get_wp_nav_menu_items( $options ) {
166 $menu = '';
167
168 if ( ! empty( $options['theme_location_menu'] ) && has_nav_menu( $options['theme_location_menu'] ) ) {
169 $menu = get_term( get_nav_menu_locations()[ $options['theme_location_menu'] ], 'nav_menu' )->slug;
170 } elseif ( ! empty( $options['menu_to_use'] ) ) {
171 $menu = $options['menu_to_use'];
172 } elseif ( ! empty( get_terms( 'nav_menu' )[0]->slug ) ) {
173 $menu = get_terms( 'nav_menu' )[0]->slug;
174 }
175
176 return wp_get_nav_menu_items( $menu );
177 }
178
179
180 /**
181 * @return allow svg html tags.
182 */
183 function rmp_allow_svg_html_tags() {
184 $kses_defaults = wp_kses_allowed_html( 'post' );
185
186 $svg_args = array(
187 'svg' => array(
188 'class' => true,
189 'aria-hidden' => true,
190 'aria-labelledby' => true,
191 'role' => true,
192 'xmlns' => true,
193 'width' => true,
194 'height' => true,
195 'viewbox' => true, // <= Must be lower case!
196 ),
197 'g' => array( 'fill' => true ),
198 'title' => array( 'title' => true ),
199 'path' => array(
200 'd' => true,
201 'fill' => true,
202 ),
203 );
204
205 return array_merge( $kses_defaults, $svg_args );
206 }
207
208 /**
209 * Sanitizes multi-dimentional array
210 *
211 * @since 4.1.6
212 */
213 function rm_sanitize_rec_array( $array, $allowhtml = false ) {
214 if ( ! is_array( $array ) ) {
215 return $allowhtml ? rm_sanitize_html_tags( wp_specialchars_decode( (string) $array, ENT_QUOTES ) ) : sanitize_text_field( (string) $array );
216 }
217 foreach ( $array as $key => $value ) {
218 if ( is_array( $value ) ) {
219 $array[ $key ] = rm_sanitize_rec_array( $value, $allowhtml );
220 } else {
221 $array[ $key ] = $allowhtml ? rm_sanitize_html_tags( wp_specialchars_decode( (string) $value, ENT_QUOTES ) ) : sanitize_text_field( (string) $value );
222 }
223 }
224 return $array;
225 }
226
227 function rm_sanitize_html_tags( $content ) {
228 // Define allowed HTML tags and attributes
229 $common_attrs = array(
230 'class' => true,
231 'id' => true,
232 // Keep accessibility attributes: icon markup supplied by the user is decorative
233 // and needs aria-hidden to survive sanitising, or screen readers announce the glyph.
234 'role' => true,
235 'title' => true,
236 'aria-hidden' => true,
237 'aria-label' => true,
238 'aria-labelledby' => true,
239 'aria-expanded' => true,
240 'aria-controls' => true,
241 );
242 $form_common_attrs = array_merge($common_attrs, array(
243 'name' => true,
244 'disabled' => true,
245 'required' => true,
246 'readonly' => true,
247 ));
248
249 $allowed_tags = array(
250 'svg' => array_merge($common_attrs, array(
251 'xmlns' => true,
252 'viewBox' => true,
253 'width' => true,
254 'height' => true,
255 'fill' => true,
256 )),
257 'path' => array(
258 'd' => true,
259 'fill' => true,
260 'stroke' => true,
261 'stroke-width' => true,
262 'stroke-linecap' => true,
263 'stroke-linejoin' => true,
264 ),
265 'div' => $common_attrs,
266 'span' => $common_attrs,
267 'br' => true,
268 'b' => true,
269 'strong' => true,
270 'img' => array(
271 'src' => true,
272 'alt' => true,
273 'width' => true,
274 'height' => true,
275 'class' => true,
276 'id' => true,
277 'style' => true,
278 ),
279 'i' => $common_attrs,
280 'label' => $common_attrs,
281 'a' => array_merge($common_attrs, array(
282 'href' => true,
283 'target' => true,
284 'rel' => true,
285 )),
286 'h1' => $common_attrs,
287 'h2' => $common_attrs,
288 'h3' => $common_attrs,
289 'h4' => $common_attrs,
290 'h5' => $common_attrs,
291 'h6' => $common_attrs,
292 'p' => $common_attrs,
293 'form' => array(
294 'action' => true,
295 'method' => true,
296 'class' => true,
297 'id' => true,
298 ),
299 'input' => array_merge($form_common_attrs, array(
300 'type' => true,
301 'value' => true,
302 'placeholder' => true,
303 'checked' => true,
304 )),
305 'textarea' => array_merge($form_common_attrs, array(
306 'placeholder' => true,
307 'rows' => true,
308 'cols' => true,
309 )),
310 'select' => $form_common_attrs,
311 'option' => array(
312 'value' => true,
313 'selected' => true,
314 ),
315 'button' => array_merge($form_common_attrs, array(
316 'type' => true,
317 'value' => true,
318 )),
319 );
320
321 // Sanitize content
322 return wp_kses($content, $allowed_tags);
323 }
324 /**
325 * Add RM customize button for admin menus
326 * @since 4.3.0
327 */
328 function add_rm_customize_button_to_save_menu() {
329 global $pagenow;
330 if ( 'edit.php' === $pagenow && ! empty( $_REQUEST['post_type'] ) && ! empty( $_REQUEST['open'] ) && 'rmp_menu' === $_REQUEST['post_type'] && 'wizard' === $_REQUEST['open'] ) {
331 $inline_script = "jQuery('#rmp-new-menu-wizard').fadeIn();";
332 if ( ! empty( $_REQUEST['menu-to-use'] ) ) {
333 $inline_script .= "jQuery('#rmp-menu-to-use').val('".esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['menu-to-use'] ) ) ) ."' );";
334 }
335 wp_add_inline_script( 'rmp_admin_scripts', $inline_script );
336 }
337 // Check if it's the admin menu page
338 if ( 'nav-menus.php' === $pagenow ) {
339 $menu_id = isset($_REQUEST['menu']) ? sanitize_text_field( wp_unslash( intval( $_REQUEST['menu'] ) ) ) : absint( get_user_option( 'nav_menu_recently_edited' ) );
340 $nav_menus = wp_get_nav_menus();
341 if ( ( empty( $menu_id ) || ! is_nav_menu( $menu_id ) ) && 0 < count( $nav_menus ) ) {
342 $menu_id = $nav_menus[0]->term_id;
343 }
344 $rmp_customize_menu = admin_url( 'edit.php?post_type=rmp_menu&open=wizard' );
345 if ( ! empty( $menu_id ) && is_nav_menu( $menu_id ) ) {
346 $rmp_customize_menu = admin_url( 'edit.php?post_type=rmp_menu&open=wizard&menu-to-use='.esc_attr( $menu_id ) );
347 $query = new WP_Query(array(
348 'post_type' => 'rmp_menu',
349 'posts_per_page' => 1,
350 'meta_query' => array(
351 array(
352 'key' => 'rmp_menu_meta',
353 'value' => '"menu_to_use";s:'.strlen( $menu_id ).':"'. esc_sql( $menu_id ).'";',
354 'compare' => 'LIKE',
355 ),
356 ),
357 ));
358 if ( $query->have_posts() ) {
359 $query->the_post();
360 $post_id = get_the_ID();
361 wp_reset_postdata();
362 $rmp_customize_menu = admin_url( 'post.php?post='.esc_attr( $post_id ).'&action=edit&editor=true' );
363 }
364 }
365 $inline_script = "jQuery(document).ready(function($) {
366 $('#save_menu_footer').before('<a href=\"".esc_url( $rmp_customize_menu )."\" style=\"margin-right:5px;\" class=\"button button-secondary button-large rmp-customize-menu\">". __('Customize Menu', 'responsive-menu') ."</a>');
367 $(document).on('change', '.edit-menu-item-hide-login-rmp-setting, .edit-menu-item-hide-nonlogin-rmp-setting', function() {
368 var parent = $(this).closest('.menu-item-edit-active');
369 var hideLoginCheckbox = parent.find('.edit-menu-item-hide-login-rmp-setting');
370 var hideNonLoginCheckbox = parent.find('.edit-menu-item-hide-nonlogin-rmp-setting');
371
372 if (hideLoginCheckbox.is(':checked')) {
373 hideNonLoginCheckbox.prop('disabled', true);
374 } else {
375 hideNonLoginCheckbox.prop('disabled', false);
376 }
377
378 if (hideNonLoginCheckbox.is(':checked')) {
379 hideLoginCheckbox.prop('disabled', true);
380 } else {
381 hideLoginCheckbox.prop('disabled', false);
382 }
383 });
384 });";
385 // Enqueue the script
386 wp_add_inline_script( 'admin-bar', $inline_script );
387 }
388 }
389 add_action('admin_footer', 'add_rm_customize_button_to_save_menu', 999);
390
391 /*
392 * Add custom fields to WordPress navigation menu item settings
393 * @since 4.6.0
394 */
395 function rm_custom_menu_item_settings( $item_id, $item, $depth, $args ) {
396 $hide_login_rmp_setting = get_post_meta($item_id, '_hide_login_rmp_setting', true);
397 $hide_nonlogin_rmp_setting = get_post_meta($item_id, '_hide_nonlogin_rmp_setting', true);
398
399 ?>
400 <p class="rm-menu-settings-heading description description-wide">
401 <strong><?php esc_html_e('RMP Settings', 'responsive-menu'); ?></strong>
402 </p>
403 <?php
404 /**
405 * Marks this item's settings as present in the submission.
406 *
407 * wp_update_nav_menu_item() also fires from the Customizer, WP-CLI and
408 * menu importers, none of which post these fields — and an unchecked
409 * checkbox posts nothing either, so "field absent" cannot distinguish
410 * "turned off" from "not our form". Without this marker a Customizer
411 * rename silently reset every setting below.
412 */
413 ?>
414 <input type="hidden" name="rmp-item-settings[<?php echo esc_attr($item_id); ?>]" value="1" />
415 <p class="field-hide-login-rmp description description-thin">
416 <label for="edit-menu-item-hide-login-rmp-setting-<?php echo esc_attr($item_id); ?>">
417 <input type="checkbox" class="edit-menu-item-hide-login-rmp-setting" id="edit-menu-item-hide-login-rmp-setting-<?php echo esc_attr($item_id); ?>" name="menu-item-hide-login-rmp-setting[<?php echo esc_attr($item_id); ?>]" <?php checked($hide_login_rmp_setting, 'on'); echo 'on' === $hide_nonlogin_rmp_setting ? 'disabled' : ''; ?> value="on"/>
418 <?php esc_html_e('Hide for logged in users', 'responsive-menu-pro'); ?><br />
419 </label>
420 </p>
421 <p class="field-hide-nonlogin-rmp description description-thin">
422 <label for="edit-menu-item-hide-nonlogin-rmp-setting-<?php echo esc_attr($item_id); ?>">
423 <input type="checkbox" class="edit-menu-item-hide-nonlogin-rmp-setting" id="edit-menu-item-hide-nonlogin-rmp-setting-<?php echo esc_attr($item_id); ?>" name="menu-item-hide-nonlogin-rmp-setting[<?php echo esc_attr($item_id); ?>]" <?php checked($hide_nonlogin_rmp_setting, 'on'); echo 'on' === $hide_login_rmp_setting ? 'disabled' : ''; ?> value="on" />
424 <?php esc_html_e('Hide for non logged in users', 'responsive-menu'); ?><br />
425 </label>
426 </p>
427 <?php rm_menu_item_role_field( $item_id ); ?>
428 <?php
429 }
430 add_action('wp_nav_menu_item_custom_fields', 'rm_custom_menu_item_settings', 10, 4);
431
432 /**
433 * Render the "show only to these roles" field for a menu item.
434 *
435 * Complements the logged-in / logged-out checkboxes above with a finer
436 * condition: which user roles may see the item. No role selected means the
437 * item is shown to everyone, which is the behaviour of every existing item.
438 *
439 * @since 4.7.4
440 *
441 * @param int $item_id Menu item ID.
442 *
443 * @return void
444 */
445 function rm_menu_item_role_field( $item_id ) {
446
447 $item_id = absint( $item_id );
448 $selected = rm_get_menu_item_roles( $item_id );
449 $roles = rm_get_selectable_roles();
450
451 /*
452 * A role the item is restricted to but which is no longer registered - a
453 * membership plugin deactivated, a role renamed, or this list narrowed by the
454 * rmp_menu_item_selectable_roles filter - still has to be rendered, and checked.
455 *
456 * An unchecked checkbox posts nothing, so a role with no checkbox posts nothing
457 * either, and the save path below cannot tell that from "the admin cleared it".
458 * It would delete the restriction on the next Save Menu and the item would
459 * silently become visible to everyone. A visibility condition must fail closed.
460 */
461 foreach ( $selected as $stored_role ) {
462 if ( ! isset( $roles[ $stored_role ] ) ) {
463 /* translators: %s: role slug that is no longer registered on this site. */
464 $roles[ $stored_role ] = sprintf( __( '%s (role not currently available)', 'responsive-menu' ), $stored_role );
465 }
466 }
467
468 if ( empty( $roles ) ) {
469 return;
470 }
471 ?>
472 <fieldset class="field-rmp-roles description description-wide">
473 <legend><?php esc_html_e( 'Show only to these roles', 'responsive-menu' ); ?></legend>
474 <?php
475 /*
476 * A stock WordPress site has five roles, which came to about 110px and fitted
477 * inside the old 120px cap - so the box showed no scrollbar and read as the
478 * complete list. The SIXTH role, i.e. the first one an admin adds themselves,
479 * was silently clipped, and the obvious conclusion was that the plugin had not
480 * picked it up. Give it room for a realistic number of roles and a border, so
481 * that when it does scroll it looks like something that scrolls.
482 */
483 ?>
484 <span class="rmp-roles-list" style="display:block;max-height:220px;overflow-y:auto;margin:4px 0;padding:6px 8px;border:1px solid #dcdcde;background:#fff;">
485 <?php foreach ( $roles as $role => $label ) : ?>
486 <label class="rmp-role-option">
487 <input
488 type="checkbox"
489 name="rmp-roles[<?php echo esc_attr( $item_id ); ?>][]"
490 value="<?php echo esc_attr( $role ); ?>"
491 <?php checked( in_array( $role, $selected, true ) ); ?> />
492 <?php echo esc_html( $label ); ?>
493 </label><br />
494 <?php endforeach; ?>
495 </span>
496 <span class="description">
497 <?php esc_html_e( 'Leave every role unchecked to show the item to everyone. A hidden item hides its sub-items too. This hides the link; it does not restrict access to the page, which is still reachable by its URL.', 'responsive-menu' ); ?>
498 </span>
499 </fieldset>
500 <?php
501 }
502
503 /**
504 * Roles that can be picked as a menu item condition.
505 *
506 * @since 4.7.4
507 *
508 * @return array Role slug => display name.
509 */
510 function rm_get_selectable_roles() {
511
512 if ( ! function_exists( 'wp_roles' ) ) {
513 return array();
514 }
515
516 $roles = wp_roles()->get_names();
517
518 /**
519 * Filters the roles offered as a menu item visibility condition.
520 *
521 * @since 4.7.4
522 *
523 * @param array $roles Role slug => display name.
524 */
525 return apply_filters( 'rmp_menu_item_selectable_roles', $roles );
526 }
527
528 /**
529 * Roles a menu item is restricted to.
530 *
531 * @since 4.7.4
532 *
533 * @param int $item_id Menu item ID.
534 *
535 * @return array Role slugs. Empty means "no restriction".
536 */
537 function rm_get_menu_item_roles( $item_id ) {
538
539 $roles = get_post_meta( absint( $item_id ), '_rmp_visible_roles', true );
540
541 if ( ! is_array( $roles ) ) {
542 return array();
543 }
544
545 return array_values( array_filter( array_map( 'strval', $roles ) ) );
546 }
547
548 /*
549 * Save custom fields to WordPress navigation menu item settings
550 * @since 4.6.0
551 */
552 function rm_save_custom_menu_item_setting( $menu_id, $menu_item_db_id, $menu_item_args ) {
553 // Only act when our own fields were submitted — see the marker in rm_custom_menu_item_settings().
554 if ( empty( $_POST['rmp-item-settings'][ $menu_item_db_id ] ) ) {
555 return;
556 }
557
558 $custom_setting = ! empty( $_POST['menu-item-hide-login-rmp-setting'][ $menu_item_db_id ] ) ? 'on' : 'off';
559 update_post_meta($menu_item_db_id, '_hide_login_rmp_setting', $custom_setting);
560 $custom_setting = ! empty( $_POST['menu-item-hide-nonlogin-rmp-setting'][ $menu_item_db_id ] ) ? 'on' : 'off';
561 update_post_meta($menu_item_db_id, '_hide_nonlogin_rmp_setting', $custom_setting);
562
563 rm_save_menu_item_roles( $menu_item_db_id );
564 }
565 add_action('wp_update_nav_menu_item', 'rm_save_custom_menu_item_setting', 10, 3);
566
567 /**
568 * Persist a menu item's role condition.
569 *
570 * WordPress nonce- and capability-checks the Menus screen before
571 * wp_update_nav_menu_item() fires; the capability check below keeps the meta
572 * write safe for any other caller.
573 *
574 * @since 4.7.4
575 *
576 * @param int $menu_item_db_id Menu item ID.
577 *
578 * @return void
579 */
580 function rm_save_menu_item_roles( $menu_item_db_id ) {
581
582 if ( ! current_user_can( 'edit_theme_options' ) ) {
583 return;
584 }
585
586 $menu_item_db_id = absint( $menu_item_db_id );
587
588 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by the Menus screen before this action runs.
589 $submitted = isset( $_POST['rmp-roles'][ $menu_item_db_id ] ) ? wp_unslash( $_POST['rmp-roles'][ $menu_item_db_id ] ) : array();
590
591 if ( ! is_array( $submitted ) ) {
592 $submitted = array();
593 }
594
595 // is_scalar() because rmp-roles[12][][]=x would otherwise reach strtolower( array ).
596 $submitted = array_filter( $submitted, 'is_scalar' );
597
598 /*
599 * Validate against the registered roles PLUS whatever this item already stores,
600 * so this path can only ever reject a newly submitted value - never silently
601 * drop a restriction that is already in place. Pairs with the field above,
602 * which renders a stored-but-unregistered role so it round-trips.
603 */
604 $allowed = array_unique( array_merge(
605 array_keys( rm_get_selectable_roles() ),
606 rm_get_menu_item_roles( $menu_item_db_id )
607 ) );
608 $roles = array_values( array_intersect( array_map( 'sanitize_key', $submitted ), $allowed ) );
609
610 if ( empty( $roles ) ) {
611 delete_post_meta( $menu_item_db_id, '_rmp_visible_roles' );
612 return;
613 }
614
615 update_post_meta( $menu_item_db_id, '_rmp_visible_roles', $roles );
616 }
617
618 /**
619 * Whether the current visitor may see a menu item.
620 *
621 * @since 4.7.4
622 *
623 * @param WP_Post $item Menu item.
624 *
625 * @return bool True when the item must be hidden.
626 */
627 function rmp_is_menu_item_hidden( $item ) {
628
629 $hidden = false;
630
631 if ( 'on' === get_post_meta( $item->ID, '_hide_login_rmp_setting', true ) && is_user_logged_in() ) {
632 $hidden = true;
633 }
634
635 if ( ! $hidden && 'on' === get_post_meta( $item->ID, '_hide_nonlogin_rmp_setting', true ) && ! is_user_logged_in() ) {
636 $hidden = true;
637 }
638
639 if ( ! $hidden ) {
640 $roles = rm_get_menu_item_roles( $item->ID );
641
642 if ( ! empty( $roles ) ) {
643 $user = wp_get_current_user();
644
645 $user_roles = ( $user instanceof WP_User && $user->exists() && is_array( $user->roles ) )
646 ? $user->roles
647 : array();
648
649 $hidden = empty( array_intersect( $roles, $user_roles ) );
650 }
651 }
652
653 /**
654 * Filters whether a menu item is hidden from the current visitor.
655 *
656 * @since 4.7.4
657 *
658 * @param bool $hidden Whether to hide the item.
659 * @param WP_Post $item Menu item.
660 */
661 return (bool) apply_filters( 'rmp_is_menu_item_hidden', $hidden, $item );
662 }
663
664 /*
665 * Modify menu items based on custom setting
666 * @since 4.6.0
667 */
668 function rmp_modify_menu_items( $items, $args ) {
669
670 $removed = array();
671
672 foreach ( $items as $key => $item ) {
673 if ( rmp_is_menu_item_hidden( $item ) ) {
674 $removed[ (int) $item->ID ] = true;
675 unset( $items[ $key ] );
676 }
677 }
678
679 if ( empty( $removed ) ) {
680 return $items;
681 }
682
683 /**
684 * Drop the descendants of a hidden item. Without this a role-gated
685 * parent disappears but its sub-items are promoted into the menu, which
686 * leaks exactly what the condition was set to hide.
687 *
688 * Items are not guaranteed to be ordered parent-before-child, so repeat
689 * until no further item is removed.
690 */
691 do {
692 $dropped = false;
693
694 foreach ( $items as $key => $item ) {
695 $parent = isset( $item->menu_item_parent ) ? (int) $item->menu_item_parent : 0;
696
697 if ( $parent && isset( $removed[ $parent ] ) ) {
698 $removed[ (int) $item->ID ] = true;
699 unset( $items[ $key ] );
700 $dropped = true;
701 }
702 }
703 } while ( $dropped );
704
705 return $items;
706 }
707 add_filter('wp_nav_menu_objects', 'rmp_modify_menu_items', 10, 2);
708