Completed
Push — master ( c18f14...017364 )
by mw
292:38 queued 257:37
created

includes/dataitems/SMW_DI_Property.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SMW;
4
5
use InvalidArgumentException;
6
use RuntimeException;
7
use SMWDataItem;
8
use SMWDIUri;
9
use SMW\Exception\PropertyLabelNotResolvedException;
10
use SMW\Exception\PropertyDataTypeLookupExeption;
11
use SMW\Exception\PredefinedPropertyLabelMismatchException;
12
13
/**
14
 * This class implements Property data items.
15
 *
16
 * @note PropertyRegistry class manages global registrations of
17
 * predefined (built-in) properties, and maintains an association of
18
 * property IDs, localized labels, and aliases.
19
 *
20
 * @since 1.6
21
 *
22
 * @author Markus Krötzsch
23
 * @author Jeroen De Dauw
24
 * @author mwjames
25
 */
26
class DIProperty extends SMWDataItem {
27
28
	/**
29
	 * @see PropertyRegistry::registerPredefinedProperties
30
	 */
31
	const TYPE_SUBOBJECT  = '_SOBJ';
32
	const TYPE_ERROR      = '_ERRP';
33
	const TYPE_CATEGORY = '_INST';
34
	const TYPE_SUBCATEGORY = '_SUBC';
35
	const TYPE_SORTKEY = '_SKEY';
36
	const TYPE_MODIFICATION_DATE = '_MDAT';
37
	const TYPE_CREATION_DATE = '_CDAT';
38
	const TYPE_LAST_EDITOR = '_LEDT';
39
	const TYPE_NEW_PAGE = '_NEWP';
40
	const TYPE_HAS_TYPE = '_TYPE';
41
	const TYPE_CONVERSION = '_CONV';
42
	const TYPE_ASKQUERY = '_ASK';
43
	const TYPE_MEDIA = '_MEDIA';
44
	const TYPE_MIME = '_MIME';
45
	const TYPE_DISPLAYTITLE = '_DTITLE';
46
47
	/**
48
	 * Either an internal SMW property key (starting with "_") or the DB
49
	 * key of a property page in the wiki.
50
	 * @var string
51
	 */
52
	private $m_key;
53
54
	/**
55
	 * Whether to take the inverse of this property or not.
56
	 * @var boolean
57
	 */
58
	private $m_inverse;
59
60
	/**
61
	 * Cache for property type ID.
62
	 * @var string
63
	 */
64
	private $m_proptypeid;
65
66
	/**
67
	 * Interwiki prefix for when a property represents a non-local entity
68
	 *
69
	 * @var string
70
	 */
71
	private $interwiki = '';
72
73
	/**
74
	 * Initialise a property. This constructor checks that keys of
75
	 * predefined properties do really exist (in the current configuration
76
	 * of the wiki). No check is performed to see if a user label is in
77
	 * fact the label or alias of a predefined property. If this should be
78
	 * done, the function self::newFromUserLabel() can be used.
79
	 *
80
	 * @param $key string key for the property (internal SMW key or wikipage DB key)
81
	 * @param $inverse boolean states if the inverse of the property is constructed
82
	 */
83 336
	public function __construct( $key, $inverse = false ) {
84
85 336
		if ( ( $key === '' ) || ( $key{0} == '-' ) ) {
86 3
			throw new PropertyLabelNotResolvedException( "Illegal property key \"$key\"." );
87
		}
88
89 336
		if ( $key{0} == '_' ) {
90 324
			if ( !PropertyRegistry::getInstance()->isKnownPropertyId( $key ) ) {
91
				throw new PredefinedPropertyLabelMismatchException( "There is no predefined property with \"$key\"." );
92
			}
93
		}
94
95 336
		$this->m_key = $key;
96 336
		$this->m_inverse = $inverse;
97 336
	}
98
99
	/**
100
	 * @return integer
101
	 */
102 241
	public function getDIType() {
103 241
		return SMWDataItem::TYPE_PROPERTY;
104
	}
105
106
	/**
107
	 * @return string
108
	 */
109 325
	public function getKey() {
110 325
		return $this->m_key;
111
	}
112
113
	/**
114
	 * @return boolean
115
	 */
116 303
	public function isInverse() {
117 303
		return $this->m_inverse;
118
	}
119
120
	/**
121
	 * @return string
122
	 */
123 4
	public function getSortKey() {
124 4
		return $this->m_key;
125
	}
126
127
	/**
128
	 * Specifies whether values of this property should be shown in the
129
	 * Factbox. A property may wish to prevent this if either
130
	 * (1) its information is really dull, e.g. being a mere copy of
131
	 * information that is obvious from other things that are shown, or
132
	 * (2) the property is set in a hook after parsing, so that it is not
133
	 * reliably available when Factboxes are displayed. If a property is
134
	 * internal so it should never be observed by users, then it is better
135
	 * to just not associate any translated label with it, so it never
136
	 * appears anywhere.
137
	 *
138
	 * Examples of properties that are not shown include Modification date
139
	 * (not available in time), and Has improper value for (errors are
140
	 * shown directly on the page anyway).
141
	 *
142
	 * @return boolean
143
	 */
144 257
	public function isShown() {
145
146 257
		if ( $this->isUserDefined() ) {
147 1
			return true;
148
		}
149
150 257
		return PropertyRegistry::getInstance()->isVisibleToUser( $this->m_key );
151
	}
152
153
	/**
154
	 * Return true if this is a usual wiki property that is defined by a
155
	 * wiki page, and not a property that is pre-defined in the wiki.
156
	 *
157
	 * @return boolean
158
	 */
159 314
	public function isUserDefined() {
160 314
		return $this->m_key{0} != '_';
161
	}
162
163
	/**
164
	 * Whether a user can freely use this property for value declarations or
165
	 * not.
166
	 *
167
	 * @note A user defined property is generally assumed to be unrestricted
168
	 * for usage
169
	 *
170
	 * @since 2.2
171
	 *
172
	 * @return boolean
173
	 */
174 207
	public function isUnrestricted() {
175
176 207
		if ( $this->isUserDefined() ) {
177 198
			return true;
178
		}
179
180 173
		return PropertyRegistry::getInstance()->isUnrestrictedForAnnotationUse( $this->m_key );
181
	}
182
183
	/**
184
	 * Find a user-readable label for this property, or return '' if it is
185
	 * a predefined property that has no label. For inverse properties, the
186
	 * label starts with a "-".
187
	 *
188
	 * @return string
189
	 */
190 277
	public function getLabel() {
191 277
		$prefix = $this->m_inverse ? '-' : '';
192
193 277
		if ( $this->isUserDefined() ) {
194 233
			return $prefix . str_replace( '_', ' ', $this->m_key );
195
		}
196
197 274
		return $prefix . PropertyRegistry::getInstance()->findPropertyLabelById( $this->m_key );
198
	}
199
200
	/**
201
	 * @since 2.4
202
	 *
203
	 * @return string
204
	 */
205 147
	public function getCanonicalLabel() {
206 147
		$prefix = $this->m_inverse ? '-' : '';
207
208 147
		if ( $this->isUserDefined() ) {
209 110
			return $prefix . str_replace( '_', ' ', $this->m_key );
210
		}
211
212 54
		return $prefix . PropertyRegistry::getInstance()->findCanonicalPropertyLabelById( $this->m_key );
213
	}
214
215
	/**
216
	 * Borrowing the skos:prefLabel definition where a preferred label is expected
217
	 * to have only one label per given language (skos:altLabel can have many
218
	 * alternative labels)
219
	 *
220
	 * An empty string signals that no preferred label is available in the current
221
	 * user language.
222
	 *
223
	 * @since 2.5
224
	 *
225
	 * @param string $languageCode
226
	 *
227
	 * @return string
228
	 */
229 243
	public function getPreferredLabel( $languageCode = '' ) {
230
231 243
		$label = PropertyRegistry::getInstance()->findPreferredPropertyLabelById( $this->m_key, $languageCode );
232
233 243
		if ( $label !== '' ) {
234 5
			return ( $this->m_inverse ? '-' : '' ) . $label;
235
		}
236
237 243
		return '';
238
	}
239
240
	/**
241
	 * @since 2.4
242
	 *
243
	 * @param string $interwiki
244
	 */
245 3
	public function setInterwiki( $interwiki ) {
246 3
		$this->interwiki = $interwiki;
247 3
	}
248
249
	/**
250
	 * Get an object of type DIWikiPage that represents the page which
251
	 * relates to this property, or null if no such page exists. The latter
252
	 * can happen for special properties without user-readable label.
253
	 *
254
	 * It is possible to construct subobjects of the property's wikipage by
255
	 * providing an optional subobject name.
256
	 *
257
	 * @param string $subobjectName
258
	 * @return DIWikiPage|null
259
	 */
260 219
	public function getDiWikiPage( $subobjectName = '' ) {
261
262 219
		if ( $this->isUserDefined() ) {
263 218
			$dbkey = $this->m_key;
264
		} else {
265 21
			$dbkey = $this->getLabel();
266
		}
267
268 219
		return $this->newDIWikiPage( $dbkey, $subobjectName );
269
	}
270
271
	/**
272
	 * @since 2.4
273
	 *
274
	 * @param string $subobjectName
275
	 *
276
	 * @return DIWikiPage|null
277
	 */
278 235
	public function getCanonicalDiWikiPage( $subobjectName = '' ) {
279
280 235
		if ( $this->isUserDefined() ) {
281 211
			$dbkey = $this->m_key;
282 188
		} elseif ( $this->m_key === $this->findPropertyTypeID() ) {
283
			// If _dat as property [[Date::...]] refers directly to its _dat type
284
			// then use the en-label as canonical representation
285 12
			$dbkey = PropertyRegistry::getInstance()->findPropertyLabelByLanguageCode( $this->m_key, 'en' );
286
		} else {
287 183
			$dbkey = PropertyRegistry::getInstance()->findCanonicalPropertyLabelById( $this->m_key );
288
		}
289
290 235
		return $this->newDIWikiPage( $dbkey, $subobjectName );
291
	}
292
293
	/**
294
	 * @since 2.4
295
	 *
296
	 * @return DIProperty
297
	 */
298 224
	public function getRedirectTarget() {
299
300 224
		if ( $this->m_inverse ) {
301 11
			return $this;
302
		}
303
304 223
		return ApplicationFactory::getInstance()->getStore()->getRedirectTarget( $this );
305
	}
306
307
	/**
308
	 * @since  2.0
309
	 *
310
	 * @return self
311
	 * @throws PropertyDataTypeLookupExeption
312
	 * @throws InvalidArgumentException
313
	 */
314 19
	public function setPropertyTypeId( $propertyTypeId ) {
315
316 19
		if ( !DataTypeRegistry::getInstance()->isKnownTypeId( $propertyTypeId ) ) {
317 1
			throw new PropertyDataTypeLookupExeption( "{$propertyTypeId} is an unknown type id" );
318
		}
319
320 18
		if ( $this->isUserDefined() && $this->m_proptypeid === null ) {
321 16
			$this->m_proptypeid = $propertyTypeId;
322 16
			return $this;
323
		}
324
325 2
		if ( !$this->isUserDefined() && $propertyTypeId === self::getPredefinedPropertyTypeId( $this->m_key ) ) {
326 1
			$this->m_proptypeid = $propertyTypeId;
327 1
			return $this;
328
		}
329
330 1
		throw new RuntimeException( 'Property type can not be altered for a predefined object' );
331
	}
332
333
	/**
334
	 * Find the property's type ID, either by looking up its predefined ID
335
	 * (if any) or by retrieving the relevant information from the store.
336
	 * If no type is stored for a user defined property, the global default
337
	 * type will be used.
338
	 *
339
	 * @return string type ID
340
	 */
341 291
	public function findPropertyTypeID() {
342 291
		global $smwgPDefaultType;
343
344 291
		if ( !isset( $this->m_proptypeid ) ) {
345 270
			if ( $this->isUserDefined() ) { // normal property
346 223
				$diWikiPage = new DIWikiPage( $this->getKey(), SMW_NS_PROPERTY, $this->interwiki );
347 223
				$typearray = ApplicationFactory::getInstance()->getStore()->getPropertyValues( $diWikiPage, new self( '_TYPE' ) );
348
349 223
				if ( count( $typearray ) >= 1 ) { // some types given, pick one (hopefully unique)
350 165
					$typeDataItem = reset( $typearray );
351
352 165
					if ( $typeDataItem instanceof SMWDIUri ) {
353 165
						$this->m_proptypeid = $typeDataItem->getFragment();
354
					} else {
355 165
						$this->m_proptypeid = $smwgPDefaultType;
356
						// This is important. If a page has an invalid assignment to "has type", no
357
						// value will be stored, so the elseif case below occurs. But if the value
358
						// is retrieved within the same run, then the error value for "has type" is
359
						// cached and thus this case occurs. This is why it is important to tolerate
360
						// this case -- it is not necessarily a DB error.
361
					}
362 223
				} elseif ( count( $typearray ) == 0 ) { // no type given
363 223
					$this->m_proptypeid = $smwgPDefaultType;
364
				}
365
			} else { // pre-defined property
366 258
				$this->m_proptypeid = PropertyRegistry::getInstance()->getPredefinedPropertyTypeId( $this->m_key );
367
			}
368
		}
369
370 291
		return $this->m_proptypeid;
371
	}
372
373
374 260
	public function getSerialization() {
375 260
		return ( $this->m_inverse ? '-' : '' ) . $this->m_key;
376
	}
377
378
	/**
379
	 * Create a data item from the provided serialization string and type
380
	 * ID.
381
	 *
382
	 * @param string $serialization
383
	 *
384
	 * @return DIProperty
385
	 */
386 14
	public static function doUnserialize( $serialization ) {
387 14
		$inverse = false;
388
389 14
		if ( $serialization{0} == '-' ) {
390
			$serialization = substr( $serialization, 1 );
391
			$inverse = true;
392
		}
393
394 14
		return new self( $serialization, $inverse );
395
	}
396
397
	/**
398
	 * @param SMWDataItem $di
399
	 *
400
	 * @return boolean
401
	 */
402 17
	public function equals( SMWDataItem $di ) {
403 17
		if ( $di->getDIType() !== SMWDataItem::TYPE_PROPERTY ) {
404 3
			return false;
405
		}
406
407 14
		return $di->getKey() === $this->m_key;
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getKey() does only exist in the following sub-classes of SMWDataItem: SMWDIProperty, SMW\DIProperty. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
408
	}
409
410
	/**
411
	 * Construct a property from a user-supplied label. The main difference
412
	 * to the normal constructor of DIProperty is that it is checked
413
	 * whether the label refers to a known predefined property.
414
	 * Note that this function only gives access to the registry data that
415
	 * DIProperty stores, but does not do further parsing of user input.
416
	 *
417
	 * To process wiki input, SMWPropertyValue should be used.
418
	 *
419
	 * @param $label string label for the property
420
	 * @param $inverse boolean states if the inverse of the property is constructed
421
	 *
422
	 * @return DIProperty object
423
	 */
424 254
	public static function newFromUserLabel( $label, $inverse = false, $languageCode = false ) {
425
426 254
		if ( $label !== '' && $label{0} == '-' ) {
427 3
			$label = substr( $label, 1 );
428 3
			$inverse = true;
429
		}
430
431
		// Special handling for when the user value contains a @LCODE marker
432 254
		if ( ( $annotatedLanguageCode = Localizer::getAnnotatedLanguageCodeFrom( $label ) ) !== false ) {
433 5
			$languageCode = $annotatedLanguageCode;
434
		}
435
436 254
		$id = false;
437 254
		$label = str_replace( '_', ' ', $label );
438
439 254
		if ( $languageCode ) {
440 236
			$id = PropertyRegistry::getInstance()->findPropertyIdFromLabelByLanguageCode(
441
				$label,
442
				$languageCode
0 ignored issues
show
It seems like $languageCode defined by parameter $languageCode on line 424 can also be of type boolean; however, SMW\PropertyRegistry::fi...omLabelByLanguageCode() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
443
			);
444
		}
445
446 254
		if ( $id !== false ) {
447 190
			return new self( $id, $inverse );
448
		}
449
450 238
		$id = PropertyRegistry::getInstance()->findPropertyIdByLabel(
451
			$label
452
		);
453
454 238
		if ( $id === false ) {
455 228
			return new self( str_replace( ' ', '_', $label ), $inverse );
456
		}
457
458 60
		return new self( $id, $inverse );
459
	}
460
461
	/**
462
	 * @deprecated since 2.1, use PropertyRegistry::findPropertyIdByLabel
463
	 */
464
	public static function findPropertyID( $label, $useAlias = true ) {
465
		return PropertyRegistry::getInstance()->findPropertyIdByLabel( $label, $useAlias );
466
	}
467
468
	/**
469
	 * @deprecated since 2.1, use PropertyRegistry::getPredefinedPropertyTypeId
470
	 */
471
	public static function getPredefinedPropertyTypeId( $key ) {
472
		return PropertyRegistry::getInstance()->getPredefinedPropertyTypeId( $key );
473
	}
474
475
	/**
476
	 * @deprecated since 2.1, use PropertyRegistry::findPropertyLabelById
477
	 */
478
	static public function findPropertyLabel( $id ) {
479
		return PropertyRegistry::getInstance()->findPropertyLabel( $id );
480
	}
481
482
	/**
483
	 * @deprecated since 2.1, use PropertyRegistry::registerProperty
484
	 */
485
	static public function registerProperty( $id, $typeid, $label = false, $show = false ) {
486
		PropertyRegistry::getInstance()->registerProperty( $id, $typeid, $label, $show);
487
	}
488
489
	/**
490
	 * @deprecated since 2.1, use PropertyRegistry::registerPropertyAlias
491
	 */
492
	static public function registerPropertyAlias( $id, $label ) {
493
		PropertyRegistry::getInstance()->registerPropertyAlias( $id, $label );
494
	}
495
496 255
	private function newDIWikiPage( $dbkey, $subobjectName ) {
497
498
		// If an inverse marker is present just omit the marker so a normal
499
		// property page link can be produced independent of its directionality
500 255
		if ( $dbkey !== '' && $dbkey{0} == '-'  ) {
501
			$dbkey = substr( $dbkey, 1 );
502
		}
503
504
		try {
505 255
			return new DIWikiPage( str_replace( ' ', '_', $dbkey ), SMW_NS_PROPERTY, $this->interwiki, $subobjectName );
506
		} catch ( DataItemException $e ) {
0 ignored issues
show
The class SMW\DataItemException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
507
			return null;
508
		}
509
	}
510
511
}
512