585 lines
15 KiB
PHP
585 lines
15 KiB
PHP
<?php
|
|
|
|
namespace SMW;
|
|
|
|
use MediaWiki\Json\JsonUnserializer;
|
|
use RuntimeException;
|
|
use SMW\Exception\DataItemException;
|
|
use SMW\Exception\DataTypeLookupException;
|
|
use SMW\Exception\PredefinedPropertyLabelMismatchException;
|
|
use SMW\Exception\PropertyLabelNotResolvedException;
|
|
use SMW\Localizer\Localizer;
|
|
use SMW\Services\ServicesFactory as ApplicationFactory;
|
|
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|null
|
|
*/
|
|
private $m_key;
|
|
|
|
/**
|
|
* Whether to take the inverse of this property or not.
|
|
* @var bool
|
|
*/
|
|
private $m_inverse;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private $propertyValueType;
|
|
|
|
/**
|
|
* Interwiki prefix for when a property represents a non-local entity
|
|
*
|
|
* @var string
|
|
*/
|
|
private $interwiki = '';
|
|
|
|
public int $id;
|
|
|
|
/**
|
|
* 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 string|bool|null $key Key for the property (internal SMW key or wikipage DB key)
|
|
* @param bool $inverse States if the inverse of the property is constructed
|
|
*/
|
|
public function __construct( $key, $inverse = false ) {
|
|
$key = (string)$key;
|
|
|
|
if ( $key === '' || $key[0] == '-' ) {
|
|
throw new PropertyLabelNotResolvedException( "Illegal property key \"$key\"." );
|
|
}
|
|
|
|
if ( $key[0] == '_' && !PropertyRegistry::getInstance()->isRegistered( $key ) ) {
|
|
throw new PredefinedPropertyLabelMismatchException( "There is no predefined property with \"$key\"." );
|
|
}
|
|
|
|
$this->m_key = $key;
|
|
$this->m_inverse = $inverse;
|
|
}
|
|
|
|
/**
|
|
* @since 1.6
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getDIType(): int {
|
|
return SMWDataItem::TYPE_PROPERTY;
|
|
}
|
|
|
|
/**
|
|
* @since 1.6
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getKey(): ?string {
|
|
return $this->m_key;
|
|
}
|
|
|
|
/**
|
|
* @since 1.6
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isInverse(): bool {
|
|
return $this->m_inverse;
|
|
}
|
|
|
|
/**
|
|
* @since 3.1
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getSha1(): string {
|
|
return sha1( json_encode( [ $this->m_key, SMW_NS_PROPERTY, '', '' ] ) );
|
|
}
|
|
|
|
/**
|
|
* @since 1.6
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getSortKey(): ?string {
|
|
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).
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @return int
|
|
*/
|
|
public function isShown(): bool {
|
|
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.
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isUserDefined(): bool {
|
|
return $this->m_key[0] != '_';
|
|
}
|
|
|
|
/**
|
|
* Whether a user can freely use this property for value annotation or
|
|
* not.
|
|
*
|
|
* @since 3.0
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isUserAnnotable(): bool {
|
|
// 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 "-".
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getLabel(): string {
|
|
$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|null
|
|
*/
|
|
public function getCanonicalLabel(): ?string {
|
|
$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( string $languageCode = '' ): string {
|
|
$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( string $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.
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @param string $subobjectName
|
|
*
|
|
* @return DIWikiPage|null
|
|
*/
|
|
public function getDiWikiPage( string $subobjectName = '' ): ?DIWikiPage {
|
|
$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( string $subobjectName = '' ): ?DIWikiPage {
|
|
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 );
|
|
}
|
|
|
|
if ( $dbkey === false ) {
|
|
$dbkey = $this->m_key;
|
|
}
|
|
|
|
if ( $dbkey === null ) {
|
|
return null;
|
|
}
|
|
|
|
return $this->newDIWikiPage( $dbkey, $subobjectName );
|
|
}
|
|
|
|
/**
|
|
* @since 2.4
|
|
*
|
|
* @return DIProperty
|
|
*/
|
|
public function getRedirectTarget(): self {
|
|
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( string $valueType ): self {
|
|
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(): string {
|
|
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
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getSerialization(): ?string {
|
|
return ( $this->m_inverse ? '-' : '' ) . $this->m_key;
|
|
}
|
|
|
|
/**
|
|
* Create a data item from the provided serialization string and type
|
|
* ID.
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @param ?string $serialization
|
|
*
|
|
* @return DIProperty
|
|
*/
|
|
public static function doUnserialize( ?string $serialization ): self {
|
|
$inverse = false;
|
|
|
|
if ( is_string( $serialization ) && $serialization[0] == '-' ) {
|
|
$serialization = substr( $serialization, 1 );
|
|
$inverse = true;
|
|
}
|
|
|
|
return new self( $serialization, $inverse );
|
|
}
|
|
|
|
/**
|
|
* @see DataItem::equals
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @param SMWDataItem $di
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function equals( SMWDataItem $di ): bool {
|
|
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, PropertyValue should be used.
|
|
*
|
|
* @since 1.6
|
|
*
|
|
* @param string $label
|
|
* @param bool $inverse = false
|
|
* @param $languageCode = false
|
|
*
|
|
* @return DIProperty
|
|
*/
|
|
public static function newFromUserLabel( string $label, bool $inverse = false, $languageCode = false ): self {
|
|
// Explicitly cast to a string so we are able to return an object from
|
|
// any user label
|
|
$label = (string)$label;
|
|
|
|
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( string $dbkey, string $subobjectName ): ?DIWikiPage {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements \JsonSerializable.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return array
|
|
*/
|
|
public function jsonSerialize(): array {
|
|
$json = parent::jsonSerialize();
|
|
$json['propertyValueType'] = $this->propertyValueType;
|
|
$json['interwiki'] = $this->interwiki;
|
|
return $json;
|
|
}
|
|
|
|
/**
|
|
* Implements JsonUnserializable.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @param JsonUnserializer $unserializer Unserializer
|
|
* @param array $json JSON to be unserialized
|
|
*
|
|
* @return self
|
|
*/
|
|
public static function newFromJsonArray( JsonUnserializer $unserializer, array $json ) {
|
|
$obj = parent::newFromJsonArray( $unserializer, $json );
|
|
$obj->propertyValueType = $json['propertyValueType'];
|
|
$obj->interwiki = $json['interwiki'];
|
|
return $obj;
|
|
}
|
|
|
|
}
|