Changeset 65226
- Timestamp:
- 11/03/2026 01:38:37 PM (9 days ago)
- Location:
- trunk
- Files:
-
- 2 added
- 6 edited
-
src/wp-admin/includes/class-language-pack-upgrader.php (modified) (4 diffs)
-
src/wp-admin/includes/class-wp-automatic-updater.php (modified) (1 diff)
-
src/wp-admin/includes/update.php (modified) (1 diff)
-
src/wp-admin/update-core.php (modified) (6 diffs)
-
src/wp-includes/update.php (modified) (1 diff)
-
tests/phpunit/tests/admin/includesUpdate.php (added)
-
tests/phpunit/tests/admin/languagePackUpgrader.php (added)
-
tests/phpunit/tests/admin/wpAutomaticUpdater.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/includes/class-language-pack-upgrader.php
r64435 r65226 69 69 } 70 70 71 $deferred_updates = wp_get_deferred_translation_updates( $language_updates ); 72 71 73 foreach ( $language_updates as $key => $language_update ) { 72 74 $update = ! empty( $language_update->autoupdate ); 75 76 if ( $update && wp_is_translation_update_deferred( $language_update, $deferred_updates ) ) { 77 $update = false; 78 } 73 79 74 80 /** … … 231 237 232 238 $language_updates_results = array(); 239 $successful_update_ids = array(); 233 240 234 241 foreach ( $language_updates as $language_update ) { … … 267 274 } 268 275 276 if ( is_array( $result ) ) { 277 $successful_update_ids[ wp_get_translation_update_id( $language_update ) ] = true; 278 } 279 269 280 $language_updates_results[] = array( 270 281 'language' => $language_update->language, … … 273 284 'version' => $language_update->version, 274 285 ); 286 } 287 288 if ( $successful_update_ids ) { 289 $deferred_updates = wp_get_deferred_translation_updates(); 290 $remaining = array_diff_key( $deferred_updates, $successful_update_ids ); 291 292 if ( $remaining !== $deferred_updates ) { 293 wp_set_deferred_translation_updates( $remaining ); 294 } 275 295 } 276 296 -
trunk/src/wp-admin/includes/class-wp-automatic-updater.php
r64582 r65226 313 313 } else { 314 314 $update = ! empty( $item->autoupdate ); 315 316 if ( $update && wp_is_translation_update_deferred( $item ) ) { 317 $update = false; 318 } 319 315 320 } 316 321 -
trunk/src/wp-admin/includes/update.php
r64586 r65226 643 643 644 644 /** 645 * Gets plugin names for translation updates. 646 * 647 * @since 7.2.0 648 * 649 * @return string[] Plugin names keyed by plugin file, directory, basename, and slug. 650 */ 651 function wp_get_translation_update_plugin_names() { 652 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 653 654 $plugin_names = array(); 655 $all_plugins = get_plugins(); 656 $plugin_update = get_site_transient( 'update_plugins' ); 657 658 if ( is_object( $plugin_update ) ) { 659 $plugin_updates = array(); 660 661 if ( ! empty( $plugin_update->response ) && is_array( $plugin_update->response ) ) { 662 $plugin_updates = array_merge( $plugin_updates, $plugin_update->response ); 663 } 664 665 if ( ! empty( $plugin_update->no_update ) && is_array( $plugin_update->no_update ) ) { 666 $plugin_updates = array_merge( $plugin_updates, $plugin_update->no_update ); 667 } 668 669 foreach ( $plugin_updates as $plugin_file => $plugin_data ) { 670 $plugin_data = (object) $plugin_data; 671 672 if ( ! empty( $plugin_data->slug ) && ! empty( $all_plugins[ $plugin_file ]['Name'] ) ) { 673 $plugin_names[ $plugin_data->slug ] = $all_plugins[ $plugin_file ]['Name']; 674 } 675 } 676 } 677 678 foreach ( $all_plugins as $plugin_file => $plugin_data ) { 679 $plugin_basename = dirname( $plugin_file ); 680 681 if ( isset( $plugin_data['Name'] ) ) { 682 $plugin_names[ $plugin_file ] = $plugin_data['Name']; 683 $plugin_names[ basename( $plugin_file, '.php' ) ] = $plugin_data['Name']; 684 685 if ( '.' !== $plugin_basename ) { 686 $plugin_names[ $plugin_basename ] = $plugin_data['Name']; 687 } 688 } 689 } 690 691 return $plugin_names; 692 } 693 694 /** 695 * Gets the display name for a translation update. 696 * 697 * @since 7.2.0 698 * 699 * @param object $update Translation update object. 700 * @param string[]|null $plugin_names Optional. Plugin names keyed by plugin file, directory, 701 * basename, and slug. Default null. 702 * @return string The translation update name. 703 */ 704 function wp_get_translation_update_name( $update, $plugin_names = null ) { 705 $type = isset( $update->type ) ? $update->type : ''; 706 $slug = isset( $update->slug ) ? $update->slug : ''; 707 708 switch ( $type ) { 709 case 'core': 710 return 'WordPress'; // Not translated. 711 712 case 'theme': 713 $theme = wp_get_theme( $slug ); 714 if ( $theme->exists() ) { 715 return $theme->get( 'Name' ); 716 } 717 break; 718 719 case 'plugin': 720 if ( null === $plugin_names ) { 721 $plugin_names = wp_get_translation_update_plugin_names(); 722 } 723 724 if ( ! empty( $plugin_names[ $slug ] ) ) { 725 return $plugin_names[ $slug ]; 726 } 727 break; 728 } 729 730 return $slug; 731 } 732 733 /** 734 * Gets the display language for a translation update. 735 * 736 * @since 7.2.0 737 * 738 * @param string $locale Translation update locale. 739 * @return string The translation update language. 740 */ 741 function wp_get_translation_update_language( $locale ) { 742 $translations = get_site_transient( 'available_translations' ); 743 744 if ( is_array( $translations ) && ! empty( $translations[ $locale ]['native_name'] ) ) { 745 return sprintf( 746 /* translators: 1: Native language name, 2: Locale. */ 747 __( '%1$s (%2$s)' ), 748 $translations[ $locale ]['native_name'], 749 $locale 750 ); 751 } 752 753 return $locale; 754 } 755 756 /** 757 * Gets display data for available translation updates. 758 * 759 * @since 7.2.0 760 * 761 * @return array[] { 762 * An array of translation update display data. 763 * 764 * @type bool $checked Whether the translation update is selected for installation. 765 * @type bool $deferred Whether the translation update has been deferred. 766 * @type string $id Translation update identifier. 767 * @type string $language Translation update language. 768 * @type string $language_code Translation update locale. 769 * @type string $name Translation update name. 770 * @type string $slug Translation update slug. 771 * @type string $type Translation update type. 772 * @type string $version Translation update version. 773 * } 774 */ 775 function wp_get_translation_update_data() { 776 $available_updates = wp_get_translation_updates(); 777 $deferred_translation_updates = wp_get_deferred_translation_updates( $available_updates ); 778 $plugin_names = null; 779 $translation_updates = array(); 780 781 foreach ( $available_updates as $update ) { 782 $translation_update_id = wp_get_translation_update_id( $update ); 783 $language = isset( $update->language ) ? $update->language : ''; 784 $type = isset( $update->type ) ? $update->type : ''; 785 $deferred = isset( $deferred_translation_updates[ $translation_update_id ] ); 786 787 if ( 'plugin' === $type && null === $plugin_names ) { 788 $plugin_names = wp_get_translation_update_plugin_names(); 789 } 790 791 $translation_updates[] = array( 792 'checked' => ! $deferred, 793 'deferred' => $deferred, 794 'id' => $translation_update_id, 795 'language' => wp_get_translation_update_language( $language ), 796 'language_code' => $language, 797 'name' => wp_get_translation_update_name( $update, $plugin_names ), 798 'slug' => isset( $update->slug ) ? $update->slug : '', 799 'type' => $type, 800 'version' => isset( $update->version ) ? $update->version : '', 801 ); 802 } 803 804 return $translation_updates; 805 } 806 807 /** 808 * Processes a translation update selection and stores the deferred updates. 809 * 810 * Only currently available updates included in the submitted form can be selected 811 * or deferred. Previously deferred updates outside the form remain deferred, while 812 * newly available updates outside the form are left unchanged. 813 * 814 * The caller must check capabilities and the nonce before calling this function. 815 * 816 * @since 7.2.0 817 * 818 * @param string[] $translation_update_ids Sanitized identifiers of updates included in the form. 819 * @param string[] $selected_translation_update_ids Sanitized identifiers of selected updates. 820 * @return object[]|false Selected current translation updates keyed by identifier, an empty array 821 * if all updates in the form were deferred, or false if the form contains 822 * no currently available updates. On false, stored deferrals are unchanged. 823 */ 824 function wp_process_translation_update_selection( $translation_update_ids, $selected_translation_update_ids ) { 825 if ( empty( $translation_update_ids ) ) { 826 return false; 827 } 828 829 $current_translation_updates = wp_get_translation_updates_by_id(); 830 $translation_updates = array_intersect_key( $current_translation_updates, array_flip( $translation_update_ids ) ); 831 832 if ( empty( $translation_updates ) ) { 833 return false; 834 } 835 836 $selected_translation_updates = array_intersect_key( $translation_updates, array_flip( $selected_translation_update_ids ) ); 837 $deferred_translation_updates = array_intersect_key( 838 $current_translation_updates, 839 wp_get_deferred_translation_updates() 840 ); 841 $deferred_translation_updates = array_diff_key( $deferred_translation_updates, $translation_updates ); 842 $deferred_translation_updates += array_diff_key( $translation_updates, $selected_translation_updates ); 843 844 wp_set_deferred_translation_updates( $deferred_translation_updates ); 845 846 return $selected_translation_updates; 847 } 848 849 /** 645 850 * Adds a callback to display update information for themes with updates available. 646 851 * -
trunk/src/wp-admin/update-core.php
r61520 r65226 814 814 */ 815 815 function list_translation_updates() { 816 $ updates = wp_get_translation_updates();817 if ( ! $ updates ) {816 $translation_updates = wp_get_translation_update_data(); 817 if ( ! $translation_updates ) { 818 818 if ( 'en_US' !== get_locale() ) { 819 819 echo '<h2>' . __( 'Translations' ) . '</h2>'; … … 823 823 } 824 824 825 $form_action = 'update-core.php?action=do-translation-upgrade'; 825 $form_action = 'update-core.php?action=do-translation-upgrade'; 826 $updates_count = count( $translation_updates ); 826 827 ?> 827 <h2><?php _e( 'Translations' ); ?></h2> 828 <h2> 829 <?php 830 printf( 831 '%s <span class="count">(%s)</span>', 832 __( 'Translations' ), 833 number_format_i18n( $updates_count ) 834 ); 835 ?> 836 </h2> 828 837 <form method="post" action="<?php echo esc_url( $form_action ); ?>" name="upgrade-translations" class="upgrade"> 829 <p><?php _e( 'New translations are available.' ); ?></p> 838 <p><?php _e( 'The following translation updates are available. Leave any translation updates unchecked if you want to install them later, then click “Update Translations”.' ); ?></p> 839 <p><?php _e( 'Translation updates you leave unchecked will remain available until you select them.' ); ?></p> 830 840 <?php wp_nonce_field( 'upgrade-translations' ); ?> 831 <p><input class="button" type="submit" value="<?php esc_attr_e( 'Update Translations' ); ?>" name="upgrade" /></p> 841 <p><input id="upgrade-translations" class="button" type="submit" value="<?php esc_attr_e( 'Update Translations' ); ?>" name="upgrade" /></p> 842 <table class="widefat updates-table" id="update-translations-table"> 843 <thead> 844 <tr> 845 <td class="manage-column check-column"><input type="checkbox" id="translations-select-all" /></td> 846 <td class="manage-column"><label for="translations-select-all"><?php _e( 'Select All' ); ?></label></td> 847 </tr> 848 </thead> 849 850 <tbody class="plugins"> 851 <?php foreach ( $translation_updates as $translation_update ) : ?> 852 <?php $checkbox_id = 'checkbox_' . md5( $translation_update['id'] ); ?> 853 <tr> 854 <td class="check-column"> 855 <input type="hidden" name="translations[]" value="<?php echo esc_attr( $translation_update['id'] ); ?>" /> 856 <input type="checkbox" name="checked[]" id="<?php echo esc_attr( $checkbox_id ); ?>" value="<?php echo esc_attr( $translation_update['id'] ); ?>" <?php checked( $translation_update['checked'] ); ?> /> 857 <label for="<?php echo esc_attr( $checkbox_id ); ?>"> 858 <span class="screen-reader-text"> 859 <?php 860 printf( 861 /* translators: 1: Project name, 2: Language name or locale. */ 862 esc_html__( 'Select the translation update for %1$s in %2$s' ), 863 esc_html( $translation_update['name'] ), 864 esc_html( $translation_update['language'] ) 865 ); 866 ?> 867 </span> 868 </label> 869 </td> 870 <td class="plugin-title"><p> 871 <strong><?php echo esc_html( $translation_update['name'] ); ?></strong> 872 <?php 873 printf( 874 /* translators: 1: Language name or locale, 2: Version number. */ 875 esc_html__( '%1$s translation is available for version %2$s.' ), 876 esc_html( $translation_update['language'] ), 877 esc_html( $translation_update['version'] ) 878 ); 879 880 if ( $translation_update['deferred'] ) { 881 echo ' '; 882 esc_html_e( 'This translation update will remain available until you select it.' ); 883 } 884 ?> 885 </p></td> 886 </tr> 887 <?php endforeach; ?> 888 </tbody> 889 890 <tfoot> 891 <tr> 892 <td class="manage-column check-column"><input type="checkbox" id="translations-select-all-2" /></td> 893 <td class="manage-column"><label for="translations-select-all-2"><?php _e( 'Select All' ); ?></label></td> 894 </tr> 895 </tfoot> 896 </table> 897 <p><input id="upgrade-translations-2" class="button" type="submit" value="<?php esc_attr_e( 'Update Translations' ); ?>" name="upgrade" /></p> 832 898 </form> 833 899 <?php … … 1003 1069 1004 1070 if ( 'en_US' !== get_locale() ) { 1005 $updates_howto .= '<p>' . __( '<strong>Translations</strong> — T he files translating WordPress into your language are updated for you whenever any other updates occur. But if these files are out of date, you can <strong>click the “Update Translations”</strong> button.' ) . '</p>';1071 $updates_howto .= '<p>' . __( '<strong>Translations</strong> — Translation updates are selected for installation by default. Leave any translation updates unchecked if you want to install them later, then <strong>click the “Update Translations”</strong> button. Translation updates you leave unchecked will remain available until you select them.' ) . '</p>'; 1006 1072 } 1007 1073 … … 1093 1159 } 1094 1160 1161 $translation_updates_status = isset( $_GET['translation_updates'] ) ? wp_unslash( $_GET['translation_updates'] ) : ''; 1162 if ( is_string( $translation_updates_status ) ) { 1163 $translation_updates_status = sanitize_key( $translation_updates_status ); 1164 } else { 1165 $translation_updates_status = ''; 1166 } 1167 1168 if ( 'deferred' === $translation_updates_status ) { 1169 wp_admin_notice( 1170 __( 'The unchecked translation updates will remain available until you select them.' ), 1171 array( 1172 'type' => 'success', 1173 ) 1174 ); 1175 } 1176 1095 1177 $last_update_check = false; 1096 1178 $current = get_site_transient( 'update_core' ); … … 1276 1358 check_admin_referer( 'upgrade-translations' ); 1277 1359 1360 // Requests without the form's update identifiers must not install all translations. 1361 $translation_update_ids = array_unique( 1362 array_map( 1363 'sanitize_text_field', 1364 wp_unslash( (array) ( $_POST['translations'] ?? array() ) ) 1365 ) 1366 ); 1367 $selected_translation_update_ids = array_unique( 1368 array_map( 1369 'sanitize_text_field', 1370 wp_unslash( (array) ( $_POST['checked'] ?? array() ) ) 1371 ) 1372 ); 1373 1374 $selected_translation_updates = wp_process_translation_update_selection( $translation_update_ids, $selected_translation_update_ids ); 1375 1376 if ( false === $selected_translation_updates ) { 1377 wp_redirect( self_admin_url( 'update-core.php' ) ); 1378 exit; 1379 } 1380 1381 if ( empty( $selected_translation_updates ) ) { 1382 wp_redirect( add_query_arg( 'translation_updates', 'deferred', self_admin_url( 'update-core.php' ) ) ); 1383 exit; 1384 } 1385 1278 1386 require_once ABSPATH . 'wp-admin/admin-header.php'; 1279 1387 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; … … 1285 1393 1286 1394 $upgrader = new Language_Pack_Upgrader( new Language_Pack_Upgrader_Skin( compact( 'url', 'nonce', 'title', 'context' ) ) ); 1287 $result = $upgrader->bulk_upgrade( );1395 $result = $upgrader->bulk_upgrade( array_values( $selected_translation_updates ) ); 1288 1396 1289 1397 wp_localize_script( -
trunk/src/wp-includes/update.php
r62649 r65226 922 922 923 923 /** 924 * Gets a stable identifier for a translation update. 925 * 926 * @since 7.2.0 927 * 928 * @param array|object $update Translation update data. 929 * @return string Translation update identifier. 930 */ 931 function wp_get_translation_update_id( $update ) { 932 $update = (object) $update; 933 934 return md5( 935 wp_json_encode( 936 array( 937 'type' => $update->type ?? '', 938 'slug' => $update->slug ?? '', 939 'language' => $update->language ?? '', 940 'version' => $update->version ?? '', 941 ) 942 ) 943 ); 944 } 945 946 /** 947 * Gets translation updates keyed by their stable identifiers. 948 * 949 * @since 7.2.0 950 * 951 * @param object[]|null $updates Optional. Translation update objects. Default null. 952 * @return object[] Translation updates keyed by identifier. 953 */ 954 function wp_get_translation_updates_by_id( $updates = null ) { 955 if ( null === $updates ) { 956 $updates = wp_get_translation_updates(); 957 } 958 959 $translation_updates = array(); 960 961 foreach ( (array) $updates as $update ) { 962 $translation_updates[ wp_get_translation_update_id( $update ) ] = (object) $update; 963 } 964 965 return $translation_updates; 966 } 967 968 /** 969 * Gets deferred translation updates. 970 * 971 * Deferred updates remain available but are skipped by background updates by default. 972 * Explicit updates, including WP-CLI, can still install them. Successful installation 973 * through Language_Pack_Upgrader clears the matching deferral; failure leaves it unchanged. 974 * 975 * @since 7.2.0 976 * 977 * @param object[]|null $updates Optional. Translation update objects used to filter deferred updates 978 * to currently available updates. Default null. 979 * @return array[] Deferred translation updates keyed by identifier. 980 */ 981 function wp_get_deferred_translation_updates( $updates = null ) { 982 $stored_updates = get_site_option( 'deferred_translation_updates', array() ); 983 984 if ( ! is_array( $stored_updates ) ) { 985 return array(); 986 } 987 988 $deferred_updates = array(); 989 990 foreach ( $stored_updates as $stored_update ) { 991 if ( ! is_array( $stored_update ) ) { 992 continue; 993 } 994 995 $stored_update = (object) array( 996 'type' => $stored_update['type'] ?? '', 997 'slug' => $stored_update['slug'] ?? '', 998 'language' => $stored_update['language'] ?? '', 999 'version' => $stored_update['version'] ?? '', 1000 ); 1001 1002 $deferred_updates[ wp_get_translation_update_id( $stored_update ) ] = (array) $stored_update; 1003 } 1004 1005 if ( null !== $updates ) { 1006 $deferred_updates = array_intersect_key( $deferred_updates, wp_get_translation_updates_by_id( $updates ) ); 1007 } 1008 1009 return $deferred_updates; 1010 } 1011 1012 /** 1013 * Determines whether a translation update has been deferred. 1014 * 1015 * @since 7.2.0 1016 * 1017 * @param array|object $update Translation update data. 1018 * @param array[]|null $deferred_updates Optional. Deferred translation updates keyed by identifier. 1019 * Default null. 1020 * @return bool Whether the translation update has been deferred. 1021 */ 1022 function wp_is_translation_update_deferred( $update, $deferred_updates = null ) { 1023 if ( null === $deferred_updates ) { 1024 $deferred_updates = wp_get_deferred_translation_updates(); 1025 } 1026 1027 return isset( $deferred_updates[ wp_get_translation_update_id( $update ) ] ); 1028 } 1029 1030 /** 1031 * Stores deferred translation updates. 1032 * 1033 * @since 7.2.0 1034 * 1035 * @param object[]|array[] $updates Translation updates to defer. 1036 * @return void 1037 */ 1038 function wp_set_deferred_translation_updates( $updates ) { 1039 $deferred_updates = array(); 1040 1041 foreach ( (array) $updates as $update ) { 1042 $update = (object) $update; 1043 1044 $translation_update = array( 1045 'type' => $update->type ?? '', 1046 'slug' => $update->slug ?? '', 1047 'language' => $update->language ?? '', 1048 'version' => $update->version ?? '', 1049 ); 1050 1051 $deferred_updates[ wp_get_translation_update_id( $translation_update ) ] = $translation_update; 1052 } 1053 1054 if ( empty( $deferred_updates ) ) { 1055 delete_site_option( 'deferred_translation_updates' ); 1056 return; 1057 } 1058 1059 update_site_option( 'deferred_translation_updates', $deferred_updates ); 1060 } 1061 1062 /** 924 1063 * Collects counts and UI strings for available updates. 925 1064 * -
trunk/tests/phpunit/tests/admin/wpAutomaticUpdater.php
r65069 r65226 39 39 parent::set_up(); 40 40 add_filter( 'pre_wp_mail', '__return_false' ); 41 } 42 43 public function tear_down() { 44 delete_site_option( 'deferred_translation_updates' ); 45 46 parent::tear_down(); 47 } 48 49 /** 50 * Tests that deferred translation updates are not automatically updated. 51 * 52 * @ticket 42281 53 * 54 * @covers WP_Automatic_Updater::should_update 55 */ 56 public function test_should_update_should_skip_deferred_translation_updates() { 57 $deferred_update = (object) array( 58 'type' => 'plugin', 59 'slug' => 'deferred-plugin', 60 'language' => 'de_DE', 61 'version' => '1.0.0', 62 'autoupdate' => true, 63 ); 64 65 $available_update = clone $deferred_update; 66 $available_update->slug = 'available-plugin'; 67 68 wp_set_deferred_translation_updates( array( $deferred_update ) ); 69 70 add_filter( 'request_filesystem_credentials', '__return_true' ); 71 add_filter( 'automatic_updates_is_vcs_checkout', '__return_false' ); 72 add_filter( 'automatic_updater_disabled', '__return_false' ); 73 74 $this->assertFalse( self::$updater->should_update( 'translation', $deferred_update, WP_LANG_DIR ) ); 75 $this->assertTrue( self::$updater->should_update( 'translation', $available_update, WP_LANG_DIR ) ); 41 76 } 42 77
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)