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