MediaWiki
REL1_24
|
00001 <?php 00002 00030 class MWHookException extends MWException { 00031 } 00032 00040 class Hooks { 00045 protected static $handlers = array(); 00046 00055 public static function register( $name, $callback ) { 00056 if ( !isset( self::$handlers[$name] ) ) { 00057 self::$handlers[$name] = array(); 00058 } 00059 00060 self::$handlers[$name][] = $callback; 00061 } 00062 00072 public static function clear( $name ) { 00073 if ( !defined( 'MW_PHPUNIT_TEST' ) ) { 00074 throw new MWException( 'Cannot reset hooks in operation.' ); 00075 } 00076 00077 unset( self::$handlers[$name] ); 00078 } 00079 00089 public static function isRegistered( $name ) { 00090 global $wgHooks; 00091 return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] ); 00092 } 00093 00103 public static function getHandlers( $name ) { 00104 global $wgHooks; 00105 00106 if ( !self::isRegistered( $name ) ) { 00107 return array(); 00108 } elseif ( !isset( self::$handlers[$name] ) ) { 00109 return $wgHooks[$name]; 00110 } elseif ( !isset( $wgHooks[$name] ) ) { 00111 return self::$handlers[$name]; 00112 } else { 00113 return array_merge( self::$handlers[$name], $wgHooks[$name] ); 00114 } 00115 } 00116 00136 public static function run( $event, array $args = array(), $deprecatedVersion = null ) { 00137 wfProfileIn( 'hook: ' . $event ); 00138 foreach ( self::getHandlers( $event ) as $hook ) { 00139 // Turn non-array values into an array. (Can't use casting because of objects.) 00140 if ( !is_array( $hook ) ) { 00141 $hook = array( $hook ); 00142 } 00143 00144 if ( !array_filter( $hook ) ) { 00145 // Either array is empty or it's an array filled with null/false/empty. 00146 continue; 00147 } elseif ( is_array( $hook[0] ) ) { 00148 // First element is an array, meaning the developer intended 00149 // the first element to be a callback. Merge it in so that 00150 // processing can be uniform. 00151 $hook = array_merge( $hook[0], array_slice( $hook, 1 ) ); 00152 } 00153 00159 if ( $hook[0] instanceof Closure ) { 00160 $func = "hook-$event-closure"; 00161 $callback = array_shift( $hook ); 00162 } elseif ( is_object( $hook[0] ) ) { 00163 $object = array_shift( $hook ); 00164 $method = array_shift( $hook ); 00165 00166 // If no method was specified, default to on$event. 00167 if ( $method === null ) { 00168 $method = "on$event"; 00169 } 00170 00171 $func = get_class( $object ) . '::' . $method; 00172 $callback = array( $object, $method ); 00173 } elseif ( is_string( $hook[0] ) ) { 00174 $func = $callback = array_shift( $hook ); 00175 } else { 00176 throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" ); 00177 } 00178 00179 // Run autoloader (workaround for call_user_func_array bug) 00180 // and throw error if not callable. 00181 if ( !is_callable( $callback ) ) { 00182 throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" ); 00183 } 00184 00185 /* 00186 * Call the hook. The documentation of call_user_func_array says 00187 * false is returned on failure. However, if the function signature 00188 * does not match the call signature, PHP will issue an warning and 00189 * return null instead. The following code catches that warning and 00190 * provides better error message. 00191 */ 00192 $retval = null; 00193 $badhookmsg = null; 00194 $hook_args = array_merge( $hook, $args ); 00195 00196 // Profile first in case the Profiler causes errors. 00197 wfProfileIn( $func ); 00198 set_error_handler( 'Hooks::hookErrorHandler' ); 00199 00200 // mark hook as deprecated, if deprecation version is specified 00201 if ( $deprecatedVersion !== null ) { 00202 wfDeprecated( "$event hook (used in $func)", $deprecatedVersion ); 00203 } 00204 00205 try { 00206 $retval = call_user_func_array( $callback, $hook_args ); 00207 } catch ( MWHookException $e ) { 00208 $badhookmsg = $e->getMessage(); 00209 } catch ( Exception $e ) { 00210 restore_error_handler(); 00211 throw $e; 00212 } 00213 restore_error_handler(); 00214 wfProfileOut( $func ); 00215 00216 // Process the return value. 00217 if ( is_string( $retval ) ) { 00218 // String returned means error. 00219 throw new FatalError( $retval ); 00220 } elseif ( $badhookmsg !== null ) { 00221 // Exception was thrown from Hooks::hookErrorHandler. 00222 throw new MWException( 00223 'Detected bug in an extension! ' . 00224 "Hook $func has invalid call signature; " . $badhookmsg 00225 ); 00226 } elseif ( $retval === false ) { 00227 wfProfileOut( 'hook: ' . $event ); 00228 // False was returned. Stop processing, but no error. 00229 return false; 00230 } 00231 } 00232 00233 wfProfileOut( 'hook: ' . $event ); 00234 return true; 00235 } 00236 00250 public static function hookErrorHandler( $errno, $errstr ) { 00251 if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) { 00252 throw new MWHookException( $errstr, $errno ); 00253 } 00254 return false; 00255 } 00256 }