2015-10-02 19:46:48 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace RelatedArticles;
|
|
|
|
|
2023-09-27 19:02:11 +00:00
|
|
|
use MediaWiki\Config\Config;
|
2024-02-07 18:08:55 +00:00
|
|
|
use MediaWiki\Config\ConfigFactory;
|
2024-06-10 18:16:42 +00:00
|
|
|
use MediaWiki\Context\IContextSource;
|
2024-02-05 18:19:28 +00:00
|
|
|
use MediaWiki\Extension\Disambiguator\Lookup;
|
2023-09-27 19:02:11 +00:00
|
|
|
use MediaWiki\Hook\ParserFirstCallInitHook;
|
|
|
|
use MediaWiki\Hook\SkinAfterContentHook;
|
2024-01-05 20:22:56 +00:00
|
|
|
use MediaWiki\Html\Html;
|
2024-06-10 18:16:42 +00:00
|
|
|
use MediaWiki\Output\Hook\BeforePageDisplayHook;
|
|
|
|
use MediaWiki\Output\Hook\OutputPageParserOutputHook;
|
2024-01-05 20:22:56 +00:00
|
|
|
use MediaWiki\Output\OutputPage;
|
2024-06-10 18:16:42 +00:00
|
|
|
use MediaWiki\Parser\Parser;
|
2024-01-05 20:22:56 +00:00
|
|
|
use MediaWiki\Parser\ParserOutput;
|
2023-09-27 19:02:11 +00:00
|
|
|
use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
|
2023-08-19 04:18:57 +00:00
|
|
|
use MediaWiki\Title\Title;
|
2017-08-09 19:45:03 +00:00
|
|
|
use Skin;
|
2015-10-02 19:46:48 +00:00
|
|
|
|
2023-09-27 19:02:11 +00:00
|
|
|
class Hooks implements
|
|
|
|
ParserFirstCallInitHook,
|
|
|
|
OutputPageParserOutputHook,
|
|
|
|
BeforePageDisplayHook,
|
|
|
|
ResourceLoaderGetConfigVarsHook,
|
|
|
|
SkinAfterContentHook
|
|
|
|
{
|
2015-10-02 19:46:48 +00:00
|
|
|
|
2024-02-07 18:08:55 +00:00
|
|
|
private Config $relatedArticlesConfig;
|
|
|
|
|
2024-02-05 18:19:28 +00:00
|
|
|
/** Either a Lookup from the Disambiguator extension, or null if that is not installed */
|
|
|
|
private ?Lookup $disambiguatorLookup;
|
|
|
|
|
2024-02-07 18:08:55 +00:00
|
|
|
public function __construct( ConfigFactory $configFactory, ?Lookup $disambiguatorLookup ) {
|
|
|
|
$this->relatedArticlesConfig = $configFactory->makeConfig( 'RelatedArticles' );
|
2024-02-05 18:19:28 +00:00
|
|
|
$this->disambiguatorLookup = $disambiguatorLookup;
|
|
|
|
}
|
|
|
|
|
2017-08-09 19:45:03 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2020-02-29 21:37:34 +00:00
|
|
|
* @param Title $title
|
2020-01-15 06:11:52 +00:00
|
|
|
* @return bool
|
2017-08-09 19:45:03 +00:00
|
|
|
*/
|
2024-02-05 18:19:28 +00:00
|
|
|
private function isDisambiguationPage( Title $title ) {
|
|
|
|
return $this->disambiguatorLookup &&
|
|
|
|
$this->disambiguatorLookup->isDisambiguationPage( $title );
|
2017-08-09 19:45:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether the output page is a diff page
|
|
|
|
*
|
2023-09-22 20:51:28 +00:00
|
|
|
* @param IContextSource $context
|
2017-08-09 19:45:03 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
2023-09-22 20:51:28 +00:00
|
|
|
private static function isDiffPage( IContextSource $context ) {
|
|
|
|
$request = $context->getRequest();
|
|
|
|
$type = $request->getRawVal( 'type' );
|
|
|
|
$diff = $request->getCheck( 'diff' );
|
|
|
|
$oldId = $request->getCheck( 'oldid' );
|
2017-08-09 19:45:03 +00:00
|
|
|
|
2024-04-03 02:01:24 +00:00
|
|
|
return $type === 'revision' || $diff || $oldId;
|
2017-08-09 19:45:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is ReadMore allowed on skin?
|
|
|
|
*
|
2019-06-20 13:29:29 +00:00
|
|
|
* Some wikis may want to only enable the feature on some skins, so we'll only
|
2021-04-19 00:26:58 +00:00
|
|
|
* show it if the allow list (`RelatedArticlesFooterAllowedSkins`
|
2019-06-20 13:29:29 +00:00
|
|
|
* configuration variable) is empty or the skin is listed.
|
2017-08-09 19:45:03 +00:00
|
|
|
*
|
|
|
|
* @param Skin $skin
|
|
|
|
* @return bool
|
|
|
|
*/
|
2024-02-07 18:08:55 +00:00
|
|
|
private function isReadMoreAllowedOnSkin( Skin $skin ) {
|
|
|
|
$skins = $this->relatedArticlesConfig->get( 'RelatedArticlesFooterAllowedSkins' );
|
2017-08-09 19:45:03 +00:00
|
|
|
$skinName = $skin->getSkinName();
|
2023-10-21 19:58:11 +00:00
|
|
|
return !$skins || in_array( $skinName, $skins );
|
2017-08-09 19:45:03 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 22:11:51 +00:00
|
|
|
/**
|
|
|
|
* Can the page show related articles?
|
|
|
|
*
|
|
|
|
* @param Skin $skin
|
|
|
|
* @return bool
|
|
|
|
*/
|
2024-02-05 18:19:28 +00:00
|
|
|
private function hasRelatedArticles( Skin $skin ): bool {
|
2023-09-22 20:51:28 +00:00
|
|
|
$title = $skin->getTitle();
|
2024-10-02 10:35:28 +00:00
|
|
|
$action = $skin->getRequest()->getRawVal( 'action' ) ?? 'view';
|
2022-05-10 22:11:51 +00:00
|
|
|
return $title->inNamespace( NS_MAIN ) &&
|
|
|
|
// T120735
|
|
|
|
$action === 'view' &&
|
|
|
|
!$title->isMainPage() &&
|
|
|
|
$title->exists() &&
|
2023-09-22 20:51:28 +00:00
|
|
|
!self::isDiffPage( $skin ) &&
|
2024-02-05 18:19:28 +00:00
|
|
|
!$this->isDisambiguationPage( $title ) &&
|
2024-02-07 18:08:55 +00:00
|
|
|
$this->isReadMoreAllowedOnSkin( $skin );
|
2022-05-10 22:11:51 +00:00
|
|
|
}
|
|
|
|
|
2017-08-09 19:45:03 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-09-27 19:02:11 +00:00
|
|
|
public function onBeforePageDisplay( $out, $skin ): void {
|
2024-02-05 18:19:28 +00:00
|
|
|
if ( $this->hasRelatedArticles( $skin ) ) {
|
2017-08-09 19:45:03 +00:00
|
|
|
$out->addModules( [ 'ext.relatedArticles.readMore.bootstrap' ] );
|
2021-11-04 19:27:36 +00:00
|
|
|
$out->addModuleStyles( [ 'ext.relatedArticles.styles' ] );
|
2017-08-09 19:45:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2023-09-27 19:02:11 +00:00
|
|
|
* @param string $skin
|
|
|
|
* @param Config $config
|
2017-08-09 19:45:03 +00:00
|
|
|
*/
|
2023-09-27 19:02:11 +00:00
|
|
|
public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void {
|
2024-02-07 18:08:55 +00:00
|
|
|
$limit = $this->relatedArticlesConfig->get( 'RelatedArticlesCardLimit' );
|
2017-08-09 19:45:03 +00:00
|
|
|
$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.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-02 19:46:48 +00:00
|
|
|
/**
|
|
|
|
* Handler for the <code>ParserFirstCallInit</code> hook.
|
|
|
|
*
|
|
|
|
* Registers the <code>related</code> parser function (see
|
|
|
|
* {@see Hooks::onFuncRelated}).
|
|
|
|
*
|
2019-11-15 06:17:38 +00:00
|
|
|
* @param Parser $parser Parser object
|
2015-10-02 19:46:48 +00:00
|
|
|
*/
|
2023-09-27 19:02:11 +00:00
|
|
|
public function onParserFirstCallInit( $parser ) {
|
2023-09-20 18:59:19 +00:00
|
|
|
$parser->setFunctionHook( 'related', [ self::class, 'onFuncRelated' ] );
|
2015-10-02 19:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The <code>related</code> parser function.
|
|
|
|
*
|
|
|
|
* Appends the arguments to the internal list so that it can be used
|
|
|
|
* more that once per page.
|
2015-10-08 00:21:13 +00:00
|
|
|
* 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.
|
2015-10-02 19:46:48 +00:00
|
|
|
*
|
|
|
|
* @todo Test for uniqueness
|
2017-07-13 15:22:26 +00:00
|
|
|
* @param Parser $parser Parser object
|
2019-03-10 02:36:30 +00:00
|
|
|
* @param string ...$args
|
2015-10-02 19:46:48 +00:00
|
|
|
*
|
|
|
|
* @return string Always <code>''</code>
|
|
|
|
*/
|
2019-03-10 02:36:30 +00:00
|
|
|
public static function onFuncRelated( Parser $parser, ...$args ) {
|
2015-10-08 00:21:13 +00:00
|
|
|
$parserOutput = $parser->getOutput();
|
2015-11-14 00:03:39 +00:00
|
|
|
// Add all the related pages passed by the parser function
|
2015-10-08 00:21:13 +00:00
|
|
|
// {{#related:Test with read more|Foo|Bar}}
|
2015-11-14 00:03:39 +00:00
|
|
|
foreach ( $args as $relatedPage ) {
|
2024-07-30 19:02:44 +00:00
|
|
|
$parserOutput->appendJsConfigVar( 'wgRelatedArticles', $relatedPage );
|
2015-10-02 19:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-11-14 00:03:39 +00:00
|
|
|
* Passes the related pages list from the cached parser output
|
2015-10-14 10:29:36 +00:00
|
|
|
* object to the output page for rendering.
|
|
|
|
*
|
2015-11-14 00:03:39 +00:00
|
|
|
* The list of related pages will be retrieved using
|
2015-11-10 17:03:06 +00:00
|
|
|
* <code>ParserOutput#getExtensionData</code>.
|
2015-10-02 19:46:48 +00:00
|
|
|
*
|
2024-07-30 19:02:44 +00:00
|
|
|
* @param OutputPage $outputPage the OutputPage object
|
2017-07-13 15:22:26 +00:00
|
|
|
* @param ParserOutput $parserOutput ParserOutput object
|
2015-10-02 19:46:48 +00:00
|
|
|
*/
|
2024-07-30 19:02:44 +00:00
|
|
|
public function onOutputPageParserOutput( $outputPage, $parserOutput ): void {
|
|
|
|
// Backwards-compatibility with old cached content (T371421)
|
|
|
|
// This hook can be removed once this is no longer needed.
|
|
|
|
$related = $parserOutput->getExtensionData( 'RelatedArticles' ) ?? [];
|
|
|
|
$outputPage->addJsConfigVars( 'wgRelatedArticlesCompat', $related );
|
2015-10-02 19:46:48 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 13:29:29 +00:00
|
|
|
/**
|
|
|
|
* Create container for ReadMore cards so that they're correctly placed in all skins.
|
|
|
|
*
|
|
|
|
* @param string &$data
|
|
|
|
* @param Skin $skin
|
|
|
|
*/
|
2023-09-27 19:02:11 +00:00
|
|
|
public function onSkinAfterContent( &$data, $skin ) {
|
2024-02-05 18:19:28 +00:00
|
|
|
if ( $this->hasRelatedArticles( $skin ) ) {
|
2024-01-05 20:22:56 +00:00
|
|
|
$data .= Html::element( 'div', [ 'class' => 'read-more-container' ] );
|
2022-05-10 22:11:51 +00:00
|
|
|
}
|
2019-06-20 13:29:29 +00:00
|
|
|
}
|
2015-10-02 19:46:48 +00:00
|
|
|
}
|