Completed
Push — master ( eadb3e...25e675 )
by
unknown
10:12
created

ImageModalTrigger::calculateImageWidth()   B

Complexity

Conditions 9
Paths 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 9.1918

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 13
cts 15
cp 0.8667
rs 8.0555
c 0
b 0
f 0
cc 9
nc 5
nop 3
crap 9.1918
1
<?php
2
/**
3
 * Contains the class generating the trigger string for the class {@see ImageModal}.
4
 *
5
 * @copyright (C) 2018, Tobias Oetterer, Paderborn University
6
 * @license       https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
7
 *
8
 * This file is part of the MediaWiki extension BootstrapComponents.
9
 * The BootstrapComponents extension is free software: you can redistribute it
10
 * and/or modify it under the terms of the GNU General Public License as published
11
 * by the Free Software Foundation, either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * The BootstrapComponents extension is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21
 *
22
 * @file
23
 * @ingroup       BootstrapComponents
24
 * @author        Tobias Oetterer
25
 */
26
27
namespace BootstrapComponents;
28
29
use \Linker;
30
use \Html;
31
use \MediaWiki\MediaWikiServices;
32
use \RequestContext;
33
use \Title;
34
35
/**
36
 * Class ImageModal
37
 *
38
 * @since 1.0
39
 */
40
class ImageModalTrigger {
41
	/**
42
	 * @var \File $file
43
	 */
44
	private $file;
45
46
	/**
47
	 * @var string $id
48
	 */
49
	private $id;
50
51
	/**
52
	 * ImageModal constructor.
53
	 *
54
	 * @param string $id
55
	 * @param \File  $file
56
	 */
57 14
	public function __construct( $id, $file ) {
58 14
		$this->id = $id;
59 14
		$this->file = $file;
60 14
	}
61
62
	/**
63
	 * @param array $sanitizedFrameParams
64
	 * @param array $handlerParams
65
	 *
66
	 * @throws \ConfigException cascading {@see \BootstrapComponents\ImageModalTrigger::generateTriggerCreateThumb}
67
	 * @throws \Exception       cascading {@see \BootstrapComponents\ImageModalTrigger::wrapAndFinalize}
68
	 *
69
	 * @return false|string
70
	 */
71 13
	public function generate( $sanitizedFrameParams, $handlerParams ) {
72
		/** @var \MediaTransformOutput $thumb */
73 13
		list( $thumb, $thumbHandlerParams ) = $this->createThumb(
74 13
			$this->getFile(),
75 13
			$sanitizedFrameParams,
76
			$handlerParams
77 13
		);
78
79 13
		if ( !$thumb ) {
80
			// We could deal with an invalid thumb, but then we would also need to signal in invalid modal.
81
			// Better let Linker.php take care
82 1
			wfDebugLog( 'BootstrapComponents', 'Image modal encountered an invalid thumbnail. Relegating back.' );
83 1
			return false;
84
		}
85 12
		$triggerOptions = $this->calculateHtmlOptions(
86 12
			$this->getFile(),
87 12
			$thumb,
88 12
			$sanitizedFrameParams,
89
			$thumbHandlerParams
90 12
		);
91 12
		$publicationString = $thumb->toHtml( $triggerOptions );
92 12
		return $this->wrapAndFinalize(
93 12
			$publicationString,
94 12
			$sanitizedFrameParams,
95 12
			$thumb->getWidth()
96 12
		);
97
	}
98
99
	/**
100
	 * @inheritdoc
101
	 */
102 12
	public function getId() {
103 12
		return $this->id;
104
	}
105
106
	/**
107
	 * @param \File                 $file
108
	 * @param \MediaTransformOutput $thumb
109
	 * @param array                 $sanitizedFrameParams
110
	 * @param array                 $thumbHandlerParams
111
	 *
112
	 * @return array
113
	 */
114 12
	protected function calculateHtmlOptions( $file, $thumb, $sanitizedFrameParams, $thumbHandlerParams ) {
115 12
		if ( $sanitizedFrameParams['thumbnail'] && (!isset( $sanitizedFrameParams['manualthumb'] ) && !$sanitizedFrameParams['framed']) ) {
116 2
			Linker::processResponsiveImages( $file, $thumb, $thumbHandlerParams );
117 2
		}
118
		$options = [
119 12
			'alt'       => $sanitizedFrameParams['alt'],
120 12
			'img-class' => $sanitizedFrameParams['class'],  // removed: . ' img-responsive'; keeping it in, causes line breaks around the trigger.
121 12
			'title'     => $sanitizedFrameParams['title'],
122 12
			'valign'    => $sanitizedFrameParams['valign'],
123 12
		];
124 12
		if ( $sanitizedFrameParams['thumbnail'] || isset( $sanitizedFrameParams['manualthumb'] ) || $sanitizedFrameParams['framed'] ) {
125 5
			$options['img-class'] .= ' thumbimage';
126 12
		} elseif ( $sanitizedFrameParams['border'] ) {
127
			$options['img-class'] .= ' thumbborder';
128
		}
129 12
		$options['img-class'] = trim( $options['img-class'] );
130
131
		// in Linker.php, options also run through {@see \Linker::getImageLinkMTOParams} to calculate the link value.
132
		// Since we abort at the beginning, if any link related frameParam is set, we can skip this.
133
		// also, obviously, we don't want to have ANY link around the img present.
134 12
		return $options;
135
	}
136
137
	/**
138
	 * @param \File $file
139
	 * @param array $sanitizedFrameParams
140
	 * @param array $handlerParams
141
	 *
142
	 * @throws \ConfigException cascading {@see \BootstrapComponents\ImageModal::generateTriggerReevaluateImageDimensions}
143
	 *
144
	 * @return array [ \MediaTransformOutput|false, handlerParams ]
145
	 */
146 13
	protected function createThumb( $file, $sanitizedFrameParams, $handlerParams ) {
147 13
		$transform = !isset( $sanitizedFrameParams['manualthumb'] ) && !$sanitizedFrameParams['framed'];
148 13
		$thumbFile = $file;
149 13
		$thumbHandlerParams = $this->reevaluateImageDimensions( $file, $sanitizedFrameParams, $handlerParams );
150
151 13
		if ( isset( $sanitizedFrameParams['manualthumb'] ) ) {
152 3
			$thumbFile = $this->getFileFromTitle( $sanitizedFrameParams['manualthumb'] );
153 3
		}
154
155
		if ( !$thumbFile
156 13
			|| (!$sanitizedFrameParams['thumbnail'] && !$sanitizedFrameParams['framed'] && !isset( $thumbHandlerParams['width'] ))
157 13
		) {
158 1
			return [ false, $thumbHandlerParams ];
159
		}
160
161 12
		if ( $transform ) {
162 9
			return [ $thumbFile->transform( $thumbHandlerParams ), $thumbHandlerParams ];
163
		} else {
164 3
			return [ $thumbFile->getUnscaledThumb( $thumbHandlerParams ), $thumbHandlerParams ];
165
		}
166
	}
167
168
	/**
169
	 * @return \File
170
	 */
171 13
	protected function getFile() {
172 13
		return $this->file;
173
	}
174
175
	/**
176
	 * This is mostly taken from {@see \Linker::makeImageLink}, rest originates from {@see \Linker::makeThumbLink2}. Extracts are heavily
177
	 * squashed and condensed
178
	 *
179
	 * @param \File $file
180
	 * @param array $sanitizedFrameParams
181
	 * @param array $handlerParams
182
	 *
183
	 * @throws \ConfigException cascading {@see \BootstrapComponents\ImageModal::generateTriggerCalculateImageWidth}
184
	 *
185
	 * @return array thumbnail handler params
186
	 */
187 13
	protected function reevaluateImageDimensions( $file, $sanitizedFrameParams, $handlerParams ) {
188 13
		if ( !isset( $handlerParams['width'] ) ) {
189 9
			$handlerParams = $this->calculateImageWidth( $file, $sanitizedFrameParams, $handlerParams );
190 9
		}
191 13
		if ( $this->amIThumbnailRelated( $sanitizedFrameParams ) ) {
192 7
			if ( empty( $handlerParams['width'] ) && !$sanitizedFrameParams['frameless'] ) {
193
				// Reduce width for upright images when parameter 'upright' is used
194
				$handlerParams['width'] = isset( $sanitizedFrameParams['upright'] ) ? 130 : 180;
195
			}
196 7
			$handlerParams = $this->limitSizeToSourceOnBitmapImages( $file, $sanitizedFrameParams, $handlerParams );
197 7
		}
198
199 13
		return $handlerParams;
200
	}
201
202
	/**
203
	 * Envelops the publication trigger img-tag.
204
	 *
205
	 * @param string $publicationString
206
	 * @param array  $sanitizedFrameParams
207
	 * @param int    $publicationWidth
208
	 *
209
	 * @throws \Exception cascading {@see ImageModalTrigger::buildThumbnailTrigger}
210
	 *
211
	 * @return string
212
	 */
213 12
	protected function wrapAndFinalize( $publicationString, $sanitizedFrameParams, $publicationWidth ) {
214 12
		if ( $sanitizedFrameParams['thumbnail'] || isset( $sanitizedFrameParams['manualthumb'] ) || $sanitizedFrameParams['framed'] ) {
215 5
			$ret = $this->buildThumbnailTrigger( $publicationString, $sanitizedFrameParams, $publicationWidth );
216 12
		} elseif ( strlen( $sanitizedFrameParams['align'] ) ) {
217 4
			$class = $sanitizedFrameParams['align'] == 'center' ? 'center' : 'float' . $sanitizedFrameParams['align'];
218 4
			$ret = Html::rawElement(
219 4
				'div',
220 4
				[ 'class' => $class, ],
221 4
				ModalBuilder::wrapTriggerElement( $publicationString, $this->getId() )
222 4
			);
223 4
		} else {
224 5
			$ret = ModalBuilder::wrapTriggerElement( $publicationString, $this->getId() );
225
		}
226
227 12
		return str_replace( "\n", ' ', $ret );
228
	}
229
230
	/**
231
	 * @param $sanitizedFrameParams
232
	 *
233
	 * @return bool
234
	 */
235 13
	private function amIThumbnailRelated( $sanitizedFrameParams ) {
236 13
		return $sanitizedFrameParams['thumbnail']
237 12
			|| isset( $sanitizedFrameParams['manualthumb'] )
238 12
			|| $sanitizedFrameParams['framed']
239 13
			|| $sanitizedFrameParams['frameless'];
240
	}
241
242
	/**
243
	 * Envelops a publication trigger img-tag that is a thumbnail.
244
	 *
245
	 * @param string $publicationString
246
	 * @param array  $sanitizedFrameParams
247
	 * @param int    $publicationWidth
248
	 *
249
	 * @throws \Exception cascading {@see \RequestContext::getMain}
250
	 *
251
	 * @return string
252
	 */
253 5
	private function buildThumbnailTrigger( $publicationString, $sanitizedFrameParams, $publicationWidth ) {
254 5
		if ( empty( $sanitizedFrameParams['align'] ) ) {
255
			$sanitizedFrameParams['align'] = RequestContext::getMain()->getLanguage()->alignEnd();
256
		}
257 5
		$zoomIcon = $this->buildZoomIcon( $sanitizedFrameParams );
258 5
		$outerWidth = $publicationWidth + 2;
259 5
		$class = 'thumb t' . ($sanitizedFrameParams['align'] == 'center' ? 'none' : $sanitizedFrameParams['align']);
260
261 5
		return Html::rawElement(
262 5
			'div',
263
			[
264 5
				'class' => $class,
265 5
			],
266 5
			ModalBuilder::wrapTriggerElement(
267 5
				Html::rawElement(
268 5
					'div',
269
					[
270 5
						'class' => 'thumbinner',
271 5
						'style' => 'width:' . $outerWidth . 'px;',
272 5
					],
273 5
					$publicationString . '  ' . Html::rawElement(
274 5
						'div',
275 5
						[ 'class' => 'thumbcaption' ],
276 5
						$zoomIcon . $sanitizedFrameParams['caption']
277 5
					)
278 5
				),
279 5
				$this->getId()
280 5
			)
281 5
		);
282
	}
283
284
	/**
285
	 * @param array $sanitizedFrameParams
286
	 *
287
	 * @return string
288
	 */
289 5
	private function buildZoomIcon( $sanitizedFrameParams ) {
290 5
		if ( $sanitizedFrameParams['framed'] ) {
291 2
			return '';
292
		}
293 3
		return Html::rawElement(
294 3
			'div',
295
			[
296 3
				'class' => 'magnify',
297 3
			],
298 3
			Html::rawElement(
299 3
				'a',
300
				[
301 3
					'class' => 'internal',
302 3
					'title' => wfMessage( 'thumbnail-more' )->text(),
303 3
				],
304
				""
305 3
			)
306 3
		);
307
	}
308
309
	/**
310
	 * Calculates a with from File, $sanitizedFrameParams, and $handlerParams
311
	 *
312
	 * @param \File $file
313
	 * @param array $sanitizedFrameParams
314
	 * @param array $handlerParams
315
	 *
316
	 * @throws \ConfigException cascading {@see ImageModal::getInitialWidthSuggestion} or {@see ImageModal::getPreferredWidth}
317
	 *
318
	 * @return array thumbnail handler params
319
	 */
320 9
	private function calculateImageWidth( $file, $sanitizedFrameParams, $handlerParams ) {
321 9
		$globalConfig = MediaWikiServices::getInstance()->getMainConfig();
322
323 9
		$handlerParams['width'] = $this->getInitialWidthSuggestion( $globalConfig, $file, $handlerParams );
324
325 9
		if ( $this->amIThumbnailRelated( $sanitizedFrameParams ) || !$handlerParams['width'] ) {
326
			// Reduce width for upright images when parameter 'upright' is used
327 5
			if ( isset( $sanitizedFrameParams['upright'] ) && $sanitizedFrameParams['upright'] == 0 ) {
328
				$sanitizedFrameParams['upright'] = $globalConfig->get( 'ThumbUpright' );
329
			}
330
331
			// note: the upright calculation above resided originally inside the two blocks in the following method
332 5
			$prefWidth = $this->getPreferredWidth( $globalConfig, $sanitizedFrameParams );
333
334
			// Use width which is smaller: real image width or user preference width
335
			// Unless image is scalable vector.
336 5
			if ( !isset( $handlerParams['height'] ) &&
337 5
				($handlerParams['width'] <= 0 || $prefWidth < $handlerParams['width'] || $file->isVectorized())
338 5
			) {
339 2
				$handlerParams['width'] = $prefWidth;
340 2
			}
341 5
		}
342 9
		return $handlerParams;
343
	}
344
345
	/**
346
	 * @param string $fileTitle
347
	 *
348
	 * @return bool|\File
349
	 */
350 3
	private function getFileFromTitle( $fileTitle ) {
351 3
		$manual_title = Title::makeTitleSafe( NS_FILE, $fileTitle );
352 3
		if ( $manual_title ) {
353 3
			return wfFindFile( $manual_title );
354
		}
355
		return false;
356
	}
357
358
	/**
359
	 * @param \Config $globalConfig
360
	 * @param \File   $file
361
	 * @param array   $handlerParams
362
	 *
363
	 * @throws \ConfigException cascading {@see \Config::get}
364
	 *
365
	 * @return mixed
366
	 */
367 9
	private function getInitialWidthSuggestion( $globalConfig, $file, $handlerParams ) {
368 9
		if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
369
			// If its a vector image, and user only specifies height
370
			// we don't want it to be limited by its "normal" width.
371
			return $globalConfig->get( 'SVGMaxSize' );
372
		} else {
373 9
			return $file->getWidth( $handlerParams['page'] );
374
		}
375
	}
376
377
	/**
378
	 * @param \Config $globalConfig
379
	 * @param array   $sanitizedFrameParams
380
	 *
381
	 * @throws \ConfigException cascading {@see \Config::get}
382
	 *
383
	 * @return float
384
	 */
385 5
	private function getPreferredWidth( $globalConfig, $sanitizedFrameParams ) {
386 5
		$thumbLimits = $globalConfig->get( 'ThumbLimits' );
387 5
		$widthOption = $this->getWidthOptionForThumbLimits( $thumbLimits );
388
389
		// For caching health: If width scaled down due to upright
390
		// parameter, round to full __0 pixel to avoid the creation of a
391
		// lot of odd thumbs.
392 5
		return isset( $sanitizedFrameParams['upright'] )
393 5
			? round( $thumbLimits[$widthOption] * $sanitizedFrameParams['upright'], -1 )
394 5
			: $thumbLimits[$widthOption];
395
	}
396
397
	/**
398
	 * @param array $thumbLimits
399
	 *
400
	 * @return int|string
401
	 */
402 5
	private function getWidthOptionForThumbLimits( $thumbLimits ) {
403
404
		// this could also be affected by issue #9
405 5
		$user = RequestContext::getMain()->getUser();
406 5
		$widthOption = $user::getDefaultOption( 'thumbsize' );
407
408
		// we have a problem here: the original \Linker::makeImageLink does get a value for $widthOption,
409
		// for instance in parser tests. unfortunately, this value is not passed through the hook.
410
		// so there are instances, where $thumbLimits[$widthOption] is not defined.
411
		// solution: we cheat and take the first one
412 5
		if ( $widthOption !== null && isset( $thumbLimits[$widthOption] ) ) {
413 5
			return $widthOption;
414
		}
415
		$availableOptions = array_keys( $thumbLimits );
416
		return reset( $availableOptions );
417
	}
418
419
	/**
420
	 * Do not present an image bigger than the source, for bitmap-style images
421
	 *
422
	 * @param \File $file
423
	 * @param array $sanitizedFrameParams
424
	 * @param array $handlerParams
425
	 *
426
	 * @return array
427
	 */
428 7
	private function limitSizeToSourceOnBitmapImages( $file, $sanitizedFrameParams, $handlerParams ) {
429 7
		if ( $sanitizedFrameParams['frameless']
430 6
			|| (!isset( $sanitizedFrameParams['manualthumb'] ) && !$sanitizedFrameParams['framed'])
431 7
		) {
432 3
			$srcWidth = $file->getWidth( $handlerParams['page'] );
433 3
			if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
434 2
				$handlerParams['width'] = $srcWidth;
435 2
			}
436 3
		}
437 7
		return $handlerParams;
438
	}
439
}
440