$default_placement));
/* Check for multiple dependencies */
$plugin =& serendipity_plugin_api::load_plugin($key, $authorid, $pluginPath);
if (is_object($plugin)) {
$bag = new serendipity_property_bag();
$plugin->introspect($bag);
serendipity_plugin_api::get_event_plugins(false, true); // Refresh static list of plugins to allow execution of added plugin
$plugin->register_dependencies(false, $authorid);
$plugin->install();
} else {
$serendipity['debug']['pluginload'][] = "Loading plugin failed painfully. File not found?";
echo ERROR . ': ' . $key . ' (' . $pluginPath . ')
';
}
return $key;
}
/**
* Removes a plugin by it's instance name
*
* @access public
* @param string The name of the plugin id ("serendipity_plugin_xxx:1232132fsdf")
* @return null
*/
function remove_plugin_instance($plugin_instance_id)
{
global $serendipity;
$plugin =& serendipity_plugin_api::load_plugin($plugin_instance_id);
if (is_object($plugin)) {
$bag = new serendipity_property_bag();
$plugin->introspect($bag);
$plugin->uninstall($bag);
}
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}plugins where name='$plugin_instance_id'");
if (is_object($plugin)) {
$plugin->register_dependencies(true);
}
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}config where name LIKE '$plugin_instance_id/%'");
}
/**
* Removes an empty plugin configuration value
*
* @access public
* @param string The name of the plugin id ("serendipity_plugin_xxx:1232132fsdf")
* @param array An array of configuration item names
* @return null
*/
function remove_plugin_value($plugin_instance_id, $where)
{
global $serendipity;
$where_sql = array();
foreach($where AS $key) {
$where_sql[] = "(name LIKE '{$plugin_instance_id}/{$key}_%' AND value = '')";
}
$query = "DELETE FROM {$serendipity['dbPrefix']}config
WHERE " . implode(' OR ', $where_sql);
serendipity_db_query($query);
}
/**
* Retrieve a list of available plugin classes
*
* This function searches through all directories and loaded internal files and tries
* to detect the serendipity plugins.
*
* @access public
* @param boolean If true, only event plugins will be searched. If false, sidebar plugins will be searched.
* @return
*/
function &enum_plugin_classes($event_only = false)
{
global $serendipity;
$classes = array();
/* built-in classes first */
$cls = get_declared_classes();
foreach ($cls AS $class_name) {
if (strncmp($class_name, 'serendipity_', 6)) {
continue;
}
$p = get_parent_class($class_name);
while ($p != 'serendipity_plugin' && $p != 'serendipity_event' && $p !== false) {
$p = get_parent_class($p);
}
if ($p == 'serendipity_plugin' && $class_name != 'serendipity_event' && (!$event_only || is_null($event_only))) {
$classes[$class_name] = array('name' => '@' . $class_name,
'type' => 'internal_event',
'true_name' => $class_name,
'pluginPath' => '');
} elseif ($p == 'serendipity_event' && $class_name != 'serendipity_event' && ($event_only || is_null($event_only))) {
$classes[$class_name] = array('name' => '@' . $class_name,
'type' => 'internal_plugin',
'true_name' => $class_name,
'pluginPath' => '');
}
}
/* GLOBAL third-party classes next */
$ppath = serendipity_getRealDir(__FILE__) . 'plugins';
serendipity_plugin_api::traverse_plugin_dir($ppath, $classes, $event_only);
/* LOCAL third-party classes next */
$local_ppath = $serendipity['serendipityPath'] . 'plugins';
if ($ppath != $local_ppath) {
serendipity_plugin_api::traverse_plugin_dir($local_ppath, $classes, $event_only);
}
return $classes;
}
/**
* Traverse a specific directory and search if a serendipity plugin exists there.
*
* @access public
* @param string The path to start from (usually '.')
* @param array A referenced array of currently found classes
* @param boolean If true, only event plugins will be searched. If false, only sidebar plugins will be searched.
* @param string The maindir where we started searching from [for recursive use]
* @return
*/
function traverse_plugin_dir($ppath, &$classes, $event_only, $maindir = '')
{
$d = @opendir($ppath);
if ($d) {
while (($f = readdir($d)) !== false) {
if ($f[0] == '.' || $f == 'CVS' || !is_dir($ppath . '/' . $f) || !is_readable($ppath . '/' .$f)) {
continue;
}
$subd = opendir($ppath . '/' . $f);
if (!$subd) {
continue;
}
// Instead of only looking for directories, search for files within subdirectories
$final_loop = false;
while (($subf = readdir($subd)) !== false) {
if ($subf[0] == '.' || $subf == 'CVS') {
continue;
}
if (!$final_loop && is_dir($ppath . '/' . $f . '/' . $subf) && $maindir != $ppath . '/' . $f) {
// Search for another level of subdirectories
serendipity_plugin_api::traverse_plugin_dir($ppath . '/' . $f, $classes, $event_only, $f . '/');
// We can break after that operation because the current directory has been fully checked already.
$final_loop = true;
}
if (!preg_match('@^[^_]+_(event|plugin)_.+\.php$@i', $subf)) {
continue;
}
$class_name = str_replace('.php', '', $subf);
// If an external plugin/event already exists as internal, remove the internal reference because its redundant
if (isset($classes['@' . $class_name])) {
unset($classes['@' . $class_name]);
}
// A local plugin will be preferred over general plugins [used when calling this function the second time]
if (isset($classes[$class_name])) {
unset($classes[$class_name]);
}
if (!is_null($event_only) && $event_only && !serendipity_plugin_api::is_event_plugin($subf)) {
continue;
}
if (!is_null($event_only) && !$event_only && serendipity_plugin_api::is_event_plugin($subf)) {
continue;
}
$classes[$class_name] = array('name' => $class_name,
'true_name' => $class_name,
'type' => 'additional_plugin',
'pluginPath' => $maindir . $f);
}
closedir($subd);
}
closedir($d);
}
}
/**
* Returns a list of currently installed plugins
*
* @access public
* @param string The filter for plugins (left|right|hide|event|eventh)
* @return array The list of plugins
*/
function get_installed_plugins($filter = '*')
{
$plugins = serendipity_plugin_api::enum_plugins($filter);
$res = array();
foreach ( (array)$plugins AS $plugin ) {
list($class_name) = explode(':', $plugin['name']);
$class_name = ltrim($class_name, '@');
$res[] = $class_name;
}
return $res;
}
/**
* Searches for installed plugins based on specific conditions
*
* @access public
* @param string The filter for plugins (left|right|hide|event|eventh)
* @param boolean If true, the filtering logic will be reversed an all plugins that are NOT part of the filter will be returned
* @param string Filter by a specific classname (like 'serendipity_plugin_archives'). Can take SQL wildcards.
* @param string Filter by a specific plugin instance id
* @return array Returns the associative array of found plugins in the database
*/
function enum_plugins($filter = '*', $negate = false, $classname = null, $id = null)
{
global $serendipity;
$sql = "SELECT * from {$serendipity['dbPrefix']}plugins ";
$where = array();
if ($filter !== '*') {
if ($negate) {
$where[] = " placement != '" . serendipity_db_escape_string($filter) . "' ";
} else {
$where[] = " placement = '" . serendipity_db_escape_string($filter) . "' ";
}
}
if (!empty($classname)) {
$where[] = " (name LIKE '@" . serendipity_db_escape_string($classname) . "%' OR name LIKE '" . serendipity_db_escape_string($classname) . "%') ";
}
if (!empty($id)) {
$where[] = " name = '" . serendipity_db_escape_string($id) . "' ";
}
if (count($where) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= ' ORDER BY placement, sort_order';
return serendipity_db_query($sql);
}
/**
* Count the number of plugins to which the filter criteria matches
*
* @access public
* @param string The filter for plugins (left|right|hide|event|eventh)
* @param boolean If true, the filtering logic will be reversed an all plugins that are NOT part of the filter will be evaluated
* @return int Number of plugins that were found.
*/
function count_plugins($filter = '*', $negate = false)
{
global $serendipity;
// Can be shortcircuited via a $serendipity['prevent_sidebar_plugins_(left|right|event)'] variable!
if (!$negate && $serendipity['prevent_sidebar_plugins_' . $filter] == true) {
return 0;
}
$sql = "SELECT COUNT(placement) AS count from {$serendipity['dbPrefix']}plugins ";
if ($filter !== '*') {
if ($negate) {
$sql .= "WHERE placement != '$filter' ";
} else {
$sql .= "WHERE placement='$filter' ";
}
}
$count = serendipity_db_query($sql, true);
if (is_array($count) && isset($count[0])) {
return (int) $count[0];
}
return 0;
}
/**
* Detect the filename to use for a specific plugin
*
* @access public
* @param string The name of the plugin ('serendipity_event_archive')
* @param string The path to the plugin file (if empty, the current path structure will be used.)
* @param string If an instance ID is passed this means, the plugin to be loaded is internally available
* @return string Returns the filename to include for a specific plugin
*/
function includePlugin($name, $pluginPath = '', $instance_id = '')
{
global $serendipity;
if (empty($pluginPath)) {
$pluginPath = $name;
}
$file = false;
// Security constraint
$pluginFile = 'plugins/' . $pluginPath . '/' . $name . '.php';
$pluginFile = preg_replace('@([\r\n\t\0\\\]+|\.\.+)@', '', $pluginFile);
// First try the local path, and then (if existing) a shared library repository ...
// Internal plugins ignored.
if (!empty($instance_id) && $instance_id[0] == '@') {
$file = S9Y_INCLUDE_PATH . 'include/plugin_internal.inc.php';
} elseif (file_exists($serendipity['serendipityPath'] . $pluginFile)) {
$file = $serendipity['serendipityPath'] . $pluginFile;
} elseif (file_exists(S9Y_INCLUDE_PATH . $pluginFile)) {
$file = S9Y_INCLUDE_PATH . $pluginFile;
}
return $file;
}
/**
* Returns the plugin class name by a plugin instance ID
*
* @access public
* @param string The ID of a plugin
* @param boolean If true, the plugin is a internal plugin (prefixed with '@')
* @return string The classname of the plugin
*/
function getClassByInstanceID($instance_id, &$is_internal)
{
$instance = explode(':', $instance_id);
$class_name = ltrim($instance[0], '@');
return $class_name;
}
/**
* Auto-detect a plugin and see if the file information is given, and if not, detect it.
*
* @access public
* @param string The ID of a plugin to load
* @param string A reference variable that will hold the class name of the plugin (do not pass manually)
* @param string A reference variable that will hold the path to the plugin (do not pass manually)
* @return string Returns the filename of a plugin to load
*/
/* Probes for the plugin filename */
function probePlugin($instance_id, &$class_name, &$pluginPath)
{
global $serendipity;
$filename = false;
$is_internal = false;
$class_name = serendipity_plugin_api::getClassByInstanceID($instance_id, $is_internal);
if (!$is_internal) {
/* plugin from the plugins/ dir */
// $serendipity['debug']['pluginload'][] = "Including plugin $class_name, $pluginPath";
$filename = serendipity_plugin_api::includePlugin($class_name, $pluginPath, $instance_id);
if (empty($filename) && !empty($instance_id)) {
// $serendipity['debug']['pluginload'][] = "No valid path/filename found.";
$sql = "SELECT path from {$serendipity['dbPrefix']}plugins WHERE name = '" . $instance_id . "'";
$plugdata = serendipity_db_query($sql, true, 'both', false, false, false, true);
if (is_array($plugdata) && isset($plugdata[0])) {
$pluginPath = $plugdata[0];
}
if (empty($pluginPath)) {
$pluginPath = $class_name;
}
// $serendipity['debug']['pluginload'][] = "Including plugin(2) $class_name, $pluginPath";
$filename = serendipity_plugin_api::includePlugin($class_name, $pluginPath);
}
if (empty($filename)) {
$serendipity['debug']['pluginload'][] = "No valid path/filename found. Aborting.";
$retval = false;
return $retval;
}
}
// $serendipity['debug']['pluginload'][] = "Found plugin file $filename";
return $filename;
}
/**
* Instantiates a plugin class
*
* @access public
* @param string The ID of the plugin to load
* @param int The owner of the plugin (can be autodetected)
* @param string The path to a plugin (can be autodetected)
* @param string The filename of a plugin (can be autodetected)
* @return
*/
function &load_plugin($instance_id, $authorid = null, $pluginPath = '', $pluginFile = null)
{
global $serendipity;
if ($pluginFile === null) {
$class_name = '';
// $serendipity['debug']['pluginload'][] = "Init probe for plugin $instance_id, $class_name, $pluginPath";
$pluginFile = serendipity_plugin_api::probePlugin($instance_id, $class_name, $pluginPath);
} else {
$is_internal = false;
// $serendipity['debug']['pluginload'][] = "getClassByInstanceID $instance_id, $is_internal";
$class_name = serendipity_plugin_api::getClassByInstanceID($instance_id, $is_internal);
}
if (!class_exists($class_name) && !empty($pluginFile)) {
// $serendipity['debug']['pluginload'][] = "Classname does not exist. Including $pluginFile.";
include($pluginFile);
}
if (!class_exists($class_name)) {
$serendipity['debug']['pluginload'][] = "Classname $class_name still does not exist. Aborting.";
return false;
}
// $serendipity['debug']['pluginload'][] = "Returning new $class_name($instance_id)";
$p =& new $class_name($instance_id);
if (!is_null($authorid)) {
$p->serendipity_owner = $authorid;
} else {
$sql = "SELECT authorid from {$serendipity['dbPrefix']}plugins WHERE name = '" . $instance_id . "'";
$owner = serendipity_db_query($sql, true);
if (is_array($owner) && isset($owner[0])) {
$p->serendipity_owner = $owner[0];
}
}
$p->pluginPath = $pluginPath;
$p->pluginFile = $pluginFile;
return $p;
}
/**
* Gets cached properties/information about a specific plugin, auto-loads a cache of all plugins
*
* @access public
* @param string The filename of the plugin to get information about
* @param array A referenced array that holds information about the plugin instance (self::load_plugin() response)
* @param type The type of the plugin (local|spartacus|...)
* @return array Information about the plugin
*/
function &getPluginInfo(&$pluginFile, &$class_data, $type)
{
global $serendipity;
static $pluginlist = null;
if ($pluginlist === null) {
$data = serendipity_db_query("SELECT p.*,
pc.category
FROM {$serendipity['dbPrefix']}pluginlist AS p
LEFT OUTER JOIN {$serendipity['dbPrefix']}plugincategories AS pc
ON pc.class_name = p.class_name
WHERE p.pluginlocation = 'local' AND
p.plugintype = '" . serendipity_db_escape_string($type) . "'");
if (is_array($data)) {
foreach($data AS $p) {
if (isset($p['pluginpath'])) {
$p['pluginPath'] = $p['pluginpath'];
}
if (!isset($pluginlist[$p['plugin_file']])) {
$pluginlist[$p['plugin_file']] = $p;
}
$pluginlist[$p['plugin_file']]['groups'][] = $p['category'];
}
}
}
if (is_array($pluginlist[$pluginFile]) && !preg_match('@plugin_internal\.inc\.php@', $pluginFile)) {
$data = $pluginlist[$pluginFile];
if ((int) filemtime($pluginFile) == (int) $data['last_modified']) {
$data['stackable'] = serendipity_db_bool($data['stackable']);
$plugin = $data;
return $plugin;
}
}
$plugin =& serendipity_plugin_api::load_plugin($class_data['name'], null, $class_data['pluginPath'], $pluginFile);
return $plugin;
}
/**
* Set cache information about a plugin
*
* @access public
* @param mixed Either an plugin object or a plugin information array that holds the information about the plugin
* @param string The filename of the plugin
* @param object The property bag object bundled with the plugin
* @param array Previous/additional information about the plugin
* @param string The location/type of a plugin (local|spartacus)
* @return
*/
function &setPluginInfo(&$plugin, &$pluginFile, &$bag, &$class_data, $pluginlocation = 'local')
{
global $serendipity;
static $dbfields = array(
'plugin_file',
'class_name',
'plugin_class',
'pluginPath',
'name',
'description',
'version',
'upgrade_version',
'plugintype',
'pluginlocation',
'stackable',
'author',
'requirements',
'website',
'last_modified'
);
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}pluginlist WHERE plugin_file = '" . serendipity_db_escape_string($pluginFile) . "' AND pluginlocation = '" . serendipity_db_escape_string($pluginlocation) . "'");
if (!empty($pluginFile) && file_exists($pluginFile)) {
$lastModified = filemtime($pluginFile);
} else {
$lastModified = 0;
}
if (is_object($plugin)) {
$data = array(
'class_name' => get_class($plugin),
'stackable' => $bag->get('stackable'),
'name' => $bag->get('name'),
'description' => $bag->get('description'),
'author' => $bag->get('author'),
'version' => $bag->get('version'),
'upgrade_version' => isset($class_data['upgrade_version']) ? $class_data['upgrade_version'] : $bag->get('version'),
'requirements' => serialize($bag->get('requirements')),
'website' => $bag->get('website'),
'plugin_class' => $class_data['name'],
'pluginPath' => $class_data['pluginPath'],
'plugin_file' => $pluginFile,
'pluginlocation' => $pluginlocation,
'plugintype' => $serendipity['GET']['type'],
'last_modified' => $lastModified
);
$groups = $bag->get('groups');
} elseif (is_array($plugin)) {
$data = $plugin;
$groups = $data['groups'];
unset($data['installable']);
unset($data['true_name']);
unset($data['customURI']);
unset($data['groups']);
if (isset($data['pluginpath'])) {
$data['pluginPath'] = $data['pluginpath'];
}
$data['requirements'] = serialize($data['requirements']);
}
if (!isset($data['stackable']) || empty($data['stackable'])) {
$data['stackable'] = '0';
}
if (!isset($data['last_modified'])) {
$data['last_modified'] = $lastModified;
}
// Only insert data keys that exist in the DB.
$insertdata = array();
foreach($dbfields AS $field) {
$insertdata[$field] = $data[$field];
}
if ($data['upgradable']) {
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}pluginlist
SET upgrade_version = '" . serendipity_db_escape_string($data['upgrade_version']) . "'
WHERE plugin_class = '" . serendipity_db_escape_string($data['plugin_class']) . "'");
}
serendipity_db_insert('pluginlist', $insertdata);
serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}plugincategories WHERE class_name = '" . serendipity_db_escape_string($data['class_name']) . "'");
foreach((array)$groups AS $group) {
if (empty($group)) {
continue;
}
$cat = array(
'class_name' => $data['class_name'],
'category' => $group
);
serendipity_db_insert('plugincategories', $cat);
}
$data['groups'] = $groups;
return $data;
}
/**
* Moves a sidebar plugin to a different side or up/down
*
* @access public
* @param string The instance ID of a plugin
* @param string The new placement of a plugin (left|right|hide|event|eventh)
* @param string A new sort order for the plugin
* @return
*/
function update_plugin_placement($name, $placement, $order = null)
{
global $serendipity;
$admin = '';
if (!serendipity_checkPermission('adminPlugins') && $placement == 'hide') {
// Only administrators can set plugins to 'hide' if they are not the owners.
$admin = " AND (authorid = 0 OR authorid = {$serendipity['authorid']})";
}
$sql = "UPDATE {$serendipity['dbPrefix']}plugins set placement='$placement' ";
if ($order !== null) {
$sql .= ", sort_order=$order ";
}
$sql .= "WHERE name='$name' $admin";
return serendipity_db_query($sql);
}
/**
* Updates the ownership information about a plugin
*
* @access public
* @param string The instance ID of the plugin
* @param int The ID of the new author owner of the plugin
* @return
*/
function update_plugin_owner($name, $authorid)
{
global $serendipity;
if (empty($authorid) && $authorid != '0') {
return;
}
$admin = '';
if (!serendipity_checkPermission('adminPlugins')) {
$admin = " AND (authorid = 0 OR authorid = {$serendipity['authorid']})";
}
$sql = "UPDATE {$serendipity['dbPrefix']}plugins SET authorid='$authorid' WHERE name='$name' $admin";
return serendipity_db_query($sql);
}
/**
* Get a list of Sidebar plugins and pass them to Smarty
*
* @access public
* @param string The side of plugins to show (left/right/hide/event/eventh)
* @param string deprecated: Indicated which wrapping HTML element to use for plugins
* @param boolean Indicates whether only all plugins should be shown that are not in the $side list
* @param string Only show plugins of this plugin class
* @param string Only show a plugin with this instance ID
* @return string Smarty HTML output
*/
function generate_plugins($side, $tag = '', $negate = false, $class = null, $id = null, $tpl = 'sidebar.tpl')
{
global $serendipity;
/* $tag parameter is deprecated and used in Smarty templates instead. Only use it in function
* header for layout.php BC.
*/
$plugins = serendipity_plugin_api::enum_plugins($side, $negate, $class, $id);
if (!is_array($plugins)) {
return;
}
if (!isset($serendipity['smarty'])) {
$serendipity['smarty_raw_mode'] = true;
serendipity_smarty_init();
}
$pluginData = array();
$addData = func_get_args();
serendipity_plugin_api::hook_event('frontend_generate_plugins', $plugins, $addData);
if (count($plugins) == 0) {
$serendipity['prevent_sidebar_plugins_' . $side] = true;
}
$loggedin = false;
if (serendipity_userLoggedIn() && serendipity_checkPermission('adminPlugins')) {
$loggedin = true;
}
foreach ($plugins AS $plugin_data) {
$plugin =& serendipity_plugin_api::load_plugin($plugin_data['name'], $plugin_data['authorid'], $plugin_data['path']);
if (is_object($plugin)) {
$class = get_class($plugin);
$title = '';
/* TODO: make generate_content NOT echo its output */
ob_start();
$show_plugin = $plugin->generate_content($title);
$content = ob_get_contents();
ob_end_clean();
if ($loggedin) {
$content .= '