Page MenuHomePhabricator

SpecialPage hooks are broken
Closed, ResolvedPublic

Description

Author: vickery.matthew

Description:
The hooks SpecialPageExecuteBeforePage and SpecialPageExecuteAfterPage are broken broken for extensions and anything else that subclasses SpecialPage.

There is a note in includes/SpecialPage.php where these hooks are called stating that it is broken for extensions, see below:

FIXME: these hooks are broken for extensions and anything else that subclasses SpecialPage.

I am attempting to modify the Special:Preferences page via an extension rather than directly within the core code. See my code below which is not working as expected:

$wgHooks['SpecialPageExecuteBeforePage'][] = 'CustomUserPreferencesPage';

function CustomUserPreferencesPage($specialpage, $par, $func) {

// Check if this hook has been called from the correct page
if ($specialpage->mName == 'Preferences') {
              
  global $IP ;
                      
  $specialpage->file($IP . "/extensions/CustomUserPreferencesPage/templates/CustomSpecialPreferences.php") ;
              
}
              
return true ;

}


Version: 1.10.x
Severity: blocker
URL: http://www.mediawiki.org/wiki/Manual:MediaWiki_hooks/SpecialPageExecuteBeforePage

Details

Reference
bz10719

Event Timeline

bzimport raised the priority of this task from to Medium.Nov 21 2014, 9:53 PM
bzimport set Reference to bz10719.
bzimport added a subscriber: Unknown Object (MLST).

These hooks are broken for pages that extends SpecialPage (like SpecialPreferences) because those classes override the SpecialPage execute function and never fire the hooks in their new execute function. You can probably work around this by inserting the hook firing bit back into the PreferencesForm->execute() function in the appropriate place:

function execute() {

$par = $func = '';
if ( ! wfRunHooks( 'SpecialPageExecuteBeforePage', array( &$this, &$par, &$func ) ) )
  return;

// all the rest of SpecialPreferences->execute() goes here.

}

robchur wrote:

The best solution to this, I guess, would be to move the hook execution elsewhere; extending SpecialPage and overriding the constructor and execute() method seems to be the preferred and recommended means of writing special pages now, so as it is, I doubt we're going to rewrite all the custom and core special pages in existence to call these hooks, and we shouldn't have to.

Jesus, this is complicated. Okay, it looks like you can subclass SpecialPage and still get these hooks to fire. To do this, you have to:

  1. Rename your subclass 'execute' function to something like 'run'.
  2. Specify a function name in your constructor that will be called when your subclass is executed.

If you do this, then SpecialPage->execute() will be called (instead of your subclass execute function) which will in turn call the function you specified which will call the run function of your special page. Using Brion's AskSql extension as an example, instead of:

/** Main class that define a new special page*/
class SpecialAsksql extends SpecialPage {

function SpecialAsksql() {

		SpecialPage::SpecialPage( 'Asksql', 'asksql' );

}

function execute( $par ) { ... }
etc

You would do this:

function efRunSpecialAsksql( $par ) {
SpecialAsksql::run( $par );
}

/** Main class that define a new special page*/
class SpecialAsksql extends SpecialPage {

function SpecialAsksql() {

		SpecialPage::SpecialPage( 'Asksql', 'asksql', true, 'efRunSpecialAsksql' );

}

function run( $par ) { ... }
etc

SpecialPages like SpecialPreferences don't actually subclass SpecialPage at all although they do have an execute function. Instead, when they are executed, a SpecialPage is created on demand which runs the default function name for SpecialPages (wfSpecial.$name). This global function then instantiates a new instance of PreferencesForm and calls it's execute function. Regardless, the hooks fire properly for SpecialPreferences and I assume for all the core SpecialPages. I'm going to update the documentation on http://www.mediawiki.org/wiki/Manual:Special_pages to explain some of this craziness. Maybe this bug should be closed once the documentation is clear.

This problem appears to be getting worse since some of the core special pages are being modified to subclass SpecialPage (r39873). This bug could be more or less fixed by moving the SpecialPage hook firing bits from SpecialPage->execute (which is only called if the "special page" doesn't subclass SpecialPage) to SpecialPage->executePath (which is called every time a "special page" is executed. The meaning of SpecialPageExecuteBeforePage and SpecialPageExecuteAfterPage would change a bit since they would be fired outside of the userCanExecute checks.

Does anything even use these?

I just removed the crap in r42015 all together.