A hook is an entry point within a script where plugins can be executed to modify script's behavior. You can also treat it as an event which can be handled by third-party code. Cotonti literally includes PHP files registered as handlers for a specific hook. Imagine a script that has a hook in it:
1 2 3 4 5 6 7 8 |
$foo = 'The Lord of Foo' ; $bar = 'hit the bar!' ; // ... // A HOOK is here // ... echo "$foo $bar" ; |
Then consider there is a handler for that hook which can access any variables available in the scope it hooks into, for example like this:
1 |
$foo .= ' and his mighty friends' ; |
During runtime Cotonti compiles them into a solid sequence:
1 2 3 4 5 6 |
$foo = 'The Lord of Foo' ; $bar = 'hit the bar!' ; $foo .= ' and his mighty friends' ; echo "$foo $bar" ; |
So hooks are simply your friends which give you the power to modify the behavior of existing code. Cotonti has lots of them, so you can modify almost anything.
The first question you need to answer before writing another part of your plugin is what exactly you want to extend. And this means choosing a hook where the plugin part will be executed. The set of available hooks depends on the module which is being executed, the part of that module and the actual events that happen during its execution. Their handlers are fired as the appropriate events happen.
There are several hooks which are present in any flow and are fired in the same sequence relatively to each other:
Recource
class or in cases to overwrite theme/language/resources and some other parameters ;Also depending on executed part of your site there are some special cases hooks:
As we speak about core hooks we need to mention 3rd group as well — «system functions extenders». This type of hooks fired while execution some system function. Let's count it:
cot_die_message()
cot_load_structure()
cot_parse()
cot_generate_usertags()
cot_outputfilters()
(already described above).
There are actually lots more hooks in the system. To get the list of all hooks fired during script execution in the order they are present in the code, you can use special {FOOTER_HOOKS} tag which should be put into your theme's footer.tpl. It also requires debug mode to be enabled in datas/config.php:
1 |
$cfg [ 'debug_mode' ] = true; |
(Note: the hook list feature is available since 0.9.6)
A few words about hook names and their meanings. Most hook names consist of several words separated with dots. For example:
The first word usually means the module or the main part where the hook exists. For example 'users' means the users module, or 'header' means the header part. This is the first hint for you to find the actual place in source code of the hook. The next word often means the "mode" or the "part" of the module or script, for example 'users.register' means that the hook is in user registration and it also means that you can probably find it in a file called users.register.php. In MVC terms you may also consider this the controller of a module. What follows next is the composite name of the event or action that you can handle with it, as well as the state of the event or the location of the hook (first, last, loop, etc.). In the above example it is 'add.done' which means that a new user has been added (event) successfully (state). In an MVC scenario the event/action could be a method name.
So, by the name of the hook you can as suppose both the file where you can find it and the event that it handles. You should follow the same convention when defining your own hooks. More on that later.
A special type of hook is the loop hook. The name of a loop hook usually ends with '.loop', e.g. 'forums.topics.sections.loop'. While regular hooks are fired for events which normally happen once per request, loop hooks are fired on every iteration of a loop. The most common scenario for this is within an SQL result row, where the hook is present in every row of the SQL result set.
A hook handler is a PHP file in extension's folder which has a name similar to the hook name that it uses. This is not a requirement, but it's a common convention to use filenames that match the hook, such as myext.page.edit.update.done.php which uses the page.edit.update.done hook.
A typical hook handler's template is represented below:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php /* ==================== [BEGIN_COT_EXT] Hooks=page.tags Tags=page.tpl:{PAGE_MYEXT_TAG1},{PAGE_MYEXT_TAG2} Order=10 [END_COT_EXT] ==================== */ defined( 'COT_CODE' ) or die ( 'Wrong URL' ); // Hook handler code goes here |
First let's consider hook header (settings between BEGIN_COT_EXT and END_COT_EXT). The only one which is required is Hooks setting which lists the hooks where this part is included. The same part (file) can be assigned to multiple hooks, in which case they should be separated by a comma. Other settings are optional and can be omitted.
Tags setting is used if a part assigns some new tags in a template. You can safely omit this parameter even if it assigns some tags, the only purpose for filling it is to notify the user of your extension on extension's page in Administration panel about availability and presense of those tags.
The same hook can be handled by multiple extensions at the same time, so if several files hook into the same entry point in some cases it is reasonable to give some of them a priority over the others. In such a case the Order settings is useful, because handlers with higher priority (lower Order value, e.g. 5) are executed first and with higher Order are executed later (e.g. 99). The default Order is 10 and it can be omitted.
What can you do in your hook handler? Almost anything! Here are a few humble examples:
An important thing to be aware of is variable scope. Due to its traditional script flow, Cotonti makes intensive use of global variables. Most hooks are located in the global variable scope, so you can use them out of the box. Some hooks are located within functions, so you need to define globals explicitly in them. Most frequently used globals are $cfg, $usr, $db, $id, $strucutre, $t. They actually deserve another article to explain their meaning and usage.
Be noted that since 0.9.15 all main global variables, like ($cfg, $usr, $db, $id, $strucutre, etc.) can be accessed through Cot
facade class. So you don't need to use global
keyword, use Cot
class instead: Cot::$cfg
(as example). Full list of available variables within facade: $cache, $cfg, $cot_extrafields, $db, $db_x, $env, $L, $out, $R, $structure, $sys, $usr.
The main advice for beginner Cotonti extension developers: start by reading the code. If you wonder how plugins are written, have a look at existing ones. If you wonder how you can modify script's behavior, search for the location where the hook is defined and see what is available there. Cotonti is coded in a simple and straightforward manner, so its code is very easy to understand. Once you have understood the default behavior you can easily modify it. For more complex plugins you'll often have to use multiple hooks that work together, but you'll figure that out once you've worked with hooks a couple of times.
Though in many cases you want to provide something on your own without even having a look at the code where it hooks. For example you don't need to know how header works if you want to assign some new tags in page header or load some data from the database and display it.
So, you know how to use existing hooks, but what if you want other developers to be able to extend the behavior of your module? The answer is simple: put a hook in it. It is as simple as adding the following code:
1 2 3 4 5 |
/* === Hook === */ foreach (cot_getextplugins( 'myext.hook.name' ) as $pl ) { include $pl ; } /* ===== */ |
Replace 'myext.hook.name' with the actual name of the hook. You might notice that same code could be written just in 1 line, but we usually prefer the above 6-line construct to make hooks easier to distinct from surrounding code.
A more complex situation is loop hooks. Handlers of such hooks are executed on every iteration of the loop. Here is an example of such a hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* === Hook - Part1 : Set === */ $extp = cot_getextplugins( 'myext.some.loop' ); /* ============ */ // It can be foreach/while/do, whatever you like foreach ( $items as $item ) { // Some code here /* === Hook - Part2 : Include === */ foreach ( $extp as $pl ) { include $pl ; } /* ============ */ // Possibly some code here } |
It is recomended to provide a hook for each valuable event in your module that could be handled for some reasonable purpose, e.g. all create/update/delete/display events.
Depending on the part of the site to be executed there are different hooks fired. Let's explore hook chains for some common cases:
Note: Items marked with "(*)" sign are executed several times in a loop, and hooks enclosed in brakets "[]" are depends on used Cotonti version and actual extension list, hooks marked with bold are common type hooks (described above).