Conversation
wp_generate_tag_cloud() re-sorts the terms in PHP after the database has already returned them in collation order. The comparison callback _wp_object_name_sort_cb() used strnatcasecmp(), which folds case only for ASCII and ignores accents entirely, so non-ASCII terms were ordered by raw byte value. For Greek that split one alphabet into four consecutive groups: uppercase accented, uppercase non-accented, lowercase accented and lowercase non-accented. With the Latin terms on top, a tag cloud showed five groups instead of the expected two. Fold the name to lowercase and strip combining marks to build the sort key, so case and accent variants of the same letter sort together and match the ordering the database collation already provides. The sort key is left decomposed because recomposing would restore the accents. A deterministic strnatcmp() tie-break on the original name keeps the order well defined when the folded keys are equal, and the callback result is cast to an int, which is what comparison callbacks are expected to return. mbstring and intl are not required by WordPress, so both folds are applied only when the extension is available. The plain strtolower() fallback and the unfolded fallback still keep case variants adjacent, which is the larger part of the reported problem. Plain ASCII ordering is byte for byte unchanged. Props beerallica. Fixes #35144.
Add coverage to the existing tag cloud test file for the name ordering in wp_generate_tag_cloud(). Greek terms that differ only by case or by accents must stay in a single group and must not be split apart by the comparison, and Latin terms must still sort before Greek ones. A further test pins the ASCII ordering so the change to the sort key is proven not to alter it, and an additional test asserts the variants of a single letter keep a deterministic order. The accent expectations require the intl extension, so those tests are skipped when Normalizer is unavailable. Fixes #35144.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @skikken. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
Greek terms in a tag cloud come out in five groups instead of two. The reporter described it as Latin
Aa-Zz, then GreekΑ-Ω, then Greekα-ω, and later refined it to five groups: Latin case-insensitive, uppercase accented Greek, uppercase non-accented Greek, lowercase accented Greek and lowercase non-accented Greek.wp_generate_tag_cloud()re-sorts the terms in PHP after the database has already sorted them. That second sort goes through_wp_object_name_sort_cb(), which compared withstrnatcasecmp(). That function folds case for ASCII only and ignores accents entirely, so for anything non-ASCII the comparison is decided by raw byte value, and one Greek alphabet becomes four byte ranges:Those four ranges stay distinct and sort in exactly that order, which is the reported symptom. It also explains the part of the report that was hardest to account for: the same terms sorted correctly in phpMyAdmin, because the database collation is doing the right thing and the PHP re-sort is throwing that ordering away.
The fix builds a sort key by folding the name to lowercase and stripping combining marks, so case and accent variants of the same letter share a key and sort together. Two details worth calling out:
strnatcmp()tie-break on the original name runs when the folded keys are equal.uasort()is not stable, so without a tie-break the relative order of variants would be undefined and untestable.mbstringandintlare not required by WordPress and neither is polyfilled for these functions, so both folds apply only when the extension is present, following thefunction_exists()pattern already used inwp-includes/class-wp-query.php:With neither extension available the ordering is no worse than before: the plain
strtolower()fallback still merges uppercase and lowercase forms, covering the larger half of the report.Two additional notes:
strnatcasecmp()directly. It now returns anint. This is the same class of issue that was fixed for an equivalent comparison callback inwp-includes/post.php, where returning a bool triggered a deprecation notice on PHP 8. It is mentioned here because the touched function is the one in the fix, not as a claim about any other callback.remove_accents(). It has no Greek entries in its character map and noel_GRbranch in its locale rules, so it is the wrong tool for this and adding a locale branch there would be a much larger change than this ticket needs.Tests were added to the existing tag cloud test file rather than a new one, since that file is where
wp_generate_tag_cloud()coverage already lives. They drive the public API, and the ASCII test passes both with and without the fix, so the change is pinned not to alter existing ordering.Trac ticket: https://core.trac.wordpress.org/ticket/35144
Use of AI Tools
AI assistance: Yes
Tool(s): Claude
Model(s): Sonnet
Used for: test cases, code review
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.