Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,37 @@ Each directory is one plugin, laid out exactly as it appears under
```
<plugin>/
config/plugin.config.php the manifest
class/ model + manager (schema migrations live here)
pages/ the management page
hooks/ hook registrations
tasks/ scheduled background work (optional)
src/ ALL PHP, laid out exactly like core's src/
Items/ models
Managers/ managers (schema migrations live here)
Pages/ the management page
Hooks/ hook registrations
Events/ event listeners (optional)
Reports/ reports (optional)
Tasks/ scheduled background work (optional)
Util/ plain helpers (optional)
js/ fog.<node>.<sub>.js
```

A `tasks/<name>.task.php` declares a class extending `PluginTask` with an
**`<plugin>/src/<Bucket>/<Class>.php` declares
`FOG\Plugins\<Segment>\<Bucket>\<Class>`**, with the file name equal to the
class name and `strtolower(<Segment>)` equal to the plugin's directory name.
`tests/plugin-layout.test.php` gates both. The autoloader derives the path from
the class name, so a file in the wrong place does not load and a class in the
wrong bucket is never registered. See
[ADR 0035](https://github.com/FOGProject/fogproject/blob/working-1.6/docs/adr/0035-a-plugin-is-laid-out-like-core.md);
the pre-1.6 `class/ pages/ hooks/` layout is refused with a message naming the
plugin.

`Pages`, `Hooks`, `Events`, `Reports` and `Tasks` are the buckets core
enumerates. Everything else is autoload-only, so the name is yours — these are
core's, and using them means someone who knows core knows your plugin.

A `src/Tasks/<Class>.php` declares a class extending `PluginTask` with an
`$interval` and a `run()`, and the `FOGPluginRunner` daemon runs it while the
plugin is active and installed — a plugin never ships a systemd unit of its
own. It runs as the web user rather than root, and `run()` has to be
idempotent. See `helloworld/tasks/helloworldheartbeat.task.php` for a worked
idempotent. See `helloworld/src/Tasks/HelloWorldHeartbeat.php` for a worked
example and ADR 0010 in `FOGProject/fogproject` for why it is shaped this way.

Requires FOG **1.6.0-beta.3350** or newer. The runner itself landed in
Expand All @@ -72,31 +91,32 @@ the rest.
| `ldap` | 1.6.0 | Authenticate FOG users against an LDAP or AD directory |
| `location` | 1.6.0 | Serve images from the storage node nearest a host — multi-site installs |
| `ntfy` | 1.6.0 | Notifications via ntfy.sh or a self-hosted ntfy server |
| `oidc` | 1.6.0 | Sign in through an OpenID Connect provider; the reference for a plugin that adds a route rather than a resource |
| `ou` | 1.6.0 | Predefine Active Directory OUs and associate them with hosts |
| `persistentgroups` | 1.6.0 | On joining a group, copy image, AD, printer and location settings from a template host named after that group |
| `pushbullet` | 1.6.0 | Pushbullet notifications |
| `site` | 1.6.0 | Group hosts into sites; limit which hosts a user can see |
| `slack` | 1.6.0 | Slack API integration |
| `subnetgroup` | 1.6.0 | Assign hosts to groups automatically by IP subnet |
| `taskstateedit` | 1.6.0 | Edit and create task states |
| `tasktypeedit` | 1.6.0 | Edit and create task types |
| `windowskey` | 1.6.0 | Associate Windows product keys with images |
| `wolbroadcast` | 1.6.0 | Wake-on-LAN across separate broadcast addresses |

`site` is a special case. Per
`site` used to be listed here and is not a plugin any more. Per
[ADR 0006](https://github.com/FOGProject/fogproject/blob/working-1.6/docs/adr/0006-site-object-scope-boundary.md)
the object-scope boundary is default-allow, so **with no listener the boundary
does not exist**. `site` is therefore always shipped and must not become
something an admin can uninstall or a half-failed upgrade can remove. It lives
here as source; it is not a candidate for the external plugin root.
does not exist** — which made a plugin an admin could uninstall, or a
half-failed upgrade could remove, the wrong home for it. Site moved into
`FOGProject/fogproject` proper; nothing here replaces it.

## Writing a plugin

The full guide is
[`docs/plugin-development.md`](https://github.com/FOGProject/fogproject/blob/working-1.6/docs/plugin-development.md)
in the FOG repository — manifest fields, the `schema()` migration contract,
hook events, the permission registry, and the gotchas that cost the most time.
`helloworld/` here is the working skeleton it describes.
`helloworld/` here is the working skeleton it describes, and §11b of that guide
is the port table for a plugin still on the pre-1.6 layout.

Third-party plugins belong in your own repository. Ship a `.tar.gz` holding
one directory named for the plugin with `config/plugin.config.php` inside it,
Expand Down
20 changes: 13 additions & 7 deletions bin/qualify-core-references.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
/**
* Rewrites bare references to FOG core classes into fully qualified ones.
*
* Plugins are global-namespace by design (ADR 0009) and have always named
* core classes bare -- `extends Hook`, `Route::listem()`, `new Image()`.
* Those resolve only because every file under fogproject's packages/web/src/
* ends in a class_alias() re-exporting itself globally, and that alias set is
* being retired (fogproject docs/composer-psr4-plan.md, ADR 0013 §2).
* Plugins used to be global-namespace and named core classes bare --
* `extends Hook`, `Route::listem()`, `new Image()`. Those resolved only
* because every file under fogproject's packages/web/src/ ended in a
* class_alias() re-exporting itself globally, and that alias set was retired
* (fogproject docs/composer-psr4-plan.md, ADR 0013 §2).
*
* This qualifies them: `extends \FOG\Base\Hook`, `\FOG\Router\Route::listem()`.
* The plugin stays in the global namespace -- only the names it reaches into
* core with change.
* It changes only the names a plugin reaches into CORE with; its own
* namespace declaration is not this tool's business.
*
* Written for that one-time sweep and kept as a check. Plugins have since
* taken namespaces of their own and then core's layout as well (ADR 0035), so
* a plugin file is src/<Bucket>/<Class>.php declaring
* FOG\Plugins\<Segment>\<Bucket>\<Class> -- which this tool does not care
* about, since it walks by file extension and rewrites by token.
*
* The map is read from a fogproject checkout rather than hardcoded, because
* the bucket a class lives in is fogproject's to decide and a stale copy here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Hooks;

/**
* Creates the capone menu item.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Hooks;

/**
* Injects capone stuff into the api system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Hooks;

/**
* Sets the javascript files up for this plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Hooks;

/**
* Adds the capone menu item.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Items;

/**
* Handles the database for Capone plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Managers;

/**
* Manager class for Capone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Pages;

/**
* The capone page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Capone;
namespace FOG\Plugins\Capone\Util;

/**
* This is only used for capone plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Hooks;

/**
* Injects Hello World into the API system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Hooks;

/**
* Injects the Hello World JS files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
*
* Hooks register callbacks against named events in their constructor, but
* ONLY after confirming the plugin is installed (the $pluginsinstalled
* guard). Class AddHelloWorldMenuItem must live in the file
* addhelloworldmenuitem.hook.php (lowercased class name + .hook.php).
* guard). Class AddHelloWorldMenuItem must live in src/Hooks/ under its own
* exact name -- src/Hooks/ is the directory core LISTS to find hooks to
* construct, so a hook filed anywhere else registers nothing and says
* nothing (fogproject ADR 0035).
*
* PHP version 5
*
Expand All @@ -16,7 +18,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Hooks;

/**
* Adds the Hello World menu item.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@
*
* NOTE: FOG's own autoloader, Initiator::autoload(), resolves the class --
* there is no spl_autoload fallback (fogproject ADR 0013 §2b removed it, since
* it let a plugin shadow a core class by filename). The filename still has to
* match the class name (case-insensitively), because that match is how
* Initiator finds the file. So class HelloWorld must live in the file
* helloworld.class.php, and declares namespace FOG\Plugins\Helloworld.
* it let a plugin shadow a core class by filename). It DERIVES the file from
* the class name, so path and name are the same fact written twice:
* FOG\Plugins\HelloWorld\Items\HelloWorld is
* helloworld/src/Items/HelloWorld.php and nothing else. Rename one, rename
* both. The plugin directory stays lowercase -- it is also the routing node
* and the permission string -- while the namespace segment carries the
* casing, and lowercasing the segment must land back on the directory name
* (fogproject ADR 0035).
*
* Items/ is not enumerated by anything: a model is loaded when something
* names it. Pages/, Hooks/, Events/, Reports/ and Tasks/ ARE enumerated, so
* a class in the wrong one of those is loadable and never registered.
*
* PHP version 5
*
Expand All @@ -23,7 +31,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Items;

/**
* Hello World example plugin (model).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Managers;

/**
* Hello World example plugin (manager).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Pages;

/**
* Hello World example plugin (management page).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
* runner keeps next-run times in memory, so a service restart makes every
* task immediately due, and a run that throws is retried next cycle.
*
* NAMING, and this one bites: the class name must match the filename minus
* .task.php, and it shares ONE global namespace with every other class in
* FOG -- core models included. A file named host.task.php would collide with
* the core Host model. Prefix with your plugin's name, as here.
* NAMING: the file is src/Tasks/<Class>.php and the class is
* FOG\Plugins\HelloWorld\Tasks\<Class> -- the path IS the name (fogproject
* ADR 0035), and src/Tasks/ is the directory the runner lists to find work to
* do. A task filed elsewhere is loadable and never runs.
*
* Your namespace is your own, so a class called Host here would not collide
* with the core Host model. What is still shared is the SHORT name, which is
* what getClass('X') and FOGController::getManager() resolve, and core wins
* that: a plugin class whose short name matches a core class is reachable
* only by its FQCN. Prefixing with the plugin's name, as here, sidesteps it.
*
* PHP version 5
*
Expand All @@ -35,7 +41,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Helloworld;
namespace FOG\Plugins\HelloWorld\Tasks;

/**
* Counts this plugin's rows on a schedule and writes the number to the log.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

use FOG\Plugins\LDAP\Items\LDAPGroup;

/**
* Injects LDAP stuff into the api system.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

/**
* Associates directory groups from the role and user group pages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

/**
* Sets the javascript files up for this plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

/**
* Adds the menu item for this plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

/**
* Adds the ldap type to the reports/exports items
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

use FOG\Plugins\LDAP\Items\LDAPUserGrant;

/**
* Deletes the LDAP plugin elements en-mass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Hooks;

use FOG\Plugins\LDAP\Items\LDAPUserGrant;

/**
* LDAPPluginHook enables our checks as required
Expand Down
2 changes: 1 addition & 1 deletion ldap/class/ldap.class.php → ldap/src/Items/LDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Items;

/**
* LDAP Authentication plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Items;

/**
* A directory group this server is willing to recognise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @link https://fogproject.org
*/

namespace FOG\Plugins\Ldap;
namespace FOG\Plugins\LDAP\Items;

/**
* Association between a directory group and a FOG role.
Expand Down
Loading