MediaWiki  master
Hooks.php
Go to the documentation of this file.
00001 <?php
00029 class MWHookException extends MWException {}
00030 
00038 class Hooks {
00039 
00040         protected static $handlers = array();
00041 
00052         public static function clear( $name ) {
00053                 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
00054                         throw new MWException( 'can not reset hooks in operation.' );
00055                 }
00056 
00057                 unset( self::$handlers[$name] );
00058         }
00059 
00060 
00069         public static function register( $name, $callback ) {
00070                 if( !isset( self::$handlers[$name] ) ) {
00071                         self::$handlers[$name] = array();
00072                 }
00073 
00074                 self::$handlers[$name][] = $callback;
00075         }
00076 
00086         public static function isRegistered( $name ) {
00087                 global $wgHooks;
00088 
00089                 return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
00090         }
00091 
00103         public static function getHandlers( $name ) {
00104                 global $wgHooks;
00105 
00106                 // Return quickly in the most common case
00107                 if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
00108                         return array();
00109                 }
00110 
00111                 if ( !is_array( self::$handlers ) ) {
00112                         throw new MWException( "Local hooks array is not an array!\n" );
00113                 }
00114 
00115                 if ( !is_array( $wgHooks ) ) {
00116                         throw new MWException( "Global hooks array is not an array!\n" );
00117                 }
00118 
00119                 if ( empty( Hooks::$handlers[$name] ) ) {
00120                         $hooks = $wgHooks[$name];
00121                 } elseif ( empty( $wgHooks[$name] ) ) {
00122                         $hooks = Hooks::$handlers[$name];
00123                 } else {
00124                         // so they are both not empty...
00125                         $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
00126                 }
00127 
00128                 if ( !is_array( $hooks ) ) {
00129                         throw new MWException( "Hooks array for event '$name' is not an array!\n" );
00130                 }
00131 
00132                 return $hooks;
00133         }
00134 
00143         public static function run( $event, $args = array() ) {
00144                 global $wgHooks;
00145 
00146                 // Return quickly in the most common case
00147                 if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
00148                         return true;
00149                 }
00150 
00151                 $hooks = self::getHandlers( $event );
00152 
00153                 foreach ( $hooks as $hook ) {
00154                         $object = null;
00155                         $method = null;
00156                         $func = null;
00157                         $data = null;
00158                         $have_data = false;
00159                         $closure = false;
00160                         $badhookmsg = false;
00161 
00167                         if ( is_array( $hook ) ) {
00168                                 if ( count( $hook ) < 1 ) {
00169                                         throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
00170                                 } elseif ( is_object( $hook[0] ) ) {
00171                                         $object = $hook[0];
00172                                         if ( $object instanceof Closure ) {
00173                                                 $closure = true;
00174                                                 if ( count( $hook ) > 1 ) {
00175                                                         $data = $hook[1];
00176                                                         $have_data = true;
00177                                                 }
00178                                         } else {
00179                                                 if ( count( $hook ) < 2 ) {
00180                                                         $method = 'on' . $event;
00181                                                 } else {
00182                                                         $method = $hook[1];
00183                                                         if ( count( $hook ) > 2 ) {
00184                                                                 $data = $hook[2];
00185                                                                 $have_data = true;
00186                                                         }
00187                                                 }
00188                                         }
00189                                 } elseif ( is_string( $hook[0] ) ) {
00190                                         $func = $hook[0];
00191                                         if ( count( $hook ) > 1) {
00192                                                 $data = $hook[1];
00193                                                 $have_data = true;
00194                                         }
00195                                 } else {
00196                                         throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
00197                                 }
00198                         } elseif ( is_string( $hook ) ) { # functions look like strings, too
00199                                 $func = $hook;
00200                         } elseif ( is_object( $hook ) ) {
00201                                 $object = $hook;
00202                                 if ( $object instanceof Closure ) {
00203                                         $closure = true;
00204                                 } else {
00205                                         $method = "on" . $event;
00206                                 }
00207                         } else {
00208                                 throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
00209                         }
00210 
00211                         /* We put the first data element on, if needed. */
00212                         if ( $have_data ) {
00213                                 $hook_args = array_merge( array( $data ), $args );
00214                         } else {
00215                                 $hook_args = $args;
00216                         }
00217 
00218                         if ( $closure ) {
00219                                 $callback = $object;
00220                                 $func = "hook-$event-closure";
00221                         } elseif ( isset( $object ) ) {
00222                                 $func = get_class( $object ) . '::' . $method;
00223                                 $callback = array( $object, $method );
00224                         } else {
00225                                 $callback = $func;
00226                         }
00227 
00228                         // Run autoloader (workaround for call_user_func_array bug)
00229                         is_callable( $callback );
00230 
00249                         $retval = null;
00250                         set_error_handler( 'Hooks::hookErrorHandler' );
00251                         wfProfileIn( $func );
00252                         try {
00253                                 $retval = call_user_func_array( $callback, $hook_args );
00254                         } catch ( MWHookException $e ) {
00255                                 $badhookmsg = $e->getMessage();
00256                         }
00257                         wfProfileOut( $func );
00258                         restore_error_handler();
00259 
00260                         /* String return is an error; false return means stop processing. */
00261                         if ( is_string( $retval ) ) {
00262                                 throw new FatalError( $retval );
00263                         } elseif( $retval === null ) {
00264                                 if ( $closure ) {
00265                                         $prettyFunc = "$event closure";
00266                                 } elseif( is_array( $callback ) ) {
00267                                         if( is_object( $callback[0] ) ) {
00268                                                 $prettyClass = get_class( $callback[0] );
00269                                         } else {
00270                                                 $prettyClass = strval( $callback[0] );
00271                                         }
00272                                         $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
00273                                 } else {
00274                                         $prettyFunc = strval( $callback );
00275                                 }
00276                                 if ( $badhookmsg ) {
00277                                         throw new MWException(
00278                                                 'Detected bug in an extension! ' .
00279                                                 "Hook $prettyFunc has invalid call signature; " . $badhookmsg
00280                                         );
00281                                 } else {
00282                                         throw new MWException(
00283                                                 'Detected bug in an extension! ' .
00284                                                 "Hook $prettyFunc failed to return a value; " .
00285                                                 'should return true to continue hook processing or false to abort.'
00286                                         );
00287                                 }
00288                         } elseif ( !$retval ) {
00289                                 return false;
00290                         }
00291                 }
00292 
00293                 return true;
00294         }
00295 
00306         public static function hookErrorHandler( $errno, $errstr ) {
00307                 if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
00308                         throw new MWHookException( $errstr );
00309                 }
00310                 return false;
00311         }
00312 }