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

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

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