*/
/**
- * Call hook functions defined in $wgHooks
- *
- * Because programmers assign to $wgHooks, we need to be very
- * careful about its contents. So, there's a lot more error-checking
- * in here than would normally be necessary.
- *
- * @param $event String: event name
- * @param $args Array: parameters passed to hook functions
- * @return Boolean
+ * @since 1.18
*/
-function wfRunHooks( $event, $args = array() ) {
- return Hooks::run( $event, $args );
-}
-
-function hookErrorHandler( $errno, $errstr ) {
- return Hooks::hookErrorHandler( $errno, $errstr );
-}
-
class MWHookException extends MWException {}
/**
* Hooks class.
*
* Used to supersede $wgHooks, because globals are EVIL.
+ *
+ * @since 1.18
*/
class Hooks {
protected static $handlers = array();
+ /**
+ * Clears hooks registered via Hooks::register(). Does not touch $wgHooks.
+ * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+ *
+ * @since 1.21
+ *
+ * @param $name String: the name of the hook to clear.
+ *
+ * @throws MWException if not in testing mode.
+ */
+ public static function clear( $name ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new MWException( 'can not reset hooks in operation.' );
+ }
+
+ unset( self::$handlers[$name] );
+ }
+
+
/**
* Attach an event handler to a given hook
*
- * @param $name Mixed: name of hook
+ * @since 1.18
+ *
+ * @param $name String: name of hook
* @param $callback Mixed: callback function to attach
- * @return void
*/
public static function register( $name, $callback ) {
if( !isset( self::$handlers[$name] ) ) {
/**
* Returns true if a hook has a function registered to it.
+ * The function may have been registered either via Hooks::register or in $wgHooks.
*
- * @param $name Mixed: name of hook
- * @return Boolean: true if a hook has a function registered to it
+ * @since 1.18
+ *
+ * @param $name String: name of hook
+ * @return Boolean: true if the hook has a function registered to it
*/
public static function isRegistered( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
- self::$handlers[$name] = array();
- }
+ global $wgHooks;
- return ( count( self::$handlers[$name] ) != 0 );
+ return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
}
/**
* Returns an array of all the event functions attached to a hook
+ * This combines functions registered via Hooks::register and with $wgHooks.
+ * @since 1.18
+ *
+ * @throws MWException
+ * @throws FatalError
+ * @param $name String: name of the hook
*
- * @param $name Mixed: name of the hook
* @return array
*/
public static function getHandlers( $name ) {
- if( !isset( self::$handlers[$name] ) ) {
+ global $wgHooks;
+
+ // Return quickly in the most common case
+ if ( empty( self::$handlers[$name] ) && empty( $wgHooks[$name] ) ) {
return array();
}
- return self::$handlers[$name];
+ if ( !is_array( self::$handlers ) ) {
+ throw new MWException( "Local hooks array is not an array!\n" );
+ }
+
+ if ( !is_array( $wgHooks ) ) {
+ throw new MWException( "Global hooks array is not an array!\n" );
+ }
+
+ if ( empty( Hooks::$handlers[$name] ) ) {
+ $hooks = $wgHooks[$name];
+ } elseif ( empty( $wgHooks[$name] ) ) {
+ $hooks = Hooks::$handlers[$name];
+ } else {
+ // so they are both not empty...
+ $hooks = array_merge( Hooks::$handlers[$name], $wgHooks[$name] );
+ }
+
+ if ( !is_array( $hooks ) ) {
+ throw new MWException( "Hooks array for event '$name' is not an array!\n" );
+ }
+
+ return $hooks;
}
/**
* Call hook functions defined in Hooks::register
*
- * Because programmers assign to $wgHooks, we need to be very
- * careful about its contents. So, there's a lot more error-checking
- * in here than would normally be necessary.
- *
* @param $event String: event name
- * @param $args Array: parameters passed to hook functions
- * @return Boolean
+ * @param $args Array: parameters passed to hook functions
+ *
+ * @return Boolean True if no handler aborted the hook
*/
public static function run( $event, $args = array() ) {
global $wgHooks;
// Return quickly in the most common case
- if ( !isset( self::$handlers[$event] ) && !isset( $wgHooks[$event] ) ) {
+ if ( empty( self::$handlers[$event] ) && empty( $wgHooks[$event] ) ) {
return true;
}
- if ( !is_array( self::$handlers ) ) {
- throw new MWException( "Local hooks array is not an array!\n" );
- }
-
- if ( !is_array( $wgHooks ) ) {
- throw new MWException( "Global hooks array is not an array!\n" );
- }
+ $hooks = self::getHandlers( $event );
- $new_handlers = (array) self::$handlers;
- $old_handlers = (array) $wgHooks;
-
- $hook_array = array_merge( $new_handlers, $old_handlers );
-
- if ( !is_array( $hook_array[$event] ) ) {
- throw new MWException( "Hooks array for event '$event' is not an array!\n" );
- }
-
- foreach ( $hook_array[$event] as $index => $hook ) {
+ foreach ( $hooks as $hook ) {
$object = null;
$method = null;
$func = null;
if ( count( $hook ) < 1 ) {
throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
} elseif ( is_object( $hook[0] ) ) {
- $object = $hook_array[$event][$index][0];
+ $object = $hook[0];
if ( $object instanceof Closure ) {
$closure = true;
if ( count( $hook ) > 1 ) {
} elseif ( is_string( $hook ) ) { # functions look like strings, too
$func = $hook;
} elseif ( is_object( $hook ) ) {
- $object = $hook_array[$event][$index];
+ $object = $hook;
if ( $object instanceof Closure ) {
$closure = true;
} else {
} elseif ( isset( $object ) ) {
$func = get_class( $object ) . '::' . $method;
$callback = array( $object, $method );
- } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
- $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
} else {
$callback = $func;
}
* problem here.
*/
$retval = null;
- set_error_handler( array( 'Hooks', 'hookErrorHandler' ) );
+ set_error_handler( 'Hooks::hookErrorHandler' );
wfProfileIn( $func );
try {
$retval = call_user_func_array( $callback, $hook_args );
/* String return is an error; false return means stop processing. */
if ( is_string( $retval ) ) {
- global $wgOut;
- $wgOut->showFatalError( $retval );
- return false;
+ throw new FatalError( $retval );
} elseif( $retval === null ) {
if ( $closure ) {
$prettyFunc = "$event closure";
'should return true to continue hook processing or false to abort.'
);
}
- } else if ( !$retval ) {
+ } elseif ( !$retval ) {
return false;
}
}
/**
* This REALLY should be protected... but it's public for compatibility
*
- * @param $errno Unused
+ * @since 1.18
+ *
+ * @param $errno int Unused
* @param $errstr String: error message
+ * @throws MWHookException
* @return Boolean: false
*/
public static function hookErrorHandler( $errno, $errstr ) {