Completed
Push — master ( 3abc67...80e892 )
by mw
207:38 queued 172:37
created

includes/dataitems/SMW_DI_Property.php (1 issue)

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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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;
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
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 ) {
507
			return null;
508
		}
509
	}
510
511
}
512