summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php782
1 files changed, 782 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php b/www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php
new file mode 100644
index 00000000..e54ec20e
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/includes/SemanticData.php
@@ -0,0 +1,782 @@
+<?php
+
+namespace SMW;
+
+use MWException;
+use SMW\DataModel\SubSemanticData;
+use SMW\Exception\SemanticDataImportException;
+use SMWContainerSemanticData;
+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 {
+
+ /**
+ * 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';
+
+ /**
+ * 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
+ */
+ static protected $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 boolean
+ */
+ 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 boolean
+ */
+ 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 boolean
+ */
+ 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 boolean
+ */
+ 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;
+
+ /**
+ * Internal flag that indicates if this semantic data will accept
+ * subdata. Semantic data objects that are subdata already do not allow
+ * (second level) subdata to be added. This ensures that all data is
+ * collected on the top level, and in particular that there is only one
+ * way to represent the same data with subdata. This is also useful for
+ * diff computation.
+ */
+ protected $subDataAllowed = true;
+
+ /**
+ * @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 = [];
+
+ /**
+ * This is kept public to keep track of the depth during a recursive processing
+ * when accessed through the SubSemanticData instance.
+ *
+ * @var integer
+ */
+ public $subContainerDepthCounter = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param DIWikiPage $subject to which this data refers
+ * @param boolean $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' ];
+ }
+
+ /**
+ * Return subject to which the stored semantic annotations refer to.
+ *
+ * @return DIWikiPage subject
+ */
+ public function getSubject() {
+ return $this->mSubject;
+ }
+
+ /**
+ * Get the array of all properties that have stored values.
+ *
+ * @return array of DIProperty objects
+ */
+ public function getProperties() {
+ ksort( $this->mProperties, SORT_STRING );
+ return $this->mProperties;
+ }
+
+ /**
+ * @since 2.4
+ *
+ * @param DIProperty $property
+ *
+ * @return boolean
+ */
+ 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
+ *
+ * @return array|string
+ */
+ 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 = Hash::createFromSemanticData( $this );
+ }
+
+ /**
+ * @see SubSemanticData::getSubSemanticData
+ *
+ * @since 1.8
+ *
+ * @return ContainerSemanticData[]
+ */
+ 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 boolean
+ */
+ 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 boolean
+ */
+ 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;
+ }
+
+ if ( !array_key_exists( $property->getKey(), $this->mPropVals ) ) {
+ $this->mPropVals[$property->getKey()] = [];
+ $this->mProperties[$property->getKey()] = $property;
+ }
+
+ if ( $this->mNoDuplicates ) {
+ $this->mPropVals[$property->getKey()][$dataItem->getHash()] = $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 === '' ) {
+ global $wgContLang;
+ self::$mPropertyPrefix = $wgContLang->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 )
+ );
+
+ return $this->addError( $dataValue->getErrors() );
+ }
+
+ $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;
+ }
+
+ 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()][$dataItem->getHash()] );
+ } else {
+ foreach( $this->mPropVals[$property->getKey()] as $index => $di ) {
+ if( $di->equals( $dataItem ) ) {
+ unset( $this->mPropVals[$property->getKey()][$index] );
+ }
+ }
+ $this->mPropVals[$property->getKey()] = array_values( $this->mPropVals[$property->getKey()] );
+ }
+
+ 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 ) {
+
+ if ( !$dataItem instanceof DIWikiPage || $dataItem->getSubobjectName() === '' ) {
+ continue;
+ }
+
+ if ( ( $subSemanticData = $this->findSubSemanticData( $dataItem->getSubobjectName() ) ) !== null ) {
+ $this->removeSubSemanticData( $subSemanticData );
+ }
+ }
+
+ unset( $this->mPropVals[$key] );
+ unset( $this->mProperties[$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
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ 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->mPropVals = [];
+
+ foreach ( $this->mProperties as $property ) {
+ $this->mPropVals[$property->getKey()] = $semanticData->getPropertyValues( $property );
+ }
+
+ $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);
+ }
+ }
+ }
+
+ foreach( $semanticData->getSubSemanticData() as $semData ) {
+ $this->addSubSemanticData( $semData );
+ }
+ }
+
+ /**
+ * 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 $subobjectName|null
+ *
+ * @return boolean
+ */
+ public function hasSubSemanticData( $subobjectName = null ) {
+ return $this->subSemanticData->hasSubSemanticData( $subobjectName );
+ }
+
+ /**
+ * @see SubSemanticData::findSubSemanticData
+ * @since 1.9
+ *
+ * @param string $subobjectName
+ *
+ * @return SMWContainerSemanticData|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 );
+ }
+
+}