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