PluginProbe
Polylang / 2.6.3
Polylang v2.6.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.6.3, at frontend/frontend-nav-menu.php

321 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 /**
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'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
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 /** This filter is documented in include/switcher.php */
100 $options = apply_filters( 'pll_the_languages_args', $options ); // Honor the filter here for 'show_flags', 'show_names' and 'dropdown'.
101
102 $switcher = new PLL_Switcher();
103 $args = array_merge( array( 'raw' => 1 ), $options );
104 $the_languages = $switcher->the_languages( PLL()->links, $args );
105
106 // parent item for dropdown
107 if ( ! empty( $options['dropdown'] ) ) {
108 $name = isset( $options['display_names_as'] ) && 'slug' === $options['display_names_as'] ? $this->curlang->slug : $this->curlang->name;
109 $item->title = $this->get_item_title( $this->curlang->flag, $name, $options );
110 $item->attr_title = '';
111 $item->classes = array( 'pll-parent-menu-item' );
112 $new_items[] = $item;
113 $offset++;
114 }
115
116 foreach ( $the_languages as $lang ) {
117 $lang_item = clone $item;
118 $lang_item->ID = $lang_item->ID . '-' . $lang['slug']; // A unique ID
119 $lang_item->title = $this->get_item_title( $lang['flag'], $lang['name'], $options );
120 $lang_item->attr_title = '';
121 $lang_item->url = $lang['url'];
122 $lang_item->lang = $lang['locale']; // Save this for use in nav_menu_link_attributes
123 $lang_item->classes = $lang['classes'];
124 $lang_item->menu_order += $offset + $i++;
125 if ( ! empty( $options['dropdown'] ) ) {
126 $lang_item->menu_item_parent = $item->db_id;
127 $lang_item->db_id = 0; // to avoid recursion
128 }
129 $new_items[] = $lang_item;
130 }
131 $offset += $i - 1;
132 } else {
133 $item->menu_order += $offset;
134 $new_items[] = $item;
135 }
136 }
137 return $new_items;
138 }
139
140 /**
141 * Returns the ancestors of a menu item
142 *
143 * @since 1.1.1
144 *
145 * @param object $item
146 * @return array ancestors ids
147 */
148 public function get_ancestors( $item ) {
149 $ids = array();
150 $_anc_id = (int) $item->db_id;
151 while ( ( $_anc_id = get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $ids ) ) {
152 $ids[] = $_anc_id;
153 }
154 return $ids;
155 }
156
157 /**
158 * Removes current-menu and current-menu-ancestor classes to lang switcher when not on the home page
159 *
160 * @since 1.1.1
161 *
162 * @param array $items
163 * @return array modified menu items
164 */
165 public function wp_nav_menu_objects( $items ) {
166 $r_ids = $k_ids = array();
167
168 foreach ( $items as $item ) {
169 if ( ! empty( $item->classes ) && is_array( $item->classes ) ) {
170 if ( in_array( 'current-lang', $item->classes ) ) {
171 $item->current = false;
172 $item->classes = array_diff( $item->classes, array( 'current-menu-item' ) );
173 $r_ids = array_merge( $r_ids, $this->get_ancestors( $item ) ); // Remove the classes for these ancestors
174 } elseif ( in_array( 'current-menu-item', $item->classes ) ) {
175 $k_ids = array_merge( $k_ids, $this->get_ancestors( $item ) ); // Keep the classes for these ancestors
176 }
177 }
178 }
179
180 $r_ids = array_diff( $r_ids, $k_ids );
181
182 foreach ( $items as $item ) {
183 if ( ! empty( $item->db_id ) && in_array( $item->db_id, $r_ids ) ) {
184 $item->classes = array_diff( $item->classes, array( 'current-menu-ancestor', 'current-menu-parent', 'current_page_parent', 'current_page_ancestor' ) );
185 }
186 }
187
188 return $items;
189 }
190
191 /**
192 * Adds hreflang attribute for the language switcher menu items
193 * available since WP 3.6
194 *
195 * @since 1.1
196 *
197 * @param array $atts
198 * @param object $item
199 * @return array modified $atts
200 */
201 public function nav_menu_link_attributes( $atts, $item ) {
202 if ( isset( $item->lang ) ) {
203 $atts['lang'] = $atts['hreflang'] = esc_attr( $item->lang );
204 }
205 return $atts;
206 }
207
208 /**
209 * Fills the theme nav menus locations with the right menu in the right language
210 * Needs to wait for the language to be defined
211 *
212 * @since 1.2
213 *
214 * @param array|bool $menus list of nav menus locations, false if menu locations have not been filled yet
215 * @return array|bool modified list of nav menus locations
216 */
217 public function nav_menu_locations( $menus ) {
218 if ( is_array( $menus ) && ! empty( $this->curlang ) ) {
219 // First get multilingual menu locations from DB
220 $theme = get_option( 'stylesheet' );
221
222 foreach ( $menus as $loc => $menu ) {
223 $menus[ $loc ] = empty( $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ] ) ? 0 : $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ];
224 }
225
226 // Support for theme customizer
227 if ( is_customize_preview() ) {
228 global $wp_customize;
229 foreach ( $wp_customize->unsanitized_post_values() as $key => $value ) {
230 if ( false !== strpos( $key, 'nav_menu_locations[' ) ) {
231 $loc = substr( trim( $key, ']' ), 19 );
232 $infos = $this->explode_location( $loc );
233 if ( $infos['lang'] === $this->curlang->slug ) {
234 $menus[ $infos['location'] ] = (int) $value;
235 } elseif ( $this->curlang->slug === $this->options['default_lang'] ) {
236 $menus[ $loc ] = (int) $value;
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