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
|
|||
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 |
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.