Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.55% covered (success)
91.55%
65 / 71
45.45% covered (danger)
45.45%
5 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
LanguageLinksHandler
91.55% covered (success)
91.55%
65 / 71
45.45% covered (danger)
45.45%
5 / 11
19.22
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.00% covered (success)
95.00%
19 / 20
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
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getLastModified
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hasRepresentation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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\Language\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\ParamValidator\ParamValidator;
19use Wikimedia\Rdbms\IConnectionProvider;
20use Wikimedia\Timestamp\TimestampFormat as TS;
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    private function getPage(): ?ExistingPageRecord {
76        if ( $this->page === false ) {
77            $this->page = $this->pageLookup->getExistingPageByText(
78                    $this->getValidatedParams()['title']
79                );
80        }
81        return $this->page;
82    }
83
84    /**
85     * @param string $title
86     * @return Response
87     * @throws LocalizedHttpException
88     */
89    public function run( $title ) {
90        $page = $this->getPage();
91        $params = $this->getValidatedParams();
92
93        if ( !$page ) {
94            throw new LocalizedHttpException(
95                ( new MessageValue( 'rest-nonexistent-title' ) )->plaintextParams( $title ),
96                404
97            );
98        }
99
100        '@phan-var \MediaWiki\Page\ExistingPageRecord $page';
101        $redirectResponse = $this->getRedirectHelper()->createNormalizationRedirectResponseIfNeeded(
102            $page,
103            $params['title'] ?? null
104        );
105
106        if ( $redirectResponse !== null ) {
107            return $redirectResponse;
108        }
109
110        if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) {
111            throw new LocalizedHttpException(
112                ( new MessageValue( 'rest-permission-denied-title' ) )->plaintextParams( $title ),
113                403
114            );
115        }
116
117        return $this->getResponseFactory()
118            ->createJson( $this->fetchLinks( $page->getId() ) );
119    }
120
121    private function fetchLinks( int $pageId ): array {
122        $result = [];
123        $res = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
124            ->select( [ 'll_title', 'll_lang' ] )
125            ->from( 'langlinks' )
126            ->where( [ 'll_from' => $pageId ] )
127            ->orderBy( 'll_lang' )
128            ->caller( __METHOD__ )->fetchResultSet();
129        foreach ( $res as $item ) {
130            try {
131                $targetTitle = $this->titleParser->parseTitle( $item->ll_title );
132                $result[] = [
133                    'code' => $item->ll_lang,
134                    'name' => $this->languageNameUtils->getLanguageName( $item->ll_lang ),
135                    'key' => $this->titleFormatter->getPrefixedDBkey( $targetTitle ),
136                    'title' => $this->titleFormatter->getPrefixedText( $targetTitle )
137                ];
138            } catch ( MalformedTitleException ) {
139                // skip malformed titles
140            }
141        }
142        return $result;
143    }
144
145    /** @inheritDoc */
146    public function needsWriteAccess() {
147        return false;
148    }
149
150    /** @inheritDoc */
151    public function getParamSettings() {
152        return [
153            'title' => [
154                self::PARAM_SOURCE => 'path',
155                ParamValidator::PARAM_TYPE => 'string',
156                ParamValidator::PARAM_REQUIRED => true,
157                Handler::PARAM_DESCRIPTION => new MessageValue( 'rest-param-desc-language-links-title' ),
158            ],
159        ];
160    }
161
162    protected function getETag(): ?string {
163        $page = $this->getPage();
164        if ( !$page ) {
165            return null;
166        }
167
168        // XXX: use hash of the rendered HTML?
169        return '"' . $page->getLatest() . '@' . wfTimestamp( TS::MW, $page->getTouched() ) . '"';
170    }
171
172    protected function getLastModified(): ?string {
173        $page = $this->getPage();
174        return $page ? $page->getTouched() : null;
175    }
176
177    /**
178     * @return bool
179     */
180    protected function hasRepresentation() {
181        return (bool)$this->getPage();
182    }
183
184    public function getResponseBodySchemaFileName( string $method ): ?string {
185        return __DIR__ . '/Schema/PageLanguageLinks.json';
186    }
187}