PluginProbe
Polylang / 2.9.2
Polylang v2.9.2
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / frontend / frontend-nav-menu.php

frontend-nav-menu.php in Polylang 2.9.2, at frontend/frontend-nav-menu.php

324 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Manages custom menus translations as well as the language switcher menu item on frontend
8 *
9 * @since 1.2
10 */
11 class PLL_Frontend_Nav_Menu extends PLL_Nav_Menu {
12 public $curlang;
13
14 /**
15 * Constructor
16 *
17 * @since 1.2
18 *
19 * @param object $polylang
20 */
21 public function __construct( &$polylang ) {
22 parent::__construct( $polylang );
23
24 $this->curlang = &$polylang->curlang;
25
26 // Split the language switcher menu item in several language menu items
27 add_filter( 'wp_get_nav_menu_items', array( $this, 'wp_get_nav_menu_items' ), 20 ); // after the customizer menus
28 add_filter( 'wp_nav_menu_objects', array( $this, 'wp_nav_menu_objects' ) );
29 add_filter( 'nav_menu_link_attributes', array( $this, 'nav_menu_link_attributes' ), 10, 2 );
30
31 // Filters menus by language
32 add_filter( 'theme_mod_nav_menu_locations', array( $this, 'nav_menu_locations' ), 20 );
33 add_filter( 'wp_nav_menu_args', array( $this, 'wp_nav_menu_args' ) );
34
35 // The customizer
36 if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
37 add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_before_customizer' ) );
38 add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_after_customizer' ), 2000 );
39 }
40 }
41
42 /**
43 * Sort menu items by menu order
44 *
45 * @since 1.7.9
46 *
47 * @param object $a The first object to compare
48 * @param object $b The second object to compare
49 * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
50 */
51 protected function usort_menu_items( $a, $b ) {
52 return ( $a->menu_order < $b->menu_order ) ? -1 : 1;
53 }
54
55 /**
56 * Format a language switcher menu item title based on options
57 *
58 * @since 2.2.6
59 *
60 * @param string $flag Formatted flag
61 * @param string $name Language name
62 * @param array $options Language switcher options
63 * @return string Formatted menu item title
64 */
65 protected function get_item_title( $flag, $name, $options ) {
66 if ( $options['show_flags'] ) {
67 if ( $options['show_names'] ) {
68 $title = sprintf( '%1$s<span style="margin-%2$s:0.3em;">%3$s</span>', $flag, is_rtl() ? 'right' : 'left', esc_html( $name ) );
69 } else {
70 $title = $flag;
71 }
72 } else {
73 $title = esc_html( $name );
74 }
75 return $title;
76 }
77
78 /**
79 * Splits the one item of backend in several items on frontend
80 * take care to menu_order as it is used later in wp_nav_menu
81 *
82 * @since 1.1.1
83 *
84 * @param array $items menu items
85 * @return array modified items
86 */
87 public function wp_get_nav_menu_items( $items ) {
88 if ( doing_action( 'customize_register' ) ) { // needed since WP 4.3, doing_action available since WP 3.9
89 return $items;
90 }
91
92 // The customizer menus does not sort the items and we need them to be sorted before splitting the language switcher
93 usort( $items, array( $this, 'usort_menu_items' ) );
94
95 $new_items = array();
96 $offset = 0;
97
98 foreach ( $items as $item ) {
99 if ( $options = get_post_meta( $item->ID, '_pll_menu_item', true ) ) {
100 $i = 0;
101
102 /** This filter is documented in include/switcher.php */
103 $options = apply_filters( 'pll_the_languages_args', $options ); // Honor the filter here for 'show_flags', 'show_names' and 'dropdown'.
104
105 $switcher = new PLL_Switcher();
106 $args = array_merge( array( 'raw' => 1 ), $options );
107 $the_languages = $switcher->the_languages( PLL()->links, $args );
108
109 // parent item for dropdown
110 if ( ! empty( $options['dropdown'] ) ) {
111 $name = isset( $options['display_names_as'] ) && 'slug' === $options['display_names_as'] ? $this->curlang->slug : $this->curlang->name;
112 $item->title = $this->get_item_title( $this->curlang->get_display_flag(), $name, $options );
113 $item->attr_title = '';
114 $item->classes = array( 'pll-parent-menu-item' );
115 $new_items[] = $item;
116 $offset++;
117 }
118
119 foreach ( $the_languages as $lang ) {
120 $lang_item = clone $item;
121 $lang_item->ID = $lang_item->ID . '-' . $lang['slug']; // A unique ID
122 $lang_item->title = $this->get_item_title( $lang['flag'], $lang['name'], $options );
123 $lang_item->attr_title = '';
124 $lang_item->url = $lang['url'];
125 $lang_item->lang = $lang['locale']; // Save this for use in nav_menu_link_attributes
126 $lang_item->classes = $lang['classes'];
127 $lang_item->menu_order += $offset + $i++;
128 if ( ! empty( $options['dropdown'] ) ) {
129 $lang_item->menu_item_parent = $item->db_id;
130 $lang_item->db_id = 0; // to avoid recursion
131 }
132 $new_items[] = $lang_item;
133 }
134 $offset += $i - 1;
135 } else {
136 $item->menu_order += $offset;
137 $new_items[] = $item;
138 }
139 }
140 return $new_items;
141 }
142
143 /**
144 * Returns the ancestors of a menu item
145 *
146 * @since 1.1.1
147 *
148 * @param object $item
149 * @return array ancestors ids
150 */
151 public function get_ancestors( $item ) {
152 $ids = array();
153 $_anc_id = (int) $item->db_id;
154 while ( ( $_anc_id = get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $ids ) ) {
155 $ids[] = $_anc_id;
156 }
157 return $ids;
158 }
159
160 /**
161 * Removes current-menu and current-menu-ancestor classes to lang switcher when not on the home page
162 *
163 * @since 1.1.1
164 *
165 * @param array $items
166 * @return array modified menu items
167 */
168 public function wp_nav_menu_objects( $items ) {
169 $r_ids = $k_ids = array();
170
171 foreach ( $items as $item ) {
172 if ( ! empty( $item->classes ) && is_array( $item->classes ) ) {
173 if ( in_array( 'current-lang', $item->classes ) ) {
174 $item->current = false;
175 $item->classes = array_diff( $item->classes, array( 'current-menu-item' ) );
176 $r_ids = array_merge( $r_ids, $this->get_ancestors( $item ) ); // Remove the classes for these ancestors
177 } elseif ( in_array( 'current-menu-item', $item->classes ) ) {
178 $k_ids = array_merge( $k_ids, $this->get_ancestors( $item ) ); // Keep the classes for these ancestors
179 }
180 }
181 }
182
183 $r_ids = array_diff( $r_ids, $k_ids );
184
185 foreach ( $items as $item ) {
186 if ( ! empty( $item->db_id ) && in_array( $item->db_id, $r_ids ) ) {
187 $item->classes = array_diff( $item->classes, array( 'current-menu-ancestor', 'current-menu-parent', 'current_page_parent', 'current_page_ancestor' ) );
188 }
189 }
190
191 return $items;
192 }
193
194 /**
195 * Adds hreflang attribute for the language switcher menu items
196 * available since WP 3.6
197 *
198 * @since 1.1
199 *
200 * @param array $atts
201 * @param object $item
202 * @return array modified $atts
203 */
204 public function nav_menu_link_attributes( $atts, $item ) {
205 if ( isset( $item->lang ) ) {
206 $atts['lang'] = $atts['hreflang'] = esc_attr( $item->lang );
207 }
208 return $atts;
209 }
210
211 /**
212 * Fills the theme nav menus locations with the right menu in the right language
213 * Needs to wait for the language to be defined
214 *
215 * @since 1.2
216 *
217 * @param array|bool $menus list of nav menus locations, false if menu locations have not been filled yet
218 * @return array|bool modified list of nav menus locations
219 */
220 public function nav_menu_locations( $menus ) {
221 if ( is_array( $menus ) && ! empty( $this->curlang ) ) {
222 // First get multilingual menu locations from DB
223 $theme = get_option( 'stylesheet' );
224
225 foreach ( array_keys( $menus ) as $loc ) {
226 $menus[ $loc ] = empty( $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ] ) ? 0 : $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ];
227 }
228
229 // Support for theme customizer
230 if ( is_customize_preview() ) {
231 global $wp_customize;
232 foreach ( $wp_customize->unsanitized_post_values() as $key => $value ) {
233 if ( false !== strpos( $key, 'nav_menu_locations[' ) ) {
234 $loc = substr( trim( $key, ']' ), 19 );
235 $infos = $this->explode_location( $loc );
236 if ( $infos['lang'] === $this->curlang->slug ) {
237 $menus[ $infos['location'] ] = (int) $value;
238 } elseif ( $this->curlang->slug === $this->options['default_lang'] ) {
239 $menus[ $loc ] = (int) $value;
240 }
241 }
242 }
243 }
244 }
245 return $menus;
246 }
247
248 /**
249 * Attempt to translate the nav menu when it is hardcoded or when no location is defined in wp_nav_menu
250 *
251 * @since 1.7.10
252 *
253 * @param array $args
254 * @return array modified $args
255 */
256 public function wp_nav_menu_args( $args ) {
257 $theme = get_option( 'stylesheet' );
258
259 if ( empty( $this->curlang ) || empty( $this->options['nav_menus'][ $theme ] ) ) {
260 return $args;
261 }
262
263 // Get the nav menu based on the requested menu
264 $menu = wp_get_nav_menu_object( $args['menu'] );
265
266 // Attempt to find a translation of this menu
267 // This obviously does not work if the nav menu has no associated theme location
268 if ( $menu ) {
269 foreach ( $this->options['nav_menus'][ $theme ] as $menus ) {
270 if ( in_array( $menu->term_id, $menus ) && ! empty( $menus[ $this->curlang->slug ] ) ) {
271 $args['menu'] = $menus[ $this->curlang->slug ];
272 return $args;
273 }
274 }
275 }
276
277 // Get the first menu that has items and and is in the current language if we still can't find a menu
278 if ( ! $menu && ! $args['theme_location'] ) {
279 $menus = wp_get_nav_menus();
280 foreach ( $menus as $menu_maybe ) {
281 if ( wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) ) ) {
282 foreach ( $this->options['nav_menus'][ $theme ] as $menus ) {
283 if ( in_array( $menu_maybe->term_id, $menus ) && ! empty( $menus[ $this->curlang->slug ] ) ) {
284 $args['menu'] = $menus[ $this->curlang->slug ];
285 return $args;
286 }
287 }
288 }
289 }
290 }
291
292 return $args;
293 }
294
295 /**
296 * Filters the nav menu location before the customizer so that it matches the temporary location in the customizer
297 *
298 * @since 1.8
299 *
300 * @param array $args wp_nav_menu $args
301 * @return array modified $args
302 */
303 public function filter_args_before_customizer( $args ) {
304 if ( ! empty( $this->curlang ) ) {
305 $args['theme_location'] = $this->combine_location( $args['theme_location'], $this->curlang );
306 }
307 return $args;
308 }
309
310 /**
311 * Filters the nav menu location after the customizer to get back the true nav menu location for the theme
312 *
313 * @since 1.8
314 *
315 * @param array $args wp_nav_menu $args
316 * @return array modified $args
317 */
318 public function filter_args_after_customizer( $args ) {
319 $infos = $this->explode_location( $args['theme_location'] );
320 $args['theme_location'] = $infos['location'];
321 return $args;
322 }
323 }
324