dbProvider = $dbProvider; $this->repoGroup = $repoGroup; $this->pageLookup = $pageLookup; } /** * @return ExistingPageRecord|null */ private function getPage(): ?ExistingPageRecord { if ( $this->page === false ) { $this->page = $this->pageLookup->getExistingPageByText( $this->getValidatedParams()['title'] ); } return $this->page; } /** * @param string $title * @return Response * @throws LocalizedHttpException */ public function run( $title ) { $page = $this->getPage(); if ( !$page ) { throw new LocalizedHttpException( MessageValue::new( 'rest-nonexistent-title' )->plaintextParams( $title ), 404 ); } if ( !$this->getAuthority()->authorizeRead( 'read', $page ) ) { throw new LocalizedHttpException( MessageValue::new( 'rest-permission-denied-title' )->plaintextParams( $title ), 403 ); } // @todo: add continuation if too many links are found $results = $this->getDbResults( $page->getId() ); if ( count( $results ) > $this->getMaxNumLinks() ) { throw new LocalizedHttpException( MessageValue::new( 'rest-media-too-many-links' ) ->plaintextParams( $title ) ->numParams( $this->getMaxNumLinks() ), 400 ); } $response = $this->processDbResults( $results ); return $this->getResponseFactory()->createJson( $response ); } /** * @param int $pageId the id of the page to load media links for * @return array the results */ private function getDbResults( int $pageId ) { return $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder() ->select( 'il_to' ) ->from( 'imagelinks' ) ->where( [ 'il_from' => $pageId ] ) ->orderBy( 'il_to' ) ->limit( $this->getMaxNumLinks() + 1 ) ->caller( __METHOD__ )->fetchFieldValues(); } /** * @param array $results database results, or an empty array if none * @return array response data */ private function processDbResults( $results ) { // Using "private" here means an equivalent of the Action API's "anon-public-user-private" // caching model would be necessary, if caching is ever added to this endpoint. $performer = $this->getAuthority(); $findTitles = array_map( static function ( $title ) use ( $performer ) { return [ 'title' => $title, 'private' => $performer, ]; }, $results ); $files = $this->repoGroup->findFiles( $findTitles ); [ $maxWidth, $maxHeight ] = self::getImageLimitsFromOption( $this->getAuthority()->getUser(), 'imagesize' ); $transforms = [ 'preferred' => [ 'maxWidth' => $maxWidth, 'maxHeight' => $maxHeight, ] ]; $response = []; foreach ( $files as $file ) { $response[] = $this->getFileInfo( $file, $performer, $transforms ); } $response = [ 'files' => $response ]; return $response; } public function needsWriteAccess() { return false; } public function getParamSettings() { return [ 'title' => [ self::PARAM_SOURCE => 'path', ParamValidator::PARAM_TYPE => 'string', ParamValidator::PARAM_REQUIRED => true, ], ]; } /** * @return string|null * @throws LocalizedHttpException */ protected function getETag(): ?string { $page = $this->getPage(); if ( !$page ) { return null; } // XXX: use hash of the rendered HTML? return '"' . $page->getLatest() . '@' . wfTimestamp( TS_MW, $page->getTouched() ) . '"'; } /** * @return string|null * @throws LocalizedHttpException */ protected function getLastModified(): ?string { $page = $this->getPage(); return $page ? $page->getTouched() : null; } /** * @return bool */ protected function hasRepresentation() { return (bool)$this->getPage(); } /** * For testing * * @unstable */ protected function getMaxNumLinks(): int { return self::MAX_NUM_LINKS; } public function getResponseBodySchemaFileName( string $method ): ?string { return 'includes/Rest/Handler/Schema/MediaLinks.json'; } }