PluginProbe
Polylang / 2.8.3
Polylang v2.8.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 2.8.3, at include/filters.php

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