PluginProbe
Polylang / 2.3
Polylang v2.3
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.3, at frontend/frontend-nav-menu.php

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