i plan to return and edit this message in the future, when useful input is received, when i change my mind, when i become smarter and figure a better way to do something, when i lose my mind ad post some crazy stuff etc.
this message outlines my views about hooks and plugins.
there are many details, some of them might be presented outside of logical order.
the ideas presented here are strongly influenced by what i understand about the way phorum hooks and plugin system works. if you feel this to be an insult to your "we are the best, always had been and always will be" sense, sorry.
i do not claim to fully understand phorum's system, and i do not claim to follow it loyally. this is more in the "inspired by phorum", but maybe here and there i *did* understand their system better than i think, in which cases this will be "imitating phorum". so i claim no original thought and idea in the description below.
it is, if you will, my mental image of what phorum does, which fits reality somewhere between "not at all" and "completely".
Chapter 1: what is a hook?
at the very first question i run into confusion.
a hook is a function of certain form, and at the same time it's a point in the code where such function is called.
strictly speaking, i should use two different names for these two different terms, but i am lazy, i can't think of better names, and almost always, it will be clear from the context which of the two is discussed. this may change later on when someone smarter than me comes with better names.
So, a "hook" is a point in the code where we call any plugin that wishes to be called at this specific point.
let's look at the post_message() Api call.
currently, we have an Api call that looks roughly so:
post_message($poster, $forum, $topic, $message)
where $poster and $message are objects, and $forum and $topic are IDs.
the code itself looks roughly so:
Code: Select all
function post_message($poster, $forum, $topic, $message)
{
$result = "";
// .. do all kinds of stuff, including assigning new values to $result
return $result;
}
Code: Select all
function post_message($poster, $forum, $topic, $message)
{
$params = array('poster' => $poster, 'forum' => &$forum, 'topic' => &$topic, 'message' => $message, 'result' => "");
if (! call_hook('hook_before_post_message', &$param))
{
// if call_hook returned false it means we should not continue.
return $param['result'];
// notice that this call may modify each of the parameters. 2 are "by ref", and 2 are objects, i.e. "by ref by def"
}
$result = "";
// do all the stuff you should do.
call_hook('hook_after_post_message', $param, &$result)); // no need to look at the return value: this is the end anyway.
return $result;
}
we can also place hook calls in the templates, so:
Code: Select all
<!-- {HOOK before_show_password_inputfiled} -->
Chapter 2: What is a plugin?
A plugin is a bunch of source files in PHP (ideally one), (possibly) a bunch of resources (images, language files, etc.), and one metadata file.
the php source contain one or more functions (also sometimes called "hooks", although maybe they should be called "eyes", or "hookeys" or "hookers" ).
all these functions have one thing in common: they always accept one by-ref parameter, and they all return boolean.
it is perfectly fine for a plugin to contain new hooks itself, so "secondary" (or "ternary" etc.) plugins that require this one can be written to modify the behavior of this plugin.
all strings used by the plugin are contained in a single language file. the language file name is directly derived from the plugin name.
a plugin package can contain as many translations as it wants, although an English one is generally expected.
when installing a plugin that lacks translation to language X on a board that supports language X, the installation infrastructure will contain a "translate it yourself" service for the admin that wishes to install it.
the plugin meta-data contains:
- information about the plugin: Category, Name, Author, Version, Description
- A single readme file
- a list of hooking points. each hooking point is:
- the hook itself (our "hook_before_post_message" from above),
- the name of the function to be called call at this point.
this name will always be prefixed with the plugin name, to avoid name collisions. - a call-order directive that can have the values "prefer-first", "prefer-last" or "dont-care"
- configuration definition, so a plugin that does not require too complex configuration can be configured with a generic configuration service provided by the infrastructure based solely on the metadata.
if more complex configuration is a must, the plugin can contain a management module (php files and templates) to facilitate this. - enough information to facilitate database changes required by the plugin, including uninstall.
the precise rules of what DB changes are "kosher" for a plugin (if any) should be discussed. - list of available languages supported
- dependency information: minimum infrastructure version required by the plugin, and list of other plugins (if any) which are required for this plugin, including minimum versions of each
- known incompatibilities: list of *other* plugins known to be incompatible with this one
- Optional addendum to the FAQ.
The call_hook() function, as shown above, accepts the hook name, and a single by-ref parameter.
it looks at the currently registered addon, and for each of them that registered a hookey to be called at this particular point (as identified by the hook name) it calls them with the by-ref param. after each call it examine the return value. as soon as one of them returns "false", it terminates and return false itself.
it should call all the "prefer-first" hookies before any non-prefer-first hookies, and all "prefer-last" hookies after all the non-prefer-last hookies.
since all the parameter-passing is by ref, every hookie can modify the values to be used by the next hookie or by the original caller itself.
template hooks are similar, except that the parameter that is passed is the template_var, which is, i believe a stateful object.
most typically, a template hook will emit some more html into the template. since it knows nothing about the specific style being used, it should respect all local style parameters.
Chapter 4: Tying it all together
after a new plugin is pushed, (presumably somewhere at a ....../plugins/PLUGIN_NAME/ directory), the user has to go to the "configure plugins" page in the ACP.
the ACP looks at the plugins directory, and find there a new subdirectory. it goes on and read the metadata of the plugin, and adds it to the list. since this plugin was never configured, it appears in the list of disabled plugins.
the user then can view the details (name, author, description, version etc.), configure it (based on the metadata), and eventually enable it.
as soon as the user enable the plugin, we go over all the hookies in this plugin and tie them to all the hooks w have to. (this, of course, i s kept in the database).
when we initialize the code (e.g. at the point we read all the config data from the database) we also read the hooking information and create the global $hooking_info dictionary/structure/object that is used by the call_hook() function.
of course, the same system should allow you to disable existing hooks (including ones that came with the system, i.e., "in the box" ones) at will.
Chapter 5: disabling all hooks
as a safety measure, in case some plugin did something ungodly that actually block the admin from accessing the ACP, we have a safety measure/backdoor: if $_GET['NO_PLUGINS'] == 1, the hook system look at the current logged in user and does one of the three:
-- if it's anonymous, it is redirected to login
-- if it's a regular user, the switch is ignored, and the hooks get called as usual.
-- if it's an admin, no hooks code is executed, and the function returns "true", so in effect the system behaves as if all the hooks are disabled. at this point the manager can get to the ACP and disable the offending plugin.
My Opinion
by placing enough call_hook() calls in the code and templates at the right places, practically no hacks will need to be written.
of stated differently: every time you think you need to write a "real" hack, ask yourself: are there one or twelve places in the code where injecting new hooks would allow me to implement this as a plugin and not a hack?
in 9 cases out of 10 the answer will be "yes" in those cases, new hooks should be injected to the code.
in the one out of ten cases where the answer is "no" ask yourself: "do i really want to do this"? and reply "no".
i did not describe the system that pulls new plugins from the repos. feel free to fill the blanks.
[EDIT]
Added information about metadata, dependency and incompatibility information, some more stuff about translations.
[/EDIT]