PluginProbe
Polylang / 3.0.3
Polylang v3.0.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 / include / filters.php

filters.php in Polylang 3.0.3, at include/filters.php

414 lines 13.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 * 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 array
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
35 */
36 public $curlang;
37
38 /**
39 * Constructor: setups filters
40 *
41 * @since 1.4
42 *
43 * @param object $polylang
44 */
45 public function __construct( &$polylang ) {
46 $this->links_model = &$polylang->links_model;
47 $this->model = &$polylang->model;
48 $this->options = &$polylang->options;
49 $this->curlang = &$polylang->curlang;
50
51 // Deletes our cache for sticky posts when the list is updated.
52 add_action( 'update_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
53 add_action( 'add_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
54 add_action( 'delete_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) );
55
56 // Filters the comments according to the current language
57 add_action( 'parse_comment_query', array( $this, 'parse_comment_query' ) );
58 add_filter( 'comments_clauses', array( $this, 'comments_clauses' ), 10, 2 );
59
60 // Filters the get_pages function according to the current language
61 add_filter( 'get_pages', array( $this, 'get_pages' ), 10, 2 );
62
63 // Rewrites next and previous post links to filter them by language
64 add_filter( 'get_previous_post_join', array( $this, 'posts_join' ), 10, 5 );
65 add_filter( 'get_next_post_join', array( $this, 'posts_join' ), 10, 5 );
66 add_filter( 'get_previous_post_where', array( $this, 'posts_where' ), 10, 5 );
67 add_filter( 'get_next_post_where', array( $this, 'posts_where' ), 10, 5 );
68
69 // Converts the locale to a valid W3C locale
70 add_filter( 'language_attributes', array( $this, 'language_attributes' ) );
71
72 // Prevents deleting all the translations of the default category
73 add_filter( 'map_meta_cap', array( $this, 'fix_delete_default_category' ), 10, 4 );
74
75 // Translate the site title in emails sent to users
76 add_filter( 'password_change_email', array( $this, 'translate_user_email' ) );
77 add_filter( 'email_change_email', array( $this, 'translate_user_email' ) );
78
79 // Translates the privacy policy page
80 add_filter( 'option_wp_page_for_privacy_policy', array( $this, 'translate_page_for_privacy_policy' ), 20 ); // Since WP 4.9.6
81 add_filter( 'map_meta_cap', array( $this, 'fix_privacy_policy_page_editing' ), 10, 4 );
82
83 // Personal data exporter
84 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ), 0 ); // Since WP 4.9.6
85 }
86
87 /**
88 * Deletes the cache for multilingual sticky posts.
89 *
90 * @since 2.8.4
91 *
92 * @return void
93 */
94 public function delete_sticky_posts_cache() {
95 wp_cache_delete( 'sticky_posts', 'options' );
96 }
97
98 /**
99 * Get the language to filter a comments query.
100 *
101 * @since 2.0
102 *
103 * @param WP_Comment_Query $query WP_Comment_Query object.
104 * @return PLL_Language|false The language to use in the filter, false otherwise.
105 */
106 protected function get_comments_queried_language( $query ) {
107 // Don't filter comments if comment ids or post ids are specified.
108 $plucked = wp_array_slice_assoc( $query->query_vars, array( 'comment__in', 'parent', 'post_id', 'post__in', 'post_parent' ) );
109 $fields = array_filter( $plucked );
110 if ( ! empty( $fields ) ) {
111 return false;
112 }
113
114 // Don't filter comments if a non translated post type is specified.
115 if ( ! empty( $query->query_vars['post_type'] ) && ! $this->model->is_translated_post_type( $query->query_vars['post_type'] ) ) {
116 return false;
117 }
118
119 return empty( $query->query_vars['lang'] ) ? $this->curlang : $this->model->get_language( $query->query_vars['lang'] );
120 }
121
122 /**
123 * Adds a language dependent cache domain when querying comments.
124 * Useful as the 'lang' parameter is not included in cache key by WordPress.
125 * Needed since WP 4.6 as comments have been added to persistent cache. See #36906, #37419.
126 *
127 * @since 2.0
128 *
129 * @param WP_Comment_Query $query WP_Comment_Query object.
130 * @return void
131 */
132 public function parse_comment_query( $query ) {
133 $lang = $this->get_comments_queried_language( $query );
134 if ( $lang ) {
135 $key = '_' . $lang->slug;
136 $query->query_vars['cache_domain'] = empty( $query->query_vars['cache_domain'] ) ? 'pll' . $key : $query->query_vars['cache_domain'] . $key;
137 }
138 }
139
140 /**
141 * Filters the comments according to the current language.
142 * Used by the recent comments widget and admin language filter.
143 *
144 * @since 0.2
145 *
146 * @param string[] $clauses SQL clauses.
147 * @param WP_Comment_Query $query WP_Comment_Query object.
148 * @return string[] Modified $clauses.
149 */
150 public function comments_clauses( $clauses, $query ) {
151 global $wpdb;
152
153 $lang = $this->get_comments_queried_language( $query );
154
155 if ( ! empty( $lang ) ) {
156 // If this clause is not already added by WP.
157 if ( ! strpos( $clauses['join'], '.ID' ) ) {
158 $clauses['join'] .= " JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
159 }
160
161 $clauses['join'] .= $this->model->post->join_clause();
162 $clauses['where'] .= $this->model->post->where_clause( $lang );
163 }
164 return $clauses;
165 }
166
167 /**
168 * Filters get_pages() per language.
169 *
170 * @since 1.4
171 *
172 * @param WP_Post[] $pages An array of pages already queried.
173 * @param array $args Array of get_pages() arguments.
174 * @return WP_Post[] Modified list of pages.
175 */
176 public function get_pages( $pages, $args ) {
177 if ( isset( $args['lang'] ) && empty( $args['lang'] ) ) {
178 return $pages;
179 }
180
181 $language = empty( $args['lang'] ) ? $this->curlang : $this->model->get_language( $args['lang'] );
182
183 if ( empty( $language ) || empty( $pages ) || ! $this->model->is_translated_post_type( $args['post_type'] ) ) {
184 return $pages;
185 }
186
187 static $once = false;
188
189 // Obliged to redo the get_pages query if we want to get the right number
190 if ( ! empty( $args['number'] ) && ! $once ) {
191 $once = true; // avoid infinite loop
192
193 $r = array(
194 'lang' => 0, // So this query is not filtered
195 'numberposts' => -1,
196 'nopaging' => true,
197 'post_type' => $args['post_type'],
198 'fields' => 'ids',
199 'tax_query' => array(
200 array(
201 'taxonomy' => 'language',
202 'field' => 'term_taxonomy_id', // Since WP 3.5
203 'terms' => $language->term_taxonomy_id,
204 'operator' => 'NOT IN',
205 ),
206 ),
207 );
208
209 // Take care that 'exclude' argument accepts integer or strings too
210 $args['exclude'] = array_merge( wp_parse_id_list( $args['exclude'] ), get_posts( $r ) );
211 $pages = get_pages( $args );
212 }
213
214 $ids = wp_list_pluck( $pages, 'ID' );
215
216 // Filters the queried list of pages by language
217 if ( ! $once ) {
218 $ids = array_intersect( $ids, $this->model->post->get_objects_in_language( $language ) );
219
220 foreach ( $pages as $key => $page ) {
221 if ( ! in_array( $page->ID, $ids ) ) {
222 unset( $pages[ $key ] );
223 }
224 }
225
226 $pages = array_values( $pages ); // In case 3rd parties suppose the existence of $pages[0]
227 }
228
229 // Not done by WP but extremely useful for performance when manipulating taxonomies
230 update_object_term_cache( $ids, $args['post_type'] );
231
232 $once = false; // In case get_pages is called another time
233 return $pages;
234 }
235
236 /**
237 * Modifies the sql request for get_adjacent_post to filter by the current language.
238 *
239 * @since 0.1
240 *
241 * @param string $sql The JOIN clause in the SQL.
242 * @param bool $in_same_term Whether post should be in a same taxonomy term.
243 * @param int[] $excluded_terms Array of excluded term IDs.
244 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
245 * @param WP_Post $post WP_Post object.
246 * @return string Modified JOIN clause.
247 */
248 public function posts_join( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) {
249 return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->join_clause( 'p' ) : $sql;
250 }
251
252 /**
253 * Modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language.
254 *
255 * @since 0.1
256 *
257 * @param string $sql The WHERE clause in the SQL.
258 * @param bool $in_same_term Whether post should be in a same taxonomy term.
259 * @param int[] $excluded_terms Array of excluded term IDs.
260 * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true.
261 * @param WP_Post $post WP_Post object.
262 * @return string Modified WHERE clause.
263 */
264 public function posts_where( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) {
265 return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->where_clause( $this->curlang ) : $sql;
266 }
267
268 /**
269 * Converts WordPress locale to valid W3 locale in html language attributes
270 *
271 * @since 1.8
272 *
273 * @param string $output language attributes
274 * @return string
275 */
276 public function language_attributes( $output ) {
277 if ( $language = $this->model->get_language( is_admin() ? get_user_locale() : get_locale() ) ) {
278 $output = str_replace( '"' . get_bloginfo( 'language' ) . '"', '"' . $language->get_locale( 'display' ) . '"', $output );
279 }
280 return $output;
281 }
282
283 /**
284 * Prevents deleting all the translations of the default category
285 *
286 * @since 2.1
287 *
288 * @param array $caps The user's actual capabilities.
289 * @param string $cap Capability name.
290 * @param int $user_id The user ID.
291 * @param array $args Adds the context to the cap. The category id.
292 * @return array
293 */
294 public function fix_delete_default_category( $caps, $cap, $user_id, $args ) {
295 if ( 'delete_term' === $cap ) {
296 $term = get_term( reset( $args ) ); // Since WP 4.4, we can get the term to get the taxonomy
297 if ( $term instanceof WP_Term ) {
298 $default_cat = get_option( 'default_' . $term->taxonomy );
299 if ( $default_cat && array_intersect( $args, $this->model->term->get_translations( $default_cat ) ) ) {
300 $caps[] = 'do_not_allow';
301 }
302 }
303 }
304
305 return $caps;
306 }
307
308 /**
309 * Translates the site title in emails sent to the user (change email, reset password).
310 * It is necessary to filter the email because WP evaluates the site title before calling switch_to_locale().
311 *
312 * @since 2.1.3
313 *
314 * @param string[] $email Email contents.
315 * @return string[] Translated email contents.
316 */
317 public function translate_user_email( $email ) {
318 $blog_name = wp_specialchars_decode( pll__( get_option( 'blogname' ) ), ENT_QUOTES );
319 $email['subject'] = sprintf( $email['subject'], $blog_name );
320 $email['message'] = str_replace( '###SITENAME###', $blog_name, $email['message'] );
321 return $email;
322 }
323
324 /**
325 * Translates the privacy policy page, on both frontend and admin
326 *
327 * @since 2.3.6
328 *
329 * @param int $id Privacy policy page id
330 * @return int
331 */
332 public function translate_page_for_privacy_policy( $id ) {
333 return empty( $this->curlang ) ? $id : $this->model->post->get( $id, $this->curlang );
334 }
335
336 /**
337 * Prevents edit and delete links for the translations of the privacy policy page for non admin
338 *
339 * @since 2.3.7
340 *
341 * @param array $caps The user's actual capabilities.
342 * @param string $cap Capability name.
343 * @param int $user_id The user ID.
344 * @param array $args Adds the context to the cap. The category id.
345 * @return array
346 */
347 public function fix_privacy_policy_page_editing( $caps, $cap, $user_id, $args ) {
348 if ( in_array( $cap, array( 'edit_page', 'edit_post', 'delete_page', 'delete_post' ) ) ) {
349 $privacy_page = get_option( 'wp_page_for_privacy_policy' );
350 if ( $privacy_page && array_intersect( $args, $this->model->post->get_translations( $privacy_page ) ) ) {
351 $caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) );
352 }
353 }
354
355 return $caps;
356 }
357
358 /**
359 * Register our personal data exporter
360 *
361 * @since 2.3.6
362 *
363 * @param array $exporters Personal data exporters
364 * @return array
365 */
366 public function register_personal_data_exporter( $exporters ) {
367 $exporters[] = array(
368 'exporter_friendly_name' => __( 'Translated user descriptions', 'polylang' ),
369 'callback' => array( $this, 'user_data_exporter' ),
370 );
371 return $exporters;
372 }
373
374 /**
375 * Export translated user description as WP exports only the description in the default language
376 *
377 * @since 2.3.6
378 *
379 * @param string $email_address User email address
380 * @return array Personal data
381 */
382 public function user_data_exporter( $email_address ) {
383 $email_address = trim( $email_address );
384 $data_to_export = array();
385 $user_data_to_export = array();
386
387 if ( $user = get_user_by( 'email', $email_address ) ) {
388 foreach ( $this->model->get_languages_list() as $lang ) {
389 if ( $lang->slug !== $this->options['default_lang'] && $value = get_user_meta( $user->ID, 'description_' . $lang->slug, true ) ) {
390 $user_data_to_export[] = array(
391 /* translators: %s is a language native name */
392 'name' => sprintf( __( 'User description - %s', 'polylang' ), $lang->name ),
393 'value' => $value,
394 );
395 }
396 }
397
398 if ( ! empty( $user_data_to_export ) ) {
399 $data_to_export[] = array(
400 'group_id' => 'user',
401 'group_label' => __( 'User', 'polylang' ),
402 'item_id' => "user-{$user->ID}",
403 'data' => $user_data_to_export,
404 );
405 }
406 }
407
408 return array(
409 'data' => $data_to_export,
410 'done' => true,
411 );
412 }
413 }
414