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