PluginProbe
Polylang / 3.6.7
Polylang v3.6.7
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.6.7, at include/translated-post.php

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