Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.79% covered (success)
92.79%
103 / 111
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialAboutTopic
92.79% covered (success)
92.79%
103 / 111
70.00% covered (warning)
70.00%
7 / 10
22.18
0.00% covered (danger)
0.00%
0 / 1
 newFromGlobalState
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 showContent
88.00% covered (warning)
88.00%
22 / 25
0.00% covered (danger)
0.00%
0 / 1
5.04
 getDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createForm
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 getItemIdParam
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
5.02
 getArticleUrl
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getRobotPolicy
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace ArticlePlaceholder\Specials;
4
5use ArticlePlaceholder\AboutTopicRenderer;
6use HTMLForm;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\SpecialPage\SpecialPage;
9use MediaWiki\Title\TitleFactory;
10use Wikibase\Client\WikibaseClient;
11use Wikibase\DataModel\Entity\EntityIdParser;
12use Wikibase\DataModel\Entity\EntityIdParsingException;
13use Wikibase\DataModel\Entity\ItemId;
14use Wikibase\DataModel\Services\Lookup\EntityLookup;
15use Wikibase\Lib\Store\SiteLinkLookup;
16use Wikimedia\Assert\Assert;
17use Wikimedia\Assert\ParameterTypeException;
18
19/**
20 * The AboutTopic SpecialPage for the ArticlePlaceholder extension
21 *
22 * @ingroup Extensions
23 * @license GPL-2.0-or-later
24 * @author Lucie-Aimée Kaffee
25 */
26class SpecialAboutTopic extends SpecialPage {
27
28    public static function newFromGlobalState() {
29        // TODO inject services
30        $mwServices = MediaWikiServices::getInstance();
31        $config = $mwServices->getMainConfig();
32        $settings = WikibaseClient::getSettings( $mwServices );
33        $store = WikibaseClient::getStore( $mwServices );
34
35        return new self(
36            new AboutTopicRenderer(
37                WikibaseClient::getFallbackLabelDescriptionLookupFactory( $mwServices ),
38                $store->getSiteLinkLookup(),
39                $mwServices->getSiteLookup(),
40                WikibaseClient::getLangLinkSiteGroup( $mwServices ),
41                $mwServices->getTitleFactory(),
42                WikibaseClient::getOtherProjectsSidebarGeneratorFactory( $mwServices ),
43                $mwServices->getPermissionManager(),
44                WikibaseClient::getRepoLinker( $mwServices )
45            ),
46            WikibaseClient::getEntityIdParser( $mwServices ),
47            $store->getSiteLinkLookup(),
48            $mwServices->getTitleFactory(),
49            $settings->getSetting( 'siteGlobalID' ),
50            $store->getEntityLookup(),
51            $config->get( 'ArticlePlaceholderSearchEngineIndexed' )
52        );
53    }
54
55    /**
56     * @var AboutTopicRenderer
57     */
58    private $aboutTopicRenderer;
59
60    /**
61     * @var EntityIdParser
62     */
63    private $idParser;
64
65    /**
66     * @var SiteLinkLookup
67     */
68    private $siteLinkLookup;
69
70    /**
71     * @var TitleFactory
72     */
73    private $titleFactory;
74
75    /**
76     * @var string
77     */
78    private $siteGlobalID;
79
80    /**
81     * @var EntityLookup
82     */
83    private $entityLookup;
84
85    /**
86     * @var bool|string
87     */
88    private $searchEngineIndexed;
89
90    /**
91     * @param AboutTopicRenderer $aboutTopicRenderer
92     * @param EntityIdParser $idParser
93     * @param SiteLinkLookup $siteLinkLookup
94     * @param TitleFactory $titleFactory
95     * @param string $siteGlobalID
96     * @param EntityLookup $entityLookup
97     * @param bool|string $searchEngineIndexed
98     *
99     * @throws ParameterTypeException
100     */
101    public function __construct(
102        AboutTopicRenderer $aboutTopicRenderer,
103        EntityIdParser $idParser,
104        SiteLinkLookup $siteLinkLookup,
105        TitleFactory $titleFactory,
106        $siteGlobalID,
107        EntityLookup $entityLookup,
108        $searchEngineIndexed
109    ) {
110        parent::__construct( 'AboutTopic' );
111
112        Assert::parameterType(
113            'boolean|string',
114            $searchEngineIndexed,
115            '$searchEngineIndexed'
116        );
117
118        $this->aboutTopicRenderer = $aboutTopicRenderer;
119        $this->idParser = $idParser;
120        $this->siteLinkLookup = $siteLinkLookup;
121        $this->titleFactory = $titleFactory;
122        $this->siteGlobalID = $siteGlobalID;
123        $this->entityLookup = $entityLookup;
124        $this->searchEngineIndexed = $searchEngineIndexed;
125    }
126
127    /**
128     * @param string|null $sub
129     */
130    public function execute( $sub ) {
131        $this->showContent( $sub );
132    }
133
134    /**
135     * @param string|null $itemIdString
136     */
137    private function showContent( ?string $itemIdString ) {
138        $out = $this->getOutput();
139        $itemId = $this->getItemIdParam( $itemIdString );
140
141        if ( $itemId !== null ) {
142            $out->setProperty( 'wikibase_item', $itemId->getSerialization() );
143
144            $out->setCanonicalUrl(
145                $this->getTitleFor( $this->getName(), $itemId->getSerialization() )->getCanonicalURL()
146            );
147        }
148        $this->setHeaders();
149
150        // Unconditionally cache the special page for a day, see T109458
151        $out->setCdnMaxage( 86400 );
152
153        if ( $itemId === null ) {
154            $this->createForm();
155            return;
156        }
157
158        if ( !$this->entityLookup->hasEntity( $itemId ) ) {
159            $this->createForm();
160            $out->addWikiMsg( 'articleplaceholder-abouttopic-no-entity-error' );
161            return;
162        }
163
164        $articleOnWiki = $this->getArticleUrl( $itemId );
165
166        if ( $articleOnWiki !== null ) {
167            $out->redirect( $articleOnWiki );
168        } else {
169            $this->aboutTopicRenderer->showPlaceholder(
170                $itemId,
171                $this->getLanguage(),
172                $this->getUser(),
173                $out
174            );
175        }
176    }
177
178    /**
179     * @inheritDoc
180     */
181    public function getDescription() {
182        return $this->msg( 'articleplaceholder-abouttopic' );
183    }
184
185    /**
186     * @inheritDoc
187     */
188    protected function getGroupName() {
189        return 'other';
190    }
191
192    /**
193     * Create html elements
194     */
195    protected function createForm() {
196        $form = HTMLForm::factory( 'ooui', [
197            'text' => [
198                'type' => 'text',
199                'name' => 'entityid',
200                'id' => 'ap-abouttopic-entityid',
201                'required' => true,
202                'cssclass' => 'ap-input',
203                'label-message' => 'articleplaceholder-abouttopic-entityid',
204                'default' => $this->getRequest()->getVal( 'entityid' ),
205            ]
206        ], $this->getContext() );
207
208        $form
209            ->setMethod( 'get' )
210            ->setId( 'ap-abouttopic-form1' )
211            ->setHeaderHtml( $this->msg( 'articleplaceholder-abouttopic-intro' )->parse() )
212            ->setWrapperLegend( '' )
213            ->setSubmitTextMsg( 'articleplaceholder-abouttopic-submit' )
214            ->prepareForm()
215            ->displayForm( false );
216    }
217
218    /**
219     * @param string|null $fallback
220     *
221     * @return ItemId|null
222     */
223    private function getItemIdParam( ?string $fallback ): ?ItemId {
224        $rawId = trim( $this->getRequest()->getText( 'entityid', $fallback ?? '' ) );
225
226        if ( $rawId === '' ) {
227            return null;
228        }
229
230        try {
231            $id = $this->idParser->parse( $rawId );
232            if ( !( $id instanceof ItemId ) ) {
233                throw new EntityIdParsingException();
234            }
235
236            return $id;
237        } catch ( EntityIdParsingException $ex ) {
238            $this->getOutput()->addWikiMsg( 'articleplaceholder-abouttopic-no-entity-error' );
239        }
240
241        return null;
242    }
243
244    /**
245     * @param ItemId $entityId
246     *
247     * @return string|null
248     */
249    private function getArticleUrl( ItemId $entityId ) {
250        $sitelinkTitles = $this->siteLinkLookup->getLinks(
251            [ $entityId->getNumericId() ],
252            [ $this->siteGlobalID ]
253        );
254
255        if ( isset( $sitelinkTitles[0][1] ) ) {
256            $sitelinkTitle = $sitelinkTitles[0][1];
257            return $this->titleFactory->newFromText( $sitelinkTitle )->getLinkURL();
258        }
259
260        return null;
261    }
262
263    /**
264     * @return string
265     */
266    protected function getRobotPolicy() {
267        $wikibaseItem = $this->getOutput()->getProperty( 'wikibase_item' );
268        if ( $wikibaseItem === null ) {
269            // No item id set: We're showing the form, not an actual placeholder.
270            return parent::getRobotPolicy();
271        }
272
273        if ( $this->searchEngineIndexed === true ) {
274            return 'index,follow';
275        }
276
277        if ( is_string( $this->searchEngineIndexed ) ) {
278            $entityId = new ItemId( $wikibaseItem );
279
280            $maxEntityId = new ItemId( $this->searchEngineIndexed );
281
282            if ( $entityId->getNumericId() <= $maxEntityId->getNumericId() ) {
283                return 'index,follow';
284            }
285        }
286
287        return parent::getRobotPolicy();
288    }
289
290}