Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
70.00% covered (warning)
70.00%
7 / 10
CRAP
97.06% covered (success)
97.06%
99 / 102
AboutTopicRenderer
0.00% covered (danger)
0.00%
0 / 1
70.00% covered (warning)
70.00%
7 / 10
25
97.06% covered (success)
97.06%
99 / 102
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 showPlaceholder
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
17 / 17
 showTopMessage
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
22 / 22
 showCreateArticle
0.00% covered (danger)
0.00%
0 / 1
3.00
92.31% covered (success)
92.31%
12 / 13
 getLabel
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 getDescription
0.00% covered (danger)
0.00%
0 / 1
2.03
80.00% covered (warning)
80.00%
4 / 5
 showTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 showLanguageLinks
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
20 / 20
 setOtherProjectsLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 addMetaTags
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
<?php
namespace ArticlePlaceholder;
use ExtensionRegistry;
use Html;
use Language;
use MalformedTitleException;
use MediaWiki\Permissions\PermissionManager;
use OOUI;
use OutputPage;
use SiteLookup;
use SpecialPage;
use TitleFactory;
use User;
use Wikibase\Client\Hooks\OtherProjectsSidebarGeneratorFactory;
use Wikibase\Client\RepoLinker;
use Wikibase\Client\Usage\HashUsageAccumulator;
use Wikibase\Client\WikibaseClient;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
use Wikibase\Lib\Store\SiteLinkLookup;
/**
 * The AboutTopic SpecialPage for the ArticlePlaceholder extension
 * The AboutTopicRenderer assumes the 'wikibase_item' OutputPage property
 * is set in SpecialAboutTopic
 *
 * @ingroup Extensions
 * @author Lucie-Aimée Kaffee
 * @license GPL-2.0-or-later
 */
class AboutTopicRenderer {
    /**
     * @var LanguageFallbackLabelDescriptionLookupFactory
     */
    private $termLookupFactory;
    /**
     * @var SiteLinkLookup
     */
    private $siteLinkLookup;
    /**
     * @var SiteLookup
     */
    private $siteLookup;
    /**
     * @var string
     */
    private $langLinkSiteGroup;
    /**
     * @var TitleFactory
     */
    private $titleFactory;
    /**
     * @var OtherProjectsSidebarGeneratorFactory
     */
    private $otherProjectsSidebarGeneratorFactory;
    /**
     * @var PermissionManager
     */
    private $permissionManager;
    /** @var RepoLinker */
    private $repoLinker;
    /**
     * @param LanguageFallbackLabelDescriptionLookupFactory $termLookupFactory
     * @param SiteLinkLookup $siteLinkLookup
     * @param SiteLookup $siteLookup
     * @param string $langLinkSiteGroup
     * @param TitleFactory $titleFactory
     * @param OtherProjectsSidebarGeneratorFactory $otherProjectsSidebarGeneratorFactory
     * @param PermissionManager $permissionManager
     * @param RepoLinker $repoLinker
     */
    public function __construct(
        LanguageFallbackLabelDescriptionLookupFactory $termLookupFactory,
        SiteLinkLookup $siteLinkLookup,
        SiteLookup $siteLookup,
        string $langLinkSiteGroup,
        TitleFactory $titleFactory,
        OtherProjectsSidebarGeneratorFactory $otherProjectsSidebarGeneratorFactory,
        PermissionManager $permissionManager,
        RepoLinker $repoLinker
    ) {
        $this->termLookupFactory = $termLookupFactory;
        $this->siteLinkLookup = $siteLinkLookup;
        $this->siteLookup = $siteLookup;
        $this->langLinkSiteGroup = $langLinkSiteGroup;
        $this->titleFactory = $titleFactory;
        $this->otherProjectsSidebarGeneratorFactory = $otherProjectsSidebarGeneratorFactory;
        $this->permissionManager = $permissionManager;
        $this->repoLinker = $repoLinker;
    }
    /**
     * Show content of the ArticlePlaceholder
     *
     * @param ItemId $entityId
     * @param Language $language
     * @param User $user
     * @param OutputPage $output
     */
    public function showPlaceholder(
        ItemId $entityId,
        Language $language,
        User $user,
        OutputPage $output
    ) {
        $label = $this->getLabel( $entityId, $language );
        $canEdit = false;
        if ( $label !== null ) {
            $this->showTitle( $label, $output );
            try {
                $title = $this->titleFactory->newFromTextThrow( $label );
                if ( $this->permissionManager->quickUserCan( 'createpage', $user, $title ) ) {
                    $canEdit = true;
                }
            } catch ( MalformedTitleException $ex ) {
                // When the entity's label contains characters not allowed in page titles
                $label = '';
                $canEdit = true;
            }
        }
        $this->showTopMessage( $entityId, $label, $output, $canEdit );
        $output->addModuleStyles( 'ext.articleplaceholder.defaultDisplay' );
        $output->addWikiTextAsInterface( '{{aboutTopic|' . $entityId->getSerialization() . '}}' );
        $this->showLanguageLinks( $entityId, $output );
        $this->setOtherProjectsLinks( $entityId, $output );
        $this->addMetaTags( $entityId, $output, $language );
    }
    /**
     * Adds the top message bar
     *
     * @param ItemId $entityId
     * @param string|null $label
     * @param OutputPage $output
     * @param bool $canEdit
     */
    private function showTopMessage( ItemId $entityId, ?string $label, OutputPage $output, bool $canEdit ) {
        $infoIcon = new OOUI\IconWidget( [
            'icon' => 'infoFilled',
            'title' => $output->msg( 'articleplaceholder-abouttopic-icon-title' )->text()
        ] );
        $output->enableOOUI();
        $leftDIV = Html::rawElement( 'div',
            [ 'class' => 'mw-articleplaceholder-topmessage-container-left' ],
            $infoIcon
        );
        $buttonCode = '';
        if ( $label !== null && $canEdit ) {
            $buttonCode = $this->showCreateArticle( $entityId, $label, $output );
        }
        $this->repoLinker = WikibaseClient::getRepoLinker();
        $messageP = Html::rawElement( 'p', [], $output->msg(
            'articleplaceholder-abouttopic-topmessage-text',
            $this->repoLinker->getEntityUrl( $entityId )
            )->parse()
        );
        $centerDIV = Html::rawElement( 'div',
            [ 'class' => [ 'plainlinks', 'mw-articleplaceholder-topmessage-container-center' ] ],
            $messageP
        );
        $rightDIV = Html::rawElement( 'div',
            [ 'class' => 'mw-articleplaceholder-topmessage-container-right' ],
            $buttonCode
        );
        $output->addHTML( Html::rawElement( 'div',
            [ 'class' => 'mw-articleplaceholder-topmessage-container' ],
            $leftDIV . $rightDIV . $centerDIV )
        );
    }
    /**
     * Adds a button to create an article
     *
     * @param ItemId $itemId
     * @param string $label
     * @param OutputPage $output
     *
     * @return string HTML
     */
    private function showCreateArticle( ItemId $itemId, $label, OutputPage $output ) {
        $siteLinks = $this->siteLinkLookup->getSiteLinksForItem( $itemId );
        $output->enableOOUI();
        $output->addModules( 'ext.articleplaceholder.createArticle' );
        $output->addJsConfigVars( 'apLabel', $label );
        $contents = new OOUI\ButtonWidget( [
            'id' => 'new-article-button',
            'flags' => [ 'primary', 'progressive' ],
            'infusable' => true,
            'label' => wfMessage( 'articleplaceholder-abouttopic-create-article-button' )->text(),
            'href' => SpecialPage::getTitleFor( 'CreateTopicPage', $label )
                ->getLocalURL( [ 'ref' => 'button' ] ),
            'target' => 'blank'
        ] );
        // TODO: Button should be hidden if the only sitelink links to the current wiki.
        // $wikibaseClient->getSettings()->getSetting( 'siteGlobalID' ) should be injected here!
        if ( ExtensionRegistry::getInstance()->isLoaded( 'ContentTranslation' ) && $siteLinks ) {
            $output->addJsConfigVars( 'apContentTranslation', true );
        }
        return $contents;
    }
    /**
     * @param ItemId $entityId
     * @param Language $language
     *
     * @return string|null null if the item doesn't have a label
     */
    private function getLabel( ItemId $entityId, Language $language ) {
        $label = $this->termLookupFactory->newLabelDescriptionLookup( $language )
            ->getLabel( $entityId );
        if ( $label !== null ) {
            return $label->getText();
        }
        return null;
    }
    /**
     * @param ItemId $entityId
     * @param Language $language
     *
     * @return string|null null if the item doesn't have a description
     */
    private function getDescription( ItemId $entityId, Language $language ) {
        $description = $this->termLookupFactory->newLabelDescriptionLookup( $language )
            ->getDescription( $entityId );
        if ( $description !== null ) {
            return $description->getText();
        }
        return null;
    }
    /**
     * Show label as page title
     *
     * @param string $label
     * @param OutputPage $output
     */
    private function showTitle( $label, OutputPage $output ) {
        $output->setPageTitle( htmlspecialchars( $label ) );
    }
    /**
     * Set language links
     *
     * @param ItemId $entityId
     * @param OutputPage $output
     */
    private function showLanguageLinks( ItemId $entityId, OutputPage $output ) {
        $siteLinks = $this->siteLinkLookup->getSiteLinksForItem( $entityId );
        $languageLinks = [];
        $languageNames = [];
        $pageNames = [];
        foreach ( $siteLinks as $siteLink ) {
            $site = $this->siteLookup->getSite( $siteLink->getSiteId() );
            if ( $site === null ) {
                continue;
            }
            $languageCode = $site->getLanguageCode();
            $group = $site->getGroup();
            // TODO: This should not contain the current wiki.
            // $wikibaseClient->getSettings()->getSetting( 'siteGlobalID' ) should be injected here!
            if ( $languageCode !== null && $group === $this->langLinkSiteGroup ) {
                $languageLinks[$languageCode] = $languageCode . ':' . $siteLink->getPageName();
                // TODO: We may want to filter with user languages
                $languageNames[] = [
                    'data' => $languageCode,
                    'label' => Language::fetchLanguageName( $languageCode ),
                ];
                $pageNames[ $languageCode ] = $siteLink->getPageName();
            }
        }
        $output->setLanguageLinks( $languageLinks );
        $output->addJsConfigVars( 'apLanguages', $languageNames );
        $output->addJsConfigVars( 'apPageNames', $pageNames );
    }
    /**
     * @param ItemId $itemId
     * @param OutputPage $output
     */
    private function setOtherProjectsLinks( ItemId $itemId, OutputPage $output ) {
        $otherProjectsSidebarGenerator = $this->otherProjectsSidebarGeneratorFactory
            ->getOtherProjectsSidebarGenerator( new HashUsageAccumulator() );
        $otherProjects = $otherProjectsSidebarGenerator->buildProjectLinkSidebarFromItemId( $itemId );
        $output->setProperty( 'wikibase-otherprojects-sidebar', $otherProjects );
    }
    /**
     * @param ItemId $itemId
     * @param OutputPage $output
     * @param Language $language
     */
    private function addMetaTags( ItemId $itemId, OutputPage $output, Language $language ) {
        $description = $this->getDescription( $itemId, $language );
        if ( $description !== null ) {
            $output->addMeta( 'description', trim( $description ) );
        }
    }
}