PluginProbe
Polylang / 2.0.5
Polylang v2.0.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 / admin / admin-model.php

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

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