31use UnexpectedValueException;
32use Wikimedia\Assert\Assert;
33use Wikimedia\ObjectFactory;
34use Wikimedia\ScopedCallback;
35use Wikimedia\Services\SalvageableService;
72 $this->registry = $hookRegistry;
86 public function salvage( SalvageableService $other ) {
87 Assert::parameterType( self::class, $other,
'$other' );
88 if ( $this->legacyRegisteredHandlers || $this->handlersByName ) {
89 throw new MWException(
'salvage() must be called immediately after construction' );
91 $this->handlersByName = $other->handlersByName;
92 $this->legacyRegisteredHandlers = $other->legacyRegisteredHandlers;
120 public function run(
string $hook, array
$args = [], array $options = [] ) : bool {
122 $options = array_merge(
123 $this->registry->getDeprecatedHooks()->getDeprecationInfo( $hook ) ?? [],
127 $notAbortable = ( isset( $options[
'abortable'] ) && $options[
'abortable'] === false );
128 foreach ( $legacyHandlers as $handler ) {
130 if ( $normalizedHandler ) {
131 $functionName = $normalizedHandler[
'functionName'];
133 if ( $notAbortable && $return !==
null && $return !==
true ) {
134 throw new UnexpectedValueException(
"Invalid return from $functionName" .
135 " for unabortable $hook." );
137 if ( $return ===
false ) {
140 if ( is_string( $return ) ) {
142 "Returning a string from a hook handler is deprecated since MediaWiki 1.35 ' .
143 '(done by $functionName for $hook)",
146 throw new UnexpectedValueException( $return );
152 $funcName =
'on' . strtr( ucfirst( $hook ),
':-',
'__' );
154 foreach ( $handlers as $handler ) {
155 $return = $handler->$funcName( ...
$args );
156 if ( $notAbortable && $return !==
null && $return !==
true ) {
157 throw new UnexpectedValueException(
158 "Invalid return from " . $funcName .
" for unabortable $hook."
161 if ( $return ===
false ) {
164 if ( $return !==
null && !is_bool( $return ) ) {
165 throw new UnexpectedValueException(
"Invalid return from " . $funcName .
" for $hook." );
182 public function clear(
string $hook ) : void {
183 if ( !defined(
'MW_PHPUNIT_TEST' ) && !defined(
'MW_PARSER_TEST' ) ) {
184 throw new MWException(
'Cannot reset hooks in operation.' );
186 unset( $this->legacyRegisteredHandlers[$hook] );
199 public function scopedRegister(
string $hook, $callback,
bool $replace =
false ) : ScopedCallback {
202 if ( !isset( $this->originalHooks[$hook] ) &&
203 isset( $this->legacyRegisteredHandlers[$hook] )
205 $this->originalHooks[$hook] = $this->legacyRegisteredHandlers[$hook];
207 $this->legacyRegisteredHandlers[$hook] = [ $callback ];
208 return new ScopedCallback(
function () use ( $hook ) {
209 unset( $this->legacyRegisteredHandlers[$hook] );
212 $id = $this->nextScopedRegisterId++;
213 $this->legacyRegisteredHandlers[$hook][$id] = $callback;
214 return new ScopedCallback(
function () use ( $hook, $id ) {
215 unset( $this->legacyRegisteredHandlers[$hook][$id] );
227 if ( !defined(
'MW_PHPUNIT_TEST' ) && !defined(
'MW_PARSER_TEST' ) ) {
228 throw new MWException(
'Cannot get original hooks outside when not in test mode' );
230 return $this->originalHooks ?? [];
244 $normalizedHandler = $handler;
245 if ( !is_array( $handler ) ) {
246 $normalizedHandler = [ $normalizedHandler ];
250 if ( !array_filter( $normalizedHandler ) ) {
254 if ( is_array( $normalizedHandler[0] ) ) {
258 $normalizedHandler = array_merge( $normalizedHandler[0], array_slice( $normalizedHandler, 1 ) );
261 $firstArg = $normalizedHandler[0];
264 if ( $firstArg instanceof Closure ) {
265 $functionName =
"hook-$hook-closure";
266 $callback = array_shift( $normalizedHandler );
267 } elseif ( is_object( $firstArg ) ) {
268 $object = array_shift( $normalizedHandler );
269 $functionName = array_shift( $normalizedHandler );
272 if ( $functionName ===
null ) {
273 $functionName =
"on$hook";
275 $colonPos = strpos( $functionName,
'::' );
276 if ( $colonPos !==
false ) {
280 $functionName = substr( $functionName, $colonPos + 2 );
284 $callback = [ $object, $functionName ];
285 } elseif ( is_string( $firstArg ) ) {
286 if ( is_callable( $normalizedHandler,
true, $functionName )
287 && class_exists( $firstArg )
289 $callback = $normalizedHandler;
290 $normalizedHandler = [];
292 $functionName = $callback = array_shift( $normalizedHandler );
295 throw new UnexpectedValueException(
'Unknown datatype in hooks for ' . $hook );
299 'callback' => $callback,
300 'args' => $normalizedHandler,
301 'functionName' => $functionName,
319 $callback = $handler[
'callback'];
320 $hookArgs = array_merge( $handler[
'args'],
$args );
321 if ( isset( $options[
'deprecatedVersion'] ) && empty( $options[
'silent'] ) ) {
323 "$hook hook (used in " . $handler[
'functionName'] .
")",
324 $options[
'deprecatedVersion'] ??
false,
325 $options[
'component'] ??
false
329 return $callback( ...$hookArgs );
340 $legacyRegisteredHook = !empty( $this->registry->getGlobalHooks()[$hook] ) ||
341 !empty( $this->legacyRegisteredHandlers[$hook] );
342 $registeredHooks = $this->registry->getExtensionHooks();
343 return !empty( $registeredHooks[$hook] ) || $legacyRegisteredHook;
352 public function register(
string $hook, $callback ) {
353 $deprecatedHooks = $this->registry->getDeprecatedHooks();
354 $deprecated = $deprecatedHooks->isHookDeprecated( $hook );
356 $info = $deprecatedHooks->getDeprecationInfo( $hook );
357 if ( empty( $info[
'silent'] ) ) {
358 $deprecatedVersion = $info[
'deprecatedVersion'] ??
false;
359 $component = $info[
'component'] ??
false;
361 "$hook hook", $deprecatedVersion, $component
365 $this->legacyRegisteredHandlers[$hook][] = $callback;
376 $handlers = array_merge(
377 $this->legacyRegisteredHandlers[$hook] ?? [],
378 $this->registry->getGlobalHooks()[$hook] ?? []
391 public function getHandlers(
string $hook, array $options = [] ) : array {
393 $deprecatedHooks = $this->registry->getDeprecatedHooks();
394 $registeredHooks = $this->registry->getExtensionHooks();
395 if ( isset( $registeredHooks[$hook] ) ) {
396 foreach ( $registeredHooks[$hook] as $hookReference ) {
398 $handlerSpec = $hookReference[
'handler'];
400 $flaggedDeprecated = !empty( $hookReference[
'deprecated'] );
401 $deprecated = $deprecatedHooks->isHookDeprecated( $hook );
402 if ( $deprecated && $flaggedDeprecated ) {
405 $handlerName = $handlerSpec[
'name'];
406 if ( !empty( $options[
'noServices'] ) && isset( $handlerSpec[
'services'] ) ) {
407 throw new UnexpectedValueException(
408 "The handler for the hook $hook registered in " .
409 "{$hookReference['extensionPath']} has a service dependency, " .
410 "but this hook does not allow it." );
412 if ( !isset( $this->handlersByName[$handlerName] ) ) {
413 $this->handlersByName[$handlerName] =
414 $this->objectFactory->createObject( $handlerSpec );
416 $handlers[] = $this->handlersByName[$handlerName];
429 $deprecatedHooks = $this->registry->getDeprecatedHooks();
430 $registeredHooks = $this->registry->getExtensionHooks();
431 foreach ( $registeredHooks as $name => $handlers ) {
432 if ( $deprecatedHooks->isHookDeprecated( $name ) ) {
433 $deprecationInfo = $deprecatedHooks->getDeprecationInfo( $name );
434 if ( !empty( $deprecationInfo[
'silent'] ) ) {
437 $version = $deprecationInfo[
'deprecatedVersion'] ??
'';
438 $component = $deprecationInfo[
'component'] ??
'MediaWiki';
439 foreach ( $handlers as $handler ) {
440 if ( !isset( $handler[
'deprecated'] ) || !$handler[
'deprecated'] ) {
441 MWDebug::sendRawDeprecated(
442 "Hook $name was deprecated in $component $version " .
443 "but is registered in " . $handler[
'extensionPath']
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
if(ini_get('mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
New debugger system that outputs a toolbar on page view.