mediawiki-extensions-Relate.../includes/Hooks.php
Isarra b31970084f Use SkinAfterContent hook to place cards in the DOM
This allows skins to determine where the cards appear in a consistent
fashion with other extensions, and require minimal special handling from
the extension itself.

Also change default settings as to when RelatedArticle cards will appear
- keep the whitelist to allow projects to configure their usage such as
in the case of only wanting it on mobile, etc, but now that it should
just work in any given skin, default it to on for all so that it's easier
for the average third party to install for whatever use.

Note that this default setting change will not affect WMF wikis, as
wmgRelatedArticlesFooterWhitelistedSkins is explicitly set in the
wmf-config regardless.

Bug: T181242
Depends-On: I5b0ad889e633fde88c392577ce5373c81fc5486a
Change-Id: Iebd759c0d1a536768d18953f372664df762d9e04
2019-07-26 19:28:49 +00:00

254 lines
7.6 KiB
PHP

<?php
namespace RelatedArticles;
use Parser;
use OutputPage;
use ParserOutput;
use MediaWiki\MediaWikiServices;
use Skin;
use User;
use DisambiguatorHooks;
use Title;
class Hooks {
/**
* Handler for the <code>MakeGlobalVariablesScript</code> hook.
*
* Sets the value of the <code>wgRelatedArticles</code> global variable
* to the list of related articles in the cached parser output.
*
* @param array &$vars variables to be added into the output of OutputPage::headElement.
* @param OutputPage $out OutputPage instance calling the hook
*/
public static function onMakeGlobalVariablesScript( &$vars, OutputPage $out ) {
$editorCuratedPages = $out->getProperty( 'RelatedArticles' );
if ( $editorCuratedPages ) {
$vars['wgRelatedArticles'] = $editorCuratedPages;
}
}
/**
* Uses the Disambiguator extension to test whether the page is a disambiguation page.
*
* If the Disambiguator extension isn't installed, then the test always fails, i.e. the page is
* never a disambiguation page.
*
* @return boolean
*/
private static function isDisambiguationPage( Title $title ) {
return \ExtensionRegistry::getInstance()->isLoaded( 'Disambiguator' ) &&
DisambiguatorHooks::isDisambiguationPage( $title );
}
/**
* Check whether the output page is a diff page
*
* @param OutputPage $out
* @return bool
*/
private static function isDiffPage( OutputPage $out ) {
$request = $out->getRequest();
$type = $request->getText( 'type' );
$diff = $request->getText( 'diff' );
$oldId = $request->getText( 'oldid' );
$isSpecialMobileDiff = $out->getTitle()->isSpecial( 'MobileDiff' );
return $type === 'revision' || $diff || $oldId || $isSpecialMobileDiff;
}
/**
* Is ReadMore allowed on skin?
*
* Some wikis may want to only enable the feature on some skins, so we'll only
* show it if the whitelist (`RelatedArticlesFooterWhitelistedSkins`
* configuration variable) is empty or the skin is listed.
*
* @param User $user
* @param Skin $skin
* @return bool
*/
private static function isReadMoreAllowedOnSkin( User $user, Skin $skin ) {
$config = MediaWikiServices::getInstance()->getConfigFactory()
->makeConfig( 'RelatedArticles' );
$skins = $config->get( 'RelatedArticlesFooterWhitelistedSkins' );
$skinName = $skin->getSkinName();
return empty( $skins ) || in_array( $skinName, $skins );
}
/**
* Handler for the <code>BeforePageDisplay</code> hook.
*
* Adds the <code>ext.relatedArticles.readMore.bootstrap</code> module
* to the output when:
*
* <ol>
* <li>On mobile, the output is being rendered with
* <code>SkinMinervaBeta<code></li>
* <li>The page is in mainspace</li>
* <li>The action is 'view'</li>
* <li>The page is not the Main Page</li>
* <li>The page is not a disambiguation page</li>
* <li>The page is not a diff page</li>
* <li>The feature is allowed on the skin (see isReadMoreAllowedOnSkin() above)</li>
* </ol>
*
* @param OutputPage $out The OutputPage object
* @param Skin $skin Skin object that will be used to generate the page
* @return bool Always <code>true</code>
*/
public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) {
$title = $out->getContext()->getTitle();
$action = $out->getRequest()->getText( 'action', 'view' );
if (
$title->inNamespace( NS_MAIN ) &&
// T120735
$action === 'view' &&
!$title->isMainPage() &&
!self::isDisambiguationPage( $title ) &&
!self::isDiffPage( $out ) &&
self::isReadMoreAllowedOnSkin( $out->getUser(), $skin )
) {
$out->addModules( [ 'ext.relatedArticles.readMore.bootstrap' ] );
}
return true;
}
/**
* ResourceLoaderGetConfigVars hook handler for setting a config variable
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderGetConfigVars
*
* @param array &$vars Array of variables to be added into the output of the startup module.
* @return bool
*/
public static function onResourceLoaderGetConfigVars( &$vars ) {
$config = MediaWikiServices::getInstance()->getConfigFactory()
->makeConfig( 'RelatedArticles' );
$limit = $config->get( 'RelatedArticlesCardLimit' );
$vars['wgRelatedArticlesCardLimit'] = $limit;
if ( $limit < 1 || $limit > 20 ) {
throw new \RuntimeException(
'The value of wgRelatedArticlesCardLimit is not valid. It should be between 1 and 20.'
);
}
return true;
}
/**
* Handler for the <code>ParserFirstCallInit</code> hook.
*
* Registers the <code>related</code> parser function (see
* {@see Hooks::onFuncRelated}).
*
* @param Parser &$parser Parser object
* @return bool Always <code>true</code>
*/
public static function onParserFirstCallInit( Parser &$parser ) {
$parser->setFunctionHook( 'related', 'RelatedArticles\\Hooks::onFuncRelated' );
return true;
}
/**
* The <code>related</code> parser function.
*
* Appends the arguments to the internal list so that it can be used
* more that once per page.
* We don't use setProperty here is there is no need
* to store it as a page prop in the database, only in the cache.
*
* @todo Test for uniqueness
* @param Parser $parser Parser object
* @param string ...$args
*
* @return string Always <code>''</code>
*/
public static function onFuncRelated( Parser $parser, ...$args ) {
$parserOutput = $parser->getOutput();
$relatedPages = $parserOutput->getExtensionData( 'RelatedArticles' );
if ( !$relatedPages ) {
$relatedPages = [];
}
// Add all the related pages passed by the parser function
// {{#related:Test with read more|Foo|Bar}}
foreach ( $args as $relatedPage ) {
$relatedPages[] = $relatedPage;
}
$parserOutput->setExtensionData( 'RelatedArticles', $relatedPages );
return '';
}
/**
* Passes the related pages list from the cached parser output
* object to the output page for rendering.
*
* The list of related pages will be retrieved using
* <code>ParserOutput#getExtensionData</code>.
*
* @param OutputPage &$out the OutputPage object
* @param ParserOutput $parserOutput ParserOutput object
* @return bool Always <code>true</code>
*/
public static function onOutputPageParserOutput( OutputPage &$out, ParserOutput $parserOutput ) {
$related = $parserOutput->getExtensionData( 'RelatedArticles' );
if ( $related ) {
$out->setProperty( 'RelatedArticles', $related );
}
return true;
}
/**
* Register QUnit tests.
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderTestModules
*
* @param array &$modules array of javascript testing modules
* @param \ResourceLoader &$rl Resource Loader
* @return bool
*/
public static function onResourceLoaderTestModules( &$modules, &$rl ) {
$boilerplate = [
'localBasePath' => __DIR__ . '/../tests/qunit/',
'remoteExtPath' => 'RelatedArticles/tests/qunit',
'targets' => [ 'desktop', 'mobile' ],
];
$modules['qunit']['ext.relatedArticles.cards.tests'] = $boilerplate + [
'dependencies' => [
'ext.relatedArticles.cards'
],
'scripts' => [
'ext.relatedArticles.cards/CardModel.test.js',
'ext.relatedArticles.cards/CardView.test.js',
]
];
$modules['qunit']['ext.relatedArticles.readMore.gateway.tests'] = $boilerplate + [
'scripts' => [
'ext.relatedArticles.readMore.gateway/RelatedPagesGateway.test.js',
],
'dependencies' => [
'ext.relatedArticles.readMore.gateway',
],
];
return true;
}
/**
* Create container for ReadMore cards so that they're correctly placed in all skins.
*
* @param string &$data
* @param Skin $skin
*/
public static function onSkinAfterContent( &$data, Skin $skin ) {
$data .= \Html::element( 'div', [ 'class' => 'read-more-container' ] );
}
}