Completed
Push — master ( d0ca26...98fb90 )
by mw
37:57
created

ExtraneousLanguage::fetchByLanguageCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SMW\ExtraneousLanguage;
4
5
use RuntimeException;
6
7
/**
8
 * This class provides "extraneous" language functions independent from MediaWiki
9
 * serving a special need to handle certain language options in a way required by
10
 * Semantic MediaWiki and its registration system.
11
 *
12
 * This class makes use of:
13
 *
14
 * - `LanguageContents` as the interface that manages the raw content
15
 * - `LanguageFileContentsReader` is serving (reading) content from a JSON file
16
 * - `LanguageFallbackFinder` is responsible for resolving a fallback language
17
 *
18
 * @license GNU GPL v2+
19
 * @since 2.4
20
 *
21
 * @author mwjames
22
 */
23
class ExtraneousLanguage {
24
25
	/**
26
	 * @var ExtraneousLanguage
27
	 */
28
	private static $instance = null;
29
30
	/**
31
	 * @var LanguageContents
32
	 */
33
	private $languageContents;
34
35
	/**
36
	 * @var boolean
37
	 */
38
	private $historicTypeNamespace = false;
39
40
	/**
41
	 * @var string
42
	 */
43
	private $languageCode = 'en';
44
45
	/**
46
	 * @var array
47
	 */
48
	private $propertyIdByLabelMap = array();
49
50
	/**
51
	 * @var array
52
	 */
53
	private $dateFormatsMap = array();
54
55
	/**
56
	 * @var array
57
	 */
58
	private $monthMap = array();
0 ignored issues
show
Unused Code introduced by
The property $monthMap is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
59
60
	/**
61
	 * @since 2.4
62
	 *
63
	 * @param LanguageContents $languageContents
64
	 */
65
	public function __construct( LanguageContents $languageContents ) {
66
		$this->languageContents = $languageContents;
67
	}
68
69
	/**
70
	 * @since 2.4
71
	 *
72
	 * @return ExtraneousLanguage
73
	 */
74
	public static function getInstance() {
0 ignored issues
show
Coding Style introduced by
getInstance 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...
75
76
		if ( self::$instance !== null ) {
77
			return self::$instance;
78
		}
79
80
		// $cache = ApplicationFactory::getInstance()->getCache()
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
81
82
		$languageFileContentsReader = new LanguageFileContentsReader();
83
		//$languageFileContentsReader->setCachePrefix( $cacheFactory->getCachePrefix() )
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
84
85
		self::$instance = new self(
86
			new LanguageContents(
87
				$languageFileContentsReader,
88
				new LanguageFallbackFinder( $languageFileContentsReader )
89
			)
90
		);
91
92
		self::$instance->setHistoricTypeNamespace(
93
			$GLOBALS['smwgHistoricTypeNamespace']
94
		);
95
96
		return self::$instance;
97
	}
98
99
	/**
100
	 * @since 2.4
101
	 */
102
	public static function clear() {
103
		self::$instance = null;
104
	}
105
106
	/**
107
	 * @since 2.5
108
	 *
109
	 * @param boolean $historicTypeNamespace
110
	 */
111
	public function setHistoricTypeNamespace( $historicTypeNamespace ) {
112
		$this->historicTypeNamespace = (bool)$historicTypeNamespace;
113
	}
114
115
	/**
116
	 * @since 2.4
117
	 *
118
	 * @return string
119
	 */
120
	public function getCode() {
121
		return $this->languageCode;
122
	}
123
124
	/**
125
	 * @since 2.4
126
	 *
127
	 * @return string
128
	 */
129
	public function fetchByLanguageCode( $languageCode ) {
130
131
		$this->languageCode = strtolower( trim( $languageCode ) );
132
133
		if ( !$this->languageContents->has( $this->languageCode ) ) {
134
			$this->languageContents->prepareWithLanguage( $this->languageCode );
135
		}
136
137
		return $this;
138
	}
139
140
	/**
141
	 * Function that returns an array of namespace identifiers.
142
	 *
143
	 * @since 2.4
144
	 *
145
	 * @return array
146
	 */
147
	public function getNamespaces() {
148
149
		$namespaces = $this->languageContents->getFromLanguageWithIndex(
150
			$this->languageCode,
151
			'namespaces'
152
		);
153
154
		$namespaces += $this->languageContents->getFromLanguageWithIndex(
155
			$this->languageContents->getCanonicalFallbackLanguageCode(),
156
			'namespaces'
157
		);
158
159
		foreach ( $namespaces as $key => $value ) {
160
			unset( $namespaces[ $key ] );
161
			$namespaces[constant($key)] = $value;
162
		}
163
164
		if ( $this->historicTypeNamespace ) {
165
			return $namespaces;
166
		}
167
168
		unset( $namespaces[SMW_NS_TYPE] );
169
		unset( $namespaces[SMW_NS_TYPE_TALK] );
170
171
		return $namespaces;
172
	}
173
174
	/**
175
	 * Function that returns an array of namespace aliases, if any
176
	 *
177
	 * @since 2.4
178
	 *
179
	 * @return array
180
	 */
181
	public function getNamespaceAliases() {
182
183
		$namespaceAliases = $this->languageContents->getFromLanguageWithIndex(
184
			$this->languageCode,
185
			'namespaceAliases'
186
		);
187
188
		$namespaceAliases += $this->languageContents->getFromLanguageWithIndex(
189
			$this->languageContents->getCanonicalFallbackLanguageCode(),
190
			'namespaceAliases'
191
		);
192
193
		foreach ( $namespaceAliases as $alias => $namespace ) {
194
			$namespaceAliases[$alias] = constant( $namespace );
195
		}
196
197
		if ( $this->historicTypeNamespace ) {
198
			return $namespaceAliases;
199
		}
200
201
		foreach ( $namespaceAliases as $alias => $namespace ) {
202
			if ( $namespace === SMW_NS_TYPE || $namespace === SMW_NS_TYPE_TALK ) {
203
				unset( $namespaceAliases[$alias] );
204
			}
205
		}
206
207
		return $namespaceAliases;
208
	}
209
210
	/**
211
	 * Return all labels that are available as names for built-in datatypes. Those
212
	 * are the types that users can access via [[has type::...]] (more built-in
213
	 * types may exist for internal purposes but the user won't need to
214
	 * know this). The returned array is indexed by (internal) type ids.
215
	 *
216
	 * @since 2.4
217
	 *
218
	 * @return array
219
	 */
220
	public function getDatatypeLabels() {
221
222
		$datatypeLabels = $this->languageContents->getFromLanguageWithIndex(
223
			$this->languageCode,
224
			'dataTypeLabels'
225
		);
226
227
		$datatypeLabels += $this->languageContents->getFromLanguageWithIndex(
228
			$this->languageContents->getCanonicalFallbackLanguageCode(),
229
			'dataTypeLabels'
230
		);
231
232
		return $datatypeLabels;
233
	}
234
235
	/**
236
	 * @since 2.4
237
	 *
238
	 * @return array
239
	 */
240
	public function getCanonicalDatatypeLabels() {
241
		return $this->languageContents->getFromLanguageWithIndex(
242
			$this->languageContents->getCanonicalFallbackLanguageCode(),
243
			'dataTypeAliases'
244
		);
245
	}
246
247
	/**
248
	 * Return an array that maps aliases to internal type ids. All ids used here
249
	 * should also have a primary label defined in m_DatatypeLabels.
250
	 *
251
	 * @since 2.4
252
	 *
253
	 * @return array
254
	 */
255
	public function getDatatypeAliases() {
256
257
		$datatypeAliases = $this->languageContents->getFromLanguageWithIndex(
258
			$this->languageCode,
259
			'dataTypeAliases'
260
		);
261
262
		$datatypeAliases += $this->languageContents->getFromLanguageWithIndex(
263
			$this->languageContents->getCanonicalFallbackLanguageCode(),
264
			'dataTypeAliases'
265
		);
266
267
		return $datatypeAliases;
268
	}
269
270
	/**
271
	 * @since 2.4
272
	 *
273
	 * @return array
274
	 */
275
	public function getCanonicalPropertyLabels() {
276
277
		$canonicalPropertyLabels = $this->languageContents->getFromLanguageWithIndex(
278
			$this->languageContents->getCanonicalFallbackLanguageCode(),
279
			'propertyLabels'
280
		);
281
282
		$canonicalPropertyLabels = array_flip( $canonicalPropertyLabels );
283
284
		$canonicalPropertyLabels += $this->languageContents->getFromLanguageWithIndex(
285
			$this->languageContents->getCanonicalFallbackLanguageCode(),
286
			'propertyAliases'
287
		);
288
289
		return $canonicalPropertyLabels;
290
	}
291
292
	/**
293
	 * Function that returns the labels for predefined properties.
294
	 *
295
	 * @since 2.4
296
	 *
297
	 * @return array
298
	 */
299
	public function getPropertyLabels() {
300
301
		$propertyLabels = $this->languageContents->getFromLanguageWithIndex(
302
			$this->languageCode,
303
			'propertyLabels'
304
		);
305
306
		$propertyLabels += $this->languageContents->getFromLanguageWithIndex(
307
			$this->languageContents->getCanonicalFallbackLanguageCode(),
308
			'propertyLabels'
309
		);
310
311
		return $propertyLabels;
312
	}
313
314
	/**
315
	 * Aliases for predefined properties, if any.
316
	 *
317
	 * @since 2.4
318
	 *
319
	 * @return array
320
	 */
321
	public function getCanonicalPropertyAliases() {
322
323
		$canonicalPropertyAliases = $this->languageContents->getFromLanguageWithIndex(
324
			$this->languageContents->getCanonicalFallbackLanguageCode(),
325
			'propertyAliases'
326
		);
327
328
		$canonicalPropertyAliases += $this->languageContents->getFromLanguageWithIndex(
329
			$this->languageCode,
330
			'propertyAliases'
331
		);
332
333
		return $canonicalPropertyAliases;
334
	}
335
336
	/**
337
	 * Aliases for predefined properties, if any.
338
	 *
339
	 * @since 2.4
340
	 *
341
	 * @return array
342
	 */
343
	public function getPropertyAliases() {
344
345
		$propertyAliases = $this->languageContents->getFromLanguageWithIndex(
346
			$this->languageCode,
347
			'propertyLabels'
348
		);
349
350
		$propertyAliases += $this->languageContents->getFromLanguageWithIndex(
351
			$this->languageContents->getCanonicalFallbackLanguageCode(),
352
			'propertyLabels'
353
		);
354
355
		return $propertyAliases;
356
	}
357
358
	/**
359
	 * @deprecated use getPropertyIdByLabel
360
	 */
361
	protected function getPropertyId( $propertyLabel ) {
0 ignored issues
show
Unused Code introduced by
The parameter $propertyLabel is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
362
363
		$list += $this->languageContents->getFromLanguageWithIndex(
0 ignored issues
show
Bug introduced by
The variable $list does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
364
			$this->languageCode,
365
			'propertyAliases'
366
		);
367
368
		$list += $this->languageContents->getFromLanguageWithIndex(
369
			$this->languageContents->getCanonicalFallbackLanguageCode(),
370
			'propertyAliases'
371
		);
372
373
		return $list;
374
	}
375
376
	/**
377
	 * Function receives property name (for example, `Modificatino date') and
378
	 * returns a property id (for example, `_MDAT'). Property name may be
379
	 * localized one. If property name is not recognized, a null value returned.
380
	 *
381
	 * @since 2.4
382
	 *
383
	 * @return string|null
384
	 */
385
	public function getPropertyIdByLabel( $propertyLabel ) {
386
387
		$languageCode = $this->languageCode;
388
389
		if ( !isset( $this->propertyIdByLabelMap[$languageCode] ) || $this->propertyIdByLabelMap[$languageCode] === array() ) {
390
			foreach ( $this->getPropertyLabels() as $id => $label ) {
391
				$this->propertyIdByLabelMap[$languageCode][$label] = $id;
392
			}
393
		}
394
395
		if ( isset( $this->propertyIdByLabelMap[$languageCode][$propertyLabel] ) ) {
396
			return $this->propertyIdByLabelMap[$languageCode][$propertyLabel];
397
		};
398
399
		$propertyAliases = $this->getPropertyAliases();
400
401
		if ( isset( $propertyAliases[$propertyLabel] ) ) {
402
			return $propertyAliases[$propertyLabel];
403
		}
404
405
		return null;
406
	}
407
408
	/**
409
	 * Function that returns the preferred date formats
410
	 *
411
	 * Preferred interpretations for dates with 1, 2, and 3 components. There
412
	 * is an array for each case, and the constants define the obvious order
413
	 * (e.g. SMW_YDM means "first Year, then Day, then Month). Unlisted
414
	 * combinations will not be accepted at all.
415
	 *
416
	 * @since 2.4
417
	 *
418
	 * @return array
419
	 */
420
	public function getDateFormats() {
421
422
		$languageCode = $this->languageCode;
423
424
		if ( !isset( $this->dateFormatsMap[$languageCode] ) || $this->dateFormatsMap[$languageCode] === array() ) {
425
			$this->dateFormatsMap[$languageCode] = $this->getDateFormatsByLanguageCode( $languageCode );
426
		}
427
428
		return $this->dateFormatsMap[$languageCode];
429
	}
430
431
	/**
432
	 * @since 2.4
433
	 *
434
	 * @param integer|null $precision
435
	 *
436
	 * @return string
437
	 */
438
	public function getPreferredDateFormatByPrecision( $precision = null ) {
439
440
		$dateOutputFormats = $this->languageContents->getFromLanguageWithIndex(
441
			$this->languageCode,
442
			'dateFormatsByPrecision'
443
		);
444
445
		foreach ( $dateOutputFormats as $key => $format ) {
0 ignored issues
show
Bug introduced by
The expression $dateOutputFormats of type array|string|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
446
			if ( @constant( $key ) === $precision ) {
447
				return $format;
448
			}
449
		}
450
451
		// Fallback
452
		return 'd F Y H:i:s';
453
	}
454
455
	/**
456
	 * @deprecated use findMonthNumberByLabel
457
	 */
458
	public function findMonth( $label ) {
459
		return $this->findMonthNumberByLabel( $label );
460
	}
461
462
	/**
463
	 * Function looks up a month and returns the corresponding number.
464
	 *
465
	 * @since 2.4
466
	 *
467
	 * @param string $label
468
	 *
469
	 * @return false|integer
470
	 */
471
	public function findMonthNumberByLabel( $label ) {
472
473
		$languageCode = $this->languageCode;
474
475
		if ( !isset( $this->months[$languageCode] ) || $this->months[$languageCode] === array() ) {
476
			$this->months[$languageCode] = $this->languageContents->getFromLanguageWithIndex( $languageCode, 'months' );
0 ignored issues
show
Bug introduced by
The property months does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
477
		}
478
479
		foreach ( $this->months[$languageCode] as $key => $value ) {
480
			if ( strcasecmp( $value[0], $label ) == 0 || strcasecmp( $value[1], $label ) == 0 ) {
481
				return $key + 1; // array starts with 0
482
			}
483
		}
484
485
		return false;
486
	}
487
488
	/**
489
	 * @deprecated use getMonthLabelByNumber
490
	 */
491
	public function getMonthLabel( $number ) {
492
		return $this->getMonthLabelByNumber( $number );
493
	}
494
495
	/**
496
	 * Return the name of the month with the given number.
497
	 *
498
	 * @since 2.4
499
	 *
500
	 * @param integer $number
501
	 *
502
	 * @return array
503
	 */
504
	public function getMonthLabelByNumber( $number ) {
505
506
		$languageCode = $this->languageCode;
507
		$number = (int)( $number - 1 ); // array starts with 0
508
509
		if ( !isset( $this->months[$languageCode] ) || $this->months[$languageCode] === array() ) {
510
			$this->months[$languageCode] = $this->languageContents->getFromLanguageWithIndex( $languageCode, 'months' );
511
		}
512
513
		if ( ( ( $number >= 0 ) && ( $number <= 11 ) ) && isset( $this->months[$languageCode][$number]) ) {
514
			return $this->months[$languageCode][$number][0]; // Long name
515
		}
516
517
		return '';
0 ignored issues
show
Bug Best Practice introduced by
The return type of return ''; (string) is incompatible with the return type documented by SMW\ExtraneousLanguage\E...::getMonthLabelByNumber of type array.

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...
518
	}
519
520
	private function getDateFormatsByLanguageCode( $languageCode ) {
521
522
		$dateformats = array();
523
524
		foreach ( $this->languageContents->getFromLanguageWithIndex( $languageCode, 'dateFormats' ) as $row ) {
0 ignored issues
show
Bug introduced by
The expression $this->languageContents-...ageCode, 'dateFormats') of type array|string|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
525
			$internalNumberFormat = array();
526
527
			foreach ( $row as $value ) {
528
				$internalNumberFormat[] = constant( $value );
529
			}
530
531
			$dateformats[] = $internalNumberFormat;
532
		}
533
534
		return $dateformats;
535
	}
536
537
}
538