Re-apply: Convert to new hook system (Workshop)

This converts the CategtoryTree extension to the new (MW1.35) hook system.
The patch was written live during a hackathon workshop.
A recording of the workshop is available at
<https://www.youtube.com/watch?v=ZOj44Rbh0tM&t>.

Re-applying after fixing: premature access to $wgRequest
Restores change: Ie52c393af378a980a2dac4ae7076fd6c016a8e0e
Reverts revert: Ieee300c7b35b7069bd7e781610c915f2ecae1bf1

NOTE: the "mode" parameter for category pages seems to be broken,
as the mode is not proeprly propoagated to dynamic requests.
This was already the case before this change, and this change
does not try to fix that issue. I was tempted to just remove
the feature entirely, but T137812 inidcates that it may be useful to
some. I will comment there to see if this should be fiexed or removed.

Bug: T282110
Bug: T271011
Change-Id: Ic3ee05e9dc382f4ada5cfeb9ff5d9a69249cc60d
This commit is contained in:
daniel 2021-05-22 15:43:11 +02:00
parent 31314b9332
commit ece310d7be
3 changed files with 106 additions and 52 deletions

View file

@ -11,9 +11,6 @@
"ConfigRegistry": {
"categorytree": "GlobalVarConfig::newInstance"
},
"ExtensionFunctions": [
"MediaWiki\\Extension\\CategoryTree\\Hooks::initialize"
],
"SpecialPages": {
"CategoryTree": {
"class": "MediaWiki\\Extension\\CategoryTree\\CategoryTreePage",
@ -84,16 +81,23 @@
"localBasePath": "modules",
"remoteExtPath": "CategoryTree/modules"
},
"HookHandlers": {
"default": {
"class": "MediaWiki\\Extension\\CategoryTree\\Hooks",
"services": [ "DBLoadBalancer", "MainConfig" ]
}
},
"Hooks": {
"ArticleFromTitle": "MediaWiki\\Extension\\CategoryTree\\Hooks::articleFromTitle",
"SpecialTrackingCategories::preprocess": "MediaWiki\\Extension\\CategoryTree\\Hooks::onSpecialTrackingCategoriesPreprocess",
"SpecialTrackingCategories::generateCatLink": "MediaWiki\\Extension\\CategoryTree\\Hooks::onSpecialTrackingCategoriesGenerateCatLink",
"SkinBuildSidebar": "MediaWiki\\Extension\\CategoryTree\\Hooks::onSkinBuildSidebar",
"ParserFirstCallInit": "MediaWiki\\Extension\\CategoryTree\\Hooks::setHooks",
"OutputPageMakeCategoryLinks": "MediaWiki\\Extension\\CategoryTree\\Hooks::outputPageMakeCategoryLinks",
"BeforePageDisplay": "MediaWiki\\Extension\\CategoryTree\\Hooks::addHeaders",
"BeforePageDisplayMobile": "MediaWiki\\Extension\\CategoryTree\\Hooks::addHeaders",
"OutputPageParserOutput": "MediaWiki\\Extension\\CategoryTree\\Hooks::parserOutput"
"MediaWikiServices": "default",
"ArticleFromTitle": "default",
"SpecialTrackingCategories::preprocess": "default",
"SpecialTrackingCategories::generateCatLink": "default",
"SkinBuildSidebar": "default",
"ParserFirstCallInit": "default",
"OutputPageMakeCategoryLinks": "default",
"BeforePageDisplay": "default",
"OutputPageParserOutput": "default",
"BeforePageDisplayMobile": "MediaWiki\\Extension\\CategoryTree\\Hooks::addHeaders"
},
"config": {
"CategoryTreeMaxChildren": {

View file

@ -40,7 +40,14 @@ class CategoryTreeCategoryViewer extends CategoryViewer {
CategoryTree::setHeaders( $this->getOutput() );
}
$this->categorytree = new CategoryTree( $this->getConfig()->get( 'CategoryTreeCategoryPageOptions' ) );
$options = $this->getConfig()->get( 'CategoryTreeCategoryPageOptions' );
$mode = $this->getRequest()->getVal( 'mode' );
if ( $mode !== null ) {
$options['mode'] = CategoryTree::decodeMode( $mode );
}
$this->categorytree = new CategoryTree( $options );
}
return $this->categorytree;

View file

@ -26,7 +26,19 @@ namespace MediaWiki\Extension\CategoryTree;
use Article;
use Category;
use Config;
use Html;
use IContextSource;
use MediaWiki\Hook\BeforePageDisplayHook;
use MediaWiki\Hook\MediaWikiServicesHook;
use MediaWiki\Hook\OutputPageMakeCategoryLinksHook;
use MediaWiki\Hook\OutputPageParserOutputHook;
use MediaWiki\Hook\ParserFirstCallInitHook;
use MediaWiki\Hook\SkinBuildSidebarHook;
use MediaWiki\Hook\SpecialTrackingCategories__generateCatLinkHook;
use MediaWiki\Hook\SpecialTrackingCategories__preprocessHook;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\Hook\ArticleFromTitleHook;
use OutputPage;
use Parser;
use ParserOutput;
@ -35,15 +47,45 @@ use Sanitizer;
use Skin;
use SpecialPage;
use Title;
use Wikimedia\Rdbms\ILoadBalancer;
/**
* Hooks for the CategoryTree extension, an AJAX based gadget
* to display the category structure of a wiki
*
* @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
*/
class Hooks {
class Hooks implements
ArticleFromTitleHook,
SpecialTrackingCategories__preprocessHook,
SpecialTrackingCategories__generateCatLinkHook,
BeforePageDisplayHook,
OutputPageParserOutputHook,
MediaWikiServicesHook,
SkinBuildSidebarHook,
ParserFirstCallInitHook,
OutputPageMakeCategoryLinksHook
{
private const EXTENSION_DATA_FLAG = 'CategoryTree';
/** @var ILoadBalancer */
private $loadBalancer;
/**
* @var Config
*/
private $config;
/**
* @param ILoadBalancer $loadBalancer
* @param Config $config
*/
public function __construct( ILoadBalancer $loadBalancer, Config $config ) {
$this->loadBalancer = $loadBalancer;
$this->config = $config;
}
/**
* @internal For use by CategoryTreeCategoryViewer and CategoryTreePage only!
* @return bool
@ -56,44 +98,35 @@ class Hooks {
/**
* Adjusts config once MediaWiki is fully initialised
* TODO: Don't do this, lazy initialize the config
* @param MediaWikiServices $services
*/
public static function initialize() {
global $wgRequest;
public function onMediaWikiServices( $services ) {
global $wgCategoryTreeDefaultOptions, $wgCategoryTreeDefaultMode;
global $wgCategoryTreeCategoryPageOptions, $wgCategoryTreeCategoryPageMode;
global $wgCategoryTreeOmitNamespace;
if ( !isset( $wgCategoryTreeDefaultOptions['mode'] )
|| $wgCategoryTreeDefaultOptions['mode'] === null
) {
if ( !isset( $wgCategoryTreeDefaultOptions['mode'] ) ) {
$wgCategoryTreeDefaultOptions['mode'] = $wgCategoryTreeDefaultMode;
}
if ( !isset( $wgCategoryTreeDefaultOptions['hideprefix'] )
|| $wgCategoryTreeDefaultOptions['hideprefix'] === null
) {
if ( !isset( $wgCategoryTreeDefaultOptions['hideprefix'] ) ) {
$wgCategoryTreeDefaultOptions['hideprefix'] = $wgCategoryTreeOmitNamespace;
}
if ( !isset( $wgCategoryTreeCategoryPageOptions['mode'] )
|| $wgCategoryTreeCategoryPageOptions['mode'] === null
) {
$mode = $wgRequest->getVal( 'mode' );
$wgCategoryTreeCategoryPageOptions['mode'] = ( $mode )
? CategoryTree::decodeMode( $mode ) : $wgCategoryTreeCategoryPageMode;
if ( !isset( $wgCategoryTreeCategoryPageOptions['mode'] ) ) {
$wgCategoryTreeCategoryPageOptions['mode'] = $wgCategoryTreeCategoryPageMode;
}
}
/**
* @param Parser $parser
*/
public static function setHooks( Parser $parser ) {
global $wgCategoryTreeAllowTag;
if ( !$wgCategoryTreeAllowTag ) {
public function onParserFirstCallInit( $parser ) {
if ( !$this->config->get( 'CategoryTreeAllowTag' ) ) {
return;
}
$parser->setHook( 'categorytree', [ self::class, 'parserHook' ] );
$parser->setFunctionHook( 'categorytree', [ self::class, 'parserFunction' ] );
$parser->setHook( 'categorytree', [ $this, 'parserHook' ] );
$parser->setFunctionHook( 'categorytree', [ $this, 'parserFunction' ] );
}
/**
@ -103,7 +136,7 @@ class Hooks {
* @param string ...$params
* @return array|string
*/
public static function parserFunction( Parser $parser, ...$params ) {
public function parserFunction( Parser $parser, ...$params ) {
// first user-supplied parameter must be category name
if ( !$params ) {
// no category specified, return nothing
@ -131,7 +164,7 @@ class Hooks {
$cat . Html::closeElement( 'categorytree' );
} else {
// now handle just like a <categorytree> tag
$html = self::parserHook( $cat, $argv, $parser );
$html = $this->parserHook( $cat, $argv, $parser );
return [ $html, 'noparse' => true, 'isHTML' => true ];
}
}
@ -142,7 +175,7 @@ class Hooks {
* @param Skin $skin
* @param array &$sidebar
*/
public static function onSkinBuildSidebar( Skin $skin, array &$sidebar ) {
public function onSkinBuildSidebar( $skin, &$sidebar ) {
global $wgCategoryTreeSidebarRoot, $wgCategoryTreeSidebarOptions;
if ( !$wgCategoryTreeSidebarRoot ) {
@ -166,7 +199,7 @@ class Hooks {
* @param bool $allowMissing
* @return bool|string
*/
public static function parserHook(
public function parserHook(
$cat,
array $argv,
Parser $parser = null,
@ -203,7 +236,7 @@ class Hooks {
* @param OutputPage $outputPage
* @param ParserOutput $parserOutput
*/
public static function parserOutput( OutputPage $outputPage, ParserOutput $parserOutput ) {
public function onOutputPageParserOutput( $outputPage, $parserOutput ) : void {
if ( self::shouldForceHeaders() ) {
// Skip, we've already set the headers unconditionally
return;
@ -213,6 +246,17 @@ class Hooks {
}
}
/**
* This hook is called prior to outputting a page.
*
* @param OutputPage $out
* @param Skin $skin
* @return void This hook must not abort, it must return no value
*/
public function onBeforePageDisplay( $out, $skin ) : void {
self::addHeaders( $out );
}
/**
* BeforePageDisplay and BeforePageDisplayMobile hooks.
* These hooks are used when $wgCategoryTreeForceHeaders is set.
@ -231,8 +275,10 @@ class Hooks {
*
* @param Title $title
* @param Article|null &$article Article (object) that will be returned
* @param IContextSource $context
* @return bool|void True or no return value to continue or false to abort
*/
public static function articleFromTitle( Title $title, Article &$article = null ) {
public function onArticleFromTitle( $title, &$article, $context ) {
if ( $title->getNamespace() == NS_CATEGORY ) {
$article = new CategoryTreeCategoryPage( $title );
}
@ -245,11 +291,7 @@ class Hooks {
* @param array &$links
* @return bool
*/
public static function outputPageMakeCategoryLinks(
OutputPage $out,
array $categories,
array &$links
) {
public function onOutputPageMakeCategoryLinks( $out, $categories, &$links ) {
global $wgCategoryTreePageCategoryOptions, $wgCategoryTreeHijackPageCategories;
if ( !$wgCategoryTreeHijackPageCategories ) {
@ -258,7 +300,7 @@ class Hooks {
}
foreach ( $categories as $category => $type ) {
$links[$type][] = self::parserHook( $category, $wgCategoryTreePageCategoryOptions, null, null, true );
$links[$type][] = $this->parserHook( $category, $wgCategoryTreePageCategoryOptions, null, null, true );
}
CategoryTree::setHeaders( $out );
@ -289,8 +331,9 @@ class Hooks {
* @param array $trackingCategories [ 'msg' => Title, 'cats' => Title[] ]
* @phan-param array<string,array{msg:Title,cats:Title[]}> $trackingCategories
*/
public static function onSpecialTrackingCategoriesPreprocess(
SpecialPage $specialPage, array $trackingCategories
public function onSpecialTrackingCategories__preprocess(
$specialPage,
$trackingCategories
) {
$categoryDbKeys = [];
foreach ( $trackingCategories as $catMsg => $data ) {
@ -300,7 +343,7 @@ class Hooks {
}
$categories = [];
if ( $categoryDbKeys ) {
$dbr = wfGetDB( DB_REPLICA );
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
$res = $dbr->select(
'category',
[ 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ],
@ -321,16 +364,16 @@ class Hooks {
* @param Title $catTitle Title object of the linked category
* @param string &$html Result html
*/
public static function onSpecialTrackingCategoriesGenerateCatLink(
SpecialPage $specialPage, Title $catTitle, &$html
public function onSpecialTrackingCategories__generateCatLink( $specialPage,
$catTitle, &$html
) {
if ( !isset( $specialPage->categoryTreeCategories ) ) {
return;
}
$cat = null;
if ( isset( $specialPage->categoryTreeCategories[$catTitle->getDbKey()] ) ) {
$cat = $specialPage->categoryTreeCategories[$catTitle->getDbKey()];
if ( isset( $specialPage->categoryTreeCategories[$catTitle->getDBkey()] ) ) {
$cat = $specialPage->categoryTreeCategories[$catTitle->getDBkey()];
}
$html .= CategoryTree::createCountString( $specialPage->getContext(), $cat, 0 );