Skip to content

Hold back updates where mbstring lacks mbregex - #467

Draft
KevinBatdorf wants to merge 10 commits into
mainfrom
feature/mbregex-gate
Draft

Hold back updates where mbstring lacks mbregex#467
KevinBatdorf wants to merge 10 commits into
mainfrom
feature/mbregex-gate

Conversation

@KevinBatdorf

@KevinBatdorf KevinBatdorf commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Some servers run PHP without a piece of text-matching that the next big release needs to colour code. Site owners can't add it — that takes a different PHP build — and WordPress.org can't hold an update back over it. So the plugin looks at the server it's on, stops offering itself an update when it wouldn't be able to render, and says so in the editor. Sites that are fine see no difference.

  • Updates stop being offered. The plugin removes its own entry from the update list on a server that can't render, so nothing is offered and nothing auto-updates.
  • The update is also refused if something goes around that. wp plugin update and the update screens hand a package straight to the installer without consulting that list, so the installer is told no as well, with a message saying why. Files copied in by hand still can't be stopped.
  • The editor explains it. A notice in the Theme panel names what version 2 needs and what this particular server is missing — the PHP version, the text-matching piece, or both. There's nothing to click: letting the update through would leave every code block rendering without colours, and hiding the notice would take away the only explanation the site has.
  • The PHP version is left to WordPress. Core reads a plugin's PHP requirement out of the package and refuses the install itself, wp plugin update included, so this release only gates on the piece core can't see. Two tests hold that line.
  • The minimum PHP version stays where it is. Raising it would stop this release reaching the very sites it's meant for.
  • Tests run twice, the second time on a PHP built without that piece, so the real behaviour is covered rather than a stand-in for it. The editor notice is covered separately, since no such PHP can run in the browser-based test environment.

PR description generated by Claude.

KevinBatdorf and others added 7 commits August 21, 2026 21:51
The next major renders code server-side and needs mb_ereg_*. mbstring
built with --disable-mbregex has no ini switch and no add-on package to
fix it — only a different PHP build — and wordpress.org can gate updates
on PHP version but not on extensions. Left alone, those sites would
auto-update into a version they cannot render.

So the plugin holds itself back: a capability check behind the
blocks.codeBlockPro.canHighlight filter, and a site_transient_update_plugins
filter that drops this plugin's own entry when the check fails. Verified on
a playground site — with the check forced false the Plugins screen offers
no update, the Updates screen doesn't list the plugin, and the menu bubble
reads 0; other plugins are untouched. Note that playground's PHP does have
mbregex, so the editor-facing phases have to drive the filter seam rather
than the real capability.

Reading, not writing, is filtered on purpose. The stored transient still
carries the update, so nothing is lost if a later release can run here and
the gate comes off.

CI runs the suite twice, once with mb_ereg_search_init taken away by
disable_functions. No prebuilt PHP offers mbstring without mbregex, and
that ini setting reproduces the only part that matters: function_exists
returns false while the rest of mbstring keeps working. So one test covers
the unfiltered path for real in both directions, rather than trusting the
filter seam to stand in for a build nobody can install.

This also stands up composer and phpunit, which the repo had neither of —
tests run against a WordPress checkout named by WP_ABSPATH, with a
dedicated database so they never touch a working install. Requires PHP
stays at 7.0: bumping it would stop this release reaching the very sites
it exists for.

Co-Authored-By: Claude <noreply@anthropic.com>
Hiding mb_ereg_search_init with disable_functions made the capability
check false, but it was still a stand-in: mbstring kept the rest of the
mb_ereg family, and nothing proved the plugin behaves on the build this
release exists for. No prebuilt PHP ships mbstring without mbregex, so
the only way to get one is to compile it.

The Dockerfile builds PHP with --enable-mbstring --disable-mbregex and
without libonig, so a build that quietly keeps mbregex fails instead of
passing. Confirmed locally on 8.4.24: mbstring loads, mb_substr works,
the whole mb_ereg family is gone, and phpinfo no longer reports an
oniguruma version. WordPress installs and the suite passes, and with the
transient filter removed the unfiltered test fails — so it is reading the
build, not a filter.

The image is pushed to GHCR and the second job runs inside it. Public
packages cost nothing and don't expire, unlike Actions cache entries,
which are evicted after a week idle and would silently recompile. Both
jobs live in one workflow so the image is built before the job that
pulls it, and the database now comes from the service's own MYSQL_DATABASE
rather than a client call, since the image carries no mysql client.

The mbregex-absent job talks to the service by hostname because a job
running in a container can't reach it on 127.0.0.1.

Co-Authored-By: Claude <noreply@anthropic.com>
The gate makes the plugin go quiet on servers without mbregex, which on its
own reads as a plugin that simply stopped getting updates. The capability now
rides along with the editor's inline script, next to pluginUrl, and the Theme
panel carries a notice naming mbregex and the mbstring extension so a site
owner has something concrete to take to their host. The check stays in PHP —
the editor is told the answer rather than working it out, so the notice can't
disagree with the update gate.

The notice states a requirement and offers nothing to click. Letting the
update through anyway would leave every new code block rendering without
colors, which is a broken site rather than a choice worth putting in front of
someone; and hiding the notice would take away the only explanation the site
has for a plugin that no longer updates. It stays until the server can
highlight.

Playground's PHP has mbregex and no build without it can run there, so the
spec installs an mu-plugin that turns the capability off for requests
carrying cbp_no_mbregex — the filter seam the gate left for exactly this. The
absence case waits for the Manage themes button first, since the panel is
empty until the settings store hydrates and a missing notice would prove
nothing. Confirmed the other direction is real: with the component left out
of the bundle, the notice test failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping our entry from the update transient only removes the offer. wp-cli
and the update screens hand a package straight to the upgrader, which never
reads that transient, so `wp plugin update code-block-pro` would still put a
version this server cannot render onto the site.

upgrader_pre_install returns a WP_Error for our own basename when the
capability is missing, which aborts install_package before anything is
copied over the live plugin. wp-cli prints the message and the update screen
shows it in place of a success line, so the refusal is visible rather than a
silent no-op.

Two paths stay open, because the upgrader can't be told what they carry: an
install action names no plugin in hook_extra, so `wp plugin install --force`
and files copied in over SFTP both look like any other install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notice only knew about mbregex, so a site running PHP 8.1 with mbregex
present was told nothing at all: wordpress.org quietly stops offering a
version whose Requires PHP it can't meet, and the editor stayed silent about
it. The editor now receives hasNextPhp and nextPhp alongside canHighlight,
and the notice picks between three messages so it names what this particular
server lacks — mbregex, the PHP version, or both. 8.2 is written once, in
code_block_pro_next_php, and reaches the copy through a placeholder.

The gates themselves stay mbregex-only. Core reads Requires PHP out of the
package and refuses the install on its own, wp-cli included, so holding
1.x back over a version would take away updates core would have allowed
through. Two tests pin that separation.

Playground runs 8.5 with mbregex, so the mu-plugin the spec installs now
fakes both, one request parameter each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The messages opened on the requirement and read like a failure report. They
now open on version 2 arriving and keep one sentence for what this server
lacks, which is the part a site owner repeats to their host. The wrapper
carries vertical margin so the notice isn't wedged between the panel header
and the theme search.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The wrapper carried my-6 while the utilities in this build are emitted as
`.code-block-pro-editor .my-6`, so the class only styles descendants and the
computed margin came out 0. Moved onto an inner div, which measures 24px top
and bottom. An emoji goes with the announcement of version 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
KevinBatdorf and others added 3 commits August 22, 2026 11:03
The editor fell back to a hardcoded 8.2 when the localised value was
missing, so a later bump would have left the notice quoting a version the
check no longer used. The version now only ever comes from PHP, and a notice
with no version to name treats the PHP requirement as unmet rather than
guessing at it. Splitting the three messages apart drops the flag-shaped
argument that decided between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An install action carries no plugin in hook_extra, so uploading the zip or
`wp plugin install --force` walked straight past the refusal that covers
updates. upgrader_source_selection sees the unpacked directory instead, so
the package can be identified from its own headers: any php file in its root
declaring our text domain means it is us, whatever the file or folder is
called. The test builds a package named entry.php for that reason.

Files copied in over SFTP still can't be caught — no WordPress code runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mbregex compiles in or out as a unit, so one probe answers for the family on
any normal build. disable_functions doesn't work that way: a host can hide
mb_ereg_search_getregs and leave mb_ereg_search_init in place, and the gate
would wave the update through to a site that fatals on render. The check now
covers all four functions phiki calls, and a test pins the list so a
narrowing goes noticed.

Blocking a capable site wrongly only leaves it on 1.x. Letting an incapable
one through breaks its pages.

The announcement draft names the same four.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant