PluginProbe
Polylang / 2.9.2
Polylang v2.9.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 / include / filters.php

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

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