Repository navigation
Derive a plugin class's path from its name - #1540
Merged
Merged
Conversation
Namespacing left the plugins half-migrated: every class went into
FOG\Plugins\<Plugin>, but the files stayed where they were, so a plugin
declared FOG\Plugins\LDAP\LDAPManager out of class/ldapmanager.class.php.
Core paid for the mismatch. autoload() could not derive a path, so it
read all 176 plugin files to learn what each declared, cached that in
pluginmap.*.json, and kept a second suffix-scan pipeline alongside it for
discovery.
fog-plugins v1.6.23 lays a plugin out exactly like core --
<plugin>/src/<Bucket>/<Class>.php declaring
FOG\Plugins\<Segment>\<Bucket>\<Class>, file name equal to class name,
strtolower(<Segment>) equal to the plugin directory (ADR 0035). So the
path is derivable, and this is what that buys:
autoload() one is_file() per plugin root, nothing read
pluginShortMap() derived from paths, not from 176 file reads
discovery pluginitems('Pages'), the twin of coreitems('Pages')
Net -310 lines in init.php. Retired: classFileList(), _scanClassFiles(),
_scanRoots(), _readFileListCache(), _isPluginPath(), the classMap build
and lookup, _bridgeNamespaced()'s resolution tail, set_include_path(),
spl_autoload_extensions(), and FOGBase::fileitems().
Retiring the suffix pipeline was not optional. _readFileListCache()
treats an empty list as a cache miss, and with no suffixed files left to
find the list is empty forever -- so leaving it in place would have cost
a full recursive walk of the tree on every request.
The plugin directory name does not move. It is plugins.pName, the
?node= value, the <node>.view permission string and the js/ path, so
there is no schema migration and no RBAC churn.
A plugin still on the pre-1.6 layout is refused with a line naming
itself, the directory it kept and the shape it needs, rather than being
silently absent. Verified against a real v1.6.22 tree, not a fixture.
Plugin discovery itself is untouched: Plugin::_getDirs() still finds a
plugin by config/plugin.config.php, which is also now the definition
_scanPluginSource() uses -- without it, a .git checkout's hooks/
directory reads as a pre-1.6 plugin.
Report classes keep core's underscored names (LDAP_Report). The menu
key, the base64 `f` parameter and the permission node are all derived
from that name, so renaming them would move a report to a different URL
under a different permission node.
Eighteen mutations run against the new and rewritten gates; one came
back green and was a fake gate -- autoload()'s bare-core arm no longer
needs its `return` to be a guard, since nothing maps a bare name to a
file any more. The comment now says that, and the check that replaced it
asserts the diagnostic names the qualified spelling.
Docs: ADR 0035 is new. ADR 0013's namespacing amendment, ADR 0010's task
seam, ADR 0009's "contracts are conventions" note and
docs/composer-psr4-plan.md's "plugins do not live under FOG\" section are
all amended rather than rewritten. docs/plugin-development.md gains a
port table (§11b) and loses the section that taught the rule this
reverses.
FOG_PLUGINS_VERSION moves to v1.6.23 in the same commit deliberately:
old core with new plugins, or new core with old plugins, is each a
server with no working plugins.
Co-Authored-By: Claude <noreply@anthropic.com>
mastacontrola
added a commit
to FOGProject/fog-docs
that referenced
this pull request
Aug 31, 2026
Mirrors FOGProject/fogproject#1540 and FOGProject/fog-plugins#34. A plugin's PHP moved out of class/ pages/ hooks/ events/ reports/ tasks/ and into src/<Bucket>/<Class>.php, declaring FOG\Plugins\<Segment>\<Bucket>\<Class> -- the same PSR-4 arrangement core already used on itself. The plugin DIRECTORY does not move: it is still the routing node, the permission string and the js/ path. plugin-development.md is re-ported from the fogproject copy, so it carries the rewritten §2, §3, §4.2-4.5, §7a, §9a, §10 and §11, loses the "Discovery still works by filename" subsection that taught the rule this reverses, and gains §11b -- the port table for a plugin still on the pre-1.6 layout. plugin-schema-migrations.md's Key files table still named lib/fog/*.class.php paths that moved when core itself went PSR-4; the reference implementation is now lib/plugins/location/src/Managers/LocationManager.php. reports.md is the user-facing half and was teaching a path that no longer exists. A report is packages/web/src/Reports/<Class>.php, or <plugin>/src/Reports/<Class>.php inside a plugin, and the underscores in Fleet_Report are load bearing -- the menu label, the base64 `f` parameter and the permission node are all derived from that name. Two adjacent references corrected while here: the storage-node hook guide pointed at lib/fog/storagegroup.class.php and told authors to write a *.hook.php in lib/hooks/, and the CSV guide named the Location plugin's hook by its old filename. Translations are left alone. scripts/translate.mjs regenerates a page when its source hash moves, so the French copies refresh themselves once a provider is configured. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pairs with FOGProject/fog-plugins#34, which must merge and tag v1.6.23 first. Old core with new plugins, or new core with old plugins, is each a server with no working plugins — which is why the
FOG_PLUGINS_VERSIONpin moves in this same PR.The problem
Namespacing left the plugins half-migrated. Every class went into
FOG\Plugins\<Plugin>, but the files stayed where they were — a plugin declaredFOG\Plugins\LDAP\LDAPManagerout of a file calledclass/ldapmanager.class.php. Core paid for the mismatch:autoload()could not derive a path, so it read all 176 plugin files to learn what each one declared, cached that inpluginmap.*.json, and kept a second suffix-scan pipeline alongside it for discovery.The fix
fog-plugins v1.6.23 lays a plugin out exactly like core:
file name equal to class name,
strtolower(<Segment>)equal to the plugin's directory. So the path is derivable, and that is the whole of this change (ADR 0035):autoload()a plugin classis_file()per plugin root, nothing readpluginShortMap()file_get_contentson a cold cachefileitems('.page.php', 'pages')pluginitems('Pages'), the twin ofcoreitems('Pages')Net −310 lines in
init.php. Retired:classFileList(),_scanClassFiles(),_scanRoots(),_readFileListCache(),_isPluginPath(), the classMap build and lookup,_bridgeNamespaced()'s resolution tail,set_include_path(),spl_autoload_extensions(), andFOGBase::fileitems().Retiring the suffix pipeline was not optional.
_readFileListCache()treats an empty list as a cache miss, and with no suffixed files left to find the list is empty forever — leaving it in place would have cost a full recursive walk of the tree on every request.What deliberately does not change
plugins.pName, the?node=value, the<node>.viewpermission string and thejs/path. No schema migration, no URL change, no RBAC churn.Plugin::_getDirs()still finds a plugin byconfig/plugin.config.php— which is now also the definition_scanPluginSource()uses. Without it, a.gitcheckout'shooks/directory reads as a pre-1.6 plugin and gets reported as one.LDAP_Report). The menu key, the base64fparameter and the permission node are all derived from that name, so renaming would move a report to a different URL under a different permission node.Failing loudly
A plugin still on the pre-1.6 layout gets one line naming itself, the directory it kept and the shape it needs — rather than a page that never registers with nothing anywhere saying why. Verified against a real v1.6.22 plugin tree on this box, not a fixture:
Verification
relationship-filter-in-join.test.phpfails locally only because this box'spackages/web/lib/pluginsis still the v1.6.22 release; it passes oncebin/fetch-plugins.shpulls v1.6.23.autoload()'s bare-core arm no longer needs itsreturnto be a guard, because nothing maps a bare name to a file any more. Rather than leave a check that could not fail, the comment now says thereturnis explicitness, and the replacement check asserts the diagnostic names the qualified spelling — mutation-verified red.qualify()answering short names correctly (OUAssociation→FOG\Plugins\OU\Items\OUAssociation,OIDCFlow→FOG\Plugins\OIDC\Util\OIDCFlow).Documentation
Tom asked for the docs to land with the code, so they are in here rather than following.
docs/adr/0035-a-plugin-is-laid-out-like-core.md.docs/composer-psr4-plan.md's "Plugins do not live underFOG\" section.docs/plugin-development.md: §2, §3, §4.2–4.5, §7a, §9a, §10 and §11 rewritten; the "Discovery still works by filename" subsection is the claim being reversed and is gone; new §11b is the port table for a plugin still on the old layout.docs/PLUGIN_SCHEMA_MIGRATIONS.md: its Key files table still namedlib/fog/*.class.phppaths that moved in the core PSR-4 work.A fog-docs PR mirrors the user-facing half separately.
Downstream
No route classes added or removed, so FogApi's hardcoded class list is unaffected.