Changeset 15168
- Timestamp:
- 09/09/2026 06:10:53 PM (11 days ago)
- Location:
- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
- Files:
-
- 3 edited
-
jobs/class-api-update-updater.php (modified) (8 diffs)
-
tests/Current_Release_Resolution_Test.php (modified) (3 diffs)
-
tests/Update_Source_Hold_Test.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/jobs/class-api-update-updater.php
r15106 r15168 90 90 $existing_row = $wpdb->get_row( 91 91 $wpdb->prepare( 92 "SELECT version, meta FROM {$wpdb->prefix}update_source WHERE plugin_slug = %s",92 "SELECT version, stable_tag, meta FROM {$wpdb->prefix}update_source WHERE plugin_slug = %s", 93 93 $post->post_name 94 94 ) 95 95 ); 96 $existing_version = (string) ( $existing_row->version ?? '' );97 96 98 97 $release_delay = (int) ( $release['release_delay'] ?? 0 ); 99 98 100 // `update_source.version` is varchar(128); mirror cron_trigger()'s `left( pm.meta_value, 128 )` truncation allowance.101 $is_new_ version = substr( (string) $version, 0, 128 ) !== $existing_version;99 // Never judged by the Version header: an author can keep the served version's label on a new tag. 100 $is_new_release = ! $existing_row || ! self::is_current_ref_served( $post, $existing_row ); 102 101 103 102 /* 104 103 * Hold a blocked release out of the row: the previous version keeps being 105 104 * served, the deferred serve is cancelled, and status changes still reach 106 * the row. Gated on the block alone — a header renamed to the served 107 * version turns the $is_new_version proxy false, and block_release() 108 * refuses served releases, so a held release is never already in the row. 105 * the row. Gated on the block alone — block_release() refuses served 106 * releases, so a held release is never already in the row. 109 107 */ 110 108 if ( self::is_release_blocked( $release ) ) { … … 134 132 * repeats as a no-op. 135 133 */ 136 if ( $release_delay && $is_new_ version) {134 if ( $release_delay && $is_new_release ) { 137 135 $cooldown_until = $release_time + $release_delay; 138 136 if ( $cooldown_until > time() ) { … … 151 149 // phased_rollout()'s `manual-updates-24hr` window measuring from public availability, 152 150 // even if the commit/confirmation was long ago because the cooldown deferred the write. 153 if ( $release_delay && $is_new_ version) {151 if ( $release_delay && $is_new_release ) { 154 152 $release_time = time(); 155 153 } … … 235 233 236 234 /** 235 * The ref a plugin's current version is served from: its stable tag, or 236 * `trunk@{version}` for trunk-stable plugins, as release rows are keyed. 237 * 238 * @param \WP_Post $post The plugin post. 239 * @return string The ref. 240 */ 241 public static function get_current_ref( $post ) { 242 return self::build_ref( 243 get_post_meta( $post->ID, 'stable_tag', true ), 244 get_post_meta( $post->ID, 'version', true ) 245 ); 246 } 247 248 /** 249 * Whether a plugin's current ref is the one its `update_source` row serves. 250 * 251 * Compared by ref rather than by the release the ref resolves to: a 252 * fallback-resolved release keeps its own tag, and the Version header alone 253 * can be kept at the served label on a new tag. The row's columns are 254 * varchar(128), so the meta is truncated to match before the refs are built. 255 * 256 * @param \WP_Post $post The plugin post. 257 * @param object $served The `update_source` row, with `version` and `stable_tag`. 258 * @return bool 259 */ 260 public static function is_current_ref_served( $post, $served ) { 261 $current_ref = self::build_ref( 262 substr( (string) get_post_meta( $post->ID, 'stable_tag', true ), 0, 128 ), 263 substr( (string) get_post_meta( $post->ID, 'version', true ), 0, 128 ) 264 ); 265 266 return self::build_ref( $served->stable_tag, $served->version ) === $current_ref; 267 } 268 269 /** 270 * Build the ref a stable tag and version are served from. 271 * 272 * @param string $stable_tag The stable tag; empty or 'trunk' for trunk-stable plugins. 273 * @param string $version The version, keying trunk-stable refs. 274 * @return string The ref. 275 */ 276 protected static function build_ref( $stable_tag, $version ) { 277 return ( ! $stable_tag || 'trunk' === $stable_tag ) 278 ? 'trunk@' . $version 279 : (string) $stable_tag; 280 } 281 282 /** 237 283 * The release being served or held for a plugin's current version. 238 284 * … … 254 300 */ 255 301 public static function get_current_release( $post ) { 256 $stable_tag = get_post_meta( $post->ID, 'stable_tag', true ); 257 258 $target = ( ! $stable_tag || 'trunk' === $stable_tag ) 259 ? 'trunk@' . get_post_meta( $post->ID, 'version', true ) 260 : $stable_tag; 261 302 $target = self::get_current_ref( $post ); 262 303 $releases = (array) Plugin_Directory::get_releases( $post ); 263 304 … … 326 367 } 327 368 328 // Already live: a block can't un-ship a served release — compared by identity (the ref for tagged rows, the version for ref-less trunk-stable rows), with the columns' varchar(128) truncation.369 // Already live: a block can't un-ship a served release. 329 370 $served = self::get_served_release( $plugin_slug ); 330 if ( $served ) { 331 if ( $served->stable_tag && 'trunk' !== $served->stable_tag ) { 332 $is_served = substr( (string) $release['tag'], 0, 128 ) === (string) $served->stable_tag; 333 } else { 334 $is_served = '' !== (string) $served->version 335 && substr( (string) ( $release['version'] ?? '' ), 0, 128 ) === (string) $served->version; 336 } 337 338 if ( $is_served ) { 339 return false; 340 } 371 if ( $served && self::is_current_ref_served( $post, $served ) ) { 372 return false; 341 373 } 342 374 … … 455 487 * Determine the release timestamp for a plugin version. 456 488 * 457 * Anchored on the version's commit time (`version_date`), falling back to the 458 * release row's own date — unlike post_modified, neither slides on unrelated 459 * post edits — and replaced by the latest committer-confirmation time when 460 * release confirmations are required (the version isn't really "released" 461 * until the last confirmation lands). 489 * Anchored on the later of the version's commit time (`version_date`) and the 490 * release row's own date — a new tag at an unchanged version, or a re-opened 491 * release, moves only the latter, and unlike post_modified neither slides on 492 * unrelated post edits — and replaced by the latest committer-confirmation 493 * time when release confirmations are required (the version isn't really 494 * "released" until the last confirmation lands). 462 495 * 463 496 * @param \WP_Post $post The plugin post. … … 466 499 */ 467 500 public static function compute_release_time( $post, $release ) { 468 if ( $post->version_date ) {469 $ release_time = strtotime( $post->version_date );470 } elseif ( ! empty( $release['date'] ) ) {471 $release_time = (int) $release['date'];472 } else{501 $release_time = max( 502 $post->version_date ? (int) strtotime( $post->version_date ) : 0, 503 (int) ( $release['date'] ?? 0 ) 504 ); 505 if ( ! $release_time ) { 473 506 $release_time = strtotime( $post->post_modified ); 474 507 } -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Current_Release_Resolution_Test.php
r15078 r15168 138 138 139 139 /** 140 * The stable tag currently in the plugin's update_source row. 141 * 142 * @return string The served stable tag. 143 */ 144 private function served_stable_tag(): string { 145 return (string) ( API_Update_Updater::get_served_release( $this->plugin->post_name )->stable_tag ?? '' ); 146 } 147 148 /** 140 149 * A renamed Version header inside a tag under cooldown keeps the hold. 141 150 */ … … 146 155 147 156 $this->assertSame( self::SERVED_VERSION, $this->served_version() ); 157 $this->assertNotFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 158 } 159 160 /** 161 * A new tag whose header keeps the served version is still a new release: 162 * the cooldown gate keys on the release's identity, not the header proxy. 163 */ 164 public function test_new_tag_with_served_header_keeps_cooldown_hold(): void { 165 update_post_meta( $this->plugin->ID, 'version', self::SERVED_VERSION ); 166 $this->add_release( self::HELD_TAG, self::SERVED_VERSION ); 167 168 $this->assertTrue( API_Update_Updater::update_single_plugin( $this->plugin->post_name ) ); 169 170 $this->assertSame( self::SERVED_VERSION, $this->served_stable_tag() ); 171 $this->assertNotFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 172 } 173 174 /** 175 * The full evasion: a new tag keeps the served header, so the importer bumps 176 * neither the version nor its version_date. The hold must survive the stale 177 * plugin-wide anchor and measure the cooldown from the release itself. 178 */ 179 public function test_new_tag_with_served_header_and_stale_version_date_keeps_cooldown_hold(): void { 180 update_post_meta( $this->plugin->ID, 'version', self::SERVED_VERSION ); 181 update_post_meta( $this->plugin->ID, 'version_date', gmdate( 'Y-m-d H:i:s', time() - WEEK_IN_SECONDS ) ); 182 $this->add_release( self::HELD_TAG, self::SERVED_VERSION ); 183 184 $this->assertTrue( API_Update_Updater::update_single_plugin( $this->plugin->post_name ) ); 185 186 $this->assertSame( self::SERVED_VERSION, $this->served_stable_tag() ); 187 $this->assertNotFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 188 } 189 190 /** 191 * Switching a trunk-stable plugin to a tag at an unchanged version is a new 192 * release: the trunk row's identity is trunk@{version}, which no tag matches. 193 */ 194 public function test_tag_switch_from_served_trunk_keeps_cooldown_hold(): void { 195 global $wpdb; 196 197 $wpdb->update( 198 $wpdb->prefix . 'update_source', 199 array( 'stable_tag' => 'trunk' ), 200 array( 'plugin_slug' => $this->plugin->post_name ) 201 ); 202 update_post_meta( $this->plugin->ID, 'version', self::SERVED_VERSION ); 203 $this->add_release( self::HELD_TAG, self::SERVED_VERSION ); 204 205 $this->assertTrue( API_Update_Updater::update_single_plugin( $this->plugin->post_name ) ); 206 207 $this->assertSame( 'trunk', $this->served_stable_tag() ); 208 $this->assertNotFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 209 } 210 211 /** 212 * A tag-to-trunk flip at an unchanged version creates no trunk release, so 213 * the current version resolves to the old tag's record. Once the row serves 214 * trunk, that fallback is the served release and a block is refused. 215 */ 216 public function test_block_refused_for_served_trunk_fallback_release(): void { 217 $this->serve_trunk_fallback(); 218 219 $this->assertFalse( API_Update_Updater::block_release( $this->plugin->post_name, array( 'risk_score' => 9.8 ) ) ); 220 $this->assertFalse( API_Update_Updater::is_release_blocked( Plugin_Directory::get_release( get_post( $this->plugin->ID ), self::SERVED_VERSION ) ) ); 221 } 222 223 /** 224 * The served trunk fallback is not a new release: the row is rewritten 225 * without a cooldown hold. 226 */ 227 public function test_served_trunk_fallback_release_schedules_no_cooldown(): void { 228 $this->serve_trunk_fallback(); 229 230 $this->assertTrue( API_Update_Updater::update_single_plugin( $this->plugin->post_name ) ); 231 232 $this->assertSame( 'trunk', $this->served_stable_tag() ); 233 $this->assertFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 234 } 235 236 /** 237 * Serve trunk at the served version with only the old tag's release record, 238 * so the current version resolves through the version-named fallback. 239 */ 240 private function serve_trunk_fallback(): void { 241 global $wpdb; 242 243 $wpdb->update( 244 $wpdb->prefix . 'update_source', 245 array( 'stable_tag' => 'trunk' ), 246 array( 'plugin_slug' => $this->plugin->post_name ) 247 ); 248 update_post_meta( $this->plugin->ID, 'version', self::SERVED_VERSION ); 249 update_post_meta( $this->plugin->ID, 'stable_tag', 'trunk' ); 250 $this->add_release( self::SERVED_VERSION, self::SERVED_VERSION ); 251 252 $this->assertSame( self::SERVED_VERSION, API_Update_Updater::get_current_release( get_post( $this->plugin->ID ) )['tag'] ); 253 } 254 255 /** 256 * Trunk versions are compared on the full 128 bytes the row stores: the 257 * `trunk@` prefix must not eat into the allowance, or two long versions 258 * differing past byte 122 read as the same release. 259 */ 260 public function test_long_trunk_versions_differing_past_prefix_allowance_are_distinct(): void { 261 global $wpdb; 262 263 $served_version = str_repeat( '1', 130 ); 264 $new_version = substr_replace( $served_version, '2', 124, 1 ); 265 266 $wpdb->update( 267 $wpdb->prefix . 'update_source', 268 array( 269 'stable_tag' => 'trunk', 270 'version' => substr( $served_version, 0, 128 ), 271 ), 272 array( 'plugin_slug' => $this->plugin->post_name ) 273 ); 274 update_post_meta( $this->plugin->ID, 'stable_tag', 'trunk' ); 275 update_post_meta( $this->plugin->ID, 'version', $new_version ); 276 $this->add_release( 'trunk@' . $new_version, $new_version ); 277 278 $this->assertTrue( API_Update_Updater::update_single_plugin( $this->plugin->post_name ) ); 279 280 $this->assertSame( substr( $served_version, 0, 128 ), $this->served_version() ); 148 281 $this->assertNotFalse( wp_next_scheduled( "release_to_update_api:{$this->plugin->post_name}" ) ); 149 282 } … … 438 571 439 572 $this->assertSame( $version_time, API_Update_Updater::compute_release_time( get_post( $this->plugin->ID ), $release ) ); 573 } 574 575 /** 576 * A release row newer than the plugin's version_date anchors the clock: a 577 * new tag at an unchanged version, or a re-commit that re-opens a release, 578 * bumps only the release date. 579 */ 580 public function test_release_time_prefers_newer_release_date_over_version_date(): void { 581 update_post_meta( $this->plugin->ID, 'version_date', gmdate( 'Y-m-d H:i:s', time() - WEEK_IN_SECONDS ) ); 582 583 $release = array( 584 'date' => time() - DAY_IN_SECONDS, 585 'confirmations_required' => 0, 586 'confirmations' => array(), 587 ); 588 589 $this->assertSame( $release['date'], API_Update_Updater::compute_release_time( get_post( $this->plugin->ID ), $release ) ); 440 590 } 441 591 -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Update_Source_Hold_Test.php
r15077 r15168 360 360 361 361 update_post_meta( $this->plugin->ID, 'version', $long_version ); 362 update_post_meta( $this->plugin->ID, 'stable_tag', $long_version ); 362 363 update_post_meta( $this->plugin->ID, 'releases', array( $release ) ); 363 364 $this->insert_served_row( substr( $long_version, 0, 128 ) ); … … 379 380 380 381 update_post_meta( $this->plugin->ID, 'version', $long_version ); 382 update_post_meta( $this->plugin->ID, 'stable_tag', $long_version ); 381 383 update_post_meta( $this->plugin->ID, 'releases', array( $release ) ); 382 384 $this->insert_served_row( substr( $long_version, 0, 128 ) );
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)