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
element.
//if ( $html == '' ) {
// $html = '
';
//}
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 )
);
}
}