3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2009 The Nucleus Group
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 * (see nucleus/documentation/index.html#license for more info)
13 * This is an (abstract) class of which all Nucleus Plugins must inherit
15 * for more information on plugins and how to write your own, see the
16 * plugins.html file that is included with the Nucleus documenation
18 * @license http://nucleuscms.org/license.txt GNU General Public License
19 * @copyright Copyright (C) 2002-2009 The Nucleus Group
20 * @version $Id: PLUGIN.php 1630 2012-01-28 12:16:14Z sakamocchi $
22 abstract class NucleusPlugin
24 // these final public functions _have_ to be redefined in your plugin
25 public function getName()
30 public function getAuthor()
35 public function getURL()
40 public function getVersion()
45 public function getDescription()
50 // these final public function _may_ be redefined in your plugin
52 public function getMinNucleusVersion()
57 public function getMinNucleusPatchLevel()
62 public function getEventList()
67 public function getTableList()
72 public function hasAdminArea()
77 public function install()
82 public function unInstall()
87 public function init()
92 public function doSkinVar($skinType)
97 public function doTemplateVar(&$item)
99 $args = func_get_args();
101 array_unshift($args, 'template');
102 call_user_func_array(array(&$this,'doSkinVar'),$args);
106 public function doTemplateCommentsVar(&$item, &$comment)
108 $args = func_get_args();
111 array_unshift($args, 'template');
112 call_user_func_array(array(&$this,'doSkinVar'),$args);
116 public function doAction($type)
118 return _ERROR_PLUGIN_NOSUCHACTION;
121 public function doIf($key,$value)
126 public function doItemVar (&$item)
132 * Checks if a plugin supports a certain feature.
134 * @returns 1 if the feature is reported, 0 if not
136 * Name of the feature. See plugin documentation for more info
137 * 'SqlTablePrefix' -> if the plugin uses the sql_table() method to get table names
138 * 'HelpPage' -> if the plugin provides a helppage
139 * 'SqlApi' -> if the plugin uses the complete sql_* api (must also require nucleuscms 3.5)
141 public function supportsFeature($feature)
147 * Report a list of plugin that is required to final public function
149 * @returns an array of names of plugin, an empty array indicates no dependency
151 public function getPluginDep()
156 // these helper final public functions should not be redefined in your plugin
159 * Creates a new option for this plugin
162 * A string uniquely identifying your option. (max. length is 20 characters)
164 * A description that will show up in the nucleus admin area (max. length: 255 characters)
166 * Either 'text', 'yesno' or 'password'
167 * This info is used when showing 'edit plugin options' screens
169 * Initial value for the option (max. value length is 128 characters)
171 final public function createOption($name, $desc, $type, $defValue = '', $typeExtras = '')
173 return $this->create_option('global', $name, $desc, $type, $defValue, $typeExtras);
176 final public function createBlogOption($name, $desc, $type, $defValue = '', $typeExtras = '')
178 return $this->create_option('blog', $name, $desc, $type, $defValue, $typeExtras);
181 final public function createMemberOption($name, $desc, $type, $defValue = '', $typeExtras = '')
183 return $this->create_option('member', $name, $desc, $type, $defValue, $typeExtras);
186 final public function createCategoryOption($name, $desc, $type, $defValue = '', $typeExtras = '')
188 return $this->create_option('category', $name, $desc, $type, $defValue, $typeExtras);
191 final public function createItemOption($name, $desc, $type, $defValue = '', $typeExtras = '')
193 return $this->create_option('item', $name, $desc, $type, $defValue, $typeExtras);
197 * Removes the option from the database
199 * Note: Options get erased automatically on plugin uninstall
201 final public function deleteOption($name)
203 return $this->delete_option('global', $name);
206 final public function deleteBlogOption($name)
208 return $this->delete_option('blog', $name);
211 final public function deleteMemberOption($name)
213 return $this->delete_option('member', $name);
216 final public function deleteCategoryOption($name)
218 return $this->delete_option('category', $name);
221 final public function deleteItemOption($name)
223 return $this->delete_option('item', $name);
227 * Sets the value of an option to something new
229 final public function setOption($name, $value)
231 return $this->set_option('global', 0, $name, $value);
234 final public function setBlogOption($blogid, $name, $value)
236 return $this->set_option('blog', $blogid, $name, $value);
239 final public function setMemberOption($memberid, $name, $value)
241 return $this->set_option('member', $memberid, $name, $value);
244 final public function setCategoryOption($catid, $name, $value)
246 return $this->set_option('category', $catid, $name, $value);
249 final public function setItemOption($itemid, $name, $value) {
250 return $this->set_option('item', $itemid, $name, $value);
254 * Retrieves the current value for an option
256 final public function getOption($name)
258 // only request the options the very first time. On subsequent requests
259 // the static collection is used to save SQL queries.
260 if ( $this->plugin_options == 0 )
262 $this->plugin_options = array();
264 $query = "SELECT d.oname as name, o.ovalue as value FROM %s o, %s d WHERE d.opid=%d AND d.oid=o.oid;";
265 $query = sprintf($query, sql_table('plugin_option'), sql_table('plugin_option_desc'), (integer) $this->getID());
266 $result = sql_query($query);
267 while ( $row = sql_fetch_object($result) )
269 $this->plugin_options[strtolower($row->name)] = $row->value;
272 if ( isset($this->plugin_options[strtolower($name)]) )
274 return $this->plugin_options[strtolower($name)];
278 return $this->get_option('global', 0, $name);
282 final public function getBlogOption($blogid, $name)
284 return $this->get_option('blog', $blogid, $name);
287 final public function getMemberOption($memberid, $name)
289 return $this->get_option('member', $memberid, $name);
292 final public function getCategoryOption($catid, $name)
294 return $this->get_option('category', $catid, $name);
297 final public function getItemOption($itemid, $name)
299 return $this->get_option('item', $itemid, $name);
303 * Retrieves an associative array with the option value for each
306 final public function getAllBlogOptions($name)
308 return $this->get_all_options('blog', $name);
311 final public function getAllMemberOptions($name)
313 return $this->get_all_options('member', $name);
316 final public function getAllCategoryOptions($name)
318 return $this->get_all_options('category', $name);
321 final public function getAllItemOptions($name)
323 return $this->get_all_options('item', $name);
327 * Retrieves an indexed array with the top (or bottom) of an option
328 * (delegates to getOptionTop())
330 final public function getBlogOptionTop($name, $amount = 10, $sort = 'desc')
332 return $this->get_option_top('blog', $name, $amount, $sort);
335 final public function getMemberOptionTop($name, $amount = 10, $sort = 'desc')
337 return $this->get_option_top('member', $name, $amount, $sort);
340 final public function getCategoryOptionTop($name, $amount = 10, $sort = 'desc')
342 return $this->get_option_top('category', $name, $amount, $sort);
345 final public function getItemOptionTop($name, $amount = 10, $sort = 'desc')
347 return $this->get_option_top('item', $name, $amount, $sort);
351 * Returns the plugin ID
355 final public function getID()
357 return $this->plugid;
361 * Returns the URL of the admin area for this plugin (in case there's
362 * no such area, the returned information is invalid)
366 final public function getAdminURL()
369 return $CONF['PluginURL'] . $this->getShortName() . '/';
373 * Returns the directory where the admin directory is located and
374 * where the plugin can maintain his extra files
378 final public function getDirectory()
381 return $DIR_PLUGINS . $this->getShortName() . '/';
385 * Derives the short name for the plugin from the classname (all
390 final public function getShortName()
392 return str_replace('np_','',strtolower(get_class($this)));
396 * Clears the option value cache which saves the option values during
397 * the plugin execution. This function is usefull if the options has
398 * changed during the plugin execution (especially in association with
399 * the PrePluginOptionsUpdate and the PostPluginOptionsUpdate events)
403 final public function clearOptionValueCache()
405 $this->option_values = array();
406 $this->plugin_options = 0;
410 // internal functions of the class starts here
412 protected $option_values; // oid_contextid => value
413 protected $option_info; // context_name => array('oid' => ..., 'default' => ...)
414 protected $plugin_options; // see getOption()
415 /* TODO: This should be protected member */
416 public $plugid; // plugin id
419 * Class constructor: Initializes some internal data
421 public function __construct()
423 $this->option_values = array(); // oid_contextid => value
424 $this->option_info = array(); // context_name => array('oid' => ..., 'default' => ...)
425 $this->plugin_options = 0;
429 * Retrieves an array of the top (or bottom) of an option from a plugin.
431 * @param string $context the context for the option: item, blog, member,...
432 * @param string $name the name of the option
433 * @param int $amount how many rows must be returned
434 * @param string $sort desc or asc
435 * @return array array with both values and contextid's
438 final protected function get_option_top($context, $name, $amount = 10, $sort = 'desc')
440 if ( ($sort != 'desc') && ($sort != 'asc') )
445 $oid = $this->get_option_id($context, $name);
447 // retrieve the data and return
448 $query = "SELECT otype, oextra FROM %s WHERE oid = %d;";
449 $query = sprintf($query, sql_table('plugin_option_desc'), $oid);
450 $result = sql_query($query);
452 $o = sql_fetch_array($result);
454 if ( ($this->optionCanBeNumeric($o['otype'])) && ($o['oextra'] == 'number' ) )
456 $orderby = 'CAST(ovalue AS SIGNED)';
462 $query = "SELECT ovalue value, ocontextid id FROM %s WHERE oid = %d ORDER BY %s %s LIMIT 0,%d;";
463 $query = sprintf($query, sql_table('plugin_option'), $oid, $orderby, $sort, (integer) $amount);
464 $result = sql_query($query);
469 while( $row = sql_fetch_array($result) )
474 // return the array (duh!)
479 * Creates an option in the database table plugin_option_desc
483 final protected function create_option($context, $name, $desc, $type, $defValue, $typeExtras = '')
485 // create in plugin_option_desc
486 $query = 'INSERT INTO ' . sql_table('plugin_option_desc')
487 .' (opid, oname, ocontext, odesc, otype, odef, oextra)'
488 .' VALUES ('.intval($this->plugid)
489 .', \''.sql_real_escape_string($name).'\''
490 .', \''.sql_real_escape_string($context).'\''
491 .', \''.sql_real_escape_string($desc).'\''
492 .', \''.sql_real_escape_string($type).'\''
493 .', \''.sql_real_escape_string($defValue).'\''
494 .', \''.sql_real_escape_string($typeExtras).'\');';
496 $oid = sql_insert_id();
498 $key = $context . '_' . $name;
499 $this->option_info[$key] = array('oid' => $oid, 'default' => $defValue);
504 * Deletes an option from the database tables
505 * plugin_option and plugin_option_desc
509 final protected function delete_option($context, $name)
511 $oid = $this->get_option_id($context, $name);
514 return 0; // no such option
517 // delete all things from plugin_option
518 $query = "DELETE FROM %s WHERE oid=%d;";
519 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid);
522 // delete entry from plugin_option_desc
523 $query = "DELETE FROM %s WHERE oid=%d;";
524 $query = sprintf($query, sql_table('plugin_option_desc'), $oid);
528 unset($this->option_info["{$context}_{$name}"]);
529 $this->option_values = array();
534 * Update an option in the database table plugin_option
536 * returns: 1 on success, 0 on failure
539 final protected function set_option($context, $contextid, $name, $value)
543 $oid = $this->get_option_id($context, $name);
549 // check if context id exists
553 if ( !MEMBER::existsID($contextid) )
559 if ( !$manager->existsBlogID($contextid) )
565 if ( !$manager->existsCategory($contextid) )
571 if ( !$manager->existsItem($contextid, true, true) )
577 if ( $contextid != 0 )
584 // update plugin_option
585 $query = "DELETE FROM %s WHERE oid=%d and ocontextid=%d;";
586 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid, (integer) $contextid);
589 $query = "INSERT INTO %s (ovalue, oid, ocontextid) VALUES ('%s', %d, %d);";
590 $query = sprintf($query, sql_table('plugin_option'), sql_real_escape_string($value), $oid, $contextid);
594 $this->option_values["{$oid}_{$contextid}"] = $value;
595 if ( $context == 'global' )
597 $this->plugin_options[strtolower($name)] = $value;
604 * Get an option from Cache or database
605 * - if not in the option Cache read it from the database
606 * - if not in the database write default values into the database
610 final protected function get_option($context, $contextid, $name)
612 $oid = $this->get_option_id($context, $name);
618 $key = "{$oid}_{$contextid}";
620 if ( isset($this->option_values[$key]) )
622 return $this->option_values[$key];
626 $query = "SELECT ovalue FROM %s WHERE oid=%d and ocontextid=%d;";
627 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid, (integer) $contextid);
628 $result = sql_query($query);
630 if ( !$result || (sql_num_rows($result) == 0) )
632 // fill DB with default value
633 $this->option_values[$key] = $this->get_default_value($context, $name);
634 $query = "INSERT INTO %s (oid, ocontextid, ovalue) VALUES (%d, %d, '%s');";
635 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid, (integer) $contextid, sql_real_escape_string($defVal));
640 $o = sql_fetch_object($result);
641 $this->option_values[$key] = $o->ovalue;
644 return $this->option_values[$key];
648 * Returns assoc array with all values for a given option
649 * (one option per possible context id)
653 final protected function get_all_options($context, $name)
655 $oid = $this->get_option_id($context, $name);
660 $default_value = $this->get_default_value($context, $name);
663 $query = "SELECT %s as contextid FROM %s;";
667 $query = sprintf($query, 'bnumber', sql_table('blog'));
670 $query = sprintf($query, 'catid', sql_table('category'));
673 $query = sprintf($query, 'mnumber', sql_table('member'));
676 $query = sprintf($query, 'inumber', sql_table('item'));
680 $result = sql_query($query);
683 while ( $o = sql_fetch_object($r) )
685 $options[$o->contextid] = $default_value;
689 $query = "SELECT ocontextid, ovalue FROM %s WHERE oid=%d;";
690 $query = sprintf($query, sql_table('plugin_option'), $oid);
691 $result = sql_query($query);
692 while ( $o = sql_fetch_object($result) )
694 $options[$o->ocontextid] = $o->ovalue;
701 * NucleusPlugin::get_option_id
703 * Gets the 'option identifier' that corresponds to a given option name.
704 * When this method is called for the first time, all the OIDs for the plugin
705 * are loaded into memory, to avoid re-doing the same query all over.
707 * @param string $context option context
708 * @param string $name plugin name
709 * @return integer option id
711 final protected function get_option_id($context, $name)
713 $key = "{$context}_{$name}";
715 if ( array_key_exists($key, $this->option_info)
716 && array_key_exists('oid', $this->option_info[$key]) )
718 return $this->option_info[$key]['oid'];
721 // load all OIDs for this plugin from the database
722 $this->option_info = array();
723 $query = "SELECT oid, oname, ocontext, odef FROM %s WHERE opid=%d;";
724 $query = sprintf($query, sql_table('plugin_option_desc'), $this->plugid);
725 $result = sql_query($query);
726 while ( $o = sql_fetch_object($result) )
728 $k = $o->ocontext . '_' . $o->oname;
729 $this->option_info[$k] = array('oid' => $o->oid, 'default' => $o->odef);
731 sql_free_result($result);
733 return $this->option_info[$key]['oid'];
735 final protected function get_default_value($context, $name)
737 $key = $context . '_' . $name;
739 if ( array_key_exists($key, $this->option_info)
740 && array_key_exists('default', $this->option_info[$key]) )
742 return $this->option_info[$key]['default'];
748 * Deletes all option values for a given context and contextid
749 * (used when e.g. a blog, member or category is deleted)
753 final protected function delete_option_values($context, $contextid)
755 // delete all associated plugin options
758 $query = "SELECT oid FROM %s WHERE ocontext='%s';";
759 $query = sprintf($query, sql_table('plugin_option_desc'), sql_real_escape_string($context));
761 $result = sql_query($query);
762 while ( $o = sql_fetch_object($result) )
764 array_push($aOIDs, $o->oid);
766 sql_free_result($result);
767 // delete those options. go go go
768 if ( count($aOIDs) > 0 )
770 $query = "DELETE FROM %s WHERE oid in (%s) and ocontextid=%d;";
771 $query = sprintf($query, sql_table('plugin_option'), implode(',',$aOIDs), (integer) $contextid);
778 * NucleusPlugin::getOptionMeta()
779 * splits the option's typeextra field (at ;'s) to split the meta collection
782 * @param string $typeExtra the value of the typeExtra field of an option
783 * @return array array of the meta-key/value-pairs
785 static public function getOptionMeta($typeExtra)
789 /* 1. if $typeExtra includes delimiter ';', split it to tokens */
790 $tokens = i18n::explode(';', $typeExtra);
793 * 2. if each of tokens includes "=", it consists of key => value
794 * else it's 'select' option
796 foreach ( $tokens as $token )
799 if ( preg_match("#^([^=]+)?=([^=]+)?$#", $token, $matches) )
801 $meta[$matches[1]] = $matches[2];
805 $meta['select'] = $token;
812 * NucleusPlugin::getOptionSelectValues()
813 * filters the selectlists out of the meta collection
816 * @param string $typeExtra the value of the typeExtra field of an option
817 * @return string the selectlist
819 static public function getOptionSelectValues($typeExtra)
821 $meta = NucleusPlugin::getOptionMeta($typeExtra);
823 if ( array_key_exists('select', $meta) )
825 return $meta['select'];
831 * checks if the eventlist in the database is up-to-date
832 * @return bool if it is up-to-date it return true, else false
835 public function subscribtionListIsUptodate()
837 $res = sql_query('SELECT event FROM '.sql_table('plugin_event').' WHERE pid = '.$this->getID());
839 while( $a = sql_fetch_array($res) )
841 array_push($ev, $a['event']);
843 if ( count($ev) != count($this->getEventList()) )
847 $d = array_diff($ev, $this->getEventList());
850 // there are differences so the db is not up-to-date
857 * NucleusPlugin::_applyPluginOptions()
858 * Update its entry in database table
861 * @param $aOptions: array ( 'oid' => array( 'contextid' => 'value'))
862 * (taken from request using requestVar())
863 * @param $newContextid: integer (accepts a contextid when it is for a new
864 * contextid there was no id available at the moment of writing the
865 * formcontrols into the page (by ex: itemOptions for new item)
868 static public function _applyPluginOptions(&$aOptions, $newContextid = 0)
871 if ( !is_array($aOptions) )
876 foreach ( $aOptions as $oid => $values )
878 // get option type info
879 $query = "SELECT opid, oname, ocontext, otype, oextra, odef FROM %s WHERE oid=%d;";
880 $query = sprintf($query, sql_table('plugin_option_desc'), (integer) $oid);
881 $result = sql_query($query);
882 if ( $info = sql_fetch_object($result) )
884 foreach ( $values as $key => $value )
886 // avoid overriding the key used by foreach statement
889 // retreive any metadata
890 $meta = NucleusPlugin::getOptionMeta($info->oextra);
892 // if the option is readonly or hidden it may not be saved
893 if ( array_key_exists('access', $meta)
894 && in_array($meta['access'], array('readonly', 'hidden')) )
899 // value comes from request
900 $value = undoMagic($value);
902 /* validation the value according to its type */
903 switch ( $info->otype )
906 if ( ($value != 'yes') && ($value != 'no') )
913 if ( array_key_exists('datatype', $meta)
914 && ($meta['datatype'] == 'numerical') && ($value != (integer) $value) )
916 $value = (integer) $info->odef;
925 // decide wether we are using the contextid of newContextid
926 if ( $newContextid != 0 )
928 $contextid = $newContextid;
932 * trigger event PrePluginOptionsUpdate to give the plugin the
933 * possibility to change/validate the new value for the option
936 'context' => $info->ocontext,
937 'plugid' => $info->opid,
938 'optionname' => $info->oname,
939 'contextid' => $contextid,
941 $manager->notify('PrePluginOptionsUpdate', $data);
943 // delete and insert its fields of table in database
944 $query = "DELETE FROM %s WHERE oid=%d AND ocontextid=%d;";
945 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid, (integer) $contextid);
947 $query = "INSERT INTO %s (oid, ocontextid, ovalue) VALUES (%d, %d, '%s');";
948 $query = sprintf($query, sql_table('plugin_option'), (integer) $oid, (integer) $contextid, sql_real_escape_string($value));
952 // clear option value cache if the plugin object is already loaded
953 if ( is_object($info) )
955 $plugin=& $manager->pidLoaded($info->opid);
958 $plugin->clearOptionValueCache();