[ Index ] |
PHP Cross Reference of GlotPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Things: GP_Translation_Set class 4 * 5 * @package GlotPress 6 * @subpackage Things 7 * @since 1.0.0 8 */ 9 10 /** 11 * Core class used to implement the translation sets. 12 * 13 * @since 1.0.0 14 */ 15 class GP_Translation_Set extends GP_Thing { 16 17 var $table_basename = 'gp_translation_sets'; 18 var $field_names = array( 'id', 'name', 'slug', 'project_id', 'locale' ); 19 var $non_db_field_names = array( 'current_count', 'untranslated_count', 'waiting_count', 'fuzzy_count', 'all_count', 'warnings_count', 'percent_translated', 'wp_locale', 'last_modified' ); 20 var $int_fields = array( 'id', 'project_id' ); 21 var $non_updatable_attributes = array( 'id' ); 22 23 /** 24 * ID of the translation set. 25 * 26 * @var int 27 */ 28 public $id; 29 30 /** 31 * Name of the translation set. 32 * 33 * @var string 34 */ 35 public $name; 36 37 /** 38 * Slug of the translation set. 39 * 40 * @var string 41 */ 42 public $slug; 43 44 /** 45 * Project ID of the translation set. 46 * 47 * @var int 48 */ 49 public $project_id; 50 51 /** 52 * Locale of the translation set. 53 * 54 * @var string 55 */ 56 public $locale; 57 58 /** 59 * GP project of the translation set. 60 * 61 * @var GP_Project 62 */ 63 public $project; 64 65 /** 66 * Number of waiting translations. 67 * 68 * @var int 69 */ 70 public $waiting_count; 71 72 /** 73 * Number of fuzzy translations. 74 * 75 * @var int 76 */ 77 public $fuzzy_count; 78 79 /** 80 * Number of untranslated originals. 81 * 82 * @var int 83 */ 84 public $untranslated_count; 85 86 /** 87 * Number of current translations. 88 * 89 * @var int 90 */ 91 public $current_count; 92 93 /** 94 * Number of translations with warnings. 95 * 96 * @var int 97 */ 98 public $warnings_count; 99 100 /** 101 * Number of all originals. 102 * 103 * @var int 104 */ 105 public $all_count; 106 107 /** 108 * Sets restriction rules for fields. 109 * 110 * @since 1.0.0 111 * 112 * @param GP_Validation_Rules $rules The validation rules instance. 113 */ 114 public function restrict_fields( $rules ) { 115 $rules->name_should_not_be( 'empty' ); 116 $rules->slug_should_not_be( 'empty' ); 117 $rules->locale_should_not_be( 'empty' ); 118 $rules->project_id_should_not_be( 'empty' ); 119 } 120 121 /** 122 * Normalizes an array with key-value pairs representing 123 * a GP_Translation_Set object. 124 * 125 * @since 1.0.0 126 * 127 * @param array $args Arguments for a GP_Translation_Set object. 128 * @return array Normalized arguments for a GP_Translation_Set object. 129 */ 130 public function normalize_fields( $args ) { 131 $args = parent::normalize_fields( $args ); 132 133 if ( isset( $args['name'] ) && empty( $args['name'] ) ) { 134 if ( isset( $args['locale'] ) && ! empty( $args['locale'] ) ) { 135 $locale = GP_locales::by_slug( $args['locale'] ); 136 $args['name'] = $locale->english_name; 137 } 138 } 139 140 if ( isset( $args['slug'] ) && ! $args['slug'] ) { 141 $args['slug'] = 'default'; 142 } 143 144 if ( ! empty( $args['slug'] ) ) { 145 $args['slug'] = gp_sanitize_slug( $args['slug'] ); 146 } 147 148 return $args; 149 } 150 151 /** 152 * Returns the English name of a locale. 153 * 154 * If the slug of the locale is not 'default' then the name of the 155 * current translation sets gets added as a suffix. 156 * 157 * @since 1.0.0 158 * 159 * @param string $separator Separator, in case the slug is not 'default'. Default: '→'. 160 * @return string The English name of a locale. 161 */ 162 public function name_with_locale( $separator = '→' ) { 163 $locale = GP_Locales::by_slug( $this->locale ); 164 $parts = array( $locale->english_name ); 165 166 if ( 'default' !== $this->slug ) { 167 $parts[] = $this->name; 168 } 169 170 return implode( ' ' . $separator . ' ', $parts ); 171 } 172 173 public function by_project_id_slug_and_locale( $project_id, $slug, $locale_slug ) { 174 $result = $this->one( 175 "SELECT * FROM $this->table 176 WHERE slug = %s AND project_id= %d AND locale = %s", 177 $slug, 178 $project_id, 179 $locale_slug 180 ); 181 182 if ( ! $result && 0 === $project_id ) { 183 $result = $this->create( 184 array( 185 'project_id' => $project_id, 186 'name' => GP_Locales::by_slug( $locale_slug )->english_name, 187 'slug' => $slug, 188 'locale' => $locale_slug, 189 ) 190 ); 191 } 192 193 return $result; 194 } 195 196 public function by_locale( $locale_slug ) { 197 return $this->many( 198 "SELECT * FROM $this->table 199 WHERE locale = %s", 200 $locale_slug 201 ); 202 } 203 204 public function existing_locales() { 205 global $wpdb; 206 207 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared 208 return $wpdb->get_col( "SELECT DISTINCT(locale) FROM $this->table" ); 209 } 210 211 public function existing_slugs() { 212 global $wpdb; 213 214 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared 215 return $wpdb->get_col( "SELECT DISTINCT(slug) FROM $this->table" ); 216 } 217 218 public function by_project_id( $project_id ) { 219 return $this->many( 220 "SELECT * FROM $this->table 221 WHERE project_id = %d ORDER BY name ASC", 222 $project_id 223 ); 224 } 225 226 /** 227 * Import translations from a Translations object. 228 * 229 * @param Translations $translations the translations to be imported to this translation-set. 230 * @param string $desired_status 'current', 'waiting' or 'fuzzy'. 231 * @return boolean or void 232 */ 233 public function import( $translations, $desired_status = 'current' ) { 234 wp_raise_memory_limit( 'gp_translations_import' ); 235 236 if ( ! isset( $this->project ) || ! $this->project ) { 237 $this->project = GP::$project->get( $this->project_id ); 238 } 239 240 // Fuzzy is also checked in the flags, but if all strings in an import are fuzzy, fuzzy status can be forced. 241 if ( ! in_array( $desired_status, array( 'current', 'waiting', 'fuzzy' ), true ) ) { 242 return false; 243 } 244 245 $locale = GP_Locales::by_slug( $this->locale ); 246 $user = wp_get_current_user(); 247 248 $existing_translations = array(); 249 250 $current_translations_list = GP::$translation->for_translation( 251 $this->project, 252 $this, 253 'no-limit', 254 array( 255 'status' => 'current', 256 ) 257 ); 258 $existing_translations['current'] = new Translations(); 259 foreach ( $current_translations_list as $entry ) { 260 $existing_translations['current']->add_entry( $entry ); 261 } 262 unset( $current_translations_list ); 263 264 $translations_added = 0; 265 foreach ( $translations->entries as $entry ) { 266 if ( empty( $entry->translations ) ) { 267 continue; 268 } 269 270 $is_fuzzy = in_array( 'fuzzy', $entry->flags, true ); 271 272 /** 273 * Filter whether to import fuzzy translations. 274 * 275 * @since 1.0.0 276 * 277 * @param bool $import_over Import fuzzy translation. Default true. 278 * @param Translation_Entry $entry Translation entry object to import. 279 * @param Translations $translations Translations collection. 280 */ 281 if ( $is_fuzzy && ! apply_filters( 'gp_translation_set_import_fuzzy_translations', true, $entry, $translations ) ) { 282 continue; 283 } 284 285 /** 286 * Filters the the status of imported translations of a translation set. 287 * 288 * @since 1.0.0 289 * @since 2.3.0 Added `$new_translation` and `$old_translation` parameters. 290 * 291 * @param string $status The status of imported translations. 292 * @param Translation_Entry $new_translation Translation entry object to import. 293 * @param GP_Translation|null $old_translation The previous translation. 294 */ 295 $entry->status = apply_filters( 'gp_translation_set_import_status', $is_fuzzy ? 'fuzzy' : $desired_status, $entry, null ); 296 297 $entry->warnings = maybe_unserialize( GP::$translation_warnings->check( $entry->singular, $entry->plural, $entry->translations, $locale ) ); 298 if ( ! empty( $entry->warnings ) && 'current' === $entry->status ) { 299 $entry->status = 'waiting'; 300 } 301 302 // Lazy load other entries. 303 if ( ! isset( $existing_translations[ $entry->status ] ) ) { 304 $existing_translations_list = GP::$translation->for_translation( 305 $this->project, 306 $this, 307 'no-limit', 308 array( 309 'status' => $entry->status, 310 ) 311 ); 312 $existing_translations[ $entry->status ] = new Translations(); 313 foreach ( $existing_translations_list as $_entry ) { 314 $existing_translations[ $entry->status ]->add_entry( $_entry ); 315 } 316 unset( $existing_translations_list ); 317 } 318 319 $create = false; 320 $translated = $existing_translations[ $entry->status ]->translate_entry( $entry ); 321 if ( 'current' !== $entry->status && ! $translated ) { 322 // Don't create an entry if it already exists as current. 323 $translated = $existing_translations['current']->translate_entry( $entry ); 324 } 325 326 if ( $translated ) { 327 // We have the same string translated, so create a new one if they don't match. 328 $entry->original_id = $translated->original_id; 329 $translated_is_different = array_pad( $entry->translations, $locale->nplurals, null ) !== $translated->translations; 330 331 /** 332 * Filter whether to import over an existing translation on a translation set. 333 * 334 * @since 1.0.0 335 * 336 * @param bool $import_over Import over an existing translation. 337 */ 338 $create = apply_filters( 'gp_translation_set_import_over_existing', $translated_is_different ); 339 } else { 340 // we don't have the string translated, let's see if the original is there 341 $original = GP::$original->by_project_id_and_entry( $this->project->id, $entry, '+active' ); 342 if ( $original ) { 343 $entry->original_id = $original->id; 344 $create = true; 345 } 346 } 347 if ( $create ) { 348 if ( $user ) { 349 $entry->user_id = $user->ID; 350 } 351 352 $entry->translation_set_id = $this->id; 353 354 $entry->status = apply_filters( 'gp_translation_set_import_status', $entry->status, $entry, $translated ); 355 // Check for errors. 356 $translation = GP::$translation->create( $entry ); 357 if ( is_object( $translation ) ) { 358 $translation->set_status( $entry->status ); 359 $translations_added += 1; 360 } 361 } 362 } 363 364 gp_clean_translation_set_cache( $this->id ); 365 366 /** 367 * Fires after translations have been imported to a translation set. 368 * 369 * @since 1.0.0 370 * 371 * @param int $translation_set The ID of the translation set the import was made into. 372 */ 373 do_action( 'gp_translations_imported', $this->id ); 374 375 return $translations_added; 376 } 377 378 /** 379 * Retrieves the number of waiting translations. 380 * 381 * @return int Number of waiting translations. 382 */ 383 public function waiting_count() { 384 if ( ! isset( $this->waiting_count ) ) { 385 $this->update_status_breakdown(); 386 } 387 388 return $this->waiting_count; 389 } 390 391 /** 392 * Retrieves the number of untranslated originals. 393 * 394 * @return int Number of untranslated originals. 395 */ 396 public function untranslated_count() { 397 if ( ! isset( $this->untranslated_count ) ) { 398 $this->update_status_breakdown(); 399 } 400 401 return $this->untranslated_count; 402 } 403 404 /** 405 * Retrieves the number of fuzzy translations. 406 * 407 * @return int Number of fuzzy translations. 408 */ 409 public function fuzzy_count() { 410 if ( ! isset( $this->fuzzy_count ) ) { 411 $this->update_status_breakdown(); 412 } 413 414 return $this->fuzzy_count; 415 } 416 417 /** 418 * Retrieves the number of current translations. 419 * 420 * @return int Number of current translations. 421 */ 422 public function current_count() { 423 if ( ! isset( $this->current_count ) ) { 424 $this->update_status_breakdown(); 425 } 426 427 return $this->current_count; 428 } 429 430 /** 431 * Retrieves the number of translations with warnings. 432 * 433 * @return int Number of translations with warnings. 434 */ 435 public function warnings_count() { 436 if ( ! isset( $this->warnings_count ) ) { 437 $this->update_status_breakdown(); 438 } 439 440 return $this->warnings_count; 441 } 442 443 /** 444 * Retrieves the number of all originals. 445 * 446 * @return int Number of all originals. 447 */ 448 public function all_count() { 449 if ( ! isset( $this->all_count ) ) { 450 $this->update_status_breakdown(); 451 } 452 453 return $this->all_count; 454 } 455 456 /** 457 * Populates the count properties. 458 */ 459 public function update_status_breakdown() { 460 $counts = wp_cache_get( $this->id, 'translation_set_status_breakdown' ); 461 462 /* 463 * The format was changed in 2.1 and 3.0. 464 * 465 * In 2.1 the format was changed to an array of objects in the following sequence, 466 * however not all may exist in the array, so for example, the array may only be 3 467 * entries long and skip any of the values: 468 * 469 * [0] = Current translations 470 * [1] = Fuzzy translations 471 * [2] = Rejected translations 472 * [3] = Old translations 473 * [4] = Translations with warnings 474 * [5] = All translations 475 * 476 * In 3.0 the untranslated object was added: 477 * 478 * [0] = Current translations 479 * [1] = Fuzzy translations 480 * [2] = Rejected translations 481 * [3] = Old translations 482 * [4] = Translations with warnings 483 * [5] = Untranslated originals 484 * [6] = All translations 485 * 486 * Version 3.0 also introduced the cache 'version' array entry to allow for easy 487 * detection of when the cache should be expired due to changes in the cache. 488 * 489 * Note: The _version key is unset after the cache is loaded so that it does not 490 * get used in the for loops when setting the object properties. 491 * 492 */ 493 if ( ! is_array( $counts ) || ! array_key_exists( '_version', $counts ) || GP_CACHE_VERSION !== $counts['_version'] ) { 494 global $wpdb; 495 $counts = array(); 496 497 $status_counts = $wpdb->get_results( 498 $wpdb->prepare( 499 "SELECT 500 t.status AS translation_status, 501 COUNT(*) AS total, 502 COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`, 503 COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public` 504 FROM {$wpdb->gp_translations} AS t 505 INNER JOIN {$wpdb->gp_originals} AS o ON t.original_id = o.id 506 WHERE 507 t.translation_set_id = %d 508 AND o.status = '+active' 509 GROUP BY t.status", 510 $this->id 511 ) 512 ); 513 514 if ( $status_counts ) { 515 $counts = $status_counts; 516 } 517 518 $warnings_counts = $wpdb->get_row( 519 $wpdb->prepare( 520 "SELECT 521 COUNT(*) AS total, 522 COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`, 523 COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public` 524 FROM {$wpdb->gp_translations} AS t 525 INNER JOIN {$wpdb->gp_originals} AS o ON t.original_id = o.id 526 WHERE 527 t.translation_set_id = %d AND 528 o.status = '+active' AND 529 ( t.status = 'current' OR t.status = 'waiting' ) 530 AND warnings IS NOT NULL 531 AND warnings != ''", 532 $this->id 533 ) 534 ); 535 536 if ( $warnings_counts ) { 537 $counts[] = (object) array( 538 'translation_status' => 'warnings', 539 'total' => (int) $warnings_counts->total, 540 'hidden' => (int) $warnings_counts->hidden, 541 'public' => (int) $warnings_counts->public, 542 ); 543 } 544 545 $untranslated_counts = $wpdb->get_row( 546 $wpdb->prepare( 547 "SELECT 548 COUNT(*) AS total, 549 COUNT( CASE WHEN o.priority = '-2' THEN o.priority END ) AS `hidden`, 550 COUNT( CASE WHEN o.priority <> '-2' THEN o.priority END ) AS `public` 551 FROM {$wpdb->gp_originals} AS o 552 LEFT JOIN {$wpdb->gp_translations} AS t ON o.id = t.original_id AND t.translation_set_id = %d AND t.status != 'rejected' AND t.status != 'old' 553 WHERE 554 o.project_id = %d AND 555 o.status = '+active' AND 556 t.translation_0 IS NULL", 557 $this->id, 558 $this->project_id 559 ) 560 ); 561 562 if ( $untranslated_counts ) { 563 $counts[] = (object) array( 564 'translation_status' => 'untranslated', 565 'total' => (int) $untranslated_counts->total, 566 'public' => (int) $untranslated_counts->public, 567 'hidden' => (int) $untranslated_counts->hidden, 568 ); 569 } 570 571 $counts['_version'] = GP_CACHE_VERSION; 572 573 wp_cache_set( $this->id, $counts, 'translation_set_status_breakdown' ); 574 } 575 576 if ( array_key_exists( '_version', $counts ) ) { 577 unset( $counts['_version'] ); 578 } 579 580 $all_count = GP::$original->count_by_project_id( $this->project_id, 'all' ); 581 $counts[] = (object) array( 582 'translation_status' => 'all', 583 'total' => $all_count->total, 584 'hidden' => $all_count->hidden, 585 'public' => $all_count->public, 586 ); 587 588 $statuses = GP::$translation->get_static( 'statuses' ); 589 $statuses[] = 'warnings'; 590 $statuses[] = 'untranslated'; 591 $statuses[] = 'all'; 592 foreach ( $statuses as $status ) { 593 $this->{$status . '_count'} = 0; 594 } 595 596 $user_can_view_hidden = GP::$permission->current_user_can( 'write', 'project', $this->project_id ); 597 foreach ( $counts as $count ) { 598 if ( in_array( $count->translation_status, $statuses, true ) ) { 599 $this->{$count->translation_status . '_count'} = $user_can_view_hidden ? (int) $count->total : (int) $count->public; 600 } 601 } 602 } 603 604 /** 605 * Copies translations from a translation set to the current one 606 * 607 * This function doesn't merge then, just copies unconditionally. If a translation already exists, it will be duplicated. 608 * When copying translations from another project, it will search to find the original first. 609 */ 610 public function copy_translations_from( $source_translation_set_id ) { 611 global $wpdb; 612 $current_date = $this->now_in_mysql_format(); 613 614 $source_set = GP::$translation_set->get( $source_translation_set_id ); 615 if ( $source_set->project_id != $this->project_id ) { 616 $translations = GP::$translation->find_many_no_map( "translation_set_id = '{$source_set->id}'" ); 617 foreach ( $translations as $entry ) { 618 $source_original = GP::$original->get( $entry->original_id ); 619 $original = GP::$original->by_project_id_and_entry( $this->project_id, $source_original ); 620 if ( $original ) { 621 $entry->original_id = $original->id; 622 $entry->translation_set_id = $this->id; 623 GP::$translation->create( $entry ); 624 } 625 } 626 } else { 627 return $this->query( 628 "INSERT INTO $wpdb->gp_translations ( 629 original_id, translation_set_id, translation_0, translation_1, translation_2, user_id, status, date_added, date_modified, warnings 630 ) 631 SELECT 632 original_id, %s AS translation_set_id, translation_0, translation_1, translation_2, user_id, status, date_added, %s AS date_modified, warnings 633 FROM $wpdb->gp_translations WHERE translation_set_id = %s", 634 $this->id, 635 $current_date, 636 $source_translation_set_id 637 ); 638 } 639 } 640 641 642 public function percent_translated() { 643 $original_counts = GP::$original->count_by_project_id( $this->project_id, 'all' ); 644 645 if ( GP::$permission->current_user_can( 'write', 'project', $this->project_id ) ) { 646 $original_count = $original_counts->total; 647 } else { 648 $original_count = $original_counts->public; 649 } 650 651 return $original_count ? floor( $this->current_count() / $original_count * 100 ) : 0; 652 } 653 654 /** 655 * Retrieves the last modified date of a translation in this translation set. 656 * 657 * @since 1.0.0 658 * 659 * @return string|false The last modified date on success, false on failure. 660 */ 661 public function last_modified() { 662 return GP::$translation->last_modified( $this ); 663 } 664 665 /** 666 * Deletes a translation set and all of its translations and glossaries. 667 * 668 * @since 2.0.0 669 * 670 * @return bool 671 */ 672 public function delete() { 673 GP::$translation->delete_many( array( 'translation_set_id' => $this->id ) ); 674 675 GP::$glossary->delete_many( array( 'translation_set_id', $this->id ) ); 676 677 return parent::delete(); 678 } 679 680 /** 681 * Executes after creating a translation set. 682 * 683 * @since 3.0.0 684 * 685 * @return bool 686 */ 687 public function after_create() { 688 /** 689 * Fires after creating a translation set. 690 * 691 * @since 3.0.0 692 * 693 * @param GP_Translation_Set $translation_set The translation set that was created. 694 */ 695 do_action( 'gp_translation_set_created', $this ); 696 697 return true; 698 } 699 700 /** 701 * Executes after saving a translation set. 702 * 703 * @since 3.0.0 704 * 705 * @param GP_Translation_Set $translation_set_before Translation set before the update. 706 * @return bool 707 */ 708 public function after_save( $translation_set_before ) { 709 /** 710 * Fires after saving a translation set. 711 * 712 * @since 3.0.0 713 * 714 * @param GP_Translation_Set $translation_set Translation set following the update. 715 * @param GP_Translation_Set $translation_set_before Translation set before the update. 716 */ 717 do_action( 'gp_translation_set_saved', $this, $translation_set_before ); 718 719 return true; 720 } 721 722 /** 723 * Executes after deleting a translation set. 724 * 725 * @since 3.0.0 726 * 727 * @return bool 728 */ 729 public function after_delete() { 730 /** 731 * Fires after deleting a translation set. 732 * 733 * @since 3.0.0 734 * 735 * @param GP_Translation_Set $translation_set The translation set that was deleted. 736 */ 737 do_action( 'gp_translation_set_deleted', $this ); 738 739 return true; 740 } 741 } 742 GP::$translation_set = new GP_Translation_Set();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sat Nov 23 01:01:06 2024 | Cross-referenced by PHPXref 0.7.1 |