PluginProbe
Polylang / 2.0.3
Polylang v2.0.3
3.8.10 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 All 234 releases
polylang / frontend / frontend-nav-menu.php

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

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