Hooks

What hooks are and how to use them in your extensions

Эта страница еще не переведена на украинский. Отображен оригинал страницы на английском языке. Вы можете зарегистрироваться и помочь с переводом.

#1. What is a Hook?

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:

$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:

$foo .= ' and his mighty friends';

During runtime Cotonti compiles them into a solid sequence:

$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.

#2. Choosing the right hook

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.

#2.1. Common hooks

There are several hooks which are present in any flow and are fired in the same sequence relatively to each other:

  • input — this is the first extension point in the script's flow. At this point configuration, structure, extensions and user objects are available but the system hasn't been initialized completely yet. May be used in special cases to control flow by overriding some system data.
  • rc (ignored in AJAX mode) —  usually used to load any extension related scripts via Recource class or in cases to overwrite theme/language/resources and some other parameters ;
  • global — this is for the code which needs to be executed on every request right after system initialization;
  • header.first - is executed before page header is generated;
  • header.main (ignored in AJAX mode) — here you can modify some header variables before they get rendered;
  • header.tags  (ignored in AJAX mode) — here you can set more header tags in addition to standard ones;
  • footer.first — is called before rendering page footer;
  • footer.main  (ignored in AJAX mode— here you can modify some footer variables before they get rendered;
  • footer.tags  (ignored in AJAX mode— here you can set more footer tags in addition to standard ones;
  • footer.last - is executed after page footer has been rendered, no matter if AJAX is used or not;
  • output - this is the last hook which is right before output buffers are flushed and the script is shut down. It can be used to manipulate output buffers and release some resources.

Also depending on executed part of your site there are some special cases hooks:

  • popup — handler for pop-uped loaded windows;
  • stanalone — an entry point for pages related to certain plugin only;
  • module — same as 'standalone', but used for modules;
  • ajax — hook used only in cases of ajax requested data;
  • tools — an entry point for Admin panel tools page for certain plugin;
  • admin — same as `tools` but used for modules

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: 

  • die.message in cot_die_message()
  • structure in cot_load_structure()
  • parser.last fromcot_parse()  
  • usertags.first and usertags.main from cot_generate_usertags()  
  • output in 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:

$cfg['debug_mode'] = true;

(Note: the hook list feature is available since 0.9.6)

#2.2. Naming convention

A few words about hook names and their meanings. Most hook names consist of several words separated with dots. For example:

Hook naming convention

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.

#3. Coding a hook handler

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:

<?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:

  • Run database queries to fetch and manipulate with data.
  • Modify current variables and objects.
  • Assign template tags and parse template blocks.

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.

#4. Putting hooks in your modules

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:

/* === 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:

/* === 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.

#4.1. Common Hooks list

Depending on the part of the site to be executed there are different hooks fired. Let's explore hook chains for some common cases:

  • index page requested: input, rc, global, [pagetags.first, pagetags.main], parser.last (*), index.first, index.main, header.first, header.main, header.body, header.user.tags, header.tags, index.tags, [polls.index.tags, news.first, news.loop, news.tags], parser.last (*), [usertags.first, usertags.main], parser.last (*), footer.first, footer.main, footer.tags, footer.last, output;
  • some news pageinput, [urleditor.rewrite.first,] rc, global, [pagetags.first, pagetags.main, parser.last, page.first, page.main,] header.first, header.main, header.body, header.user.tags, header.tags, parser.last (*), usertags.first, usertags.main, parser.last (*), page.tags, comments.count.query, comments.main, comments.newcomment.tags, comments.query, comments.tags, parser.last (*), ratings.main, ratings.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • standalone plugin page requested: input, urleditor.rewrite.first, rc, global, [pagetags.first, pagetags.main, parser.last (*), search.first, search.page.catlist, search.list, search.tags,] header.first, header.main, header.body, header.user.tags, header.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • AJAX data request: input, rc, global, [pagetags.first, pagetags.main, parser.last (*),] ajaxfooter.last
  • Admin tools for module pageinput, rc, global, adminadmin.main, [forums.admin.first, forums.admin.tags,] header.first, header.main, header.body, header.user.tags, header.tags, admin.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • Admin tools for plugin pageinput, rc, global, tools, admin.main, admin.other.first, [admin.comments.first, admin.comments.loop, admin.comments.tags,] header.first, header.main, header.body, header.user.tags, header.tags, admin.tags, footer.first, footer.main, footer.tags, footer.last, output;

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).


Коментарі відсутні
Додавання комментарів доступно лише зареєстрованим користувачам