Completed
Pull Request — master (#453)
by
unknown
18:28
created

Gallery::getNumbers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 0
cp 0
rs 9.9
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 20
1
<?php
2
3
namespace SRF;
4
5
use Html;
6
use SMW\ResultPrinter;
7
use SMWDataItem;
8
use SMWOutputs;
9
use SMWPrintRequest;
10
use SMWQueryResult;
11
use SRFUtils;
12
use Title;
13
use TraditionalImageGallery;
14
15
/**
16
 * Result printer that outputs query results as a image gallery.
17
 *
18
 * @author Jeroen De Dauw < [email protected] >
19
 * @author mwjames
20
 * @author Rowan Rodrik van der Molen
21
 */
22
class Gallery extends ResultPrinter {
23
24
	/**
25
	 * @see SMWResultPrinter::getName
26
	 *
27
	 * @return string
28
	 */
29
	public function getName() {
30
		return $this->msg( 'srf_printername_gallery' )->text();
31
	}
32
33
	/**
34
	 * @see SMWResultPrinter::buildResult
35
	 *
36
	 * @since 1.8
37
	 *
38
	 * @param SMWQueryResult $results
39
	 *
40
	 * @return string
41
	 */
42 1
	protected function buildResult( SMWQueryResult $results ) {
43
44
		// Intro/outro are not planned to work with the widget option
45 1
		if ( ( $this->params['intro'] !== '' || $this->params['outro'] !== '' ) && $this->params['widget'] !== '' ) {
46
			$results->addErrors(
47
				[
48
					$this->msg( 'srf-error-option-mix', 'widget' )->inContentLanguage()->text()
49
				]
50
			);
51
52
			return '';
53
		};
54
55 1
		return $this->getResultText( $results, $this->outputMode );
56
	}
57
58
	/**
59
	 * @see SMWResultPrinter::getResultText
60
	 *
61
	 * @param $results SMWQueryResult
62
	 * @param $outputmode integer
63
	 *
64
	 * @return string | array
65
	 */
66 1
	public function getResultText( SMWQueryResult $results, $outputmode ) {
0 ignored issues
show
Coding Style introduced by
getResultText uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
67
68 1
		$numbers = $this->getNumbers( $results );
69
				if ( count( $numbers ) == 0 ) {
70 1
					return $this->params['default'];
71 1
				}
72
73 1
		$ig = new TraditionalImageGallery();
74
75
		$ig->setShowBytes( false );
76
		$ig->setShowFilename( false );
77 1
78
		if ( method_exists( $ig, 'setShowDimensions' ) ) {
79
			$ig->setShowDimensions( false );
80
		}
81 1
82 1
		$ig->setCaption( $this->mIntro ); // set caption to IQ header
83
84
		// No need for a special page to use the parser but for the "normal" page
85 1
		// view we have to ensure caption text is parsed correctly through the parser
86 1
		if ( !$this->isSpecialPage() ) {
87
			$ig->setParser( $GLOBALS['wgParser'] );
88 1
		}
89
90
		$html = '';
91 1
		$processing = '';
92
93
		if ( $this->params['widget'] == 'carousel' ) {
94
			// Carousel widget
95
			$ig->setAttributes( $this->getCarouselWidget() );
96
		} elseif ( $this->params['widget'] == 'slideshow' ) {
97
			// Slideshow widget
98 1
			$ig->setAttributes( $this->getSlideshowWidget() );
99 1
		} else {
100
101
			// Standard gallery attributes
102 1
			$attribs = [
103
				'id' => uniqid(),
104
				'class' => $this->getImageOverlay(),
105
			];
106
107 1
			$ig->setAttributes( $attribs );
108
		}
109
110
		// Only use redirects where the overlay option is not used and redirect
111
		// thumb images towards a different target
112 1
		if ( $this->params['redirects'] !== '' && !$this->params['overlay'] ) {
113
			SMWOutputs::requireResource( 'ext.srf.gallery.redirect' );
114
		}
115
116 1
		// For the carousel widget, the perrow option should not be set
117
		if ( $this->params['perrow'] !== '' && $this->params['widget'] !== 'carousel' ) {
118
			$ig->setPerRow( $this->params['perrow'] );
119
		}
120 1
121
		if ( $this->params['widths'] !== '' ) {
122
			$ig->setWidths( $this->params['widths'] );
123
		}
124 1
125 1
		if ( $this->params['heights'] !== '' ) {
126
			$ig->setHeights( $this->params['heights'] );
127
		}
128
129
		$printReqLabels = [];
130 1
		$redirectType = '';
131 1
132
		/**
133
		 * @var SMWPrintRequest $printReq
134 1
		 */
135 1
		foreach ( $results->getPrintRequests() as $printReq ) {
136
			$printReqLabels[] = $printReq->getLabel();
137
138
			// Get redirect type
139 1
			if ( $this->params['redirects'] === $printReq->getLabel() ) {
140 1
				$redirectType = $printReq->getTypeID();
141
			}
142
		}
143
144
		if ( $this->params['imageproperty'] !== '' && in_array( $this->params['imageproperty'], $printReqLabels ) ||
145
			$this->params['redirects'] !== '' && in_array( $this->params['redirects'], $printReqLabels ) ) {
146
147
			$this->addImageProperties(
148
				$results,
149
				$ig,
150
				$this->params['imageproperty'],
151 1
				$this->params['captionproperty'],
152
				$this->params['redirects'],
153
				$outputmode
154
			);
155 1
		} else {
156
			$this->addImagePages( $results, $ig );
157
		}
158 1
159
		// SRF Global settings
160
		SRFUtils::addGlobalJSVariables();
161
162
		// Display a processing image as long as the DOM is no ready
163 1
		if ( $this->params['widget'] !== '' ) {
164 1
			$processing = SRFUtils::htmlProcessingElement();
165 1
		}
166
167
		// Beautify the class selector
168 1
		$class = $this->params['widget'] ? '-' . $this->params['widget'] . ' ' : '';
169
		$class = $this->params['redirects'] !== '' && $this->params['overlay'] === false ? $class . ' srf-redirect' . ' ' : $class;
170 1
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class;
171 1
172 1
		// Separate content from result output
173
		if ( !$ig->isEmpty() ) {
174
			$attribs = [
175 1
				'class' => 'srf-gallery' . $class,
176
				'data-redirect-type' => $redirectType,
177
				'data-ns-text' => $this->getFileNsTextForPageLanguage()
178
			];
179 1
180
			$html = Html::rawElement( 'div', $attribs, $processing . $ig->toHTML() );
181
		}
182
183 1
		// If available, create a link that points to further results
184
		if ( $this->linkFurtherResults( $results ) ) {
185
			$html .= $this->getLink( $results, SMW_OUTPUT_HTML )->getText( SMW_OUTPUT_HTML, $this->mLinker );
186
		}
187
188
		return [ $html, 'nowiki' => true, 'isHTML' => true ];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($html, 'now...rue, 'isHTML' => true); (array) is incompatible with the return type documented by SRF\Gallery::getResultText of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
189
	}
190
191
	/**
192
	 * Handles queries where the images (and optionally their captions) are specified as properties.
193
	 *
194
	 * @since 1.5.3
195
	 *
196
	 * @param SMWQueryResult $results
197
	 * @param TraditionalImageGallery $ig
198
	 * @param string $imageProperty
199
	 * @param string $captionProperty
200
	 * @param string $redirectProperty
201
	 * @param $outputMode
202
	 */
203
	protected function addImageProperties( SMWQueryResult $results, &$ig, $imageProperty, $captionProperty, $redirectProperty, $outputMode ) {
204
		while ( /* array of SMWResultArray */
205
		$rows = $results->getNext() ) { // Objects (pages)
206
			$images = [];
207
			$captions = [];
208
			$redirects = [];
209
210
			for ( $i = 0, $n = count( $rows ); $i < $n; $i++ ) { // Properties
211
				/**
212
				 * @var \SMWResultArray $resultArray
213
				 * @var \SMWDataValue $dataValue
214
				 */
215
				$resultArray = $rows[$i];
216
217
				$label = $resultArray->getPrintRequest()->getMode() == SMWPrintRequest::PRINT_THIS
218
					? '-' : $resultArray->getPrintRequest()->getLabel();
219
220
				// Make sure always use real label here otherwise it results in an empty array
221
				if ( $resultArray->getPrintRequest()->getLabel() == $imageProperty ) {
222
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
223
						if ( $dataValue->getTypeID() == '_wpg' ) {
224
							$images[] = $dataValue->getTitle();
225
						}
226
					}
227
				} elseif ( $label == $captionProperty ) {
228
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
229
						$captions[] = $dataValue->getShortText( $outputMode, $this->getLinker( true ) );
230
					}
231
				} elseif ( $label == $redirectProperty ) {
232
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
233
						if ( $dataValue->getDataItem()->getDIType() == SMWDataItem::TYPE_WIKIPAGE ) {
234
							$redirects[] = $dataValue->getTitle();
235
						} elseif ( $dataValue->getDataItem()->getDIType() == SMWDataItem::TYPE_URI ) {
236
							$redirects[] = $dataValue->getURL();
237
						}
238
					}
239
				}
240
			}
241
242
			// Check available matches against captions
243
			$amountMatches = count( $captions ) == count( $images );
244
			$hasCaption = $amountMatches || count( $captions ) > 0;
245
246
			// Check available matches against redirects
247
			$amountRedirects = count( $redirects ) == count( $images );
248
			$hasRedirect = $amountRedirects || count( $redirects ) > 0;
249
250
			/**
251
			 * @var Title $imgTitle
252
			 */
253
			foreach ( $images as $imgTitle ) {
254
				if ( $imgTitle->exists() ) {
255
					$imgCaption = $hasCaption ? ( $amountMatches ? array_shift( $captions ) : $captions[0] ) : '';
256
					$imgRedirect = $hasRedirect ? ( $amountRedirects ? array_shift( $redirects ) : $redirects[0] ) : '';
257
					$this->addImageToGallery( $ig, $imgTitle, $imgCaption, $imgRedirect );
258
				}
259
			}
260
		}
261
	}
262
263
	/**
264
	 * Handles queries where the result objects are image pages.
265
	 *
266 1
	 * @since 1.5.3
267 1
	 *
268
	 * @param SMWQueryResult $results
269
	 * @param TraditionalImageGallery $ig
270
	 */
271 1
	protected function addImagePages( SMWQueryResult $results, &$ig ) {
272 1
		while ( $row = $results->getNext() ) {
273
			/**
274 1
			 * @var \SMWResultArray $firstField
275 1
			 */
276
			$firstField = $row[0];
277
			$nextObject = $firstField->getNextDataValue();
278 1
279 1
			if ( $nextObject !== false ) {
280
				$imgTitle = method_exists( $nextObject, 'getTitle' ) ? $nextObject->getTitle() : null;
281
282 1
				// Ensure the title belongs to the image namespace
283 1
				if ( $imgTitle instanceof Title && $imgTitle->getNamespace() === NS_FILE ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
284 1
					$imgCaption = '';
285 1
286
					// Is there a property queried for display with ?property
287
					if ( isset( $row[1] ) ) {
288
						$imgCaption = $row[1]->getNextDataValue();
289 1
						if ( is_object( $imgCaption ) ) {
290
							$imgCaption = $imgCaption->getShortText( $this->outputMode, $this->getLinker( true ) );
291
						}
292
					}
293 1
294
					$this->addImageToGallery( $ig, $imgTitle, $imgCaption );
295
				}
296
			}
297
		}
298
	}
299
300
	/**
301
	 * Adds a single image to the gallery.
302
	 * Takes care of automatically adding a caption when none is provided and parsing it's wikitext.
303
	 *
304
	 * @since 1.5.3
305
	 *
306 1
	 * @param TraditionalImageGallery $ig The gallery to add the image to
307
	 * @param Title $imgTitle The title object of the page of the image
308 1
	 * @param string $imgCaption An optional caption for the image
309
	 * @param string $imgRedirect
310
	 */
311
	protected function addImageToGallery( &$ig, Title $imgTitle, $imgCaption, $imgRedirect = '' ) {
312
313
		if ( empty( $imgCaption ) ) {
314
			if ( $this->params['autocaptions'] ) {
315
				$imgCaption = $imgTitle->getBaseText();
316
317
				if ( !$this->params['fileextensions'] ) {
318
					$imgCaption = preg_replace( '#\.[^.]+$#', '', $imgCaption );
319 1
				}
320 1
			} else {
321
				$imgCaption = '';
322
			}
323
		} else {
324 1
			if ( $imgTitle instanceof Title && $imgTitle->getNamespace() == NS_FILE && !$this->isSpecialPage() ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
325 1
				$imgCaption = $ig->mParser->recursiveTagParse( $imgCaption );
326 1
			}
327
		}
328
		// Use image alt as helper for either text
329
		$imgAlt = $this->params['redirects'] === '' ? $imgCaption : $imgRedirect !== '' ? $imgRedirect : '';
330
		$ig->add( $imgTitle, $imgCaption, $imgAlt );
331
	}
332
333
	/**
334
	 * Returns the overlay setting
335 1
	 *
336 1
	 * @since 1.8
337
	 *
338
	 * @return string
339
	 */
340 1
	private function getImageOverlay() {
341
		if ( array_key_exists( 'overlay', $this->params ) && $this->params['overlay'] == true ) {
342
			SMWOutputs::requireResource( 'ext.srf.gallery.overlay' );
343
			return ' srf-overlay';
344
		} else {
345
			return '';
346
		}
347
	}
348
349
	/**
350
	 * Init carousel widget
351
	 *
352
	 * @since 1.8
353
	 *
354
	 * @return string[]
355
	 */
356
	private function getCarouselWidget() {
357
358
		// Set attributes for jcarousel
359
		$dataAttribs = [
360
			'wrap' => 'both', // Whether to wrap at the first/last item (or both) and jump back to the start/end.
361
			'vertical' => 'false', // Orientation: vertical = false means horizontal
362
			'rtl' => 'false', // Directionality: rtl = false means ltr
363
		];
364
365
		// Use the perrow parameter to determine the scroll sequence.
366
		if ( empty( $this->params['perrow'] ) ) {
367
			$dataAttribs['scroll'] = 1;  // default 1
368
		} else {
369
			$dataAttribs['scroll'] = $this->params['perrow'];
370
			$dataAttribs['visible'] = $this->params['perrow'];
371
		}
372
373
		$attribs = [
374
			'id' => uniqid(),
375
			'class' => 'jcarousel jcarousel-skin-smw' . $this->getImageOverlay(),
376
			'style' => 'display:none;',
377
		];
378
379
		foreach ( $dataAttribs as $name => $value ) {
380
			$attribs['data-' . $name] = $value;
381
		}
382
383
		SMWOutputs::requireResource( 'ext.srf.gallery.carousel' );
384
385
		return $attribs;
386
	}
387
388
	/**
389
	 * Init slideshow widget
390
	 *
391
	 * @since 1.8
392
	 *
393
	 * @return string[]
394
	 */
395
	private function getSlideshowWidget() {
396
397
		$attribs = [
398
			'id' => uniqid(),
399
			'class' => $this->getImageOverlay(),
400
			'style' => 'display:none;',
401
			'data-nav-control' => $this->params['navigation']
402
		];
403
404
		SMWOutputs::requireResource( 'ext.srf.gallery.slideshow' );
405
406
		return $attribs;
407
	}
408
409
	/**
410
	 * @see SMWResultPrinter::getParamDefinitions
411
	 *
412
	 * @since 1.8
413 1
	 *
414 1
	 * @param $definitions array of IParamDefinition
415
	 *
416 1
	 * @return array of IParamDefinition|array
417
	 */
418
	public function getParamDefinitions( array $definitions ) {
419
		$params = parent::getParamDefinitions( $definitions );
420
421
		$params['class'] = [
422 1
			'type' => 'string',
423
			'message' => 'srf-paramdesc-class',
424
			'default' => ''
425
		];
426
427
		$params['widget'] = [
428
			'type' => 'string',
429 1
			'default' => '',
430
			'message' => 'srf-paramdesc-widget',
431
			'values' => [ 'carousel', 'slideshow', '' ]
432
		];
433
434
		$params['navigation'] = [
435
			'type' => 'string',
436 1
			'default' => 'nav',
437
			'message' => 'srf-paramdesc-navigation',
438
			'values' => [ 'nav', 'pager', 'auto' ]
439
		];
440
441
		$params['overlay'] = [
442 1
			'type' => 'boolean',
443
			'default' => false,
444
			'message' => 'srf-paramdesc-overlay'
445
		];
446
447
		$params['perrow'] = [
448 1
			'type' => 'integer',
449
			'default' => '',
450
			'message' => 'srf_paramdesc_perrow'
451
		];
452
453
		$params['widths'] = [
454 1
			'type' => 'integer',
455
			'default' => '',
456
			'message' => 'srf_paramdesc_widths'
457
		];
458
459
		$params['heights'] = [
460 1
			'type' => 'integer',
461
			'default' => '',
462
			'message' => 'srf_paramdesc_heights'
463
		];
464
465
		$params['autocaptions'] = [
466 1
			'type' => 'boolean',
467
			'default' => true,
468
			'message' => 'srf_paramdesc_autocaptions'
469
		];
470
471
		$params['fileextensions'] = [
472 1
			'type' => 'boolean',
473
			'default' => false,
474
			'message' => 'srf_paramdesc_fileextensions'
475
		];
476
477
		$params['captionproperty'] = [
478 1
			'type' => 'string',
479
			'default' => '',
480
			'message' => 'srf_paramdesc_captionproperty'
481
		];
482
483
		$params['imageproperty'] = [
484 1
			'type' => 'string',
485
			'default' => '',
486
			'message' => 'srf_paramdesc_imageproperty'
487
		];
488
489
		$params['redirects'] = [
490 1
			'type' => 'string',
491
			'default' => '',
492
			'message' => 'srf-paramdesc-redirects'
493
		];
494
495
		return $params;
496 1
	}
497 1
498 1
	/**
499
	 * @return bool
500
	 */
501
	private function isSpecialPage() {
0 ignored issues
show
Coding Style introduced by
isSpecialPage uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
502
		$title = $GLOBALS['wgTitle'];
503
		return $title instanceof Title && $title->isSpecialPage();
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
504 1
	}
505 1
506 1
	/**
507
	 * @return bool|null|string
508
	 */
509
	private function getFileNsTextForPageLanguage() {
0 ignored issues
show
Coding Style introduced by
getFileNsTextForPageLanguage uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
510
		$title = $GLOBALS['wgTitle'];
511
		return $title instanceof Title ? $title->getPageLanguage()->getNsText( NS_FILE ) : null;
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
512
	}
513
	/**
514
	 * @param SMWQueryResult $res
515
	 * 
516
	 * @return float[]
517
	 */
518
	private function getNumbers( SMWQueryResult $res ) {
519
		$numbers = [];
520
		while ( $row = $res->getNext() ) {
521
			foreach ( $row as $resultArray ) {
522
				foreach ( $resultArray->getContent() as $dataItem ) {
523
					self::addNumbersForDataItem( $dataItem, $numbers );
524
				}
525
			}
526
		}
527
		return $numbers;
528
	}
529
}
530