[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Hooks.php (source)

   1  <?php
   2  
   3  /**
   4   * A tool for running hook functions.
   5   *
   6   * Copyright 2004, 2005 Evan Prodromou <[email protected]>.
   7   *
   8   * This program is free software; you can redistribute it and/or modify
   9   * it under the terms of the GNU General Public License as published by
  10   * the Free Software Foundation; either version 2 of the License, or
  11   * (at your option) any later version.
  12   *
  13   * This program is distributed in the hope that it will be useful,
  14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16   * GNU General Public License for more details.
  17   *
  18   * You should have received a copy of the GNU General Public License
  19   * along with this program; if not, write to the Free Software
  20   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  21   *
  22   * @author Evan Prodromou <[email protected]>
  23   * @see hooks.txt
  24   * @file
  25   */
  26  
  27  /**
  28   * @since 1.18
  29   */
  30  class MWHookException extends MWException {
  31  }
  32  
  33  /**
  34   * Hooks class.
  35   *
  36   * Used to supersede $wgHooks, because globals are EVIL.
  37   *
  38   * @since 1.18
  39   */
  40  class Hooks {
  41      /**
  42       * Array of events mapped to an array of callbacks to be run
  43       * when that event is triggered.
  44       */
  45      protected static $handlers = array();
  46  
  47      /**
  48       * Attach an event handler to a given hook.
  49       *
  50       * @param string $name Name of hook
  51       * @param callable $callback Callback function to attach
  52       *
  53       * @since 1.18
  54       */
  55  	public static function register( $name, $callback ) {
  56          if ( !isset( self::$handlers[$name] ) ) {
  57              self::$handlers[$name] = array();
  58          }
  59  
  60          self::$handlers[$name][] = $callback;
  61      }
  62  
  63      /**
  64       * Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
  65       * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
  66       *
  67       * @param string $name The name of the hook to clear.
  68       *
  69       * @since 1.21
  70       * @throws MWException If not in testing mode.
  71       */
  72  	public static function clear( $name ) {
  73          if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
  74              throw new MWException( 'Cannot reset hooks in operation.' );
  75          }
  76  
  77          unset( self::$handlers[$name] );
  78      }
  79  
  80      /**
  81       * Returns true if a hook has a function registered to it.
  82       * The function may have been registered either via Hooks::register or in $wgHooks.
  83       *
  84       * @since 1.18
  85       *
  86       * @param string $name Name of hook
  87       * @return bool True if the hook has a function registered to it
  88       */
  89  	public static function isRegistered( $name ) {
  90          global $wgHooks;
  91          return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
  92      }
  93  
  94      /**
  95       * Returns an array of all the event functions attached to a hook
  96       * This combines functions registered via Hooks::register and with $wgHooks.
  97       *
  98       * @since 1.18
  99       *
 100       * @param string $name Name of the hook
 101       * @return array
 102       */
 103  	public static function getHandlers( $name ) {
 104          global $wgHooks;
 105  
 106          if ( !self::isRegistered( $name ) ) {
 107              return array();
 108          } elseif ( !isset( self::$handlers[$name] ) ) {
 109              return $wgHooks[$name];
 110          } elseif ( !isset( $wgHooks[$name] ) ) {
 111              return self::$handlers[$name];
 112          } else {
 113              return array_merge( self::$handlers[$name], $wgHooks[$name] );
 114          }
 115      }
 116  
 117      /**
 118       * Call hook functions defined in Hooks::register and $wgHooks.
 119       *
 120       * For a certain hook event, fetch the array of hook events and
 121       * process them. Determine the proper callback for each hook and
 122       * then call the actual hook using the appropriate arguments.
 123       * Finally, process the return value and return/throw accordingly.
 124       *
 125       * @param string $event Event name
 126       * @param array $args Array of parameters passed to hook functions
 127       * @param string|null $deprecatedVersion Optionally, mark hook as deprecated with version number
 128       * @return bool True if no handler aborted the hook
 129       *
 130       * @since 1.22 A hook function is not required to return a value for
 131       *   processing to continue. Not returning a value (or explicitly
 132       *   returning null) is equivalent to returning true.
 133       * @throws MWException
 134       * @throws FatalError
 135       */
 136  	public static function run( $event, array $args = array(), $deprecatedVersion = null ) {
 137          wfProfileIn( 'hook: ' . $event );
 138          foreach ( self::getHandlers( $event ) as $hook ) {
 139              // Turn non-array values into an array. (Can't use casting because of objects.)
 140              if ( !is_array( $hook ) ) {
 141                  $hook = array( $hook );
 142              }
 143  
 144              if ( !array_filter( $hook ) ) {
 145                  // Either array is empty or it's an array filled with null/false/empty.
 146                  continue;
 147              } elseif ( is_array( $hook[0] ) ) {
 148                  // First element is an array, meaning the developer intended
 149                  // the first element to be a callback. Merge it in so that
 150                  // processing can be uniform.
 151                  $hook = array_merge( $hook[0], array_slice( $hook, 1 ) );
 152              }
 153  
 154              /**
 155               * $hook can be: a function, an object, an array of $function and
 156               * $data, an array of just a function, an array of object and
 157               * method, or an array of object, method, and data.
 158               */
 159              if ( $hook[0] instanceof Closure ) {
 160                  $func = "hook-$event-closure";
 161                  $callback = array_shift( $hook );
 162              } elseif ( is_object( $hook[0] ) ) {
 163                  $object = array_shift( $hook );
 164                  $method = array_shift( $hook );
 165  
 166                  // If no method was specified, default to on$event.
 167                  if ( $method === null ) {
 168                      $method = "on$event";
 169                  }
 170  
 171                  $func = get_class( $object ) . '::' . $method;
 172                  $callback = array( $object, $method );
 173              } elseif ( is_string( $hook[0] ) ) {
 174                  $func = $callback = array_shift( $hook );
 175              } else {
 176                  throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
 177              }
 178  
 179              // Run autoloader (workaround for call_user_func_array bug)
 180              // and throw error if not callable.
 181              if ( !is_callable( $callback ) ) {
 182                  throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" );
 183              }
 184  
 185              /*
 186               * Call the hook. The documentation of call_user_func_array says
 187               * false is returned on failure. However, if the function signature
 188               * does not match the call signature, PHP will issue an warning and
 189               * return null instead. The following code catches that warning and
 190               * provides better error message.
 191               */
 192              $retval = null;
 193              $badhookmsg = null;
 194              $hook_args = array_merge( $hook, $args );
 195  
 196              // Profile first in case the Profiler causes errors.
 197              wfProfileIn( $func );
 198              set_error_handler( 'Hooks::hookErrorHandler' );
 199  
 200              // mark hook as deprecated, if deprecation version is specified
 201              if ( $deprecatedVersion !== null ) {
 202                  wfDeprecated( "$event hook (used in $func)", $deprecatedVersion );
 203              }
 204  
 205              try {
 206                  $retval = call_user_func_array( $callback, $hook_args );
 207              } catch ( MWHookException $e ) {
 208                  $badhookmsg = $e->getMessage();
 209              } catch ( Exception $e ) {
 210                  restore_error_handler();
 211                  throw $e;
 212              }
 213              restore_error_handler();
 214              wfProfileOut( $func );
 215  
 216              // Process the return value.
 217              if ( is_string( $retval ) ) {
 218                  // String returned means error.
 219                  throw new FatalError( $retval );
 220              } elseif ( $badhookmsg !== null ) {
 221                  // Exception was thrown from Hooks::hookErrorHandler.
 222                  throw new MWException(
 223                      'Detected bug in an extension! ' .
 224                      "Hook $func has invalid call signature; " . $badhookmsg
 225                  );
 226              } elseif ( $retval === false ) {
 227                  wfProfileOut( 'hook: ' . $event );
 228                  // False was returned. Stop processing, but no error.
 229                  return false;
 230              }
 231          }
 232  
 233          wfProfileOut( 'hook: ' . $event );
 234          return true;
 235      }
 236  
 237      /**
 238       * Handle PHP errors issued inside a hook. Catch errors that have to do with
 239       * a function expecting a reference, and let all others pass through.
 240       *
 241       * This REALLY should be protected... but it's public for compatibility
 242       *
 243       * @since 1.18
 244       *
 245       * @param int $errno Error number (unused)
 246       * @param string $errstr Error message
 247       * @throws MWHookException If the error has to do with the function signature
 248       * @return bool Always returns false
 249       */
 250  	public static function hookErrorHandler( $errno, $errstr ) {
 251          if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
 252              throw new MWHookException( $errstr, $errno );
 253          }
 254          return false;
 255      }
 256  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1