File: MediaLinksHandler.php

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (195 lines) | stat: -rw-r--r-- 4,727 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<?php

namespace MediaWiki\Rest\Handler;

use MediaFileTrait;
use MediaWiki\Page\ExistingPageRecord;
use MediaWiki\Page\PageLookup;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\SimpleHandler;
use RepoGroup;
use Wikimedia\Message\MessageValue;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\Rdbms\IConnectionProvider;

/**
 * Handler class for Core REST API endpoints that perform operations on revisions
 */
class MediaLinksHandler extends SimpleHandler {
	use MediaFileTrait;

	/** int The maximum number of media links to return */
	private const MAX_NUM_LINKS = 100;

	private IConnectionProvider $dbProvider;
	private RepoGroup $repoGroup;
	private PageLookup $pageLookup;

	/**
	 * @var ExistingPageRecord|false|null
	 */
	private $page = false;

	public function __construct(
		IConnectionProvider $dbProvider,
		RepoGroup $repoGroup,
		PageLookup $pageLookup
	) {
		$this->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;
	}
}