Aleksander Machniak
2012-12-21 c4781306a54006727b05a5b8a5414e09c51b8bef
CS fixes
2 files modified
1510 ■■■■ changed files
program/lib/Roundcube/rcube_plugin.php 639 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_plugin_api.php 871 ●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_plugin.php
@@ -3,7 +3,7 @@
/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2009, The Roundcube Dev Team                       |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -25,334 +25,353 @@
 */
abstract class rcube_plugin
{
  /**
   * Class name of the plugin instance
   *
   * @var string
   */
  public $ID;
    /**
     * Class name of the plugin instance
     *
     * @var string
     */
    public $ID;
  /**
   * Instance of Plugin API
   *
   * @var rcube_plugin_api
   */
  public $api;
    /**
     * Instance of Plugin API
     *
     * @var rcube_plugin_api
     */
    public $api;
  /**
   * Regular expression defining task(s) to bind with
   *
   * @var string
   */
  public $task;
    /**
     * Regular expression defining task(s) to bind with
     *
     * @var string
     */
    public $task;
  /**
   * Disables plugin in AJAX requests
   *
   * @var boolean
   */
  public $noajax = false;
    /**
     * Disables plugin in AJAX requests
     *
     * @var boolean
     */
    public $noajax = false;
  /**
   * Disables plugin in framed mode
   *
   * @var boolean
   */
  public $noframe = false;
    /**
     * Disables plugin in framed mode
     *
     * @var boolean
     */
    public $noframe = false;
  protected $home;
  protected $urlbase;
  private $mytask;
    protected $home;
    protected $urlbase;
    private $mytask;
  /**
   * Default constructor.
   *
   * @param rcube_plugin_api $api Plugin API
   */
  public function __construct($api)
  {
    $this->ID = get_class($this);
    $this->api = $api;
    $this->home = $api->dir . $this->ID;
    $this->urlbase = $api->url . $this->ID . '/';
  }
  /**
   * Initialization method, needs to be implemented by the plugin itself
   */
  abstract function init();
  /**
   * Attempt to load the given plugin which is required for the current plugin
   *
   * @param string Plugin name
   * @return boolean True on success, false on failure
   */
  public function require_plugin($plugin_name)
  {
    return $this->api->load_plugin($plugin_name);
  }
  /**
   * Load local config file from plugins directory.
   * The loaded values are patched over the global configuration.
   *
   * @param string $fname Config file name relative to the plugin's folder
   * @return boolean True on success, false on failure
   */
  public function load_config($fname = 'config.inc.php')
  {
    $fpath = $this->home.'/'.$fname;
    $rcube = rcube::get_instance();
    if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
      rcube::raise_error(array(
        'code' => 527, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Failed to load config from $fpath"), true, false);
      return false;
    /**
     * Default constructor.
     *
     * @param rcube_plugin_api $api Plugin API
     */
    public function __construct($api)
    {
        $this->ID      = get_class($this);
        $this->api     = $api;
        $this->home    = $api->dir . $this->ID;
        $this->urlbase = $api->url . $this->ID . '/';
    }
    return true;
  }
    /**
     * Initialization method, needs to be implemented by the plugin itself
     */
    abstract function init();
  /**
   * Register a callback function for a specific (server-side) hook
   *
   * @param string $hook Hook name
   * @param mixed  $callback Callback function as string or array with object reference and method name
   */
  public function add_hook($hook, $callback)
  {
    $this->api->register_hook($hook, $callback);
  }
    /**
     * Attempt to load the given plugin which is required for the current plugin
     *
     * @param string Plugin name
     * @return boolean True on success, false on failure
     */
    public function require_plugin($plugin_name)
    {
        return $this->api->load_plugin($plugin_name);
    }
  /**
   * Unregister a callback function for a specific (server-side) hook.
   *
   * @param string $hook Hook name
   * @param mixed  $callback Callback function as string or array with object reference and method name
   */
  public function remove_hook($hook, $callback)
  {
    $this->api->unregister_hook($hook, $callback);
  }
    /**
     * Load local config file from plugins directory.
     * The loaded values are patched over the global configuration.
     *
     * @param string $fname Config file name relative to the plugin's folder
     *
     * @return boolean True on success, false on failure
     */
    public function load_config($fname = 'config.inc.php')
    {
        $fpath = $this->home.'/'.$fname;
        $rcube = rcube::get_instance();
  /**
   * Load localized texts from the plugins dir
   *
   * @param string $dir Directory to search in
   * @param mixed  $add2client Make texts also available on the client (array with list or true for all)
   */
  public function add_texts($dir, $add2client = false)
  {
    $domain = $this->ID;
    $lang   = $_SESSION['language'];
    $langs  = array_unique(array('en_US', $lang));
    $locdir = slashify(realpath(slashify($this->home) . $dir));
    $texts  = array();
    // Language aliases used to find localization in similar lang, see below
    $aliases = array(
        'de_CH' => 'de_DE',
        'es_AR' => 'es_ES',
        'fa_AF' => 'fa_IR',
        'nl_BE' => 'nl_NL',
        'pt_BR' => 'pt_PT',
        'zh_CN' => 'zh_TW',
    );
    // use buffering to handle empty lines/spaces after closing PHP tag
    ob_start();
    foreach ($langs as $lng) {
      $fpath = $locdir . $lng . '.inc';
      if (is_file($fpath) && is_readable($fpath)) {
        include $fpath;
        $texts = (array)$labels + (array)$messages + (array)$texts;
      }
      else if ($lng != 'en_US') {
        // Find localization in similar language (#1488401)
        $alias = null;
        if (!empty($aliases[$lng])) {
          $alias = $aliases[$lng];
        }
        else if ($key = array_search($lng, $aliases)) {
          $alias = $key;
        if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
            rcube::raise_error(array(
                'code' => 527, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Failed to load config from $fpath"), true, false);
            return false;
        }
        if (!empty($alias)) {
          $fpath = $locdir . $alias . '.inc';
          if (is_file($fpath) && is_readable($fpath)) {
            include $fpath;
            $texts = (array)$labels + (array)$messages + (array)$texts;
          }
        return true;
    }
    /**
     * Register a callback function for a specific (server-side) hook
     *
     * @param string $hook     Hook name
     * @param mixed  $callback Callback function as string or array
     *                         with object reference and method name
     */
    public function add_hook($hook, $callback)
    {
        $this->api->register_hook($hook, $callback);
    }
    /**
     * Unregister a callback function for a specific (server-side) hook.
     *
     * @param string $hook     Hook name
     * @param mixed  $callback Callback function as string or array
     *                         with object reference and method name
     */
    public function remove_hook($hook, $callback)
    {
        $this->api->unregister_hook($hook, $callback);
    }
    /**
     * Load localized texts from the plugins dir
     *
     * @param string $dir        Directory to search in
     * @param mixed  $add2client Make texts also available on the client
     *                           (array with list or true for all)
     */
    public function add_texts($dir, $add2client = false)
    {
        $domain = $this->ID;
        $lang   = $_SESSION['language'];
        $langs  = array_unique(array('en_US', $lang));
        $locdir = slashify(realpath(slashify($this->home) . $dir));
        $texts  = array();
        // Language aliases used to find localization in similar lang, see below
        $aliases = array(
            'de_CH' => 'de_DE',
            'es_AR' => 'es_ES',
            'fa_AF' => 'fa_IR',
            'nl_BE' => 'nl_NL',
            'pt_BR' => 'pt_PT',
            'zh_CN' => 'zh_TW',
        );
        // use buffering to handle empty lines/spaces after closing PHP tag
        ob_start();
        foreach ($langs as $lng) {
            $fpath = $locdir . $lng . '.inc';
            if (is_file($fpath) && is_readable($fpath)) {
                include $fpath;
                $texts = (array)$labels + (array)$messages + (array)$texts;
            }
            else if ($lng != 'en_US') {
                // Find localization in similar language (#1488401)
                $alias = null;
                if (!empty($aliases[$lng])) {
                    $alias = $aliases[$lng];
                }
                else if ($key = array_search($lng, $aliases)) {
                    $alias = $key;
                }
                if (!empty($alias)) {
                    $fpath = $locdir . $alias . '.inc';
                    if (is_file($fpath) && is_readable($fpath)) {
                        include $fpath;
                        $texts = (array)$labels + (array)$messages + (array)$texts;
                    }
                }
            }
        }
      }
        ob_end_clean();
        // prepend domain to text keys and add to the application texts repository
        if (!empty($texts)) {
            $add = array();
            foreach ($texts as $key => $value) {
                $add[$domain.'.'.$key] = $value;
            }
            $rcube = rcube::get_instance();
            $rcube->load_language($lang, $add);
            // add labels to client
            if ($add2client) {
                if (is_array($add2client)) {
                    $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
                }
                else {
                    $js_labels = array_keys($add);
                }
                $rcube->output->add_label($js_labels);
            }
        }
    }
    ob_end_clean();
    // prepend domain to text keys and add to the application texts repository
    if (!empty($texts)) {
      $add = array();
      foreach ($texts as $key => $value)
        $add[$domain.'.'.$key] = $value;
      $rcube = rcube::get_instance();
      $rcube->load_language($lang, $add);
      // add labels to client
      if ($add2client) {
        $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
        $rcube->output->add_label($js_labels);
      }
    }
  }
  /**
   * Wrapper for rcube::gettext() adding the plugin ID as domain
   *
   * @param string $p Message identifier
   * @return string Localized text
   * @see rcube::gettext()
   */
  public function gettext($p)
  {
    return rcube::get_instance()->gettext($p, $this->ID);
  }
  /**
   * Register this plugin to be responsible for a specific task
   *
   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
   */
  public function register_task($task)
  {
    if ($this->api->register_task($task, $this->ID))
      $this->mytask = $task;
  }
  /**
    * Register a handler for a specific client-request action
    *
    * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
    *
    * @param string $action  Action name (should be unique)
    * @param mixed $callback Callback function as string or array with object reference and method name
   */
  public function register_action($action, $callback)
  {
    $this->api->register_action($action, $this->ID, $callback, $this->mytask);
  }
  /**
   * Register a handler function for a template object
   *
   * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
   * will be replaced by the return value if the registered callback function.
   *
   * @param string $name Object name (should be unique and start with 'plugin.')
   * @param mixed  $callback Callback function as string or array with object reference and method name
   */
  public function register_handler($name, $callback)
  {
    $this->api->register_handler($name, $this->ID, $callback);
  }
  /**
   * Make this javascipt file available on the client
   *
   * @param string $fn File path; absolute or relative to the plugin directory
   */
  public function include_script($fn)
  {
    $this->api->include_script($this->resource_url($fn));
  }
  /**
   * Make this stylesheet available on the client
   *
   * @param string $fn File path; absolute or relative to the plugin directory
   */
  public function include_stylesheet($fn)
  {
    $this->api->include_stylesheet($this->resource_url($fn));
  }
  /**
   * Append a button to a certain container
   *
   * @param array $p Hash array with named parameters (as used in skin templates)
   * @param string $container Container name where the buttons should be added to
   * @see rcube_remplate::button()
   */
  public function add_button($p, $container)
  {
    if ($this->api->output->type == 'html') {
      // fix relative paths
      foreach (array('imagepas', 'imageact', 'imagesel') as $key)
        if ($p[$key])
          $p[$key] = $this->api->url . $this->resource_url($p[$key]);
      $this->api->add_content($this->api->output->button($p), $container);
    }
  }
  /**
   * Generate an absolute URL to the given resource within the current
   * plugin directory
   *
   * @param string $fn The file name
   * @return string Absolute URL to the given resource
   */
  public function url($fn)
  {
      return $this->api->url . $this->resource_url($fn);
  }
  /**
   * Make the given file name link into the plugin directory
   *
   * @param string $fn Filename
   */
  private function resource_url($fn)
  {
    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
      return $this->ID . '/' . $fn;
    else
      return $fn;
  }
  /**
   * Provide path to the currently selected skin folder within the plugin directory
   * with a fallback to the default skin folder.
   *
   * @return string Skin path relative to plugins directory
   */
  public function local_skin_path()
  {
    $rcube = rcube::get_instance();
    foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
      $skin_path = 'skins/' . $skin;
      if (is_dir(realpath(slashify($this->home) . $skin_path)))
        break;
    /**
     * Wrapper for rcube::gettext() adding the plugin ID as domain
     *
     * @param string $p Message identifier
     *
     * @return string Localized text
     * @see rcube::gettext()
     */
    public function gettext($p)
    {
        return rcube::get_instance()->gettext($p, $this->ID);
    }
    return $skin_path;
  }
    /**
     * Register this plugin to be responsible for a specific task
     *
     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
     */
    public function register_task($task)
    {
        if ($this->api->register_task($task, $this->ID)) {
            $this->mytask = $task;
        }
    }
  /**
   * Callback function for array_map
   *
   * @param string $key Array key.
   * @return string
   */
  private function label_map_callback($key)
  {
    return $this->ID.'.'.$key;
  }
    /**
     * Register a handler for a specific client-request action
     *
     * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
     *
     * @param string $action  Action name (should be unique)
     * @param mixed $callback Callback function as string
     *                        or array with object reference and method name
     */
    public function register_action($action, $callback)
    {
        $this->api->register_action($action, $this->ID, $callback, $this->mytask);
    }
    /**
     * Register a handler function for a template object
     *
     * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
     * will be replaced by the return value if the registered callback function.
     *
     * @param string $name     Object name (should be unique and start with 'plugin.')
     * @param mixed  $callback Callback function as string or array with object reference
     *                         and method name
     */
    public function register_handler($name, $callback)
    {
        $this->api->register_handler($name, $this->ID, $callback);
    }
    /**
     * Make this javascipt file available on the client
     *
     * @param string $fn File path; absolute or relative to the plugin directory
     */
    public function include_script($fn)
    {
        $this->api->include_script($this->resource_url($fn));
    }
    /**
     * Make this stylesheet available on the client
     *
     * @param string $fn File path; absolute or relative to the plugin directory
     */
    public function include_stylesheet($fn)
    {
        $this->api->include_stylesheet($this->resource_url($fn));
    }
    /**
     * Append a button to a certain container
     *
     * @param array $p Hash array with named parameters (as used in skin templates)
     * @param string $container Container name where the buttons should be added to
     *
     * @see rcube_remplate::button()
     */
    public function add_button($p, $container)
    {
        if ($this->api->output->type == 'html') {
            // fix relative paths
            foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
                if ($p[$key]) {
                    $p[$key] = $this->api->url . $this->resource_url($p[$key]);
                }
            }
            $this->api->add_content($this->api->output->button($p), $container);
        }
    }
    /**
     * Generate an absolute URL to the given resource within the current
     * plugin directory
     *
     * @param string $fn The file name
     *
     * @return string Absolute URL to the given resource
     */
    public function url($fn)
    {
        return $this->api->url . $this->resource_url($fn);
    }
    /**
     * Make the given file name link into the plugin directory
     *
     * @param string $fn Filename
     */
    private function resource_url($fn)
    {
        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
            return $this->ID . '/' . $fn;
        }
        else {
            return $fn;
        }
    }
    /**
     * Provide path to the currently selected skin folder within the plugin directory
     * with a fallback to the default skin folder.
     *
     * @return string Skin path relative to plugins directory
     */
    public function local_skin_path()
    {
        $rcube = rcube::get_instance();
        foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
            $skin_path = 'skins/' . $skin;
            if (is_dir(realpath(slashify($this->home) . $skin_path))) {
                break;
            }
        }
        return $skin_path;
    }
    /**
     * Callback function for array_map
     *
     * @param string $key Array key.
     * @return string
     */
    private function label_map_callback($key)
    {
        return $this->ID.'.'.$key;
    }
}
program/lib/Roundcube/rcube_plugin_api.php
@@ -3,7 +3,7 @@
/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -17,9 +17,9 @@
*/
// location where plugins are loade from
if (!defined('RCUBE_PLUGINS_DIR'))
  define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
if (!defined('RCUBE_PLUGINS_DIR')) {
    define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
}
/**
 * The plugin loader and global API
@@ -29,469 +29,476 @@
 */
class rcube_plugin_api
{
  static protected $instance;
    static protected $instance;
  public $dir;
  public $url = 'plugins/';
  public $task = '';
  public $output;
    public $dir;
    public $url = 'plugins/';
    public $task = '';
    public $output;
    public $handlers = array();
  public $handlers = array();
  protected $plugins = array();
  protected $tasks = array();
  protected $actions = array();
  protected $actionmap = array();
  protected $objectsmap = array();
  protected $template_contents = array();
  protected $active_hook = false;
    protected $plugins = array();
    protected $tasks = array();
    protected $actions = array();
    protected $actionmap = array();
    protected $objectsmap = array();
    protected $template_contents = array();
    protected $active_hook = false;
  // Deprecated names of hooks, will be removed after 0.5-stable release
  protected $deprecated_hooks = array(
    'create_user'       => 'user_create',
    'kill_session'      => 'session_destroy',
    'upload_attachment' => 'attachment_upload',
    'save_attachment'   => 'attachment_save',
    'get_attachment'    => 'attachment_get',
    'cleanup_attachments' => 'attachments_cleanup',
    'display_attachment' => 'attachment_display',
    'remove_attachment' => 'attachment_delete',
    'outgoing_message_headers' => 'message_outgoing_headers',
    'outgoing_message_body' => 'message_outgoing_body',
    'address_sources'   => 'addressbooks_list',
    'get_address_book'  => 'addressbook_get',
    'create_contact'    => 'contact_create',
    'save_contact'      => 'contact_update',
    'contact_save'      => 'contact_update',
    'delete_contact'    => 'contact_delete',
    'manage_folders'    => 'folders_list',
    'list_mailboxes'    => 'mailboxes_list',
    'save_preferences'  => 'preferences_save',
    'user_preferences'  => 'preferences_list',
    'list_prefs_sections' => 'preferences_sections_list',
    'list_identities'   => 'identities_list',
    'create_identity'   => 'identity_create',
    'delete_identity'   => 'identity_delete',
    'save_identity'     => 'identity_update',
    'identity_save'     => 'identity_update',
    // to be removed after 0.8
    'imap_init'         => 'storage_init',
    'mailboxes_list'    => 'storage_folders',
    'imap_connect'      => 'storage_connect',
  );
    // Deprecated names of hooks, will be removed after 0.5-stable release
    protected $deprecated_hooks = array(
        'create_user'       => 'user_create',
        'kill_session'      => 'session_destroy',
        'upload_attachment' => 'attachment_upload',
        'save_attachment'   => 'attachment_save',
        'get_attachment'    => 'attachment_get',
        'cleanup_attachments' => 'attachments_cleanup',
        'display_attachment' => 'attachment_display',
        'remove_attachment' => 'attachment_delete',
        'outgoing_message_headers' => 'message_outgoing_headers',
        'outgoing_message_body' => 'message_outgoing_body',
        'address_sources'   => 'addressbooks_list',
        'get_address_book'  => 'addressbook_get',
        'create_contact'    => 'contact_create',
        'save_contact'      => 'contact_update',
        'contact_save'      => 'contact_update',
        'delete_contact'    => 'contact_delete',
        'manage_folders'    => 'folders_list',
        'list_mailboxes'    => 'mailboxes_list',
        'save_preferences'  => 'preferences_save',
        'user_preferences'  => 'preferences_list',
        'list_prefs_sections' => 'preferences_sections_list',
        'list_identities'   => 'identities_list',
        'create_identity'   => 'identity_create',
        'delete_identity'   => 'identity_delete',
        'save_identity'     => 'identity_update',
        'identity_save'     => 'identity_update',
        // to be removed after 0.8
        'imap_init'         => 'storage_init',
        'mailboxes_list'    => 'storage_folders',
        'imap_connect'      => 'storage_connect',
    );
  /**
   * This implements the 'singleton' design pattern
   *
   * @return rcube_plugin_api The one and only instance if this class
   */
  static function get_instance()
  {
    if (!self::$instance) {
      self::$instance = new rcube_plugin_api();
    }
    return self::$instance;
  }
  /**
   * Private constructor
   */
  protected function __construct()
  {
    $this->dir = slashify(RCUBE_PLUGINS_DIR);
  }
  /**
   * Initialize plugin engine
   *
   * This has to be done after rcmail::load_gui() or rcmail::json_init()
   * was called because plugins need to have access to rcmail->output
   *
   * @param object rcube Instance of the rcube base class
   * @param string Current application task (used for conditional plugin loading)
   */
  public function init($app, $task = '')
  {
    $this->task = $task;
    $this->output = $app->output;
    // register an internal hook
    $this->register_hook('template_container', array($this, 'template_container_hook'));
    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
  }
  /**
   * Load and init all enabled plugins
   *
   * This has to be done after rcmail::load_gui() or rcmail::json_init()
   * was called because plugins need to have access to rcmail->output
   *
   * @param array List of configured plugins to load
   * @param array List of plugins required by the application
   */
  public function load_plugins($plugins_enabled, $required_plugins = array())
  {
    foreach ($plugins_enabled as $plugin_name) {
      $this->load_plugin($plugin_name);
    }
    // check existance of all required core plugins
    foreach ($required_plugins as $plugin_name) {
      $loaded = false;
      foreach ($this->plugins as $plugin) {
        if ($plugin instanceof $plugin_name) {
          $loaded = true;
          break;
    /**
     * This implements the 'singleton' design pattern
     *
     * @return rcube_plugin_api The one and only instance if this class
     */
    static function get_instance()
    {
        if (!self::$instance) {
            self::$instance = new rcube_plugin_api();
        }
      }
      // load required core plugin if no derivate was found
      if (!$loaded)
        $loaded = $this->load_plugin($plugin_name);
      // trigger fatal error if still not loaded
      if (!$loaded) {
        rcube::raise_error(array('code' => 520, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Requried plugin $plugin_name was not loaded"), true, true);
      }
    }
  }
  /**
   * Load the specified plugin
   *
   * @param string Plugin name
   * @return boolean True on success, false if not loaded or failure
   */
  public function load_plugin($plugin_name)
  {
    static $plugins_dir;
    if (!$plugins_dir) {
      $dir = dir($this->dir);
      $plugins_dir = unslashify($dir->path);
        return self::$instance;
    }
    // plugin already loaded
    if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
      return true;
    /**
     * Private constructor
     */
    protected function __construct()
    {
        $this->dir = slashify(RCUBE_PLUGINS_DIR);
    }
    $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
    /**
     * Initialize plugin engine
     *
     * This has to be done after rcmail::load_gui() or rcmail::json_init()
     * was called because plugins need to have access to rcmail->output
     *
     * @param object rcube Instance of the rcube base class
     * @param string Current application task (used for conditional plugin loading)
     */
    public function init($app, $task = '')
    {
        $this->task   = $task;
        $this->output = $app->output;
    if (file_exists($fn)) {
      include($fn);
        // register an internal hook
        $this->register_hook('template_container', array($this, 'template_container_hook'));
      // instantiate class if exists
      if (class_exists($plugin_name, false)) {
        $plugin = new $plugin_name($this);
        // check inheritance...
        if (is_subclass_of($plugin, 'rcube_plugin')) {
          // ... task, request type and framed mode
          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
              && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
              && (!$plugin->noframe || empty($_REQUEST['_framed']))
          ) {
            $plugin->init();
            $this->plugins[$plugin_name] = $plugin;
          }
          return true;
        // maybe also register a shudown function which triggers
        // shutdown functions of all plugin objects
    }
    /**
     * Load and init all enabled plugins
     *
     * This has to be done after rcmail::load_gui() or rcmail::json_init()
     * was called because plugins need to have access to rcmail->output
     *
     * @param array List of configured plugins to load
     * @param array List of plugins required by the application
     */
    public function load_plugins($plugins_enabled, $required_plugins = array())
    {
        foreach ($plugins_enabled as $plugin_name) {
            $this->load_plugin($plugin_name);
        }
      }
      else {
        rcube::raise_error(array('code' => 520, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "No plugin class $plugin_name found in $fn"), true, false);
      }
    }
    else {
      rcube::raise_error(array('code' => 520, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Failed to load plugin file $fn"), true, false);
        // check existance of all required core plugins
        foreach ($required_plugins as $plugin_name) {
            $loaded = false;
            foreach ($this->plugins as $plugin) {
                if ($plugin instanceof $plugin_name) {
                    $loaded = true;
                    break;
                }
            }
            // load required core plugin if no derivate was found
            if (!$loaded) {
                $loaded = $this->load_plugin($plugin_name);
            }
            // trigger fatal error if still not loaded
            if (!$loaded) {
                rcube::raise_error(array(
                    'code' => 520, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Requried plugin $plugin_name was not loaded"), true, true);
            }
        }
    }
    return false;
  }
    /**
     * Load the specified plugin
     *
     * @param string Plugin name
     *
     * @return boolean True on success, false if not loaded or failure
     */
    public function load_plugin($plugin_name)
    {
        static $plugins_dir;
        if (!$plugins_dir) {
            $dir         = dir($this->dir);
            $plugins_dir = unslashify($dir->path);
        }
  /**
   * Allows a plugin object to register a callback for a certain hook
   *
   * @param string $hook Hook name
   * @param mixed  $callback String with global function name or array($obj, 'methodname')
   */
  public function register_hook($hook, $callback)
  {
    if (is_callable($callback)) {
      if (isset($this->deprecated_hooks[$hook])) {
        rcube::raise_error(array('code' => 522, 'type' => 'php',
          'file' => __FILE__, 'line' => __LINE__,
          'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
        $hook = $this->deprecated_hooks[$hook];
      }
      $this->handlers[$hook][] = $callback;
    }
    else
      rcube::raise_error(array('code' => 521, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Invalid callback function for $hook"), true, false);
  }
        // plugin already loaded
        if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) {
            return true;
        }
  /**
   * Allow a plugin object to unregister a callback.
   *
   * @param string $hook Hook name
   * @param mixed  $callback String with global function name or array($obj, 'methodname')
   */
  public function unregister_hook($hook, $callback)
  {
    $callback_id = array_search($callback, $this->handlers[$hook]);
    if ($callback_id !== false) {
      unset($this->handlers[$hook][$callback_id]);
    }
  }
        $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name
            . DIRECTORY_SEPARATOR . $plugin_name . '.php';
        if (file_exists($fn)) {
            include $fn;
  /**
   * Triggers a plugin hook.
   * This is called from the application and executes all registered handlers
   *
   * @param string $hook Hook name
   * @param array $args Named arguments (key->value pairs)
   * @return array The (probably) altered hook arguments
   */
  public function exec_hook($hook, $args = array())
  {
    if (!is_array($args))
      $args = array('arg' => $args);
            // instantiate class if exists
            if (class_exists($plugin_name, false)) {
                $plugin = new $plugin_name($this);
                // check inheritance...
                if (is_subclass_of($plugin, 'rcube_plugin')) {
                    // ... task, request type and framed mode
                    if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
                        && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
                        && (!$plugin->noframe || empty($_REQUEST['_framed']))
                    ) {
                        $plugin->init();
                        $this->plugins[$plugin_name] = $plugin;
                    }
                    return true;
                }
            }
            else {
                rcube::raise_error(array('code' => 520, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "No plugin class $plugin_name found in $fn"),
                    true, false);
            }
        }
        else {
            rcube::raise_error(array('code' => 520, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Failed to load plugin file $fn"), true, false);
        }
    $args += array('abort' => false);
    $this->active_hook = $hook;
    foreach ((array)$this->handlers[$hook] as $callback) {
      $ret = call_user_func($callback, $args);
      if ($ret && is_array($ret))
        $args = $ret + $args;
      if ($args['abort'])
        break;
        return false;
    }
    $this->active_hook = false;
    return $args;
  }
  /**
   * Let a plugin register a handler for a specific request
   *
   * @param string $action Action name (_task=mail&_action=plugin.foo)
   * @param string $owner Plugin name that registers this action
   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
   * @param string $task Task name registered by this plugin
   */
  public function register_action($action, $owner, $callback, $task = null)
  {
    // check action name
    if ($task)
      $action = $task.'.'.$action;
    else if (strpos($action, 'plugin.') !== 0)
      $action = 'plugin.'.$action;
    // can register action only if it's not taken or registered by myself
    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
      $this->actions[$action] = $callback;
      $this->actionmap[$action] = $owner;
    }
    else {
      rcube::raise_error(array('code' => 523, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register action $action; already taken by another plugin"), true, false);
    }
  }
  /**
   * This method handles requests like _task=mail&_action=plugin.foo
   * It executes the callback function that was registered with the given action.
   *
   * @param string $action Action name
   */
  public function exec_action($action)
  {
    if (isset($this->actions[$action])) {
      call_user_func($this->actions[$action]);
    }
    else if (rcube::get_instance()->action != 'refresh') {
      rcube::raise_error(array('code' => 524, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "No handler found for action $action"), true, true);
    }
  }
  /**
   * Register a handler function for template objects
   *
   * @param string $name Object name
   * @param string $owner Plugin name that registers this action
   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
   */
  public function register_handler($name, $owner, $callback)
  {
    // check name
    if (strpos($name, 'plugin.') !== 0)
      $name = 'plugin.'.$name;
    // can register handler only if it's not taken or registered by myself
    if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) {
      $this->output->add_handler($name, $callback);
      $this->objectsmap[$name] = $owner;
    }
    else {
      rcube::raise_error(array('code' => 525, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
    }
  }
  /**
   * Register this plugin to be responsible for a specific task
   *
   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
   * @param string $owner Plugin name that registers this action
   */
  public function register_task($task, $owner)
  {
    // tasks are irrelevant in framework mode
    if (!class_exists('rcmail', false))
      return true;
    if ($task != asciiwords($task)) {
      rcube::raise_error(array('code' => 526, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
    }
    else if (in_array($task, rcmail::$main_tasks)) {
      rcube::raise_error(array('code' => 526, 'type' => 'php',
        'file' => __FILE__, 'line' => __LINE__,
        'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
    }
    else {
      $this->tasks[$task] = $owner;
      rcmail::$main_tasks[] = $task;
      return true;
    /**
     * Allows a plugin object to register a callback for a certain hook
     *
     * @param string $hook Hook name
     * @param mixed  $callback String with global function name or array($obj, 'methodname')
     */
    public function register_hook($hook, $callback)
    {
        if (is_callable($callback)) {
            if (isset($this->deprecated_hooks[$hook])) {
                rcube::raise_error(array('code' => 522, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Deprecated hook name. "
                        . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false);
                $hook = $this->deprecated_hooks[$hook];
            }
            $this->handlers[$hook][] = $callback;
        }
        else {
            rcube::raise_error(array('code' => 521, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Invalid callback function for $hook"), true, false);
        }
    }
    return false;
  }
  /**
   * Checks whether the given task is registered by a plugin
   *
   * @param string $task Task name
   * @return boolean True if registered, otherwise false
   */
  public function is_plugin_task($task)
  {
    return $this->tasks[$task] ? true : false;
  }
  /**
   * Check if a plugin hook is currently processing.
   * Mainly used to prevent loops and recursion.
   *
   * @param string $hook Hook to check (optional)
   * @return boolean True if any/the given hook is currently processed, otherwise false
   */
  public function is_processing($hook = null)
  {
    return $this->active_hook && (!$hook || $this->active_hook == $hook);
  }
  /**
   * Include a plugin script file in the current HTML page
   *
   * @param string $fn Path to script
   */
  public function include_script($fn)
  {
    if (is_object($this->output) && $this->output->type == 'html') {
      $src = $this->resource_url($fn);
      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
    /**
     * Allow a plugin object to unregister a callback.
     *
     * @param string $hook Hook name
     * @param mixed  $callback String with global function name or array($obj, 'methodname')
     */
    public function unregister_hook($hook, $callback)
    {
        $callback_id = array_search($callback, $this->handlers[$hook]);
        if ($callback_id !== false) {
            unset($this->handlers[$hook][$callback_id]);
        }
    }
  }
    /**
     * Triggers a plugin hook.
     * This is called from the application and executes all registered handlers
     *
     * @param string $hook Hook name
     * @param array $args Named arguments (key->value pairs)
     *
     * @return array The (probably) altered hook arguments
     */
    public function exec_hook($hook, $args = array())
    {
        if (!is_array($args)) {
            $args = array('arg' => $args);
        }
  /**
   * Include a plugin stylesheet in the current HTML page
   *
   * @param string $fn Path to stylesheet
   */
  public function include_stylesheet($fn)
  {
    if (is_object($this->output) && $this->output->type == 'html') {
      $src = $this->resource_url($fn);
      $this->output->include_css($src);
        $args += array('abort' => false);
        $this->active_hook = $hook;
        foreach ((array)$this->handlers[$hook] as $callback) {
            $ret = call_user_func($callback, $args);
            if ($ret && is_array($ret)) {
                $args = $ret + $args;
            }
            if ($args['abort']) {
                break;
            }
        }
        $this->active_hook = false;
        return $args;
    }
  }
    /**
     * Let a plugin register a handler for a specific request
     *
     * @param string $action   Action name (_task=mail&_action=plugin.foo)
     * @param string $owner    Plugin name that registers this action
     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
     * @param string $task     Task name registered by this plugin
     */
    public function register_action($action, $owner, $callback, $task = null)
    {
        // check action name
        if ($task)
            $action = $task.'.'.$action;
        else if (strpos($action, 'plugin.') !== 0)
            $action = 'plugin.'.$action;
  /**
   * Save the given HTML content to be added to a template container
   *
   * @param string $html HTML content
   * @param string $container Template container identifier
   */
  public function add_content($html, $container)
  {
    $this->template_contents[$container] .= $html . "\n";
  }
        // can register action only if it's not taken or registered by myself
        if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
            $this->actions[$action] = $callback;
            $this->actionmap[$action] = $owner;
        }
        else {
            rcube::raise_error(array('code' => 523, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Cannot register action $action;"
                    ." already taken by another plugin"), true, false);
        }
    }
    /**
     * This method handles requests like _task=mail&_action=plugin.foo
     * It executes the callback function that was registered with the given action.
     *
     * @param string $action Action name
     */
    public function exec_action($action)
    {
        if (isset($this->actions[$action])) {
            call_user_func($this->actions[$action]);
        }
        else if (rcube::get_instance()->action != 'refresh') {
            rcube::raise_error(array('code' => 524, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "No handler found for action $action"), true, true);
        }
    }
  /**
   * Returns list of loaded plugins names
   *
   * @return array List of plugin names
   */
  public function loaded_plugins()
  {
    return array_keys($this->plugins);
  }
    /**
     * Register a handler function for template objects
     *
     * @param string $name Object name
     * @param string $owner Plugin name that registers this action
     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
     */
    public function register_handler($name, $owner, $callback)
    {
        // check name
        if (strpos($name, 'plugin.') !== 0) {
            $name = 'plugin.' . $name;
        }
        // can register handler only if it's not taken or registered by myself
        if (is_object($this->output)
            && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)
        ) {
            $this->output->add_handler($name, $callback);
            $this->objectsmap[$name] = $owner;
        }
        else {
            rcube::raise_error(array('code' => 525, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Cannot register template handler $name;"
                    ." already taken by another plugin or no output object available"), true, false);
        }
    }
  /**
   * Callback for template_container hooks
   *
   * @param array $attrib
   * @return array
   */
  protected function template_container_hook($attrib)
  {
    $container = $attrib['name'];
    return array('content' => $attrib['content'] . $this->template_contents[$container]);
  }
    /**
     * Register this plugin to be responsible for a specific task
     *
     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
     * @param string $owner Plugin name that registers this action
     */
    public function register_task($task, $owner)
    {
        // tasks are irrelevant in framework mode
        if (!class_exists('rcmail', false)) {
            return true;
        }
        if ($task != asciiwords($task)) {
            rcube::raise_error(array('code' => 526, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Invalid task name: $task."
                    ." Only characters [a-z0-9_.-] are allowed"), true, false);
        }
        else if (in_array($task, rcmail::$main_tasks)) {
            rcube::raise_error(array('code' => 526, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Cannot register taks $task;"
                    ." already taken by another plugin or the application itself"), true, false);
        }
        else {
            $this->tasks[$task] = $owner;
            rcmail::$main_tasks[] = $task;
            return true;
        }
  /**
   * Make the given file name link into the plugins directory
   *
   * @param string $fn Filename
   * @return string
   */
  protected function resource_url($fn)
  {
    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
      return $this->url . $fn;
    else
      return $fn;
  }
        return false;
    }
    /**
     * Checks whether the given task is registered by a plugin
     *
     * @param string $task Task name
     *
     * @return boolean True if registered, otherwise false
     */
    public function is_plugin_task($task)
    {
        return $this->tasks[$task] ? true : false;
    }
    /**
     * Check if a plugin hook is currently processing.
     * Mainly used to prevent loops and recursion.
     *
     * @param string $hook Hook to check (optional)
     *
     * @return boolean True if any/the given hook is currently processed, otherwise false
     */
    public function is_processing($hook = null)
    {
        return $this->active_hook && (!$hook || $this->active_hook == $hook);
    }
    /**
     * Include a plugin script file in the current HTML page
     *
     * @param string $fn Path to script
     */
    public function include_script($fn)
    {
        if (is_object($this->output) && $this->output->type == 'html') {
            $src = $this->resource_url($fn);
            $this->output->add_header(html::tag('script',
                array('type' => "text/javascript", 'src' => $src)));
        }
    }
    /**
     * Include a plugin stylesheet in the current HTML page
     *
     * @param string $fn Path to stylesheet
     */
    public function include_stylesheet($fn)
    {
        if (is_object($this->output) && $this->output->type == 'html') {
            $src = $this->resource_url($fn);
            $this->output->include_css($src);
        }
    }
    /**
     * Save the given HTML content to be added to a template container
     *
     * @param string $html HTML content
     * @param string $container Template container identifier
     */
    public function add_content($html, $container)
    {
        $this->template_contents[$container] .= $html . "\n";
    }
    /**
     * Returns list of loaded plugins names
     *
     * @return array List of plugin names
     */
    public function loaded_plugins()
    {
        return array_keys($this->plugins);
    }
    /**
     * Callback for template_container hooks
     *
     * @param array $attrib
     * @return array
     */
    protected function template_container_hook($attrib)
    {
        $container = $attrib['name'];
        return array('content' => $attrib['content'] . $this->template_contents[$container]);
    }
    /**
     * Make the given file name link into the plugins directory
     *
     * @param string $fn Filename
     * @return string
     */
    protected function resource_url($fn)
    {
        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
            return $this->url . $fn;
        else
            return $fn;
    }
}