wiclear-2007-07-19/inc/classes/plugin_manager.class.php

<?php
# ***** BEGIN LICENSE BLOCK *****
# This file is part of WiClear.
# Copyright (c) 2004-2007 David Jobet. All rights
# reserved.
#
# WiClear is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# WiClear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with DotClear; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
#
# ***** END LICENSE BLOCK *****

/**
 * \brief manages plugin
 *
 * enable to load plugin on demand when they are really used.
 * This avoid consuming too much memory/cpu for code that exist but won't be necessary used.
 */
class PluginManager
{
  // constant
  var $rootDir;
  var $toolsBaseDir = 'tools/'; //!< tools base dir

  // plugin hooks
  var $contentBoxHandle       = array(); //!< array(mode)=>array(plugin_name, callback)
  var $userActionHandle       = array(); //!< array(mode)=>array(plugin_name, callback)
  var $browserTitlePageHandle = array(); //!< array(mode)=>array(plugin_name, callback)
  var $javascriptLibs         = array(); //!< array(mode)=>array(javascript lib)

  // current plugin we're loading hooks from
  var $current_plugin = '';      //!< used when importing plugins to properly store plugins info
  var $loaded_plugins = array(); //!< list of already loaded plugins
  var $loaded_i18n    = array(); //!< list of already loaded i18n

  // general callback for plugins
  var $pluginCallbacks = array();
  var $onPluginCallbacks = array();
  var $validatePluginCallbacks = array();

  // ACL hooks for plugins
  var $pluginACLs = array();    //!< array[acl_type=>description]

  function PluginManager($rootDir)
  {
    $this->rootDir = $rootDir;
  }

  /**
   * \brief allow a plugin to add an handle to contentBox area
   *
   * \param mode the url mode
   * \param callback the callback function to callback when mode is used in url
   */
  function addContentBoxHandle($mode, $callback)
  {
    $this->contentBoxHandle[$mode] = array($this->current_plugin, $callback);
  }

  /**
   * \brief allow a plugin to add an handle when reacting to user submission
   *
   * \param mode the url mode
   * \param callback the callback function to callback when mode is used in url
   */
  function addUserActionHandle($mode, $callback)
  {
    $this->userActionHandle[$mode] = array($this->current_plugin, $callback);
  }

  /**
   * \brief allow a plugin to customize browser title
   *
   * \param mode the url mode
   * \param callback the callback function to callback when mode is used in url
   */
  function addBrowserTitlePageHandle($mode, $callback)
  {
    $this->browserTitlePageHandle[$mode] = array($this->current_plugin, $callback);
  }

  /**
   * \brief allow a plugin to add a javascript lib
   *
   * \param mode the url mode
   * \param $jsLib the javascript library to add when plugin is used
   */
  function addJavascriptLibs($mode, $jsLib)
  {
    if (!array_key_exists($mode, $this->javascriptLibs))
    {
      $this->javascriptLibs[$mode] = array();
    }

    $this->javascriptLibs[$mode][] = $jsLib;
  }

  /**
   * \brief allow a plugin to add a callback from core code known hooks
   *
   * \param $clb the new callback to register
   */
  function addPluginCallback($clb)
  {
    $this->pluginCallbacks[] = $clb;
  }

  /**
   * \brief allow the main site to callback a plugin
   *
   * \param where is an identifier that can be exploited by a plugin to know which part of the site is calling back
   * \param extra is an extra parameter that can be of any type, depending of which part of the site is calling back
   */
  function pluginCallback($where, &$extra)
  {
    foreach ($this->pluginCallbacks as $clb)
    {
      $clb($where, $extra);
    }
  }

  /**
   * \brief allow a plugin to define a new type of acl
   *
   * \param acl_type acl_type of ACL
   * \param desc string describing acl
   */
  function addPluginACL($acl_type, $desc)
  {
    if ($acl_type >= acl_plugin_start && !array_key_exists($acl_type, $this->pluginACLs))
    {
      $this->pluginACLs[$acl_type] = $desc;
    }
  }

  /**
   * \brief retrieve all plugin defined ACLs
   *
   * \return array<acl_type=>description>
   */
  function getPluginACLs()
  {
    return $this->pluginACLs;
  }

  /**
   * \brief allow a plugin to add a callback from core code known hooks
   *
   * \param $clb the new callback to register
   */
  function addOnPluginCallback($clb)
  {
    $this->onPluginCallbacks[] = $clb;
  }

  /**
   * \brief allow the main site to callback a plugin when reacting to user
   *
   * \param where is an identifier that can be exploited by a plugin to know which part of the site is calling back
   * \param data is an array of data that may be of interest to plugin
   */
  function onPluginCallback($where, $data)
  {
    foreach ($this->onPluginCallbacks as $clb)
    {
      $clb($where, $data);
    }
  }

  /**
   * \brief allow a plugin to validate content from core code known hooks
   *
   * \param $clb the new callback to register
   */
  function addValidatePluginCallback($clb)
  {
    $this->validatePluginCallbacks[] = $clb;
  }

  /**
   * \brief allow the main site to callback a plugin when reacting to user
   *
   * \param where is an identifier that can be exploited by a plugin to know which part of the site is calling back
   * \param data is an array of data that may be of interest to plugin
   * \param reason inout parameter indicating the reason of the validation reject
   * \return true if content was validated, false otherwise. In the latter case, variable 'reason' indicates the reason.
   */
  function validatePluginCallback($where, $data, &$reason, $debug = false)
  {
    foreach ($this->validatePluginCallbacks as $clb)
    {
      if ($clb($where, $data, $reason, $debug) === false)
      {
        return false;
      }
    }

    return true;
  }

  /**
   * \brief called in prepend.inc.php when setuping world : it imports metadata from plugins
   *
   * This function is used when setuping world. It lists all plugins found in plugin
   * directory, verify mandatory files are presend (hooks.inc.php and index.php), then
   * require hooks.inc.php so that plugin can callback methods 
   * <ul>
   *   <li>addContentBoxHandle
   *   <li>addUserActionHandle
   *   <li>addBrowserTitlePageHandle
   *   <li>addJavascriptLibs
   * </ul>
   */
  function importPlugins()
  {
    $pluginsDir = $this->rootDir.$this->toolsBaseDir;

    // import plugins
    if (file_exists($pluginsDir))
    {
      $h = opendir($pluginsDir);
      while (($file = readdir($h)) !== false)
      {
        if ($file != '..' && is_dir($pluginsDir.$file))
        {
          $this->current_plugin = $file;

          // this is a valid tool dir, check required, mandatory files
          if (file_exists($pluginsDir.$file.'/hooks.inc.php') && 
              file_exists($pluginsDir.$file.'/index.php')
             )
          {
            require $pluginsDir.$file.'/hooks.inc.php';
          }
        }
      }
      closedir($h);
    }

    $this->current_plugin = '';
  }

  /**
   * \brief load a plugin only once. THis is based on plugin name.
   *
   * It is called when the PluginManager detects a plugin us being used, or if the
   * plugin wants to load the complete plugin.
   */
  function loadPlugin($plugin, $mode = '')
  {
    // inject javascript in global javascript structure
    if (array_key_exists($mode, $this->javascriptLibs))
    {
      $jsLibsToLoad = $this->javascriptLibs[$mode];

      global $javascriptLibs;
      foreach ($jsLibsToLoad as $jsLibToLoad)
      {
        if (in_array($jsLibToLoad, $javascriptLibs) == false)
        {
          $javascriptLibs[] = $jsLibToLoad;
        }
      }
    }

    if (in_array($plugin, $this->loaded_plugins))
    {
      // already loaded, nothing to do
      return;
    }

    // load plugin
    $pluginsDir = $this->rootDir.$this->toolsBaseDir;
    require $pluginsDir.$plugin.'/index.php';

    $this->loadI18N($plugin);

    $this->loaded_plugins[] = $plugin;
  }

  /**
   * \brief load i18N of plugin only once. THis is based on plugin name.
   *
   * It is called when the PluginManager detects a plugin us being used, or if the
   * plugin wants to load i18N.
   */
  function loadI18N($plugin)
  {
    if (in_array($plugin, $this->loaded_i18n))
    {
      // already loaded, nothing to do
      return;
    }

    // load i18n file if any
    global $ui_lang;
    global $translation;
    $pluginsDir = $this->rootDir.$this->toolsBaseDir;
    if (!empty($translation) && file_exists($pluginsDir.$plugin.'/i18n') && $ui_lang != 'en')
    {
      $translation->completeFrom($pluginsDir.$plugin.'/i18n', $ui_lang, 'en', '.lang');
    }

    $this->loaded_i18n[] = $plugin;
  }

  /**
   * \brief internal function that detects when a plugin is being used, load it, and handle callback
   */
  function handleMode($mode, &$hooks)
  {
    if (array_key_exists($mode, $hooks))
    {
      $plugin   = $hooks[$mode][0];
      $callback = $hooks[$mode][1];
      $this->loadPlugin($plugin, $mode);
      return $callback();
    }
  }

  /**
   * \brief called when handling contentBox by boxes.lib.php, load plugin before handling callback
   */
  function contentBoxHandle($mode)
  {
    return $this->handleMode($mode, $this->contentBoxHandle);
  }

  /**
   * \brief called when reacting to user input, load plugin before handling callback
   */
  function handleUserAction($mode)
  {
    $this->handleMode($mode, $this->userActionHandle);
  }

  /**
   * \brief called when customizing browser title, load plugin before handling callback
   */
  function browserTitlePageHandle($mode)
  {
    return $this->handleMode($mode, $this->browserTitlePageHandle);
  }
}
?>