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