965 lines
26 KiB
PHP
965 lines
26 KiB
PHP
<?php
|
|
|
|
namespace SMW;
|
|
|
|
use MediaWiki\Json\JsonUnserializable;
|
|
use MediaWiki\Json\JsonUnserializer;
|
|
use SMW\DataModel\ContainerSemanticData;
|
|
use SMW\DataModel\SequenceMap;
|
|
use SMW\DataModel\SubSemanticData;
|
|
use SMW\Exception\SemanticDataImportException;
|
|
use SMW\Exception\SubSemanticDataException;
|
|
use SMW\Localizer\Localizer;
|
|
use SMWDataItem;
|
|
use SMWDataValue;
|
|
use SMWDIContainer;
|
|
|
|
/**
|
|
* Class for representing chunks of semantic data for one given
|
|
* subject. This consists of property-value pairs, grouped by property,
|
|
* and possibly by SMWSemanticData objects about subobjects.
|
|
*
|
|
* Data about subobjects can be added in two ways: by directly adding it
|
|
* using addSubSemanticData() or by adding a property value of type
|
|
* SMWDIContainer.
|
|
*
|
|
* By its very design, the container is unable to hold inverse properties.
|
|
* For one thing, it would not be possible to identify them with mere keys.
|
|
* Since SMW cannot annotate pages with inverses, this is not a limitation.
|
|
*
|
|
* @ingroup SMW
|
|
*
|
|
* @author Markus Krötzsch
|
|
* @author Jeroen De Dauw
|
|
*/
|
|
class SemanticData implements JsonUnserializable {
|
|
|
|
/**
|
|
* Returns the last modified timestamp the data were stored to the Store or
|
|
* have been fetched from cache.
|
|
*/
|
|
const OPT_LAST_MODIFIED = 'opt.last.modified';
|
|
|
|
/**
|
|
* @see $smwgCheckForRemnantEntities
|
|
*/
|
|
const OPT_CHECK_REMNANT_ENTITIES = 'opt.check.remnant.entities';
|
|
|
|
/**
|
|
* Identifies that a data block was created by a user.
|
|
*/
|
|
const PROC_USER = 'proc.user';
|
|
|
|
/**
|
|
* Identifies that a data block was initiated by a delete request.
|
|
*/
|
|
const PROC_DELETE = 'proc.delete';
|
|
|
|
/**
|
|
* Cache for the localized version of the namespace prefix "Property:".
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $mPropertyPrefix = '';
|
|
|
|
/**
|
|
* States whether this is a stub object. Stubbing might happen on
|
|
* serialisation to save DB space.
|
|
*
|
|
* @todo Check why this is public and document this here. Or fix it.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $stubObject;
|
|
|
|
/**
|
|
* Array mapping property keys (string) to arrays of SMWDataItem
|
|
* objects.
|
|
*
|
|
* @var SMWDataItem[][]
|
|
*/
|
|
protected $mPropVals = [];
|
|
|
|
/**
|
|
* Array mapping property keys (string) to DIProperty objects.
|
|
*
|
|
* @var DIProperty[]
|
|
*/
|
|
protected $mProperties = [];
|
|
|
|
/**
|
|
* States whether the container holds any normal properties.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $mHasVisibleProps = false;
|
|
|
|
/**
|
|
* States whether the container holds any displayable predefined
|
|
* $mProperties (as opposed to predefined properties without a display
|
|
* label). For some settings we need this to decide if a Factbox is
|
|
* displayed.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $mHasVisibleSpecs = false;
|
|
|
|
/**
|
|
* States whether repeated values should be avoided. Not needing
|
|
* duplicate elimination (e.g. when loading from store) can save some
|
|
* time, especially in subclasses like SMWSqlStubSemanticData, where
|
|
* the first access to a data item is more costy.
|
|
*
|
|
* @note This setting is merely for optimization. The SMW data model
|
|
* never cares about the multiplicity of identical data assignments.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $mNoDuplicates;
|
|
|
|
/**
|
|
* DIWikiPage object that is the subject of this container.
|
|
* Subjects can never be null (and this is ensured in all methods setting
|
|
* them in this class).
|
|
*
|
|
* @var DIWikiPage
|
|
*/
|
|
protected $mSubject;
|
|
|
|
/**
|
|
* Semantic data associated to subobjects of the subject of this
|
|
* SMWSemanticData.
|
|
* These key-value pairs of subObjectName (string) =>SMWSemanticData.
|
|
*
|
|
* @since 1.8
|
|
* @var SubSemanticData
|
|
*/
|
|
protected $subSemanticData;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $errors = [];
|
|
|
|
/**
|
|
* Cache the hash to ensure a minimal impact in case of repeated usage. Any
|
|
* removal or insert action will reset the hash to null to ensure it is
|
|
* recreated in corresponds to changed nature of the data.
|
|
*
|
|
* @var string|null
|
|
*/
|
|
private $hash = null;
|
|
|
|
/**
|
|
* @var Options
|
|
*/
|
|
protected $options;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $extensionData = [];
|
|
|
|
/**
|
|
* @var array[]
|
|
*/
|
|
protected $sequenceMap = [];
|
|
|
|
/**
|
|
* @var array of int|array
|
|
*/
|
|
protected $countMap = [];
|
|
|
|
/**
|
|
* This is kept public to keep track of the depth during a recursive processing
|
|
* when accessed through the SubSemanticData instance.
|
|
*
|
|
* @var int
|
|
*/
|
|
public $subContainerDepthCounter = 0;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param DIWikiPage $subject to which this data refers
|
|
* @param bool $noDuplicates stating if duplicate data should be avoided
|
|
*/
|
|
public function __construct( DIWikiPage $subject, $noDuplicates = true ) {
|
|
$this->clear();
|
|
$this->mSubject = $subject;
|
|
$this->mNoDuplicates = $noDuplicates;
|
|
$this->subSemanticData = new SubSemanticData( $subject, $noDuplicates );
|
|
}
|
|
|
|
/**
|
|
* This object is added to the parser output of MediaWiki, but it is
|
|
* not useful to have all its data as part of the parser cache since
|
|
* the data is already stored in more accessible format in SMW. Hence
|
|
* this implementation of __sleep() makes sure only the subject is
|
|
* serialised, yielding a minimal stub data container after
|
|
* unserialisation. This is a little safer than serialising nothing:
|
|
* if, for any reason, SMW should ever access an unserialised parser
|
|
* output, then the Semdata container will at least look as if properly
|
|
* initialised (though empty).
|
|
*
|
|
* @return array
|
|
*/
|
|
public function __sleep() {
|
|
return [
|
|
'mSubject', 'mPropVals', 'mProperties', 'subSemanticData', 'mHasVisibleProps', 'mHasVisibleSpecs', 'options', 'extensionData', 'sequenceMap', 'countMap'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @since 3.2
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isStub(): bool {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return subject to which the stored semantic annotations refer to.
|
|
*
|
|
* @return DIWikiPage subject
|
|
*/
|
|
public function getSubject() {
|
|
return $this->mSubject;
|
|
}
|
|
|
|
/**
|
|
* Returns a hashed value map
|
|
*
|
|
* @since 3.1
|
|
*
|
|
* @return array[]
|
|
*/
|
|
public function getSequenceMap() {
|
|
return $this->sequenceMap;
|
|
}
|
|
|
|
/**
|
|
* Returns a map of property value counts
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getCountMap() {
|
|
return $this->countMap;
|
|
}
|
|
|
|
/**
|
|
* Get the array of all properties that have stored values.
|
|
*
|
|
* @return DIProperty[]
|
|
*/
|
|
public function getProperties() {
|
|
ksort( $this->mProperties, SORT_STRING );
|
|
return $this->mProperties;
|
|
}
|
|
|
|
/**
|
|
* @since 2.4
|
|
*
|
|
* @param DIProperty $property
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasProperty( DIProperty $property ) {
|
|
return isset( $this->mProperties[$property->getKey()] ) || array_key_exists( $property->getKey(), $this->mProperties );
|
|
}
|
|
|
|
/**
|
|
* Get the array of all stored values for some property.
|
|
*
|
|
* @param DIProperty $property
|
|
* @return SMWDataItem[]
|
|
*/
|
|
public function getPropertyValues( DIProperty $property ) {
|
|
if ( $property->isInverse() ) { // we never have any data for inverses
|
|
return [];
|
|
}
|
|
|
|
if ( array_key_exists( $property->getKey(), $this->mPropVals ) ) {
|
|
return array_values( $this->mPropVals[$property->getKey()] );
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
*/
|
|
public function setExtensionData( $key, $value ) {
|
|
$this->extensionData[$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @param string $key
|
|
*
|
|
* @return mixed|null
|
|
*/
|
|
public function getExtensionData( $key ) {
|
|
if ( !isset( $this->extensionData[$key] ) ) {
|
|
return null;
|
|
}
|
|
|
|
return $this->extensionData[$key];
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param string $key
|
|
* @param mixed $default
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getOption( $key, $default = null ) {
|
|
if ( !$this->options instanceof Options ) {
|
|
$this->options = new Options();
|
|
}
|
|
|
|
if ( $this->options->has( $key ) ) {
|
|
return $this->options->get( $key );
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param string $key
|
|
* @param string $value
|
|
*/
|
|
public function setOption( $key, $value ) {
|
|
if ( !$this->options instanceof Options ) {
|
|
$this->options = new Options();
|
|
}
|
|
|
|
$this->options->set( $key, $value );
|
|
}
|
|
|
|
/**
|
|
* Returns collected errors occurred during processing
|
|
*
|
|
* @since 1.9
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getErrors() {
|
|
return $this->errors;
|
|
}
|
|
|
|
/**
|
|
* Adds an error array
|
|
*
|
|
* @since 1.9
|
|
*/
|
|
public function addError( $error ) {
|
|
$this->errors = array_merge( $this->errors, (array)$error );
|
|
}
|
|
|
|
/**
|
|
* Generate a hash value to simplify the comparison of this data
|
|
* container with other containers. Subdata is taken into account.
|
|
*
|
|
* The hash uses PHP's md5 implementation, which is among the fastest
|
|
* hash algorithms that PHP offers.
|
|
*
|
|
* @note This function may be used to obtain keys for SemanticData
|
|
* objects or to do simple equality tests. Equal hashes with very
|
|
* high probability indicate equal data.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getHash() {
|
|
if ( $this->hash !== null ) {
|
|
return $this->hash;
|
|
}
|
|
|
|
return $this->hash = HashBuilder::createFromSemanticData( $this );
|
|
}
|
|
|
|
/**
|
|
* @see SubSemanticData::getSubSemanticData
|
|
*
|
|
* @since 1.8
|
|
*
|
|
* @return SemanticData[]
|
|
*/
|
|
public function getSubSemanticData() {
|
|
// Remove the check in 3.0
|
|
$subSemanticData = $this->subSemanticData;
|
|
|
|
// Avoids an issue where the serialized array from a previous usage is
|
|
// returned from a __wakeup, where now a SubSemanticData (#2177) is expected.
|
|
if ( !$subSemanticData instanceof SubSemanticData ) {
|
|
$this->subSemanticData = new SubSemanticData( $this->mSubject, $this->mNoDuplicates );
|
|
$this->subSemanticData->copyDataFrom( $subSemanticData );
|
|
}
|
|
|
|
return $this->subSemanticData->getSubSemanticData();
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*/
|
|
public function clearSubSemanticData() {
|
|
if ( $this->subContainerDepthCounter > 0 ) {
|
|
$this->subContainerDepthCounter--;
|
|
}
|
|
|
|
if ( $this->subSemanticData !== null ) {
|
|
$this->subSemanticData->clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if there are any visible properties.
|
|
*
|
|
* @note While called "visible" this check actually refers to the
|
|
* function DIProperty::isShown(). The name is kept for
|
|
* compatibility.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasVisibleProperties() {
|
|
return $this->mHasVisibleProps;
|
|
}
|
|
|
|
/**
|
|
* Return true if there are any special properties that can
|
|
* be displayed.
|
|
*
|
|
* @note While called "visible" this check actually refers to the
|
|
* function DIProperty::isShown(). The name is kept for
|
|
* compatibility.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasVisibleSpecialProperties() {
|
|
return $this->mHasVisibleSpecs;
|
|
}
|
|
|
|
/**
|
|
* Store a value for a property identified by its SMWDataItem object.
|
|
*
|
|
* @note There is no check whether the type of the given data item
|
|
* agrees with the type of the property. Since property types can
|
|
* change, all parts of SMW are prepared to handle mismatched data item
|
|
* types anyway.
|
|
*
|
|
* @param $property DIProperty
|
|
* @param $dataItem SMWDataItem
|
|
*/
|
|
public function addPropertyObjectValue( DIProperty $property, SMWDataItem $dataItem ) {
|
|
$this->hash = null;
|
|
|
|
if ( $dataItem instanceof SMWDIContainer ) {
|
|
$this->addSubSemanticData( $dataItem->getSemanticData() );
|
|
$dataItem = $dataItem->getSemanticData()->getSubject();
|
|
}
|
|
|
|
if ( $property->getKey() === DIProperty::TYPE_MODIFICATION_DATE ) {
|
|
$this->setOption( self::OPT_LAST_MODIFIED, $dataItem->getMwTimestamp() );
|
|
}
|
|
|
|
if ( $property->isInverse() ) { // inverse properties cannot be used for annotation
|
|
return;
|
|
}
|
|
|
|
$key = $property->getKey();
|
|
|
|
if ( !isset( $this->countMap[$key] ) ) {
|
|
$this->countMap[$key] = $key === '_INST' ? [] : 0;
|
|
}
|
|
|
|
if ( !array_key_exists( $property->getKey(), $this->mPropVals ) ) {
|
|
$this->mPropVals[$property->getKey()] = [];
|
|
$this->mProperties[$property->getKey()] = $property;
|
|
|
|
if ( SequenceMap::canMap( $property ) ) {
|
|
$this->sequenceMap[$property->getKey()] = [];
|
|
}
|
|
}
|
|
|
|
$hash = md5( $dataItem->getHash() );
|
|
|
|
// Only store a map for values that are allowed to
|
|
if (
|
|
$this->mNoDuplicates &&
|
|
isset( $this->sequenceMap[$property->getKey()] ) &&
|
|
!isset( $this->mPropVals[$property->getKey()][$hash] ) ) {
|
|
$this->sequenceMap[$property->getKey()][] = $hash;
|
|
}
|
|
|
|
if ( !isset( $this->mPropVals[$property->getKey()][$hash] ) ) {
|
|
|
|
// Count categories differently
|
|
if ( $key === '_INST' ) {
|
|
$this->countMap[$key][$dataItem->getDBKey()] = 1;
|
|
} else {
|
|
$this->countMap[$key]++;
|
|
}
|
|
}
|
|
|
|
if ( $this->mNoDuplicates ) {
|
|
$this->mPropVals[$property->getKey()][$hash] = $dataItem;
|
|
} else {
|
|
$this->mPropVals[$property->getKey()][] = $dataItem;
|
|
}
|
|
|
|
if ( !$property->isUserDefined() ) {
|
|
if ( $property->isShown() ) {
|
|
$this->mHasVisibleSpecs = true;
|
|
$this->mHasVisibleProps = true;
|
|
}
|
|
} else {
|
|
$this->mHasVisibleProps = true;
|
|
}
|
|
|
|
// Account for things like DISPLAYTITLE or DEFAULTSORT which are only set
|
|
// after #subobject has been processed therefore keep them in-memory
|
|
// for a post process
|
|
if ( $this->mSubject->getSubobjectName() === '' && $property->getKey() === DIProperty::TYPE_SORTKEY ) {
|
|
foreach ( $this->getSubSemanticData() as $subSemanticData ) {
|
|
$subSemanticData->setExtensionData( 'sort.extension', $dataItem->getString() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store a value for a given property identified by its text label
|
|
* (without namespace prefix).
|
|
*
|
|
* @param $propertyName string
|
|
* @param $dataItem SMWDataItem
|
|
*/
|
|
public function addPropertyValue( $propertyName, SMWDataItem $dataItem ) {
|
|
$propertyKey = smwfNormalTitleDBKey( $propertyName );
|
|
|
|
if ( array_key_exists( $propertyKey, $this->mProperties ) ) {
|
|
$property = $this->mProperties[$propertyKey];
|
|
} else {
|
|
if ( self::$mPropertyPrefix === '' ) {
|
|
self::$mPropertyPrefix = Localizer::getInstance()->getNsText( SMW_NS_PROPERTY ) . ':';
|
|
} // explicitly use prefix to cope with things like [[Property:User:Stupid::somevalue]]
|
|
|
|
$propertyDV = DataValueFactory::getInstance()->newPropertyValueByLabel( self::$mPropertyPrefix . $propertyName );
|
|
|
|
if ( !$propertyDV->isValid() ) { // error, maybe illegal title text
|
|
return;
|
|
}
|
|
|
|
$property = $propertyDV->getDataItem();
|
|
}
|
|
|
|
$this->addPropertyObjectValue( $property, $dataItem );
|
|
}
|
|
|
|
/**
|
|
* @since 1.9
|
|
*
|
|
* @param SMWDataValue $dataValue
|
|
*/
|
|
public function addDataValue( SMWDataValue $dataValue ) {
|
|
if ( !$dataValue->getProperty() instanceof DIProperty || !$dataValue->isValid() ) {
|
|
|
|
$processingErrorMsgHandler = new ProcessingErrorMsgHandler(
|
|
$this->getSubject()
|
|
);
|
|
|
|
$processingErrorMsgHandler->addToSemanticData(
|
|
$this,
|
|
$processingErrorMsgHandler->newErrorContainerFromDataValue( $dataValue )
|
|
);
|
|
|
|
$this->addError( $dataValue->getErrors() );
|
|
return;
|
|
}
|
|
|
|
$this->addPropertyObjectValue(
|
|
$dataValue->getProperty(),
|
|
$dataValue->getDataItem()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @since 2.1
|
|
*
|
|
* @param Subobject $subobject
|
|
*/
|
|
public function addSubobject( Subobject $subobject ) {
|
|
$this->addPropertyObjectValue(
|
|
$subobject->getProperty(),
|
|
$subobject->getContainer()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Remove a value for a property identified by its SMWDataItem object.
|
|
* This method removes a property-value specified by the property and
|
|
* dataitem. If there are no more property-values for this property it
|
|
* also removes the property from the mProperties.
|
|
*
|
|
* @note There is no check whether the type of the given data item
|
|
* agrees with the type of the property. Since property types can
|
|
* change, all parts of SMW are prepared to handle mismatched data item
|
|
* types anyway.
|
|
*
|
|
* @param $property DIProperty
|
|
* @param $dataItem SMWDataItem
|
|
*
|
|
* @since 1.8
|
|
*/
|
|
public function removePropertyObjectValue( DIProperty $property, SMWDataItem $dataItem ) {
|
|
$this->hash = null;
|
|
|
|
// delete associated subSemanticData
|
|
if ( $dataItem instanceof SMWDIContainer ) {
|
|
$this->removeSubSemanticData( $dataItem->getSemanticData() );
|
|
$dataItem = $dataItem->getSemanticData()->getSubject();
|
|
}
|
|
|
|
if ( $property->isInverse() ) { // inverse properties cannot be used for annotation
|
|
return;
|
|
}
|
|
|
|
$key = $property->getKey();
|
|
|
|
if (
|
|
!array_key_exists( $property->getKey(), $this->mPropVals ) ||
|
|
!array_key_exists( $property->getKey(), $this->mProperties ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( $this->mNoDuplicates ) {
|
|
// this didn't get checked for my tests, but should work
|
|
unset( $this->mPropVals[$property->getKey()][md5( $dataItem->getHash() )] );
|
|
|
|
if ( isset( $this->countMap[$key] ) && $key === '_INST' ) {
|
|
unset( $this->countMap[$key][$dataItem->getDBKey()] );
|
|
} elseif ( isset( $this->countMap[$key] ) ) {
|
|
$this->countMap[$key]--;
|
|
}
|
|
} else {
|
|
foreach ( $this->mPropVals[$property->getKey()] as $index => $di ) {
|
|
if ( $di->equals( $dataItem ) ) {
|
|
unset( $this->mPropVals[$property->getKey()][$index] );
|
|
}
|
|
|
|
if ( isset( $this->countMap[$key] ) && $key === '_INST' ) {
|
|
unset( $this->countMap[$key][$dataItem->getDBKey()] );
|
|
} elseif ( isset( $this->countMap[$key] ) ) {
|
|
$this->countMap[$key]--;
|
|
}
|
|
}
|
|
|
|
$this->mPropVals[$property->getKey()] = array_values( $this->mPropVals[$property->getKey()] );
|
|
}
|
|
|
|
if ( isset( $this->countMap[$key] ) && $this->countMap[$key] == 0 ) {
|
|
unset( $this->countMap[$key] );
|
|
}
|
|
|
|
if ( $this->mPropVals[$property->getKey()] === [] ) {
|
|
unset( $this->mProperties[$property->getKey()] );
|
|
unset( $this->mPropVals[$property->getKey()] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a property and all the values associated with this property.
|
|
*
|
|
* @since 2.5
|
|
*
|
|
* @param $property DIProperty
|
|
*/
|
|
public function removeProperty( DIProperty $property ) {
|
|
$this->hash = null;
|
|
$key = $property->getKey();
|
|
|
|
// Inverse properties cannot be used for an annotation
|
|
if ( $property->isInverse() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !isset( $this->mProperties[$key] ) || !isset( $this->mPropVals[$key] ) ) {
|
|
return;
|
|
}
|
|
|
|
// Find and remove associated assignments (e.g. _ASK as subobject
|
|
// contains _ASKSI ...)
|
|
foreach ( $this->mPropVals[$key] as $dataItem ) {
|
|
$this->removePropertyObjectValue( $property, $dataItem );
|
|
}
|
|
|
|
unset( $this->mPropVals[$key] );
|
|
unset( $this->mProperties[$key] );
|
|
unset( $this->sequenceMap[$key] );
|
|
unset( $this->countMap[$key] );
|
|
}
|
|
|
|
/**
|
|
* Delete all data other than the subject.
|
|
*/
|
|
public function clear() {
|
|
$this->mPropVals = [];
|
|
$this->mProperties = [];
|
|
$this->mHasVisibleProps = false;
|
|
$this->mHasVisibleSpecs = false;
|
|
$this->stubObject = false;
|
|
$this->clearSubSemanticData();
|
|
$this->hash = null;
|
|
$this->options = null;
|
|
}
|
|
|
|
/**
|
|
* Return true if this SemanticData is empty.
|
|
* This is the case when the subject has neither property values nor
|
|
* data for subobjects.
|
|
*
|
|
* @since 1.8
|
|
*/
|
|
public function isEmpty(): bool {
|
|
return $this->getProperties() === [] && $this->getSubSemanticData() === [];
|
|
}
|
|
|
|
/**
|
|
* Add all data from the given SMWSemanticData.
|
|
* Only works if the imported SMWSemanticData has the same subject as
|
|
* this SMWSemanticData; an exception is thrown otherwise.
|
|
*
|
|
* @since 1.7
|
|
*
|
|
* @param SemanticData $semanticData object to copy from
|
|
*
|
|
* @throws SemanticDataImportException
|
|
*/
|
|
public function importDataFrom( SemanticData $semanticData ) {
|
|
if ( !$this->mSubject->equals( $semanticData->getSubject() ) ) {
|
|
throw new SemanticDataImportException( "SemanticData can only represent data about one subject. Importing data for another subject is not possible." );
|
|
}
|
|
|
|
$this->hash = null;
|
|
|
|
// Shortcut when copying into empty objects that don't ask for
|
|
// more duplicate elimination:
|
|
if ( count( $this->mProperties ) == 0 &&
|
|
( $semanticData->mNoDuplicates >= $this->mNoDuplicates ) ) {
|
|
|
|
$this->mProperties = $semanticData->getProperties();
|
|
$this->sequenceMap = $semanticData->getSequenceMap();
|
|
$this->countMap = $semanticData->getCountMap();
|
|
$this->mPropVals = [];
|
|
|
|
foreach ( $this->mProperties as $property ) {
|
|
$key = $property->getKey();
|
|
$this->mPropVals[$key] = $semanticData->getPropertyValues( $property );
|
|
|
|
if ( isset( $this->sequenceMap[$key] ) && SequenceMap::canMap( $property ) ) {
|
|
$sequenceMap = array_flip( $this->sequenceMap[$key] );
|
|
|
|
usort( $this->mPropVals[$key], static function ( $a, $b ) use( $sequenceMap ) {
|
|
$pos_a = $sequenceMap[md5( $a->getHash() )];
|
|
$pos_b = $sequenceMap[md5( $b->getHash() )];
|
|
|
|
return ( $pos_a < $pos_b ) ? -1 : 1;
|
|
} );
|
|
}
|
|
}
|
|
|
|
$this->mHasVisibleProps = $semanticData->hasVisibleProperties();
|
|
$this->mHasVisibleSpecs = $semanticData->hasVisibleSpecialProperties();
|
|
} else {
|
|
foreach ( $semanticData->getProperties() as $property ) {
|
|
$values = $semanticData->getPropertyValues( $property );
|
|
|
|
foreach ( $values as $dataItem ) {
|
|
$this->addPropertyObjectValue( $property, $dataItem );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Postpone the import and avoid resolving `SubSemanticData` for
|
|
// a `StubSemanticData` objects that would otherwise create the entire
|
|
// object graph recursively even though it might not be necessary as in
|
|
// case when retrieving data via `Special:Browse`
|
|
//
|
|
// Subobject references are part of the value representation and assigned
|
|
// to the relevant property which may be resolved at a later point
|
|
if ( !$semanticData->isStub() ) {
|
|
foreach ( $semanticData->getSubSemanticData() as $subSemanticData ) {
|
|
$this->addSubSemanticData( $subSemanticData );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes data from the given SMWSemanticData.
|
|
* If the subject of the data that is to be removed is not equal to the
|
|
* subject of this SMWSemanticData, it will just be ignored (nothing to
|
|
* remove). Likewise, removing data that is not present does not change
|
|
* anything.
|
|
*
|
|
* @since 1.8
|
|
*
|
|
* @param SemanticData $semanticData
|
|
*/
|
|
public function removeDataFrom( SemanticData $semanticData ) {
|
|
if ( !$this->mSubject->equals( $semanticData->getSubject() ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $semanticData->getProperties() as $property ) {
|
|
$this->removeProperty( $property );
|
|
}
|
|
|
|
foreach ( $semanticData->getSubSemanticData() as $semData ) {
|
|
$this->removeSubSemanticData( $semData );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see SubSemanticData::hasSubSemanticData
|
|
* @since 1.9
|
|
*
|
|
* @param string|null $subobjectName
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasSubSemanticData( $subobjectName = null ) {
|
|
return $this->subSemanticData->hasSubSemanticData( $subobjectName );
|
|
}
|
|
|
|
/**
|
|
* @see SubSemanticData::findSubSemanticData
|
|
* @since 1.9
|
|
*
|
|
* @param string $subobjectName
|
|
*
|
|
* @return ContainerSemanticData|null
|
|
*/
|
|
public function findSubSemanticData( $subobjectName ) {
|
|
return $this->subSemanticData->findSubSemanticData( $subobjectName );
|
|
}
|
|
|
|
/**
|
|
* @see SubSemanticData::addSubSemanticData
|
|
* @since 1.8
|
|
*
|
|
* @param SemanticData $semanticData
|
|
* @throws SubSemanticDataException
|
|
*/
|
|
public function addSubSemanticData( SemanticData $semanticData ) {
|
|
$this->hash = null;
|
|
$this->subSemanticData->addSubSemanticData( $semanticData );
|
|
}
|
|
|
|
/**
|
|
* @see SubSemanticData::removeSubSemanticData
|
|
* @since 1.8
|
|
*
|
|
* @param SemanticData $semanticData
|
|
*/
|
|
public function removeSubSemanticData( SemanticData $semanticData ) {
|
|
$this->hash = null;
|
|
$this->subSemanticData->removeSubSemanticData( $semanticData );
|
|
}
|
|
|
|
/**
|
|
* Implements \JsonSerializable.
|
|
*
|
|
* @since 4.0.0
|
|
*
|
|
* @return array
|
|
*/
|
|
public function jsonSerialize(): array {
|
|
# T312589 explicitly calling jsonSerialize() will be unnecessary
|
|
# in the future.
|
|
$json = [
|
|
'stubObject' => $this->stubObject,
|
|
'mPropVals' => array_map( static function ( $x ) {
|
|
return array_map( static function ( $y ) {
|
|
return $y->jsonSerialize();
|
|
}, $x );
|
|
}, $this->mPropVals ),
|
|
'mProperties' => array_map( static function ( $x ) {
|
|
return $x->jsonSerialize();
|
|
}, $this->mProperties ),
|
|
'mHasVisibleProps' => $this->mHasVisibleProps,
|
|
'mHasVisibleSpecs' => $this->mHasVisibleSpecs,
|
|
'mNoDuplicates' => $this->mNoDuplicates,
|
|
'mSubject' => $this->mSubject ? $this->mSubject->jsonSerialize() : null,
|
|
'subSemanticData' => $this->subSemanticData ? $this->subSemanticData->jsonSerialize() : null,
|
|
'errors' => $this->errors,
|
|
'hash' => $this->hash,
|
|
'options' => $this->options ? $this->options->jsonSerialize() : null,
|
|
'extensionData' => $this->extensionData,
|
|
'sequenceMap' => $this->sequenceMap,
|
|
'countMap' => $this->countMap,
|
|
'_type_' => get_class( $this ),
|
|
];
|
|
return $json;
|
|
}
|
|
|
|
public static function maybeUnserialize( $unserializer, $value ) {
|
|
# Compatibility thunk for MW versions with T312589 fixed/unfixed
|
|
return is_object( $value ) ? $value :
|
|
$unserializer->unserialize( $value );
|
|
}
|
|
|
|
public static function maybeUnserializeArray( $unserializer, array $value ) {
|
|
# Compatibility thunk for MW versions with T312589 fixed/unfixed
|
|
$result = [];
|
|
foreach ( $value as $k => $v ) {
|
|
$result[$k] = self::maybeUnserialize( $unserializer, $v );
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 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 ) {
|
|
# T312589: In the future JsonCodec will take care of unserializing
|
|
# the values in the $json array itself.
|
|
$obj = new self(
|
|
self::maybeUnserialize( $unserializer, $json['mSubject'] ),
|
|
$json['mNoDuplicates']
|
|
);
|
|
$obj->stubObject = $json['stubObject'];
|
|
$obj->mPropVals = array_map( static function ( $x ) use( $unserializer ) {
|
|
return self::maybeUnserializeArray( $unserializer, $x );
|
|
}, $json['mPropVals'] );
|
|
$obj->mProperties = self::maybeUnserializeArray( $unserializer, $json['mProperties'] );
|
|
$obj->mHasVisibleProps = $json['mHasVisibleProps'];
|
|
$obj->mHasVisibleSpecs = $json['mHasVisibleSpecs'];
|
|
$obj->subSemanticData = $json['subSemanticData'] ? self::maybeUnserialize( $unserializer, $json['subSemanticData'] ) : null;
|
|
$obj->errors = $json['errors'];
|
|
$obj->hash = $json['hash'];
|
|
$obj->options = $json['options'] ? self::maybeUnserialize( $unserializer, $json['options'] ) : null;
|
|
$obj->extensionData = $json['extensionData'];
|
|
$obj->sequenceMap = $json['sequenceMap'];
|
|
$obj->countMap = $json['countMap'];
|
|
return $obj;
|
|
}
|
|
|
|
}
|