Opened 6 weeks ago
Last modified 6 weeks ago
#64830 new enhancement
Introduce a helper function to extract the major.minor "branch" version
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Awaiting Review | Priority: | normal |
| Severity: | normal | Version: | 3.2 |
| Component: | General | Keywords: | has-patch needs-refresh needs-testing |
| Focuses: | Cc: |
Description
In PR https://github.com/WordPress/wordpress-develop/pull/11206 we ran into the issue that getting a patchless WP version is cluttered throughout core and sparked a discussion about introducing a proper helper function for extracting the major.minor (x.y) "branch" version.
The original problem
PHPStan flags (float) get_bloginfo('version') in admin-header.php:194 because str_replace() receives a float instead of a string. The PR author initially proposed changing the cast to (string), but this broke the existing behavior (the branch- CSS class would include the full version instead of just major.minor).
Key feedback
- @apermo caught the behavior regression and pointed out that the
(float)cast is intentional — it strips the patch version (e.g., 6.9.1 → 6.9). He proposed get_bloginfo('branch_version') or a helper function. - @siliconforks identified a genuine bug: the
(float)cast is not reliably defined. If PHP's precision ini setting is non-default (e.g., 50), version 6.9.1 produces branch-6-9000000000000003552713678800500929355621337890625. This affects any version where the major.minor isn't exactly representable as a float (i.e., most versions except x.0). - @westonruter noted the
(float)approach is "abuse of a float" and proposed a string-based replacement using strtok/explode/array_slice. He also noted that for 7.0, the existing behavior produces branch-7 (not branch-7-0), and this should be preserved. - @apermo cataloged 4 different approaches used across core to extract the
x.yversion, and proposed a single helper function using the robust implode/preg_split approach from class-core-upgrader.php that all locations could use.
Consensus direction
- A helper function for extracting the branch version would benefit core, since at least 10 locations use varied (and sometimes fragile) approaches — some of which will break at version 10.0 (substr($ver, 0, 3))
- The helper function should be a separate enhancement ticket from the immediate PHPStan fix
Affected locations
- (float) cast: admin-header.php, class-wp-site-health-auto-updates.php, tests/basic.php
- substr($ver, 0, 3): theme.php, plugin-install.php, block-template-utils.php, tests/basic.php
- explode('.') with index: class-wp-site-health.php
- implode/preg_split: class-core-upgrader.php (most robust)
<?php /** * Returns the major.minor "branch" version for a given WordPress version string. * * Extracts the first two version components (major and minor) from a WordPress * version string, stripping any patch level and pre-release suffix. * * @since 7.1.0 * * @param string $version Optional. A WordPress version string. Defaults to the * current WordPress version from wp_get_wp_version(). * @return string The branch version string in "major.minor" format (e.g. "6.9"). */ function wp_get_branch_version( $version = '' ) { if ( '' === $version ) { $version = wp_get_wp_version(); } $parts = preg_split( '/[.-]/', $version, 3 ); return $parts[0] . '.' . ( $parts[1] ?? '0' ); }
Decisions:
- Reuses the
preg_split('/[.-]/')approach fromclass-core-upgrader.php:282— already proven in core and handles both . and - separators in one pass. - Accepts an optional $version parameter so callers like class-core-upgrader.php (which compares two versions) and class-wp-site-health.php can use it too, not just the current WP version.
- Limit of 3 on
preg_split()avoids unnecessary splits. - Always returns "major.minor" — e.g. "7.0" for "7.0-beta3", "6.9" for "6.9.1", "10.1" for "10.1.2-RC1".
Note: The existing admin-header.php behavior drops the .0 in the CSS class (producing branch-7 instead of branch-7-0). That's a quirk of the (float) cast. This is the main decision that needs to be taken which is the correct/expected behavior here.
Change History (9)
This ticket was mentioned in PR #11211 on WordPress/wordpress-develop by @suhan2411.
6 weeks ago
#2
- Keywords has-patch added
This introduces a new helper function wp_get_branch_version() for extracting the WordPress branch version (major.minor) from a version string.
Currently, several different approaches are used across core to determine the branch version, including:
(float)casting ofget_bloginfo( 'version' )substr( $ver, 0, 3 )explode( '.' )based parsingpreg_split()inclass-core-upgrader.php
Some of these approaches are fragile or incorrect:
(float)casting can produce incorrect values due to floating-point precision issues.substr( $ver, 0, 3 )breaks for versions >= 10.- Multiple inconsistent approaches make maintenance harder.
This patch introduces wp_get_branch_version() using a string-based approach:
function wp_get_branch_version( $version = '' ) {
if ( '' === $version ) {
$version = wp_get_wp_version();
}
$parts = preg_split( '/[.-]/', $version, 3 );
return $parts[0] . '.' . ( $parts[1] ?? '0' );
}
#3
@
6 weeks ago
A pull request has been opened for review:
https://github.com/WordPress/wordpress-develop/pull/11211
This introduces the wp_get_branch_version() helper to extract the major.minor branch version safely and replaces existing fragile approaches (float cast, substr, etc.) across core.
Feedback welcome.
#4
follow-up:
↓ 5
@
6 weeks ago
Please note that x.y is the major version, not major.minor. Minor is the third number. See: https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/
#5
in reply to:
↑ 4
@
6 weeks ago
Replying to jorbin:
Please note that
x.yis the major version, not major.minor. Minor is the third number. See: https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/
That pretty much sorts out what @westonruter said. So for any 7.0 Version including 7.0.0 the result has to be 7.0 and not 7 as it currently is in the admin header.
#6
@
6 weeks ago
For the automated tests, I would encourage using a dataProvider and test at least the following:
input -> output 7.0 -> 7.0 # Major ending with 0 and no minor 7.0.0 -> 7.0 # minor number zero 7.0.1 -> 7.0 # Minor with a major that ends in zero 7.0.10 -> 7.0 # Double Digit Minor with trailing zero 10.0.0 -> 10.0 # Double digit first part of major having a zero 100.1.0 -> 100.1 # Triple digit since we don't want to introduce a bug in 2050 something.
Further, https://core.trac.wordpress.org/browser/trunk/tests/phpunit/tests/functions/isWpVersionCompatible.php has some of the various ways version numbers can be messed up that would be good to ensure are handled correctly here.
#7
@
6 weeks ago
If there is a need for this, I'd investigate adding a parameter to wp_get_wp_version() to allow it to be called to get the major (x.x), minor (x.x.x) and full (7.0-beta3-61849) versions. A new function seems to be overkill.
#8
in reply to:
↑ description
@
6 weeks ago
Good idea to extend wp_get_wp_version() to get a subset of the version.
Replying to apermo:
Note: The existing
admin-header.phpbehavior drops the .0 in the CSS class (producing branch-7 instead of branch-7-0). That's a quirk of the (float) cast. This is the main decision that needs to be taken which is the correct/expected behavior here.
In 6.9.1, the added classes are branch-6-9 and version-6-9-1. I'm not aware how these classes are used by plugins. But since they they were introduced in r17957 to fix #17496 in 3.2, I would think it's safe to assume that the intention was that the format would be branch-X-Y. That said, I see three plugins which currently have .branch-7, .branch-6, and .branch-4: https://veloria.dev/search/58a08df4-64a6-46a4-bd55-2db4705968c2 (news flash: the new WPdirectory!!)
The .branch-7 example comes from WPeMatico RSS Feed Fetcher. It seems we should include both branch-7 and branch-7-0 for correctness and back-compat.
#9
@
6 weeks ago
- Keywords needs-refresh needs-testing added
Thanks everyone for the feedback.
I've updated the PR and pushed the changes addressing the points raised above.
- Terminology (@jorbin, @apermo)
Docblocks now follow the version numbering handbook: x.y represents the major version and x.y.z the minor version. The helper returns the major version (e.g. 7.0).
- Extending wp_get_wp_version() (@peterwilsoncc, @westonruter)
Instead of introducing a new public API, wp_get_wp_version() now accepts an optional $part parameter:
- wp_get_wp_version( 'major' ) →
x.y(e.g.7.0) - wp_get_wp_version( 'minor' ) →
x.y.z(e.g.7.0.1) - wp_get_wp_version() / wp_get_wp_version( 'full' ) → full version string (unchanged)
wp_get_branch_version() remains available for parsing arbitrary version strings, and wp_get_wp_version( 'major' ) delegates to it for the current version.
- Admin header classes (@apermo, @westonruter)
For versions like 7.0.x, the admin now outputs both:
branch-7-0branch-7(for back-compat with existing selectors)
- Test coverage (@jorbin)
Added PHPUnit tests in tests/phpunit/tests/functions/wpGetBranchVersion.php using a data provider to cover cases such as:
7.0,7.0.0,7.0.1,7.0.1010.0.0,100.1.0- alpha/beta/RC suffixes
Additional tests were added for wp_get_wp_version( 'major' ), wp_get_wp_version( 'minor' ), and wp_get_wp_version( 'full' ).
- Site Health edge case (@siliconforks)
The previous-version calculation for dev builds now uses wp_get_branch_version() and correctly resolves 7.0 → 6.9.
PR: https://github.com/WordPress/wordpress-develop/pull/11211
If
wp_get_branch_version()is introduced and returnsmajor.minor(e.g.7.0,6.9), we could keep the currentadmin-header.phpbehavior (dropping the trailing.0) like this:This removes the
(float)cast while preserving the existingbranch-7behavior.