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

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