MediaWiki
REL1_22
|
00001 <?php 00002 00030 class MWHookException extends MWException {} 00031 00039 class Hooks { 00040 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 00135 public static function run( $event, array $args = array() ) { 00136 wfProfileIn( 'hook: ' . $event ); 00137 foreach ( self::getHandlers( $event ) as $hook ) { 00138 // Turn non-array values into an array. (Can't use casting because of objects.) 00139 if ( !is_array( $hook ) ) { 00140 $hook = array( $hook ); 00141 } 00142 00143 if ( !array_filter( $hook ) ) { 00144 // Either array is empty or it's an array filled with null/false/empty. 00145 continue; 00146 } elseif ( is_array( $hook[0] ) ) { 00147 // First element is an array, meaning the developer intended 00148 // the first element to be a callback. Merge it in so that 00149 // processing can be uniform. 00150 $hook = array_merge( $hook[0], array_slice( $hook, 1 ) ); 00151 } 00152 00158 if ( $hook[0] instanceof Closure ) { 00159 $func = "hook-$event-closure"; 00160 $callback = array_shift( $hook ); 00161 } elseif ( is_object( $hook[0] ) ) { 00162 $object = array_shift( $hook ); 00163 $method = array_shift( $hook ); 00164 00165 // If no method was specified, default to on$event. 00166 if ( $method === null ) { 00167 $method = "on$event"; 00168 } 00169 00170 $func = get_class( $object ) . '::' . $method; 00171 $callback = array( $object, $method ); 00172 } elseif ( is_string( $hook[0] ) ) { 00173 $func = $callback = array_shift( $hook ); 00174 } else { 00175 throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" ); 00176 } 00177 00178 // Run autoloader (workaround for call_user_func_array bug) 00179 // and throw error if not callable. 00180 if ( !is_callable( $callback ) ) { 00181 throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" ); 00182 } 00183 00184 /* 00185 * Call the hook. The documentation of call_user_func_array says 00186 * false is returned on failure. However, if the function signature 00187 * does not match the call signature, PHP will issue an warning and 00188 * return null instead. The following code catches that warning and 00189 * provides better error message. 00190 */ 00191 $retval = null; 00192 $badhookmsg = null; 00193 $hook_args = array_merge( $hook, $args ); 00194 00195 // Profile first in case the Profiler causes errors. 00196 wfProfileIn( $func ); 00197 set_error_handler( 'Hooks::hookErrorHandler' ); 00198 try { 00199 $retval = call_user_func_array( $callback, $hook_args ); 00200 } catch ( MWHookException $e ) { 00201 $badhookmsg = $e->getMessage(); 00202 } 00203 restore_error_handler(); 00204 wfProfileOut( $func ); 00205 00206 // Process the return value. 00207 if ( is_string( $retval ) ) { 00208 // String returned means error. 00209 throw new FatalError( $retval ); 00210 } elseif ( $badhookmsg !== null ) { 00211 // Exception was thrown from Hooks::hookErrorHandler. 00212 throw new MWException( 00213 'Detected bug in an extension! ' . 00214 "Hook $func has invalid call signature; " . $badhookmsg 00215 ); 00216 } elseif ( $retval === false ) { 00217 wfProfileOut( 'hook: ' . $event ); 00218 // False was returned. Stop processing, but no error. 00219 return false; 00220 } 00221 } 00222 00223 wfProfileOut( 'hook: ' . $event ); 00224 return true; 00225 } 00226 00240 public static function hookErrorHandler( $errno, $errstr ) { 00241 if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) { 00242 throw new MWHookException( $errstr, $errno ); 00243 } 00244 return false; 00245 } 00246 }