summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php336
1 files changed, 336 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php b/www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php
new file mode 100644
index 00000000..c2bf1cd9
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/ParserFunctions/SubobjectParserFunction.php
@@ -0,0 +1,336 @@
+<?php
+
+namespace SMW\ParserFunctions;
+
+use Parser;
+use SMW\DataValueFactory;
+use SMW\DIProperty;
+use SMW\HashBuilder;
+use SMW\MediaWiki\StripMarkerDecoder;
+use SMW\Message;
+use SMW\MessageFormatter;
+use SMW\ParserData;
+use SMW\ParserParameterProcessor;
+use SMW\SemanticData;
+use SMW\Subobject;
+
+/**
+ * @private This class should not be instantiated directly, please use
+ * ParserFunctionFactory::newSubobjectParserFunction
+ *
+ * Provides the {{#subobject}} parser function
+ *
+ * @see http://www.semantic-mediawiki.org/wiki/Help:ParserFunction
+ *
+ * @license GNU GPL v2+
+ * @since 1.9
+ *
+ * @author mwjames
+ */
+class SubobjectParserFunction {
+
+ /**
+ * Fixed identifier that describes the sortkey annotation parameter
+ */
+ const PARAM_SORTKEY = '@sortkey';
+
+ /**
+ * Fixed identifier that describes a category parameter
+ *
+ * Those will not be visible by the "standard" category list as the handling
+ * of assigned categories is SMW specific for subobjects.
+ */
+ const PARAM_CATEGORY = '@category';
+
+ /**
+ * Fixed identifier that describes a property that can auto-linked the
+ * embeddedding subject
+ */
+ const PARAM_LINKWITH = '@linkWith';
+
+ /**
+ * @var ParserData
+ */
+ protected $parserData;
+
+ /**
+ * @var Subobject
+ */
+ protected $subobject;
+
+ /**
+ * @var MessageFormatter
+ */
+ protected $messageFormatter;
+
+ /**
+ * @var StripMarkerDecoder
+ */
+ private $stripMarkerDecoder;
+
+ /**
+ * @var boolean
+ */
+ private $useFirstElementAsPropertyLabel = false;
+
+ /**
+ * @var boolean
+ */
+ private $isCapitalLinks = true;
+
+ /**
+ * @var boolean
+ */
+ private $isComparableContent = false;
+
+ /**
+ * @since 1.9
+ *
+ * @param ParserData $parserData
+ * @param Subobject $subobject
+ * @param MessageFormatter $messageFormatter
+ */
+ public function __construct( ParserData $parserData, Subobject $subobject, MessageFormatter $messageFormatter ) {
+ $this->parserData = $parserData;
+ $this->subobject = $subobject;
+ $this->messageFormatter = $messageFormatter;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param StripMarkerDecoder $stripMarkerDecoder
+ */
+ public function setStripMarkerDecoder( StripMarkerDecoder $stripMarkerDecoder ) {
+ $this->stripMarkerDecoder = $stripMarkerDecoder;
+ }
+
+ /**
+ * @see $wgCapitalLinks
+ *
+ * @since 2.5
+ *
+ * @param boolean $isCapitalLinks
+ */
+ public function isCapitalLinks( $isCapitalLinks ) {
+ $this->isCapitalLinks = $isCapitalLinks;
+ }
+
+ /**
+ * Ensures that unordered parameters and property names are normalized and
+ * sorted to produce the same hash even if elements of the same literal
+ * representation are placed differently.
+ *
+ * @since 3.0
+ *
+ * @param boolean $isComparableContent
+ */
+ public function isComparableContent( $isComparableContent = true ) {
+ $this->isComparableContent = (bool)$isComparableContent;
+ }
+
+ /**
+ * @since 1.9
+ *
+ * @param boolean $useFirstElementAsPropertyLabel
+ *
+ * @return SubobjectParserFunction
+ */
+ public function useFirstElementAsPropertyLabel( $useFirstElementAsPropertyLabel = true ) {
+ $this->useFirstElementAsPropertyLabel = (bool)$useFirstElementAsPropertyLabel;
+ return $this;
+ }
+
+ /**
+ * @since 1.9
+ *
+ * @param ParserParameterProcessor $params
+ *
+ * @return string|null
+ */
+ public function parse( ParserParameterProcessor $parameters ) {
+
+ if (
+ $this->parserData->canUse() &&
+ $this->addDataValuesToSubobject( $parameters ) &&
+ $this->subobject->getSemanticData()->isEmpty() === false ) {
+ $this->parserData->getSemanticData()->addSubobject( $this->subobject );
+ }
+
+ $this->parserData->pushSemanticDataToParserOutput();
+
+ $html = $this->messageFormatter->addFromArray( $this->subobject->getErrors() )
+ ->addFromArray( $this->parserData->getErrors() )
+ ->addFromArray( $parameters->getErrors() )
+ ->getHtml();
+
+ // An empty output in MW forces an extra <br> element.
+ //if ( $html == '' ) {
+ // $html = '<p></p>';
+ //}
+
+ return $html;
+ }
+
+ protected function addDataValuesToSubobject( ParserParameterProcessor $parserParameterProcessor ) {
+
+ // Named subobjects containing a "." in the first five characters are
+ // reserved to be used by extensions only in order to separate them from
+ // user land and avoid having them accidentally to refer to the same
+ // named ID (i.e. different access restrictions etc.)
+ if ( strpos( mb_substr( $parserParameterProcessor->getFirst(), 0, 5 ), '.' ) !== false ) {
+ return $this->parserData->addError(
+ Message::encode( [ 'smw-subobject-parser-invalid-naming-scheme', $parserParameterProcessor->getFirst() ] )
+ );
+ }
+
+ list( $parameters, $id ) = $this->getParameters(
+ $parserParameterProcessor
+ );
+
+ $this->subobject->setEmptyContainerForId(
+ $id
+ );
+
+ $subject = $this->subobject->getSubject();
+
+ foreach ( $parameters as $property => $values ) {
+
+ if ( $property === self::PARAM_SORTKEY ) {
+ $property = DIProperty::TYPE_SORTKEY;
+ }
+
+ if ( $property === self::PARAM_CATEGORY ) {
+ $property = DIProperty::TYPE_CATEGORY;
+ }
+
+ foreach ( $values as $value ) {
+
+ $dataValue = DataValueFactory::getInstance()->newDataValueByText(
+ $property,
+ $value,
+ false,
+ $subject
+ );
+
+ $this->subobject->addDataValue( $dataValue );
+ }
+ }
+
+ $this->augment( $this->subobject->getSemanticData() );
+
+ return true;
+ }
+
+ private function getParameters( ParserParameterProcessor $parserParameterProcessor ) {
+
+ $id = $parserParameterProcessor->getFirst();
+ $isAnonymous = in_array( $id, [ null, '' ,'-' ] );
+
+ $useFirst = $this->useFirstElementAsPropertyLabel && !$isAnonymous;
+
+ $parameters = $this->preprocess(
+ $parserParameterProcessor,
+ $useFirst
+ );
+
+ // FIXME remove the check with 3.1, should be standard by then!
+ if ( !$this->isComparableContent ) {
+ $p = $parameters;
+ } else {
+ $p = $parameters;
+ // Sort the copy not the parameters itself
+ $parserParameterProcessor->sort( $p );
+ }
+
+ // Reclaim the ID to be content hash based
+ if ( $useFirst || $isAnonymous ) {
+ $id = HashBuilder::createFromContent( $p, '_' );
+ }
+
+ return [ $parameters, $id ];
+ }
+
+ private function preprocess( ParserParameterProcessor $parserParameterProcessor, $useFirst ) {
+
+ if ( $parserParameterProcessor->hasParameter( self::PARAM_LINKWITH ) ) {
+ $val = $parserParameterProcessor->getParameterValuesByKey( self::PARAM_LINKWITH );
+ $parserParameterProcessor->addParameter(
+ end( $val ),
+ $this->parserData->getTitle()->getPrefixedText()
+ );
+
+ $parserParameterProcessor->removeParameterByKey( self::PARAM_LINKWITH );
+ }
+
+ if ( $useFirst ) {
+ $parserParameterProcessor->addParameter(
+ $parserParameterProcessor->getFirst(),
+ $this->parserData->getTitle()->getPrefixedText()
+ );
+ }
+
+ $parameters = $this->decode(
+ $parserParameterProcessor->toArray()
+ );
+
+ foreach ( $parameters as $property => $values ) {
+
+ $prop = $property;
+
+ // Normalize property names to generate the same hash for when
+ // CapitalLinks is enabled (has foo === Has foo)
+ if ( $property !== '' && $property{0} !== '@' && $this->isCapitalLinks ) {
+ $property = mb_strtoupper( mb_substr( $property, 0, 1 ) ) . mb_substr( $property, 1 );
+ }
+
+ unset( $parameters[$prop] );
+ $parameters[$property] = $values;
+ }
+
+ return $parameters;
+ }
+
+ private function decode( $parameters ) {
+
+ if ( $this->stripMarkerDecoder === null || !$this->stripMarkerDecoder->canUse() ) {
+ return $parameters;
+ }
+
+ // Any decoding has to happen before the subject ID is generated otherwise
+ // the value would contain something like `UNIQ--nowiki-00000011-QINU`
+ // and be part of the hash. `UNIQ--nowiki-00000011-QINU` isn't stable
+ // and changes to text will create new marker positions therefore it
+ // cannot be part of the hash computation
+ foreach ( $parameters as $property => &$values ) {
+ foreach ( $values as &$value ) {
+ $value = $this->stripMarkerDecoder->decode( $value );
+ }
+ }
+
+ return $parameters;
+ }
+
+ private function augment( $semanticData ) {
+
+ // Data block created by a user
+ $semanticData->setOption( SemanticData::PROC_USER, true );
+
+ $sortkey = new DIProperty( DIProperty::TYPE_SORTKEY );
+ $displayTitle = new DIProperty( DIProperty::TYPE_DISPLAYTITLE );
+
+ if ( $semanticData->hasProperty( $sortkey ) || !$semanticData->hasProperty( $displayTitle ) ) {
+ return null;
+ }
+
+ $pv = $semanticData->getPropertyValues(
+ $displayTitle
+ );
+
+ $semanticData->addPropertyObjectValue(
+ $sortkey,
+ end( $pv )
+ );
+ }
+
+}