PluginProbe
Polylang / 3.4.2
Polylang v3.4.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 / translated-post.php

translated-post.php in Polylang 3.4.2, at include/translated-post.php

382 lines 11.1 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 defined( 'ABSPATH' ) || exit;
7
8 /**
9 * Sets the posts languages and translations model up.
10 *
11 * @since 1.8
12 */
13 class PLL_Translated_Post extends PLL_Translated_Object implements PLL_Translatable_Object_With_Types_Interface {
14
15 use PLL_Translatable_Object_With_Types_Trait;
16
17 /**
18 * Taxonomy name for the languages.
19 *
20 * @var string
21 *
22 * @phpstan-var non-empty-string
23 */
24 protected $tax_language = 'language';
25
26 /**
27 * Identifier that must be unique for each type of content.
28 * Also used when checking capabilities.
29 *
30 * @var string
31 *
32 * @phpstan-var non-empty-string
33 */
34 protected $type = 'post';
35
36 /**
37 * Identifier for each type of content to used for cache type.
38 *
39 * @var string
40 *
41 * @phpstan-var non-empty-string
42 */
43 protected $cache_type = 'posts';
44
45 /**
46 * Taxonomy name for the translation groups.
47 *
48 * @var string
49 *
50 * @phpstan-var non-empty-string
51 */
52 protected $tax_translations = 'post_translations';
53
54 /**
55 * Constructor.
56 *
57 * @since 1.8
58 *
59 * @param PLL_Model $model Instance of `PLL_Model`.
60 */
61 public function __construct( PLL_Model &$model ) {
62 $this->db = array(
63 'table' => $GLOBALS['wpdb']->posts,
64 'id_column' => 'ID',
65 'type_column' => 'post_type',
66 'default_alias' => $GLOBALS['wpdb']->posts,
67 );
68
69 parent::__construct( $model );
70
71 // Keep hooks in constructor for backward compatibility.
72 $this->init();
73 }
74
75 /**
76 * Adds hooks.
77 *
78 * @since 3.4
79 *
80 * @return static
81 */
82 public function init() {
83 // Registers completely the language taxonomy.
84 add_action( 'setup_theme', array( $this, 'register_taxonomy' ), 1 );
85
86 // Setups post types to translate.
87 add_action( 'registered_post_type', array( $this, 'registered_post_type' ) );
88
89 // Forces updating posts cache.
90 add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
91 return parent::init();
92 }
93
94 /**
95 * Deletes a translation of a post.
96 *
97 * @since 0.5
98 *
99 * @param int $id Post ID.
100 * @return void
101 */
102 public function delete_translation( $id ) {
103 $id = $this->sanitize_int_id( $id );
104
105 if ( empty( $id ) ) {
106 return;
107 }
108
109 parent::delete_translation( $id );
110 wp_set_object_terms( $id, array(), $this->tax_translations );
111 }
112
113 /**
114 * Returns object types (post types) that need to be translated.
115 * The post types list is cached for better performance.
116 * The method waits for 'after_setup_theme' to apply the cache to allow themes adding the filter in functions.php.
117 *
118 * @since 3.4
119 *
120 * @param bool $filter True if we should return only valid registered object types.
121 * @return string[] Object type names for which Polylang manages languages.
122 *
123 * @phpstan-return array<non-empty-string, non-empty-string>
124 */
125 public function get_translated_object_types( $filter = true ) {
126 $post_types = $this->model->cache->get( 'post_types' );
127
128 if ( false === $post_types ) {
129 $post_types = array( 'post' => 'post', 'page' => 'page', 'wp_block' => 'wp_block' );
130
131 if ( ! empty( $this->model->options['post_types'] ) && is_array( $this->model->options['post_types'] ) ) {
132 $post_types = array_merge( $post_types, array_combine( $this->model->options['post_types'], $this->model->options['post_types'] ) );
133 }
134
135 if ( empty( $this->model->options['media_support'] ) ) {
136 // In case the post type attachment is stored in the option.
137 unset( $post_types['attachment'] );
138 } else {
139 $post_types['attachment'] = 'attachment';
140 }
141
142 /**
143 * Filters the list of post types available for translation.
144 * The default are post types which have the parameter ‘public’ set to true.
145 * The filter must be added soon in the WordPress loading process:
146 * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes.
147 *
148 * @since 0.8
149 *
150 * @param string[] $post_types List of post type names (as array keys and values).
151 * @param bool $is_settings True when displaying the list of custom post types in Polylang settings.
152 */
153 $post_types = apply_filters( 'pll_get_post_types', $post_types, false );
154
155 if ( did_action( 'after_setup_theme' ) ) {
156 $this->model->cache->set( 'post_types', $post_types );
157 }
158 }
159
160 /** @var array<non-empty-string, non-empty-string> $post_types */
161 return $filter ? array_intersect( $post_types, get_post_types() ) : $post_types;
162 }
163
164 /**
165 * Returns true if Polylang manages languages for this object type.
166 *
167 * @since 3.4
168 *
169 * @param string|string[] $object_type Object type (post type) name or array of object type names.
170 * @return bool
171 *
172 * @phpstan-param non-empty-string|non-empty-string[] $object_type
173 */
174 public function is_translated_object_type( $object_type ) {
175 $post_types = $this->get_translated_object_types( false );
176 return ( is_array( $object_type ) && array_intersect( $object_type, $post_types ) || in_array( $object_type, $post_types ) || 'any' === $object_type && ! empty( $post_types ) );
177 }
178
179 /**
180 * Registers the language taxonomy.
181 *
182 * @since 1.2
183 *
184 * @return void
185 */
186 public function register_taxonomy() {
187 register_taxonomy(
188 $this->tax_language,
189 $this->model->get_translated_post_types(),
190 array(
191 'labels' => array(
192 'name' => __( 'Languages', 'polylang' ),
193 'singular_name' => __( 'Language', 'polylang' ),
194 'all_items' => __( 'All languages', 'polylang' ),
195 ),
196 'public' => false,
197 'show_ui' => false, // Hide the taxonomy on admin side, needed for WP 4.4.x.
198 'show_in_nav_menus' => false, // No metabox for nav menus, needed for WP 4.4.x.
199 'publicly_queryable' => true, // Since WP 4.5.
200 'query_var' => 'lang',
201 'rewrite' => $this->model->options['force_lang'] < 2, // No rewrite for domains and sub-domains.
202 '_pll' => true, // Polylang taxonomy.
203 )
204 );
205 }
206
207 /**
208 * Checks if registered post type must be translated.
209 *
210 * @since 1.2
211 *
212 * @param string $post_type Post type name.
213 * @return void
214 *
215 * @phpstan-param non-empty-string $post_type
216 */
217 public function registered_post_type( $post_type ) {
218 if ( $this->model->is_translated_post_type( $post_type ) ) {
219 register_taxonomy_for_object_type( $this->tax_language, $post_type );
220 register_taxonomy_for_object_type( $this->tax_translations, $post_type );
221 }
222 }
223
224 /**
225 * Forces calling 'update_object_term_cache' when querying posts or pages.
226 * This is especially useful for nav menus with a lot of pages as, without doing this,
227 * we would have one query per page in the menu to get the page language for the permalink.
228 *
229 * @since 1.8
230 *
231 * @param WP_Query $query Reference to the query object.
232 * @return void
233 */
234 public function pre_get_posts( $query ) {
235 if ( ! empty( $query->query['post_type'] ) && $this->model->is_translated_post_type( $query->query['post_type'] ) ) {
236 $query->query_vars['update_post_term_cache'] = true;
237 }
238 }
239
240 /**
241 * Checks if the current user can read the post.
242 *
243 * @since 1.5
244 * @since 3.4 Renamed the parameter $post_id into $id.
245 *
246 * @param int $id Post ID
247 * @param string $context Optional, 'edit' or 'view'. Defaults to 'view'.
248 * @return bool
249 *
250 * @phpstan-param non-empty-string $context
251 */
252 public function current_user_can_read( $id, $context = 'view' ) {
253 $id = $this->sanitize_int_id( $id );
254
255 if ( empty( $id ) ) {
256 return false;
257 }
258
259 $post = get_post( $id );
260
261 if ( empty( $post ) ) {
262 return false;
263 }
264
265 if ( 'inherit' === $post->post_status && $post->post_parent ) {
266 $post = get_post( $post->post_parent );
267
268 if ( empty( $post ) ) {
269 return false;
270 }
271 }
272
273 if ( 'inherit' === $post->post_status || in_array( $post->post_status, get_post_stati( array( 'public' => true ) ) ) ) {
274 return true;
275 }
276
277 // Follow WP practices, which shows links to private posts ( when readable ), but not for draft posts ( ex: get_adjacent_post_link() )
278 if ( in_array( $post->post_status, get_post_stati( array( 'private' => true ) ) ) ) {
279 if ( ! is_user_logged_in() ) {
280 return false;
281 }
282
283 $user = wp_get_current_user();
284
285 if ( (int) $user->ID === (int) $post->post_author ) {
286 return true;
287 }
288
289 $post_type_object = get_post_type_object( $post->post_type );
290
291 return ! empty( $post_type_object ) && current_user_can( $post_type_object->cap->read_private_posts );
292 }
293
294 // In edit context, show draft and future posts.
295 if ( 'edit' === $context ) {
296 $states = get_post_stati(
297 array(
298 'protected' => true,
299 'show_in_admin_all_list' => true,
300 )
301 );
302
303 if ( in_array( $post->post_status, $states ) ) {
304 $user = wp_get_current_user();
305 return is_user_logged_in() && ( current_user_can( 'edit_posts' ) || (int) $user->ID === (int) $post->post_author );
306 }
307 }
308
309 return false;
310 }
311
312 /**
313 * Returns a list of posts in a language ($lang) not translated in another language ($untranslated_in).
314 *
315 * @since 2.6
316 *
317 * @param string $type Post type.
318 * @param PLL_Language $untranslated_in The language the posts must not be translated in.
319 * @param PLL_Language $lang Language of the searched posts.
320 * @param string $search Limit the results to the posts matching this string.
321 * @return WP_Post[] Array of posts.
322 */
323 public function get_untranslated( $type, PLL_Language $untranslated_in, PLL_Language $lang, $search = '' ) {
324 global $wpdb;
325
326 $args = array( 'numberposts' => 20 ); // Limit to 20 posts by default.
327 /**
328 * Filters the query args when auto suggesting untranslated posts in the Languages metabox.
329 *
330 * @since 1.7
331 * @since 3.4 Handled arguments restricted to `numberposts` to limit queried posts.
332 * No `WP_Query` is made anymore, a custom one is used instead.
333 *
334 * @param array $args WP_Query arguments
335 */
336 $args = apply_filters( 'pll_ajax_posts_not_translated_args', $args );
337
338 $limit = $args['numberposts'];
339 $search_like = '%' . $wpdb->esc_like( $search ) . '%';
340 $untranslated_like = '%' . $wpdb->esc_like( $untranslated_in->slug ) . '%';
341 $posts = $wpdb->get_results(
342 $wpdb->prepare(
343 "SELECT * FROM {$wpdb->posts}
344 INNER JOIN {$wpdb->term_relationships} AS tr1 ON ({$wpdb->posts}.ID = tr1.object_id)
345 AND tr1.term_taxonomy_id IN (%d)
346 AND (({$wpdb->posts}.post_title LIKE %s)
347 OR ({$wpdb->posts}.post_excerpt LIKE %s)
348 OR ({$wpdb->posts}.post_content LIKE %s)
349 )
350 AND {$wpdb->posts}.post_type = %s
351 AND {$wpdb->posts}.post_status NOT IN ('trash', 'auto-draft')
352 WHERE {$wpdb->posts}.ID NOT IN (
353 SELECT {$wpdb->posts}.ID FROM {$wpdb->posts}
354 LEFT JOIN {$wpdb->term_relationships} AS tr2 ON ({$wpdb->posts}.ID = tr2.object_id)
355 INNER JOIN {$wpdb->term_taxonomy} AS tt ON (tt.term_taxonomy_id = tr2.term_taxonomy_id)
356 AND (tt.taxonomy = 'post_translations')
357 AND tt.description LIKE %s
358 )
359 LIMIT 0, %d",
360 $lang->get_tax_prop( $this->tax_language, 'term_taxonomy_id' ),
361 $search_like,
362 $search_like,
363 $search_like,
364 $type,
365 $untranslated_like,
366 $limit
367 )
368 );
369
370 foreach ( $posts as $i => $post ) {
371 if ( ! $this->current_user_can_read( $post->ID, 'edit' ) ) {
372 unset( $posts[ $i ] );
373 continue;
374 }
375
376 $posts[ $i ] = new WP_Post( $post );
377 }
378
379 return $posts;
380 }
381 }
382