How to create your own hooks

Discussion in 'IPS' started by BrandonSheley, Dec 2, 2009.

  1. BrandonSheley

    BrandonSheley loving life

    2,663
    1,072
    +926
    I found this guide at the IPB site created by Alex.

    IP.Board 3 now includes a hook and plugin management system. As the name implies, a 'hook' is a point in the code execution where a modification author can tell IP.Board to execute his or her code, and then return to the primary code execution. A 'plugin' is simply a collection of hooks packaged together (for example a modification may require 3 hooks, but is still one modification).

    There are hook points included in the conditional tags in the skin so that you can hook into the output easily, and you can 'overload' any class or function to add your own code. This provides a lot of functionality out of the box, without the need of asking for more hook points into files manually. The term overload means that you can supply a function that uses the same name as one already defined, and yours will be called instead (you will then need to call the parent function manually).

    The hook system has a lot of benefits to it, with the major benefit being upgrades. When users upgrade to a newer release of IP.Board, users of your modification will not have to reapply modifications if you use the hook system, because the hook is a standalone file which would not be affected by an upgrade.

    As a developer, you are encouraged to use these hook points where possible to reduce the need to edit the core code.

    For reference, all hooks need to be stored in the hooks folder, in the root of the forums.

    Hook types
    The hooks system in IP.Board supports three main types of hooks: action and skin overloaders, and template hooks.

    Action Overloaders
    Action overloaders are methods in your hook file which override the default functions of the class you are overloading. If you are familiar with PHP and OOP, what actually happens behind the scenes is your class extends an existing action class in IPB, and your function is then called instead of the default one within the IPB action file that normally would be called. The structure of an action overloader is very simple. All you need to do on your end is create the PHP file, add it to the Admin CP and export it. IP.Board packages the hook in an XML file which users can install through the Admin CP themselves at the click of a button. We will go over the packaging and exporting of hooks later in this article.

    Action overloaders can be useful for processing form fields you have added to a form (through a template hook, for instance), or for executing additional PHP code before outputting a page. We do not ship IP.Board with any examples of action overloaders for you to reference.

    hooks/myFirstActionOverloader.php
    PHP:
    <?php

    class myFirstActionOverloader extends public_forums_forums_boards
    {
    public function 
    doExecuteipsRegistry $registry )
    {
    $content 'Welcome to the forums!';

    if ( 
    $this->memberData['g_access_cp'] == )
    {
    $content '<h3>You are an administrator on this forum!</h3>';
    }

    $this->registry->getClass('output')->addContent$content );
    parent::doExecute$registry );
    }    
    }

    • The class name must be unique to avoid clashes with other hooks. It should also be the same as the filename.
    • Action overloaders must extend an action class already called by IP.Board. Action classes are, basically, files within each application's modules_public directory. In this example we are extending the main board index ('public_forums_forums_boards')
    • To extend a function within this class, just redeclare the function itself. For example in this hook we are overloading the main doExecute function, and adding our own content to the top of it. You cannot overload an IPB method that is declared as 'private'. You can overload public and protected methods only.
    • It is important that you return to the primary code execution, otherwise the proper page will not be displayed. Therefore you must return to the parent class by called the same method again (parent::doExecute( $registry ))


    Skin Overloaders
    A skin overloader is very similar to an action overloader, but instead of overloading an action method, it overloads a skin function. These can be useful when you need to show content at the top or bottom of a page, when you wish to check a new permission setting to determine if the HTML should be shown to the user, or when you want to radically change the page structure.

    Whilst you could use template hooks to accomplish a lot of things, if you needed to override an entire template, a skin overloader is the best way to do it. You will notice that functionally, this works almost the same as an action overloader. You can refer to the Blog hook 'boardIndexFeaturedEntry' for an example of a skin overloader, if you have access to it.

    hooks/myFirstSkinOverloader.php
    PHP:
    class myFirstSkinOverloader extends skin_boards(~id~)
    {
    public function 
    boardIndexTemplate$lastvisit=""$stats=array(), $calendar_events=FALSE$birthdays=FALSE$chat_html=''$news_data=array(), $cat_data=array(), $show_side_blocks=true$side_blocks=array() )
    {
    $output '<h3>Welcome to our forums!</h3>';
    $output .= parent::boardIndexTemplate$lastvisit$stats$calendar_events$birthdays$chat_html$news_data$cat_data$show_side_blocks$side_blocks );

    return 
    $output;
    }
    }

    • It is important that skin overloaders do not contain PHP tags as the code is eval()'d in the runtime of IP.Board, and eval()'d code cannot have PHP tags.
    • The class name must, still, be unique to avoid clashes with other hooks. It should also be the same as the filename.
    • Skin overloaders must extend a skin class already called by IP.Board. Additionally, you must add the shortcut key '(~id~)' at the end of the class name - this is because cached skin files in IP.Board actually append the skin id to the class (e.g. 'skin_boards_2'), and so we have this token in place so that we can correct the classname on the fly.
    • To extend a function within the skin file, just redeclare the function itself. For example in this hook we are overloading the boardIndexTemplate skin template, and adding our own content to the top of it. All skin functions are 'public', so you can extend any template you wish.
    • It is important that you return to the primary code execution, otherwise the proper page will not be displayed. Therefore you must return to the parent class by called the same method again (parent::doExecute( $registry )). It is, of course, up to you if you call the parent method before appending your content, or whether you perform your own execution and then call the parent method. You could even conditionally call different skin templates, or not call the parent method (though this means, of course, that the default IPB content will not be output).
    • You should be sure to return the HTML result when you are finished.


    Template Hooks
    By and far, you will find that you can accomplish most things that you may wish to accomplish through template hooks. Most hooks, ultimately, are designed to add content to the page after all.

    Any if statement or foreach statement in any skin file that has been tagged can be hooked into. Please refer to the following examples where the tag we will need to look for is 'hookTag'.

    Code:
    <if test="hookTag:|:$ifStatement">
    
    <foreach loop="hookTag:$array as $piece">
    Template hooks are self-contained classes. This has a few implications we will discuss below. First, an example:

    hooks/myFirstTemplateHook.php
    PHP:
    <?php

    class myFirstTemplateHook
    {
    public 
    $registry;

    public function 
    __construct()
    {
    $this->registry ipsRegistry::instance();
    }

    public function 
    getOutput()
    {
    return 
    "Welcome to our site!";
    }
    }

    • The class name must, still, be unique to avoid clashes with other hooks. It should also be the same as the filename.
    • Template hooks do not need to extend any class. However, as they do not extend an existing class (where the registry shortcuts have already been established) it is up to you to create any registry shortcuts you intend to use in your constructor.
    • You must define a single function 'getOutput()' that will return the HTML you wish to insert at the hook point. You can specify in the admin CP whether you want to insert the HTML before or after the opening or closing of the mapped if, else or foreach statement.
    • There are, naturally, a finite number of template hook tag points. If you find you wish to insert hook code somewhere in the skin that there is no defined template hook point located at, you can easily add one yourself like so:

      Code:
      <if test="myCustomHookPoint:|:1===1"></if>
      The above code does nothing useful, but it will create a hook point in the skin that you can then use.


    Applying your hook
    Now that you have created your custom hook, you must register it in the admin CP to tell IP.Board that it needs to be executed. Under the System tab in the ACP, click on Manage Hooks, and then click the 'Create New Hook' button at the top of the page. You will see a basic information form that you should fill in.

    At the bottom you will then need to click 'Add Another File' to associate your hook file. First fill in your hook's filename and classname, and then choose what type of hook this is. Your selection will then conditionally add form fields to allow you to specify where your hook executes. If you choose 'Action Overloader' or 'Skin Overloader' you will be asked which class the hook extends (for skin overloaders, you can enter just the skin filename (e.g. 'skin_boards')). For template hooks, you will be presented with a dropdown to allow you to select which skin file the hook point is in. Once you select the skin file you will be asked which function in the skin file to look in. Upon selecting the function name, you will be asked if this hook point is found in a foreach tag or an if statement tag. After you select one of these options, and then select the hook tag name, you will be presented with one more option.

    For foreach loop hook points you can select one of the following:

    • outer.pre: Your template hook HTML content will be inserted into the page before the foreach loop is executed.
    • inner.pre: Your template hook will be executed at the beginning of the foreach loop (note that this means it is executed once for each iteration of the foreach statement)
    • inner.post: Your template hook will be executed at the end of the foreach loop (again, this means it is executed once for each iteration of the foreach statement)
    • outer.post: Your template hook HTML content will be inserted after the results of the foreach statement.


    Here is an example to help you visualize where foreach hook points are executed

    Code:
    //outer.pre hook points would execute here
    
    foreach( $array as $piece )
    {
    // inner.pre hook points would execute here
    
    $myCodeHere = '';
    
    // inner.post hook points would execute here
    }
    
    // outer.post hook points would execute here
    For if statement hook points you can select one of the following:

    • pre.startif: Your template hook HTML content will be inserted into the page before the if statement is executed.
    • post.startif: Your template hook will be executed within the if statement.
    • pre.else: Your template hook will be executed before the else statement is called
    • post.else: Your template hook will be executed at the start of the else statement
    • pre.endif: Your template hook will be executed at the end of the if statement
    • post.endif: Your template hook HTML content will be inserted after the if statement has finished executing.


    It may be hard to visualize how some of these locations are possible (e.g. pre.else) but that is because internally IP.Board uses the ternary operator to perform if and else statements. See the following example to visualize where hook points might execute at

    Code:
    // pre.startif hook points execute here
    
    if( $phpStatement )
    {
    // post.startif hook points execute here
    
    $ifStatementCode = '';
    }
    // pre.else hook points would execute here
    else
    {
    // post.else hook points execute here
    
    $elseStatementCode = '';
    
    // pre.endif hook points execute here
    }
    
    // post.endif hook points execute here
    Generally speaking, you will likely use pre.startif or post.endif, and outer.pre or outer.post, locations to add HTML content to a page near where an existing hook point is located, however feel free to make use of any of the above options as needed.

    Afterwards, you can add more files in the same manner if you need to. For instance, you may need to add a template hook to add an additional form field to the page, and then an action overloader to process the submitted form input, for a single plugin. When you are done adding the needed files, save the hook.

    Your hook should be installed and ready for use at this point. Load up the forums, test the changes, and verify everything works. Once you are sure everything works, you may wish to export your hooks for others to use. You do this by clicking 'Export Hook' after clicking on the options button for your hook on the Manage Hooks page.

    You will notice that you can associate many pieces of data in IP.Board with your hook for export. At this time you can associate all of the following if you wish to:


    • Settings (and setting groups)
    • Language strings
    • Skin Templates
    • Modules (specifically, the XML definitions)
    • Help Files
    • Tasks
    • Custom database changes (create table, alter table, insert data, and update data)
    • Custom install/uninstall script


    A walk through process assist you with associating any of the above with your hook. When you do this, and you export your hook, the necessary data is then included with the hook export. Afterwards, when users import your hook, the data is inserted/executed automatically, providing for a much easier 'installation' routine for end users. Database changes are monitored, and where possible if a user uninstalls a hook, IP.Board will attempt to undo the database changes. If all else fails, however, the hook installation and uninstallation routines support execution of a custom install/uninstall script, which you can create and associate during the export process.

    Note that any of these things that you define will be saved and remembered for the next time you need to export the hook.

    When you are done, click Export and you are presented with the XML export that you can distribute to users. The users can then import this XML file on the Manage Hooks index screen, and they will then have access to your custom hook from their hooks management screen.

    One last thing
    The hooks system supports an optional function to allow you to indicate to users when a new version of a particular hook is available. When you are creating your hook, you would have noticed the settings 'Hook Version' and 'Update URL'. If you define these two things, IP.Board will call out to the defined 'Update URL' when listing the hooks in the ACP to verify if the hook is the latest version or not. The process is relatively simple.

    If I create a hook and version the first release as '10000' (1.0.0) I can then also define the Update URL that the ACP should call out to. For instance, I could enter 'http://my-website.com/external/hookVersionCheck.php', and then place this file at the location I specified. This file should accept the incoming $_GET parameter 'version' (which will be equivalent to 10000 in the above example) and verify if it is the latest version or not. If I later release an update and call the version '10001' (1.0.1), I can verify this in hookVersionCheck.php, and print a 0 to the screen. Otherwise, if the hook is up to date, I should print a 1. The IP.Board ACP will then show an out of date message if 0 is the result returned, and the user will have a link to your website presented (as defined when creating the hook) so that they can retrieve the update.

    You are not required to make use of this feature, but it is a convenience that is available should you wish to.

    Conclusion
    Hooks have an implementation that should prove simple yet powerful. Through hooks you can provide changes to the IP.Board software that will not be wiped out upon each upgrade of the core software, and that can be updated independently without requiring the user to modify or re-modify the software manually. Additionally, because hooks can be associated with many different pieces of data (such as settings, or language strings), most modification authors will find that for many smaller and medium modifications, hooks provide all the functionality they need for an installation routine without creating a full application.
     
Verification:
Draft saved Draft deleted
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.