Page MenuHomePhabricator

Provide documentation for migrating away from UnknownAction hook.
Closed, ResolvedPublic

Description

The UnknownAction hook was deprecated in 1.19, and has recently started throwing deprecation warnings.

As an extension developer using this hook, I would like to upgrade my extension to use $wgActions. However, I've been unable to find any documentation about how to do this.

The UnknownAction page points to Manual:$wgActions which is where I would probably expect to find the information, but it doesn't tell you very much about how to set up a new action; It just points to Manual:Action.php, which is a bit of a dead-end. There is a not-very-obvious link to the docs for classAction.html but that also doesn't really provide any usage information.

Some documentation needs to be written so that developers can migrate away from this deprecated hook. This needs to happen before the hook can be removed from MediaWiki.

Event Timeline

HappyDog created this task.Oct 23 2017, 9:42 PM
Restricted Application added a project: Documentation. · View Herald TranscriptOct 23 2017, 9:42 PM
Restricted Application added a subscriber: Aklapper. · View Herald Transcript

Any word on this? This is preventing me from upgrading my extensions to support the latest MediaWiki versions.

If it's too much effort to document it properly. perhaps a developer could write some notes here? I'd be happy to add them to an appropriate location on MediaWiki.org.

Create a class that subclasses Action, and then do

{
    "Actions": {
        "my-action": "MyActionClass"
    }
}

in your extension.json.

Reedy added a subscriber: Reedy.Jun 15 2018, 8:36 PM

What part are you struggling with? It looks like it's a not so commonly used feature, so probably really explains the lack of general documentation...

But looking at CirrusSearch, for example (the WMF hosted extensions probably provide the best examples of it being used in action)

$wgActions[ 'cirrusdump' ] = 'CirrusSearch\Dump';

Look at the Dump class...

class Dump extends FormlessAction {
	public function onView() {
		// Disable regular results
		$this->getOutput()->disable();

		$response = $this->getRequest()->response();
		$response->header( 'Content-type: application/json; charset=UTF-8' );

		$config = MediaWikiServices::getInstance()
			->getConfigFactory()
			->makeConfig( 'CirrusSearch' );
		/** @suppress PhanTypeMismatchArgument $config is actually a SearchConfig */
		$conn = new Connection( $config );
		/** @suppress PhanTypeMismatchArgument $config is actually a SearchConfig */
		$searcher = new Searcher( $conn, 0, 0, $config, [], $this->getUser() );

		/** @suppress PhanUndeclaredMethod Phan doesn't know $config is a SearchConfig */
		$docId = $config->makeId( $this->getTitle()->getArticleID() );
		$esSources = $searcher->get( [ $docId ], true );
		if ( !$esSources->isOK() ) {
			// Exception has been logged
			echo '{}';
			return;
		}
		$esSources = $esSources->getValue();

		$result = [];
		foreach ( $esSources as $esSource ) {
			$result[] = [
				'_index' => $esSource->getIndex(),
				'_type' => $esSource->getType(),
				'_id' => $esSource->getId(),
				'_version' => $esSource->getVersion(),
				'_source' => $esSource->getData(),
			];
		}
		echo json_encode( $result );
	}

	/**
	 * @return string
	 */
	public function getName() {
		return 'cirrusdump';
	}

	/**
	 * @return bool
	 */
	public function requiresWrite() {
		return false;
	}

	/**
	 * @return bool
	 */
	public function requiresUnblock() {
		return false;
	}
}

So...

$wgActions['myaction'] = 'MyActionHandlerClassThing';

Then just make your class subclass Action or FormlessAction

Without example code/reference extensions, it's hard for anyone to advise more specifically

HappyDog added a comment.EditedAug 16 2018, 8:59 PM

Hi Reedy - sorry not to reply to this sooner. I will take a look at the example you've posted (which I would never have found on my own!) and see if I can get things working.

In the meantime, here is a generalised version of what I'm currently doing in the UnknownAction hook:

function UnknownAction($action, $article) {
	global $wgOut, $wgRequest;

// If the requested action is not 'example_action' then don't do anything.
// We return true, indicating that MediaWiki should carry on as normal.
	if ($action != "example_action")
		return true;

// The view is the same for the main page and the talk page, so if we're on the
// talk page then we need to change $Title to point to the subject page instead.
	$Title = $article->getTitle();
	if ($Title->isTalkPage())
		$Title = $Title->getSubjectPage();

// Set page title.
	$wgOut->setPageTitle("Example Page Title");

// Get some parameters from the URL
	$Param = $wgRequest->getIntOrNull('example_param');

// Do some internal stuff to generate the content.

// Output the results.
	$wgOut->addHTML($Output);
// or
	$wgOut->addWikiText($Output);

// Return false to tell MediaWiki that we have successfully generated the page
// contents and to skip processing of other hooks.
	return false;
}

As well as general how-to help for this new $wgActions method of implementing a new action, I would hope that the documentation would also cover migration issues. Feel free to use the above as an example, e.g. to show before/after.

One thing not covered in your example is how to output a standard page, but with a custom content area in the manner of my example. Your code seems to generate the whole output (including headers) manually, unless that's simply a matter of removing the disable() call on the first line...

The hook has now been entirely removed as of MediaWiki 1.32.0.

Adding to 1.32 release, as it is imperative that there are upgrade instructions before the hook is formally removed.

What is the status of updating the documentation?

HappyDog added a comment.EditedFeb 16 2019, 8:49 PM

I notice that 1.32 has now been release, but this bug has not been fixed. That means extensions will be broken without any upgrade path being supplied.

This is very irresponsible behaviour.

Can I ask why this was not fixed, despite (still) being on the 1.32 work board? Internal processes must be very broken if the software is shipping without dependencies being resolved (or removed), first!

Please can this bug be worked on as a matter of urgency, so I am able to resolve it before I start getting bug reports from my users.

What is the status of updating the documentation?

I think it was already adequate, but (a) I've never written a new Action, and (b) clearly the requester disagrees. :-(

I notice that 1.32 has now been release, but this bug has not been fixed. That means extensions will be broken without any upgrade path being supplied.

What of the following documentation is unclear?

Details
Array of allowed values for the "action" parameter for normal pages.
Syntax is:

  • 'foo' => 'ClassName' - Load the specified class which subclasses Action
  • 'foo' => true - Load the class FooAction which subclasses Action
  • 'foo' => false - The action is disabled; show an error message

Instead of registering your action as a function called from the hook, you register it as a class via the global. There are a fair number of examples of this in code search.

Instead of registering your action as a function called from the hook, you register it as a class via the global. There are a fair number of examples of this in code search.

It is not simply a matter of changing how you register the hook. There are substantial code changes required, as well.

What of the following documentation is unclear?

I put that in the task description. To quote:

The UnknownAction page points to Manual:$wgActions which is where I would probably expect to find the information, but it doesn't tell you very much about how to set up a new action; It just points to Manual:Action.php, which is a bit of a dead-end. There is a not-very-obvious link to the docs for classAction.html but that also doesn't really provide any usage information.

None of the expected documentation pages have any information about how to actually create an action.

There is also no information about how to migrate existing code. I have documented what I would expect to see with regards this matter in comment T178844#4508316.

I think it was already adequate, but (a) I've never written a new Action, and (b) clearly the requester disagrees. :-(

What documentation are you looking at, that you consider adequate? If there is additional documentation that already exists elsewhere, perhaps we just need to provide links to it from the places I mention so that it can be more easily found.

What documentation are you looking at, that you consider adequate?

I quoted it. You claim "it doesn't tell you very much", but don't give anything actionable as to what more you want. As with all of MediaWiki, reviewing what others have done is generally the best way to find out what on Earth to do.

I'm sorry, but that is a totally unacceptable answer.

  • Code search is not documentation.
  • Code search (of the nature you link to) is undiscoverable.
  • Code search is unreliable (how do you know this random other implementation is following best-practice?).
  • Code search is unmanageable (your search result gives 116 files - are you expecting someone to trawl through them to find an appropriate implementation?).
  • Code search shows you new implementations but doesn't show you how the old implementation maps onto them.

I don't understand why there is resistance to documenting a migration path for breaking changes. 15 minutes of time from the developers who understand the software will save maybe half a day's worth of time from every single extension developer who is using the deprecated functionality.

...and if it takes more than 15 minutes for a knowledgeable developer to document then there is absolutely no chance of us figuring it out on our own, so the documentation becomes even more important!

but don't give anything actionable as to what more you want

I've told you exactly what I want, but perhaps you didn't read my previous comment (Phabricator has an annoying habit of hiding old comments). I'm not going to be didactic enough to insist that it has to be done a certain way, but if you want me to spell out a specific set of actionable instructions:

  1. Take my generic example from T178844#4508316
  2. Re-implement this under the new methodology
  3. Put both bits of code on UnknownAction hook as an example of migrating from the old style to the new style.
  4. Put the new bit of code on Manual:$wgActions as an example of how to implement a simple hook that covers the most common use-case (generating a custom page, possibly with some extra URL arguments).
  5. Put a note on Manual:$wgActions indicating that migration notes from the UnknownAction hook are available on the other page, so that these are discoverable.

Ideally there would also be some background information and more detailed breakdown of the various class functions and how they should be used, but if someone is able to complete the above items then I would be more than happy to mark this ticket as resolved.

In fact, if someone can do steps 1 & 2, I would happily do the rest myself.

Jdforrester-WMF closed this task as Declined.Feb 21 2019, 10:10 AM

Why was this declined?

Legoktm reopened this task as Open.Feb 23 2019, 6:59 AM
Legoktm assigned this task to HappyDog.

In the meantime, here is a generalised version of what I'm currently doing in the UnknownAction hook:

Here's the converted version (untested):

extension.json
{
	"Actions": {
		"example": "ExampleAction"
	}
}
ExampleAction.php
<?php

class ExampleAction extends FormlessAction {
	public function getName() {
		return 'example';
	}

	public function onView() {
		return null;
	}

	public function show() {
		$out = $this->getOutput();
		$request = $this->getRequest();
		$title = $this->page->getTitle();
		if ( $title->isTalkPage() ) {
			$title = $title->getSubjectPage();
		}

		$out->setPageTitle( 'Example Page Title' );
		$param = $request->getIntOrNull( 'example_param' );

		$out->addHTML( ... );
	}
}

In fact, if someone can do steps 1 & 2, I would happily do the rest myself.

Re-assigning.

HappyDog closed this task as Resolved.EditedMar 7 2019, 10:45 PM
  1. Done
  2. & 5. Done

Resolving as complete. Thanks for your help, @Legoktm.