PluginProbe
Polylang / 1.8.2
Polylang v1.8.2
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 / admin / admin-model.php

admin-model.php in Polylang 1.8.2, at admin/admin-model.php

495 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * extends the PLL_Model class with methods needed only in Polylang settings pages
5 *
6 * @since 1.2
7 */
8 class PLL_Admin_Model extends PLL_Model {
9
10 /*
11 * adds a new language
12 * creates a default category for this language
13 *
14 * list of arguments that $args must contain:
15 * name -> language name ( used only for display )
16 * slug -> language code ( ideally 2-letters ISO 639-1 language code
17 * locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
18 * rtl -> 1 if rtl language, 0 otherwise
19 * term_group -> language order when displayed
20 *
21 * optional arguments that $args can contain:
22 * no_default_cat -> if set, no default category will be created for this language
23 *
24 * @since 1.2
25 *
26 * @param array $args
27 * @return bool true if success / false if failed
28 */
29 public function add_language( $args ) {
30 if ( ! $this->validate_lang( $args ) ) {
31 return false;
32 }
33
34 // first the language taxonomy
35 $description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
36 $r = wp_insert_term( $args['name'], 'language', array( 'slug' => $args['slug'], 'description' => $description ) );
37 if ( is_wp_error( $r ) ) {
38 // avoid an ugly fatal error if something went wrong ( reported once in the forum )
39 add_settings_error( 'general', 'pll_add_language', __( 'Impossible to add the language.', 'polylang' ) );
40 return false;
41 }
42 wp_update_term( (int) $r['term_id'], 'language', array( 'term_group' => (int) $args['term_group'] ) ); // can't set the term group directly in wp_insert_term
43
44 // the term_language taxonomy
45 // don't want shared terms so use a different slug
46 wp_insert_term( $args['name'], 'term_language', array( 'slug' => 'pll_' . $args['slug'] ) );
47
48 $this->clean_languages_cache(); // udpate the languages list now !
49
50 if ( ! isset( $this->options['default_lang'] ) ) {
51 // if this is the first language created, set it as default language
52 $this->options['default_lang'] = $args['slug'];
53 update_option( 'polylang', $this->options );
54
55 // and assign default language to default category
56 $this->term->set_language( (int) get_option( 'default_category' ), (int) $r['term_id'] );
57 }
58 elseif ( empty( $args['no_default_cat'] ) ) {
59 $this->create_default_category( $args['slug'] );
60 }
61
62 // init a mo_id for this language
63 $mo = new PLL_MO();
64 $mo->export_to_db( $this->get_language( $args['slug'] ) );
65
66 $this->clean_languages_cache(); // again to set add mo_id in the cached languages list
67 flush_rewrite_rules(); // refresh rewrite rules
68
69 add_settings_error( 'general', 'pll_languages_created', __( 'Language added.', 'polylang' ), 'updated' );
70 return true;
71 }
72
73 /*
74 * delete a language
75 *
76 * @since 1.2
77 *
78 * @param int $lang_id language term_id
79 */
80 public function delete_language( $lang_id ) {
81 $lang = $this->get_language( (int) $lang_id );
82
83 // oops ! we are deleting the default language...
84 // need to do this before loosing the information for default category translations
85 if ( $this->options['default_lang'] == $lang->slug ) {
86 $slugs = $this->get_languages_list( array( 'fields' => 'slug' ) );
87 $slugs = array_diff( $slugs, array( $lang->slug ) );
88
89 if ( ! empty( $slugs ) ) {
90 $this->update_default_lang( reset( $slugs ) ); // arbitrary choice...
91 }
92 else {
93 unset( $this->options['default_lang'] );
94 }
95 }
96
97 // delete the translations
98 $this->update_translations( $lang->slug );
99
100 // delete language option in widgets
101 foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) {
102 if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) {
103 $obj = $widget['callback'][0];
104 $number = $widget['params'][0]['number'];
105 if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) {
106 $settings = $obj->get_settings();
107 if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $lang->slug ) {
108 unset( $settings[ $number ]['pll_lang'] );
109 $obj->save_settings( $settings );
110 }
111 }
112 }
113 }
114
115 // delete menus locations
116 if ( ! empty( $this->options['nav_menus'] ) ) {
117 foreach ( $this->options['nav_menus'] as $theme => $locations ) {
118 foreach ( $locations as $location => $languages ) {
119 unset( $this->options['nav_menus'][ $theme ][ $location ][ $lang->slug ] );
120 }
121 }
122 }
123
124 // delete users options
125 foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
126 delete_user_meta( $user_id, 'user_lang', $lang->locale );
127 delete_user_meta( $user_id, 'pll_filter_content', $lang->slug );
128 delete_user_meta( $user_id, 'description_'.$lang->slug );
129 }
130
131 // delete the string translations
132 $post = get_page_by_title( 'polylang_mo_' . $lang->term_id, OBJECT, 'polylang_mo' );
133 if ( ! empty( $post ) ) {
134 wp_delete_post( $post->ID );
135 }
136
137 // delete domain
138 unset( $this->options['domains'][ $lang->slug ] );
139
140 // delete the language itself
141 wp_delete_term( $lang->term_id, 'language' );
142 wp_delete_term( $lang->tl_term_id, 'term_language' );
143
144 // update languages list
145 $this->clean_languages_cache();
146
147 update_option( 'polylang', $this->options );
148 flush_rewrite_rules(); // refresh rewrite rules
149 add_settings_error( 'general', 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'updated' );
150 }
151
152 /*
153 * update language properties
154 *
155 * list of arguments that $args must contain:
156 * lang_id -> term_id of the language to modify
157 * name -> language name ( used only for display )
158 * slug -> language code ( ideally 2-letters ISO 639-1 language code
159 * locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
160 * rtl -> 1 if rtl language, 0 otherwise
161 * term_group -> language order when displayed
162 *
163 * @since 1.2
164 *
165 * @param array $args
166 * @return bool true if success / false if failed
167 */
168 public function update_language( $args ) {
169 $lang = $this->get_language( (int) $args['lang_id'] );
170 if ( ! $this->validate_lang( $args, $lang ) ) {
171 return false;
172 }
173
174 // Update links to this language in posts and terms in case the slug has been modified
175 $slug = $args['slug'];
176 $old_slug = $lang->slug;
177
178 if ( $old_slug != $slug ) {
179 // update the language slug in translations
180 $this->update_translations( $old_slug, $slug );
181
182 // update language option in widgets
183 foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) {
184 if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) {
185 $obj = $widget['callback'][0];
186 $number = $widget['params'][0]['number'];
187 if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) {
188 $settings = $obj->get_settings();
189 if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $old_slug ) {
190 $settings[ $number ]['pll_lang'] = $slug;
191 $obj->save_settings( $settings );
192 }
193 }
194 }
195 }
196
197 // update menus locations
198 if ( ! empty( $this->options['nav_menus'] ) ) {
199 foreach ( $this->options['nav_menus'] as $theme => $locations ) {
200 foreach ( $locations as $location => $languages ) {
201 if ( ! empty( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] ) ) {
202 $this->options['nav_menus'][ $theme ][ $location ][ $slug ] = $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ];
203 unset( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] );
204 }
205 }
206 }
207 }
208
209 // update domains
210 if ( ! empty( $this->options['domains'][ $old_slug ] ) ) {
211 $this->options['domains'][ $slug ] = $this->options['domains'][ $old_slug ];
212 unset( $this->options['domains'][ $old_slug ] );
213 }
214
215 // update the default language option if necessary
216 if ( $this->options['default_lang'] == $old_slug ) {
217 $this->options['default_lang'] = $slug;
218 }
219 }
220
221 update_option( 'polylang', $this->options );
222
223 // and finally update the language itself
224 $description = serialize( array( 'locale' => $args['locale'], 'rtl' => (int) $args['rtl'], 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'] ) );
225 wp_update_term( (int) $lang->term_id, 'language', array( 'slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group'] ) );
226 wp_update_term( (int) $lang->tl_term_id, 'term_language', array( 'slug' => 'pll_' . $slug, 'name' => $args['name'] ) );
227
228 $this->clean_languages_cache();
229 flush_rewrite_rules(); // refresh rewrite rules
230 add_settings_error( 'general', 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'updated' );
231 return true;
232 }
233
234 /*
235 * validates data entered when creating or updating a language
236 *
237 * @since 0.4
238 *
239 * @param array $args
240 * @param object $lang optional the language currently updated, the language is created if not set
241 * @return bool true if success / false if failed
242 * @see PLL_Admin_Model::add_language
243 */
244 protected function validate_lang( $args, $lang = null ) {
245 // validate locale with the same pattern as WP 4.3. See #28303
246 if ( ! preg_match( '#^[a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?$#', $args['locale'], $matches ) ) {
247 add_settings_error( 'general', 'pll_invalid_locale', __( 'Enter a valid WordPress locale', 'polylang' ) );
248 }
249
250 // validate slug characters
251 if ( ! preg_match( '#^[a-z_-]+$#', $args['slug'] ) ) {
252 add_settings_error( 'general', 'pll_invalid_slug', __( 'The language code contains invalid characters', 'polylang' ) );
253 }
254
255 // validate slug is unique
256 if ( $this->get_language( $args['slug'] ) && ( null === $lang || ( isset( $lang ) && $lang->slug != $args['slug'] ) ) ) {
257 add_settings_error( 'general', 'pll_non_unique_slug', __( 'The language code must be unique', 'polylang' ) );
258 }
259
260 // validate name
261 // no need to sanitize it as wp_insert_term will do it for us
262 if ( empty( $args['name'] ) ) {
263 add_settings_error( 'general', 'pll_invalid_name', __( 'The language must have a name', 'polylang' ) );
264 }
265
266 // validate flag
267 if ( ! empty( $args['flag'] ) && ! file_exists( POLYLANG_DIR . '/flags/' . $args['flag'] . '.png' ) ) {
268 add_settings_error( 'general', 'pll_invalid_flag', __( 'The flag does not exist', 'polylang' ) );
269 }
270
271 return get_settings_errors() ? false : true;
272 }
273
274 /*
275 * used to set the language of posts or terms in mass
276 *
277 * @since 1.2
278 *
279 * @param string $type either 'post' or 'term'
280 * @param array $ids array of post ids or term ids
281 * @param object|string $lang object or slug
282 */
283 public function set_language_in_mass( $type, $ids, $lang ) {
284 global $wpdb;
285
286 $ids = array_map( 'intval', $ids );
287 $lang = $this->get_language( $lang );
288 $tt_id = 'term' == $type ? $lang->tl_term_taxonomy_id : $lang->term_taxonomy_id;
289
290 foreach ( $ids as $id ) {
291 $values[] = $wpdb->prepare( '( %d, %d )', $id, $tt_id );
292 }
293
294 if ( ! empty( $values ) ) {
295 $values = array_unique( $values );
296 $wpdb->query( "INSERT INTO $wpdb->term_relationships ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $values ) );
297 $lang->update_count(); // updating term count is mandatory ( thanks to AndyDeGroo )
298 }
299
300 if ( 'term' == $type ) {
301 foreach ( $ids as $id ) {
302 $translations[] = array( $lang->slug => $id );
303 }
304
305 if ( ! empty( $translations ) ) {
306 $this->set_translation_in_mass( 'term', $translations );
307 }
308 }
309 }
310
311 /*
312 * used to create a translations groups in mass
313 *
314 * @since 1.6.3
315 *
316 * @param string $type either 'post' or 'term'
317 * @param array $translations array of translations arrays
318 */
319 public function set_translation_in_mass( $type, $translations ) {
320 global $wpdb;
321
322 foreach ( $translations as $t ) {
323 $term = uniqid( 'pll_' ); // the term name
324 $terms[] = $wpdb->prepare( '( "%1$s", "%1$s" )', $term );
325 $slugs[] = $wpdb->prepare( '"%s"', $term );
326 $description[ $term ] = serialize( $t );
327 $count[ $term ] = count( $t );
328 }
329
330 // insert terms
331 if ( ! empty( $terms ) ) {
332 $terms = array_unique( $terms );
333 $wpdb->query( "INSERT INTO $wpdb->terms ( slug, name ) VALUES " . implode( ',', $terms ) );
334 }
335
336 // get all terms with their term_id
337 $terms = $wpdb->get_results( "SELECT term_id, slug FROM $wpdb->terms WHERE slug IN ( " . implode( ',', $slugs ) . " )" );
338
339 // prepare terms taxonomy relationship
340 foreach ( $terms as $term ) {
341 $tts[] = $wpdb->prepare( '( %d, "%s", "%s", %d )', $term->term_id, $type . '_translations', $description[ $term->slug ], $count[ $term->slug ] );
342 }
343
344 // insert term_taxonomy
345 if ( ! empty( $tts ) ) {
346 $tts = array_unique( $tts );
347 $wpdb->query( "INSERT INTO $wpdb->term_taxonomy ( term_id, taxonomy, description, count ) VALUES " . implode( ',', $tts ) );
348 }
349
350 // get all terms with term_taxonomy_id
351 $terms = get_terms( $type . '_translations', array( 'hide_empty' => false ) );
352
353 // prepare objects relationships
354 foreach ( $terms as $term ) {
355 $t = unserialize( $term->description );
356 if ( in_array( $t, $translations ) ) {
357 foreach ( $t as $object_id ) {
358 if ( ! empty( $object_id ) ) {
359 $trs[] = $wpdb->prepare( '( %d, %d )', $object_id, $term->term_taxonomy_id );
360 }
361 }
362 }
363 }
364
365 // insert term_relationships
366 if ( ! empty( $trs ) ) {
367 $wpdb->query( "INSERT INTO $wpdb->term_relationships ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $trs ) );
368 $trs = array_unique( $trs );
369 }
370 }
371
372 /*
373 * returns unstranslated posts and terms ids ( used in settings )
374 *
375 * @since 0.9
376 *
377 * @return array array made of an array of post ids and an array of term ids
378 */
379 public function get_objects_with_no_lang() {
380 $posts = get_posts( array(
381 'numberposts' => -1,
382 'nopaging' => true,
383 'post_type' => $this->get_translated_post_types(),
384 'post_status' => 'any',
385 'fields' => 'ids',
386 'tax_query' => array( array(
387 'taxonomy' => 'language',
388 'terms' => $this->get_languages_list( array( 'fields' => 'term_id' ) ),
389 'operator' => 'NOT IN',
390 ) )
391 ) );
392
393 $terms = get_terms( $this->get_translated_taxonomies(), array( 'get' => 'all', 'fields' => 'ids' ) );
394 $groups = $this->get_languages_list( array( 'fields' => 'tl_term_id' ) );
395 $tr_terms = get_objects_in_term( $groups, 'term_language' );
396 $terms = array_unique( array_diff( $terms, $tr_terms ) ); // array_unique to avoid duplicates if a term is in more than one taxonomy
397 $terms = array_map( 'intval', $terms );
398
399 return apply_filters( 'pll_get_objects_with_no_lang', empty( $posts ) && empty( $terms ) ? false : array( 'posts' => $posts, 'terms' => $terms ) );
400 }
401
402 /*
403 * used to delete translations or update the translations when a language slug has been modified in settings
404 *
405 * @since 0.5
406 *
407 * @param string $old_slug the old language slug
408 * @param string $new_slug optional, the new language slug, if not set it means the correspondant has been deleted
409 */
410 public function update_translations( $old_slug, $new_slug = '' ) {
411 global $wpdb;
412
413 $terms = get_terms( array( 'post_translations', 'term_translations' ) );
414
415 foreach ( $terms as $term ) {
416 $tr = unserialize( $term->description );
417 if ( ! empty( $tr[ $old_slug ] ) ) {
418 if ( $new_slug ) {
419 $tr[ $new_slug ] = $tr[ $old_slug ]; // suppress this for delete
420 }
421 else {
422 $dr['id'][] = (int) $tr[ $old_slug ];
423 $dr['tt'][] = (int) $term->term_taxonomy_id;
424 }
425 unset( $tr[ $old_slug ] );
426
427 if ( empty( $tr ) || 1 == count( $tr ) ) {
428 $dt['t'][] = (int) $term->term_id;
429 $dt['tt'][] = (int) $term->term_taxonomy_id;
430 }
431 else {
432 $ut['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $term->term_id, serialize( $tr ) );
433 $ut['in'][] = (int) $term->term_id;
434 }
435 }
436 }
437
438 // delete relationships
439 if ( ! empty( $dr ) ) {
440 $wpdb->query( "
441 DELETE FROM $wpdb->term_relationships
442 WHERE object_id IN ( " . implode( ',', $dr['id'] ) . " )
443 AND term_taxonomy_id IN ( " . implode( ',', $dr['tt'] ) . " )
444 " );
445 }
446
447 // delete terms
448 if ( ! empty( $dt ) ) {
449 $wpdb->query( "DELETE FROM $wpdb->terms WHERE term_id IN ( " . implode( ',', $dt['t'] ) . " ) " );
450 $wpdb->query( "DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( " . implode( ',', $dt['tt'] ) . " ) " );
451 }
452
453 // update terms
454 if ( ! empty( $ut ) ) {
455 $wpdb->query( "
456 UPDATE $wpdb->term_taxonomy
457 SET description = ( CASE term_id " . implode( ' ', $ut['case'] ) . " END )
458 WHERE term_id IN ( " . implode( ',', $ut['in'] ) . " )
459 " );
460 }
461 }
462
463 /*
464 * updates the default language
465 * taking care to update the default category & the nav menu locations
466 *
467 * @since 1.8
468 *
469 * @param string $slug new language slug
470 */
471 public function update_default_lang( $slug ) {
472 // the nav menus stored in theme locations should be in the default language
473 $theme = get_stylesheet();
474 if ( ! empty( $this->options['nav_menus'][ $theme ] ) ) {
475 foreach ( $this->options['nav_menus'][ $theme ] as $key => $loc ) {
476 $menus[ $key ] = empty( $loc[ $slug ] ) ? 0 : $loc[ $slug ];
477 }
478 set_theme_mod( 'nav_menu_locations', $menus );
479 }
480
481 // the default category should be in the default language
482 $default_cats = $this->term->get_translations( get_option( 'default_category' ) );
483 if ( isset( $default_cats[ $slug ] ) ) {
484 update_option( 'default_category', $default_cats[ $slug ] );
485 }
486
487 // update options
488 $this->options['default_lang'] = $slug;
489 update_option( 'polylang', $this->options );
490
491 $this->clean_languages_cache();
492 flush_rewrite_rules();
493 }
494 }
495