Easily Extend SMF’s Integration Hooks

With the releases of SMF 2.0 RC4 and RC5, many new integration hooks were added to allow mod authors to extend the code without hacking it up. Previously to this, the primary use of these hooks was for integration with bridges or other systems, no one really used them for modifications. SimpleSEF was the first mod to really exploit these hooks and use them to accomplish some pretty major tasks. What I am presenting here is a simple PHP class that can be used to easily extend SMF using it’s integration hooks with very little effort. If you are fairly new to PHP or new to PHP5’s OOP model, you should turn away now.

SMF’s integration hooks have always allowed the use of calling static methods within PHP classes. This is fine and dandy and it ‘works’ but use a class full of static methods is almost no better than using procedural code with globally scoped functions. I wanted the ability to use OOP in the manner it was meant to be used. To overcome this, I created a small class that contains a couple of static methods that SMF can call from the integration hooks, which in turn can make calls to an instantiated object. This class can also automatically register the hooks you want to use with SMF, either persistently in SMF’s settings table, or at runtime via the add_integration_function() function.

The class uses the singleton design pattern, allowing us to retrieve an instantiated object from a static context. Once the object is created and stored in the class, it can be used by a static hook callback to determine which hook SMF called, and what function in the object to call based on that hook.

Here is the code for the class.
[code lang=”php”]/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is http://code.mattzuba.com code.
*
* The Initial Developer of the Original Code is
* Matt Zuba.
* Portions created by the Initial Developer are Copyright (C) 2010-2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** */

if (!class_exists(‘SMFSingleton’)):
class SMFSingleton
{
/**
* @var SMF_Singleton Instance of the bridge class
*/
protected static $instance;

/**
* @var array Comma separated list of hooks this class implements
*/
protected $hooks = array();

/**
* @var boolean Should the hooks only be installed once?
*/
protected $persistHooks = FALSE;

/**
* This should be overwritten
*/
protected function __construct()
{
if (!$this->persistHooks)
$this->installHooks();
}

/**
* Installs the hooks to be used by this module.
*/
public function installHooks()
{
foreach ($this->hooks as $hook)
add_integration_function($hook, static::$__CLASS__ . ‘::handleHook’, $this->persistHooks);
}

/**
* Takes all call_integration_hook calls from SMF and figures out what
* method to call within the class
*/
public static function handleHook()
{
$backtrace = debug_backtrace();
$method = NULL;
$args = NULL;
foreach ($backtrace as $item)
if ($item[‘function’] === ‘call_integration_hook’)
{
$method = $item[‘args’][0];
$args = !empty($item[‘args’][1]) ? $item[‘args’][1] : array();
break;
}

if (!isset($method) || !is_callable(array(self::$instance, $method)))
trigger_error(‘Invalid call to handleHook’, E_USER_ERROR);

return call_user_func_array(array(self::$instance, $method), $args);
}

/**
* Let’s try the singleton method
*
* @return void
*/
public static function getInstance()
{
if (!isset(static::$__CLASS__))
trigger_error(‘<strong>protected static $__CLASS__ = __CLASS__;</strong> must be contained in child class’, E_USER_ERROR);

if (!isset(self::$instance) || !(self::$instance instanceof static::$__CLASS__))
self::$instance = new static::$__CLASS__();

return self::$instance;
}
}
endif;[/code]

Here is an example of how to use the class to add a button to the main menu that links to Google.com.
[code lang=”php”]<?php

require_once($sourcedir . ‘/SMFSingleton.php’);

class GoogleButton extends SMFSingleton
{
protected static $__CLASS__ = __CLASS__;

/**
* Setup the object, gather all of the relevant settings
*/
protected function __construct()
{
$this->hooks = array(
‘integrate_menu_buttons’,
);

parent::__construct();
}

protected function integrate_menu_buttons(&$menu_buttons)
{
global $context, $user_info;

$menu_buttons[‘google’] = array(
‘title’ => ‘Google’,
‘href’ => ‘http://www.google.com’,
‘is_last’ => !$context[‘right_to_left’],
);

// Need to set the previous last button to not be last
$menu_buttons[$user_info[‘is_guest’] ? ‘register’ : ‘logout’][‘is_last’] = FALSE;
}
}

GoogleButton::getInstance();
[/code]

And here is a sample package-info.xml file demonstrating how to install the mod.
[code lang=”xml”]<?xml version="1.0"?>
<!DOCTYPE package-info SYSTEM "http://www.simplemachines.org/xml/package-info">

<package-info xmlns="http://www.simplemachines.org/xml/package-info" xmlns:smf="http://www.simplemachines.org/">
<id>username:googlebutton</id>
<name>Google Menu Button</name>
<version>0.1</version>
<type>modification</type>

<install for="2.0 RC5">
<require-file name="SMFSingleton.php" destination="$sourcedir">Hooks Framework</require-file>
<require-file name="GoogleButton.php" destination="$sourcedir">Google Button mod</require-file>
<code type="inline"><![CDATA[<?php
add_integration_function(‘integrate_pre_include’, ‘$sourcedir/GoogleButton.php’);
]]></code>
</install>

<uninstall for="2.0 RC5">
<code type="inline"><![CDATA[<?php
remove_integration_function(‘integrate_pre_include’, ‘$sourcedir/GoogleButton.php’);
]]></code>
<remove-file name="$sourcedir/GoogleButton.php" />
<remove-file name="$sourcedir/SMFSingleton.php" />
</uninstall>
</package-info>
[/code]

There are some caveats to this. If multiple mod authors use this, there is the possibility that upon uninstall, you could remove the file that other mods depend on. I would recommend including the class in your own file and either renaming it to something like YourModSingleton or leave the class_exists check. Please see the terms of the MPL if you decide to use this as well.

Once SimpleMachines.org opens up a ‘Library’ category on the customize site, this will likely be submitted, so instead of including it in your own modifications, you could require that people install this framework before installing your mod.

  • Pingback: PHP OOP Tutorial 9 | Static Classes | PHP5 Web Hosting()

  • http://arwym.com Arwym

    Such an excellent resource. :3 I’ll definitely play with this. Thanks!

  • http://http://missallsunday.com Miss All Sunday

    WOW definitely something I will use in the near future, many thanks!