PluginProbe
Polylang / 3.8
Polylang v3.8
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 / src / filters.php

filters.php in Polylang 3.8, at src/filters.php

401 lines 12.9 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 * Setup filters common to admin and frontend
8 *
9 * @since 1.4
10 */
11 class PLL_Filters {
12 /**
13 * Stores the plugin options.
14 *
15 * @var \WP_Syntex\Polylang\Options\Options
16 */
17 public $options;
18
19 /**
20 * @var PLL_Model
21 */
22 public $model;
23
24 /**
25 * Instance of a child class of PLL_Links_Model.
26 *
27 * @var PLL_Links_Model
28 */
29 public $links_model;
30
31 /**
32 * Current language.
33 *
34 * @var PLL_Language|null
35 */
36 public $curlang;
37
38 /**
39 * Constructor: setups filters
40 *
41 * @since 1.4
42 *
43 * @param PLL_Base $polylang The Polylang object.
44 * @param-out PLL_Base $polylang
45 */
46 public function __construct( PLL_Base &$polylang ) {
47 $this->links_model = &$polylang->links_model;
48 $this->model = &$polylang->model;
49 $this->options = $polylang->options;
50 $this->curlang = &$polylang->curlang;
51
52 // Deletes our cache for sticky posts when the list is updated.
53 add_action( 'update_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
54 add_action( 'add_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
55 add_action( 'delete_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
56
57 // Filters the comments according to the current language.
58 add_action( 'parse_comment_query', array( $this, 'parse_comment_query' ) );
59 add_filter( 'comments_clauses', array( $this, 'comments_clauses' ), 10, 2 );
60
61 // Filters the get_pages function according to the current language.
62 add_filter( 'get_pages_query_args', array( $this, 'get_pages_query_args' ), 10, 2 );
63
64 // Rewrites next and previous post links to filter them by language.
65 add_filter( 'get_previous_post_join', array( $this, 'posts_join' ), 10, 5 );
66 add_filter( 'get_next_post_join', array( $this, 'posts_join' ), 10, 5 );
67 add_filter( 'get_previous_post_where', array( $this, 'posts_where' ), 10, 5 );
68 add_filter( 'get_next_post_where', array( $this, 'posts_where' ), 10, 5 );
69
70 // Converts the locale to a valid W3C locale.
71 add_filter( 'language_attributes', array( $this, 'language_attributes' ) );
72
73 // Translate the site title in emails sent to users
74 add_filter( 'password_change_email', array( $this, 'translate_user_email' ) );
75 add_filter( 'email_change_email', array( $this, 'translate_user_email' ) );
76
77 // Translates the privacy policy page.
78 add_filter( 'option_wp_page_for_privacy_policy', array( $this, 'translate_page_for_privacy_policy' ), 20 ); // Since WP 4.9.6.
79 add_filter( 'map_meta_cap', array( $this, 'fix_privacy_policy_page_editing' ), 10, 4 );
80
81 // Personal data exporter.
82 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ), 0 ); // Since WP 4.9.6.
83
84 // Fix for `term_exists()`.
85 add_filter( 'term_exists_default_query_args', array( $this, 'term_exists_default_query_args' ), 0, 3 ); // Since WP 6.0.0.
86 }
87
88 /**
89 * Deletes the cache for multilingual sticky posts.
90 *
91 * @since 2.8.4
92 *
93 * @return void
94 */
95 public function delete_sticky_posts_cache() {
96 wp_cache_delete( 'sticky_posts', 'options' );
97 }
98
99 /**
100 * Get the language to filter a comments query.
101 *
102 * @since 2.0
103 * @since 3.1 Always returns an array. Renamed from get_comments_queried_language().
104 *
105 * @param WP_Comment_Query $query WP_Comment_Query object.
106 * @return PLL_Language[] The languages to use in the filter.
107 */
108 protected function get_comments_queried_languages( $query ) {
109 // Don't filter comments if comment ids or post ids are specified.
110 $plucked = wp_array_slice_assoc( $query->query_vars, array( 'comment__in', 'parent', 'post_id', 'post__in', 'post_parent' ) );
111 $fields = array_filter( $plucked );
112 if ( ! empty( $fields ) ) {
113 return array();
114 }
115
116 // Don't filter comments if a non translated post type is specified.
117 if ( ! empty( $query->query_vars['post_type'] ) && ! $this->model->is_translated_post_type( $query->query_vars['post_type'] ) ) {
118 return array();
119 }
120
121 // If comments are queried with a 'lang' parameter, keeps only language codes.
122 if ( isset( $query->query_vars['lang'] ) ) {
123 $languages = is_string( $query->query_vars['lang'] ) ? explode( ',', $query->query_vars['lang'] ) : $query->query_vars['lang'];
124 if ( is_array( $languages ) ) {
125 $languages = array_map( array( $this->model, 'get_language' ), $languages );
126 return array_filter( $languages );
127 }
128 }
129
130 if ( ! empty( $this->curlang ) ) {
131 return array( $this->curlang );
132 }
133
134 return array();
135 }
136
137 /**
138 * Adds a language dependent cache domain when querying comments.
139 * Useful as the 'lang' parameter is not included in cache key by WordPress.
140 * Needed since WP 4.6 as comments have been added to persistent cache. See #36906, #37419.
141 *
142 * @since 2.0
143 *
144 * @param WP_Comment_Query $query WP_Comment_Query object.
145 * @return void
146 */
147 public function parse_comment_query( $query ) {
148 $lang = $this->get_comments_queried_languages( $query );
149 if ( ! empty( $lang ) ) {
150 $lang = wp_list_pluck( $lang, 'slug' );
151 $key = '_' . implode( ',', $lang );
152 $query->query_vars['cache_domain'] = empty( $query->query_vars['cache_domain'] ) ? 'pll' . $key : $query->query_vars['cache_domain'] . $key;
153 }
154 }
155
156 /**
157 * Filters the comments according to the current language.
158 * Used by the recent comments widget and admin language filter.
159 *
160 * @since 0.2
161 *
162 * @param string[] $clauses SQL clauses.
163 * @param WP_Comment_Query $query WP_Comment_Query object.
164 * @return string[] Modified $clauses.
165 */
166 public function comments_clauses( $clauses, $query ) {
167 global $wpdb;
168
169 $lang = $this->get_comments_queried_languages( $query );
170
171 if ( ! empty( $lang ) ) {
172 $lang = wp_list_pluck( $lang, 'slug' );
173
174 // If this clause is not already added by WP.
175 if ( ! preg_match( "#JOIN\s+{$wpdb->posts}\s+ON\s+(({$wpdb->posts}\.)?ID|({$wpdb->comments}\.)?comment_post_ID)\s*=#", $clauses['join'] ) ) {
176 $clauses['join'] .= " JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
177 }
178
179 $clauses['join'] .= $this->model->post->join_clause();
180 $clauses['where'] .= $this->model->post->where_clause( $lang );
181 }
182 return $clauses;
183 }
184
185 /**
186 * Filters the WP_Query in get_pages() per language.
187 *
188 * @since 3.4.3
189 *
190 * @param array $query_args Array of arguments passed to WP_Query.
191 * @param array $parsed_args Array of get_pages() arguments.
192 * @return array Array of arguments passed to WP_Query with the language.
193 */
194 public function get_pages_query_args( $query_args, $parsed_args ) {
195 if ( isset( $parsed_args['lang'] ) ) {
196 $query_args['lang'] = $parsed_args['lang'];
197 }
198
199 return $query_args;
200 }
201
202 /**
203 * Modifies the sql request for get_adjacent_post to filter by the current language.
204 *
205 * @since 0.1
206 *
207 * @param string $sql The JOIN clause in the SQL.
208 * @param bool $in_same_term Whether post should be in a same taxonomy term.
209 * @param int[] $excluded_terms Array of excluded term IDs.
210 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
211 * @param WP_Post $post WP_Post object.
212 * @return string Modified JOIN clause.
213 */
214 public function posts_join( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) {
215 return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->join_clause( 'p' ) : $sql;
216 }
217
218 /**
219 * Modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language.
220 *
221 * @since 0.1
222 *
223 * @param string $sql The WHERE clause in the SQL.
224 * @param bool $in_same_term Whether post should be in a same taxonomy term.
225 * @param int[] $excluded_terms Array of excluded term IDs.
226 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
227 * @param WP_Post $post WP_Post object.
228 * @return string Modified WHERE clause.
229 */
230 public function posts_where( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) {
231 return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->where_clause( $this->curlang ) : $sql;
232 }
233
234 /**
235 * Converts WordPress locale to valid W3 locale in html language attributes
236 *
237 * @since 1.8
238 *
239 * @param string $output language attributes
240 * @return string
241 */
242 public function language_attributes( $output ) {
243 $language = $this->model->get_language( determine_locale() );
244
245 if ( ! $language ) {
246 return $output;
247 }
248
249 return str_replace( '"' . get_bloginfo( 'language' ) . '"', '"' . $language->get_locale( 'display' ) . '"', $output );
250 }
251
252 /**
253 * Translates the site title in emails sent to the user (change email, reset password)
254 * It is necessary to filter the email because WP evaluates the site title before calling switch_to_locale()
255 *
256 * @since 2.1.3
257 *
258 * @param string[] $email Email contents.
259 * @return string[] Translated email contents.
260 */
261 public function translate_user_email( $email ) {
262 $blog_name = wp_specialchars_decode( pll__( get_option( 'blogname' ) ), ENT_QUOTES );
263 $email['subject'] = sprintf( $email['subject'], $blog_name );
264 $email['message'] = str_replace( '###SITENAME###', $blog_name, $email['message'] );
265 return $email;
266 }
267
268 /**
269 * Translates the privacy policy page, on both frontend and admin
270 *
271 * @since 2.3.6
272 *
273 * @param int $id Privacy policy page id
274 * @return int
275 */
276 public function translate_page_for_privacy_policy( $id ) {
277 return empty( $this->curlang ) ? $id : $this->model->post->get( $id, $this->curlang );
278 }
279
280 /**
281 * Prevents edit and delete links for the translations of the privacy policy page for non admin
282 *
283 * @since 2.3.7
284 *
285 * @param array $caps The user's actual capabilities.
286 * @param string $cap Capability name.
287 * @param int $user_id The user ID.
288 * @param array $args Adds the context to the cap. The category id.
289 * @return array
290 */
291 public function fix_privacy_policy_page_editing( $caps, $cap, $user_id, $args ) {
292 if ( ! in_array( $cap, array( 'edit_page', 'edit_post', 'delete_page', 'delete_post' ), true ) ) {
293 return $caps;
294 }
295
296 if ( empty( $args[0] ) ) {
297 return $caps;
298 }
299
300 /*
301 * We expect a post ID, but nothing prevents to receive a `WP_Post`,
302 * so let make sure we manipulate a post ID in any case.
303 */
304 $post = get_post( $args[0] );
305 if ( ! $post ) {
306 return $caps;
307 }
308
309 $privacy_page = get_option( 'wp_page_for_privacy_policy' );
310 if ( $privacy_page && in_array( $post->ID, $this->model->post->get_translations( $privacy_page ), true ) ) {
311 $caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) );
312 }
313
314 return $caps;
315 }
316
317 /**
318 * Register our personal data exporter
319 *
320 * @since 2.3.6
321 *
322 * @param array $exporters Personal data exporters
323 * @return array
324 */
325 public function register_personal_data_exporter( $exporters ) {
326 $exporters[] = array(
327 'exporter_friendly_name' => __( 'Translated user descriptions', 'polylang' ),
328 'callback' => array( $this, 'user_data_exporter' ),
329 );
330 return $exporters;
331 }
332
333 /**
334 * Export translated user description as WP exports only the description in the default language
335 *
336 * @since 2.3.6
337 *
338 * @param string $email_address User email address
339 * @return array Personal data
340 */
341 public function user_data_exporter( $email_address ) {
342 $email_address = trim( $email_address );
343 $data_to_export = array();
344 $user_data_to_export = array();
345
346 if ( $user = get_user_by( 'email', $email_address ) ) {
347 foreach ( $this->model->get_languages_list() as $lang ) {
348 if ( ! $lang->is_default && $value = get_user_meta( $user->ID, 'description_' . $lang->slug, true ) ) {
349 $user_data_to_export[] = array(
350 /* translators: %s is a language native name */
351 'name' => sprintf( __( 'User description - %s', 'polylang' ), $lang->name ),
352 'value' => $value,
353 );
354 }
355 }
356
357 if ( ! empty( $user_data_to_export ) ) {
358 $data_to_export[] = array(
359 'group_id' => 'user',
360 'group_label' => __( 'User', 'polylang' ),
361 'item_id' => "user-{$user->ID}",
362 'data' => $user_data_to_export,
363 );
364 }
365 }
366
367 return array(
368 'data' => $data_to_export,
369 'done' => true,
370 );
371 }
372
373 /**
374 * Filters default query arguments for checking if a term exists.
375 * In `term_exists()`, WP 6.0 uses `get_terms()`, which is filtered by language by Polylang.
376 * This filter prevents `term_exists()` to be filtered by language.
377 *
378 * @since 3.2
379 *
380 * @param array $defaults An array of arguments passed to get_terms().
381 * @param int|string $term The term to check. Accepts term ID, slug, or name.
382 * @param string $taxonomy The taxonomy name to use. An empty string indicates the search is against all taxonomies.
383 * @return array
384 */
385 public function term_exists_default_query_args( $defaults, $term, $taxonomy ) {
386 if ( ! empty( $taxonomy ) && ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
387 return $defaults;
388 }
389
390 if ( ! is_array( $defaults ) ) {
391 $defaults = array();
392 }
393
394 if ( ! isset( $defaults['lang'] ) ) {
395 $defaults['lang'] = '';
396 }
397
398 return $defaults;
399 }
400 }
401