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