MediaWiki
master
|
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 }