PluginProbe
Polylang / 3.0.2
Polylang v3.0.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 3.0.2, at frontend/frontend-nav-menu.php

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