Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/plugins/model/_plugins.class.php
<?php
/**
 * This file implements the PluginS class.
 *
 * This is where you can plug in some {@link Plugin plugins} :D
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package plugins
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


// DEBUG: (Turn switch on or off to log debug info for specified category)
$GLOBALS['debug_plugins'] = false;


load_class( 'plugins/_plugin.class.php', 'Plugin' );


/**
 * Plugins Class
 *
 * This is where you can plug in some {@link Plugin plugins} :D
 *
 * @todo dh> Currently when a plugin goes into "broken" status (e.g. file not readable), it is "disabled" afterwards.
 *       This should rather remember the old status (e.g. "enabled") and make it enabled again.
 *
 * @package plugins
 */
class Plugins
{
   
/**#@+
     * @access private
     */

    /**
     * @var array of plugin_code => Plugin
     */
   
var $index_code_Plugins = array();

   
/**
     * @var array of plugin_ID => Plugin
     */
   
var $index_ID_Plugins = array();

   
/**
     * @see Plugins::load_events()
     * @var array of event => plug_ID. IDs are sorted by priority.
     */
   
var $index_event_IDs = array();

   
/**
     * @var array of plug_ID => DB row from T_plugins. Used to lazy-instantiate Plugins.
     */
   
var $index_ID_rows = array();

   
/**
     * @var array of plug_code => plug_ID. Used to lazy-instantiate by code.
     */
   
var $index_code_ID = array();

   
/**
     * Cache Plugin codes by apply_rendering setting.
     * @var array of apply_rendering => plug_code
     */
   
var $index_apply_rendering_codes = array();

   
/**
     * Cache all plugin codes by blogs apply_comment_rendering setting.
     * @var array of blog_ID => array of plugin codes
     */
   
var $coll_apply_comment_rendering = array();

   
/**
     * Path to plugins.
     *
     * The preferred method is to have a sub-directory for each plugin (named
     * after the plugin's classname), but they can be supplied just in this
     * directory.
     */
   
var $plugins_path;

   
/**
     * Have we loaded the plugins table (T_plugins)?
     * @var boolean
     */
   
var $loaded_plugins_table = false;

   
/**
     * Current object index in {@link $sorted_IDs} array.
     * @var integer
     */
   
var $current_idx = -1;

   
/**
     * List of IDs, sorted. This gets used to lazy-instantiate a Plugin.
     *
     * @var array
     */
   
var $sorted_IDs = array();

   
/**
     * The smallest internal/auto-generated Plugin ID.
     * @var integer
     */
   
var $smallest_internal_ID = 0;

   
/**#@-*/


    /**#@+
     * @access protected
     */

    /**
     * SQL to use in {@link load_plugins_table()}. Gets overwritten for {@link Plugins_admin}.
     * @var string
     * @static
     */
   
var $sql_load_plugins_table = '
            SELECT plug_ID, plug_priority, plug_classname, plug_code, plug_name, plug_shortdesc, plug_status, plug_version, plug_spam_weight
              FROM T_plugins
             WHERE plug_status = \'enabled\'
             ORDER BY plug_priority, plug_classname'
;

   
/**#@-*/


    /**
     * Errors associated to plugins (during loading), indexed by plugin_ID and
     * error class ("register").
     *
     * @var array
     */
   
var $plugin_errors = array();


   
/**
     * Constructor. Sets {@link $plugins_path} and load events.
     */
   
function __construct()
    {
        global
$basepath, $plugins_subdir, $Timer;

       
// Set plugin path:
       
$this->plugins_path = $basepath.$plugins_subdir;

       
$Timer->resume( 'plugins_init' );

       
// Load events for enabled plugins:
       
$this->load_events();

       
$Timer->pause( 'plugins_init' );
    }


   
/**
     * Get a list of available Plugin groups.
     *
     * @return array
     */
   
function get_plugin_groups()
    {
       
$result = array();

        foreach(
$this->sorted_IDs as $plugin_ID )
        {
           
$Plugin = & $this->get_by_ID( $plugin_ID );

            if( empty(
$Plugin->group) || in_array( $Plugin->group, $result ) )
            {
                continue;
            }

           
$result[] = $Plugin->group;
        }

        return
$result;
    }


   
/**
     * Will return an array that contents are references to plugins that have the same group, regardless of the sub_group.
     *
     * @return array
     */
   
function get_Plugins_in_group( $group )
    {
       
$result = array();

        foreach(
$this->sorted_IDs as $plugin_ID )
        {
           
$Plugin = & $this->get_by_ID( $plugin_ID );
            if(
$Plugin->group == $group )
            {
               
$result[] = & $Plugin;
            }
        }

        return
$result;
    }


   
/**
     * Will return an array that contents are references to plugins that have the same group and sub_group.
     *
     * @return array
     */
   
function get_Plugins_in_sub_group( $group, $sub_group = '' )
    {
       
$result = array();

        foreach(
$this->sorted_IDs as $plugin_ID )
        {
           
$Plugin = & $this->get_by_ID( $plugin_ID );
            if(
$Plugin->group == $group && $Plugin->sub_group == $sub_group )
            {
               
$result[] = & $Plugin;
            }
        }

        return
$result;
    }


   
/**
     * Sets the status of a Plugin in DB and registers it into the internal indices when "enabled".
     * Otherwise it gets unregistered, but only when we're not in {@link Plugins_admin}, because we
     * want to keep it in then in our indices.
     *
     * {@internal
     * Note: this should probably always get called on the {@link $Plugins} object,
     *       not {@link $admin_Plugins}.
     * }}
     *
     * @param Plugin
     * @param string New status ("enabled", "disabled", "needs_config", "broken")
     * @return boolean True on success, false on failure.
     */
   
function set_Plugin_status( & $Plugin, $status )
    {
        global
$DB, $Debuglog;

        if(
$Plugin->status == $status
           
&& $DB->query( "SELECT plug_status FROM T_plugins WHERE plug_ID = ".$DB->quote($Plugin->ID), 'set_Plugin_status safety net' ) == $status )
        {
            return
true;
        }

       
$DB->query( "UPDATE T_plugins SET plug_status = '".$status."' WHERE plug_ID = '".$Plugin->ID."'" );

        if(
$status == 'enabled' )
        {
// Reload plugins tables, which includes the plugin in further requests
           
$this->loaded_plugins_table = false;
           
$this->load_plugins_table();
           
$this->load_events();
        }
        else
        {
           
// Notify the plugin that it has been disabled:
           
$Plugin->BeforeDisable();

           
$this->unregister( $Plugin );
        }

       
$Plugin->status = $status;

       
$Debuglog->add( 'Set status for plugin #'.$Plugin->ID.' to "'.$status.'"!', 'plugins' );

        return
true;
    }


   
/**
     * Get path of a given Plugin classname.
     *
     * @param string Classname
     * @return string Path
     */
   
function get_classfile_path($classname)
    {
       
$plugin_filename = '_'.str_replace( '_plugin', '.plugin', $classname ).'.php';
       
// Try <plug_classname>/<plug_classname>.php (subfolder) first
       
$classfile_path = $this->plugins_path.$classname.'/'.$plugin_filename;

        if( !
is_readable( $classfile_path ) )
        {
// Look directly in $plugins_path
           
$classfile_path = $this->plugins_path.$plugin_filename;
        }
        return
$classfile_path;
    }


   
/**
     * Register a plugin.
     *
     * This handles the indexes, dynamically unregisters a Plugin that does not exist (anymore)
     * and instantiates the Plugin's (User)Settings.
     *
     * @access protected
     * @param string name of plugin class to instantiate and register
     * @param int ID in database (0 if not installed)
     * @param int Priority in database (-1 to keep default)
     * @param string Path of the .php class file of the plugin.
     * @param boolean Must the plugin exist (classfile_path and classname)?
     *                This is used internally to be able to unregister a non-existing plugin.
     * @return Plugin Plugin ref to newly created plugin; string in case of error
     */
   
function & register( $classname, $ID = 0, $priority = -1, $classfile_path = NULL, $must_exists = true )
    {
        global
$Debuglog, $Messages, $Timer;

        if(
$ID && isset($this->index_ID_Plugins[$ID]) )
        {
           
debug_die( 'Tried to register already registered Plugin (ID '.$ID.')' ); // should never happen!
       
}

       
$Timer->resume( 'plugins_register' );

        if( empty(
$classfile_path) )
        {
           
$classfile_path = $this->get_classfile_path($classname);
        }

       
$Debuglog->add( 'register(): '.$classname.', ID: '.$ID.', priority: '.$priority.', classfile_path: ['.$classfile_path.']', 'plugins' );

        if( ! empty(
$this->log_register ) )
        {    
// Display additional log on upgrade page when we reload all plugins:
           
global $plugins_path;
            echo
get_install_format_text_and_log( '- Reloading "'.$classname.'" from <code>'.substr( $classfile_path, strlen( $plugins_path ) ).'</code><br />' );
           
evo_flush();
        }

        if( !
is_readable( $classfile_path ) )
        {
// Plugin file not found!
           
if( $must_exists )
            {
               
$r = 'Plugin class file ['.rel_path_to_base($classfile_path).'] is not readable!';
               
$Debuglog->add( $r, array( 'plugins', 'error' ) );

               
// Get the Plugin object (must not exist)
               
$Plugin = & $this->register( $classname, $ID, $priority, $classfile_path, false );
               
$this->plugin_errors[$ID]['register'] = $r;
               
$this->set_Plugin_status( $Plugin, 'broken' );

               
// unregister:
               
if( $this->unregister( $Plugin ) )
                {
                   
$Debuglog->add( 'Unregistered plugin ['.$classname.']!', array( 'plugins', 'error' ) );
                }
                else
                {
                   
$Plugin->name = $Plugin->classname; // use the classname instead of "unnamed plugin"
                   
$Timer->pause( 'plugins_register' );
                    return
$Plugin;
                }

               
$Timer->pause( 'plugins_register' );
                return
$r;
            }
        }
        elseif( !
class_exists($classname) ) // If there are several copies of one plugin for example..
       
{
           
$Debuglog->add( 'Loading plugin class file: '.$classname, 'plugins' );
            require_once
$classfile_path;
        }

        if( !
class_exists( $classname ) )
        {
// the given class does not exist
           
if( $must_exists )
            {
               
$r = sprintf( 'Plugin class for &laquo;%s&raquo; in file &laquo;%s&raquo; not defined.', $classname, rel_path_to_base($classfile_path) );
               
$Debuglog->add( $r, array( 'plugins', 'error' ) );

               
// Get the Plugin object (must not exist)    fp> why is this recursive?
               
$Plugin = & $this->register( $classname, $ID, $priority, $classfile_path, false );
               
$this->plugin_errors[$ID]['register'] = $r;
               
$this->set_Plugin_status( $Plugin, 'broken' );

               
// unregister:
               
if( $this->unregister( $Plugin ) )
                {
                   
$Debuglog->add( 'Unregistered plugin ['.$classname.']!', array( 'plugins', 'error' ) );
                }
                else
                {
                   
$Plugin->name = $Plugin->classname; // use the classname instead of "unnamed plugin"
                   
$Timer->pause( 'plugins_register' );
                    return
$Plugin;
                }

               
$Timer->pause( 'plugins_register' );
                return
$r;
            }
            else
            {
               
$Plugin = new Plugin;    // COPY !
               
$Plugin->code = NULL;
            }
        }
        else
        {
           
$Plugin = new $classname;    // COPY !
       
}

       
$Plugin->classfile_path = $classfile_path;

       
// Tell him his ID :)
       
if( $ID == 0 )
        {
           
$Plugin->ID = --$this->smallest_internal_ID;
        }
        else
        {
           
$Plugin->ID = $ID;

            if(
$ID > 0 )
            {
// Properties from T_plugins
                // Code
               
$Plugin->code = $this->index_ID_rows[$Plugin->ID]['plug_code'];
               
// Status
               
$Plugin->status = $this->index_ID_rows[$Plugin->ID]['plug_status'];
            }
        }
       
// Tell him his name :)
       
$Plugin->classname = $classname;
       
// Tell him his priority:
       
if( $priority > -1 ) { $Plugin->priority = $priority; }

        if( empty(
$Plugin->name) )
        {
           
$Plugin->name = $Plugin->classname;
        }

       
// Memorizes Plugin in code hash array:
       
if( ! empty($this->index_code_ID[ $Plugin->code ]) && $this->index_code_ID[ $Plugin->code ] != $Plugin->ID )
        {
// The plugin's default code is already in use!
           
$Plugin->code = NULL;
        }
        else
        {
           
$this->index_code_Plugins[ $Plugin->code ] = & $Plugin;
           
$this->index_code_ID[ $Plugin->code ] = & $Plugin->ID;
        }
       
$this->index_ID_Plugins[ $Plugin->ID ] = & $Plugin;

        if( !
in_array( $Plugin->ID, $this->sorted_IDs ) ) // TODO: check if this extra check is required..
       
{ // not in our sort index yet
           
$this->sorted_IDs[] = & $Plugin->ID;
        }

       
// Init the Plugins (User)Settings members.
       
$this->init_settings( $Plugin );

       
// Stuff only for real/existing Plugins (which exist in DB):
       
if( $Plugin->ID > 0 )
        {
           
$tmp_params = array( 'db_row' => $this->index_ID_rows[$Plugin->ID], 'is_installed' => true );
            if(
$Plugin->PluginInit( $tmp_params ) === false && $this->unregister( $Plugin ) )
            {
               
$Debuglog->add( 'Unregistered plugin, because PluginInit returned false.', 'plugins' );
               
$Plugin = '';
            }
           
// Version check:
           
elseif( $Plugin->version != $this->index_ID_rows[$Plugin->ID]['plug_version'] && $must_exists )
            {
// Version has changed since installation or last update
               
$db_deltas = array();

               
// Tell the Plugin that we've detected a version change:
               
$tmp_params = array( 'old_version'=>$this->index_ID_rows[$Plugin->ID]['plug_version'], 'db_row'=>$this->index_ID_rows[$Plugin->ID] );

                if(
$this->call_method( $Plugin->ID, 'PluginVersionChanged', $tmp_params ) === false )
                {
                   
$Debuglog->add( 'Set plugin status to "needs_config", because PluginVersionChanged returned false.', 'plugins' );
                   
$this->set_Plugin_status( $Plugin, 'needs_config' );
                    if(
$this->unregister( $Plugin ) )
                    {
// only unregister the Plugin, if it's not the admin list's class:
                       
$Plugin = '';
                    }
                }
                else
                {
                   
// Check if there are DB deltas required (also when downgrading!), without excluding any query type:
                   
load_funcs('_core/model/db/_upgrade.funcs.php');
                   
$db_deltas = db_delta( $Plugin->GetDbLayout() );

                    if( empty(
$db_deltas) )
                    {
// No DB changes needed, update (bump or decrease) the version
                       
global $DB;
                       
$Plugins_admin = & get_Plugins_admin();

                       
// Update version in DB:
                       
$DB->query( '
                                UPDATE T_plugins
                                   SET plug_version = '
.$DB->quote($Plugin->version).'
                                 WHERE plug_ID = '
.$Plugin->ID );

                       
// Update "plug_version" in indexes:
                       
$this->index_ID_rows[$Plugin->ID]['plug_version'] = $Plugin->version;
                        if( isset(
$Plugins_admin->index_ID_rows[$Plugin->ID]) )
                        {
                           
$Plugins_admin->index_ID_rows[$Plugin->ID]['plug_version'] = $Plugin->version;
                        }

                       
// Remove any prerendered content for the Plugins renderer code:
                       
if( ! empty($Plugin->code) )
                        {
                           
$DB->query( '
                                    DELETE FROM T_items__prerendering
                                     WHERE itpr_renderers REGEXP "^(.*\.)?'
.$DB->escape($Plugin->code).'(\..*)?$"' );
                        }

                       
// Detect new events (and delete obsolete ones - in case of downgrade):
                       
if( $Plugins_admin->save_events( $Plugin, array() ) )
                        {
                           
$this->load_events(); // re-load for the current request
                       
}

                       
$Debuglog->add( 'Version for '.$Plugin->classname.' changed from '.$this->index_ID_rows[$Plugin->ID]['plug_version'].' to '.$Plugin->version, 'plugins' );
                    }
                    else
                    {
// If there are DB schema changes needed, set the Plugin status to "needs_config"

                        // TODO: automatic upgrade in some cases (e.g. according to query types)?

                       
$this->set_Plugin_status( $Plugin, 'needs_config' );
                       
$Debuglog->add( 'Set plugin status to "needs_config", because version DB schema needs upgrade.', 'plugins' );

                        if(
$this->unregister( $Plugin ) )
                        {
// only unregister the Plugin, if it's not the admin list's class:
                           
$Plugin = '';
                        }
                    }
                }
            }

            if(
$Plugin && isset($this->index_ID_rows[$Plugin->ID]) ) // may have been unregistered above
           
{
                if(
$this->index_ID_rows[$Plugin->ID]['plug_name'] !== NULL )
                {
                   
$Plugin->name = $this->index_ID_rows[$Plugin->ID]['plug_name'];
                }
                if(
$this->index_ID_rows[$Plugin->ID]['plug_shortdesc'] !== NULL )
                {
                   
$Plugin->short_desc = $this->index_ID_rows[$Plugin->ID]['plug_shortdesc'];
                }
            }
        }
        else
        {
// This gets called for non-installed Plugins:
            // We're not instantiating UserSettings/Settings, since that needs a DB backend.

           
$tmp_params = array( 'db_row' => array(), 'is_installed' => false );
            if(
$Plugin->PluginInit( $tmp_params ) === false && $this->unregister( $Plugin ) )
            {
               
$Debuglog->add( 'Unregistered plugin, because PluginInit returned false.', 'plugins' );
               
$Plugin = '';
            }
        }

       
$Timer->pause( 'plugins_register' );

        return
$Plugin;
    }


   
/**
     * Un-register a plugin.
     *
     * This does not un-install it from DB, just from the internal indexes.
     *
     * @param Plugin
     * @param boolean Force unregistering (ignored here, but used in Plugins_admin)
     * @return boolean True, if unregistered
     */
   
function unregister( & $Plugin, $force = false )
    {
        global
$Debuglog;

       
$this->forget_events( $Plugin->ID );

       
// TODO: Check if it is necessarry to unset from the $index_apply_rendering_codes and unset if it must be removed
       
unset( $this->index_code_Plugins[ $Plugin->code ] );
        unset(
$this->index_ID_Plugins[ $Plugin->ID ] );

        if( isset(
$this->index_ID_rows[ $Plugin->ID ]) )
        {
// It has an associated DB row (load_plugins_table() was called)
           
unset($this->index_ID_rows[ $Plugin->ID ]);
        }

       
$sort_key = array_search( $Plugin->ID, $this->sorted_IDs );
        if(
$sort_key === false )
        {
// this may happen if a Plugin has unregistered itself
           
$Debuglog->add( 'Tried to unregister not-installed plugin (not in $sorted_IDs)!', 'plugins' );
            return
false;
        }
        unset(
$this->sorted_IDs[$sort_key] );
       
$this->sorted_IDs = array_values( $this->sorted_IDs );

        if(
$this->current_idx >= $sort_key )
        {
// We have removed a file before or at the $sort_key'th position
           
$this->current_idx--;
        }

        return
true;
    }


   
/**
     * Forget the events a Plugin has registered.
     *
     * This gets used when {@link unregister() unregistering} a Plugin or if
     * {@link Plugin::PluginInit()} returned false, which means
     * "do not use it for subsequent events in the request".
     *
     * @param integer Plugin ID
     */
   
function forget_events( $plugin_ID )
    {
       
// Forget events:
       
foreach( array_keys($this->index_event_IDs) as $l_event )
        {
            while( (
$key = array_search( $plugin_ID, $this->index_event_IDs[$l_event] )) !== false )
            {
                unset(
$this->index_event_IDs[$l_event][$key] );
            }
        }
    }


   
/**
     * Init {@link Plugin::$Settings} and {@link Plugin::$UserSettings}, either by
     * unsetting them for PHP5's overloading or instantiating them for PHP4.
     *
     * This only gets used for installed plugins, but we're initing it for PHP 5.1,
     * to trigger a helpful error, when e.g. Plugin::Settings gets accessed in PluginInit().
     *
     * @param Plugin
     */
   
function init_settings( & $Plugin )
    {
       
// we use overloading for PHP5, therefor the member has to be unset:
        // Note: this is somehow buggy at least in PHP 5.0.5, therefor we use it from 5.1 on.
        //       see http://forums.b2evolution.net/viewtopic.php?p=49031#49031
       
unset( $Plugin->Settings );
        unset(
$Plugin->UserSettings );
        unset(
$Plugin->GroupSettings );

       
// Nothing to do here, will get called through Plugin::__get() when accessed
       
return;
    }


   
/**
     * Instantiate Settings object (class {@link PluginSettings}) for the given plugin.
     *
     * The plugin must provide setting definitions (through {@link Plugin::GetDefaultSettings()}
     * OR {@link Plugin::GetDefaultUserSettings()}).
     *
     * @todo dh> there should be only one instance of Plugin(User)Settings, which would allow to
     *           query the DB at once.
     *           Defaults would need to be handled by Plugin(User)Settings::get_default() then.
     *
     * @param Plugin
     * @param string settings type: "Settings", "UserSettings" or "GroupSettings"
     * @return boolean NULL, if no Settings
     */
   
function instantiate_Settings( & $Plugin, $set_type )
    {
        global
$Debuglog, $Timer, $Blog;

       
$Timer->resume( 'plugins_inst_'.$set_type );

       
// Call Plugin::GetDefaultSettings() or Plugin::GetDefaultUserSettings():
        // This does not use call_method, since the plugin may not be accessible through get_by_ID().
        // This may happen, if Plugin(User)Settings get accessed in PluginInit (which is allowed
        // when is_installed=true).
       
$method = 'GetDefault'.$set_type;
       
$params = array('for_editing'=>false);
       
$defaults = $Plugin->$method( $params );

        if(
$set_type == 'Settings' )
        {    
// If general settings are requested we should also append custom, collection, widgets, messages, emails and shared settings:
           
if( ! is_array( $defaults ) )
            {    
// Initialize array for default settings:
               
$defaults = array();
            }

           
// Initialize object for Settings temporary because it may be used in the functions below to get default values:
           
load_class( 'plugins/model/_pluginsettings.class.php', 'PluginSettings' );
           
$Plugin->Settings = new PluginSettings( $Plugin->ID );

           
$defaults = array_merge( $defaults, $Plugin->get_custom_setting_definitions( $params ) );
           
$defaults = array_merge( $defaults, $Plugin->get_msg_setting_definitions( $params ) );
           
$defaults = array_merge( $defaults, $Plugin->get_email_setting_definitions( $params ) );
           
$defaults = array_merge( $defaults, $Plugin->get_shared_setting_definitions( $params ) );

           
// Check what other settings are defined for the Plugin,
            // We should not merge them with $defaults because they are stored in different DB table,
            // I.e. they are should be initialized in $Plugin->Settings, but we still need this object for a proper settings work:
           
if( isset( $Blog ) )
            {    
// Only when current collection is defined in order to avoid errors because global $Blog may be used inside:
               
$other_defaults = $Plugin->get_coll_setting_definitions( $params );
               
$other_defaults = array_merge( $other_defaults, $Plugin->get_widget_param_definitions( $params ) );
            }
        }

        if( empty(
$defaults ) && empty( $other_defaults ) )
        {    
// No settings, no need to instantiate:
           
$Timer->pause( 'plugins_inst_'.$set_type );
            return
NULL;
        }

        if( !
is_array( $defaults ) )
        {    
// Invalid format of default settings:
           
$Debuglog->add( $Plugin->classname.'::GetDefault'.$set_type.'() did not return array!', array('plugins', 'error') );
            return
NULL; // fp> correct me if I'm wrong.
       
}

        if(
$set_type == 'UserSettings' )
        {
// User specific settings:
           
load_class( 'plugins/model/_pluginusersettings.class.php', 'PluginUserSettings' );

           
$Plugin->UserSettings = new PluginUserSettings( $Plugin->ID );

           
$set_Obj = & $Plugin->UserSettings;
        }
        elseif(
$set_type == 'GroupSettings' )
        {    
// Group specific settings:
           
load_class( 'plugins/model/_plugingroupsettings.class.php', 'PluginGroupSettings' );

           
$Plugin->GroupSettings = new PluginGroupSettings( $Plugin->ID );

           
$set_Obj = & $Plugin->GroupSettings;
        }
        else
        {
// Global settings:
           
load_class( 'plugins/model/_pluginsettings.class.php', 'PluginSettings' );

           
$Plugin->Settings = new PluginSettings( $Plugin->ID );

           
$set_Obj = & $Plugin->Settings;
        }

       
// Register default values:
       
foreach( $defaults as $l_name => $l_meta )
        {
            if( isset(
$l_meta['layout']) )
            {
// Skip non-value entries
               
continue;
            }

           
// Register settings as _defaults into Settings:
           
if( isset($l_meta['defaultvalue']) )
            {
               
$set_Obj->_defaults[$l_name] = $l_meta['defaultvalue'];
            }
            elseif( isset(
$l_meta['type'] ) && strpos( $l_meta['type'], 'array' ) === 0 )
            {
               
$set_Obj->_defaults[$l_name] = array();
            }
            elseif( isset(
$l_meta['type'] ) && $l_meta['type'] == 'checklist' )
            {
               
$set_Obj->_defaults[$l_name] = NULL;
            }
            elseif( isset(
$l_meta['type'] ) && $l_meta['type'] == 'input_group' && is_array( $l_meta['inputs'] ) )
            {    
// Get default values from input group fields:
               
foreach( $l_meta['inputs'] as $l_meta_input_key => $l_meta_input_data )
                {
                    if( isset(
$l_meta_input_data['defaultvalue'] ) )
                    {
                       
$set_Obj->_defaults[ $l_name.$l_meta_input_key ] = $l_meta_input_data['defaultvalue'];
                    }
                }
            }
            else
            {
               
$set_Obj->_defaults[$l_name] = '';
            }
        }

       
$Timer->pause( 'plugins_inst_'.$set_type );

        return
true;
    }


   
/**
     * Load plugins table and rewind iterator used by {@link get_next()}.
     */
   
function restart()
    {
       
$this->load_plugins_table();

       
$this->current_idx = -1;
    }


   
/**
     * Get next plugin in the list.
     *
     * NOTE: You'll have to call {@link restart()} or {@link load_plugins_table()}
     * before using it.
     *
     * @return Plugin (false if no more plugin).
     */
   
function & get_next()
    {
        global
$Debuglog;

        ++
$this->current_idx;

       
$Debuglog->add( 'get_next() ('.$this->current_idx.')..', 'plugins' );

        if( isset(
$this->sorted_IDs[$this->current_idx]) )
        {
           
$Plugin = & $this->get_by_ID( $this->sorted_IDs[$this->current_idx] );

            if( !
$Plugin )
            {
// recurse until we've been through whole $sorted_IDs!
               
return $this->get_next();
            }

           
$Debuglog->add( 'return: '.$Plugin->classname.' ('.$Plugin->ID.')', 'plugins' );
            return
$Plugin;
        }
        else
        {
           
$Debuglog->add( 'return: false', 'plugins' );
            --
$this->current_idx;
           
$r = false;
            return
$r;
        }
    }


   
/**
     * Stop propagation of events to next plugins in {@link trigger_event()}.
     */
   
function stop_propagation()
    {
       
$this->_stop_propagation = true;
    }


   
/**
     * Call all plugins for a given event.
     *
     * @param string event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @return boolean True, if at least one plugin has been called.
     */
   
function trigger_event( $event, $params = array() )
    {
        global
$Debuglog;

       
$Debuglog->add( 'Trigger event '.$event, 'plugins' );

        if( empty(
$this->index_event_IDs[$event]) )
        {
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
            return
false;
        }

       
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' );

        foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
        {
           
$this->call_method( $l_plugin_ID, $event, $params );

            if( ! empty(
$this->_stop_propagation) )
            {    
// A plugin has requested to stop propagation.
               
$this->_stop_propagation = false;
                break;
            }
        }
        return
true;
    }


   
/**
     * Call all plugins for a given event, until the first one returns true.
     *
     * @param string event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @return array The (modified) params array with key "plugin_ID" set to the last called plugin;
     *               Empty array if no Plugin returned true or no Plugin has this event registered.
     */
   
function trigger_event_first_true( $event, $params = NULL )
    {
        global
$Debuglog;

       
$Debuglog->add( 'Trigger event '.$event.' (first true)', 'plugins' );

        if( empty(
$this->index_event_IDs[$event]) )
        {
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
// DON'T RETURN HERE BECAUSE OF DIRTY HACK!!!
       
}
        else
        {    
// We have some events registered, loop through them:
           
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' );
            foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
            {
               
$r = $this->call_method( $l_plugin_ID, $event, $params );
                if(
$r === true )
                {
                   
$Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned true!', 'plugins' );
                   
// Save the ID of the plugin which returned true:
                   
$params['plugin_ID'] = & $l_plugin_ID;
                    return
$params;
                }
            }
        }
        return array();
    }


   
/**
     * Call all plugins for a given event, until the first one returns true.
     *
     * @param string event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @return array The (modified) params array with key "plugin_ID" set to the last called plugin;
     *               Empty array if no Plugin returned true or no Plugin has this event registered.
     */
   
function trigger_event_first_true_with_params( $event, & $params )
    {
        global
$Debuglog;

       
$Debuglog->add( 'Trigger event '.$event.' (first true)', 'plugins' );

        if( empty(
$this->index_event_IDs[ $event ] ) )
        {    
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
// DON'T RETURN HERE BECAUSE OF DIRTY HACK!!!
       
}
        else
        {    
// We have some events registered, loop through them:
           
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[ $event ] ), 'plugins' );
            foreach(
$this->index_event_IDs[ $event ] as $l_plugin_ID )
            {
               
$r = $this->call_method( $l_plugin_ID, $event, $params );
                if(
$r === true )
                {
                   
$Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned true!', 'plugins' );
                   
// Save the ID of the plugin which returned true:
                   
$params['plugin_ID'] = & $l_plugin_ID;
                    return
$params;
                }
            }
        }

        return array();
    }


   
/**
     * Call all plugins for a given event, until the first one returns false.
     *
     * @param string event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @return array The (modified) params array with key "plugin_ID" set to the last called plugin;
     *               Empty array if no Plugin returned true or no Plugin has this event registered.
     */
   
function trigger_event_first_false( $event, $params = NULL )
    {
        global
$Debuglog;

       
$Debuglog->add( 'Trigger event '.$event.' (first false)', 'plugins' );

        if( empty(
$this->index_event_IDs[$event]) )
        {
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
            return array();
        }

       
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' );
        foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
        {
           
$r = $this->call_method( $l_plugin_ID, $event, $params );
            if(
$r === false )
            {
               
$Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned false!', 'plugins' );
               
$params['plugin_ID'] = & $l_plugin_ID;
                return
$params;
            }
        }
        return array();
    }


   
/**
     * Call all plugins for a given event, until the first one returns a value
     * (not NULL) (and $search is fulfilled, if given).
     *
     * @param string event name, see {@link Plugins_admin::get_supported_events()}
     * @param array|NULL Associative array of parameters for the Plugin
     * @param array|NULL If provided, the return value gets checks against this criteria.
     *        Can be:
     *         - ( 'in_array' => 'needle' )
     * @return array The (modified) params array with key "plugin_ID" set to the last called plugin
     *               and 'plugin_return' set to the return value;
     *               Empty array if no Plugin returned true or no Plugin has this event registered.
     */
   
function trigger_event_first_return( $event, $params = NULL, $search = NULL )
    {
        global
$Debuglog;

       
$Debuglog->add( 'Trigger event '.$event.' (first return)', 'plugins' );

        if( empty(
$this->index_event_IDs[$event]) )
        {
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
            return array();
        }

       
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' );
        foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
        {
           
$r = $this->call_method( $l_plugin_ID, $event, $params );
            if( isset(
$r) )
            {
                if( isset(
$search) )
                {
// Apply $search:
                   
foreach( $search as $k => $v )
                    {
// Check search criterias and continue if it does not match:
                       
switch( $k )
                        {
                            case
'in_array':
                                if( !
in_array( $v, $r ) )
                                {
                                    continue
3; // continue in main foreach loop
                               
}
                                break;
                            default:
                               
debug_die('Invalid search criteria in Plugins::trigger_event_first_return / '.$k);
                        }
                    }
                }
               
$Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned '.( $r ? 'true' : 'false' ).'!', 'plugins' );
               
$params['plugin_return'] = $r;
               
$params['plugin_ID'] = & $l_plugin_ID;
                return
$params;
            }
        }
        return array();
    }


   
/**
     * Trigger an event and return an index of params.
     *
     * This is handy to collect return values from all plugins hooking an event.
     *
     * @param string Event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @param string Index of $params that should get returned
     * @return mixed The requested index of $params
     */
   
function get_trigger_event( $event, $params = NULL, $get = 'data' )
    {
       
$params[$get] = & $params[$get]; // make it a reference, so it can get changed

       
$this->trigger_event( $event, $params );

        return
$params[$get];
    }


   
/**
     * The same as {@link get_trigger_event()}, but stop when the first Plugin returns true.
     *
     * @param string Event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @param string Index of $params that should get returned
     * @return mixed The requested index of $params
     */
   
function get_trigger_event_first_true( $event, $params = NULL, $get = 'data' )
    {
       
$params[$get] = & $params[$get]; // make it a reference, so it can get changed

       
$this->trigger_event_first_true( $event, $params );

        return
$params[$get];
    }


   
/**
     * Trigger an event and return the first return value of a plugin.
     *
     * @param string Event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @return mixed NULL if no Plugin returned something or the return value of the first Plugin
     */
   
function get_trigger_event_first_return( $event, $params = NULL )
    {
       
$r = $this->trigger_event_first_return( $event, $params );

        if( ! isset(
$r['plugin_return']) )
        {
            return
NULL;
        }

        return
$r['plugin_return'];
    }


   
/**
     * Trigger an event and return an array of all return values of the
     * relevant plugins.
     *
     * @param string Event name, see {@link Plugins_admin::get_supported_events()}
     * @param array Associative array of parameters for the Plugin
     * @param boolean Ignore {@link empty() empty} return values?
     * @return array List of return values, indexed by Plugin ID
     */
   
function trigger_collect( $event, $params = NULL, $ignore_empty = true )
    {
        if( empty(
$this->index_event_IDs[$event]) )
        {
            return array();
        }

       
$r = array();
        foreach(
$this->index_event_IDs[$event] as $p_ID )
        {
           
$sub_r = $this->call_method_if_active( $p_ID, $event, $params );

            if(
$ignore_empty && empty($sub_r) )
            {
                continue;
            }

           
$r[$p_ID] = $sub_r;
        }

        return
$r;
    }


   
/**
     * Trigger a karma collecting event in order to get Karma percentage.
     *
     * @param string Event
     * @param array Params to the event
     * @return integer|NULL Spam Karma (-100 - 100); "100" means "absolutely spam"; NULL if no plugin gave us a karma value
     */
   
function trigger_karma_collect( $event, $params )
    {
        global
$Debuglog;

       
$karma_abs = NULL;
       
$karma_divider = 0; // total of the "spam detection relevance weight"

       
$Debuglog->add( 'Trigger karma collect event '.$event, 'plugins' );

        if( empty(
$this->index_event_IDs[$event]) )
        {
// No events registered
           
$Debuglog->add( 'No registered plugins.', 'plugins' );
            return
NULL;
        }

       
$this->load_plugins_table(); // We need index_ID_rows below

       
$Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' );

       
$count_plugins = 0;
        foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
        {
           
$plugin_weight = $this->index_ID_rows[$l_plugin_ID]['plug_spam_weight'];

            if(
$plugin_weight < 1 )
            {
               
$Debuglog->add( 'Skipping plugin #'.$l_plugin_ID.', because is has weight '.$plugin_weight.'.', 'plugins' );
                continue;
            }

           
$params['cur_karma'] = ( $karma_divider ? round($karma_abs / $karma_divider) : NULL );
           
$params['cur_karma_abs'] = $karma_abs;
           
$params['cur_karma_divider'] = $karma_divider;
           
$params['cur_count_plugins'] = $count_plugins;

           
// Call the plugin:
           
$plugin_karma = $this->call_method( $l_plugin_ID, $event, $params );

            if( !
is_numeric( $plugin_karma ) )
            {
                continue;
            }

           
$count_plugins++;

            if(
$plugin_karma > 100 )
            {
               
$plugin_karma = 100;
            }
            elseif(
$plugin_karma < -100 )
            {
               
$plugin_karma = -100;
            }

           
$karma_abs += ( $plugin_karma * $plugin_weight );
           
$karma_divider += $plugin_weight;

            if( ! empty(
$this->_stop_propagation) )
            {
               
$this->_stop_propagation = false;
                break;
            }
        }

        if( !
$karma_divider )
        {
            return
NULL;
        }

       
$karma = round($karma_abs / $karma_divider);

        if(
$karma > 100 )
        {
           
$karma = 100;
        }
        elseif(
$karma < -100 )
        {
           
$karma = -100;
        }

        return
$karma;
    }


   
/**
     * Call a method on a Plugin.
     *
     * This makes sure that the Timer for the Plugin gets resumed.
     *
     * @param integer Plugin ID
     * @param string Method name.
     * @param array Params (by reference).
     * @return NULL|mixed Return value of the plugin's method call or NULL if no such method.
     */
   
function call_method( $plugin_ID, $method, & $params )
    {
        global
$Timer, $debug, $Debuglog;

       
$Plugin = & $this->get_by_ID( $plugin_ID );

        if( !
method_exists( $Plugin, $method ) )
        {
            return
NULL;
        }

        if(
$debug )
        {
           
/*
            // Note: this is commented out, because $debug_params gets not dumped anymore (last line of this block)
            // Hide passwords from Debuglog!
            // Clone/copy (references!):
            $debug_params = array();
            foreach( $params as $k => $v )
            {
                $debug_params[$k] = $v;
            }
            if( isset($debug_params['pass']) )
            {
                $debug_params['pass'] = '-hidden-';
            }
            if( isset($debug_params['pass_md5']) )
            {
                $debug_params['pass_md5'] = '-hidden-';
            }
            $Debuglog->add( 'Calling '.$Plugin->classname.'(#'.$Plugin->ID.')->'.$method.'( '.htmlspecialchars(var_export( $debug_params, true )).' )', 'plugins' );
            */
           
$Debuglog->add( 'Calling '.$Plugin->classname.'(#'.$Plugin->ID.')->'.$method.'( )', 'plugins' );
        }

        if(
$method == 'CacheObjects' )
        {    
// Deny plugins with deprecated event:
           
debug_die( 'The plugin event CacheObjects is deprecated' );
        }

       
$Timer->resume( $Plugin->classname.'_(#'.$Plugin->ID.')' );
       
$r = $Plugin->$method( $params );
       
$Timer->pause( $Plugin->classname.'_(#'.$Plugin->ID.')' );

        return
$r;
    }


   
/**
     * Call a method on a Plugin if it is not deactivated.
     *
     * This is a wrapper around {@link call_method()}.
     *
     * fp> why doesn't call_method always check if it's deactivated?
     *
     * @param integer Plugin ID
     * @param string Method name.
     * @param array Params (by reference).
     * @return NULL|mixed Return value of the plugin's method call or NULL if no such method (or inactive).
     */
   
function call_method_if_active( $plugin_ID, $method, & $params )
    {
        if( !
$this->has_event($plugin_ID, $method) )
        {
            return
NULL;
        }

        return
$this->call_method( $plugin_ID, $method, $params );
    }


   
/**
     * Call a specific plugin by its code.
     *
     * This will call the {@link Plugin::SkinTag()} event handler.
     *
     * @param string plugin code
     * @param array Associative array of parameters (gets passed to the plugin)
     * @return boolean
     */
   
function call_by_code( $code, $params = array() )
    {
       
$Plugin = & $this->get_by_code( $code );

        if( !
$Plugin )
        {
            global
$Debuglog;
           
$Debuglog->add( 'No plugin available for code ['.$code.']!', array('plugins', 'error') );
            return
false;
        }

       
$this->call_method_if_active( $Plugin->ID, 'SkinTag', $params );

        return
true;
    }


   
/**
     * Render the content of an item, Comment, Message, Widget by calling the relevant renderer plugins.
     *
     * @param string content to render (by reference)
     * @param array renderer codes to use for opt-out, opt-in and lazy
     * @param string Output format, see {@link format_to_output()}. Only 'htmlbody',
     *        'entityencoded', 'xml', 'htmlfeed' and 'text' are supported.
     * @param array Additional params to the Render* methods (e.g. "Item" for items).
     *              Do not use "data" or "format" here, because it gets used internally.
     * @param string Prefix of render function: 'Render' - for rendering at save in DB time, 'Display' - for rendering at display on screen time
     * @return string rendered content
     */
   
function render( & $content, $renderers, $format, $params, $event_prefix = 'Render' )
    {
       
$params['data'] = & $content;
       
$params['format'] = $format;

        if(
in_array( $format, array('htmlbody','htmlfeed','entityencoded') ) )
        {
           
$event = $event_prefix.'ItemAsHtml'; // 'RenderItemAsHtml'/'DisplayItemAsHtml'
       
}
        elseif(
$format == 'xml' )
        {
           
$event = $event_prefix.'ItemAsXml'; // 'RenderItemAsXml'/'DisplayItemAsXml'
       
}
        elseif(
$format == 'text' )
        {
           
$event = $event_prefix.'ItemAsText'; // 'RenderItemAsText'/'DisplayItemAsText'
       
}
        else
debug_die( 'Unexpected $format passed to Plugins::render(): '.var_export($format, true) );

       
$renderer_Plugins = $this->get_list_by_event( $event );
       
// Set blog from where the "apply_rendering" collection setting should be get
       
if( isset( $params['Item'] ) && !empty( $params['Item'] ) )
        {
// the Item is set, get Item Blog
           
$Item = & $params['Item'];
           
$setting_Blog = & $Item->get_Blog();
        }
        else
        {
// use global Blog if it is set
           
global $Collection, $Blog;
            if( !empty(
$Blog ) )
            {
               
$setting_Blog = $Blog;
            }
            else
            {    
// Try to get Blog by global blog ID
               
global $blog;
                if( !empty(
$blog ) )
                {
                   
$BlogCache = & get_BlogCache();
                   
$setting_Blog = & $BlogCache->get_by_ID( $blog );
                }
                else
                {
                   
$setting_Blog = NULL;
                }
            }
        }

        if( empty(
$setting_Blog ) )
        {
// This should be impossible, but make sure $setting_Blog is set
           
$setting_Blog = & get_setting_Blog( 'default_blog_ID' );
        }

       
// Collect all renderer plugins used in current page in order to know when we really need to load their JS/CSS files:
       
global $evo_renderers_used_in_current_page;
        if( !
is_array( $evo_renderers_used_in_current_page ) )
        {    
// Initialize array for curently used renderer plugins once:
           
$evo_renderers_used_in_current_page = array();
        }
       
$evo_renderers_used_in_current_page += $renderers;

        foreach(
$renderer_Plugins as $loop_RendererPlugin )
        {
// Go through whole list of renders
           
$apply_rendering_value = $loop_RendererPlugin->get_coll_setting( 'coll_apply_rendering', $setting_Blog );
            if(
$loop_RendererPlugin->is_renderer_enabled( $apply_rendering_value, $renderers ) )
            {
// Plugin is enabled to call method
               
$this->call_method( $loop_RendererPlugin->ID, $event, $params );
            }
        }

        return
$content;
    }


   
/**
     * Quick-render a string with a single plugin and format it for output.
     *
     * @todo rename
     *
     * @param string Plugin code (must have render() method)
     * @param array
     *   'data': Data to render
     *   'format: format to output, see {@link format_to_output()}
     * @return string Rendered string
     */
   
function quick( $plugin_code, $params )
    {
        global
$Debuglog;

        if( !
is_array($params) )
        {
           
$params = array( 'format' => 'htmlbody', 'data' => $params );
        }
        else
        {
           
$params = $params; // copy
       
}

       
$Plugin = & $this->get_by_code( $plugin_code );
        if(
$Plugin )
        {
           
// Get the most appropriate handler:
           
$events = $this->get_enabled_events( $Plugin->ID );
           
$event = false;
            if(
$params['format'] == 'htmlbody' || $params['format'] == 'htmlentityencoded' )
            {
                if(
in_array( 'RenderItemAsHtml', $events ) )
                {
                   
$event = 'RenderItemAsHtml';
                }
            }
            elseif(
$params['format'] == 'xml' )
            {
                if(
in_array( 'RenderItemAsXml', $events ) )
                {
                   
$event = 'RenderItemAsXml';
                }
            }

            if(
$event )
            {
               
$this->call_method( $Plugin->ID, $event, $params );
            }
            else
            {
               
$Debuglog->add( $Plugin->classname.'(ID '.$Plugin->ID.'): failed to quick-render (tried method '.$event.')!', array( 'plugins', 'error' ) );
            }
            return
format_to_output( $params['data'], $params['format'] );
        }
        else
        {
           
$Debuglog->add( 'Plugins::quick() - failed to instantiate Plugin by code ['.$plugin_code.']!', array( 'plugins', 'error' ) );
            return
format_to_output( $params['data'], $params['format'] );
        }
    }


   
/**
     * Load Plugins data from T_plugins (only once), ordered by priority.
     *
     * This fills the needed indexes to lazy-instantiate a Plugin when requested.
     */
   
function load_plugins_table()
    {
        if(
$this->loaded_plugins_table )
        {
            return;
        }
        global
$Debuglog, $DB;

       
$Debuglog->add( 'Loading plugins table data.', 'plugins' );

       
$this->index_ID_rows = array();
       
$this->index_code_ID = array();
       
$this->sorted_IDs = array();

        foreach(
$DB->get_results( $this->sql_load_plugins_table, ARRAY_A, 'Loading plugins table data' ) as $row )
        {
// Loop through installed plugins:
           
$this->index_ID_rows[$row['plug_ID']] = $row; // remember the rows to instantiate the Plugin on request
           
if( ! empty( $row['plug_code'] ) )
            {
               
$this->index_code_ID[$row['plug_code']] = $row['plug_ID'];
            }

           
$this->sorted_IDs[] = $row['plug_ID'];
        }

       
$this->loaded_plugins_table = true;
    }


   
/**
     * Load Plugin data from T_plugins by Class name
     *
     * This fills the needed indexes to lazy-instantiate a Plugin when requested.
     *
     * @param string Class name
     */
   
function load_plugin_by_classname( $classname )
    {
        global
$Debuglog, $DB;

       
$Debuglog->add( sprintf( 'Loading plugin %s by class name.', $classname ), 'plugins' );

       
$SQL = new SQL( 'Load Plugin data by class name' );
       
$SQL->SELECT( 'plug_ID, plug_priority, plug_classname, plug_code, plug_name, plug_shortdesc, plug_status, plug_version, plug_spam_weight' );
       
$SQL->FROM( 'T_plugins' );
       
$SQL->WHERE( 'plug_classname = '.$DB->quote( $classname ) );
        if(
$plugin = $DB->get_row( $SQL, ARRAY_A ) )
        {
            if( isset(
$this->index_ID_rows[$plugin['plug_ID']] ) )
            {
// Plugin already was loaded before
               
return;
            }

           
$this->index_ID_rows[$plugin['plug_ID']] = $plugin; // remember the rows to instantiate the Plugin on request
           
if( ! empty( $plugin['plug_code'] ) )
            {
               
$this->index_code_ID[$plugin['plug_code']] = $plugin['plug_ID'];
            }

           
$this->sorted_IDs[] = $plugin['plug_ID'];

           
// Load the events of this plugin
           
$this->load_events_by_classname( $classname );
        }
    }


   
/**
     * Load rendering Plugins specific apply_rendering setting for the given Blog and setting_name
     *
     * @param String setting name ( 'coll_apply_rendering', 'coll_apply_comment_rendering' )
     * @param Object the Blog which apply rendering setting should be loaded
     * @param string Setting type: 'coll', 'msg', 'email', 'shared'
     */
   
function load_index_apply_rendering( $setting_name, & $Blog, $type = 'coll' )
    {
        if(
is_null( $Blog ) )
        {
// Use general settings (e.g. for Messages)
           
$blog_ID = 0;
        }
        else
        {
// Use collection settings (for Items and Comments)
           
$blog_ID = $Blog->ID;
        }

        if( isset(
$this->index_apply_rendering_codes[$blog_ID][$setting_name] ) )
        {
// This data is already loaded
           
return;
        }

       
// make sure plugins are loaded
       
$this->load_plugins_table();

       
$index = & $this->sorted_IDs;
        foreach(
$index as $plugin_ID )
        {
// iterate through each plugin
           
$Plugin = $this->get_by_ID( $plugin_ID );
            if(
$Plugin->group != 'rendering' )
            {
// This plugin doesn't belong to the rendering plugins group
               
continue;
            }
            switch(
$type )
            {
                case
'coll':
                   
// Get plugin collection setting value:
                   
$rendering_value = $Plugin->get_coll_setting( $setting_name, $Blog );
                    break;

                case
'msg':
                   
// Get plugin message setting value:
                   
$rendering_value = $Plugin->get_msg_setting( $setting_name );
                    break;

                case
'email':
                   
// Get plugin email setting value:
                   
$rendering_value = $Plugin->get_email_setting( $setting_name );
                    break;

                case
'shared':
                   
// Get plugin shared setting value:
                   
$rendering_value = 'opt-in';
                    break;

                default:
                   
debug_die( 'Invalid plugin setting type!' );
                   
// EXIT HERE.
           
}
           
$this->index_apply_rendering_codes[$blog_ID][$setting_name][$rendering_value][] = $Plugin->code;
        }
    }


   
/**
     * Get a specific plugin by its ID.
     *
     * This is the workhorse when it comes to lazy-instantiating a Plugin.
     *
     * @param integer plugin ID
     * @return Plugin (false in case of error)
     */
   
function & get_by_ID( $plugin_ID )
    {
        global
$Debuglog;

        if( ! isset(
$this->index_ID_Plugins[ $plugin_ID ]) )
        {
// Plugin is not instantiated yet
           
$Debuglog->add( 'get_by_ID(): Instantiate Plugin (ID '.$plugin_ID.').', 'plugins' );

           
$this->load_plugins_table();

           
#pre_dump( 'get_by_ID(), index_ID_rows', $this->index_ID_rows );

           
if( ! isset( $this->index_ID_rows[$plugin_ID] ) || ! $this->index_ID_rows[$plugin_ID] )
            {
// no plugin rows cached
                #debug_die( 'Cannot instantiate Plugin (ID '.$plugin_ID.') without DB information.' );
               
$Debuglog->add( 'get_by_ID(): Plugin (ID '.$plugin_ID.') not registered/enabled in DB!', array( 'plugins', 'error' ) );
               
$r = false;
                return
$r;
            }

           
$row = & $this->index_ID_rows[$plugin_ID];

           
// Register the plugin:
           
$Plugin = & $this->register( $row['plug_classname'], $row['plug_ID'], $row['plug_priority'] );

            if(
is_string( $Plugin ) )
            {
               
$Debuglog->add( 'Requested plugin [#'.$plugin_ID.'] not found!', 'plugins' );
               
$r = false;
                return
$r;
            }

           
$this->index_ID_Plugins[ $plugin_ID ] = & $Plugin;
        }

        return
$this->index_ID_Plugins[ $plugin_ID ];
    }


   
/**
     * Get a plugin by its classname.
     *
     * @param string
     * @return Plugin (false in case of error)
     */
   
function & get_by_classname( $classname )
    {
       
$this->load_plugins_table(); // We use index_ID_rows (no own index yet)

       
foreach( $this->index_ID_rows as $plug_ID => $row )
        {
            if(
$row['plug_classname'] == $classname )
            {
                return
$this->get_by_ID($plug_ID);
            }
        }

       
$r = false;
        return
$r;
    }


   
/**
     * Get a specific Plugin by its code.
     *
     * @param string plugin code
     * @return Plugin (false in case of error)
     */
   
function & get_by_code( $plugin_code )
    {
        global
$Debuglog;

       
$r = false;

        if( ! isset(
$this->index_code_Plugins[ $plugin_code ]) )
        {
// Plugin is not registered yet
           
$this->load_plugins_table();

            if( ! isset(
$this->index_code_ID[ $plugin_code ]) )
            {
               
$Debuglog->add( 'Requested plugin ['.$plugin_code.'] is not registered/enabled!', 'plugins' );
                return
$r;
            }

            if( !
$this->get_by_ID( $this->index_code_ID[$plugin_code] ) )
            {
               
$Debuglog->add( 'Requested plugin ['.$plugin_code.'] could not get instantiated!', 'plugins' );
                return
$r;
            }
        }

        return
$this->index_code_Plugins[ $plugin_code ];
    }


   
/**
     * Get a list of Plugins for a given event.
     *
     * @param string Event name
     * @return array plugin_ID => & Plugin
     */
   
function get_list_by_event( $event )
    {
       
$r = array();

        if( isset(
$this->index_event_IDs[$event]) )
        {
            foreach(
$this->index_event_IDs[$event] as $l_plugin_ID )
            {
                if(
$Plugin = & $this->get_by_ID( $l_plugin_ID ) )
                {
                   
$r[ $l_plugin_ID ] = & $Plugin;
                    unset(
$Plugin); // so that we do not overwrite the reference in the next loop
               
}
            }
        }

        return
$r;
    }


   
/**
     * Get a list of Plugins for a list of events. Every Plugin is only once in this list.
     *
     * @param array Array of events
     * @return array plugin_ID => & Plugin
     */
   
function get_list_by_events( $events )
    {
       
$r = array();

        foreach(
$events as $l_event )
        {
            foreach(
array_keys($this->get_list_by_event( $l_event )) as $l_plugin_ID )
            {
                if(
$Plugin = & $this->get_by_ID( $l_plugin_ID ) )
                {
                   
$r[ $l_plugin_ID ] = & $Plugin;
                    unset(
$Plugin); // so that we do not overwrite the reference in the next loop
               
}
            }
        }

        return
$r;
    }


   
/**
     * Get a list of plugins that provide all given events.
     *
     * @return array plugin_ID => & Plugin
     */
   
function get_list_by_all_events( $events )
    {
       
$candidates = array();

        foreach(
$events as $l_event )
        {
            if( empty(
$this->index_event_IDs[$l_event]) )
            {
                return array();
            }

            if( empty(
$candidates) )
            {
               
$candidates = $this->index_event_IDs[$l_event];
            }
            else
            {
               
$candidates = array_intersect( $candidates, $this->index_event_IDs[$l_event] );
                if( empty(
$candidates) )
                {
                    return array();
                }
            }
        }

       
$r = array();
        foreach(
$candidates as $plugin_ID )
        {
           
$Plugin = & $this->get_by_ID( $plugin_ID );
            if(
$Plugin )
            {
               
$r[ $plugin_ID ] = & $Plugin;
                unset(
$Plugin); // so that we do not overwrite the reference in the next loop
           
}
        }

        return
$r;
    }


   
/**
     * Get a list of (enabled) events for a given Plugin ID.
     *
     * @param integer Plugin ID
     * @return array
     */
   
function get_enabled_events( $plugin_ID )
    {
       
$r = array();
        foreach(
$this->index_event_IDs as $l_event => $l_plugin_IDs )
        {
            if(
in_array( $plugin_ID, $l_plugin_IDs ) )
            {
               
$r[] = $l_event;
            }
        }
        return
$r;
    }


   
/**
     * Has a plugin a specific event registered and enabled?
     *
     * @todo fp> The plugin should discover its events itself / This question should be asked to the Plugin itself.
     *       dh> Well, "discover" means to look up if it has a given method; this does not appear to belong into the Plugin class.
     *           A plugin won't also really know if some event is enabled or disabled.
     *
     * @param integer
     * @param string
     * @return boolean
     */
   
function has_event( $plugin_ID, $event )
    {
        return isset(
$this->index_event_IDs[$event])
            &&
in_array( $plugin_ID, $this->index_event_IDs[$event] );
    }


   
/**
     * Check if the requested list of events is provided by any or one plugin.
     *
     * @param array|string A single event or a list thereof
     * @param boolean Make sure there's at least one plugin that provides them all?
     *                This is useful for event pairs like "RequestCaptcha" and "ValidateCaptcha", which
     *                should be served by the same plugin.
     * @return boolean
     */
   
function are_events_available( $events, $require_all_in_same_plugin = false )
    {
        if( !
is_array($events) )
        {
           
$events = array($events);
        }

        if(
$require_all_in_same_plugin )
        {
            return (bool)
$this->get_list_by_all_events( $events );
        }

        return (bool)
$this->get_list_by_events( $events );
    }


   
/**
     * (Re)load Plugin Events for enabled (normal use) or all (admin use) plugins.
     */
   
function load_events()
    {
        global
$Debuglog, $DB;

       
$this->index_event_IDs = array();

       
$Debuglog->add( 'Loading plugin events.', 'plugins' );
        foreach(
$DB->get_results( '
                SELECT pevt_plug_ID, pevt_event
                    FROM T_pluginevents INNER JOIN T_plugins ON pevt_plug_ID = plug_ID
                 WHERE pevt_enabled > 0
                   AND plug_status = \'enabled\'
                 ORDER BY plug_priority, plug_classname'
, OBJECT, 'Loading plugin events' ) as $l_row )
        {
           
$this->index_event_IDs[$l_row->pevt_event][] = $l_row->pevt_plug_ID;
        }
    }


   
/**
     * (Re)load Plugin Events plugin by classname.
     *
     * @param string Class name
     */
   
function load_events_by_classname( $classname )
    {
        global
$Debuglog, $DB;

       
$Debuglog->add( sprintf( 'Loading plugin events by classname "%s".', $classname ), 'plugins' );
        foreach(
$DB->get_results( '
                SELECT pevt_plug_ID, pevt_event
                    FROM T_pluginevents INNER JOIN T_plugins ON pevt_plug_ID = plug_ID
                 WHERE pevt_enabled > 0
                   AND plug_classname = '
.$DB->quote( $classname ), OBJECT, sprintf( 'Loading plugin events by classname "%s"', $classname ) ) as $l_row )
        {
           
$this->index_event_IDs[$l_row->pevt_event][] = $l_row->pevt_plug_ID;
        }
    }


   
/**
     * Load an object from a Cache plugin or create a new one if we have a
     * cache miss or no caching plugins.
     *
     * It registers a shutdown function, that refreshes the data to the cache plugin
     * which is not optimal, but we have no hook to see if data retrieved from
     * a {@link DataObjectCache} derived class has changed.
     * @param string object name
     * @param string eval this to create the object. Default is to create an object
     *               of class $objectName.
     * @return boolean True, if retrieved from cache; false if not
     */
   
function get_object_from_cacheplugin_or_create( $objectName, $eval_create_object = NULL )
    {
       
$get_return = $this->trigger_event_first_true( 'CacheObjects',
            array(
'action' => 'get', 'key' => 'object_'.$objectName ) );

        if( isset(
$get_return['plugin_ID'] ) )
        {
            if(
is_object($get_return['data']) )
            {
               
$GLOBALS[$objectName] = $get_return['data']; // COPY! (get_Cache() uses $$objectName instead of $GLOBALS - no deal for PHP5 anyway)

               
$Plugin = & $this->get_by_ID( $get_return['plugin_ID'] );
               
register_shutdown_function( array(&$Plugin, 'CacheObjects'),
                    array(
'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) );

                return
true;
            }
        }

       
// Cache miss, create it:
       
if( empty($eval_create_object) )
        {
           
$GLOBALS[$objectName] = new $objectName(); // COPY (FUNC)
       
}
        else
        {
            eval(
'$GLOBALS[\''.$objectName.'\'] = '.$eval_create_object.';' );
        }

       
// Try to set in cache:
       
$set_return = $this->trigger_event_first_true( 'CacheObjects',
            array(
'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) );

        if( isset(
$set_return['plugin_ID'] ) )
        {
// success, register a shutdown function to save this data on shutdown
           
$Plugin = & $this->get_by_ID( $set_return['plugin_ID'] );
           
register_shutdown_function( array(&$Plugin, 'CacheObjects'),
                array(
'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) );
        }

        return
false;
    }


   
/**
     * Callback, which gets used for {@link Results}.
     *
     * @return Plugin (false in case of error)
     */
   
function & instantiate( $row )
    {
        return
$this->get_by_ID( $row->plug_ID );
    }


   
/**
     * Validate renderer list.
     *
     * @param array renderer codes ('default' will include all "opt-out"-ones)
     * @params array params to set Blog and apply_rendering setting_name ( see {@link load_index_apply_rendering()} )
     * @return array validated array of renderer codes
     */
   
function validate_renderer_list( $renderers = array('default'), $params )
    {
       
// Init Blog and $setting_name from the given params
       
if( isset( $params['Item'] ) )
        {
// Validate post renderers
           
$Item = & $params['Item'];
           
$Collection = $Blog = & $Item->get_Blog();
           
$setting_name = 'coll_apply_rendering';
           
$setting_type = 'coll';
        }
        elseif( isset(
$params['Comment'] ) )
        {
// Validate comment renderers
           
$Comment = & $params['Comment'];
           
$Item = & $Comment->get_Item();
           
$Collection = $Blog = & $Item->get_Blog();
           
$setting_name = 'coll_apply_comment_rendering';
           
$setting_type = 'coll';
        }
        elseif( isset(
$params['Message'] ) )
        {
// Validate message renderers
           
$Collection = $Blog = NULL;
           
$setting_name = 'msg_apply_rendering';
           
$setting_type = 'msg';
        }
        elseif( isset(
$params['EmailCampaign'] ) )
        {    
// Validate message renderers:
           
$Collection = $Blog = NULL;
           
$setting_name = 'email_apply_rendering';
           
$setting_type = 'email';
        }
        elseif( isset(
$params['setting_name'] ) )
        {    
// Validate the given rendering option:
           
$setting_name = $params['setting_name'];
            if( isset(
$params['Blog'] ) )
            {    
// If Collection is given:
               
$Collection = $Blog = & $params['Blog'];
               
$setting_type = 'coll';
                if(
$setting_name == 'shared_apply_rendering' )
                {    
// Force to posts/items rendering settings:
                   
$setting_name = 'coll_apply_rendering';
                }
                if( !
in_array( $setting_name, array( 'coll_apply_rendering', 'coll_apply_comment_rendering', 'shared_apply_rendering' ) ) )
                {
                   
debug_die( 'Invalid apply rendering param name received!' );
                }
            }
            if(
$setting_name == 'shared_apply_rendering' )
            {    
// Set shared type instead of collection for this spec setting name:
               
$Collection = $Blog = NULL;
               
$setting_type = 'shared';
            }
        }
        else
        {
// Invalid params to validate renderers!
           
return array();
        }

       
$blog_ID = empty( $Blog ) ? 0 : $Blog->ID;

       
// Make sure the requested apply_rendering settings are loaded
       
$this->load_index_apply_rendering( $setting_name, $Blog, $setting_type );

       
$validated_renderers = array();

       
// Get requested apply_rendering setting array
       
$index = & $this->index_apply_rendering_codes[$blog_ID][$setting_name];

        if( isset(
$index['stealth'] ) )
        {
           
// pre_dump( 'stealth:', $index['stealth'] );
           
$validated_renderers = array_merge( $validated_renderers, $index['stealth'] );
        }
        if( isset(
$index['always'] ) )
        {
           
// pre_dump( 'always:', $index['always'] );
           
$validated_renderers = array_merge( $validated_renderers, $index['always'] );
        }

        if( isset(
$index['opt-out'] ) )
        {
            foreach(
$index['opt-out'] as $l_code )
            {
                if(
in_array( $l_code, $renderers ) // Option is activated
                   
|| in_array( 'default', $renderers ) ) // OR we're asking for default renderer set
               
{
                   
// pre_dump( 'opt-out:', $l_code );
                   
$validated_renderers[] = $l_code;
                }
            }
        }

        if( isset(
$index['opt-in'] ) )
        {
            foreach(
$index['opt-in'] as $l_code )
            {
                if(
in_array( $l_code, $renderers ) ) // Option is activated
               
{
                   
// pre_dump( 'opt-in:', $l_code );
                   
$validated_renderers[] = $l_code;
                }
            }
        }
        if( isset(
$index['lazy'] ) )
        {
            foreach(
$index['lazy'] as $l_code )
            {
                if(
in_array( $l_code, $renderers ) ) // Option is activated
               
{
                   
// pre_dump( 'lazy:', $l_code );
                   
$validated_renderers[] = $l_code;
                }
            }
        }

       
// Make sure there's no renderer code with a dot, as the list gets imploded by that when saved:
       
foreach( $validated_renderers as $k => $l_code )
        {
            if( empty(
$l_code) || strpos( $l_code, '.' ) !== false )
            {
                unset(
$validated_renderers[$k] );
            }
           
/*
            dh> not required, now that the method has been moved back to Plugins (from ~_admin)
            else
            { // remove the ones which are not enabled:
                $Plugin = & $this->get_by_code($l_code);
                if( ! $Plugin || $Plugin->status != 'enabled' )
                {
                    unset( $validated_renderers[$k] );
                }
            }
            */
       
}

       
// echo 'validated Renderers: '.count( $validated_renderers );
       
return $validated_renderers;
    }


   
/**
     * Get renderers options which can be used for form selectors, checkboxes and etc.
     *
     * @param array If given, assume these renderers to be checked.
     * @param array params from where to get 'apply_rendering' setting
     */
   
function get_renderer_options( $current_renderers = NULL, $params )
    {
        global
$inc_path, $admin_url;

       
load_funcs( 'plugins/_plugin.funcs.php' );

       
$checkbox_options = array();

       
$this->restart(); // make sure iterator is at start position

       
if( ! is_array( $current_renderers ) )
        {
           
$current_renderers = explode( '.', $current_renderers );
        }

       
$setting_Blog = NULL;
        if( isset(
$params['setting_name'] ) && $params['setting_name'] == 'msg_apply_rendering' )
        {
// get Message apply_rendering setting
           
$setting_name = $params['setting_name'];
        }
        elseif( isset(
$params['setting_name'] ) && $params['setting_name'] == 'email_apply_rendering' )
        {    
// get EmailCampaign apply_rendering setting:
           
$setting_name = $params['setting_name'];
        }
        elseif( isset(
$params['setting_name'] ) && $params['setting_name'] == 'shared_apply_rendering' )
        {    
// get apply_rendering setting for widget from sharaed container:
           
$setting_name = $params['setting_name'];
        }
        elseif( isset(
$params['Comment'] ) && !empty( $params['Comment'] ) )
        {
// get Comment apply_rendering setting
           
$Comment = & $params['Comment'];
           
$comment_Item = & $Comment->get_Item();
           
$setting_Blog = & $comment_Item->get_Blog();
           
$setting_name = 'coll_apply_comment_rendering';
        }
        elseif( isset(
$params['Item'] ) )
        {
// get Post apply_rendering setting
           
$setting_name = 'coll_apply_rendering';
           
$Item = & $params['Item'];
           
$setting_Blog = & $Item->get_Blog();
        }
        elseif( isset(
$params['setting_name'] ) )
        {    
// Get given setting:
           
$setting_name = $params['setting_name'];
            if( ! empty(
$params['Blog'] ) )
            {    
// If Collection is given::
               
$setting_Blog = & $params['Blog'];
                if(
$setting_name == 'shared_apply_rendering' )
                {    
// Force to posts/items rendering settings:
                   
$setting_name = 'coll_apply_rendering';
                }
            }
        }
        else
        {
// Invalid params
           
return $checkbox_options;
        }

        switch(
$setting_name )
        {
            case
'msg_apply_rendering':
               
// Get Message renderer plugins
               
$RendererPlugins = $this->get_list_by_events( array('FilterMsgContent') );
                break;

            case
'email_apply_rendering':
               
// Get Email Campaign renderer plugins
               
$RendererPlugins = $this->get_list_by_events( array('FilterEmailContent') );
                break;

            case
'coll_apply_comment_rendering':
               
// Get Comment renderer plugins
               
$RendererPlugins = $this->get_list_by_events( array('FilterCommentContent') );
                break;

            case
'shared_apply_rendering':
            case
'coll_apply_rendering':
            default:
               
// Get Item renderer plugins
               
$RendererPlugins = $this->get_list_by_events( array( 'RenderItemAsHtml', 'RenderItemAsXml', 'RenderItemAsText', 'DisplayItemAsHtml', 'DisplayItemAsXml', 'DisplayItemAsText' ) );
                break;
        }

        foreach(
$RendererPlugins as $loop_RendererPlugin )
        {
// Go through whole list of renders
            // echo ' ',$loop_RendererPlugin->code;
           
if( empty( $loop_RendererPlugin->code ) )
            {
// No unique code!
               
continue;
            }
            if( empty(
$setting_Blog ) && $setting_name != 'msg_apply_rendering' && $setting_name != 'email_apply_rendering' && $setting_name != 'shared_apply_rendering' )
            {
// If $setting_Blog is not set we can't get collection apply_rendering options
               
continue;
            }

            if(
$setting_name == 'msg_apply_rendering' )
            {
// get rendering setting from plugin message settings
               
$apply_rendering = $loop_RendererPlugin->get_msg_setting( $setting_name );
            }
            elseif(
$setting_name == 'email_apply_rendering' )
            {    
// get rendering setting from plugin email settings:
               
$apply_rendering = $loop_RendererPlugin->get_email_setting( $setting_name );
            }
            elseif(
$setting_name == 'shared_apply_rendering' )
            {    
// get rendering setting from plugin shared settings:
               
$apply_rendering = $loop_RendererPlugin->get_shared_setting( $setting_name );
            }
            else
            {
// get rendering setting from plugin coll settings
               
$apply_rendering = $loop_RendererPlugin->get_coll_setting( $setting_name, $setting_Blog );
            }

           
$ignored_apply_rendering = array( 'stealth', 'never' );
            if( isset(
$params['ignored_apply_rendering'] ) )
            {
               
$ignored_apply_rendering = array_merge( $ignored_apply_rendering, $params['ignored_apply_rendering'] );
            }

            if(
in_array( $apply_rendering, $ignored_apply_rendering )
                || empty(
$apply_rendering ) )
            {
// This is not an option.
               
continue;
            }

           
$checkbox_option = array(
                   
'code'       => $loop_RendererPlugin->code,
                   
'short_desc' => $loop_RendererPlugin->short_desc,
                   
'name'       => $loop_RendererPlugin->name,
                   
'checked'    => false,
                   
'disabled'   => false,
                   
'help_link'  => $loop_RendererPlugin->get_help_link( '$help_url' ),
                );

            switch(
$apply_rendering )
            {
                case
'always':
                   
//$r .= ' checked="checked" disabled="disabled"';
                   
$checkbox_option['checked'] = true;
                   
$checkbox_option['disabled'] = true;
                    break;

                case
'opt-out':
                    if(
in_array( $loop_RendererPlugin->code, $current_renderers ) // Option is activated
                       
|| in_array( 'default', $current_renderers ) ) // OR we're asking for default renderer set
                   
{
                       
$checkbox_option['checked'] = true;
                    }
                    break;

                case
'opt-in':
                    if(
in_array( $loop_RendererPlugin->code, $current_renderers ) ) // Option is activated
                   
{
                       
$checkbox_option['checked'] = true;
                    }
                    break;

                case
'lazy':
                    if(
in_array( $loop_RendererPlugin->code, $current_renderers ) ) // Option is activated
                   
{
                       
$checkbox_option['checked'] = true;
                    }
                   
$checkbox_option['disabled'] = true;
                    break;
            }

           
$checkbox_options[] = $checkbox_option;
        }

        return
$checkbox_options;
    }


   
/**
     * Get checkable list of renderers
     *
     * @param array If given, assume these renderers to be checked.
     * @param array params from where to get 'apply_rendering' setting
     */
   
function get_renderer_checkboxes( $current_renderers = NULL, $params )
    {
        if( isset(
$params['setting_name'] ) )
        {    
// Use the defined setting name from params:
           
$setting_name = $params['setting_name'];
        }
        elseif( ! empty(
$params['Comment'] ) )
        {    
// Use setting name for Comment:
           
$setting_name = 'coll_apply_comment_rendering';
        }
        elseif( ! empty(
$params['Item'] ) )
        {    
// Use setting name for Item:
           
$setting_name = 'coll_apply_rendering';
        }
        else
        {    
// Invalid params, Exit here:
           
return '';
        }

       
$name_prefix = isset( $params['name_prefix'] ) ? $params['name_prefix'] : '';

       
// Set different prefix if you use several toolbars on one page:
       
$js_prefix = isset( $params['js_prefix'] ) ? $params['js_prefix'] : '';

       
$r = '<input type="hidden" name="renderers_displayed" value="1" />';

       
$renderer_checkbox_options = $this->get_renderer_options( $current_renderers, $params );
        foreach(
$renderer_checkbox_options as $option )
        {
           
$r .= '<div id="block_renderer_'.$option['code'].'">';

           
// Checkbox:
           
$r .= '<input type="checkbox" class="checkbox" name="'.$name_prefix.'renderers[]" value="'.$option['code'].'" id="'.$js_prefix.'renderer_'.$option['code'].'"';
            if(
$option['checked'] )
            {    
// Is checked:
               
$r .= ' checked="checked"';
            }
            if(
$option['disabled'] )
            {    
// Is disabled:
               
$r .= ' disabled="disabled"';
            }
           
$r .= ' title="'.format_to_output( $option['short_desc'], 'formvalue' ).'"';
            if( ! empty(
$js_prefix ) )
            {    
// Set prefix, Used in JS code to disable/enable plugin toolbar:
               
$r .= ' data-prefix="'.format_to_output( $js_prefix, 'formvalue' ).'"';
            }
           
$r .= ' />';

           
// Label:
           
$r .= ' <label for="'.$js_prefix.'renderer_'.$option['code'].'"';
           
$r .= ' title="'.format_to_output( $option['short_desc'], 'formvalue' ).'">';
           
$r .= format_to_output( $option['name'] ).'</label>';

           
// fp> TODO: the first thing we want here is a TINY javascript popup with the LONG desc. The links to readme and external help should be inside of the tiny popup.
            // fp> a javascript DHTML onhover help would be even better than the JS popup

            // External help link:
           
$r .= ' '.$option['help_link'];

           
$r .= "</div>\n";
        }

        if( empty(
$renderer_checkbox_options ) )
        {
            if(
is_admin_page() )
            {
// Display info about no renderer plugins only in backoffice
               
if( check_user_perm( 'admin', 'normal' ) )
                {
                    global
$admin_url;
                    switch(
$setting_name )
                    {
                        case
'msg_apply_rendering':
                            if(
check_user_perm( 'perm_messaging', 'reply' ) && check_user_perm( 'options', 'edit' ) )
                            {
// Check if current user can edit the messaging settings
                               
$settings_url = $admin_url.'?ctrl=msgsettings&amp;tab=renderers';
                            }
                            break;

                        case
'email_apply_rendering':
                            if(
check_user_perm( 'perm_messaging', 'reply' ) && check_user_perm( 'options', 'edit' ) )
                            {
// Check if current user can edit the email settings
                               
$settings_url = $admin_url.'?ctrl=email&amp;tab=settings&amp;tab3=renderers';
                            }
                            break;

                        case
'shared_apply_rendering':
                            if(
check_user_perm( 'options', 'edit' ) )
                            {    
// Check if current user can edit the plugin settings for shared container:
                               
$settings_url = $admin_url.'?ctrl=plugins&amp;tab=shared';
                            }
                            break;

                        case
'coll_apply_comment_rendering':
                        case
'coll_apply_rendering':
                        default:
                            if( ! empty(
$setting_Blog ) && check_user_perm( 'blog_properties', 'edit', false, $setting_Blog->ID ) )
                            {
// Check if current user can edit the blog plugin settings
                               
$settings_url = $admin_url.'?ctrl=coll_settings&amp;tab=plugins&amp;blog='.$setting_Blog->ID;
                            }
                            break;
                    }
                }

                if( ! empty(
$settings_url ) )
                {
// Display a link to plugin settings only when current user has a permission to edit them
                   
$r .= '<a title="'.T_('Configure plugins').'" href="'.$settings_url.'"'.'>';
                }
               
$r .= T_('No renderer plugins can be selected by the user.');
                if( ! empty(
$settings_url ) )
                {
                   
$r .= '</a>';
                }
            }
            else
            {
                return
'';
            }
        }

        return
$r;
    }


   
/**
     * Display captcha
     *
     * @param array Associative array of parameters:
     *   - 'Form':                   Form object
     *   - 'form_type':              Form type
     *   - 'form_position':          Current form position where this function is called
     *   - 'captcha_info':           Info under the captcha field in note style
     *   - 'captcha_info_anonymous': Captcha info for not logged in user when the plugin knows it will NOT ask for captcha in case of logged in users
     */
   
function display_captcha( $params = array() )
    {
        if( ! isset(
$params['Form'] ) ||
            ! isset(
$params['form_type'] ) ||
            ! isset(
$params['form_position'] ) )
        {    
// Exit here if the mandatory params are not defined:
           
return;
        }

       
$params = array_merge( array(
               
'captcha_template_question' => '<span class="evo_captcha_question">$captcha_question$</span><br>',
               
'captcha_template_answer'   => '<span class="evo_captcha_answer">$captcha_answer$</span><br>',
               
// Default captcha info text(can be customized by plugin):
               
'captcha_info' => T_('We ask for this in order to slow down spammers.')
                                                    .
'<br>'.T_('Sorry for the inconvenience.')
                                                    .(
is_logged_in() ? '' : '<br>'.T_('Please log in to avoid this antispam check.') ),
            ),
$params );

       
$form_type = $params['form_type'];

        if( ! isset(
$this->captcha_data ) )
        {    
// Initialize array to cache captcha data per current page request:
           
$this->captcha_data = array();
        }

        if( ! isset(
$this->captcha_data[ $form_type ] ) )
        {    
// Load once captcha data per form type and use this for all next calls of this function:
           
$plugin_data = $this->trigger_event_first_return( 'RequestCaptcha', $params );
           
$this->captcha_data[ $form_type ] = isset( $plugin_data['plugin_return'] ) ? $plugin_data['plugin_return'] : false;
        }

       
$captcha_data = $this->captcha_data[ $form_type ];

        if( isset(
$captcha_data['captcha_position'], $captcha_data['captcha_html'] ) &&
           
$captcha_data['captcha_position'] == $params['form_position'] )
        {    
// Display captcha html code only for requested form type and position:
           
$Form = & $params['Form'];
            if( ! isset(
$params['form_use_fieldset'] ) || $params['form_use_fieldset'] )
            {    
// Begin fieldset if it is required from skin file:
               
$Form->begin_fieldset();
            }

           
$Form->info_field( T_('Antispam'), $captcha_data['captcha_html'], array(
               
'note'     => ( isset( $captcha_data['captcha_info'] ) ? $captcha_data['captcha_info'] : $params['captcha_info'] ),
               
'required' => true,
            ) );

            if( ! isset(
$params['form_use_fieldset'] ) || $params['form_use_fieldset'] )
            {    
// End fieldset if it is required from skin file:
               
$Form->end_fieldset();
            }
        }
    }


   
// Deprecated stubs: {{{

    /**
     * @deprecated by Plugins_admin::count_regs()
     */
   
function count_regs( $classname )
    {
        global
$Debuglog;
       
$Debuglog->add('Call to deprecated method Plugins::count_regs()', 'deprecated');
       
$Plugins_admin = & get_Plugins_admin();
        return
$Plugins_admin->count_regs($classname);
    }


   
/**
     * Validate renderer list.
     *
     * @deprecated by Plugins::validate_renderer_list()
     * @param array renderer codes ('default' will include all "opt-out"-ones)
     * @return array validated array of renderer codes
     */
   
function validate_list( $renderers = array('default') )
    {
        global
$Debuglog, $Plugins, $debug;
       
$Debuglog->add('Call to deprecated method Plugins::validate_list()', 'deprecated');

       
$error_msg = 'Error because of a deprecated method call Plugins::validate_list().';
        if(
$debug == 0 )
        {
           
$error_msg = $error_msg."<br />".'Enable debugging to see which incompatible plugin has triggered this error and uninstall the deprecated plugin.';
           
$error_msg = $error_msg."<br />".get_manual_link( 'debugging', 'Check the manual about how to enable debugging.' );
        }
        else
        {
           
$error_msg = $error_msg."<br />".'Check the debug info below which incompatible plugin has triggered this error and uninstall the deprecated plugin.';
        }
       
$error_msg = $error_msg."<br />".get_manual_link( 'plugins', 'Check the manual about how to uninstall a plugin.' );
       
debug_die( $error_msg );
       
// Already exited here

        // Return an empty array, because this method call is not supported anymore
       
return array();
    }


   
// }}}
}

?>