[ Index ] |
|
Code source de b2evolution 2.1.0-beta |
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 ?>
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 |
![]() |