Skip to content

fix(plugin): load nothing next to Pro instead of deactivating - #252

Merged
nk-o merged 2 commits into
masterfrom
pro-plugin-guard
Aug 30, 2026
Merged

nk-o merged 2 commits into
masterfrom
pro-plugin-guard

Conversation

@nk-o

@nk-o nk-o commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Ghost Kit Pro ships this plugin's core inside core-plugin/, so the two declare the same
classes and only one of them may run. That was enforced by deactivating whichever of the pair
was not just activated, on top of an older class_exists guard that only holds while Pro
happens to be included first. Both can now stay activated instead: the standalone plugin reads
the active plugin list before it defines anything and returns when Pro is there, so none of its
own code loads.

The check reads the option rather than leaning on class_exists because the include order is
not ours to choose. WordPress includes network-activated plugins before site-activated ones,
and a third party can reorder active_plugins; in either case this plugin wins the race and
Pro ends up running its modules against a core it did not ship. Testing
plugin_basename( __FILE__ ) is what separates the two roles this one file plays: the
standalone plugin's basename is in the list, the copy Pro bundles resolves to
ghostkit-pro/core-plugin/… and never is, so Pro keeps loading its own core. Copies embedded
in a theme or another plugin resolve outside the plugins directory and are likewise unaffected.

This has to ship as 3.7.1 or later. FREE_PLUGIN_MIN_VERSION is the floor below which an
installed free copy is still deactivated, because those releases declare the core class a
second time instead of standing aside. Carrying this change in a release numbered below the
floor leaves it inert.

Deactivation is narrowed rather than removed, and it never touches Pro any more. Activating
this plugin next to Pro no longer switches Pro off; deactivating Pro is now the way back to the
free plugin. It also refuses to go quiet for a Pro that WordPress will not load, since
active_plugins goes on naming a plugin whose directory was deleted by hand and the site would
be left with neither.

Ghost Kit Pro needs no change of its own: it carries no bootstrap-level deactivation, and it
picks this up through the core-plugin submodule bump in its own pull request. The equivalent
change is open in visual-portfolio and lazy-blocks; the Visual Portfolio one additionally
reads the Pro version, because that Pro does have such a bootstrap.

Verified in wp-env for Visual Portfolio, whose diff is identical to this one after normalising
names: Pro alone loads its bundled core, both plugins active in either include order leave only
this file included and returning, the free plugin alone is unchanged, and a free copy below the
floor is still deactivated. npm run lint and npm run test:unit:php pass here.

nk-o added 2 commits August 31, 2026 00:13
Ghost Kit Pro ships this core inside itself, so the two must never run together.
That was enforced by deactivating whichever plugin was not just activated,
on top of a `class_exists` guard that only holds while Ghost Kit Pro is included
first.

The standalone plugin now reads the active plugin list before it defines
anything and returns when Ghost Kit Pro is there, so the guarantee no longer
depends on include order: network-activated plugins are included before
site-activated ones, and a third party can reorder `active_plugins`.
Testing `plugin_basename( __FILE__ )` keeps the copy bundled in Ghost Kit Pro
loading, along with copies embedded in a theme or another plugin.

Deactivation now reaches only free versions below 3.7.1, which declare the
core class a second time instead of standing aside. Ghost Kit Pro is never
deactivated.
`active_plugins` goes on naming a plugin whose directory was removed by
hand, and WordPress simply skips it while the option keeps the row. Reading
the option alone meant this plugin could go quiet for a Ghost Kit Pro that never
loads, leaving the site with neither. Check that the Pro main file is there
before stepping aside.

The version floors gain an `-alpha` suffix so `bump:prerelease` builds of
the same version count as new enough. `version_compare()` ranks a
pre-release below the release it precedes.

Guard the plugin header read behind `file_exists` as well, since
`get_plugin_data()` reads the file without checking that it is there.
@nk-o
nk-o merged commit 5bd24f0 into master Aug 30, 2026
7 checks passed
@nk-o
nk-o deleted the pro-plugin-guard branch August 30, 2026 22:53
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