Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.05% covered (success)
99.05%
104 / 105
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
AboutTopicRenderer
99.05% covered (success)
99.05%
104 / 105
87.50% covered (warning)
87.50%
7 / 8
20
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 showPlaceholder
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
4
 showTopMessage
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
3
 showCreateArticle
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
3.00
 showTitle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 showLanguageLinks
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
5
 setOtherProjectsLinks
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addMetaTags
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace ArticlePlaceholder;
4
5use ExtensionRegistry;
6use Language;
7use MediaWiki\Html\Html;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Output\OutputPage;
10use MediaWiki\Permissions\PermissionManager;
11use MediaWiki\SpecialPage\SpecialPage;
12use MediaWiki\Title\MalformedTitleException;
13use MediaWiki\Title\TitleFactory;
14use MediaWiki\User\User;
15use OOUI;
16use SiteLookup;
17use Wikibase\Client\Hooks\OtherProjectsSidebarGeneratorFactory;
18use Wikibase\Client\RepoLinker;
19use Wikibase\Client\Usage\HashUsageAccumulator;
20use Wikibase\Client\WikibaseClient;
21use Wikibase\DataModel\Entity\ItemId;
22use Wikibase\DataModel\Term\Term;
23use Wikibase\DataModel\Term\TermTypes;
24use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory;
25use Wikibase\Lib\Store\SiteLinkLookup;
26
27/**
28 * The AboutTopic SpecialPage for the ArticlePlaceholder extension
29 * The AboutTopicRenderer assumes the 'wikibase_item' OutputPage property
30 * is set in SpecialAboutTopic
31 *
32 * @ingroup Extensions
33 * @author Lucie-Aimée Kaffee
34 * @license GPL-2.0-or-later
35 */
36class AboutTopicRenderer {
37
38    /**
39     * @var FallbackLabelDescriptionLookupFactory
40     */
41    private $termLookupFactory;
42
43    /**
44     * @var SiteLinkLookup
45     */
46    private $siteLinkLookup;
47
48    /**
49     * @var SiteLookup
50     */
51    private $siteLookup;
52
53    /**
54     * @var string
55     */
56    private $langLinkSiteGroup;
57
58    /**
59     * @var TitleFactory
60     */
61    private $titleFactory;
62
63    /**
64     * @var OtherProjectsSidebarGeneratorFactory
65     */
66    private $otherProjectsSidebarGeneratorFactory;
67
68    /**
69     * @var PermissionManager
70     */
71    private $permissionManager;
72
73    /** @var RepoLinker */
74    private $repoLinker;
75
76    /**
77     * @param FallbackLabelDescriptionLookupFactory $termLookupFactory
78     * @param SiteLinkLookup $siteLinkLookup
79     * @param SiteLookup $siteLookup
80     * @param string $langLinkSiteGroup
81     * @param TitleFactory $titleFactory
82     * @param OtherProjectsSidebarGeneratorFactory $otherProjectsSidebarGeneratorFactory
83     * @param PermissionManager $permissionManager
84     * @param RepoLinker $repoLinker
85     */
86    public function __construct(
87        FallbackLabelDescriptionLookupFactory $termLookupFactory,
88        SiteLinkLookup $siteLinkLookup,
89        SiteLookup $siteLookup,
90        string $langLinkSiteGroup,
91        TitleFactory $titleFactory,
92        OtherProjectsSidebarGeneratorFactory $otherProjectsSidebarGeneratorFactory,
93        PermissionManager $permissionManager,
94        RepoLinker $repoLinker
95    ) {
96        $this->termLookupFactory = $termLookupFactory;
97        $this->siteLinkLookup = $siteLinkLookup;
98        $this->siteLookup = $siteLookup;
99        $this->langLinkSiteGroup = $langLinkSiteGroup;
100        $this->titleFactory = $titleFactory;
101        $this->otherProjectsSidebarGeneratorFactory = $otherProjectsSidebarGeneratorFactory;
102        $this->permissionManager = $permissionManager;
103        $this->repoLinker = $repoLinker;
104    }
105
106    /**
107     * Show content of the ArticlePlaceholder
108     *
109     * @param ItemId $entityId
110     * @param Language $language
111     * @param User $user
112     * @param OutputPage $output
113     */
114    public function showPlaceholder(
115        ItemId $entityId,
116        Language $language,
117        User $user,
118        OutputPage $output
119    ) {
120        $termLookup = $this->termLookupFactory->newLabelDescriptionLookup(
121            $language,
122            [ $entityId ],
123            [ TermTypes::TYPE_LABEL, TermTypes::TYPE_DESCRIPTION ]
124        );
125        $label = $termLookup->getLabel( $entityId );
126        $description = $termLookup->getDescription( $entityId );
127        $canEdit = false;
128
129        if ( $label !== null ) {
130            $label = $label->getText();
131            $this->showTitle( $label, $output );
132
133            try {
134                $title = $this->titleFactory->newFromTextThrow( $label );
135                if ( $this->permissionManager->quickUserCan( 'createpage', $user, $title ) ) {
136                    $canEdit = true;
137                }
138            } catch ( MalformedTitleException $ex ) {
139                // When the entity's label contains characters not allowed in page titles
140                $label = '';
141                $canEdit = true;
142            }
143        }
144
145        $this->showTopMessage( $entityId, $label, $output, $canEdit );
146        $output->addModuleStyles( 'ext.articleplaceholder.defaultDisplay' );
147        $output->addWikiTextAsInterface( '{{aboutTopic|' . $entityId->getSerialization() . '}}' );
148
149        $this->showLanguageLinks( $entityId, $output );
150        $this->setOtherProjectsLinks( $entityId, $output );
151        $this->addMetaTags( $output, $description );
152    }
153
154    /**
155     * Adds the top message bar
156     *
157     * @param ItemId $entityId
158     * @param string|null $label
159     * @param OutputPage $output
160     * @param bool $canEdit
161     */
162    private function showTopMessage( ItemId $entityId, ?string $label, OutputPage $output, bool $canEdit ) {
163        $infoIcon = new OOUI\IconWidget( [
164            'icon' => 'infoFilled',
165            'title' => $output->msg( 'articleplaceholder-abouttopic-icon-title' )->text()
166        ] );
167
168        $output->enableOOUI();
169
170        $leftDIV = Html::rawElement( 'div',
171            [ 'class' => 'mw-articleplaceholder-topmessage-container-left' ],
172            $infoIcon
173        );
174
175        $buttonCode = '';
176        if ( $label !== null && $canEdit ) {
177            $buttonCode = $this->showCreateArticle( $entityId, $label, $output );
178        }
179
180        $this->repoLinker = WikibaseClient::getRepoLinker();
181        $messageP = Html::rawElement( 'p', [], $output->msg(
182            'articleplaceholder-abouttopic-topmessage-text',
183            $this->repoLinker->getEntityUrl( $entityId )
184            )->parse()
185        );
186        $centerDIV = Html::rawElement( 'div',
187            [ 'class' => [ 'plainlinks', 'mw-articleplaceholder-topmessage-container-center' ] ],
188            $messageP
189        );
190
191        $rightDIV = Html::rawElement( 'div',
192            [ 'class' => 'mw-articleplaceholder-topmessage-container-right' ],
193            $buttonCode
194        );
195
196        $output->addHTML( Html::rawElement( 'div',
197            [ 'class' => 'mw-articleplaceholder-topmessage-container' ],
198            $leftDIV . $rightDIV . $centerDIV )
199        );
200    }
201
202    /**
203     * Adds a button to create an article
204     *
205     * @param ItemId $itemId
206     * @param string $label
207     * @param OutputPage $output
208     *
209     * @return string HTML
210     */
211    private function showCreateArticle( ItemId $itemId, $label, OutputPage $output ) {
212        $siteLinks = $this->siteLinkLookup->getSiteLinksForItem( $itemId );
213
214        $output->enableOOUI();
215        $output->addModules( 'ext.articleplaceholder.createArticle' );
216        $output->addJsConfigVars( 'apLabel', $label );
217
218        $contents = new OOUI\ButtonWidget( [
219            'id' => 'new-article-button',
220            'flags' => [ 'primary', 'progressive' ],
221            'infusable' => true,
222            'label' => wfMessage( 'articleplaceholder-abouttopic-create-article-button' )->text(),
223            'href' => SpecialPage::getTitleFor( 'CreateTopicPage', $label )
224                ->getLocalURL( [ 'ref' => 'button' ] ),
225            'target' => 'blank'
226        ] );
227
228        // TODO: Button should be hidden if the only sitelink links to the current wiki.
229        // $wikibaseClient->getSettings()->getSetting( 'siteGlobalID' ) should be injected here!
230        if ( ExtensionRegistry::getInstance()->isLoaded( 'ContentTranslation' ) && $siteLinks ) {
231            $output->addJsConfigVars( 'apContentTranslation', true );
232        }
233
234        return $contents;
235    }
236
237    /**
238     * Show label as page title
239     *
240     * @param string $label
241     * @param OutputPage $output
242     */
243    private function showTitle( $label, OutputPage $output ) {
244        $output->setPageTitle( htmlspecialchars( $label ) );
245    }
246
247    /**
248     * Set language links
249     *
250     * @param ItemId $entityId
251     * @param OutputPage $output
252     */
253    private function showLanguageLinks( ItemId $entityId, OutputPage $output ) {
254        $siteLinks = $this->siteLinkLookup->getSiteLinksForItem( $entityId );
255        $languageLinks = [];
256        $languageNames = [];
257        $pageNames = [];
258
259        foreach ( $siteLinks as $siteLink ) {
260            $site = $this->siteLookup->getSite( $siteLink->getSiteId() );
261            if ( $site === null ) {
262                continue;
263            }
264            $languageCode = $site->getLanguageCode();
265            $group = $site->getGroup();
266            // TODO: This should not contain the current wiki.
267            // $wikibaseClient->getSettings()->getSetting( 'siteGlobalID' ) should be injected here!
268            if ( $languageCode !== null && $group === $this->langLinkSiteGroup ) {
269                $languageLinks[$languageCode] = $languageCode . ':' . $siteLink->getPageName();
270
271                // TODO: We may want to filter with user languages
272                $languageNames[] = [
273                    'data' => $languageCode,
274                    'label' => MediaWikiServices::getInstance()->getLanguageNameUtils()
275                        ->getLanguageName( $languageCode ),
276                ];
277                $pageNames[ $languageCode ] = $siteLink->getPageName();
278            }
279        }
280
281        $output->setLanguageLinks( $languageLinks );
282        $output->addJsConfigVars( 'apLanguages', $languageNames );
283        $output->addJsConfigVars( 'apPageNames', $pageNames );
284    }
285
286    /**
287     * @param ItemId $itemId
288     * @param OutputPage $output
289     */
290    private function setOtherProjectsLinks( ItemId $itemId, OutputPage $output ) {
291        $otherProjectsSidebarGenerator = $this->otherProjectsSidebarGeneratorFactory
292            ->getOtherProjectsSidebarGenerator( new HashUsageAccumulator() );
293
294        $otherProjects = $otherProjectsSidebarGenerator->buildProjectLinkSidebarFromItemId( $itemId );
295        $output->setProperty( 'wikibase-otherprojects-sidebar', $otherProjects );
296    }
297
298    /**
299     * @param OutputPage $output
300     * @param Term|null $description
301     */
302    private function addMetaTags( OutputPage $output, ?Term $description ) {
303        if ( $description !== null ) {
304            $output->addMeta( 'description', trim( $description->getText() ) );
305        }
306    }
307
308}