wikimedia/mediawiki-extensions-Wikibase

View on GitHub
client/includes/ClientHooks.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace Wikibase\Client;

use CirrusSearch\SearchConfig;
use MediaWiki\MediaWikiServices;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use Skin;
use Wikibase\Client\DataAccess\Scribunto\WikibaseEntityLibrary;
use Wikibase\Client\DataAccess\Scribunto\WikibaseLibrary;
use Wikibase\Client\Hooks\SkinAfterBottomScriptsHandler;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityIdParsingException;
use Wikibase\Lib\ContentLanguages;
use Wikibase\Lib\Formatters\AutoCommentFormatter;
use Wikibase\Lib\StaticContentLanguages;
use Wikibase\Lib\UnionContentLanguages;
use Wikibase\Lib\WikibaseContentLanguages;

/**
 * File defining the hook handlers for the Wikibase Client extension.
 *
 * @license GPL-2.0-or-later
 */
final class ClientHooks {

    /**
     * @see NamespaceChecker::isWikibaseEnabled
     *
     * @param int $namespace
     *
     * @return bool
     */
    protected static function isWikibaseEnabled( $namespace ) {
        return WikibaseClient::getNamespaceChecker()->isWikibaseEnabled( $namespace );
    }

    /**
     * External library for Scribunto
     *
     * @param string $engine
     * @param string[] &$extraLibraries
     */
    public static function onScribuntoExternalLibraries( $engine, array &$extraLibraries ) {
        $allowDataTransclusion = WikibaseClient::getSettings()->getSetting( 'allowDataTransclusion' );
        if ( $engine == 'lua' && $allowDataTransclusion === true ) {
            $extraLibraries['mw.wikibase'] = WikibaseLibrary::class;
            $extraLibraries['mw.wikibase.entity'] = WikibaseEntityLibrary::class;
        }
    }

    /**
     * Handler for the FormatAutocomments hook, implementing localized formatting
     * for machine readable autocomments generated by SummaryFormatter.
     *
     * @param string &$comment reference to the autocomment text
     * @param bool $pre true if there is content before the autocomment
     * @param string $auto the autocomment unformatted
     * @param bool $post true if there is content after the autocomment
     * @param Title|null $title use for further information
     * @param bool $local shall links be generated locally or globally
     * @param string|null $wikiId The ID of the wiki the comment applies to, if not the local wiki.
     */
    public static function onFormat( &$comment, $pre, $auto, $post, $title, $local, $wikiId = null ) {
        $repoId = WikibaseClient::getSettings()->getSetting( 'repoSiteId' );

        // Only do special formatting for comments from a wikibase repo.
        // XXX: what to do if the local wiki is the repo? For entity pages, RepoHooks has a handler.
        // But what to do for other pages? Note that if the local wiki is the repo, $repoId will be
        // false, and $wikiId will be null.
        if ( $wikiId !== $repoId ) {
            return;
        }

        $formatter = new AutoCommentFormatter(
            MediaWikiServices::getInstance()->getContentLanguage(),
            [ 'wikibase-entity' ]
        );
        $formattedComment = $formatter->formatAutoComment( $auto );

        if ( is_string( $formattedComment ) ) {
            $comment = $formatter->wrapAutoComment( $pre, $formattedComment, $post );
        }
    }

    /**
     * Build 'Wikidata item' link for later addition to the toolbox section of the sidebar
     *
     * @param Skin $skin
     *
     * @return string[]|null Array of link elements or Null if link cannot be created.
     */
    public static function buildWikidataItemLink( Skin $skin ): ?array {
        $title = $skin->getTitle();
        $idString = $skin->getOutput()->getProperty( 'wikibase_item' );
        $entityId = null;

        if ( $idString !== null ) {
            $entityIdParser = WikibaseClient::getEntityIdParser();
            $entityId = $entityIdParser->parse( $idString );
        } elseif ( $title &&
            $skin->getActionName() !== 'view' && $title->exists()
        ) {
            // Try to load the item ID from Database, but only do so on non-article views,
            // (where the article's OutputPage isn't available to us).
            $entityId = self::getEntityIdForTitle( $title );
        }

        if ( $entityId !== null ) {
            $repoLinker = WikibaseClient::getRepoLinker();

            return [
                // Warning: This id is misleading; the 't' refers to the link's original place in the toolbox,
                // it now lives in the other projects section, but we must keep the 't' for compatibility with gadgets.
                'id' => 't-wikibase',
                'icon' => 'logoWikidata',
                'text' => $skin->msg( 'wikibase-dataitem' )->text(),
                'href' => $repoLinker->getEntityUrl( $entityId ),
            ];
        }

        return null;
    }

    /**
     * @param Title $title
     * @return EntityId|null
     */
    private static function getEntityIdForTitle( Title $title ): ?EntityId {
        if ( !self::isWikibaseEnabled( $title->getNamespace() ) ) {
            return null;
        }

        $entityIdLookup = WikibaseClient::getEntityIdLookup();
        return $entityIdLookup->getEntityIdForTitle( $title );
    }

    /**
     * Adds a preference for showing or hiding Wikidata entries in recent changes
     *
     * @param User $user
     * @param array[] &$prefs
     */
    public static function onGetPreferences( User $user, array &$prefs ) {
        $settings = WikibaseClient::getSettings();

        if ( !$settings->getSetting( 'showExternalRecentChanges' ) ) {
            return;
        }

        $prefs['rcshowwikidata'] = [
            'type' => 'toggle',
            'label-message' => 'wikibase-rc-show-wikidata-pref',
            'section' => 'rc/advancedrc',
        ];

        $prefs['wlshowwikibase'] = [
            'type' => 'toggle',
            'label-message' => 'wikibase-watchlist-show-changes-pref',
            'section' => 'watchlist/advancedwatchlist',
        ];
    }

    /**
     * Add morelikewithwikibase keyword.
     * @param SearchConfig $config
     * @param array &$extraFeatures
     */
    public static function onCirrusSearchAddQueryFeatures(
        $config,
        array &$extraFeatures
    ) {
        $extraFeatures[] = new MoreLikeWikibase( $config );
    }

    /**
     * Injects a Wikidata inline JSON-LD script schema for search engine optimization.
     *
     * @param Skin $skin
     * @param string &$html
     *
     * @return bool Always true.
     */
    public static function onSkinAfterBottomScripts( Skin $skin, &$html ) {
        $services = MediaWikiServices::getInstance();
        $enabledNamespaces = WikibaseClient::getSettings( $services )
            ->getSetting( 'pageSchemaNamespaces' );

        $out = $skin->getOutput();
        $entityId = self::parseEntityId( $out->getProperty( 'wikibase_item' ) );
        $title = $out->getTitle();
        if (
            !$entityId ||
            !$title ||
            !in_array( $title->getNamespace(), $enabledNamespaces ) ||
            !$title->exists()
        ) {
            return true;
        }

        $handler = new SkinAfterBottomScriptsHandler(
            $services->getContentLanguage()->getCode(),
            WikibaseClient::getRepoLinker( $services ),
            WikibaseClient::getTermLookup( $services ),
            $services->getRevisionLookup()
        );
        $revisionTimestamp = $out->getRevisionTimestamp();
        $html .= $handler->createSchemaElement(
            $title,
            $revisionTimestamp,
            $entityId
        );

        return true;
    }

    private static function parseEntityId( ?string $prefixedId ): ?EntityId {
        if ( !$prefixedId ) {
            return null;
        }

        try {
            return WikibaseClient::getEntityIdParser()->parse( $prefixedId );
        } catch ( EntityIdParsingException $ex ) {
            return null;
        }
    }

    /**
     * Used to propagate configuration for the linkitem feature to JavaScript.
     * This is used in the "wikibase.client.linkitem.init" module.
     */
    public static function getLinkitemConfiguration(): array {
        $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
        $key = $cache->makeKey(
            'wikibase-client',
            'siteConfiguration'
        );
        return $cache->getWithSetCallback(
            $key,
            $cache::TTL_DAY, // when changing the TTL, also update linkItemTags in options.md
            function () {
                $site = WikibaseClient::getSite();
                $currentSite = [
                    'globalSiteId' => $site->getGlobalId(),
                    'languageCode' => $site->getLanguageCode(),
                    'langLinkSiteGroup' => WikibaseClient::getLangLinkSiteGroup(),
                ];
                $value = [ 'currentSite' => $currentSite ];

                $tags = WikibaseClient::getSettings()->getSetting( 'linkItemTags' );
                if ( $tags !== [] ) {
                    $value['tags'] = $tags;
                }

                return $value;
            }
        );
    }

    /** @param ContentLanguages[] &$contentLanguages */
    public static function onWikibaseContentLanguages( array &$contentLanguages ): void {
        if ( !WikibaseClient::getSettings()->getSetting( 'tmpEnableMulLanguageCode' ) ) {
            return;
        }

        if ( $contentLanguages[WikibaseContentLanguages::CONTEXT_TERM]->hasLanguage( 'mul' ) ) {
            return;
        }

        $contentLanguages[WikibaseContentLanguages::CONTEXT_TERM] = new UnionContentLanguages(
            $contentLanguages[WikibaseContentLanguages::CONTEXT_TERM],
            new StaticContentLanguages( [ 'mul' ] )
        );
    }

}