Completed
Push — master ( 14d2bd...06e609 )
by mw
81:37 queued 59:24
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
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 269
	public function __construct( $key, $inverse = false ) {
81 269
		if ( ( $key === '' ) || ( $key{0} == '-' ) ) {
82
			throw new InvalidPropertyException( "Illegal property key \"$key\"." );
83
		}
84
85 269
		if ( $key{0} == '_' ) {
86 257
			if ( !PropertyRegistry::getInstance()->isKnownPropertyId( $key ) ) {
87 1
				throw new InvalidPredefinedPropertyException( "There is no predefined property with \"$key\"." );
88
			}
89
		}
90
91 268
		$this->m_key = $key;
92 268
		$this->m_inverse = $inverse;
93 268
	}
94
95
	/**
96
	 * @return integer
97
	 */
98 194
	public function getDIType() {
99 194
		return SMWDataItem::TYPE_PROPERTY;
100
	}
101
102
	/**
103
	 * @return string
104
	 */
105 257
	public function getKey() {
106 257
		return $this->m_key;
107
	}
108
109
	/**
110
	 * @return boolean
111
	 */
112 252
	public function isInverse() {
113 252
		return $this->m_inverse;
114
	}
115
116
	/**
117
	 * @return string
118
	 */
119 1
	public function getSortKey() {
120 1
		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 209
	public function isShown() {
141
142 209
		if ( $this->isUserDefined() ) {
143 1
			return true;
144
		}
145
146 209
		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 252
	public function isUserDefined() {
156 252
		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 162
	public function isUnrestrictedForUse() {
171
172 162
		if ( $this->isUserDefined() ) {
173 155
			return true;
174
		}
175
176 128
		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 234
	public function getLabel() {
187 234
		$prefix = $this->m_inverse ? '-' : '';
188
189 234
		if ( $this->isUserDefined() ) {
190 187
			return $prefix . str_replace( '_', ' ', $this->m_key );
191
		}
192
193 232
		return $prefix . PropertyRegistry::getInstance()->findPropertyLabelById( $this->m_key );
194
	}
195
196
	/**
197
	 * @since 2.4
198
	 *
199
	 * @return string
200
	 */
201 60
	public function getCanonicalLabel() {
202 60
		$prefix = $this->m_inverse ? '-' : '';
203
204 60
		if ( $this->isUserDefined() ) {
205 54
			return $prefix . str_replace( '_', ' ', $this->m_key );
206
		}
207
208 13
		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 191
	public function getDiWikiPage( $subobjectName = '' ) {
232
233 191
		if ( $this->isUserDefined() ) {
234 181
			$dbkey = $this->m_key;
235
		} else {
236 129
			$dbkey = $this->getLabel();
237
		}
238
239 191
		return $this->newDIWikiPage( $dbkey, $subobjectName );
240
	}
241
242
	/**
243
	 * @since 2.4
244
	 *
245
	 * @param string $subobjectName
246
	 *
247
	 * @return DIWikiPage|null
248
	 */
249 106
	public function getCanonicalDiWikiPage( $subobjectName = '' ) {
250
251 106
		if ( $this->isUserDefined() ) {
252 92
			$dbkey = $this->m_key;
253
		} else {
254 33
			$dbkey = PropertyRegistry::getInstance()->findCanonicalPropertyLabelById( $this->m_key );
255
		}
256
257 106
		return $this->newDIWikiPage( $dbkey, $subobjectName );
258
	}
259
260
	/**
261
	 * @since 2.4
262
	 *
263
	 * @return DIProperty
264
	 */
265 177
	public function getRedirectTarget() {
266
267 177
		if ( $this->m_inverse ) {
268 8
			return $this;
269
		}
270
271 176
		return ApplicationFactory::getInstance()->getStore()->getRedirectTarget( $this );
272
	}
273
274
	/**
275
	 * @since  2.0
276
	 *
277
	 * @return self
278
	 * @throws RuntimeException
279
	 * @throws InvalidArgumentException
280
	 */
281 19
	public function setPropertyTypeId( $propertyTypeId ) {
282
283 19
		if ( !DataTypeRegistry::getInstance()->isKnownTypeId( $propertyTypeId ) ) {
284 1
			throw new RuntimeException( "{$propertyTypeId} is an unknown type id" );
285
		}
286
287 18
		if ( $this->isUserDefined() && $this->m_proptypeid === null ) {
288 16
			$this->m_proptypeid = $propertyTypeId;
289 16
			return $this;
290
		}
291
292 2
		if ( !$this->isUserDefined() && $propertyTypeId === self::getPredefinedPropertyTypeId( $this->m_key ) ) {
293 1
			$this->m_proptypeid = $propertyTypeId;
294 1
			return $this;
295
		}
296
297 1
		throw new InvalidArgumentException( 'Property type can not be altered for a predefined object' );
298
	}
299
300
	/**
301
	 * Find the property's type ID, either by looking up its predefined ID
302
	 * (if any) or by retrieving the relevant information from the store.
303
	 * If no type is stored for a user defined property, the global default
304
	 * type will be used.
305
	 *
306
	 * @return string type ID
307
	 */
308 232
	public function findPropertyTypeID() {
309 232
		global $smwgPDefaultType;
310
311 232
		if ( !isset( $this->m_proptypeid ) ) {
312 211
			if ( $this->isUserDefined() ) { // normal property
313 176
				$diWikiPage = new DIWikiPage( $this->getKey(), SMW_NS_PROPERTY, $this->interwiki );
314 176
				$typearray = ApplicationFactory::getInstance()->getStore()->getPropertyValues( $diWikiPage, new self( '_TYPE' ) );
315
316 176
				if ( count( $typearray ) >= 1 ) { // some types given, pick one (hopefully unique)
317 125
					$typeDataItem = reset( $typearray );
318
319 125
					if ( $typeDataItem instanceof SMWDIUri ) {
320 125
						$this->m_proptypeid = $typeDataItem->getFragment();
321
					} else {
322 125
						$this->m_proptypeid = $smwgPDefaultType;
323
						// This is important. If a page has an invalid assignment to "has type", no
324
						// value will be stored, so the elseif case below occurs. But if the value
325
						// is retrieved within the same run, then the error value for "has type" is
326
						// cached and thus this case occurs. This is why it is important to tolerate
327
						// this case -- it is not necessarily a DB error.
328
					}
329 172
				} elseif ( count( $typearray ) == 0 ) { // no type given
330 176
					$this->m_proptypeid = $smwgPDefaultType;
331
				}
332
			} else { // pre-defined property
333 201
				$this->m_proptypeid = PropertyRegistry::getInstance()->getPredefinedPropertyTypeId( $this->m_key );
334
			}
335
		}
336
337 232
		return $this->m_proptypeid;
338
	}
339
340
341 189
	public function getSerialization() {
342 189
		return ( $this->m_inverse ? '-' : '' ) . $this->m_key;
343
	}
344
345
	/**
346
	 * Create a data item from the provided serialization string and type
347
	 * ID.
348
	 *
349
	 * @param string $serialization
350
	 *
351
	 * @return DIProperty
352
	 */
353 14
	public static function doUnserialize( $serialization ) {
354 14
		$inverse = false;
355
356 14
		if ( $serialization{0} == '-' ) {
357
			$serialization = substr( $serialization, 1 );
358
			$inverse = true;
359
		}
360
361 14
		return new self( $serialization, $inverse );
362
	}
363
364
	/**
365
	 * @param SMWDataItem $di
366
	 *
367
	 * @return boolean
368
	 */
369 17
	public function equals( SMWDataItem $di ) {
370 17
		if ( $di->getDIType() !== SMWDataItem::TYPE_PROPERTY ) {
371 3
			return false;
372
		}
373
374 14
		return $di->getKey() === $this->m_key;
375
	}
376
377
	/**
378
	 * Construct a property from a user-supplied label. The main difference
379
	 * to the normal constructor of DIProperty is that it is checked
380
	 * whether the label refers to a known predefined property.
381
	 * Note that this function only gives access to the registry data that
382
	 * DIProperty stores, but does not do further parsing of user input.
383
	 *
384
	 * To process wiki input, SMWPropertyValue should be used.
385
	 *
386
	 * @param $label string label for the property
387
	 * @param $inverse boolean states if the inverse of the property is constructed
388
	 *
389
	 * @return DIProperty object
390
	 */
391 188
	public static function newFromUserLabel( $label, $inverse = false ) {
392
393 188
		if ( $label !== '' && $label{0} == '-' ) {
394 2
			$label = substr( $label, 1 );
395 2
			$inverse = true;
396
		}
397
398 188
		$id = false;
399
400
		// Special handling for when the user value contains a @LCODE marker
401 188
		if ( ( $langCode = Localizer::getLanguageCodeFrom( $label ) ) !== false ) {
402 3
			$id = PropertyRegistry::getInstance()->findPropertyIdByLanguageCode(
403
				$label,
404
				$langCode
405
			);
406
		}
407
408 188
		if ( $id !== false ) {
409 2
			return new self( $id, $inverse );
410
		}
411
412 188
		$id = PropertyRegistry::getInstance()->findPropertyIdByLabel(
413 188
			str_replace( '_', ' ', $label )
414
		);
415
416 188
		if ( $id === false ) {
417 178
			return new self( str_replace( ' ', '_', $label ), $inverse );
418
		}
419
420 140
		return new self( $id, $inverse );
421
	}
422
423
	/**
424
	 * @deprecated since 2.1, use PropertyRegistry::findPropertyIdByLabel
425
	 */
426
	public static function findPropertyID( $label, $useAlias = true ) {
427
		return PropertyRegistry::getInstance()->findPropertyIdByLabel( $label, $useAlias );
428
	}
429
430
	/**
431
	 * @deprecated since 2.1, use PropertyRegistry::getPredefinedPropertyTypeId
432
	 */
433
	public static function getPredefinedPropertyTypeId( $key ) {
434
		return PropertyRegistry::getInstance()->getPredefinedPropertyTypeId( $key );
435
	}
436
437
	/**
438
	 * @deprecated since 2.1, use PropertyRegistry::findPropertyLabelById
439
	 */
440
	static public function findPropertyLabel( $id ) {
0 ignored issues
show
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
441
		return PropertyRegistry::getInstance()->findPropertyLabel( $id );
442
	}
443
444
	/**
445
	 * @deprecated since 2.1, use PropertyRegistry::registerProperty
446
	 */
447
	static public function registerProperty( $id, $typeid, $label = false, $show = false ) {
0 ignored issues
show
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
448
		PropertyRegistry::getInstance()->registerProperty( $id, $typeid, $label, $show);
449
	}
450
451
	/**
452
	 * @deprecated since 2.1, use PropertyRegistry::registerPropertyAlias
453
	 */
454
	static public function registerPropertyAlias( $id, $label ) {
0 ignored issues
show
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
455
		PropertyRegistry::getInstance()->registerPropertyAlias( $id, $label );
456
	}
457
458 199
	private function newDIWikiPage( $dbkey, $subobjectName ) {
459
460
		// If an inverse marker is present just omit the marker so a normal
461
		// property page link can be produced independent of its directionality
462 199
		if ( $dbkey !== '' && $dbkey{0} == '-'  ) {
463 3
			$dbkey = substr( $dbkey, 1 );
464
		}
465
466
		try {
467 199
			return new DIWikiPage( str_replace( ' ', '_', $dbkey ), SMW_NS_PROPERTY, $this->interwiki, $subobjectName );
468
		} catch ( DataItemException $e ) {
469
			return null;
470
		}
471
	}
472
473
}
474