LuckyCoinkydink/include/plugin_api.inc.php

1874 lines
69 KiB
PHP

<?php
# Copyright (c) 2003-2005, Jannis Hermanns (on behalf the Serendipity Developer Team)
# All rights reserved. See LICENSE file for licensing details
if (IN_serendipity !== true) {
die ('Don\'t hack!');
}
// List of bundled core plugins
define('BUNDLED_PLUGINS',
array(
'serendipity_event_bbcode',
'serendipity_event_creativecommons',
'serendipity_event_emoticate',
'serendipity_event_entryproperties',
'serendipity_event_gravatar',
'serendipity_event_mailer',
'serendipity_event_nl2br',
'serendipity_event_responsiveimages',
'serendipity_event_s9ymarkup',
'serendipity_event_spamblock',
'serendipity_event_spartacus',
'serendipity_event_templatechooser',
'serendipity_event_textile',
'serendipity_event_xhtmlcleanup',
'serendipity_plugin_archives',
'serendipity_plugin_authors',
'serendipity_plugin_calendar',
'serendipity_plugin_categories',
'serendipity_plugin_comments',
'serendipity_plugin_creativecommons',
'serendipity_plugin_entrylinks',
'serendipity_plugin_eventwrapper',
'serendipity_plugin_history',
'serendipity_plugin_html_nugget',
'serendipity_plugin_plug',
'serendipity_plugin_quicksearch',
'serendipity_plugin_recententries',
'serendipity_plugin_remoterss',
'serendipity_plugin_superuser',
'serendipity_plugin_syndication',
'serendipity_plugin_templatedropdown'
)
);
include_once S9Y_INCLUDE_PATH . 'include/functions.inc.php';
/* Core API function mappings
* This allows the s9y Core to also execute internal core actions on plugin API hooks
* Future use: Global variable can be customized/overriden by your own plugin on the frontend_configure event
* or during runtime. The capabilities are theme or plugin based only.
*/
$serendipity['capabilities']['jquery'] = true;
$serendipity['capabilities']['jquery_backend'] = true;
$serendipity['capabilities']['jquery-noconflict'] = true; //set as being deprecated, while we should not need it anymore
$serendipity['core_events']['frontend_header']['jquery'] = 'serendipity_plugin_api_frontend_header';
$serendipity['core_events']['backend_header']['jquery'] = 'serendipity_plugin_api_backend_header';
// Add jquery to all frontend templates (in noConflict mode)
function serendipity_plugin_api_frontend_header($event_name, &$bag, &$eventData, $addData) {
global $serendipity;
// Only execute if current template (only) does not have its own jquery.js file
// jquery can be disabled if a template's config.inc.php or a plugin sets
// $serendipity['capabilities']['jquery'] = false
$check = file_exists($serendipity['serendipityPath'] . $serendipity['templatePath'] . $serendipity['template'] . '/jquery.js');
if (!$check && $serendipity['capabilities']['jquery']) {
?>
<script src="<?php echo $serendipity['serendipityHTTPPath']; ?>templates/jquery.js"></script>
<?php
if ($serendipity['capabilities']['jquery-noconflict']) {
?>
<script>jQuery.noConflict();</script>
<?php
}
}
}
// Add jquery to all backend templates
function serendipity_plugin_api_backend_header($event_name, &$bag, &$eventData, $addData) {
global $serendipity;
// Only execute if current template does not have its own backend_jquery.js file
// jquery can be disabled if a template's config.inc.php or a plugin sets
// $serendipity['capabilities']['jquery'] = false
$check = serendipity_getTemplateFile('jquery_backend.js', 'serendipityPath', true);
if (!$check && $serendipity['capabilities']['jquery_backend']) {
?>
<script src="<?php echo $serendipity['serendipityHTTPPath']; ?>templates/jquery.js"></script>
<?php
}
}
// Add backend core (pre) hooks
function serendipity_plugin_api_core_event_hook($event, &$bag, &$eventData, &$addData) {
global $serendipity;
switch($event) {
case 'js_backend':
case 'js':
// Add a global available (index.tpl; admin/index.tpl; preview_iframe.tpl) redirect error string function used by errorToExceptionHandler()
// hardened by admin only - better have that here, to be reachable everywhere
if( $serendipity['production'] === true && $serendipity['serendipityUserlevel'] >= USERLEVEL_ADMIN ) {
echo "
function errorHandlerCreateDOM(htmlStr) {
var frag = document.createDocumentFragment(),
temp = document.createElement('div');
temp.innerHTML = htmlStr;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
return frag;
} \n";
}
break;
case 'external_plugin':
if ($eventData == 'admin/serendipity_editor.js') {
if (! headers_sent()) {
header('Content-Type: application/javascript');
}
echo serendipity_smarty_show('admin/serendipity_editor.js.tpl', null, 'JS', 'include/plugin_api.inc.php:external_plugin');
}
break;
case 'backend_save':
case 'backend_publish':
// this is preview_iframe.tpl updertHooks [ NOT ONLY!! See freetags ]
if (($serendipity['GET']['is_iframe'] ?? false) == 'true' && ($serendipity['GET']['iframe_mode'] ?? '') == 'save') {
echo "\n".'<script>document.addEventListener("DOMContentLoaded", function() { window.parent.serendipity.eraseEntryEditorCache(); });</script>'."\n";
}
break;
}
}
/* This file defines the plugin API for serendipity.
* By extending these classes, you can add your own code
* to appear in the sidebar(s) of serendipity.
*
*
* The system defines a number of built-in plugins; these are
* identified by @class_name.
*
* Third-party plugins are identified by the name of the folder into
* which they were uploaded (so there is no @ sign at the start of
* their class name.
*
* The user creates instances of plugins; an instance is assigned
* an identifier like this:
* classname:uniqid()
*
* The user can configure instances of plugins.
*/
class serendipity_plugin_api
{
/**
* Register the default list of plugins for installation.
*
* @access public
* @return null
*/
static function register_default_plugins()
{
/* Register default sidebar plugins, order matters */
serendipity_plugin_api::create_plugin_instance('@serendipity_plugin_archives');
serendipity_plugin_api::create_plugin_instance('@serendipity_plugin_categories');
serendipity_plugin_api::create_plugin_instance('@serendipity_plugin_syndication');
serendipity_plugin_api::create_plugin_instance('@serendipity_plugin_superuser');
serendipity_plugin_api::create_plugin_instance('@serendipity_plugin_plug');
/* Register default event plugins */
serendipity_plugin_api::create_plugin_instance('serendipity_event_s9ymarkup', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_emoticate', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_nl2br', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_spamblock', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_spartacus', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_entryproperties', null, 'event');
serendipity_plugin_api::create_plugin_instance('serendipity_event_responsiveimages', null, 'event');
/* Register additional plugins? */
if (file_exists(S9Y_INCLUDE_PATH . 'plugins/preload.txt')) {
// Expects this format, one plugin per line:
// serendipity_event_xxx:event
// serendipity_plugin_xxx:left
$plugins = file(S9Y_INCLUDE_PATH . 'plugins/preload.txt');
foreach($plugins AS $plugin) {
$plugin = trim($plugin);
if (empty($plugin)) {
continue;
}
$plugin_info = explode(':', $plugin);
serendipity_plugin_api::create_plugin_instance($plugin_info[0], null, $plugin_info[1]);
}
}
}
/**
* Create an instance of a plugin.
*
* $plugin_class_id is of the form:
* @class_name for a built-in plugin
* or
* plugin_dir_name for a third-party plugin
* returns the instance identifier for the newly created plugin.
*
* TO BE IMPLEMENTED:
* If $copy_from_instance is not null, and identifies another plugin
* of the same class, then the persistent state will be copied.
* This allows the user to clone a plugin.
*
* @access public
* @param string classname of the plugin to insert (see description above for details)
* @param boolean (reserved) variable to indicate a copy of an existing instance
* @param string The type of the plugin to insert (event/left/right/hide/eventh)
* @param int The authorid of the plugin owner
* @param string The source path of the plugin file
* @return string ID of the new plugin
*/
static function create_plugin_instance($plugin_class_id, $copy_from_instance = null, $default_placement = 'right', $authorid = '0', $pluginPath = '')
{
global $serendipity;
$id = bin2hex(random_bytes(16));
$key = $plugin_class_id . ':' . $id;
$key = serendipity_db_escape_string($key);
// Secure Plugin path. No leading slashes, no backslashes, no "up" directories
$pluginPath = preg_replace('@^(/)@', '', $pluginPath);
$pluginPath = str_replace(array('..', "\\"), array('', '/'), serendipity_db_escape_string($pluginPath));
if ($pluginPath == 'online_repository') {
$pluginPath = $key;
}
$rs = serendipity_db_query("SELECT MAX(sort_order) as sort_order_max FROM {$serendipity['dbPrefix']}plugins WHERE placement = '$default_placement'", true, 'num');
if (is_array($rs) && isset($rs[0]) && !empty($rs[0])) {
$nextidx = intval($rs[0] + 1);
} else {
$nextidx = 0;
}
$serendipity['debug']['pluginload'][] = "Installing plugin: " . print_r(func_get_args(), true);
$iq = "INSERT INTO {$serendipity['dbPrefix']}plugins (name, sort_order, placement, authorid, path) values ('" . serendipity_db_escape_string(serendipity_specialchars($key)) . "', $nextidx, '$default_placement', '$authorid', '" . serendipity_specialchars($pluginPath) . "')";
$serendipity['debug']['pluginload'][] = $iq;
serendipity_db_query($iq);
serendipity_plugin_api::hook_event('backend_plugins_new_instance', $key, array('default_placement' => $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 '<span class="msg_error">' . ERROR . ': ' . serendipity_specialchars($key) . ' (' . serendipity_specialchars($pluginPath) . ')</span>';
}
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
*/
static function remove_plugin_instance($plugin_instance_id)
{
global $serendipity;
$plugin_instance_id = serendipity_db_escape_string($plugin_instance_id);
$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/%'");
return;
}
/**
* 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
*/
static 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
*/
static 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
*/
static 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
*/
static 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 and 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
*/
static 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 and all plugins that are NOT part of the filter will be evaluated
* @return int Number of plugins that were found.
*/
static 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] ?? false) == 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
*/
static 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 (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 '@'). (Unused, keep for compat.)
* @return string The classname of the plugin
*/
static 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 */
static 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 = '" . serendipity_db_escape_string($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
*/
static 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 = '" . serendipity_db_escape_string($instance_id) . "'";
$owner = serendipity_db_query($sql, true);
if (is_array($owner) && isset($owner[0])) {
$p->serendipity_owner = $owner[0];
}
}
$p->pluginPath = $p->act_pluginPath = $pluginPath;
if (empty($p->act_pluginPath)) {
$p->act_pluginPath = $class_name;
}
$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 (sidebar|event)
* @return array Information about the plugin
*/
static 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] ?? null) && !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
*/
static 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] ?? null;
}
if ($data['upgradable'] ?? false) {
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
*/
static 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
*/
static 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
*/
static function generate_plugins($side, $negate = false, $class = null, $id = null, $tpl = 'sidebar.tpl')
{
global $serendipity;
$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 .= '<div class="serendipity_edit_nugget"><a href="' . $serendipity['serendipityHTTPPath'] . 'serendipity_admin.php?serendipity[adminModule]=plugins&amp;serendipity[plugin_to_conf]=' . serendipity_entities($plugin->instance) . '">' . EDIT . '</a></div>';
}
if ($show_plugin !== false) {
$pluginData[] = array('side' => $side,
'class' => $class,
'title' => $title,
'content' => $content,
'id' => $plugin->instance);
}
} else {
$pluginData[] = array('side' => $side,
'title' => ERROR,
'class' => $class,
'content' => sprintf(INCLUDE_ERROR, $plugin_data['name']));
}
}
serendipity_plugin_api::hook_event('frontend_sidebar_plugins', $pluginData, $addData);
$serendipity['smarty']->assignByRef('plugindata', $pluginData);
$serendipity['smarty']->assign('pluginside', ucfirst($side));
return serendipity_smarty_fetch('sidebar_'. $side, $tpl, true);
}
/**
* Gets the title of a plugin to be shown in plugin overview
*
* @access public
* @param object The plugin object
* @param string The default title, if none was configured
* @return string The title of the plugin
*/
static function get_plugin_title(&$plugin, $default_title = '')
{
global $serendipity;
// Generate plugin output. Make sure that by probing the plugin, no events are actually called. After that,
// restore setting of 'no_events'.
if (!is_null($plugin->title)) {
// Preferred way of fetching a plugins title
$title = &$plugin->title;
} else {
$ne = (isset($serendipity['no_events']) && $serendipity['no_events'] ? true : false);
$serendipity['no_events'] = true;
ob_start();
$plugin->generate_content($title);
ob_end_clean();
$serendipity['no_events'] = $ne;
}
if (strlen(trim($title)) == 0) {
if (!empty($default_title)) {
$title = $default_title;
} else {
$title = $plugin->instance;
}
}
return $title;
}
/**
* Check if a plugin is bundled with s9y core
*
* @access public
* @param string Name of a plugin
* @return boolean
*/
static function is_bundled_plugin($name)
{
return in_array ($name, BUNDLED_PLUGINS);
}
/**
* Check if a plugin is an event plugin
*
* Refactoring: decompose conditional
*
* @access public
* @param string Name of a plugin
* @return boolean
*/
static function is_event_plugin($name)
{
return (strstr($name, '_event_'));
}
/**
* Prepares a cache of all event plugins and load them in queue so that they can be fetched
*
* @access public
* @param mixed If set to a string, a certain event plugin cache object will be returned by this function
* @param boolean If set to true, the list of cached event plugins will be refreshed
* @return mixed Either returns the whole list of event plugins, or only a specific instance
*/
static function &get_event_plugins($getInstance = false, $refresh = false)
{
static $event_plugins;
static $false = false;
if (!$refresh && isset($event_plugins) && is_array($event_plugins)) {
if ($getInstance) {
if (isset($event_plugins[$getInstance]['p'])) {
return $event_plugins[$getInstance]['p'];
}
return $false;
}
return $event_plugins;
}
$plugins = serendipity_plugin_api::enum_plugins('event');
if (!is_array($plugins)) {
return $false;
}
$event_plugins = array();
foreach($plugins AS $plugin_data) {
if ($event_plugins[$plugin_data['name']]['p'] = &serendipity_plugin_api::load_plugin($plugin_data['name'], $plugin_data['authorid'], $plugin_data['path'])) {
/* query for its name, description and configuration data */
$event_plugins[$plugin_data['name']]['b'] = new serendipity_property_bag;
$event_plugins[$plugin_data['name']]['p']->introspect($event_plugins[$plugin_data['name']]['b']);
$event_plugins[$plugin_data['name']]['t'] = serendipity_plugin_api::get_plugin_title($event_plugins[$plugin_data['name']]['p']);
} else {
unset($event_plugins[$plugin_data['name']]); // Unset failed plugins
}
}
if ($getInstance) {
if (isset($event_plugins[$getInstance]['p'])) {
return $event_plugins[$getInstance]['p'];
}
return $false;
}
return $event_plugins;
}
/**
* Executes a specific Eventhook
*
* If you want to temporarily block any event plugins, you can set $serendipity['no_events'] before
* this method call.
*
* @access public
* @param string The name of the event to hook on to
* @param mixed May contain any type of variables that are passed by reference to an event plugin
* @param mixed May contain any type of variables that are passed to an event plugin
* @return true
*/
static function hook_event($event_name, &$eventData, $addData = null)
{
global $serendipity;
// Can be bypassed globally by setting $serendipity['no_events'] = TRUE;
if (isset($serendipity['no_events']) && $serendipity['no_events'] == true) {
return false;
}
if (($serendipity['enablePluginACL'] ?? false) && !serendipity_hasPluginPermissions($event_name)) {
return false;
}
// We can NOT use a "return by reference" here, because then when
// a plugin executes another event_hook, the referenced variable within
// that call will overwrite the previous original plugin listing and
// skip the execution of any follow-up plugins.
$plugins = serendipity_plugin_api::get_event_plugins();
if (array_key_exists($event_name, $serendipity['core_events']) && $serendipity['core_events'][$event_name]) {
foreach($serendipity['core_events'][$event_name] as $apifunc_key => $apifunc) {
$apifunc($event_name, $bag, $eventData, $addData);
}
}
// execute backend needed core hooks
serendipity_plugin_api_core_event_hook($event_name, $bag, $eventData, $addData);
if (function_exists('serendipity_plugin_api_pre_event_hook')) {
$apifunc = 'serendipity_plugin_api_pre_event_hook';
$apifunc($event_name, $bag, $eventData, $addData);
}
// Function names cannot contain ":" etc, so if we ever have event looks like "backend:js" this
// needs to be replaced to "backend_js". The real event name is passed as a function argument
// These specific per-hook functions are utilized for theme's config.inc.php files
// that act as an engine for other themes.
$safe_event_name = preg_replace('@[^a-z0-9_]+@i', '_', $event_name);
if (function_exists('serendipity_plugin_api_pre_event_hook_' . $safe_event_name)) {
$apifunc = 'serendipity_plugin_api_pre_event_hook_' . $safe_event_name;
$apifunc($event_name, $bag, $eventData, $addData);
}
if (is_array($plugins)) {
foreach($plugins as $plugin => $plugin_data) {
$bag = &$plugin_data['b'];
$phooks = &$bag->get('event_hooks');
if (isset($phooks[$event_name])) {
// Check for cachable events.
if (isset($eventData['is_cached']) && $eventData['is_cached']) {
$chooks = &$bag->get('cachable_events');
if (is_array($chooks) && isset($chooks[$event_name])) {
continue;
}
}
if (($serendipity['enablePluginACL'] ?? false) && !serendipity_hasPluginPermissions($plugin)) {
continue;
}
$plugins[$plugin]['p']->event_hook($event_name, $bag, $eventData, $addData);
}
}
if (function_exists('serendipity_plugin_api_event_hook')) {
$apifunc = 'serendipity_plugin_api_event_hook';
$apifunc($event_name, $bag, $eventData, $addData);
}
if (function_exists('serendipity_plugin_api_event_hook_' . $safe_event_name)) {
$apifunc = 'serendipity_plugin_api_event_hook_' . $safe_event_name;
$apifunc($event_name, $bag, $eventData, $addData);
}
}
return true;
}
/**
* Checks if a specific plugin instance is already installed
*
* @access public
* @param string A name (may contain wildcards) of a plugin class to check
* @return boolean True if a plugin was found
*/
static function exists($instance_id)
{
global $serendipity;
if (!strstr($instance_id, ':')) {
$instance_id .= ':';
}
$existing = serendipity_db_query("SELECT name FROM {$serendipity['dbPrefix']}plugins WHERE name LIKE '%" . serendipity_db_escape_string($instance_id) . "%'");
if (is_array($existing) && !empty($existing[0][0])) {
return $existing[0][0];
}
return false;
}
/**
* Install a new plugin by ensuring that it does not already exist
*
* @access public
* @param string The classname of the plugin
* @param int The new owner author
* @param boolean Indicates if the plugin is an event plugin
* @return object Returns the plugin object or false, if failure
*/
static function &autodetect_instance($plugin_name, $authorid, $is_event_plugin = false)
{
if ($is_event_plugin) {
$side = 'event';
} else {
$side = 'right';
}
$classes = serendipity_plugin_api::enum_plugin_classes(null);
if (isset($classes[$plugin_name])) {
$instance = serendipity_plugin_api::create_plugin_instance($plugin_name, null, $side, $authorid, $classes[$plugin_name]['pluginPath']);
} else {
$instance = false;
}
return $instance;
}
/**
* Find the plugin instances for a given classname
*
* @access public
* @param string The classname of the plugin
* @param int The owner author
* @param boolean ignore hidden (deactivated) plugins
* @return object Array with ids of the installed plugin
*/
static function find_plugin_id($plugin_name,$authorid = 0, $ignore_hidden = true) {
global $serendipity;
$sql = "SELECT * from {$serendipity['dbPrefix']}plugins ";
$where = array();
$where[] = "(name LIKE '@" . serendipity_db_escape_string($plugin_name) . "%' OR name LIKE '" . serendipity_db_escape_string($plugin_name) . "%') ";
$where[] = "authorid='" . serendipity_db_escape_string($authorid) . "' ";
if ($ignore_hidden) $where[] = "NOT ( placement = 'hidden' OR placement = 'eventh') ";
if (count($where) > 0) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$rs = serendipity_db_query($sql,false,'assoc');
$ids = array();
if (!empty($rs) & is_Array($rs)) {
foreach($rs as $line) {
$ex = explode(':',$line['name']);
$ids[] = $ex[1];
}
return $ids;
} else {
return null;
}
}
/**
* Probe for a language include with constants. Still include defines later on, if some constants were missing
*
* @access public
* @param current plugin's path
* @return object Returns the plugin object or false, if failure
*/
static function load_language($path) {
global $serendipity;
// We deactivate error reporting here, because in PHP 8 setting constants twice always throws a warning.
// However, the language files of some plugins sometimes rely on constants being set only in the fallback
// language file, so we do have to set the other constants twice.
$oldReportingLevel = error_reporting();
error_reporting(0);
$probelang = $path . '/' . $serendipity['charset'] . 'lang_' . $serendipity['lang'] . '.inc.php';
if (file_exists($probelang)) {
include $probelang;
}
include $path . '/lang_en.inc.php';
error_reporting($oldReportingLevel);
}
}
/**
* holds a bunch of properties; since serendipity 0.8 only one value per key is
* allowed [was never really useful]
*/
class serendipity_property_bag
{
/**
* @access private
* @var array property storage container.
*/
var $properties = array();
/**
* @access private
* @var string Name of the property bag
*/
var $name = null;
/**
* Adds a property value to the bag
*
* @access public
* @param string The name of the property
* @param mixed The value of a property
* @return null
*/
function add($name, $value)
{
$this->properties[$name] = $value;
}
/**
* Returns a property value of a bag
*
* @access public
* @param string Name of property to fetch
* @return mixed The value of the property
*/
function &get($name)
{
return $this->properties[$name];
}
/**
* Check if a specific property name is already set
*
* @access public
* @param string Name of the property to check
* @return boolean True, if already set.
*/
function is_set($name)
{
return isset($this->properties[$name]);
}
}
/**
* A core plugin, with methods that both event and sidebar plugins share
*/
class serendipity_plugin
{
var $instance = null;
var $protected = false;
var $wrap_class = 'serendipitySideBarItem';
var $title_class = 'serendipitySideBarTitle';
var $content_class = 'serendipitySideBarContent';
var $title = null;
var $pluginPath = null;
var $act_pluginPath = null;
var $pluginFile = null;
var $serendipity_owner = null;
/**
* The constructor of a plugin
*
* Needs to be implemented by your own class.
* Be sure to call this method from your derived classes constructors,
* otherwise your config data will not be stored or retrieved correctly
*
* @access public
* @return true
*/
function __construct($instance)
{
$this->instance = $instance;
}
/**
* Perform configuration routines
*
* Called by Serendipity when the plugin is being configured.
* Can be used to query the database for configuration values that
* only need to be available for the global configuration and not
* on each page request.
*
* @access public
* @return true
*/
function performConfig(&$bag)
{
return true;
}
/**
* Perform install routines
*
* Called by Serendipity when the plugin is first installed.
* Can be used to install database tables etc.
*
* @access public
* @return true
*/
function install()
{
return true;
}
/**
* Perform uninstall routines
*
* Called by Serendipity when the plugin is removed/uninstalled.
* Can be used to drop installed database tables etc.
*
* @access public
* @param object A property bag object
* @return true
*/
function uninstall(&$propbag)
{
return true;
}
/**
* The introspection function of a plugin, to setup properties
*
* Called by serendipity when it wants to display information
* about your plugin.
* You need to override this method in your child class.
*
* @access public
* @param object A property bag object you can manipulate
* @return true
*/
function introspect(&$propbag)
{
$propbag->add('copyright', 'MIT License');
$propbag->add('name' , get_class($this));
// $propbag->add(
// 'configuration',
// array(
// 'text field',
// 'foo bar'
// )
// );
$this->protected = false; // If set to TRUE, only allows the owner of the plugin to modify its configuration
return true;
}
/**
* Introspection of a plugin configuration item
*
* Called by serendipity when it wants to display the configuration
* editor for your plugin.
* $name is the name of a configuration item you added in
* your instrospect method.
* You need to fill the property bag with appropriate items
* that describe the type and value(s) for that particular
* configuration option.
* You need to override this method in your child class if
* you have configuration options.
*
* @access public
* @param string Name of the config item
* @param object A property bag object you can store the configuration in
* @return
*/
function introspect_config_item($name, &$propbag)
{
return false;
}
/**
* Validate plugin configuration options.
*
* Called from Plugin Configuration manager. Can be extended by your own plugin, if you need.
*
* @access public
* @param string Name of the config item to validate
* @param object Property bag of the config item
* @param value The value of a config item
* @return
*/
function validate($config_item, &$cbag, &$value)
{
static $pattern_mail = '([\.\-\+~@_0-9a-z]+?)';
static $pattern_url = '([@!=~\?:&;0-9a-z#\.\-_\/]+?)';
$validate = $cbag->get('validate');
$valid = true;
if (!empty($validate)) {
switch ($validate) {
case 'string':
if (!preg_match('@^\w*$@i', $value)) {
$valid = false;
}
break;
case 'words':
if (!preg_match('@^[\w\s\r\n,\.\-!\?:;&_/=%\$]*$@i', $value)) {
$valid = false;
}
break;
case 'number':
if (!preg_match('@^[\d]*$@', $value)) {
$valid = false;
}
break;
case 'url':
if (!preg_match('§^' . $pattern_url . '$§', $value)) {
$valid = false;
}
break;
case 'mail':
if (!preg_match('§^' . $pattern_mail . '$§', $value)) {
$valid = false;
}
break;
case 'path':
if (!preg_match('@^[\w/_.\-~]$@', $value)) {
$valid = false;
}
break;
default:
if (!preg_match($validate, $value)) {
$valid = false;
}
break;
}
$error = $cbag->get('validate_error');
if ($valid) {
return true;
} elseif (!empty($error)) {
return $error;
} else {
return sprintf(PLUGIN_API_VALIDATE_ERROR, $config_item, $validate);
}
}
return true;
}
/**
* Output plugin's contents (Sidebar plugins)
*
* Called by serendipity when it wants your plugin to display itself.
* You need to set $title to be whatever text you want want to
* appear in the item caption space.
* Simply echo/print your content to the output; serendipity will
* capture it and make things work.
* You need to override this method in your child class.
*
* @access public
* @param string The referenced variable that holds the sidebar title of your plugin.
* @return null
*/
function generate_content(&$title)
{
$title = 'Sample!';
echo 'This is a sample!';
}
/**
* Get a config value of the plugin
*
* @access public
* @param string Name of the config value to fetch
* @param mixed The default value of a configuration item, if not set
* @param boolean If true, the default value will only be set if the plugin config item was not set.
* @return mixed The value of the config item
*/
function get_config($name, $defaultvalue = null, $empty = true)
{
$_res = serendipity_get_config_var($this->instance . '/' . $name, $defaultvalue, $empty);
if (is_null($_res)) {
// A protected plugin by a specific owner may not have its values stored in $serendipity
// because of the special authorid. To display such contents, we need to fetch it
// separately from the DB.
$_res = serendipity_get_user_config_var($this->instance . '/' . $name, null, $defaultvalue);
}
if (is_null($_res)) {
$cbag = new serendipity_property_bag();
$this->introspect_config_item($name, $cbag);
$_res = $cbag->get('default');
unset($cbag);
// Set the fetched value, so the default will not be fetched the next config call time
$this->set_config($name, $_res);
}
return $_res;
}
/**
* Sets a configuration value for a plugin
*
* @access public
* @param string Name of the plugin configuration item
* @param string Value of the plugin configuration item
* @param string A concatenation key for imploding arrays
* @return
*/
function set_config($name, $value, $implodekey = '^')
{
$name = $this->instance . '/' . $name;
if (is_array($value)) {
$dbvalue = implode($implodekey, $value);
$_POST['serendipity']['plugin'][$name] = $dbvalue;
} else {
$dbvalue = $value;
}
return serendipity_set_config_var($name, $dbvalue);
}
/**
* Garbage Collection
*
* Called by serendipity after insertion of a config item. If you want to kick out certain
* elements based on contents, create the corresponding function here.
*
* @access public
* @return true
*/
function cleanup()
{
// Cleanup. Remove all empty configs on SAVECONF-Submit.
// serendipity_plugin_api::remove_plugin_value($this->instance, array('configname1', 'configname2'));
return true;
}
/**
* Auto-Register dependencies of a plugin
*
* This method evaluates the "dependencies" member variable to check which plugins need to be installed.
*
* @access public
* @param boolean If true, a depending plugin will be removed when this plugin is uninstalled
* @param int The owner id of the current plugin
* @return true
*/
function register_dependencies($remove = false, $authorid = '0')
{
global $serendipity;
if (isset($this->dependencies) && is_array($this->dependencies)) {
if ($remove) {
$dependencies = @explode(';', $this->get_config('dependencies'));
$modes = @explode(';', $this->get_config('dependency_modes'));
if (!empty($dependencies) && is_array($dependencies)) {
foreach($dependencies AS $idx => $dependency) {
if ($modes[$idx] == 'remove' && serendipity_plugin_api::exists($dependency)) {
serendipity_plugin_api::remove_plugin_instance($dependency);
}
}
}
} else {
$keys = array();
$modes = array();
foreach($this->dependencies AS $dependency => $mode) {
$exists = serendipity_plugin_api::exists($dependency);
if (!$exists) {
if (serendipity_plugin_api::is_event_plugin($dependency)) {
$keys[] = serendipity_plugin_api::autodetect_instance($dependency, $authorid, true);
} else {
$keys[] = serendipity_plugin_api::autodetect_instance($dependency, $authorid, false);
}
} else {
$keys[] = $exists;
}
$modes[] = $mode;
}
$this->set_config('dependencies', implode(';', $keys));
$this->set_config('dependency_modes', implode(';', $modes));
}
}
return true;
}
/**
* Parses a smarty template file (which can be stored in either the plugin directory, the user template directory
* or the default template directory, and return the parsed output.
*
* @access public
* @param string template filename (no directory!)
* @return string Parsed Smarty return
*/
function &parseTemplate($filename)
{
global $serendipity;
$filename = basename($filename);
$tfile = serendipity_getTemplateFile($filename, 'serendipityPath', true);
if (!$tfile || $tfile == $filename) {
$tfile = dirname($this->pluginFile) . '/' . $filename;
}
return $serendipity['smarty']->fetch('file:'. $tfile);
}
/**
* Get full path for a filename. Will first look into themes and then in the plugins directory
* @param string relative path to file
* @param string The path selector that tells whether to return a HTTP or realpath
* @return string The full path+filename to the requested file
* */
function &getFile($filename, $key = 'serendipityPath')
{
global $serendipity;
$path = serendipity_getTemplateFile($filename, $key, true);
if (!$path) {
if (file_exists(dirname($this->pluginFile) . '/' . $filename)) {
return $serendipity[$key] . 'plugins/' . basename(dirname($this->pluginFile)) . '/' . $filename;
}
}
return $path;
}
}
/**
* Events can be called on several occasions when s9y performs an action.
* One or multiple plugin can be registered for each of those hooks.
*/
class serendipity_event extends serendipity_plugin
{
/**
* The class constructor
*
* Be sure to call this method from your derived classes constructors,
* otherwise your config data will not be stored or retrieved correctly
*
* @access public
* @param string The instance name
* @return
*/
function __construct($instance)
{
$this->instance = $instance;
}
/**
* Gets a reference to an $entry / $eventData array pointer, interacting with Cache-Options
*
* This function is used by specific event plugins that require to properly get a reference
* to the 'extended' or 'body' field of an entry superarray. If they would immediately operate
* on the 'body' field, it might get overwritten by other plugins later on.
*
* @access public
* @param string The fieldname to get a reference for
* @param array The entry superarray to get the reference from
* @return array The value of the array for the fieldname (reference)
*/
function &getFieldReference($fieldname = 'body', &$eventData = [])
{
// Get a reference to a content field (body/extended) of
// $entries input data. This is a unifying function because
// several plugins are using similar fields.
if (is_array($eventData) && isset($eventData[0]) && is_array($eventData[0]) && is_array($eventData[0]['properties'])) {
if (!empty($eventData[0]['properties']['ep_cache_' . $fieldname])) {
// It may happen that there is no extended entry to concatenate to. In that case,
// create a dummy extended entry.
if (!isset($eventData[0]['properties']['ep_cache_' . $fieldname])) {
$eventData[0]['properties']['ep_cache_' . $fieldname] = '';
}
$key = &$eventData[0]['properties']['ep_cache_' . $fieldname];
} else {
$key = &$eventData[0][$fieldname];
}
} elseif (is_array($eventData) && is_array($eventData['properties'] ?? null)) {
if (!empty($eventData['properties']['ep_cache_' . $fieldname])) {
$key = &$eventData['properties']['ep_cache_' . $fieldname];
} else {
$key = &$eventData[$fieldname];
}
} elseif (isset($eventData[0]) && isset($eventData[0][$fieldname])) {
$key = &$eventData[0][$fieldname];
} elseif (isset($eventData[$fieldname])) {
$key = &$eventData[$fieldname];
} else {
$key = '';
}
return $key;
}
/**
* Main logic for making a plugin "listen" to an event
*
* This method is called by the main plugin API for every event, that is executed.
* You need to implement each actions that shall be performed by your plugin here.
*
* @access public
* @param string The name of the executed event
* @param object A property bag for the current plugin
* @param mixed Any referenced event data from the serendipity_plugin_api::hook_event() function
* @param mixed Any additional data from the hook_event call
* @return true
*/
function event_hook($event, &$bag, &$eventData, $addData = null)
{
// Define event hooks here, if you want your plugin to execute those instead of being a sidebar item.
// Look at in/external plugins 'serendipity_event_mailer' or 'serendipity_event_weblogping' for usage.
// Currently available events:
// backend_publish [after insertion of a new article in your s9y-backend]
// backend_display [after displaying an article in your s9y-backend]
// frontend_display [before displaying an article in your s9y-frontend]
// frontend_comment [after displaying the "enter comment" dialog]
// ...and some more in the meanwhile...! :)
return true;
}
}
/* vim: set sts=4 ts=4 expandtab : */