PluginProbe
Polylang / 1.8
Polylang v1.8
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 1.8, at include/translated-object.php

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