[ Index ]
 

Code source de b2evolution 2.1.0-beta

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/blogs/inc/plugins/model/ -> _plugins_admin.class.php (source)

   1  <?php
   2  /**

   3   * This file implements the {@link Plugins_admin} class, which gets used for administrative

   4   * handling of the {@link Plugin Plugins}.

   5   *

   6   * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.

   7   * See also {@link http://sourceforge.net/projects/evocms/}.

   8   *

   9   * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/}.

  10   * Parts of this file are copyright (c)2006 by Daniel HAHLER - {@link http://daniel.hahler.de/}.

  11   *

  12   * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)

  13   *

  14   * {@internal Open Source relicensing agreement:

  15   * Daniel HAHLER grants Francois PLANQUE the right to license

  16   * Daniel HAHLER's contributions to this file and the b2evolution project

  17   * under any OSI approved OSS license (http://www.opensource.org/licenses/).

  18   * }}

  19   *

  20   * @package evocore

  21   *

  22   * @author blueyed: Daniel HAHLER

  23   *

  24   * @version $Id: _plugins_admin.class.php,v 1.3 2007/07/24 23:29:25 blueyed Exp $

  25   */
  26  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  27  
  28  
  29  load_class('plugins/model/_plugins.class.php');
  30  
  31  
  32  /**

  33   * A Plugins object that loads all Plugins, not just the enabled ones.

  34   * This is needed for the backoffice plugin management.

  35   *

  36   * This extends the basic Plugins by adding all the functionnality needed for administering plugins

  37   * in addition to just using the already enabled plugins.

  38   *

  39   * @package evocore

  40   */
  41  class Plugins_admin extends Plugins
  42  {
  43      /**

  44       * Load all plugins (not just enabled ones).

  45       */
  46      var $sql_load_plugins_table = '
  47              SELECT plug_ID, plug_priority, plug_classname, plug_code, plug_name, plug_shortdesc, plug_apply_rendering, plug_status, plug_version, plug_spam_weight
  48                FROM T_plugins
  49               ORDER BY plug_priority, plug_classname';
  50  
  51  
  52      /**

  53       * Get the list of all events/hooks supported by the plugin framework.

  54       *

  55       * Also puts in additional events provided by plugins.

  56       * fp> please provide an example/use case

  57       *

  58       * Additional to the returned event methods (which can be disabled), there are internal

  59       * ones which just get called on the plugin (and get not remembered in T_pluginevents), e.g.:

  60       *  - AfterInstall

  61       *  - BeforeEnable

  62       *  - BeforeDisable

  63       *  - BeforeInstall

  64       *  - BeforeUninstall

  65       *  - BeforeUninstallPayload

  66       *  - DisplaySkin (called on a skin from {@link GetProvidedSkins()})

  67       *  - ExecCronJob

  68       *  - GetDefaultSettings

  69       *  - GetDefaultUserSettings

  70       *  - GetExtraEvents

  71       *  - GetHtsrvMethods

  72       *  - PluginInit

  73       *  - PluginSettingsUpdateAction (Called as action before updating the plugin's settings)

  74       *  - PluginSettingsEditAction (Called as action before editing the plugin's settings)

  75       *  - PluginSettingsEditDisplayAfter (Called after standard plugin settings are displayed for editing)

  76       *  - PluginSettingsValidateSet (Called before setting a plugin's setting in the backoffice)

  77       *  - PluginUserSettingsUpdateAction (Called as action before updating the plugin's user settings)

  78       *  - PluginUserSettingsEditDisplayAfter (Called after displaying normal user settings)

  79       *  - PluginUserSettingsValidateSet (Called before setting a plugin's user setting in the backoffice)

  80       *  - PluginVersionChanged (Called when we detect a version change)

  81       *

  82       *  The max length of event names is 40 chars (T_pluginevents.pevt_event).

  83       *

  84       * @todo Finish/Complete descriptions

  85       *

  86       * @return array Name of event (key) => description (value)

  87       */
  88  	function get_supported_events()
  89      {
  90          static $supported_events;
  91  
  92          if( empty( $supported_events ) )
  93          {
  94              $supported_events = array(
  95                  'AdminAfterPageFooter' => '',
  96                  'AdminDisplayEditorButton' => '',
  97                  'AdminDisplayToolbar' => 'Display a toolbar on the edit screen(s)',
  98                  'AdminDisplayCommentFormFieldset' => '',
  99                  'AdminDisplayItemFormFieldset' => '',
 100                  'AdminEndHtmlHead' => '',
 101                  'AdminAfterMenuInit' => '',
 102                  'AdminTabAction' => '',
 103                  'AdminTabPayload' => '',
 104                  'AdminToolAction' => '',
 105                  'AdminToolPayload' => '',
 106  
 107                  'AdminBeforeItemEditCreate' => 'This gets called before a new item gets created from the backoffice.',
 108                  'AdminBeforeItemEditUpdate' => 'This gets called before an existing item gets updated from the backoffice.',
 109                  'AdminBeforeItemEditDelete' => 'This gets called before an existing item gets deleted from the backoffice.',
 110  
 111                  'AdminBeginPayload' => '',
 112  
 113                  'CacheObjects' => 'Cache data objects.',
 114                  'CachePageContent' => 'Cache page content.',
 115                  'CacheIsCollectingContent' => 'Gets asked for if we are generating cached content.',
 116  
 117                  'AfterCommentDelete' => 'Gets called after a comment has been deleted from the database.',
 118                  'AfterCommentInsert' => 'Gets called after a comment has been inserted into the database.',
 119                  'AfterCommentUpdate' => 'Gets called after a comment has been updated in the database.',
 120  
 121                  'AfterItemDelete' => '',
 122                  'PrependItemInsertTransact' => '',
 123                  'AfterItemInsert' => '',
 124                  'PrependItemUpdateTransact' => '',
 125                  'AfterItemUpdate' => '',
 126                  'AppendItemPreviewTransact' => '',
 127  
 128                   'FilterItemContents' => 'Filters the content of a post/item right after input.',
 129                   'UnfilterItemContents' => 'Unfilters the content of a post/item right before editing.',
 130  
 131                   // fp> rename to "PreRender"

 132                  'RenderItemAsHtml' => 'Renders content when generated as HTML.',
 133                  'RenderItemAsXml' => 'Renders content when generated as XML.',
 134                  'RenderItemAsText' => 'Renders content when generated as plain text.',
 135  
 136                  // fp> rename to "DispRender"

 137                  // dh> TODO: those do not get called anymore!

 138                  'DisplayItemAsHtml' => 'Called on an item when it gets displayed as HTML.',
 139                  'DisplayItemAsXml' => 'Called on an item when it gets displayed as XML.',
 140                  'DisplayItemAsText' => 'Called on an item when it gets displayed as text.',
 141  
 142  // fp> These is actually RENDERing, right?

 143  // TODO: Rename to "DispRender"

 144                  'FilterCommentAuthor' => 'Filters the comment author.',
 145                  'FilterCommentAuthorUrl' => 'Filters the URL of the comment author.',
 146                  'FilterCommentContent' => 'Filters the content of a comment.',
 147  
 148                  'AfterUserDelete' => '',
 149                  'AfterUserInsert' => '',
 150                  'AfterUserUpdate' => '',
 151  
 152  // fp> This is actually RENDERing, right?

 153  // TODO: Rename to "DispRender"

 154                  'FilterIpAddress' => 'Called when displaying an IP address.',
 155  
 156                  'ItemApplyAsRenderer' => 'Asks the plugin if it wants to apply as a renderer for an item.',
 157                  'ItemCanComment' => 'Asks the plugin if an item can receive comments/feedback.',
 158                  'ItemSendPing' => 'Send a ping to a service about new items.',
 159                  'ItemViewsIncreased' => 'Called when the view counter of an item got increased.',
 160  
 161                  'SkinTag' => '',
 162  
 163                  'AppendHitLog' => 'Called when a hit gets logged, but before it gets recorded.',
 164  
 165                  'DisplayCommentToolbar' => 'Display a toolbar on the public feedback form',
 166                  'DisplayCommentFormButton' => '',
 167                  'DisplayCommentFormFieldset' => '',
 168                  'DisplayMessageFormButton' => '',
 169                  'DisplayMessageFormFieldset' => '',
 170                  'DisplayLoginFormFieldset' => 'Called when displaying the "Login" form.',
 171                  'DisplayRegisterFormFieldset' => 'Called when displaying the "Register" form.',
 172                  'DisplayValidateAccountFormFieldset' => 'Called when displaying the "Validate account" form.',
 173  
 174                  'CommentFormSent' => 'Called when a public comment form has been sent and gets received.',
 175                  'BeforeCommentFormInsert' => 'Called before a comment gets recorded through the public comment form.',
 176                  'AfterCommentFormInsert' => 'Called after a comment has been added through public form.',
 177  
 178                  'BeforeTrackbackInsert' => 'Gets called before a trackback gets recorded.',
 179                  'AfterTrackbackInsert' => 'Gets called after a trackback has been recorded.',
 180  
 181                  'LoginAttempt' => 'Called when a user tries to login.',
 182                  'LoginAttemptNeedsRawPassword' => 'A plugin has to return true here, if it needs a raw (un-hashed) password in LoginAttempt.',
 183                  'AlternateAuthentication' => '',
 184                  'MessageFormSent' => 'Called when the "Message to user" form has been submitted.',
 185                  'MessageFormSentCleanup' => 'Called after a email message has been sent through public form.',
 186                  'Logout' => 'Called when a user logs out.',
 187  
 188                  'GetSpamKarmaForComment' => 'Asks plugin for the spam karma of a comment/trackback.',
 189  
 190                  // Other Plugins can use this:

 191                  'CaptchaValidated' => 'Validate the test from CaptchaPayload to detect humans.',
 192                  'CaptchaValidatedCleanup' => 'Cleanup data used for CaptchaValidated.',
 193                  'CaptchaPayload' => 'Provide a turing test to detect humans.',
 194  
 195                  'RegisterFormSent' => 'Called when the "Register" form has been submitted.',
 196                  'ValidateAccountFormSent' => 'Called when the "Validate account" form has been submitted.',
 197                  'AppendUserRegistrTransact' => 'Gets appended to the transaction that creates a new user on registration.',
 198                  'AfterUserRegistration' => 'Gets called after a new user has registered.',
 199  
 200                  'SessionLoaded' => '', // gets called after $Session is initialized, quite early.
 201  
 202                  'AfterLoginAnonymousUser' => 'Gets called at the end of the login procedure for anonymous visitors.',
 203                  'AfterLoginRegisteredUser' => 'Gets called at the end of the login procedure for registered users.',
 204  
 205                  'BeforeBlogDisplay' => 'Gets called before a (part of the blog) gets displayed.',
 206                  'SkinBeginHtmlHead' => 'Gets called at the top of the HTML HEAD section in a skin.',
 207                  'SkinEndHtmlBody' => 'Gets called at the end of the skin\'s HTML BODY section.',
 208                  'DisplayTrackbackAddr' => '',
 209  
 210                  'GetCronJobs' => 'Gets a list of implemented cron jobs.',
 211                  'GetProvidedSkins' => 'Get a list of "skins" handled by the plugin.',
 212  
 213                  'PluginUserSettingsEditAction' => 'Called as action before editing a user\'s settings.',
 214              );
 215  
 216  
 217              if( ! defined('EVO_IS_INSTALLING') || ! EVO_IS_INSTALLING )
 218              { // only call this, if we're not in the process of installation, to avoid errors from Plugins in this case!
 219  
 220                  // Let Plugins add additional events (if they trigger those events themselves):

 221                  // fp> please provide an example/use case

 222                  $this->load_plugins_table();
 223  
 224                  $rev_sorted_IDs = array_reverse( $this->sorted_IDs ); // so higher priority overwrites lower (just for desc)

 225  
 226                  foreach( $rev_sorted_IDs as $plugin_ID )
 227                  {
 228                      $Plugin = & $this->get_by_ID( $plugin_ID );
 229  
 230                      if( ! $Plugin )
 231                      {
 232                          continue;
 233                      }
 234  
 235                      $extra_events = $Plugin->GetExtraEvents();
 236                      if( is_array($extra_events) )
 237                      {
 238                          $supported_events = array_merge( $supported_events, $extra_events );
 239                      }
 240                  }
 241              }
 242          }
 243  
 244          return $supported_events;
 245      }
 246  
 247  
 248      /**

 249       * Un-register a plugin, only if forced.

 250       *

 251       * This does not un-install it from DB, just from the internal indexes.

 252       *

 253       * @param Plugin

 254       * @param boolean Force unregistering

 255       * @return boolean True, if unregistered

 256       */
 257  	function unregister( & $Plugin, $force = false )
 258      {
 259          if( ! $force )
 260          {
 261              return false;
 262          }
 263  
 264          return parent::unregister($Plugin, $force);
 265      }
 266  
 267  
 268      /**

 269       * Count # of registrations of same plugin.

 270       *

 271       * Plugins with negative ID (auto-generated; not installed (yet)) will not get considered.

 272       *

 273       * @param string class name

 274       * @return int # of regs

 275       */
 276  	function count_regs( $classname )
 277      {
 278          $count = 0;
 279  
 280          foreach( $this->sorted_IDs as $plugin_ID )
 281          {
 282              $Plugin = & $this->get_by_ID( $plugin_ID );
 283              if( $Plugin && $Plugin->classname == $classname && $Plugin->ID > 0 )
 284              {
 285                  $count++;
 286              }
 287          }
 288          return $count;
 289      }
 290  
 291  
 292      /**

 293       * Discover and register all available plugins in the {@link $plugins_path} folder/subfolders.

 294       */
 295  	function discover()
 296      {
 297          global $Debuglog, $Timer;
 298  
 299          $Timer->resume('plugins_discover');
 300  
 301          $Debuglog->add( 'Discovering plugins...', 'plugins' );
 302  
 303          // too inefficient: foreach( get_filenames( $this->plugins_path, true, false ) as $path )

 304  
 305          // Get subdirs in $this->plugins_path

 306          $subdirs = array();
 307          $subdirs = get_filenames( $this->plugins_path, false, true, true, false );
 308  
 309          // Skip plugins which are in a directory that starts with an underscore ("_")

 310          foreach( $subdirs as $k => $v )
 311          {
 312              $v_bn = basename($v);
 313              if( substr(basename($v_bn), 0, 1) == '_' || substr($v_bn, -7) != '_plugin' )
 314              {
 315                  unset($subdirs[$k]);
 316              }
 317          }
 318          $subdirs[] = $this->plugins_path;
 319  
 320          foreach( $subdirs as $subdir )
 321          {
 322              foreach( get_filenames( $subdir, true, false, true, false ) as $filename )
 323              {
 324                  if( ! preg_match( '~/_([^/]+)\.plugin\.php$~', $filename, $match ) && is_file( $filename ) )
 325                  {
 326                      continue;
 327                  }
 328  
 329                  $classname = $match[1].'_plugin';
 330  
 331                  if( $this->get_by_classname($classname) )
 332                  {
 333                      $Debuglog->add( 'Skipping duplicate plugin (classname '.$classname.')!', array('error', 'plugins') );
 334                      continue;
 335                  }
 336                  $this->register( $classname, 0, -1, NULL, $filename ); // auto-generate negative ID; will return string on error.

 337              }
 338          }
 339  
 340          $Timer->pause('plugins_discover');
 341      }
 342  
 343  
 344      /**

 345       * Get the list of all possible values for apply_rendering (defines when a rendering Plugin can apply).

 346       *

 347       * @todo Add descriptions.

 348       *

 349       * @param boolean Return an associative array with description for the values?

 350       * @return array

 351       */
 352  	function get_apply_rendering_values( $with_desc = false )
 353      {
 354          static $apply_rendering_values;
 355  
 356          if( empty( $apply_rendering_values ) )
 357          {
 358              $apply_rendering_values = array(
 359                      'stealth' => '',
 360                      'always' => '',
 361                      'opt-out' => '',
 362                      'opt-in' => '',
 363                      'lazy' => '',
 364                      'never' => '',
 365                  );
 366          }
 367          if( ! $with_desc )
 368          {
 369              return array_keys( $apply_rendering_values );
 370          }
 371  
 372          return $apply_rendering_values;
 373      }
 374  
 375  
 376      /**

 377       * Discover plugin events from its source file.

 378       *

 379       * Get a list of methods that are supported as events out of the Plugin's

 380       * class definition.

 381       *

 382       * @todo Extend to get list of defined classes and global functions and check this list before sourcing/including a Plugin! (prevent fatal error)

 383       *

 384       * @return array

 385       */
 386  	function get_registered_events( $Plugin )
 387      {
 388          global $Timer, $Debuglog;
 389  
 390          $Timer->resume( 'plugins_detect_events' );
 391  
 392          $plugin_class_methods = array();
 393  
 394          if( ! is_readable($Plugin->classfile_path) )
 395          {
 396              $Debuglog->add( 'get_registered_events(): "'.$Plugin->classfile_path.'" is not readable.', array('plugins', 'error') );
 397              return array();
 398          }
 399  
 400          $classfile_contents = @file_get_contents( $Plugin->classfile_path );
 401          if( ! is_string($classfile_contents) )
 402          {
 403              $Debuglog->add( 'get_registered_events(): "'.$Plugin->classfile_path.'" could not get read.', array('plugins', 'error') );
 404              return array();
 405          }
 406  
 407          // TODO: allow optional Plugin callback to get list of methods. Like Plugin::GetRegisteredEvents().

 408          // fp> bloated. what problem does it solve?

 409          // dh> With a captcha_base.class.php the actual plugin (extending the class) would have to define all the event methods and not just the methods to provide the tests.

 410          //     With a GetRegisteredEvents method in captcha_base.class.php this would not be required.

 411          //     The whole point of such a base class would be to simplify writing a captcha plugin and IMHO it's "bloated" to force a whole block of methods into it that do only call the parent method.

 412  
 413          // TODO: dh> only match in the relevant "class block"

 414          if( preg_match_all( '~^\s*function\s+(\w+)~mi', $classfile_contents, $matches ) )
 415          {
 416              $plugin_class_methods = $matches[1];
 417          }
 418          else
 419          {
 420              $Debuglog->add( 'No functions found in file "'.$Plugin->classfile_path.'".', array('plugins', 'error') );
 421              return array();
 422          }
 423  
 424          $supported_events = $this->get_supported_events();
 425          $supported_events = array_keys($supported_events);
 426          $verified_events = array_intersect( $plugin_class_methods, $supported_events );
 427  
 428          $Timer->pause( 'plugins_detect_events' );
 429  
 430          // TODO: Report, when difference in $events_verified and what getRegisteredEvents() returned

 431          return $verified_events;
 432      }
 433  
 434  
 435      /**

 436       * Install a plugin into DB.

 437       *

 438       * NOTE: this won't install necessary DB changes and not trigger {@link Plugin::AfterInstall}!

 439       *

 440       * @param string Classname of the plugin to install

 441       * @param string Initial DB Status of the plugin ("enabled", "disabled", "needs_config", "broken")

 442       * @param string Optional classfile path, if not default (used for tests).

 443       * @return Plugin The installed Plugin (perhaps with $install_dep_notes set) or a string in case of error.

 444       */
 445      function & install( $classname, $plug_status = 'enabled', $classfile_path = NULL )
 446      {
 447          global $DB, $Debuglog;
 448  
 449          // Load Plugins data from T_plugins (only once), ordered by priority.

 450          $this->load_plugins_table();
 451  
 452          // Register the plugin:

 453          $Plugin = & $this->register( $classname, 0, -1, NULL, $classfile_path ); // Auto-generates negative ID; New ID will be set a few lines below

 454  
 455          if( is_string($Plugin) )
 456          { // return error message from register()
 457              return $Plugin;
 458          }
 459  
 460          if( isset($Plugin->number_of_installs)
 461              && ( $this->count_regs( $Plugin->classname ) >= $Plugin->number_of_installs ) )
 462          {
 463              $this->unregister( $Plugin, true );
 464              $r = T_('The plugin cannot be installed again.');
 465              return $r;
 466          }
 467  
 468          $install_return = $Plugin->BeforeInstall();
 469          if( $install_return !== true )
 470          {
 471              $this->unregister( $Plugin, true );
 472              $r = T_('The installation of the plugin failed.');
 473              if( is_string($install_return) )
 474              {
 475                  $r .= '<br />'.$install_return;
 476              }
 477              return $r;
 478          }
 479  
 480          // Dependencies:

 481          /*

 482          // We must check dependencies against installed Plugins ($Plugins)

 483          // TODO: not possible anymore.. check it..

 484          global $Plugins;

 485          $dep_msgs = $Plugins->validate_dependencies( $Plugin, 'enable' );

 486          */
 487          $dep_msgs = $this->validate_dependencies( $Plugin, 'enable' );
 488  
 489          if( ! empty( $dep_msgs['error'] ) )
 490          { // fatal errors (required dependencies):
 491              $this->unregister( $Plugin, true );
 492              $r = T_('Some plugin dependencies are not fulfilled:').' <ul><li>'.implode( '</li><li>', $dep_msgs['error'] ).'</li></ul>';
 493              return $r;
 494          }
 495  
 496          // All OK, install:

 497          if( empty($Plugin->code) )
 498          {
 499              $Plugin->code = NULL;
 500          }
 501  
 502          $Plugin->status = $plug_status;
 503  
 504          // Record into DB

 505          $DB->begin();
 506  
 507          $DB->query( '
 508                  INSERT INTO T_plugins( plug_classname, plug_priority, plug_code, plug_apply_rendering, plug_version, plug_status )
 509                  VALUES( "'.$classname.'", '.$Plugin->priority.', '.$DB->quote($Plugin->code).', '.$DB->quote($Plugin->apply_rendering).', '.$DB->quote($Plugin->version).', '.$DB->quote($Plugin->status).' ) ' );
 510  
 511          // Unset auto-generated ID info

 512          unset( $this->index_ID_Plugins[ $Plugin->ID ] );
 513          $key = array_search( $Plugin->ID, $this->sorted_IDs );
 514  
 515          // New ID:

 516          $Plugin->ID = $DB->insert_id;
 517          $this->index_ID_Plugins[ $Plugin->ID ] = & $Plugin;
 518          $this->index_ID_rows[ $Plugin->ID ] = array(
 519                  'plug_ID' => $Plugin->ID,
 520                  'plug_priority' => $Plugin->priority,
 521                  'plug_classname' => $Plugin->classname,
 522                  'plug_code' => $Plugin->code,
 523                  'plug_apply_rendering' => $Plugin->apply_rendering,
 524                  'plug_status' => $Plugin->status,
 525                  'plug_version' => $Plugin->version,
 526              );
 527          $this->sorted_IDs[$key] = $Plugin->ID;
 528  
 529          $this->save_events( $Plugin );
 530  
 531          $DB->commit();
 532  
 533          $Debuglog->add( 'Installed plugin: '.$Plugin->name.' ID: '.$Plugin->ID, 'plugins' );
 534  
 535          if( ! empty($dep_msgs['note']) )
 536          { // Add dependency notes
 537              $Plugin->install_dep_notes = $dep_msgs['note'];
 538          }
 539  
 540          // Do the stuff that we've skipped in register method at the beginning:

 541  
 542          $this->init_settings( $Plugin );
 543  
 544          $tmp_params = array('db_row' => $this->index_ID_rows[$Plugin->ID], 'is_installed' => false);
 545  
 546          if( $Plugin->PluginInit( $tmp_params ) === false && $this->unregister( $Plugin ) )
 547          {
 548              $Debuglog->add( 'Unregistered plugin, because PluginInit returned false.', 'plugins' );
 549              $Plugin = '';
 550          }
 551  
 552          if( ! defined('EVO_IS_INSTALLING') || ! EVO_IS_INSTALLING )
 553          { // do not sort, if we're installing/upgrading.. instantiating Plugins might cause a fatal error!
 554              $this->sort();
 555          }
 556  
 557          return $Plugin;
 558      }
 559  
 560  
 561      /**

 562       * Uninstall a plugin.

 563       *

 564       * Removes the Plugin, its Settings and Events from the database.

 565       *

 566       * @return boolean True on success

 567       */
 568  	function uninstall( $plugin_ID )
 569      {
 570          global $DB, $Debuglog;
 571  
 572          $Debuglog->add( 'Uninstalling plugin (ID '.$plugin_ID.')...', 'plugins' );
 573  
 574          $Plugin = & $this->get_by_ID( $plugin_ID ); // get the Plugin before any not loaded data might get deleted below

 575  
 576          $DB->begin();
 577  
 578          // Delete Plugin settings (constraints)

 579          $DB->query( "DELETE FROM T_pluginsettings
 580                        WHERE pset_plug_ID = $plugin_ID" );
 581  
 582          // Delete Plugin user settings (constraints)

 583          $DB->query( "DELETE FROM T_pluginusersettings
 584                        WHERE puset_plug_ID = $plugin_ID" );
 585  
 586          // Delete Plugin events (constraints)

 587          foreach( $DB->get_col( '
 588                  SELECT pevt_event
 589                    FROM T_pluginevents
 590                   WHERE pevt_enabled = 1' ) as $event )
 591          {
 592              if( strpos($event, 'RenderItemAs') === 0 )
 593              { // Clear pre-rendered content cache, if RenderItemAs* events get removed:
 594                  $DB->query( 'DELETE FROM T_items__prerendering WHERE 1=1' );
 595                  $ItemCache = & get_Cache( 'ItemCache' );
 596                  $ItemCache->clear();
 597                  break;
 598              }
 599          }
 600          $DB->query( "DELETE FROM T_pluginevents
 601                        WHERE pevt_plug_ID = $plugin_ID" );
 602  
 603          // Delete from DB

 604          $DB->query( "DELETE FROM T_plugins
 605                        WHERE plug_ID = $plugin_ID" );
 606  
 607          $DB->commit();
 608  
 609          if( $Plugin )
 610          {
 611              $this->unregister( $Plugin, true );
 612          }
 613  
 614          $Debuglog->add( 'Uninstalled plugin (ID '.$plugin_ID.').', 'plugins' );
 615          return true;
 616      }
 617  
 618  
 619      /**

 620       * (Re)load Plugin Events for enabled (normal use) or all (admin use) plugins.

 621       *

 622       * This is the same as {@link Plugins::load_events()} except that it loads all Plugins (not just enabled ones)

 623       */
 624  	function load_events()
 625      {
 626          global $Debuglog, $DB;
 627  
 628          $this->index_event_IDs = array();
 629  
 630          $Debuglog->add( 'Loading plugin events.', 'plugins' );
 631          foreach( $DB->get_results( '
 632                  SELECT pevt_plug_ID, pevt_event
 633                      FROM T_pluginevents INNER JOIN T_plugins ON pevt_plug_ID = plug_ID
 634                   WHERE pevt_enabled > 0
 635                   ORDER BY plug_priority, plug_classname', OBJECT, 'Loading plugin events' ) as $l_row )
 636          {
 637              $this->index_event_IDs[$l_row->pevt_event][] = $l_row->pevt_plug_ID;
 638          }
 639      }
 640  
 641  
 642      /**

 643       * Save the events that the plugin provides into DB, while removing obsolete

 644       * entries (that the plugin does not register anymore).

 645       *

 646       * @param Plugin Plugin to save events for

 647       * @param array List of events to save as enabled for the Plugin.

 648       *              By default all provided events get saved as enabled. Pass array() to discover only new ones.

 649       * @param array List of events to save as disabled for the Plugin.

 650       *              By default, no events get disabled. Disabling an event takes priority over enabling.

 651       * @return boolean True, if events have changed, false if not.

 652       */
 653  	function save_events( $Plugin, $enable_events = NULL, $disable_events = NULL )
 654      {
 655          global $DB, $Debuglog;
 656  
 657          $r = false;
 658  
 659          $saved_events = array();
 660          foreach( $DB->get_results( '
 661                  SELECT pevt_event, pevt_enabled
 662                    FROM T_pluginevents
 663                   WHERE pevt_plug_ID = '.$Plugin->ID ) as $l_row )
 664          {
 665              $saved_events[$l_row->pevt_event] = $l_row->pevt_enabled;
 666          }
 667  
 668          // Discover events from plugin's source file:

 669          $available_events = $this->get_registered_events( $Plugin );
 670  
 671          $obsolete_events = array_diff( array_keys($saved_events), $available_events );
 672  
 673          if( is_null( $enable_events ) )
 674          { // Enable all events:
 675              $enable_events = $available_events;
 676          }
 677          if( is_null( $disable_events ) )
 678          {
 679              $disable_events = array();
 680          }
 681          if( $disable_events )
 682          { // Remove events to be disabled from enabled ones:
 683              $enable_events = array_diff( $enable_events, $disable_events );
 684          }
 685  
 686          // New discovered events:

 687          $discovered_events = array_diff( $available_events, array_keys($saved_events), $enable_events, $disable_events );
 688  
 689  
 690          // Delete obsolete events from DB:

 691          if( $obsolete_events && $DB->query( "
 692                  DELETE FROM T_pluginevents
 693                  WHERE pevt_plug_ID = ".$Plugin->ID."
 694                      AND pevt_event IN ( '".implode( '", "', $obsolete_events )."' )" ) )
 695          {
 696              $r = true;
 697          }
 698  
 699          if( $discovered_events )
 700          {
 701              $DB->query( '
 702                  INSERT INTO T_pluginevents( pevt_plug_ID, pevt_event, pevt_enabled )
 703                  VALUES ( '.$Plugin->ID.', "'.implode( '", 1 ), ('.$Plugin->ID.', "', $discovered_events ).'", 1 )' );
 704              $r = true;
 705  
 706              $Debuglog->add( 'Discovered events ['.implode( ', ', $discovered_events ).'] for Plugin '.$Plugin->name, 'plugins' );
 707          }
 708  
 709          $new_events_enabled = array();
 710          if( $enable_events )
 711          {
 712              foreach( $enable_events as $l_event )
 713              {
 714                  if( ! isset( $saved_events[$l_event] ) || ! $saved_events[$l_event] )
 715                  { // Event not saved yet or not enabled
 716                      $new_events_enabled[] = $l_event;
 717                  }
 718              }
 719              if( $new_events_enabled )
 720              {
 721                  $DB->query( '
 722                      REPLACE INTO T_pluginevents( pevt_plug_ID, pevt_event, pevt_enabled )
 723                      VALUES ( '.$Plugin->ID.', "'.implode( '", 1 ), ('.$Plugin->ID.', "', $new_events_enabled ).'", 1 )' );
 724                  $r = true;
 725              }
 726              $Debuglog->add( 'Enabled events ['.implode( ', ', $new_events_enabled ).'] for Plugin '.$Plugin->name, 'plugins' );
 727          }
 728  
 729          $new_events_disabled = array();
 730          if( $disable_events )
 731          {
 732              foreach( $disable_events as $l_event )
 733              {
 734                  if( ! isset( $saved_events[$l_event] ) || $saved_events[$l_event] )
 735                  { // Event not saved yet or enabled
 736                      $new_events_disabled[] = $l_event;
 737                  }
 738              }
 739              if( $new_events_disabled )
 740              {
 741                  $DB->query( '
 742                      REPLACE INTO T_pluginevents( pevt_plug_ID, pevt_event, pevt_enabled )
 743                      VALUES ( '.$Plugin->ID.', "'.implode( '", 0 ), ('.$Plugin->ID.', "', $new_events_disabled ).'", 0 )' );
 744                  $r = true;
 745              }
 746              $Debuglog->add( 'Disabled events ['.implode( ', ', $new_events_disabled ).'] for Plugin '.$Plugin->name, 'plugins' );
 747          }
 748  
 749          if( $r )
 750          { // Something has changed: Reload event index
 751              foreach( array_merge($obsolete_events, $discovered_events, $new_events_enabled, $new_events_disabled) as $event )
 752              {
 753                  if( strpos($event, 'RenderItemAs') === 0 )
 754                  { // Clear pre-rendered content cache, if RenderItemAs* events have been added or removed:
 755                      $DB->query( 'DELETE FROM T_items__prerendering WHERE 1=1' );
 756                      $ItemCache = & get_Cache( 'ItemCache' );
 757                      $ItemCache->clear();
 758                      break;
 759                  }
 760              }
 761  
 762              $this->load_events();
 763          }
 764  
 765          return $r;
 766      }
 767  
 768  
 769      /**

 770       * Set the apply_rendering value for a given Plugin ID.

 771       *

 772       * It makes sure that the index is handled and writes it to DB.

 773       *

 774       * @return boolean true if set to new value, false in case of error or if already set to same value

 775       */
 776  	function set_apply_rendering( $plugin_ID, $apply_rendering )
 777      {
 778          global $DB;
 779  
 780          if( ! in_array( $apply_rendering, $this->get_apply_rendering_values() ) )
 781          {
 782              debug_die( 'Plugin apply_rendering not in allowed list.' );
 783          }
 784  
 785          $Plugin = & $this->get_by_ID($plugin_ID);
 786          if( ! $Plugin )
 787          {
 788              return false;
 789          }
 790  
 791          if( $this->index_ID_rows[$Plugin->ID]['plug_apply_rendering'] == $apply_rendering )
 792          { // Already set to same value
 793              return false;
 794          }
 795  
 796          $r = $DB->query( '
 797              UPDATE T_plugins
 798                SET plug_apply_rendering = '.$DB->quote($apply_rendering).'
 799              WHERE plug_ID = '.$plugin_ID );
 800  
 801          $Plugin->apply_rendering = $apply_rendering;
 802  
 803          // Apply-rendering index:

 804          if( ! isset( $this->index_apply_rendering_codes[ $Plugin->apply_rendering ] )
 805              || ! in_array( $Plugin->code, $this->index_apply_rendering_codes[ $Plugin->apply_rendering ] ) )
 806          {
 807              $this->index_apply_rendering_codes[ $Plugin->apply_rendering ][] = $Plugin->code;
 808          }
 809  
 810          return true;
 811      }
 812  
 813  
 814      /**

 815       * Set the code for a given Plugin ID.

 816       *

 817       * It makes sure that the index is handled and writes it to DB.

 818       *

 819       * @param string Plugin ID

 820       * @param string Code to set the plugin to

 821       * @return boolean|integer|string

 822       *   true, if already set to same value.

 823       *   string: error message (already in use, wrong format)

 824       *   1 in case of setting it into DB (number of affected rows).

 825       *   false, if invalid Plugin.

 826       */
 827  	function set_code( $plugin_ID, $code )
 828      {
 829          global $DB;
 830  
 831          if( strlen( $code ) > 32 )
 832          {
 833              return T_( 'The maximum length of a plugin code is 32 characters.' );
 834          }
 835  
 836          // TODO: more strict check?! Just "[\w_-]+" as regexp pattern?

 837          if( strpos( $code, '.' ) !== false )
 838          {
 839              return T_( 'The plugin code cannot include a dot!' );
 840          }
 841  
 842          if( ! empty($code) && isset( $this->index_code_ID[$code] ) )
 843          {
 844              if( $this->index_code_ID[$code] == $plugin_ID )
 845              { // Already set to same value
 846                  return true;
 847              }
 848              else
 849              {
 850                  return T_( 'The plugin code is already in use by another plugin.' );
 851              }
 852          }
 853  
 854          $Plugin = & $this->get_by_ID( $plugin_ID );
 855          if( ! $Plugin )
 856          {
 857              return false;
 858          }
 859  
 860          if( empty($code) )
 861          {
 862              $code = NULL;
 863          }
 864          else
 865          { // update indexes
 866              $this->index_code_ID[$code] = & $Plugin->ID;
 867              $this->index_code_Plugins[$code] = & $Plugin;
 868          }
 869  
 870          // Update references to code:

 871          // TODO: dh> we might want to update item renderer codes and blog ping plugin codes here! (old code=>new code)

 872  
 873          $Plugin->code = $code;
 874  
 875          return $DB->query( '
 876              UPDATE T_plugins
 877                SET plug_code = '.$DB->quote($code).'
 878              WHERE plug_ID = '.$plugin_ID );
 879      }
 880  
 881  
 882      /**

 883       * Set the status of an event for a given Plugin.

 884       *

 885       * @return boolean True, if status has changed; false if not

 886       */
 887  	function set_event_status( $plugin_ID, $plugin_event, $enabled )
 888      {
 889          global $DB;
 890  
 891          $enabled = $enabled ? 1 : 0;
 892  
 893          $DB->query( '
 894              UPDATE T_pluginevents
 895                 SET pevt_enabled = '.$enabled.'
 896               WHERE pevt_plug_ID = '.$plugin_ID.'
 897                 AND pevt_event = "'.$plugin_event.'"' );
 898  
 899          if( $DB->rows_affected )
 900          {
 901              $this->load_events();
 902  
 903              if( strpos($plugin_event, 'RenderItemAs') === 0 )
 904              { // Clear pre-rendered content cache, if RenderItemAs* events have been added or removed:
 905                  $DB->query( 'DELETE FROM T_items__prerendering WHERE 1=1' );
 906                  $ItemCache = & get_Cache( 'ItemCache' );
 907                  $ItemCache->clear();
 908                  break;
 909              }
 910  
 911              return true;
 912          }
 913  
 914          return false;
 915      }
 916  
 917  
 918      /**

 919       * Set the priority for a given Plugin ID.

 920       *

 921       * It makes sure that the index is handled and writes it to DB.

 922       *

 923       * @return boolean|integer

 924       *   true, if already set to same value.

 925       *   false if another Plugin uses that priority already.

 926       *   1 in case of setting it into DB.

 927       */
 928  	function set_priority( $plugin_ID, $priority )
 929      {
 930          global $DB;
 931  
 932          if( ! preg_match( '~^1?\d?\d$~', $priority ) ) // using preg_match() to catch floating numbers
 933          {
 934              debug_die( 'Plugin priority must be numeric (0-100).' );
 935          }
 936  
 937          $Plugin = & $this->get_by_ID($plugin_ID);
 938          if( ! $Plugin )
 939          {
 940              return false;
 941          }
 942  
 943          if( $Plugin->priority == $priority )
 944          { // Already set to same value
 945              return true;
 946          }
 947  
 948          $r = $DB->query( '
 949              UPDATE T_plugins
 950                SET plug_priority = '.$DB->quote($priority).'
 951              WHERE plug_ID = '.$plugin_ID );
 952  
 953          $Plugin->priority = $priority;
 954  
 955          // TODO: dh> should only re-sort, if sorted by priority before - if it should get re-sorted at all!

 956          //$this->sort();

 957  
 958          return $r;
 959      }
 960  
 961  
 962      /**

 963       * Sort the list of plugins.

 964       *

 965       * WARNING: do NOT sort by anything else than priority unless you're handling a list of NOT-YET-INSTALLED plugins!

 966       *

 967       * @param string Order: 'priority' (default), 'name'

 968       */
 969  	function sort( $order = 'priority' )
 970      {
 971          $this->load_plugins_table();
 972  
 973          foreach( $this->sorted_IDs as $k => $plugin_ID )
 974          { // Instantiate every plugin, so invalid ones do not get unregistered during sorting (crashes PHP, because $sorted_IDs gets changed etc)
 975              if( ! $this->get_by_ID( $plugin_ID ) )
 976              {
 977                  unset($this->sorted_IDs[$k]);
 978              }
 979          }
 980  
 981          switch( $order )
 982          {
 983              case 'name':
 984                  usort( $this->sorted_IDs, array( & $this, 'sort_Plugin_name') );
 985                  break;
 986  
 987              case 'group':
 988                  usort( $this->sorted_IDs, array( & $this, 'sort_Plugin_group') );
 989                  break;
 990  
 991              default:
 992                  // Sort array by priority:

 993                  usort( $this->sorted_IDs, array( & $this, 'sort_Plugin_priority') );
 994          }
 995  
 996          $this->current_idx = -1;
 997      }
 998  
 999      /**

1000       * Callback function to sort plugins by priority (and classname, if they have same priority).

1001       */
1002  	function sort_Plugin_priority( & $a_ID, & $b_ID )
1003      {
1004          $a_Plugin = & $this->get_by_ID( $a_ID );
1005          $b_Plugin = & $this->get_by_ID( $b_ID );
1006  
1007          $r = $a_Plugin->priority - $b_Plugin->priority;
1008  
1009          if( $r == 0 )
1010          {
1011              $r = strcasecmp( $a_Plugin->classname, $b_Plugin->classname );
1012          }
1013  
1014          return $r;
1015      }
1016  
1017      /**

1018       * Callback function to sort plugins by name.

1019       *

1020       * WARNING: do NOT sort by anything else than priority unless you're handling a list of NOT-YET-INSTALLED plugins

1021       */
1022  	function sort_Plugin_name( & $a_ID, & $b_ID )
1023      {
1024          $a_Plugin = & $this->get_by_ID( $a_ID );
1025          $b_Plugin = & $this->get_by_ID( $b_ID );
1026  
1027          return strcasecmp( $a_Plugin->name, $b_Plugin->name );
1028      }
1029  
1030  
1031      /**

1032       * Callback function to sort plugins by group, sub-group and name.

1033       *

1034       * Those, which have a group get sorted above the ones without one.

1035       *

1036       * WARNING: do NOT sort by anything else than priority unless you're handling a list of NOT-YET-INSTALLED plugins

1037       */
1038  	function sort_Plugin_group( & $a_ID, & $b_ID )
1039      {
1040          $a_Plugin = & $this->get_by_ID( $a_ID );
1041          $b_Plugin = & $this->get_by_ID( $b_ID );
1042  
1043          // first check if both have a group (-1: only A has a group; 1: only B has a group; 0: both have a group or no group):

1044          $r = (int)empty($a_Plugin->group) - (int)empty($b_Plugin->group);
1045          if( $r != 0 )
1046          {
1047              return $r;
1048          }
1049  
1050          // Compare Group

1051          $r = strcasecmp( $a_Plugin->group, $b_Plugin->group );
1052          if( $r != 0 )
1053          {
1054              return $r;
1055          }
1056  
1057          // Compare Sub Group

1058          $r = strcasecmp( $a_Plugin->sub_group, $b_Plugin->sub_group );
1059          if( $r != 0 )
1060          {
1061              return $r;
1062          }
1063  
1064          // Compare Name

1065          return strcasecmp( $a_Plugin->name, $b_Plugin->name );
1066      }
1067  
1068  
1069      /**

1070       * Validate dependencies of a Plugin.

1071       *

1072       * @param Plugin

1073       * @param string Mode of check: either 'enable' or 'disable'

1074       * @return array The key 'note' holds an array of notes (recommendations), the key 'error' holds a list

1075       *               of messages for dependency errors.

1076       */
1077  	function validate_dependencies( & $Plugin, $mode )
1078      {
1079          global $DB, $app_name;
1080          global $app_version;
1081  
1082          $msgs = array();
1083  
1084          if( $mode == 'disable' )
1085          { // Check the whole list of installed plugins if they depend on our Plugin or it's (set of) events.
1086              $required_by_plugin = array(); // a list of plugin classnames that require our poor Plugin

1087  
1088              foreach( $this->sorted_IDs as $validate_against_ID )
1089              {
1090                  if( $validate_against_ID == $Plugin->ID )
1091                  { // the plugin itself
1092                      continue;
1093                  }
1094  
1095                  $against_Plugin = & $this->get_by_ID($validate_against_ID);
1096  
1097                  if( $against_Plugin->status != 'enabled' )
1098                  { // The plugin is not enabled (this check is needed when checking deps with the Plugins_admin class)
1099                      continue;
1100                  }
1101  
1102                  $deps = $against_Plugin->GetDependencies();
1103  
1104                  if( empty($deps['requires']) )
1105                  { // has no dependencies
1106                      continue;
1107                  }
1108  
1109                  if( ! empty($deps['requires']['plugins']) )
1110                  {
1111                      foreach( $deps['requires']['plugins'] as $l_req_plugin )
1112                      {
1113                          if( ! is_array($l_req_plugin) )
1114                          {
1115                              $l_req_plugin = array( $l_req_plugin, 0 );
1116                          }
1117  
1118                          if( $Plugin->classname == $l_req_plugin[0] )
1119                          { // our plugin is required by this one, check if it is the only instance
1120                              if( $this->count_regs($Plugin->classname) < 2 )
1121                              {
1122                                  $required_by_plugin[] = $against_Plugin->classname;
1123                              }
1124                          }
1125                      }
1126                  }
1127  
1128                  if( ! empty($deps['requires']['events_by_one']) )
1129                  {
1130                      foreach( $deps['requires']['events_by_one'] as $req_events )
1131                      {
1132                          // Get a list of plugins that provide all the events

1133                          $provided_by = array_keys( $this->get_list_by_all_events( $req_events ) );
1134  
1135                          if( in_array($Plugin->ID, $provided_by) && count($provided_by) < 2 )
1136                          { // we're the only Plugin which provides this set of events
1137                              $msgs['error'][] = sprintf( T_( 'The events %s are required by %s (ID %d).' ), implode_with_and($req_events), $against_Plugin->classname, $against_Plugin->ID );
1138                          }
1139                      }
1140                  }
1141  
1142                  if( ! empty($deps['requires']['events']) )
1143                  {
1144                      foreach( $deps['requires']['events'] as $req_event )
1145                      {
1146                          // Get a list of plugins that provide all the events

1147                          $provided_by = array_keys( $this->get_list_by_event( $req_event ) );
1148  
1149                          if( in_array($Plugin->ID, $provided_by) && count($provided_by) < 2 )
1150                          { // we're the only Plugin which provides this event
1151                              $msgs['error'][] = sprintf( T_( 'The event %s is required by %s (ID %d).' ), $req_event, $against_Plugin->classname, $against_Plugin->ID );
1152                          }
1153                      }
1154                  }
1155  
1156                  // TODO: We might also handle the 'recommends' and add it to $msgs['note']

1157              }
1158  
1159              if( ! empty( $required_by_plugin ) )
1160              { // Prepend the message to the beginning, because it's the most restrictive (IMHO)
1161                  $required_by_plugin = array_unique($required_by_plugin);
1162                  if( ! isset($msgs['error']) )
1163                  {
1164                      $msgs['error'] = array();
1165                  }
1166                  array_unshift( $msgs['error'], sprintf( T_('The plugin is required by the following plugins: %s.'), implode_with_and($required_by_plugin) ) );
1167              }
1168  
1169              return $msgs;
1170          }
1171  
1172  
1173          // mode 'enable':

1174          $deps = $Plugin->GetDependencies();
1175  
1176          if( empty($deps) )
1177          {
1178              return array();
1179          }
1180  
1181          foreach( $deps as $class => $dep_list ) // class: "requires" or "recommends"
1182          {
1183              if( ! is_array($dep_list) )
1184              { // Invalid format: "throw" error (needs not translation)
1185                  return array(
1186                          'error' => array( 'GetDependencies() did not return array of arrays. Please contact the plugin developer.' )
1187                      );
1188              }
1189              foreach( $dep_list as $type => $type_params )
1190              {
1191                  switch( $type )
1192                  {
1193                      case 'events_by_one':
1194                          foreach( $type_params as $sub_param )
1195                          {
1196                              if( ! is_array($sub_param) )
1197                              { // Invalid format: "throw" error (needs not translation)
1198                                  return array(
1199                                          'error' => array( 'GetDependencies() did not return array of arrays for "events_by_one". Please contact the plugin developer.' )
1200                                      );
1201                              }
1202                              if( ! $this->are_events_available( $sub_param, true ) )
1203                              {
1204                                  if( $class == 'recommends' )
1205                                  {
1206                                      $msgs['note'][] = sprintf( T_( 'The plugin recommends a plugin which provides all of the following events: %s.' ), implode_with_and( $sub_param ) );
1207                                  }
1208                                  else
1209                                  {
1210                                      $msgs['error'][] = sprintf( T_( 'The plugin requires a plugin which provides all of the following events: %s.' ), implode_with_and( $sub_param ) );
1211                                  }
1212                              }
1213                          }
1214                          break;
1215  
1216                      case 'events':
1217                          if( ! $this->are_events_available( $type_params, false ) )
1218                          {
1219                              if( $class == 'recommends' )
1220                              {
1221                                  $msgs['note'][] = sprintf( T_( 'The plugin recommends plugins which provide the events: %s.' ), implode_with_and( $type_params ) );
1222                              }
1223                              else
1224                              {
1225                                  $msgs['error'][] = sprintf( T_( 'The plugin requires plugins which provide the events: %s.' ), implode_with_and( $type_params ) );
1226                              }
1227                          }
1228                          break;
1229  
1230                      case 'plugins':
1231                          if( ! is_array($type_params) )
1232                          { // Invalid format: "throw" error (needs not translation)
1233                              return array(
1234                                      'error' => array( 'GetDependencies() did not return array of arrays for "plugins". Please contact the plugin developer.' )
1235                                  );
1236                          }
1237                          foreach( $type_params as $plugin_req )
1238                          {
1239                              if( ! is_array($plugin_req) )
1240                              {
1241                                  $plugin_req = array( $plugin_req, '0' );
1242                              }
1243                              elseif( ! isset($plugin_req[1]) )
1244                              {
1245                                  $plugin_req[1] = '0';
1246                              }
1247  
1248                              if( $versions = $DB->get_col( '
1249                                  SELECT plug_version FROM T_plugins
1250                                   WHERE plug_classname = '.$DB->quote($plugin_req[0]).'
1251                                       AND plug_status = "enabled"' ) )
1252                              {
1253                                  // Clean up version from CVS Revision prefix/suffix:

1254                                  $versions[] = $plugin_req[1];
1255                                  $clean_versions = preg_replace( array( '~^(CVS\s+)?\$'.'Revision:\s*~i', '~\s*\$$~' ), '', $versions );
1256                                  $clean_req_ver = array_pop($clean_versions);
1257                                  usort( $clean_versions, 'version_compare' );
1258                                  $clean_oldest_enabled = array_shift($clean_versions);
1259  
1260                                  if( version_compare( $clean_oldest_enabled, $clean_req_ver, '<' ) )
1261                                  { // at least one instance of the installed plugins is not the current version
1262                                      $msgs['error'][] = sprintf( T_( 'The plugin requires at least version %s of the plugin %s, but you have %s.' ), $plugin_req[1], $plugin_req[0], $clean_oldest_enabled );
1263                                  }
1264                              }
1265                              else
1266                              { // no plugin existing
1267                                  if( $class == 'recommends' )
1268                                  {
1269                                      $recommends[] = $plugin_req[0];
1270                                  }
1271                                  else
1272                                  {
1273                                      $requires[] = $plugin_req[0];
1274                                  }
1275                              }
1276                          }
1277  
1278                          if( ! empty( $requires ) )
1279                          {
1280                              $msgs['error'][] = sprintf( T_( 'The plugin requires the plugins: %s.' ), implode_with_and( $requires ) );
1281                          }
1282  
1283                          if( ! empty( $recommends ) )
1284                          {
1285                              $msgs['note'][] = sprintf( T_( 'The plugin recommends to install the plugins: %s.' ), implode_with_and( $recommends ) );
1286                          }
1287                          break;
1288  
1289  
1290                      case 'app_min':
1291                          // min b2evo version:

1292                          if( ! version_compare( $app_version, $type_params, '>=' ) )
1293                          {
1294                              if( $class == 'recommends' )
1295                              {
1296                                  $msgs['note'][] = sprintf( /* 1: recommened version; 2: application name (default "b2evolution"); 3: current application version */
1297                                      T_('The plugin recommends version %s of %s (%s is installed). Think about upgrading.'), $type_params, $app_name, $app_version );
1298                              }
1299                              else
1300                              {
1301                                  $msgs['error'][] = sprintf( /* 1: required version; 2: application name (default "b2evolution"); 3: current application version */
1302                                      T_('The plugin requires version %s of %s, but %s is installed.'), $type_params, $app_name, $app_version );
1303                              }
1304                          }
1305                          break;
1306  
1307  
1308                      case 'api_min':
1309                          // obsolete since 1.9:

1310                          continue;
1311  
1312  
1313                      default:
1314                          // Unknown depency type, throw an error:

1315                          $msgs['error'][] = sprintf( T_('Unknown dependency type (%s). This probably means that the plugin is not compatible and you have to upgrade your %s installation.'), $type, $app_name );
1316  
1317                  }
1318              }
1319          }
1320  
1321          return $msgs;
1322      }
1323  
1324  
1325      /**

1326       * Validate renderer list.

1327       *

1328       * @param array renderer codes ('default' will include all "opt-out"-ones)

1329       * @return array validated array of renderer codes

1330       */
1331  	function validate_renderer_list( $renderers = array('default') )
1332      {
1333          $this->load_plugins_table();
1334  
1335          $validated_renderers = array();
1336  
1337          $index = & $this->index_apply_rendering_codes;
1338  
1339          if( isset( $index['stealth'] ) )
1340          {
1341              // pre_dump( 'stealth:', $index['stealth'] );

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

1347              $validated_renderers = array_merge( $validated_renderers, $index['always'] );
1348          }
1349  
1350          if( isset( $index['opt-out'] ) )
1351          {
1352              foreach( $index['opt-out'] as $l_code )
1353              {
1354                  if( in_array( $l_code, $renderers ) // Option is activated
1355                      || in_array( 'default', $renderers ) ) // OR we're asking for default renderer set
1356                  {
1357                      // pre_dump( 'opt-out:', $l_code );

1358                      $validated_renderers[] = $l_code;
1359                  }
1360              }
1361          }
1362  
1363          if( isset( $index['opt-in'] ) )
1364          {
1365              foreach( $index['opt-in'] as $l_code )
1366              {
1367                  if( in_array( $l_code, $renderers ) ) // Option is activated
1368                  {
1369                      // pre_dump( 'opt-in:', $l_code );

1370                      $validated_renderers[] = $l_code;
1371                  }
1372              }
1373          }
1374          if( isset( $index['lazy'] ) )
1375          {
1376              foreach( $index['lazy'] as $l_code )
1377              {
1378                  if( in_array( $l_code, $renderers ) ) // Option is activated
1379                  {
1380                      // pre_dump( 'lazy:', $l_code );

1381                      $validated_renderers[] = $l_code;
1382                  }
1383              }
1384          }
1385  
1386          // Make sure there's no renderer code with a dot, as the list gets imploded by that when saved:

1387          foreach( $validated_renderers as $k => $l_code )
1388          {
1389              if( empty($l_code) || strpos( $l_code, '.' ) !== false )
1390              {
1391                  unset( $validated_renderers[$k] );
1392              }
1393              else
1394              { // remove the ones which are not enabled:
1395                  $Plugin = & $this->get_by_code($l_code);
1396                  if( ! $Plugin || $Plugin->status != 'enabled' )
1397                  {
1398                      unset( $validated_renderers[$k] );
1399                  }
1400              }
1401          }
1402  
1403          // echo 'validated Renderers: '.count( $validated_renderers );

1404          return $validated_renderers;
1405      }
1406  
1407  
1408      /**

1409       * Filter (post) contents by calling the relevant filter plugins.

1410       *

1411       * Works very much like render() except that it's called at insert/update time and BEFORE validation.

1412       * Gives an opportunity to do some serious cleanup on what the user has typed.

1413       *

1414       * This uses the lost of renderers, because filtering may need to work in conjunction with rendering,

1415       * e-g: code display: you want to filter out tags before validation and later you want to render color/fixed font.

1416       * For brute force filtering, use 'always' or 'stealth' modes.

1417       * @see Plugins::render()

1418       *

1419       * @param string content to render (by reference)

1420       * @param array renderer codes to use for opt-out, opt-in and lazy

1421       * @return string rendered content

1422       */
1423  	function filter_contents( & $title, & $content, $renderers )
1424      {
1425          // echo 'CALLING FILTERS: '.implode(',',$renderers);

1426  
1427          $params['title'] = & $title;
1428          $params['content'] = & $content;
1429  
1430          $filter_Plugins = $this->get_list_by_event( 'FilterItemContents' );
1431  
1432          foreach( $filter_Plugins as $loop_filter_Plugin )
1433          { // Go through whole list of renders
1434              // echo ' ',$loop_RendererPlugin->code, ':';

1435  
1436              switch( $loop_filter_Plugin->apply_rendering )
1437              {
1438                  case 'stealth':
1439                  case 'always':
1440                      // echo 'FORCED ';

1441                      $this->call_method( $loop_filter_Plugin->ID, 'FilterItemContents', $params );
1442                      break;
1443  
1444                  case 'opt-out':
1445                  case 'opt-in':
1446                  case 'lazy':
1447                      if( in_array( $loop_filter_Plugin->code, $renderers ) )
1448                      { // Option is activated
1449                          // echo 'OPT ';

1450                          $this->call_method( $loop_filter_Plugin->ID, 'FilterItemContents', $params );
1451                      }
1452                      // else echo 'NOOPT ';

1453                      break;
1454  
1455                  case 'never':
1456                      // echo 'NEVER ';

1457                      break;    // STOP, don't render, go to next renderer

1458              }
1459          }
1460  
1461          return $content;
1462      }
1463  
1464  
1465      /**

1466       * UnFilter (post) contents by calling the relevant filter plugins.

1467       *

1468       * This is the opposite of filter_content. It is used to restore some specifcs before editing text.

1469       * For example, this can be used to replace complex sequences of tags with a custome meta-tag,

1470       * e-g: <strong> can become <s> for convenient editing.

1471       *

1472       * This uses the list of renderers, because un/filtering may need to work in conjunction with rendering,

1473       * e-g: code display: you want to filter in/out tags before validation and later you want to render color/fixed font.

1474       * For brute force unfiltering, use 'always' or 'stealth' modes.

1475       * @see Plugins::render()

1476       * @see Plugins::filter()

1477       *

1478       * @todo fp> it would probably make sense to do the unfiltering in reverse order compared to filtering

1479       *

1480       * @param string title to render (by reference)

1481       * @param string content to render (by reference)

1482       * @param array renderer codes to use for opt-out, opt-in and lazy

1483       * @return string rendered content

1484       */
1485  	function unfilter_contents( & $title, & $content, $renderers )
1486      {
1487          // echo 'CALLING FILTERS: '.implode(',',$renderers);

1488  
1489          $params['title'] = & $title;
1490          $params['content'] = & $content;
1491  
1492          $filter_Plugins = $this->get_list_by_event( 'UnfilterItemContents' );
1493  
1494          // fp> TODO: reverse order

1495  
1496          foreach( $filter_Plugins as $loop_filter_Plugin )
1497          { // Go through whole list of renders
1498              // echo ' ',$loop_RendererPlugin->code, ':';

1499  
1500              switch( $loop_filter_Plugin->apply_rendering )
1501              {
1502                  case 'stealth':
1503                  case 'always':
1504                      // echo 'FORCED ';

1505                      $this->call_method( $loop_filter_Plugin->ID, 'UnfilterItemContents', $params );
1506                      break;
1507  
1508                  case 'opt-out':
1509                  case 'opt-in':
1510                  case 'lazy':
1511                      if( in_array( $loop_filter_Plugin->code, $renderers ) )
1512                      { // Option is activated
1513                          // echo 'OPT ';

1514                          $this->call_method( $loop_filter_Plugin->ID, 'UnfilterItemContents', $params );
1515                      }
1516                      // else echo 'NOOPT ';

1517                      break;
1518  
1519                  case 'never':
1520                      // echo 'NEVER ';

1521                      break;    // STOP, don't render, go to next renderer

1522              }
1523          }
1524  
1525          return $content;
1526      }
1527  
1528  }
1529  
1530  /*

1531   * $Log: _plugins_admin.class.php,v $

1532   * Revision 1.3  2007/07/24 23:29:25  blueyed

1533   * todo

1534   *

1535   * Revision 1.2  2007/07/04 23:37:29  blueyed

1536   * PluginUserSettingsEditAction gets triggered and therefor needs to be in the

1537   * list of "available events", so Plugins can actually hook there.

1538   *

1539   * Revision 1.1  2007/06/25 11:00:46  fplanque

1540   * MODULES (refactored MVC)

1541   *

1542   * Revision 1.41  2007/05/14 02:43:05  fplanque

1543   * Started renaming tables. There probably won't be a better time than 2.0.

1544   *

1545   * Revision 1.40  2007/04/26 00:11:08  fplanque

1546   * (c) 2007

1547   *

1548   * Revision 1.39  2007/04/05 22:57:33  fplanque

1549   * Added hook: UnfilterItemContents

1550   *

1551   * Revision 1.38  2007/03/31 22:46:47  fplanque

1552   * FilterItemContent event

1553   *

1554   * Revision 1.37  2007/03/12 14:07:08  waltercruz

1555   * Changing the WHERE 1 queries to boolean (WHERE 1=1) queries to satisfy the standarts

1556   *

1557   * Revision 1.36  2007/02/24 23:03:00  blueyed

1558   * MFB: Fixed "Unknown dependency type" error display

1559   *

1560   * Revision 1.35  2007/02/23 00:21:23  blueyed

1561   * Fixed Plugins::get_next() if the last Plugin got unregistered; Added AdminBeforeItemEditDelete hook

1562   *

1563   * Revision 1.34  2007/02/19 23:20:07  blueyed

1564   * Added plugin event SkinEndHtmlBody

1565   *

1566   * Revision 1.33  2007/02/18 23:19:28  blueyed

1567   * doc

1568   *

1569   * Revision 1.32  2007/02/18 20:52:38  blueyed

1570   * Load parent class

1571   *

1572   * Revision 1.31  2007/02/16 13:34:08  waltercruz

1573   * Changing double quotes to single quotes

1574   *

1575   * Revision 1.30  2007/01/28 23:58:46  blueyed

1576   * - Added hook CommentFormSent

1577   * - Re-ordered comment_post.php to: init, validate, process

1578   * - RegisterFormSent hook can now filter the form values in a clean way

1579   *

1580   * Revision 1.29  2007/01/20 23:43:12  blueyed

1581   * bugfix for new discover(): exclude subdirs that start with "_" now again

1582   *

1583   * Revision 1.28  2007/01/20 16:03:24  blueyed

1584   * Optimized discover()

1585   *

1586   * Revision 1.27  2007/01/15 19:28:29  blueyed

1587   * Small opt

1588   *

1589   * Revision 1.26  2007/01/14 18:15:51  blueyed

1590   * Nuked hackish $is_admin_class as per todo

1591   *

1592   * Revision 1.25  2007/01/13 14:57:28  blueyed

1593   * Removed $is_admin_class hack from load_events() by re-implementing (copying most of it) to Plugins_admin as per todo

1594   *

1595   * Revision 1.24  2007/01/12 05:14:42  fplanque

1596   * doc

1597   *

1598   * Revision 1.23  2007/01/09 01:00:51  blueyed

1599   * todo

1600   *

1601   * Revision 1.22  2007/01/09 00:53:53  blueyed

1602   * doc

1603   *

1604   * Revision 1.21  2007/01/07 19:39:44  fplanque

1605   * doc

1606   *

1607   * Revision 1.20  2007/01/07 05:26:01  fplanque

1608   * doc

1609   *

1610   * Revision 1.19  2006/12/28 23:20:40  fplanque

1611   * added plugin event for displaying comment form toolbars

1612   * used by smilies plugin

1613   *

1614   * Revision 1.18  2006/12/07 23:13:13  fplanque

1615   * @var needs to have only one argument: the variable type

1616   * Otherwise, I can't code!

1617   *

1618   * Revision 1.17  2006/12/04 00:18:53  fplanque

1619   * keeping the login hashing

1620   *

1621   * Revision 1.14  2006/12/01 20:46:25  blueyed

1622   * Moved Plugins::set_priority() to Plugins_admin class

1623   *

1624   * Revision 1.13  2006/12/01 20:44:01  blueyed

1625   * Moved Plugins::set_code() to Plugins_admin class

1626   *

1627   * Revision 1.12  2006/12/01 20:41:38  blueyed

1628   * Moved Plugins::uninstall() to Plugins_admin class

1629   *

1630   * Revision 1.11  2006/12/01 20:34:03  blueyed

1631   * Moved Plugins::get_apply_rendering_values() and Plugins::set_apply_rendering() to Plugins_admin class

1632   *

1633   * Revision 1.10  2006/12/01 20:19:15  blueyed

1634   * Moved Plugins::get_supported_events() to Plugins_admin class

1635   *

1636   * Revision 1.9  2006/12/01 20:13:24  blueyed

1637   * Moved Plugins::count_regs() to Plugins_admin class

1638   *

1639   * Revision 1.8  2006/12/01 20:11:24  blueyed

1640   * Renamed Plugins_admin::validate_list() to validate_renderer_list()

1641   *

1642   * Revision 1.7  2006/12/01 20:01:38  blueyed

1643   * Moved Plugins::validate_dependencies() to Plugins_admin class

1644   *

1645   * Revision 1.6  2006/12/01 19:46:42  blueyed

1646   * Moved Plugins::validate_list() to Plugins_admin class; added stub in Plugins, because at least the starrating_plugin uses it

1647   *

1648   * Revision 1.5  2006/12/01 19:16:00  blueyed

1649   * Moved Plugins::get_registered_events() to Plugins_admin class

1650   *

1651   * Revision 1.4  2006/12/01 18:18:22  blueyed

1652   * Moved Plugins::save_events() to Plugins_admin class

1653   *

1654   * Revision 1.3  2006/12/01 02:03:04  blueyed

1655   * Moved Plugins::set_event_status() to Plugins_admin

1656   *

1657   * Revision 1.2  2006/11/30 05:57:54  blueyed

1658   * Moved Plugins::install() and sort() galore to Plugins_admin

1659   *

1660   * Revision 1.1  2006/11/30 05:43:40  blueyed

1661   * Moved Plugins::discover() to Plugins_admin::discover(); Renamed Plugins_no_DB to Plugins_admin_no_DB (and deriving from Plugins_admin)

1662   */
1663  ?>


Généré le : Thu Nov 29 23:58:50 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics