| [ Index ] |
|
Code source de b2evolution 2.1.0-beta |
1 <?php 2 /** 3 * This file implements the PluginS class. 4 * 5 * This is where you can plug in some {@link Plugin plugins} :D 6 * 7 * This file is part of the evoCore framework - {@link http://evocore.net/} 8 * See also {@link http://sourceforge.net/projects/evocms/}. 9 * 10 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/} 11 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. 12 * 13 * {@internal License choice 14 * - If you have received this file as part of a package, please find the license.txt file in 15 * the same folder or the closest folder above for complete license terms. 16 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) 17 * then you must choose one of the following licenses before using the file: 18 * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php 19 * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php 20 * }} 21 * 22 * {@internal Open Source relicensing agreement: 23 * Daniel HAHLER grants Francois PLANQUE the right to license 24 * Daniel HAHLER's contributions to this file and the b2evolution project 25 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 26 * }} 27 * 28 * @package evocore 29 * 30 * {@internal Below is a list of authors who have contributed to design/coding of this file: }} 31 * @author fplanque: Francois PLANQUE - {@link http://fplanque.net/} 32 * @author blueyed: Daniel HAHLER 33 * 34 * @version $Id: _plugins.class.php,v 1.2 2007/09/22 22:11:18 fplanque Exp $ 35 */ 36 if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); 37 38 load_class('plugins/_plugin.class.php'); 39 40 41 /** 42 * Plugins Class 43 * 44 * This is where you can plug in some {@link Plugin plugins} :D 45 * 46 * @todo dh> Currently when a plugin goes into "broken" status (e.g. file not readable), it is "disabled" afterwards. 47 * This should rather remember the old status (e.g. "enabled") and make it enabled again. 48 * 49 * @package evocore 50 */ 51 class Plugins 52 { 53 /**#@+ 54 * @access private 55 */ 56 57 /** 58 * @var array of plugin_code => Plugin 59 */ 60 var $index_code_Plugins = array(); 61 62 /** 63 * @var array of plugin_ID => Plugin 64 */ 65 var $index_ID_Plugins = array(); 66 67 /** 68 * @see Plugins::load_events() 69 * @var array of event => plug_ID. IDs are sorted by priority. 70 */ 71 var $index_event_IDs = array(); 72 73 /** 74 * fp> does it cost that much to instantiate plugins right away, now that init is no longer in the constructor? 75 * @var array of plug_ID => DB row from T_plugins. Used to lazy-instantiate Plugins. 76 */ 77 var $index_ID_rows = array(); 78 79 /** 80 * fp> does it cost that much to instantiate plugins right away, now that init is no longer in the constructor? 81 * @var array of plug_code => plug_ID. Usedp to lazy-instantiate by code. 82 */ 83 var $index_code_ID = array(); 84 85 /** 86 * Cache Plugin codes by apply_rendering setting. 87 * @var array of apply_rendering => plug_code 88 */ 89 var $index_apply_rendering_codes = array(); 90 91 /** 92 * Path to plugins. 93 * 94 * The preferred method is to have a sub-directory for each plugin (named 95 * after the plugin's classname), but they can be supplied just in this 96 * directory. 97 */ 98 var $plugins_path; 99 100 /** 101 * Have we loaded the plugins table (T_plugins)? 102 * @var boolean 103 */ 104 var $loaded_plugins_table = false; 105 106 /** 107 * Current object index in {@link $sorted_IDs} array. 108 * @var integer 109 */ 110 var $current_idx = -1; 111 112 /** 113 * List of IDs, sorted. This gets used to lazy-instantiate a Plugin. 114 * 115 * @var array 116 */ 117 var $sorted_IDs = array(); 118 119 /** 120 * The smallest internal/auto-generated Plugin ID. 121 * @var integer 122 */ 123 var $smallest_internal_ID = 0; 124 125 /**#@-*/ 126 127 128 /**#@+ 129 * @access protected 130 */ 131 132 /** 133 * SQL to use in {@link load_plugins_table()}. Gets overwritten for {@link Plugins_admin}. 134 * @var string 135 * @static 136 */ 137 var $sql_load_plugins_table = ' 138 SELECT plug_ID, plug_priority, plug_classname, plug_code, plug_name, plug_shortdesc, plug_apply_rendering, plug_status, plug_version, plug_spam_weight 139 FROM T_plugins 140 WHERE plug_status = \'enabled\' 141 ORDER BY plug_priority, plug_classname'; 142 143 /**#@-*/ 144 145 146 /** 147 * Errors associated to plugins (during loading), indexed by plugin_ID and 148 * error class ("register"). 149 * 150 * @var array 151 */ 152 var $plugin_errors = array(); 153 154 155 /** 156 * Constructor. Sets {@link $plugins_path} and load events. 157 */ 158 function Plugins() 159 { 160 global $basepath, $plugins_subdir, $Timer; 161 162 // Set plugin path: 163 $this->plugins_path = $basepath.$plugins_subdir; 164 165 $Timer->resume( 'plugin_init' ); 166 167 // Load events for enabled plugins: 168 $this->load_events(); 169 170 $Timer->pause( 'plugin_init' ); 171 } 172 173 174 /** 175 * Get a list of available Plugin groups. 176 * 177 * @return array 178 */ 179 function get_plugin_groups() 180 { 181 $result = array(); 182 183 foreach( $this->sorted_IDs as $plugin_ID ) 184 { 185 $Plugin = & $this->get_by_ID( $plugin_ID ); 186 187 if( empty($Plugin->group) || in_array( $Plugin->group, $result ) ) 188 { 189 continue; 190 } 191 192 $result[] = $Plugin->group; 193 } 194 195 return $result; 196 } 197 198 199 /** 200 * Will return an array that contents are references to plugins that have the same group, regardless of the sub_group. 201 * 202 * @return array 203 */ 204 function get_Plugins_in_group( $group ) 205 { 206 $result = array(); 207 208 foreach( $this->sorted_IDs as $plugin_ID ) 209 { 210 $Plugin = & $this->get_by_ID( $plugin_ID ); 211 if( $Plugin->group == $group ) 212 { 213 $result[] = & $Plugin; 214 } 215 } 216 217 return $result; 218 } 219 220 221 /** 222 * Will return an array that contents are references to plugins that have the same group and sub_group. 223 * 224 * @return array 225 */ 226 function get_Plugins_in_sub_group( $group, $sub_group = '' ) 227 { 228 $result = array(); 229 230 foreach( $this->sorted_IDs as $plugin_ID ) 231 { 232 $Plugin = & $this->get_by_ID( $plugin_ID ); 233 if( $Plugin->group == $group && $Plugin->sub_group == $sub_group ) 234 { 235 $result[] = & $Plugin; 236 } 237 } 238 239 return $result; 240 } 241 242 243 /** 244 * Sets the status of a Plugin in DB and registers it into the internal indices when "enabled". 245 * Otherwise it gets unregistered, but only when we're not in {@link Plugins_admin}, because we 246 * want to keep it in then in our indices. 247 * 248 * {@internal 249 * Note: this should probably always get called on the {@link $Plugins} object, 250 * not {@link $admin_Plugins}. 251 * }} 252 * 253 * @param Plugin 254 * @param string New status ("enabled", "disabled", "needs_config", "broken") 255 */ 256 function set_Plugin_status( & $Plugin, $status ) 257 { 258 global $DB, $Debuglog; 259 260 $DB->query( "UPDATE T_plugins SET plug_status = '".$status."' WHERE plug_ID = '".$Plugin->ID."'" ); 261 262 if( $status == 'enabled' ) 263 { // Reload plugins tables, which includes the plugin in further requests 264 $this->loaded_plugins_table = false; 265 $this->load_plugins_table(); 266 $this->load_events(); 267 } 268 else 269 { 270 // Notify the plugin that it has been disabled: 271 $Plugin->BeforeDisable(); 272 273 $this->unregister( $Plugin ); 274 } 275 276 $Plugin->status = $status; 277 278 $Debuglog->add( 'Set status for plugin #'.$Plugin->ID.' to "'.$status.'"!', 'plugins' ); 279 } 280 281 282 /** 283 * Register a plugin. 284 * 285 * This handles the indexes, dynamically unregisters a Plugin that does not exist (anymore) 286 * and instantiates the Plugin's (User)Settings. 287 * 288 * @access protected 289 * @param string name of plugin class to instantiate and register 290 * @param int ID in database (0 if not installed) 291 * @param int Priority in database (-1 to keep default) 292 * @param array When should rendering apply? (NULL to keep default) 293 * @param string Path of the .php class file of the plugin. 294 * @param boolean Must the plugin exist (classfile_path and classname)? 295 * This is used internally to be able to unregister a non-existing plugin. 296 * @return Plugin Plugin ref to newly created plugin; string in case of error 297 */ 298 function & register( $classname, $ID = 0, $priority = -1, $apply_rendering = NULL, $classfile_path = NULL, $must_exists = true ) 299 { 300 global $Debuglog, $Messages, $Timer; 301 302 if( $ID && isset($this->index_ID_Plugins[$ID]) ) 303 { 304 debug_die( 'Tried to register already registered Plugin (ID '.$ID.')' ); // should never happen! 305 } 306 307 $Timer->resume( 'plugins_register' ); 308 309 if( empty($classfile_path) ) 310 { 311 $plugin_filename = '_'.str_replace( '_plugin', '.plugin', $classname ).'.php'; 312 // Try <plug_classname>/<plug_classname>.php (subfolder) first 313 $classfile_path = $this->plugins_path.$classname.'/'.$plugin_filename; 314 315 if( ! is_readable( $classfile_path ) ) 316 { // Look directly in $plugins_path 317 $classfile_path = $this->plugins_path.$plugin_filename; 318 } 319 } 320 321 $Debuglog->add( 'register(): '.$classname.', ID: '.$ID.', priority: '.$priority.', classfile_path: ['.$classfile_path.']', 'plugins' ); 322 323 if( ! is_readable( $classfile_path ) ) 324 { // Plugin file not found! 325 if( $must_exists ) 326 { 327 $r = 'Plugin class file ['.rel_path_to_base($classfile_path).'] is not readable!'; 328 $Debuglog->add( $r, array( 'plugins', 'error' ) ); 329 330 // Get the Plugin object (must not exist) 331 $Plugin = & $this->register( $classname, $ID, $priority, $apply_rendering, $classfile_path, false ); 332 $this->plugin_errors[$ID]['register'] = $r; 333 $this->set_Plugin_status( $Plugin, 'broken' ); 334 335 // unregister: 336 if( $this->unregister( $Plugin ) ) 337 { 338 $Debuglog->add( 'Unregistered plugin ['.$classname.']!', array( 'plugins', 'error' ) ); 339 } 340 else 341 { 342 $Plugin->name = $Plugin->classname; // use the classname instead of "unnamed plugin" 343 $Timer->pause( 'plugins_register' ); 344 return $Plugin; 345 } 346 347 $Timer->pause( 'plugins_register' ); 348 return $r; 349 } 350 } 351 elseif( ! class_exists($classname) ) // If there are several copies of one plugin for example.. 352 { 353 $Debuglog->add( 'Loading plugin class file: '.$classname, 'plugins' ); 354 require_once $classfile_path; 355 } 356 357 if( ! class_exists( $classname ) ) 358 { // the given class does not exist 359 if( $must_exists ) 360 { 361 $r = sprintf( 'Plugin class for «%s» in file «%s» not defined.', $classname, rel_path_to_base($classfile_path) ); 362 $Debuglog->add( $r, array( 'plugins', 'error' ) ); 363 364 // Get the Plugin object (must not exist) fp> why is this recursive? 365 $Plugin = & $this->register( $classname, $ID, $priority, $apply_rendering, $classfile_path, false ); 366 $this->plugin_errors[$ID]['register'] = $r; 367 $this->set_Plugin_status( $Plugin, 'broken' ); 368 369 // unregister: 370 if( $this->unregister( $Plugin ) ) 371 { 372 $Debuglog->add( 'Unregistered plugin ['.$classname.']!', array( 'plugins', 'error' ) ); 373 } 374 else 375 { 376 $Plugin->name = $Plugin->classname; // use the classname instead of "unnamed plugin" 377 $Timer->pause( 'plugins_register' ); 378 return $Plugin; 379 } 380 381 $Timer->pause( 'plugins_register' ); 382 return $r; 383 } 384 else 385 { 386 $Plugin = new Plugin; // COPY ! 387 $Plugin->code = NULL; 388 $Plugin->apply_rendering = 'never'; 389 } 390 } 391 else 392 { 393 $Plugin = new $classname; // COPY ! 394 } 395 396 $Plugin->classfile_path = $classfile_path; 397 398 // Tell him his ID :) 399 if( $ID == 0 ) 400 { 401 $Plugin->ID = --$this->smallest_internal_ID; 402 } 403 else 404 { 405 $Plugin->ID = $ID; 406 407 if( $ID > 0 ) 408 { // Properties from T_plugins 409 // Code 410 $Plugin->code = $this->index_ID_rows[$Plugin->ID]['plug_code']; 411 // Status 412 $Plugin->status = $this->index_ID_rows[$Plugin->ID]['plug_status']; 413 } 414 } 415 // Tell him his name :) 416 $Plugin->classname = $classname; 417 // Tell him his priority: 418 if( $priority > -1 ) { $Plugin->priority = $priority; } 419 420 if( isset($apply_rendering) ) 421 { 422 $Plugin->apply_rendering = $apply_rendering; 423 } 424 425 if( empty($Plugin->name) ) 426 { 427 $Plugin->name = $Plugin->classname; 428 } 429 430 // Memorizes Plugin in code hash array: 431 if( ! empty($this->index_code_ID[ $Plugin->code ]) && $this->index_code_ID[ $Plugin->code ] != $Plugin->ID ) 432 { // The plugin's default code is already in use! 433 $Plugin->code = NULL; 434 } 435 else 436 { 437 $this->index_code_Plugins[ $Plugin->code ] = & $Plugin; 438 $this->index_code_ID[ $Plugin->code ] = & $Plugin->ID; 439 } 440 $this->index_ID_Plugins[ $Plugin->ID ] = & $Plugin; 441 442 if( ! in_array( $Plugin->ID, $this->sorted_IDs ) ) // TODO: check if this extra check is required.. 443 { // not in our sort index yet 444 $this->sorted_IDs[] = & $Plugin->ID; 445 } 446 447 // Stuff only for real/existing Plugins (which exist in DB): 448 if( $Plugin->ID > 0 ) 449 { 450 // Instantiate the Plugins (User)Settings members: 451 $this->init_settings( $Plugin ); 452 453 $tmp_params = array( 'db_row' => $this->index_ID_rows[$Plugin->ID], 'is_installed' => true ); 454 if( $Plugin->PluginInit( $tmp_params ) === false && $this->unregister( $Plugin ) ) 455 { 456 $Debuglog->add( 'Unregistered plugin, because PluginInit returned false.', 'plugins' ); 457 $Plugin = ''; 458 } 459 // Version check: 460 elseif( $Plugin->version != $this->index_ID_rows[$Plugin->ID]['plug_version'] && $must_exists ) 461 { // Version has changed since installation or last update 462 $db_deltas = array(); 463 464 // Tell the Plugin that we've detected a version change: 465 $tmp_params = array( 'old_version'=>$this->index_ID_rows[$Plugin->ID]['plug_version'], 'db_row'=>$this->index_ID_rows[$Plugin->ID] ); 466 467 if( $this->call_method( $Plugin->ID, 'PluginVersionChanged', $tmp_params ) === false ) 468 { 469 $Debuglog->add( 'Set plugin status to "needs_config", because PluginVersionChanged returned false.', 'plugins' ); 470 $this->set_Plugin_status( $Plugin, 'needs_config' ); 471 if( $this->unregister( $Plugin ) ) 472 { // only unregister the Plugin, if it's not the admin list's class: 473 $Plugin = ''; 474 } 475 } 476 else 477 { 478 // Check if there are DB deltas required (also when downgrading!), without excluding any query type: 479 load_class('_core/model/db/_upgrade.funcs.php'); 480 $db_deltas = db_delta( $Plugin->GetDbLayout() ); 481 482 if( empty($db_deltas) ) 483 { // No DB changes needed, update (bump or decrease) the version 484 global $DB; 485 $Plugins_admin = & get_Cache('Plugins_admin'); 486 487 // Update version in DB: 488 $DB->query( ' 489 UPDATE T_plugins 490 SET plug_version = '.$DB->quote($Plugin->version).' 491 WHERE plug_ID = '.$Plugin->ID ); 492 493 // Update "plug_version" in indexes: 494 $this->index_ID_rows[$Plugin->ID]['plug_version'] = $Plugin->version; 495 if( isset($Plugins_admin->index_ID_rows[$Plugin->ID]) ) 496 { 497 $Plugins_admin->index_ID_rows[$Plugin->ID]['plug_version'] = $Plugin->version; 498 } 499 500 // Remove any prerenderered content for the Plugins renderer code: 501 if( ! empty($Plugin->code) ) 502 { 503 $DB->query( ' 504 DELETE FROM T_items__prerendering 505 WHERE itpr_renderers REGEXP "^(.*\.)?'.$DB->escape($Plugin->code).'(\..*)?$"' ); 506 } 507 508 // Detect new events (and delete obsolete ones - in case of downgrade): 509 if( $Plugins_admin->save_events( $Plugin, array() ) ) 510 { 511 $this->load_events(); // re-load for the current request 512 } 513 514 $Debuglog->add( 'Version for '.$Plugin->classname.' changed from '.$this->index_ID_rows[$Plugin->ID]['plug_version'].' to '.$Plugin->version, 'plugins' ); 515 } 516 else 517 { // If there are DB schema changes needed, set the Plugin status to "needs_config" 518 519 // TODO: automatic upgrade in some cases (e.g. according to query types)? 520 521 $this->set_Plugin_status( $Plugin, 'needs_config' ); 522 $Debuglog->add( 'Set plugin status to "needs_config", because version DB schema needs upgrade.', 'plugins' ); 523 524 if( $this->unregister( $Plugin ) ) 525 { // only unregister the Plugin, if it's not the admin list's class: 526 $Plugin = ''; 527 } 528 } 529 } 530 } 531 532 if( $Plugin && isset($this->index_ID_rows[$Plugin->ID]) ) // may have been unregistered above 533 { 534 if( $this->index_ID_rows[$Plugin->ID]['plug_name'] !== NULL ) 535 { 536 $Plugin->name = $this->index_ID_rows[$Plugin->ID]['plug_name']; 537 } 538 if( $this->index_ID_rows[$Plugin->ID]['plug_shortdesc'] !== NULL ) 539 { 540 $Plugin->short_desc = $this->index_ID_rows[$Plugin->ID]['plug_shortdesc']; 541 } 542 } 543 } 544 else 545 { // This gets called for non-installed Plugins: 546 // Instantiate the Plugins (User)Settings members: 547 $this->init_settings( $Plugin ); 548 549 $tmp_params = array( 'db_row' => array(), 'is_installed' => false ); 550 if( $Plugin->PluginInit( $tmp_params ) === false && $this->unregister( $Plugin ) ) 551 { 552 $Debuglog->add( 'Unregistered plugin, because PluginInit returned false.', 'plugins' ); 553 $Plugin = ''; 554 } 555 } 556 557 $Timer->pause( 'plugins_register' ); 558 559 return $Plugin; 560 } 561 562 563 /** 564 * Un-register a plugin. 565 * 566 * This does not un-install it from DB, just from the internal indexes. 567 * 568 * @param Plugin 569 * @param boolean Force unregistering (ignored here, but used in Plugins_admin) 570 * @return boolean True, if unregistered 571 */ 572 function unregister( & $Plugin, $force = false ) 573 { 574 global $Debuglog; 575 576 $this->forget_events( $Plugin->ID ); 577 578 // Unset apply-rendering index: 579 if( isset( $this->index_apply_rendering_codes[ $Plugin->apply_rendering ] ) ) 580 { 581 while( ( $key = array_search( $Plugin->code, $this->index_apply_rendering_codes[$Plugin->apply_rendering] ) ) !== false ) 582 { 583 unset( $this->index_apply_rendering_codes[$Plugin->apply_rendering][$key] ); 584 } 585 } 586 587 unset( $this->index_code_Plugins[ $Plugin->code ] ); 588 unset( $this->index_ID_Plugins[ $Plugin->ID ] ); 589 590 if( isset($this->index_ID_rows[ $Plugin->ID ]) ) 591 { // It has an associated DB row (load_plugins_table() was called) 592 unset($this->index_ID_rows[ $Plugin->ID ]); 593 } 594 595 $sort_key = array_search( $Plugin->ID, $this->sorted_IDs ); 596 if( $sort_key === false ) 597 { // this may happen if a Plugin has unregistered itself 598 $Debuglog->add( 'Tried to unregister not-installed plugin (not in $sorted_IDs)!', 'plugins' ); 599 return false; 600 } 601 unset( $this->sorted_IDs[$sort_key] ); 602 $this->sorted_IDs = array_values( $this->sorted_IDs ); 603 604 if( $this->current_idx >= $sort_key ) 605 { // We have removed a file before or at the $sort_key'th position 606 $this->current_idx--; 607 } 608 609 return true; 610 } 611 612 613 /** 614 * Forget the events a Plugin has registered. 615 * 616 * This gets used when {@link unregister() unregistering} a Plugin or if 617 * {@link Plugin::PluginInit()} returned false, which means 618 * "do not use it for subsequent events in the request". 619 * 620 * @param integer Plugin ID 621 */ 622 function forget_events( $plugin_ID ) 623 { 624 // Forget events: 625 foreach( array_keys($this->index_event_IDs) as $l_event ) 626 { 627 while( ($key = array_search( $plugin_ID, $this->index_event_IDs[$l_event] )) !== false ) 628 { 629 unset( $this->index_event_IDs[$l_event][$key] ); 630 } 631 } 632 } 633 634 635 /** 636 * Init {@link Plugin::$Settings} and {@link Plugin::$UserSettings}, either by 637 * unsetting them for PHP5's overloading or instantiating them for PHP4. 638 * 639 * @param Plugin 640 */ 641 function init_settings( & $Plugin ) 642 { 643 if( version_compare( PHP_VERSION, '5.1', '>=' ) ) 644 { // we use overloading for PHP5, therefor the member has to be unset: 645 // Note: this is somehow buggy at least in PHP 5.0.5, therefor we use it from 5.1 on. 646 // see http://forums.b2evolution.net/viewtopic.php?p=49031#49031 647 unset( $Plugin->Settings ); 648 unset( $Plugin->UserSettings ); 649 650 // Nothing to do here, will get called through Plugin::__get() when accessed 651 return; 652 } 653 654 // PHP < 5.1: instantiate now: 655 $this->instantiate_Settings( $Plugin, 'Settings' ); 656 $this->instantiate_Settings( $Plugin, 'UserSettings' ); 657 } 658 659 660 /** 661 * Instantiate Settings object (class {@link PluginSettings}) for the given plugin. 662 * 663 * The plugin must provide setting definitions (through {@link Plugin::GetDefaultSettings()} 664 * OR {@link Plugin::GetDefaultUserSettings()}). 665 * 666 * @param Plugin 667 * @param string settings type: "Settings" or "UserSettings" 668 * @return boolean NULL, if no Settings 669 */ 670 function instantiate_Settings( & $Plugin, $set_type ) 671 { 672 global $Debuglog, $Timer; 673 674 $Timer->resume( 'plugins_inst_'.$set_type ); 675 676 // call Plugin::GetDefaultSettings() or Plugin::GetDefaultUserSettings(): 677 $defaults = $this->call_method( $Plugin->ID, 'GetDefault'.$set_type, $params = array('for_editing'=>false) ); 678 679 if( empty($defaults) ) 680 { // No settings, no need to instantiate. 681 $Timer->pause( 'plugins_inst_'.$set_type ); 682 return NULL; 683 } 684 685 if( ! is_array($defaults) ) 686 { // invalid data 687 $Debuglog->add( $Plugin->classname.'::GetDefault'.$set_type.'() did not return array!', array('plugins', 'error') ); 688 return NULL; // fp> correct me if I'm wrong. 689 } 690 691 if( $set_type == 'UserSettings' ) 692 { // User specific settings: 693 load_class('plugins/model/_pluginusersettings.class.php'); 694 695 $Plugin->UserSettings = new PluginUserSettings( $Plugin->ID ); 696 697 $set_Obj = & $Plugin->UserSettings; 698 } 699 else 700 { // Global settings: 701 load_class('plugins/model/_pluginsettings.class.php'); 702 703 $Plugin->Settings = new PluginSettings( $Plugin->ID ); 704 705 $set_Obj = & $Plugin->Settings; 706 } 707 708 // Register default values: 709 foreach( $defaults as $l_name => $l_meta ) 710 { 711 if( isset($l_meta['layout']) ) 712 { // Skip non-value entries 713 continue; 714 } 715 716 // Register settings as _defaults into Settings: 717 if( isset($l_meta['defaultvalue']) ) 718 { 719 $set_Obj->_defaults[$l_name] = $l_meta['defaultvalue']; 720 } 721 elseif( isset( $l_meta['type'] ) && $l_meta['type'] == 'array' ) 722 { 723 $set_Obj->_defaults[$l_name] = array(); 724 } 725 else 726 { 727 $set_Obj->_defaults[$l_name] = ''; 728 } 729 } 730 731 $Timer->pause( 'plugins_inst_'.$set_type ); 732 733 return true; 734 } 735 736 737 /** 738 * Load plugins table and rewind iterator used by {@link get_next()}. 739 */ 740 function restart() 741 { 742 $this->load_plugins_table(); 743 744 $this->current_idx = -1; 745 } 746 747 748 /** 749 * Get next plugin in the list. 750 * 751 * NOTE: You'll have to call {@link restart()} or {@link load_plugins_table()} 752 * before using it. 753 * 754 * @return Plugin (false if no more plugin). 755 */ 756 function & get_next() 757 { 758 global $Debuglog; 759 760 ++$this->current_idx; 761 762 $Debuglog->add( 'get_next() ('.$this->current_idx.')..', 'plugins' ); 763 764 if( isset($this->sorted_IDs[$this->current_idx]) ) 765 { 766 $Plugin = & $this->get_by_ID( $this->sorted_IDs[$this->current_idx] ); 767 768 if( ! $Plugin ) 769 { // recurse until we've been through whole $sorted_IDs! 770 return $this->get_next(); 771 } 772 773 $Debuglog->add( 'return: '.$Plugin->classname.' ('.$Plugin->ID.')', 'plugins' ); 774 return $Plugin; 775 } 776 else 777 { 778 $Debuglog->add( 'return: false', 'plugins' ); 779 --$this->current_idx; 780 $r = false; 781 return $r; 782 } 783 } 784 785 786 /** 787 * Stop propagation of events to next plugins in {@link trigger_event()}. 788 */ 789 function stop_propagation() 790 { 791 $this->_stop_propagation = true; 792 } 793 794 795 /** 796 * Call all plugins for a given event. 797 * 798 * @param string event name, see {@link Plugins_admin::get_supported_events()} 799 * @param array Associative array of parameters for the Plugin 800 * @return boolean True, if at least one plugin has been called. 801 */ 802 function trigger_event( $event, $params = array() ) 803 { 804 global $Debuglog; 805 806 $Debuglog->add( 'Trigger event '.$event, 'plugins' ); 807 808 if( empty($this->index_event_IDs[$event]) ) 809 { // No events registered 810 $Debuglog->add( 'No registered plugins.', 'plugins' ); 811 return false; 812 } 813 814 $Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' ); 815 816 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 817 { 818 $this->call_method( $l_plugin_ID, $event, $params ); 819 820 if( ! empty($this->_stop_propagation) ) 821 { // A plugin has requested to stop propagation. 822 $this->_stop_propagation = false; 823 break; 824 } 825 } 826 return true; 827 } 828 829 830 /** 831 * Call all plugins for a given event, until the first one returns true. 832 * 833 * @param string event name, see {@link Plugins_admin::get_supported_events()} 834 * @param array Associative array of parameters for the Plugin 835 * @return array The (modified) params array with key "plugin_ID" set to the last called plugin; 836 * Empty array if no Plugin returned true or no Plugin has this event registered. 837 */ 838 function trigger_event_first_true( $event, $params = NULL ) 839 { 840 global $Debuglog; 841 842 $Debuglog->add( 'Trigger event '.$event.' (first true)', 'plugins' ); 843 844 if( empty($this->index_event_IDs[$event]) ) 845 { // No events registered 846 $Debuglog->add( 'No registered plugins.', 'plugins' ); 847 return array(); 848 } 849 850 $Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' ); 851 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 852 { 853 $r = $this->call_method( $l_plugin_ID, $event, $params ); 854 if( $r === true ) 855 { 856 $Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned true!', 'plugins' ); 857 $params['plugin_ID'] = & $l_plugin_ID; 858 return $params; 859 } 860 } 861 return array(); 862 } 863 864 865 /** 866 * Call all plugins for a given event, until the first one returns false. 867 * 868 * @param string event name, see {@link Plugins_admin::get_supported_events()} 869 * @param array Associative array of parameters for the Plugin 870 * @return array The (modified) params array with key "plugin_ID" set to the last called plugin; 871 * Empty array if no Plugin returned true or no Plugin has this event registered. 872 */ 873 function trigger_event_first_false( $event, $params = NULL ) 874 { 875 global $Debuglog; 876 877 $Debuglog->add( 'Trigger event '.$event.' (first false)', 'plugins' ); 878 879 if( empty($this->index_event_IDs[$event]) ) 880 { // No events registered 881 $Debuglog->add( 'No registered plugins.', 'plugins' ); 882 return array(); 883 } 884 885 $Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' ); 886 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 887 { 888 $r = $this->call_method( $l_plugin_ID, $event, $params ); 889 if( $r === false ) 890 { 891 $Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned false!', 'plugins' ); 892 $params['plugin_ID'] = & $l_plugin_ID; 893 return $params; 894 } 895 } 896 return array(); 897 } 898 899 900 /** 901 * Call all plugins for a given event, until the first one returns a value 902 * (not NULL) (and $search is fulfilled, if given). 903 * 904 * @param string event name, see {@link Plugins_admin::get_supported_events()} 905 * @param array|NULL Associative array of parameters for the Plugin 906 * @param array|NULL If provided, the return value gets checks against this criteria. 907 * Can be: 908 * - ( 'in_array' => 'needle' ) 909 * @return array The (modified) params array with key "plugin_ID" set to the last called plugin 910 * and 'plugin_return' set to the return value; 911 * Empty array if no Plugin returned true or no Plugin has this event registered. 912 */ 913 function trigger_event_first_return( $event, $params = NULL, $search = NULL ) 914 { 915 global $Debuglog; 916 917 $Debuglog->add( 'Trigger event '.$event.' (first return)', 'plugins' ); 918 919 if( empty($this->index_event_IDs[$event]) ) 920 { // No events registered 921 $Debuglog->add( 'No registered plugins.', 'plugins' ); 922 return array(); 923 } 924 925 $Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' ); 926 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 927 { 928 $r = $this->call_method( $l_plugin_ID, $event, $params ); 929 if( isset($r) ) 930 { 931 if( isset($search) ) 932 { // Apply $search: 933 foreach( $search as $k => $v ) 934 { // Check search criterias and continue if it does not match: 935 switch( $k ) 936 { 937 case 'in_array': 938 if( ! in_array( $v, $r ) ) 939 { 940 continue 3; // continue in main foreach loop 941 } 942 break; 943 default: 944 debug_die('Invalid search criteria in Plugins::trigger_event_first_return / '.$k); 945 } 946 } 947 } 948 $Debuglog->add( 'Plugin ID '.$l_plugin_ID.' returned '.( $r ? 'true' : 'false' ).'!', 'plugins' ); 949 $params['plugin_return'] = $r; 950 $params['plugin_ID'] = & $l_plugin_ID; 951 return $params; 952 } 953 } 954 return array(); 955 } 956 957 958 /** 959 * Trigger an event and return an index of params. 960 * 961 * This is handy to collect return values from all plugins hooking an event. 962 * 963 * @param string Event name, see {@link Plugins_admin::get_supported_events()} 964 * @param array Associative array of parameters for the Plugin 965 * @param string Index of $params that should get returned 966 * @return mixed The requested index of $params 967 */ 968 function get_trigger_event( $event, $params = NULL, $get = 'data' ) 969 { 970 $params[$get] = & $params[$get]; // make it a reference, so it can get changed 971 972 $this->trigger_event( $event, $params ); 973 974 return $params[$get]; 975 } 976 977 978 /** 979 * The same as {@link get_trigger_event()}, but stop when the first Plugin returns true. 980 * 981 * @param string Event name, see {@link Plugins_admin::get_supported_events()} 982 * @param array Associative array of parameters for the Plugin 983 * @param string Index of $params that should get returned 984 * @return mixed The requested index of $params 985 */ 986 function get_trigger_event_first_true( $event, $params = NULL, $get = 'data' ) 987 { 988 $params[$get] = & $params[$get]; // make it a reference, so it can get changed 989 990 $this->trigger_event_first_true( $event, $params ); 991 992 return $params[$get]; 993 } 994 995 996 /** 997 * Trigger an event and return the first return value of a plugin. 998 * 999 * @param string Event name, see {@link Plugins_admin::get_supported_events()} 1000 * @param array Associative array of parameters for the Plugin 1001 * @return mixed NULL if no Plugin returned something or the return value of the first Plugin 1002 */ 1003 function get_trigger_event_first_return( $event, $params = NULL ) 1004 { 1005 $r = $this->trigger_event_first_return( $event, $params ); 1006 1007 if( ! isset($r['plugin_return']) ) 1008 { 1009 return NULL; 1010 } 1011 1012 return $r['plugin_return']; 1013 } 1014 1015 1016 /** 1017 * Trigger an event and return an array of all return values of the 1018 * relevant plugins. 1019 * 1020 * @param string Event name, see {@link Plugins_admin::get_supported_events()} 1021 * @param array Associative array of parameters for the Plugin 1022 * @param boolean Ignore {@link empty() empty} return values? 1023 * @return array List of return values, indexed by Plugin ID 1024 */ 1025 function trigger_collect( $event, $params = NULL, $ignore_empty = true ) 1026 { 1027 if( empty($this->index_event_IDs[$event]) ) 1028 { 1029 return array(); 1030 } 1031 1032 $r = array(); 1033 foreach( $this->index_event_IDs[$event] as $p_ID ) 1034 { 1035 $sub_r = $this->call_method_if_active( $p_ID, $event, $params ); 1036 1037 if( $ignore_empty && empty($sub_r) ) 1038 { 1039 continue; 1040 } 1041 1042 $r[$p_ID] = $sub_r; 1043 } 1044 1045 return $r; 1046 } 1047 1048 1049 /** 1050 * Trigger a karma collecting event in order to get Karma percentage. 1051 * 1052 * @param string Event 1053 * @param array Params to the event 1054 * @return integer|NULL Spam Karma (-100 - 100); "100" means "absolutely spam"; NULL if no plugin gave us a karma value 1055 */ 1056 function trigger_karma_collect( $event, $params ) 1057 { 1058 global $Debuglog; 1059 1060 $karma_abs = NULL; 1061 $karma_divider = 0; // total of the "spam detection relevance weight" 1062 1063 $Debuglog->add( 'Trigger karma collect event '.$event, 'plugins' ); 1064 1065 if( empty($this->index_event_IDs[$event]) ) 1066 { // No events registered 1067 $Debuglog->add( 'No registered plugins.', 'plugins' ); 1068 return NULL; 1069 } 1070 1071 $this->load_plugins_table(); // We need index_ID_rows below 1072 1073 $Debuglog->add( 'Registered plugin IDs: '.implode( ', ', $this->index_event_IDs[$event]), 'plugins' ); 1074 1075 $count_plugins = 0; 1076 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 1077 { 1078 $plugin_weight = $this->index_ID_rows[$l_plugin_ID]['plug_spam_weight']; 1079 1080 if( $plugin_weight < 1 ) 1081 { 1082 $Debuglog->add( 'Skipping plugin #'.$l_plugin_ID.', because is has weight '.$plugin_weight.'.', 'plugins' ); 1083 continue; 1084 } 1085 1086 $params['cur_karma'] = ( $karma_divider ? round($karma_abs / $karma_divider) : NULL ); 1087 $params['cur_karma_abs'] = $karma_abs; 1088 $params['cur_karma_divider'] = $karma_divider; 1089 $params['cur_count_plugins'] = $count_plugins; 1090 1091 // Call the plugin: 1092 $plugin_karma = $this->call_method( $l_plugin_ID, $event, $params ); 1093 1094 if( ! is_numeric( $plugin_karma ) ) 1095 { 1096 continue; 1097 } 1098 1099 $count_plugins++; 1100 1101 if( $plugin_karma > 100 ) 1102 { 1103 $plugin_karma = 100; 1104 } 1105 elseif( $plugin_karma < -100 ) 1106 { 1107 $plugin_karma = -100; 1108 } 1109 1110 $karma_abs += ( $plugin_karma * $plugin_weight ); 1111 $karma_divider += $plugin_weight; 1112 1113 if( ! empty($this->_stop_propagation) ) 1114 { 1115 $this->_stop_propagation = false; 1116 break; 1117 } 1118 } 1119 1120 if( ! $karma_divider ) 1121 { 1122 return NULL; 1123 } 1124 1125 $karma = round($karma_abs / $karma_divider); 1126 1127 if( $karma > 100 ) 1128 { 1129 $karma = 100; 1130 } 1131 elseif( $karma < -100 ) 1132 { 1133 $karma = -100; 1134 } 1135 1136 return $karma; 1137 } 1138 1139 1140 /** 1141 * Call a method on a Plugin. 1142 * 1143 * This makes sure that the Timer for the Plugin gets resumed. 1144 * 1145 * @param integer Plugin ID 1146 * @param string Method name. 1147 * @param array Params (by reference). 1148 * @return NULL|mixed Return value of the plugin's method call or NULL if no such method. 1149 */ 1150 function call_method( $plugin_ID, $method, & $params ) 1151 { 1152 global $Timer, $debug, $Debuglog; 1153 1154 $Plugin = & $this->get_by_ID( $plugin_ID ); 1155 1156 if( ! method_exists( $Plugin, $method ) ) 1157 { 1158 return NULL; 1159 } 1160 1161 if( $debug ) 1162 { 1163 /* 1164 // Note: this is commented out, because $debug_params gets not dumped anymore (last line of this block) 1165 // Hide passwords from Debuglog! 1166 // Clone/copy (references!): 1167 $debug_params = array(); 1168 foreach( $params as $k => $v ) 1169 { 1170 $debug_params[$k] = $v; 1171 } 1172 if( isset($debug_params['pass']) ) 1173 { 1174 $debug_params['pass'] = '-hidden-'; 1175 } 1176 if( isset($debug_params['pass_md5']) ) 1177 { 1178 $debug_params['pass_md5'] = '-hidden-'; 1179 } 1180 $Debuglog->add( 'Calling '.$Plugin->classname.'(#'.$Plugin->ID.')->'.$method.'( '.htmlspecialchars(var_export( $debug_params, true )).' )', 'plugins' ); 1181 */ 1182 $Debuglog->add( 'Calling '.$Plugin->classname.'(#'.$Plugin->ID.')->'.$method.'( )', 'plugins' ); 1183 } 1184 1185 $Timer->resume( $Plugin->classname.'_(#'.$Plugin->ID.')' ); 1186 $r = $Plugin->$method( $params ); 1187 $Timer->pause( $Plugin->classname.'_(#'.$Plugin->ID.')' ); 1188 1189 return $r; 1190 } 1191 1192 1193 /** 1194 * Call a method on a Plugin if it is not deactivated. 1195 * 1196 * This is a wrapper around {@link call_method()}. 1197 * 1198 * fp> why doesn't call_method always check if it's deactivated? 1199 * 1200 * @param integer Plugin ID 1201 * @param string Method name. 1202 * @param array Params (by reference). 1203 * @return NULL|mixed Return value of the plugin's method call or NULL if no such method (or inactive). 1204 */ 1205 function call_method_if_active( $plugin_ID, $method, & $params ) 1206 { 1207 if( ! $this->has_event($plugin_ID, $method) ) 1208 { 1209 return NULL; 1210 } 1211 1212 return $this->call_method( $plugin_ID, $method, $params ); 1213 } 1214 1215 1216 /** 1217 * Call a specific plugin by its code. 1218 * 1219 * This will call the SkinTag event handler. 1220 * 1221 * @param string plugin code 1222 * @param array Associative array of parameters (gets passed to the plugin) 1223 * @return boolean 1224 */ 1225 function call_by_code( $code, $params = array() ) 1226 { 1227 $Plugin = & $this->get_by_code( $code ); 1228 1229 if( ! $Plugin ) 1230 { 1231 global $Debuglog; 1232 $Debuglog->add( 'No plugin available for code ['.$code.']!', array('plugins', 'error') ); 1233 return false; 1234 } 1235 1236 $this->call_method_if_active( $Plugin->ID, 'SkinTag', $params ); 1237 1238 return true; 1239 } 1240 1241 1242 /** 1243 * Render the content of an item by calling the relevant renderer plugins. 1244 * 1245 * @param string content to render (by reference) 1246 * @param array renderer codes to use for opt-out, opt-in and lazy 1247 * @param string Output format, see {@link format_to_output()}. Only 'htmlbody', 1248 * 'entityencoded', 'xml' and 'text' are supported. 1249 * @param array Additional params to the Render* methods (e.g. "Item" for items). 1250 * Do not use "data" or "format" here, because it gets used internally. 1251 * @return string rendered content 1252 */ 1253 function render( & $content, $renderers, $format, $params, $event_prefix = 'Render' ) 1254 { 1255 // echo implode(',',$renderers); 1256 1257 $params['data'] = & $content; 1258 $params['format'] = $format; 1259 1260 if( $format == 'htmlbody' || $format == 'entityencoded' ) 1261 { 1262 $event = $event_prefix.'ItemAsHtml'; // 'RenderItemAsHtml'/'DisplayItemAsHtml' 1263 } 1264 elseif( $format == 'xml' ) 1265 { 1266 $event = $event_prefix.'ItemAsXml'; // 'RenderItemAsXml'/'DisplayItemAsXml' 1267 } 1268 elseif( $format == 'text' ) 1269 { 1270 $event = $event_prefix.'ItemAsText'; // 'RenderItemAsText'/'DisplayItemAsText' 1271 } 1272 else debug_die( 'Unexpected format in Plugins::render(): '.var_export($format, true) ); 1273 1274 $renderer_Plugins = $this->get_list_by_event( $event ); 1275 1276 foreach( $renderer_Plugins as $loop_RendererPlugin ) 1277 { // Go through whole list of renders 1278 // echo ' ',$loop_RendererPlugin->code, ':'; 1279 1280 switch( $loop_RendererPlugin->apply_rendering ) 1281 { 1282 case 'stealth': 1283 case 'always': 1284 // echo 'FORCED '; 1285 $this->call_method( $loop_RendererPlugin->ID, $event, $params ); 1286 break; 1287 1288 case 'opt-out': 1289 case 'opt-in': 1290 case 'lazy': 1291 if( in_array( $loop_RendererPlugin->code, $renderers ) ) 1292 { // Option is activated 1293 // echo 'OPT '; 1294 $this->call_method( $loop_RendererPlugin->ID, $event, $params ); 1295 } 1296 // else echo 'NOOPT '; 1297 break; 1298 1299 case 'never': 1300 // echo 'NEVER '; 1301 break; // STOP, don't render, go to next renderer 1302 } 1303 } 1304 1305 return $content; 1306 } 1307 1308 1309 /** 1310 * Quick-render a string with a single plugin and format it for output. 1311 * 1312 * @todo rename 1313 * 1314 * @param string Plugin code (must have render() method) 1315 * @param array 1316 * 'data': Data to render 1317 * 'format: format to output, see {@link format_to_output()} 1318 * @return string Rendered string 1319 */ 1320 function quick( $plugin_code, $params ) 1321 { 1322 global $Debuglog; 1323 1324 if( !is_array($params) ) 1325 { 1326 $params = array( 'format' => 'htmlbody', 'data' => $params ); 1327 } 1328 else 1329 { 1330 $params = $params; // copy 1331 } 1332 1333 $Plugin = & $this->get_by_code( $plugin_code ); 1334 if( $Plugin ) 1335 { 1336 // Get the most appropriate handler: 1337 $events = $this->get_enabled_events( $Plugin->ID ); 1338 $event = false; 1339 if( $params['format'] == 'htmlbody' || $params['format'] == 'htmlentityencoded' ) 1340 { 1341 if( in_array( 'RenderItemAsHtml', $events ) ) 1342 { 1343 $event = 'RenderItemAsHtml'; 1344 } 1345 } 1346 elseif( $params['format'] == 'xml' ) 1347 { 1348 if( in_array( 'RenderItemAsXml', $events ) ) 1349 { 1350 $event = 'RenderItemAsXml'; 1351 } 1352 } 1353 1354 if( $event ) 1355 { 1356 $this->call_method( $Plugin->ID, $event, $params ); 1357 } 1358 else 1359 { 1360 $Debuglog->add( $Plugin->classname.'(ID '.$Plugin->ID.'): failed to quick-render (tried method '.$event.')!', array( 'plugins', 'error' ) ); 1361 } 1362 return format_to_output( $params['data'], $params['format'] ); 1363 } 1364 else 1365 { 1366 $Debuglog->add( 'Plugins::quick() - failed to instantiate Plugin by code ['.$plugin_code.']!', array( 'plugins', 'error' ) ); 1367 return format_to_output( $params['data'], $params['format'] ); 1368 } 1369 } 1370 1371 1372 /** 1373 * Load Plugins data from T_plugins (only once), ordered by priority. 1374 * 1375 * This fills the needed indexes to lazy-instantiate a Plugin when requested. 1376 */ 1377 function load_plugins_table() 1378 { 1379 if( $this->loaded_plugins_table ) 1380 { 1381 return; 1382 } 1383 global $Debuglog, $DB; 1384 1385 $Debuglog->add( 'Loading plugins table data.', 'plugins' ); 1386 1387 $this->index_ID_rows = array(); 1388 $this->index_code_ID = array(); 1389 $this->index_apply_rendering_codes = array(); 1390 $this->sorted_IDs = array(); 1391 1392 foreach( $DB->get_results( $this->sql_load_plugins_table, ARRAY_A ) as $row ) 1393 { // Loop through installed plugins: 1394 $this->index_ID_rows[$row['plug_ID']] = $row; // remember the rows to instantiate the Plugin on request 1395 if( ! empty( $row['plug_code'] ) ) 1396 { 1397 $this->index_code_ID[$row['plug_code']] = $row['plug_ID']; 1398 } 1399 $this->index_apply_rendering_codes[$row['plug_apply_rendering']][] = $row['plug_code']; 1400 1401 $this->sorted_IDs[] = $row['plug_ID']; 1402 } 1403 1404 $this->loaded_plugins_table = true; 1405 } 1406 1407 1408 /** 1409 * Get a specific plugin by its ID. 1410 * 1411 * This is the workhorse when it comes to lazy-instantiating a Plugin. 1412 * 1413 * @param integer plugin ID 1414 * @return Plugin (false in case of error) 1415 */ 1416 function & get_by_ID( $plugin_ID ) 1417 { 1418 global $Debuglog; 1419 1420 if( ! isset($this->index_ID_Plugins[ $plugin_ID ]) ) 1421 { // Plugin is not instantiated yet 1422 $Debuglog->add( 'get_by_ID(): Instantiate Plugin (ID '.$plugin_ID.').', 'plugins' ); 1423 1424 $this->load_plugins_table(); 1425 1426 #pre_dump( 'get_by_ID(), index_ID_rows', $this->index_ID_rows ); 1427 1428 if( ! isset( $this->index_ID_rows[$plugin_ID] ) || ! $this->index_ID_rows[$plugin_ID] ) 1429 { // no plugin rows cached 1430 #debug_die( 'Cannot instantiate Plugin (ID '.$plugin_ID.') without DB information.' ); 1431 $Debuglog->add( 'get_by_ID(): Plugin (ID '.$plugin_ID.') not registered/enabled in DB!', array( 'plugins', 'error' ) ); 1432 $r = false; 1433 return $r; 1434 } 1435 1436 $row = & $this->index_ID_rows[$plugin_ID]; 1437 1438 // Register the plugin: 1439 $Plugin = & $this->register( $row['plug_classname'], $row['plug_ID'], $row['plug_priority'], $row['plug_apply_rendering'] ); 1440 1441 if( is_string( $Plugin ) ) 1442 { 1443 $Debuglog->add( 'Requested plugin [#'.$plugin_ID.'] not found!', 'plugins' ); 1444 $r = false; 1445 return $r; 1446 } 1447 1448 $this->index_ID_Plugins[ $plugin_ID ] = & $Plugin; 1449 } 1450 1451 return $this->index_ID_Plugins[ $plugin_ID ]; 1452 } 1453 1454 1455 /** 1456 * Get a plugin by its classname. 1457 * 1458 * @param string 1459 * @return Plugin (false in case of error) 1460 */ 1461 function & get_by_classname( $classname ) 1462 { 1463 $this->load_plugins_table(); // We use index_ID_rows (no own index yet) 1464 1465 foreach( $this->index_ID_rows as $plug_ID => $row ) 1466 { 1467 if( $row['plug_classname'] == $classname ) 1468 { 1469 return $this->get_by_ID($plug_ID); 1470 } 1471 } 1472 1473 $r = false; 1474 return $r; 1475 } 1476 1477 1478 /** 1479 * Get a specific Plugin by its code. 1480 * 1481 * @param string plugin code 1482 * @return Plugin (false in case of error) 1483 */ 1484 function & get_by_code( $plugin_code ) 1485 { 1486 global $Debuglog; 1487 1488 $r = false; 1489 1490 if( ! isset($this->index_code_Plugins[ $plugin_code ]) ) 1491 { // Plugin is not registered yet 1492 $this->load_plugins_table(); 1493 1494 if( ! isset($this->index_code_ID[ $plugin_code ]) ) 1495 { 1496 $Debuglog->add( 'Requested plugin ['.$plugin_code.'] is not registered/enabled!', 'plugins' ); 1497 return $r; 1498 } 1499 1500 if( ! $this->get_by_ID( $this->index_code_ID[$plugin_code] ) ) 1501 { 1502 $Debuglog->add( 'Requested plugin ['.$plugin_code.'] could not get instantiated!', 'plugins' ); 1503 return $r; 1504 } 1505 } 1506 1507 return $this->index_code_Plugins[ $plugin_code ]; 1508 } 1509 1510 1511 /** 1512 * Get a list of Plugins for a given event. 1513 * 1514 * @param string Event name 1515 * @return array plugin_ID => & Plugin 1516 */ 1517 function get_list_by_event( $event ) 1518 { 1519 $r = array(); 1520 1521 if( isset($this->index_event_IDs[$event]) ) 1522 { 1523 foreach( $this->index_event_IDs[$event] as $l_plugin_ID ) 1524 { 1525 if( $Plugin = & $this->get_by_ID( $l_plugin_ID ) ) 1526 { 1527 $r[ $l_plugin_ID ] = & $Plugin; 1528 unset($Plugin); // so that we do not overwrite the reference in the next loop 1529 } 1530 } 1531 } 1532 1533 return $r; 1534 } 1535 1536 1537 /** 1538 * Get a list of Plugins for a list of events. Every Plugin is only once in this list. 1539 * 1540 * @param array Array of events 1541 * @return array plugin_ID => & Plugin 1542 */ 1543 function get_list_by_events( $events ) 1544 { 1545 $r = array(); 1546 1547 foreach( $events as $l_event ) 1548 { 1549 foreach( array_keys($this->get_list_by_event( $l_event )) as $l_plugin_ID ) 1550 { 1551 if( $Plugin = & $this->get_by_ID( $l_plugin_ID ) ) 1552 { 1553 $r[ $l_plugin_ID ] = & $Plugin; 1554 unset($Plugin); // so that we do not overwrite the reference in the next loop 1555 } 1556 } 1557 } 1558 1559 return $r; 1560 } 1561 1562 1563 /** 1564 * Get a list of plugins that provide all given events. 1565 * 1566 * @return array plugin_ID => & Plugin 1567 */ 1568 function get_list_by_all_events( $events ) 1569 { 1570 $candidates = array(); 1571 1572 foreach( $events as $l_event ) 1573 { 1574 if( empty($this->index_event_IDs[$l_event]) ) 1575 { 1576 return array(); 1577 } 1578 1579 if( empty($candidates) ) 1580 { 1581 $candidates = $this->index_event_IDs[$l_event]; 1582 } 1583 else 1584 { 1585 $candidates = array_intersect( $candidates, $this->index_event_IDs[$l_event] ); 1586 if( empty($candidates) ) 1587 { 1588 return array(); 1589 } 1590 } 1591 } 1592 1593 $r = array(); 1594 foreach( $candidates as $plugin_ID ) 1595 { 1596 $Plugin = & $this->get_by_ID( $plugin_ID ); 1597 if( $Plugin ) 1598 { 1599 $r[ $plugin_ID ] = & $Plugin; 1600 unset($Plugin); // so that we do not overwrite the reference in the next loop 1601 } 1602 } 1603 1604 return $r; 1605 } 1606 1607 1608 /** 1609 * Get a list of (enabled) events for a given Plugin ID. 1610 * 1611 * @param integer Plugin ID 1612 * @return array 1613 */ 1614 function get_enabled_events( $plugin_ID ) 1615 { 1616 $r = array(); 1617 foreach( $this->index_event_IDs as $l_event => $l_plugin_IDs ) 1618 { 1619 if( in_array( $plugin_ID, $l_plugin_IDs ) ) 1620 { 1621 $r[] = $l_event; 1622 } 1623 } 1624 return $r; 1625 } 1626 1627 1628 /** 1629 * Has a plugin a specific event registered/enabled? 1630 * 1631 * @todo fp> The plugin should discover its events itself / This question should be asked to the Plugin itself. 1632 * 1633 * @param integer 1634 * @param string 1635 * @return boolean 1636 */ 1637 function has_event( $plugin_ID, $event ) 1638 { 1639 return isset($this->index_event_IDs[$event]) 1640 && in_array( $plugin_ID, $this->index_event_IDs[$event] ); 1641 } 1642 1643 1644 /** 1645 * Check if the requested list of events is provided by any or one plugin. 1646 * 1647 * @param array|string A single event or a list thereof 1648 * @param boolean Make sure there's at least one plugin that provides them all? 1649 * This is useful for event pairs like "CaptchaPayload" and "CaptchaValidated", which 1650 * should be served by the same plugin. 1651 * @return boolean 1652 */ 1653 function are_events_available( $events, $require_all_in_same_plugin = false ) 1654 { 1655 if( ! is_array($events) ) 1656 { 1657 $events = array($events); 1658 } 1659 1660 if( $require_all_in_same_plugin ) 1661 { 1662 return (bool)$this->get_list_by_all_events( $events ); 1663 } 1664 1665 return (bool)$this->get_list_by_events( $events ); 1666 } 1667 1668 1669 /** 1670 * (Re)load Plugin Events for enabled (normal use) or all (admin use) plugins. 1671 */ 1672 function load_events() 1673 { 1674 global $Debuglog, $DB; 1675 1676 $this->index_event_IDs = array(); 1677 1678 $Debuglog->add( 'Loading plugin events.', 'plugins' ); 1679 foreach( $DB->get_results( ' 1680 SELECT pevt_plug_ID, pevt_event 1681 FROM T_pluginevents INNER JOIN T_plugins ON pevt_plug_ID = plug_ID 1682 WHERE pevt_enabled > 0 1683 AND plug_status = \'enabled\' 1684 ORDER BY plug_priority, plug_classname', OBJECT, 'Loading plugin events' ) as $l_row ) 1685 { 1686 $this->index_event_IDs[$l_row->pevt_event][] = $l_row->pevt_plug_ID; 1687 } 1688 } 1689 1690 1691 /** 1692 * Load an object from a Cache plugin or create a new one if we have a 1693 * cache miss or no caching plugins. 1694 * 1695 * It registers a shutdown function, that refreshes the data to the cache plugin 1696 * which is not optimal, but we have no hook to see if data retrieved from 1697 * a {@link DataObjectCache} derived class has changed. 1698 * @param string object name 1699 * @param string eval this to create the object. Default is to create an object 1700 * of class $objectName. 1701 * @return boolean True, if retrieved from cache; false if not 1702 */ 1703 function get_object_from_cacheplugin_or_create( $objectName, $eval_create_object = NULL ) 1704 { 1705 $get_return = $this->trigger_event_first_true( 'CacheObjects', 1706 array( 'action' => 'get', 'key' => 'object_'.$objectName ) ); 1707 1708 if( isset( $get_return['plugin_ID'] ) ) 1709 { 1710 if( is_object($get_return['data']) ) 1711 { 1712 $GLOBALS[$objectName] = $get_return['data']; // COPY! (get_Cache() uses $$objectName instead of $GLOBALS - no deal for PHP5 anyway) 1713 1714 $Plugin = & $this->get_by_ID( $get_return['plugin_ID'] ); 1715 register_shutdown_function( array(&$Plugin, 'CacheObjects'), 1716 array( 'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) ); 1717 1718 return true; 1719 } 1720 } 1721 1722 // Cache miss, create it: 1723 if( empty($eval_create_object) ) 1724 { 1725 $GLOBALS[$objectName] = new $objectName(); // COPY (FUNC) 1726 } 1727 else 1728 { 1729 eval( '$GLOBALS[\''.$objectName.'\'] = '.$eval_create_object.';' ); 1730 } 1731 1732 // Try to set in cache: 1733 $set_return = $this->trigger_event_first_true( 'CacheObjects', 1734 array( 'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) ); 1735 1736 if( isset( $set_return['plugin_ID'] ) ) 1737 { // success, register a shutdown function to save this data on shutdown 1738 $Plugin = & $this->get_by_ID( $set_return['plugin_ID'] ); 1739 register_shutdown_function( array(&$Plugin, 'CacheObjects'), 1740 array( 'action' => 'set', 'key' => 'object_'.$objectName, 'data' => & $GLOBALS[$objectName] ) ); 1741 } 1742 1743 return false; 1744 } 1745 1746 1747 /** 1748 * Callback, which gets used for {@link Results}. 1749 * 1750 * @return Plugin (false in case of error) 1751 */ 1752 function & instantiate( $row ) 1753 { 1754 return $this->get_by_ID( $row->plug_ID ); 1755 } 1756 1757 1758 // Deprecated stubs: {{{ 1759 1760 /** 1761 * @deprecated since EVO_NEXT_VERSION by Plugins_admin::count_regs() 1762 */ 1763 function count_regs( $classname ) 1764 { 1765 global $Debuglog; 1766 $Debuglog->add('Call to deprecated method Plugins::count_regs()', 'deprecated'); 1767 $Plugins_admin = & get_Cache('Plugins_admin'); 1768 return $Plugins_admin->count_regs($classname); 1769 } 1770 1771 1772 /** 1773 * Set the apply_rendering value for a given Plugin ID. 1774 * 1775 * It makes sure that the index is handled and writes it to DB. 1776 * 1777 * @deprecated since EVO_NEXT_VERSION by Plugins_admin::set_apply_rendering() 1778 * @return boolean true if set to new value, false in case of error or if already set to same value 1779 */ 1780 function set_apply_rendering( $plugin_ID, $apply_rendering ) 1781 { 1782 $Plugins_admin = & get_Cache('Plugins_admin'); 1783 return $Plugins_admin->set_apply_rendering($plugin_ID, $apply_rendering); 1784 } 1785 1786 1787 /** 1788 * Validate renderer list. 1789 * 1790 * @deprecated since EVO_NEXT_VERSION by Plugins_admin::validate_renderer_list() 1791 * @param array renderer codes ('default' will include all "opt-out"-ones) 1792 * @return array validated array of renderer codes 1793 */ 1794 function validate_list( $renderers = array('default') ) 1795 { 1796 global $Debuglog; 1797 $Debuglog->add('Call to deprecated method Plugins::validate_list()', 'deprecated'); 1798 $Plugins_admin = & get_Cache('Plugins_admin'); 1799 return $Plugins_admin->validate_renderer_list($renderers); 1800 } 1801 1802 1803 // }}} 1804 } 1805 1806 1807 /* 1808 * $Log: _plugins.class.php,v $ 1809 * Revision 1.2 2007/09/22 22:11:18 fplanque 1810 * minor 1811 * 1812 * Revision 1.1 2007/06/25 11:00:45 fplanque 1813 * MODULES (refactored MVC) 1814 * 1815 * Revision 1.155 2007/06/19 23:15:08 blueyed 1816 * doc fixes 1817 * 1818 * Revision 1.154 2007/06/19 00:03:26 fplanque 1819 * doc / trying to make sense of automatic settings forms generation. 1820 * 1821 * Revision 1.153 2007/05/26 19:01:29 blueyed 1822 * Use has_event() in call_method_if_active() 1823 * 1824 * Revision 1.152 2007/05/14 02:43:05 fplanque 1825 * Started renaming tables. There probably won't be a better time than 2.0. 1826 * 1827 * Revision 1.151 2007/04/26 00:11:08 fplanque 1828 * (c) 2007 1829 * 1830 * Revision 1.150 2007/03/26 21:34:59 blueyed 1831 * Removed $Plugin->Plugins reference 1832 * 1833 * Revision 1.149 2007/02/23 00:21:23 blueyed 1834 * Fixed Plugins::get_next() if the last Plugin got unregistered; Added AdminBeforeItemEditDelete hook 1835 * 1836 * Revision 1.148 2007/02/18 20:50:42 blueyed 1837 * Fixed possible E_NOTICE when a plugin got unregistered 1838 * 1839 * Revision 1.147 2007/02/16 13:30:38 waltercruz 1840 * Changing double quotes to single quotes 1841 * 1842 * Revision 1.146 2007/02/12 15:42:59 fplanque 1843 * no message 1844 * 1845 * Revision 1.145 2007/02/10 18:39:54 blueyed 1846 * Fix: update "plug_version" in indices, so PluginVersionChanged does not get called twice (in Plugins_admin too) 1847 * 1848 * Revision 1.144 2007/02/06 14:33:21 waltercruz 1849 * Changing double quotes to single quotes 1850 * 1851 * Revision 1.143 2007/02/06 14:26:20 blueyed 1852 * MFB: do not pass $renderers by reference to Plugins::render() 1853 * 1854 * Revision 1.142 2007/02/06 00:08:56 waltercruz 1855 * Changing double quotes to single quotes 1856 * 1857 * Revision 1.141 2007/02/05 22:37:06 blueyed 1858 * doc 1859 * 1860 * Revision 1.140 2007/02/03 19:00:31 fplanque 1861 * doc 1862 * 1863 * Revision 1.139 2007/01/30 19:52:48 blueyed 1864 * Only deactivate a Plugin if PluginVersionChanged returns === false; fixes the basic_antispam_plugin, which returned NULL 1865 * 1866 * Revision 1.138 2007/01/29 21:33:52 blueyed 1867 * Fixed login with JS-hashing disabled and debugging turned on (PHP5) 1868 * 1869 * Revision 1.137 2007/01/25 23:48:18 blueyed 1870 * Fixed notice if Plugin got unregistered, e.g. because of DB schema change during loading; always pass array() if calling a Plugin method as $params 1871 * 1872 * Revision 1.136 2007/01/18 00:23:57 blueyed 1873 * doc 1874 * 1875 * Revision 1.135 2007/01/14 19:37:24 blueyed 1876 * Fix for overloading problems of (User)Settings in PHP 5.0.5 1877 * 1878 * Revision 1.134 2007/01/14 18:15:51 blueyed 1879 * Nuked hackish $is_admin_class as per todo 1880 * 1881 * Revision 1.133 2007/01/14 08:05:03 blueyed 1882 * Started to remove $is_admin_class in Plugins::register() 1883 * 1884 * Revision 1.132 2007/01/13 14:57:28 blueyed 1885 * Removed $is_admin_class hack from load_events() by re-implementing (copying most of it) to Plugins_admin as per todo 1886 * 1887 * Revision 1.131 2007/01/13 04:09:40 fplanque 1888 * doc 1889 * 1890 * Revision 1.130 2007/01/12 22:05:28 blueyed 1891 * Real fix for Plugins::get_list_by_* (keeping and returning reference instead of copy) 1892 * 1893 * Revision 1.129 2007/01/12 21:53:12 blueyed 1894 * Probably fixed Plugins::get_list_by_* methods: the returned references were always the one to the last Plugin 1895 * 1896 * Revision 1.128 2007/01/12 21:11:52 blueyed 1897 * doc fixed: it is important to know what gets returned in case of error 1898 * 1899 * Revision 1.127 2007/01/12 05:14:42 fplanque 1900 * doc 1901 * 1902 * Revision 1.126 2007/01/07 05:26:01 fplanque 1903 * doc 1904 * 1905 * Revision 1.125 2006/12/07 23:13:13 fplanque 1906 * @var needs to have only one argument: the variable type 1907 * Otherwise, I can't code! 1908 * 1909 * Revision 1.124 2006/12/05 00:23:55 blueyed 1910 * Return value for get_object_from_cacheplugin_or_create() 1911 * 1912 * Revision 1.123 2006/12/04 22:27:19 blueyed 1913 * Also call init_settings() on not installed Plugins, because it allows using $Settings/$UserSettings in Plugin::PluginInit() 1914 * 1915 * Revision 1.122 2006/12/01 20:51:27 blueyed 1916 * doc 1917 * 1918 * Revision 1.121 2006/12/01 20:46:25 blueyed 1919 * Moved Plugins::set_priority() to Plugins_admin class 1920 * 1921 * Revision 1.120 2006/12/01 20:44:01 blueyed 1922 * Moved Plugins::set_code() to Plugins_admin class 1923 * 1924 * Revision 1.119 2006/12/01 20:41:38 blueyed 1925 * Moved Plugins::uninstall() to Plugins_admin class 1926 * 1927 * Revision 1.118 2006/12/01 20:34:03 blueyed 1928 * Moved Plugins::get_apply_rendering_values() and Plugins::set_apply_rendering() to Plugins_admin class 1929 * 1930 * Revision 1.117 2006/12/01 20:19:15 blueyed 1931 * Moved Plugins::get_supported_events() to Plugins_admin class 1932 * 1933 * Revision 1.116 2006/12/01 20:13:23 blueyed 1934 * Moved Plugins::count_regs() to Plugins_admin class 1935 * 1936 * Revision 1.115 2006/12/01 20:04:31 blueyed 1937 * Renamed Plugins_admin::validate_list() to validate_renderer_list() 1938 * 1939 * Revision 1.114 2006/12/01 20:01:38 blueyed 1940 * Moved Plugins::validate_dependencies() to Plugins_admin class 1941 * 1942 * Revision 1.113 2006/12/01 19:46:42 blueyed 1943 * Moved Plugins::validate_list() to Plugins_admin class; added stub in Plugins, because at least the starrating_plugin uses it 1944 * 1945 * Revision 1.112 2006/12/01 19:16:00 blueyed 1946 * Moved Plugins::get_registered_events() to Plugins_admin class 1947 * 1948 * Revision 1.111 2006/12/01 18:18:21 blueyed 1949 * Moved Plugins::save_events() to Plugins_admin class 1950 * 1951 * Revision 1.110 2006/12/01 16:26:34 blueyed 1952 * Added AdminDisplayCommentFormFieldset hook 1953 * 1954 * Revision 1.109 2006/12/01 02:03:04 blueyed 1955 * Moved Plugins::set_event_status() to Plugins_admin 1956 * 1957 * Revision 1.108 2006/11/30 05:57:54 blueyed 1958 * Moved Plugins::install() and sort() galore to Plugins_admin 1959 * 1960 * Revision 1.107 2006/11/30 05:43:40 blueyed 1961 * Moved Plugins::discover() to Plugins_admin::discover(); Renamed Plugins_no_DB to Plugins_admin_no_DB (and deriving from Plugins_admin) 1962 * 1963 * Revision 1.106 2006/11/30 05:10:16 blueyed 1964 * Marked a bunch of methods to be moved to PLugins_admin 1965 * 1966 * Revision 1.105 2006/11/30 04:32:23 blueyed 1967 * Minor change in Plugins::discover(), before moving it 1968 * 1969 * Revision 1.104 2006/11/30 00:30:33 blueyed 1970 * Some minor memory optimizations regarding "Plugins" screen 1971 * 1972 * Revision 1.103 2006/11/24 18:27:27 blueyed 1973 * Fixed link to b2evo CVS browsing interface in file docblocks 1974 * 1975 * Revision 1.102 2006/11/14 00:47:32 fplanque 1976 * doc 1977 * 1978 * Revision 1.101 2006/11/14 00:21:05 blueyed 1979 * removed todo 1980 * 1981 * Revision 1.100 2006/11/01 14:59:27 blueyed 1982 * Handle obsoleting pre-rendered item content, if a renderer plugin version changes 1983 * 1984 * Revision 1.99 2006/11/01 14:22:33 blueyed 1985 * Fixed E_NOTICE/doc 1986 * 1987 * Revision 1.98 2006/10/30 19:00:36 blueyed 1988 * Lazy-loading of Plugin (User)Settings for PHP5 through overloading 1989 * 1990 * Revision 1.97 2006/10/29 20:07:34 blueyed 1991 * Added "app_min" plugin dependency; Deprecated "api_min" 1992 * 1993 * Revision 1.96 2006/10/16 08:39:10 blueyed 1994 * Merged fixes from v-1-9 branch 1995 * 1996 * Revision 1.95 2006/10/14 20:50:29 blueyed 1997 * Define EVO_IS_INSTALLING for /install/ and use it in Plugins to skip "dangerous" but unnecessary instantiating of other Plugins 1998 * 1999 * Revision 1.94 2006/10/14 16:27:06 blueyed 2000 * Client-side password hashing in the login form. 2001 * 2002 * Revision 1.93 2006/10/08 22:59:31 blueyed 2003 * Added GetProvidedSkins and DisplaySkin hooks. Allow for optimization in Plugins::trigger_event_first_return() 2004 * 2005 * Revision 1.92 2006/10/05 02:10:26 blueyed 2006 * Do not add empty codes in Plugins::validate_list() 2007 * 2008 * Revision 1.91 2006/10/05 01:06:37 blueyed 2009 * Removed dirty "hack"; added ItemApplyAsRenderer hook instead. 2010 * 2011 * Revision 1.90 2006/10/01 22:11:42 blueyed 2012 * Ping services as plugins. 2013 * 2014 * Revision 1.89 2006/10/01 19:56:36 blueyed 2015 * TODO 2016 * 2017 * Revision 1.88 2006/10/01 15:11:08 blueyed 2018 * Added DisplayItemAs* equivs to RenderItemAs*; removed DisplayItemAllFormats; clearing of pre-rendered cache, according to plugin event changes 2019 * 2020 * Revision 1.87 2006/10/01 00:14:58 blueyed 2021 * plug_classpath should not have get merged already 2022 */ 2023 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
|