summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/includes/dataitems/SMW_DI_Property.php
blob: 06e076dfc0ee3f929713bef4f71c3d345b5a4119 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
<?php

namespace SMW;

use RuntimeException;
use SMW\Exception\DataTypeLookupException;
use SMW\Exception\PredefinedPropertyLabelMismatchException;
use SMW\Exception\PropertyLabelNotResolvedException;
use SMWDataItem;
use SMWDIUri;

/**
 * This class implements Property item
 *
 * @note PropertyRegistry class manages global registrations of predefined
 * (built-in) properties, and maintains an association of property IDs, localized
 * labels, and aliases.
 *
 * @since 1.6
 *
 * @author Markus Krötzsch
 * @author Jeroen De Dauw
 * @author mwjames
 */
class DIProperty extends SMWDataItem {

	/**
	 * @see PropertyRegistry::registerPredefinedProperties
	 */
	const TYPE_SUBOBJECT  = '_SOBJ';
	const TYPE_ERROR      = '_ERRP';
	const TYPE_CATEGORY = '_INST';
	const TYPE_SUBCATEGORY = '_SUBC';
	const TYPE_SORTKEY = '_SKEY';
	const TYPE_MODIFICATION_DATE = '_MDAT';
	const TYPE_CREATION_DATE = '_CDAT';
	const TYPE_LAST_EDITOR = '_LEDT';
	const TYPE_NEW_PAGE = '_NEWP';
	const TYPE_HAS_TYPE = '_TYPE';
	const TYPE_CONVERSION = '_CONV';
	const TYPE_ASKQUERY = '_ASK';
	const TYPE_MEDIA = '_MEDIA';
	const TYPE_MIME = '_MIME';
	const TYPE_DISPLAYTITLE = '_DTITLE';

	/**
	 * Change propagation
	 */
	const TYPE_CHANGE_PROP = '_CHGPRO';

	/**
	 * Either an internal SMW property key (starting with "_") or the DB
	 * key of a property page in the wiki.
	 * @var string
	 */
	private $m_key;

	/**
	 * Whether to take the inverse of this property or not.
	 * @var boolean
	 */
	private $m_inverse;

	/**
	 * @var string
	 */
	private $propertyValueType;

	/**
	 * Interwiki prefix for when a property represents a non-local entity
	 *
	 * @var string
	 */
	private $interwiki = '';

	/**
	 * Initialise a property. This constructor checks that keys of
	 * predefined properties do really exist (in the current configuration
	 * of the wiki). No check is performed to see if a user label is in
	 * fact the label or alias of a predefined property. If this should be
	 * done, the function self::newFromUserLabel() can be used.
	 *
	 * @param $key string key for the property (internal SMW key or wikipage DB key)
	 * @param $inverse boolean states if the inverse of the property is constructed
	 */
	public function __construct( $key, $inverse = false ) {

		if ( ( $key === '' ) || ( $key{0} == '-' ) ) {
			throw new PropertyLabelNotResolvedException( "Illegal property key \"$key\"." );
		}

		if ( $key{0} == '_' ) {
			if ( !PropertyRegistry::getInstance()->isRegistered( $key ) ) {
				throw new PredefinedPropertyLabelMismatchException( "There is no predefined property with \"$key\"." );
			}
		}

		$this->m_key = $key;
		$this->m_inverse = $inverse;
	}

	/**
	 * @return integer
	 */
	public function getDIType() {
		return SMWDataItem::TYPE_PROPERTY;
	}

	/**
	 * @return string
	 */
	public function getKey() {
		return $this->m_key;
	}

	/**
	 * @return boolean
	 */
	public function isInverse() {
		return $this->m_inverse;
	}

	/**
	 * @return string
	 */
	public function getSortKey() {
		return $this->m_key;
	}

	/**
	 * Specifies whether values of this property should be shown in the
	 * Factbox. A property may wish to prevent this if either
	 * (1) its information is really dull, e.g. being a mere copy of
	 * information that is obvious from other things that are shown, or
	 * (2) the property is set in a hook after parsing, so that it is not
	 * reliably available when Factboxes are displayed. If a property is
	 * internal so it should never be observed by users, then it is better
	 * to just not associate any translated label with it, so it never
	 * appears anywhere.
	 *
	 * Examples of properties that are not shown include Modification date
	 * (not available in time), and Has improper value for (errors are
	 * shown directly on the page anyway).
	 *
	 * @return boolean
	 */
	public function isShown() {

		if ( $this->isUserDefined() ) {
			return true;
		}

		return PropertyRegistry::getInstance()->isVisible( $this->m_key );
	}

	/**
	 * Return true if this is a usual wiki property that is defined by a
	 * wiki page, and not a property that is pre-defined in the wiki.
	 *
	 * @return boolean
	 */
	public function isUserDefined() {
		return $this->m_key{0} != '_';
	}

	/**
	 * Whether a user can freely use this property for value annotation or
	 * not.
	 *
	 * @since 3.0
	 *
	 * @return boolean
	 */
	public function isUserAnnotable() {

		// A user defined property is generally assumed to be unrestricted for
		// usage
		if ( $this->isUserDefined() ) {
			return true;
		}

		return PropertyRegistry::getInstance()->isAnnotable( $this->m_key );
	}

	/**
	 * Find a user-readable label for this property, or return '' if it is
	 * a predefined property that has no label. For inverse properties, the
	 * label starts with a "-".
	 *
	 * @return string
	 */
	public function getLabel() {
		$prefix = $this->m_inverse ? '-' : '';

		if ( $this->isUserDefined() ) {
			return $prefix . str_replace( '_', ' ', $this->m_key );
		}

		return $prefix . PropertyRegistry::getInstance()->findPropertyLabelById( $this->m_key );
	}

	/**
	 * @since 2.4
	 *
	 * @return string
	 */
	public function getCanonicalLabel() {
		$prefix = $this->m_inverse ? '-' : '';

		if ( $this->isUserDefined() ) {
			return $prefix . str_replace( '_', ' ', $this->m_key );
		}

		return $prefix . PropertyRegistry::getInstance()->findCanonicalPropertyLabelById( $this->m_key );
	}

	/**
	 * Borrowing the skos:prefLabel definition where a preferred label is expected
	 * to have only one label per given language (skos:altLabel can have many
	 * alternative labels)
	 *
	 * An empty string signals that no preferred label is available in the current
	 * user language.
	 *
	 * @since 2.5
	 *
	 * @param string $languageCode
	 *
	 * @return string
	 */
	public function getPreferredLabel( $languageCode = '' ) {

		$label = PropertyRegistry::getInstance()->findPreferredPropertyLabelFromIdByLanguageCode(
			$this->m_key,
			$languageCode
		);

		if ( $label !== '' ) {
			return ( $this->m_inverse ? '-' : '' ) . $label;
		}

		return '';
	}

	/**
	 * @since 2.4
	 *
	 * @param string $interwiki
	 */
	public function setInterwiki( $interwiki ) {
		$this->interwiki = $interwiki;
	}

	/**
	 * Get an object of type DIWikiPage that represents the page which
	 * relates to this property, or null if no such page exists. The latter
	 * can happen for special properties without user-readable label.
	 *
	 * It is possible to construct subobjects of the property's wikipage by
	 * providing an optional subobject name.
	 *
	 * @param string $subobjectName
	 * @return DIWikiPage|null
	 */
	public function getDiWikiPage( $subobjectName = '' ) {

		$dbkey = $this->m_key;

		if ( !$this->isUserDefined() ) {
			$dbkey = $this->getLabel();
		}

		return $this->newDIWikiPage( $dbkey, $subobjectName );
	}

	/**
	 * @since 2.4
	 *
	 * @param string $subobjectName
	 *
	 * @return DIWikiPage|null
	 */
	public function getCanonicalDiWikiPage( $subobjectName = '' ) {

		if ( $this->isUserDefined() ) {
			$dbkey = $this->m_key;
		} elseif ( $this->m_key === $this->findPropertyTypeID() ) {
			// If _dat as property [[Date::...]] refers directly to its _dat type
			// then use the en-label as canonical representation
			$dbkey = PropertyRegistry::getInstance()->findPropertyLabelFromIdByLanguageCode( $this->m_key, 'en' );
		} else {
			$dbkey = PropertyRegistry::getInstance()->findCanonicalPropertyLabelById( $this->m_key );
		}

		return $this->newDIWikiPage( $dbkey, $subobjectName );
	}

	/**
	 * @since 2.4
	 *
	 * @return DIProperty
	 */
	public function getRedirectTarget() {

		if ( $this->m_inverse ) {
			return $this;
		}

		return ApplicationFactory::getInstance()->getStore()->getRedirectTarget( $this );
	}

	/**
	 * @deprecated since 3.0, use DIProperty::setPropertyValueType
	 */
	public function setPropertyTypeId( $valueType ) {
		return $this->setPropertyValueType( $valueType );
	}

	/**
	 * @since 3.0
	 *
	 * @param string $valueType
	 *
	 * @return self
	 * @throws DataTypeLookupException
	 * @throws RuntimeException
	 */
	public function setPropertyValueType( $valueType ) {

		if ( !DataTypeRegistry::getInstance()->isRegistered( $valueType ) ) {
			throw new DataTypeLookupException( "{$valueType} is an unknown type id" );
		}

		if ( $this->isUserDefined() && $this->propertyValueType === null ) {
			$this->propertyValueType = $valueType;
			return $this;
		}

		if ( !$this->isUserDefined() && $valueType === PropertyRegistry::getInstance()->getPropertyValueTypeById( $this->m_key ) ) {
			$this->propertyValueType = $valueType;
			return $this;
		}

		throw new RuntimeException( 'DataType cannot be altered for a predefined property' );
	}

	/**
	 * @deprecated since 3.0, use DIProperty::findPropertyValueType
	 */
	public function findPropertyTypeId() {
		return $this->findPropertyValueType();
	}

	/**
	 * Find the property's type ID, either by looking up its predefined ID
	 * (if any) or by retrieving the relevant information from the store.
	 * If no type is stored for a user defined property, the global default
	 * type will be used.
	 *
	 * @since 3.0
	 *
	 * @return string type ID
	 */
	public function findPropertyValueType() {

		if ( isset( $this->propertyValueType ) ) {
			return $this->propertyValueType;
		}

		if ( !$this->isUserDefined() ) {
			return $this->propertyValueType = PropertyRegistry::getInstance()->getPropertyValueTypeById( $this->m_key );
		}

		$diWikiPage = new DIWikiPage( $this->getKey(), SMW_NS_PROPERTY, $this->interwiki );
		$applicationFactory = ApplicationFactory::getInstance();

		$typearray = $applicationFactory->getPropertySpecificationLookup()->getSpecification(
			$this,
			new self( '_TYPE' )
		);

		if ( is_array( $typearray ) && count( $typearray ) >= 1 ) { // some types given, pick one (hopefully unique)
			$typeDataItem = reset( $typearray );

			if ( $typeDataItem instanceof SMWDIUri ) {
				$this->propertyValueType = $typeDataItem->getFragment();
			} else {
				// This is important. If a page has an invalid assignment to "has type", no
				// value will be stored, so the elseif case below occurs. But if the value
				// is retrieved within the same run, then the error value for "has type" is
				// cached and thus this case occurs. This is why it is important to tolerate
				// this case -- it is not necessarily a DB error.
				$this->propertyValueType = $applicationFactory->getSettings()->get( 'smwgPDefaultType' );
			}
		} else { // no type given
			$this->propertyValueType = $applicationFactory->getSettings()->get( 'smwgPDefaultType' );
		}

		return $this->propertyValueType;
	}

	/**
	 * @see DataItem::getSerialization
	 *
	 * @return string
	 */
	public function getSerialization() {
		return ( $this->m_inverse ? '-' : '' ) . $this->m_key;
	}

	/**
	 * Create a data item from the provided serialization string and type
	 * ID.
	 *
	 * @param string $serialization
	 *
	 * @return DIProperty
	 */
	public static function doUnserialize( $serialization ) {
		$inverse = false;

		if ( $serialization{0} == '-' ) {
			$serialization = substr( $serialization, 1 );
			$inverse = true;
		}

		return new self( $serialization, $inverse );
	}

	/**
	 * @param SMWDataItem $di
	 *
	 * @return boolean
	 */
	public function equals( SMWDataItem $di ) {
		if ( $di->getDIType() !== SMWDataItem::TYPE_PROPERTY ) {
			return false;
		}

		return $di->getKey() === $this->m_key;
	}

	/**
	 * Construct a property from a user-supplied label. The main difference
	 * to the normal constructor of DIProperty is that it is checked
	 * whether the label refers to a known predefined property.
	 * Note that this function only gives access to the registry data that
	 * DIProperty stores, but does not do further parsing of user input.
	 *
	 * To process wiki input, SMWPropertyValue should be used.
	 *
	 * @param $label string label for the property
	 * @param $inverse boolean states if the inverse of the property is constructed
	 *
	 * @return DIProperty object
	 */
	public static function newFromUserLabel( $label, $inverse = false, $languageCode = false ) {

		if ( $label !== '' && $label{0} == '-' ) {
			$label = substr( $label, 1 );
			$inverse = true;
		}

		// Special handling for when the user value contains a @LCODE marker
		if ( ( $annotatedLanguageCode = Localizer::getAnnotatedLanguageCodeFrom( $label ) ) !== false ) {
			$languageCode = $annotatedLanguageCode;
		}

		$id = false;
		$label = str_replace( '_', ' ', $label );

		if ( $languageCode ) {
			$id = PropertyRegistry::getInstance()->findPropertyIdFromLabelByLanguageCode(
				$label,
				$languageCode
			);
		}

		if ( $id !== false ) {
			return new self( $id, $inverse );
		}

		$id = PropertyRegistry::getInstance()->findPropertyIdByLabel(
			$label
		);

		if ( $id === false ) {
			return new self( str_replace( ' ', '_', $label ), $inverse );
		}

		return new self( $id, $inverse );
	}

	private function newDIWikiPage( $dbkey, $subobjectName ) {

		// If an inverse marker is present just omit the marker so a normal
		// property page link can be produced independent of its directionality
		if ( $dbkey !== '' && $dbkey{0} == '-'  ) {
			$dbkey = substr( $dbkey, 1 );
		}

		try {
			return new DIWikiPage( str_replace( ' ', '_', $dbkey ), SMW_NS_PROPERTY, $this->interwiki, $subobjectName );
		} catch ( DataItemException $e ) {
			return null;
		}
	}

}