PluginProbe
Polylang / 2.0
Polylang v2.0
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.0, at include/translated-object.php

273 lines 9.2 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, $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 ) ) {
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 foreach ( wp_get_object_terms( $object_id, $taxonomies ) as $t ) {
54 $terms[ $t->taxonomy ] = $t;
55 if ( $t->taxonomy == $taxonomy ) {
56 $term = $t;
57 }
58 }
59
60 // store it the way WP wants it
61 // set an empty cache if no term found in the taxonomy
62 foreach ( $taxonomies as $tax ) {
63 wp_cache_add( $object_id, empty( $terms[ $tax ] ) ? array() : array( $terms[ $tax ] ), $tax . '_relationships' );
64 }
65 }
66 else {
67 $term = reset( $term );
68 }
69
70 return empty( $term ) ? false : $term;
71 }
72
73 /**
74 * Tells wether to store a translation term
75 *
76 * @since 1.8
77 *
78 * @param array $translations an associative array of translations with language code as key and translation id as value
79 */
80 protected function keep_translation_group( $translations ) {
81 return count( $translations ) > 1;
82 }
83
84 /**
85 * Saves translations for posts or terms
86 *
87 * @since 0.5
88 *
89 * @param int $id post id or term id
90 * @param array $translations an associative array of translations with language code as key and translation id as value
91 */
92 public function save_translations( $id, $translations ) {
93 $id = (int) $id;
94
95 if ( ( $lang = $this->get_language( $id ) ) && isset( $translations ) && is_array( $translations ) ) {
96 // sanitize the translations array
97 $translations = array_map( 'intval', $translations );
98 $translations = array_merge( array( $lang->slug => $id ), $translations ); // make sure this object is in translations
99 $translations = array_diff( $translations, array( 0 ) ); // don't keep non translated languages
100 $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) ); // keep only valid languages slugs as keys
101
102 // unlink removed translations
103 $old_translations = $this->get_translations( $id );
104 foreach ( array_diff_assoc( $old_translations, $translations ) as $object_id ) {
105 $this->delete_translation( $object_id );
106 }
107
108 // don't create a translation group for untranslated posts as it is useless
109 // but we need one for terms to allow relationships remap when importing from a WXR file
110 if ( $this->keep_translation_group( $translations ) ) {
111 $terms = wp_get_object_terms( $translations, $this->tax_translations );
112 $term = reset( $terms );
113
114 // create a new term if necessary
115 if ( empty( $term ) ) {
116 wp_insert_term( $group = uniqid( 'pll_' ), $this->tax_translations, array( 'description' => serialize( $translations ) ) );
117 }
118 else {
119 // take care not to overwrite extra data stored in description field, if any
120 $d = unserialize( $term->description );
121 $d = is_array( $d ) ? array_diff_key( $d, $old_translations ) : array(); // remove old translations
122 $d = array_merge( $d, $translations ); // add new one
123 wp_update_term( $group = (int) $term->term_id, $this->tax_translations, array( 'description' => serialize( $d ) ) );
124 }
125
126 // link all translations to the new term
127 foreach ( $translations as $p ) {
128 wp_set_object_terms( $p, $group, $this->tax_translations );
129 }
130
131 // clean now unused translation groups
132 foreach ( wp_list_pluck( $terms, 'term_id' ) as $term_id ) {
133 $term = get_term( $term_id, $this->tax_translations );
134 if ( empty( $term->count ) ) {
135 wp_delete_term( $term_id, $this->tax_translations );
136 }
137 }
138 }
139 }
140 }
141
142 /**
143 * Deletes a translation of a post or term
144 *
145 * @since 0.5
146 *
147 * @param int $id post id or term id
148 */
149 public function delete_translation( $id ) {
150 $id = (int) $id;
151 $term = $this->get_object_term( $id, $this->tax_translations );
152
153 if ( ! empty( $term ) ) {
154 $d = unserialize( $term->description );
155 $slug = array_search( $id, $this->get_translations( $id ) ); // in case some plugin stores the same value with different key
156 unset( $d[ $slug ] );
157
158 if ( empty( $d ) ) {
159 wp_delete_term( (int) $term->term_id, $this->tax_translations );
160 }
161 else {
162 wp_update_term( (int) $term->term_id, $this->tax_translations, array( 'description' => serialize( $d ) ) );
163 }
164 }
165 }
166
167 /**
168 * Returns an array of translations of a post or term
169 *
170 * @since 0.5
171 *
172 * @param int $id post id or term id
173 * @return array an associative array of translations with language code as key and translation id as value
174 */
175 public function get_translations( $id ) {
176 $term = $this->get_object_term( $id, $this->tax_translations );
177 $translations = empty( $term ) ? array() : unserialize( $term->description );
178
179 // make sure we return only translations ( thus we allow plugins to store other informations in the array )
180 if ( is_array( $translations ) ) {
181 $translations = array_intersect_key( $translations, array_flip( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) );
182 }
183
184 // make sure to return at least the passed post or term in its translation array
185 if ( empty( $translations ) && $lang = $this->get_language( $id ) ) {
186 $translations = array( $lang->slug => $id );
187 }
188
189 return $translations;
190 }
191
192 /**
193 * Returns the id of the translation of a post or term
194 *
195 * @since 0.5
196 *
197 * @param int $id post id or term id
198 * @param object|string $lang object or slug
199 * @return bool|int post id or term id of the translation, false if there is none
200 */
201 public function get_translation( $id, $lang ) {
202 if ( ! $lang = $this->model->get_language( $lang ) ) {
203 return false;
204 }
205
206 $translations = $this->get_translations( $id );
207
208 return isset( $translations[ $lang->slug ] ) ? $translations[ $lang->slug ] : false;
209 }
210
211 /**
212 * Among the object and its translations, returns the id of the object which is in $lang
213 *
214 * @since 0.1
215 *
216 * @param int $id post id or term id
217 * @param int|string|object $lang language ( term_id or slug or object )
218 * @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
219 */
220 public function get( $id, $lang ) {
221 $obj_lang = $this->get_language( $id ); // FIXME is this necessary?
222 if ( ! $lang || ! $obj_lang ) {
223 return false;
224 }
225
226 $lang = $this->model->get_language( $lang );
227 return $obj_lang->term_id == $lang->term_id ? $id : $this->get_translation( $id, $lang );
228 }
229
230 /**
231 * A where clause to add to sql queries when filtering by language is needed directly in query
232 *
233 * @since 1.2
234 *
235 * @param object|array|string $lang a PLL_Language object or a comma separated list of languag slug or an array of language slugs
236 * @return string where clause
237 */
238 public function where_clause( $lang ) {
239 global $wpdb;
240 $tt_id = $this->tax_tt;
241
242 // $lang is an object
243 // generally the case if the query is coming from Polylang
244 if ( is_object( $lang ) ) {
245 return $wpdb->prepare( ' AND pll_tr.term_taxonomy_id = %d', $lang->$tt_id );
246 }
247
248 // $lang is a comma separated list of slugs ( or an array of slugs )
249 // generally the case is the query is coming from outside with 'lang' parameter
250 $slugs = is_array( $lang ) ? $lang : explode( ',', $lang );
251 foreach ( $slugs as $slug ) {
252 $languages[] = (int) $this->model->get_language( $slug )->$tt_id;
253 }
254
255 return ' AND pll_tr.term_taxonomy_id IN ( ' . implode( ',', $languages ) . ' )';
256 }
257
258 /**
259 * Returns ids of objects in a language similarly to get_objects_in_term for a taxonomy
260 * faster than get_objects_in_term as it avoids a JOIN
261 *
262 * @since 1.4
263 *
264 * @param object $lang a PLL_Language object
265 * @return array
266 */
267 public function get_objects_in_language( $lang ) {
268 global $wpdb;
269 $tt_id = $this->tax_tt;
270 return $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $lang->$tt_id ) );
271 }
272 }
273