PluginProbe
Polylang / 2.7
Polylang v2.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-object.php

translated-object.php in Polylang 2.7, at include/translated-object.php

315 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Setups the objects languages and translations model
5 *
6 * @since 1.8
7 */
8 abstract class PLL_Translated_Object {
9 public $model;
10 protected $object_type, $type, $tax_language, $tax_translations, $tax_tt;
11
12 /**
13 * Constructor
14 *
15 * @since 1.8
16 *
17 * @param object $model
18 */
19 public function __construct( &$model ) {
20 $this->model = &$model;
21
22 // register our taxonomies as soon as possible
23 // this is early registration, not ready for rewrite rules as wp_rewrite will be setup later
24 $args = array( 'label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false, '_pll' => true );
25 register_taxonomy( $this->tax_language, $this->object_type, $args );
26 $args['update_count_callback'] = '_update_generic_term_count'; // count *all* posts to avoid deleting in clean_translations_terms
27 register_taxonomy( $this->tax_translations, $this->object_type, $args );
28 }
29
30 /**
31 * Wrap wp_get_object_terms to cache it and return only one object
32 * inspired by the function get_the_terms
33 *
34 * @since 1.2
35 *
36 * @param int $object_id post_id or term_id
37 * @param string $taxonomy Polylang taxonomy depending if we are looking for a post ( or term ) language ( or translation )
38 * @return bool|object the term associated to the object in the requested taxonomy if exists, false otherwise
39 */
40 public function get_object_term( $object_id, $taxonomy ) {
41 if ( empty( $object_id ) || is_wp_error( $object_id ) ) {
42 return false;
43 }
44
45 $object_id = (int) $object_id;
46 $term = get_object_term_cache( $object_id, $taxonomy );
47
48 if ( false === $term ) {
49 // query language and translations at the same time
50 $taxonomies = array( $this->tax_language, $this->tax_translations );
51
52 // query terms
53 $terms = array();
54 foreach ( wp_get_object_terms( $object_id, $taxonomies, array( 'update_term_meta_cache' => false ) ) as $t ) {
55 $terms[ $t->taxonomy ] = $t;
56 if ( $t->taxonomy == $taxonomy ) {
57 $term = $t;
58 }
59 }
60
61 // store it the way WP wants it
62 // set an empty cache if no term found in the taxonomy
63 foreach ( $taxonomies as $tax ) {
64 wp_cache_add( $object_id, empty( $terms[ $tax ] ) ? array() : array( $terms[ $tax ] ), $tax . '_relationships' );
65 }
66 }
67 else {
68 $term = reset( $term );
69 }
70
71 return empty( $term ) ? false : $term;
72 }
73
74 /**
75 * Tells whether a translation term must be updated
76 *
77 * @since 2.3
78 *
79 * @param array $id Post id or term id
80 * @param array $translations An associative array of translations with language code as key and translation id as value
81 */
82 protected function should_update_translation_group( $id, $translations ) {
83 // Don't do anything if no translations have been added to the group
84 $old_translations = $this->get_translations( $id ); // Includes at least $id itself
85 return count( array_diff_assoc( $translations, $old_translations ) ) > 0;
86 }
87
88 /**
89 * Saves translations for posts or terms
90 *
91 * @since 0.5
92 *
93 * @param int $id Post id or term id
94 * @param array $translations An associative array of translations with language code as key and translation id as value
95 */
96 public function save_translations( $id, $translations ) {
97 $id = (int) $id;
98
99 if ( ( $lang = $this->get_language( $id ) ) && isset( $translations ) && is_array( $translations ) ) {
100 // sanitize the translations array
101 $translations = array_map( 'intval', $translations );
102 $translations = array_merge( array( $lang->slug => $id ), $translations ); // make sure this object is in translations
103 $translations = array_diff( $translations, array( 0 ) ); // don't keep non translated languages
104 $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ); // keep only valid languages slugs as keys
105
106 // unlink removed translations
107 $old_translations = $this->get_translations( $id );
108 foreach ( array_diff_assoc( $old_translations, $translations ) as $object_id ) {
109 $this->delete_translation( $object_id );
110 }
111
112 // Check id we need to create or update the translation group
113 if ( $this->should_update_translation_group( $id, $translations ) ) {
114 $terms = wp_get_object_terms( $translations, $this->tax_translations );
115 $term = reset( $terms );
116
117 // create a new term if necessary
118 if ( empty( $term ) ) {
119 wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => maybe_serialize( $translations ) ) );
120 }
121 else {
122 // take care not to overwrite extra data stored in description field, if any
123 $d = maybe_unserialize( $term->description );
124 $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // remove old translations
125 $d = array_merge( $d, $translations ); // add new one
126 wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
127 }
128
129 // link all translations to the new term
130 foreach ( $translations as $p ) {
131 wp_set_object_terms( $p, $group, $this->tax_translations );
132 }
133
134 // clean now unused translation groups
135 foreach ( wp_list_pluck( $terms, 'term_id' ) as $term_id ) {
136 $term = get_term( $term_id, $this->tax_translations );
137 if ( empty( $term->count ) ) {
138 wp_delete_term( $term_id, $this->tax_translations );
139 }
140 }
141 }
142 }
143 }
144
145 /**
146 * Deletes a translation of a post or term
147 *
148 * @since 0.5
149 *
150 * @param int $id post id or term id
151 */
152 public function delete_translation( $id ) {
153 $id = (int) $id;
154 $term = $this->get_object_term( $id, $this->tax_translations );
155
156 if ( ! empty( $term ) ) {
157 $d = maybe_unserialize( $term->description );
158 $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
159 unset( $d[ $slug ] );
160
161 if ( empty( $d ) ) {
162 wp_delete_term( (int) $term->term_id, $this->tax_translations );
163 }
164 else {
165 wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => maybe_serialize( $d ) ) );
166 }
167 }
168 }
169
170 /**
171 * Returns an array of translations of a post or term
172 *
173 * @since 0.5
174 *
175 * @param int $id post id or term id
176 * @return array an associative array of translations with language code as key and translation id as value
177 */
178 public function get_translations( $id ) {
179 $term = $this->get_object_term( $id, $this->tax_translations );
180 $translations = empty( $term ) ? array() : maybe_unserialize( $term->description );
181
182 // make sure we return only translations ( thus we allow plugins to store other information in the array )
183 if ( is_array( $translations ) ) {
184 $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
185 }
186
187 // make sure to return at least the passed post or term in its translation array
188 if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
189 $translations = array( $lang->slug => $id );
190 }
191
192 return $translations;
193 }
194
195 /**
196 * Returns the id of the translation of a post or term
197 *
198 * @since 0.5
199 *
200 * @param int $id post id or term id
201 * @param object|string $lang object or slug
202 * @return bool|int post id or term id of the translation, false if there is none
203 */
204 public function get_translation( $id, $lang ) {
205 if ( ! $lang = $this->model->get_language( $lang ) ) {
206 return false;
207 }
208
209 $translations = $this->get_translations( $id );
210
211 return isset( $translations[ $lang->slug ] ) ? $translations[ $lang->slug ] : false;
212 }
213
214 /**
215 * Among the object and its translations, returns the id of the object which is in $lang
216 *
217 * @since 0.1
218 *
219 * @param int $id post id or term id
220 * @param int|string|object $lang language ( term_id or slug or object )
221 * @return bool|int the translation post id or term id if exists, otherwise the post id or term id, false if the post has no language
222 */
223 public function get( $id, $lang ) {
224 $id = (int) $id;
225 $obj_lang = $this->get_language( $id ); // FIXME is this necessary?
226 if ( ! $lang || ! $obj_lang ) {
227 return false;
228 }
229
230 $lang = $this->model->get_language( $lang );
231 return $obj_lang->term_id == $lang->term_id ? $id : $this->get_translation( $id, $lang );
232 }
233
234 /**
235 * A where clause to add to sql queries when filtering by language is needed directly in query
236 *
237 * @since 1.2
238 *
239 * @param object|array|string $lang a PLL_Language object or a comma separated list of language slug or an array of language slugs
240 * @return string where clause
241 */
242 public function where_clause( $lang ) {
243 $tt_id = $this->tax_tt;
244
245 // $lang is an object
246 // generally the case if the query is coming from Polylang
247 if ( is_object( $lang ) ) {
248 return ' AND pll_tr.term_taxonomy_id = ' . absint( $lang->$tt_id );
249 }
250
251 // $lang is a comma separated list of slugs ( or an array of slugs )
252 // generally the case is the query is coming from outside with 'lang' parameter
253 $slugs = is_array( $lang ) ? $lang : explode( ',', $lang );
254 $languages = array();
255 foreach ( $slugs as $slug ) {
256 $languages[] = absint( $this->model->get_language( $slug )->$tt_id );
257 }
258
259 return ' AND pll_tr.term_taxonomy_id IN ( ' . implode( ',', $languages ) . ' )';
260 }
261
262 /**
263 * Returns ids of objects in a language similarly to get_objects_in_term for a taxonomy
264 * faster than get_objects_in_term as it avoids a JOIN
265 *
266 * @since 1.4
267 *
268 * @param object $lang a PLL_Language object
269 * @return array
270 */
271 public function get_objects_in_language( $lang ) {
272 global $wpdb;
273 $tt_id = $this->tax_tt;
274 return $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
275 }
276
277 /**
278 * Check if a user can synchronize translations
279 *
280 * @since 2.6
281 *
282 * @param int $id Object id
283 * @return bool
284 */
285 public function current_user_can_synchronize( $id ) {
286 /**
287 * Filters whether a synchronization capability check should take place
288 *
289 * @since 2.6
290 *
291 * @param $check null to enable the capability check,
292 * true to always allow the synchronization,
293 * false to always disallow the synchronization.
294 * Defaults to true.
295 * @param $id The synchronization source object id
296 */
297 $check = apply_filters( "pll_pre_current_user_can_synchronize_{$this->type}", true, $id );
298 if ( null !== $check ) {
299 return $check;
300 }
301
302 if ( ! current_user_can( "edit_{$this->type}", $id ) ) {
303 return false;
304 }
305
306 foreach ( $this->get_translations( $id ) as $tr_id ) {
307 if ( $tr_id !== $id && ! current_user_can( "edit_{$this->type}", $tr_id ) ) {
308 return false;
309 }
310 }
311
312 return true;
313 }
314 }
315