Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
69 / 72
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
LanguageLinksHandler
95.83% covered (success)
95.83%
69 / 72
70.00% covered (warning)
70.00%
7 / 10
18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getRedirectHelper
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getPage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 run
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
4
 fetchLinks
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 needsWriteAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParamSettings
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getETag
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getLastModified
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hasRepresentation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Rest\Handler;
4
5use MediaWiki\Languages\LanguageNameUtils;
6use MediaWiki\Page\ExistingPageRecord;
7use MediaWiki\Page\PageLookup;
8use MediaWiki\Rest\Handler\Helper\PageRedirectHelper;
9use MediaWiki\Rest\Handler\Helper\PageRestHelperFactory;
10use MediaWiki\Rest\LocalizedHttpException;
11use MediaWiki\Rest\Response;
12use MediaWiki\Rest\SimpleHandler;
13use MediaWiki\Title\MalformedTitleException;
14use MediaWiki\Title\TitleFormatter;
15use MediaWiki\Title\TitleParser;
16use Wikimedia\Message\MessageValue;
17use Wikimedia\Message\ParamType;
18use Wikimedia\Message\ScalarParam;
19use Wikimedia\ParamValidator\ParamValidator;
20use Wikimedia\Rdbms\IConnectionProvider;
21
22/**
23 * Class LanguageLinksHandler
24 * REST API handler for /page/{title}/links/language endpoint.
25 *
26 * @package MediaWiki\Rest\Handler
27 */
28class LanguageLinksHandler extends SimpleHandler {
29
30    private IConnectionProvider $dbProvider;
31    private LanguageNameUtils $languageNameUtils;
32    private TitleFormatter $titleFormatter;
33    private TitleParser $titleParser;
34    private PageLookup $pageLookup;
35    private PageRestHelperFactory $helperFactory;
36
37    /**
38     * @var ExistingPageRecord|false|null
39     */
40    private $page = false;
41
42    /**
43     * @param IConnectionProvider $dbProvider
44     * @param LanguageNameUtils $languageNameUtils
45     * @param TitleFormatter $titleFormatter
46     * @param TitleParser $titleParser
47     * @param PageLookup $pageLookup
48     * @param PageRestHelperFactory $helperFactory
49     */
50    public function __construct(
51        IConnectionProvider $dbProvider,
52        LanguageNameUtils $languageNameUtils,
53        TitleFormatter $titleFormatter,
54        TitleParser $titleParser,
55        PageLookup $pageLookup,
56        PageRestHelperFactory $helperFactory
57    ) {
58        $this->dbProvider = $dbProvider;
59        $this->languageNameUtils = $languageNameUtils;
60        $this->titleFormatter = $titleFormatter;
61        $this->titleParser = $titleParser;
62        $this->pageLookup = $pageLookup;
63        $this->helperFactory = $helperFactory;
64    }
65
66    private function getRedirectHelper(): PageRedirectHelper {
67        return $this->helperFactory->newPageRedirectHelper(
68            $this->getResponseFactory(),
69            $this->getRouter(),
70            $this->getPath(),
71            $this->getRequest()
72        );
73    }
74
75    /**
76     * @return ExistingPageRecord|null
77     */
78    private function getPage(): ?ExistingPageRecord {
79        if ( $this->page === false ) {
80            $this->page = $this->pageLookup->getExistingPageByText(
81                    $this->getValidatedParams()['title']
82                );
83        }
84        return $this->page;
85    }
86
87    /**
88     * @param string $title
89     * @return Response
90     * @throws LocalizedHttpException
91     */
92    public function run( $title ) {
93        $page = $this->getPage();
94        $params = $this->getValidatedParams();
95
96        if ( !$page ) {
97            throw new LocalizedHttpException(
98                new MessageValue( 'rest-nonexistent-title',
99                    [ new ScalarParam( ParamType::PLAINTEXT, $title ) ]
100                ),
101                404
102            );
103        }
104
105        '@phan-var \MediaWiki\Page\ExistingPageRecord $page';
106        $redirectResponse = $this->getRedirectHelper()->createNormalizationRedirectResponseIfNeeded(
107            $page,
108            $params['title'] ?? null
109        );
110
111        if ( $redirectResponse !== null ) {
112            return $redirectResponse;
113        }
114
115        if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) {
116            throw new LocalizedHttpException(
117                new MessageValue( 'rest-permission-denied-title',
118                    [ new ScalarParam( ParamType::PLAINTEXT, $title ) ] ),
119                403
120            );
121        }
122
123        return $this->getResponseFactory()
124            ->createJson( $this->fetchLinks( $page->getId() ) );
125    }
126
127    private function fetchLinks( $pageId ) {
128        $result = [];
129        $res = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
130            ->select( [ 'll_title', 'll_lang' ] )
131            ->from( 'langlinks' )
132            ->where( [ 'll_from' => $pageId ] )
133            ->orderBy( 'll_lang' )
134            ->caller( __METHOD__ )->fetchResultSet();
135        foreach ( $res as $item ) {
136            try {
137                $targetTitle = $this->titleParser->parseTitle( $item->ll_title );
138                $result[] = [
139                    'code' => $item->ll_lang,
140                    'name' => $this->languageNameUtils->getLanguageName( $item->ll_lang ),
141                    'key' => $this->titleFormatter->getPrefixedDBkey( $targetTitle ),
142                    'title' => $this->titleFormatter->getPrefixedText( $targetTitle )
143                ];
144            } catch ( MalformedTitleException $e ) {
145                // skip malformed titles
146            }
147        }
148        return $result;
149    }
150
151    public function needsWriteAccess() {
152        return false;
153    }
154
155    public function getParamSettings() {
156        return [
157            'title' => [
158                self::PARAM_SOURCE => 'path',
159                ParamValidator::PARAM_TYPE => 'string',
160                ParamValidator::PARAM_REQUIRED => true,
161            ],
162        ];
163    }
164
165    /**
166     * @return string|null
167     */
168    protected function getETag(): ?string {
169        $page = $this->getPage();
170        if ( !$page ) {
171            return null;
172        }
173
174        // XXX: use hash of the rendered HTML?
175        return '"' . $page->getLatest() . '@' . wfTimestamp( TS_MW, $page->getTouched() ) . '"';
176    }
177
178    /**
179     * @return string|null
180     */
181    protected function getLastModified(): ?string {
182        $page = $this->getPage();
183        return $page ? $page->getTouched() : null;
184    }
185
186    /**
187     * @return bool
188     */
189    protected function hasRepresentation() {
190        return (bool)$this->getPage();
191    }
192
193}