m_fixNamespace = SMW_NS_PROPERTY; break; case '_wpc': case '__suc': case '__sin': $this->m_fixNamespace = NS_CATEGORY; break; case '_wps': $this->m_fixNamespace = SMW_NS_SCHEMA; break; case '_wpu': $this->m_fixNamespace = NS_USER; break; default: // case '_wpg': $this->m_fixNamespace = NS_MAIN; } } protected function parseUserValue( $value ) { $localizer = Localizer::getInstance(); $titleFactory = MediaWikiServices::getInstance()->getTitleFactory(); // support inputs like " [[Test]] "; // note that this only works when SMW_PARSER_LINV is set $value = ltrim( rtrim( (string)$value, ' ]' ), ' [' ); // #1066, Manipulate the output only for when the value has no caption // assigned and only if a single :Foo is being present, ::Foo is not permitted if ( $this->m_caption === false && isset( $value[2] ) && $value[0] === ':' && $value[1] !== ':' ) { $value = substr( $value, 1 ); } if ( $this->m_caption === false ) { $this->m_caption = $value; } if ( $value === '' && !$this->getOption( self::OPT_QUERY_CONTEXT ) ) { $this->addErrorMsg( [ 'smw-datavalue-wikipage-empty' ], Message::ESCAPED ); return; } // #1701 If the DV is part of a Description and an approximate search // (e.g. ~foo* / ~Foo*) then use the value as-is and avoid being // transformed by the Title object // If the vaue contains a valid NS then use the Title to create a correct // instance to distinguish [[~Foo*]] from [[Help:~Foo*]] if ( $this->getOption( self::OPT_QUERY_COMP_CONTEXT ) || $this->getOption( self::OPT_QUERY_CONTEXT ) ) { $title = $titleFactory->newFromText( $value, $this->m_fixNamespace ); // T:P0427 If the user value says `ab c*` then make sure to use this one // instead of the transformed DBKey which would be `Ab c*` if ( $title !== null && $title->getNamespace() === NS_MAIN && $this->getOption( 'isCapitalLinks' ) === false ) { return $this->m_dataitem = new DIWikiPage( $value, NS_MAIN ); // If we know that it is a wikipage in a query context and the wiki // requires `isCapitalLinks` then use the standard transformation so // they appear as standard links even though the user input was `abc`. // T:P0902 (`[[Help:]]`) } elseif ( $title !== null ) { return $this->m_dataitem = DIWikiPage::newFromTitle( $title ); } elseif ( !$localizer->getNsIndex( substr( $value, 0, -1 ) ) ) { return $this->m_dataitem = new DIWikiPage( $value, NS_MAIN ); } } if ( $value[0] == '#' ) { if ( $this->m_contextPage === null ) { $this->addErrorMsg( [ 'smw-datavalue-wikipage-missing-fragment-context', $value ] ); return; } else { $this->m_title = $titleFactory->makeTitle( $this->m_contextPage->getNamespace(), $this->m_contextPage->getDBkey(), substr( $value, 1 ), $this->m_contextPage->getInterwiki() ); } } else { $this->m_title = $titleFactory->newFromText( $value, $this->m_fixNamespace ); } $property = $this->getProperty(); /// TODO: Escape the text so users can see punctuation problems (bug 11666). if ( $this->m_title === null && $property !== null ) { $this->addErrorMsg( [ 'smw-datavalue-wikipage-property-invalid-title', $property->getLabel(), $value ] ); } elseif ( $this->m_title === null ) { $this->addErrorMsg( [ 'smw-datavalue-wikipage-invalid-title', $value ] ); } elseif ( ( $this->m_fixNamespace != NS_MAIN ) && ( $this->m_fixNamespace != $this->m_title->getNamespace() ) ) { $this->addErrorMsg( [ 'smw_wrong_namespace', $localizer->getNsText( $this->m_fixNamespace ) ] ); } else { $this->m_fragment = str_replace( ' ', '_', $this->m_title->getFragment() ); $this->m_prefixedtext = ''; $this->m_id = -1; // unset id $this->m_dataitem = DIWikiPage::newFromTitle( $this->m_title, $this->m_typeid ); } } /** * @see SMWDataValue::loadDataItem() * @param $dataItem SMWDataItem * @return bool */ protected function loadDataItem( SMWDataItem $dataItem ) { if ( $dataItem->getDIType() == SMWDataItem::TYPE_CONTAINER ) { // might throw an exception, we just pass it through $dataItem = $dataItem->getSemanticData()->getSubject(); } if ( $dataItem->getDIType() !== SMWDataItem::TYPE_WIKIPAGE ) { return false; } $this->m_dataitem = $dataItem; $this->m_id = -1; $this->m_title = null; $this->m_fragment = (string)$dataItem->getSubobjectName(); $this->m_prefixedtext = ''; $this->m_caption = false; // this class can handle this $this->linkAttributes = []; if ( ( $this->m_fixNamespace != NS_MAIN ) && ( $this->m_fixNamespace != $dataItem->getNamespace() ) ) { $this->addErrorMsg( [ 'smw_wrong_namespace', Localizer::getInstance()->getNsText( $this->m_fixNamespace ) ] ); } return true; } /** * @since 2.4 * * @param array $linkAttributes */ public function setLinkAttributes( array $linkAttributes ) { $this->linkAttributes = $linkAttributes; } /** * @since 2.5 * * @param array $queryParameters */ public function setQueryParameters( array $queryParameters ) { $this->queryParameters = $queryParameters; } /** * Display the value on a wiki page. This is used to display the value * in the place where it was annotated on a wiki page. The desired * behavior is that the display in this case looks as if no property * annotation had been given, i.e. an annotation [[property::page|foo]] * should display like [[page|foo]] in MediaWiki. But this should lead * to a link, not to a category assignment. This means that: * * (1) If Image: is used (instead of Media:) then let MediaWiki embed * the image. * * (2) If Category: is used, treat it as a page and link to it (do not * categorize the page) * * (3) Preserve everything given after "|" for display (caption, image * parameters, ...) * * (4) Use the (default) caption for display. When the value comes from * user input, this includes the full value that one would also see in * MediaWiki. * * @param null $linked mixed generate links if not null or false * @return string */ public function getShortWikiText( $linked = null ) { if ( $linked === null || $linked === false || $this->m_outformat == '-' || !$this->isValid() || $this->m_caption === '' ) { $text = $this->m_caption !== false ? $this->m_caption : $this->getWikiValue(); // #4037 if ( $this->linkAttributes !== [] ) { $text = Html::rawElement( 'span', $this->linkAttributes, $text ); } return $text; } $noImage = $this->getOption( self::NO_IMAGE, false ); if ( $this->m_outformat == 'noimage' ) { $noImage = true; } if ( Image::isImage( $this->m_dataitem ) && $this->m_dataitem->getInterwiki() === '' && !$noImage ) { $linkEscape = ''; $sanitizedHtml = Sanitizer::removeSomeTags( $this->m_outformat ); $options = $this->m_outformat === false ? 'frameless|border|text-top|' : str_replace( ';', '|', $sanitizedHtml ); $defaultCaption = '|' . $this->getShortCaptionText() . '|' . $options; } else { $linkEscape = ':'; $defaultCaption = '|' . $this->getShortCaptionText(); } if ( $this->m_caption === false ) { $link = '[[' . $linkEscape . $this->getWikiLinkTarget() . $defaultCaption . ']]'; } else { $link = '[[' . $linkEscape . $this->getWikiLinkTarget() . '|' . $this->m_caption . ']]'; } if ( $this->m_fragment !== '' ) { $this->linkAttributes['class'] = 'smw-subobject-entity'; } if ( $this->linkAttributes !== [] ) { $link = Html::rawElement( 'span', $this->linkAttributes, $link ); } return $link; } /** * Display the value as in getShortWikiText() but create HTML. * The only difference is that images are not embedded. * * @param Linker|null $linker mixed the Linker object to use or null if no linking is desired * @return string */ public function getShortHTMLText( $linker = null ) { if ( $this->m_fragment !== '' ) { $this->linkAttributes['class'] = 'smw-subobject-entity'; } // init the Title object, may reveal hitherto unnoticed errors: if ( $linker !== null && $linker !== false && $this->m_caption !== '' && $this->m_outformat != '-' ) { $this->getTitle(); } if ( $linker === null || $linker === false || !$this->isValid() || $this->m_outformat == '-' || $this->m_caption === '' ) { $caption = $this->m_caption === false ? $this->getWikiValue() : $this->m_caption; return Sanitizer::removeSomeTags( $caption ); } $caption = $this->m_caption === false ? $this->getShortCaptionText() : $this->m_caption; $caption = Sanitizer::removeSomeTags( $caption ); if ( $this->getNamespace() == NS_MEDIA ) { // this extra case *is* needed return $linker->makeMediaLinkObj( $this->getTitle(), $caption ); } return $linker->link( $this->getTitle(), $caption, $this->linkAttributes, $this->queryParameters ); } /** * Display the "long" value on a wiki page. This behaves largely like * getShortWikiText() but does not use the caption. Instead, it always * takes the long display form (wiki value). * * @param null $linked mixed if true the result will be linked * @return string */ public function getLongWikiText( $linked = null ) { if ( !$this->isValid() ) { return $this->getErrorText(); } $noImage = $this->getOption( self::NO_IMAGE, false ); if ( $this->m_outformat == 'noimage' ) { $noImage = true; } if ( $linked === null || $linked === false || $this->m_outformat == '-' ) { return $this->getWikiValue(); } elseif ( Image::isImage( $this->m_dataitem ) && $this->m_dataitem->getInterwiki() === '' && !$noImage ) { // Embed images and other files // Note that the embedded file links to the image, hence needs no additional link text. // There should not be a linebreak after an impage, just like there is no linebreak after // other values (whether formatted or not). return '[[' . $this->getWikiLinkTarget() . '|' . $this->getLongCaptionText() . '|frameless|border|text-top]]'; } $link = '[[:' . $this->getWikiLinkTarget() . '|' . $this->getLongCaptionText() . ']]'; if ( $this->m_fragment !== '' ) { $this->linkAttributes['class'] = 'smw-subobject-entity'; } if ( $this->linkAttributes !== [] ) { $link = Html::rawElement( 'span', $this->linkAttributes, $link ); } return $link; } /** * Display the "long" value in HTML. This behaves largely like * getLongWikiText() but does not embed images. * * @param null $linker mixed if a Linker is given, the result will be linked * @return string */ public function getLongHTMLText( $linker = null ) { if ( $this->m_fragment !== '' ) { $this->linkAttributes['class'] = 'smw-subobject-entity'; } // init the Title object, may reveal hitherto unnoticed errors: if ( $linker !== null && ( $this->m_outformat != '-' ) ) { $this->getTitle(); } if ( !$this->isValid() ) { return $this->getErrorText(); } $sanitizerCallback = [ Sanitizer::class, 'removeSomeTags' ]; if ( $linker === null || $linker === false || $this->m_outformat == '-' ) { return call_user_func( $sanitizerCallback, $this->getWikiValue() ); } elseif ( $this->getNamespace() == NS_MEDIA ) { // this extra case is really needed return $linker->makeMediaLinkObj( $this->getTitle(), call_user_func( $sanitizerCallback, $this->getLongCaptionText() ) ); } // all others use default linking, no embedding of images here return $linker->link( $this->getTitle(), call_user_func( $sanitizerCallback, $this->getLongCaptionText() ), $this->linkAttributes, $this->queryParameters ); } /** * Return a string that could be used in an in-page property assignment * for setting this value. This does not include initial ":" for * escaping things like Category: links since the property value does * not include such escapes either. Fragment information is included. * Namespaces are omitted if a fixed namespace is used, since they are * not needed in this case when making a property assignment. * * @return string */ public function getWikiValue() { if ( $this->getOption( self::SHORT_FORM, false ) ) { $text = $this->getText(); } elseif ( $this->getOption( self::PREFIXED_FORM, false ) ) { $text = $this->getPrefixedText(); } elseif ( in_array( $this->getTypeID(), [ '_wpp', '_wps', '_wpu', '__sup', '__sin', '__suc', '__con' ] ) || $this->m_fixNamespace === NS_MAIN ) { $text = $this->getPrefixedText(); } else { $text = $this->getText(); } return $text . ( $this->m_fragment !== '' ? "#{$this->m_fragment}" : '' ); } public function getHash() { return $this->isValid() ? $this->getPrefixedText() : implode( "\t", $this->getErrors() ); } /** * Create links to mapping services based on a wiki-editable message. * The parameters available to the message are: * $1: urlencoded article name (no namespace) * * @return array */ protected function getServiceLinkParams() { if ( $this->isValid() ) { return [ rawurlencode( str_replace( '_', ' ', $this->m_dataitem->getDBkey() ) ) ]; } else { return []; } } ///// special interface for wiki page values /** * @deprecated since 3.1 use getDataItem()->getTitle() instead * * Return according Title object or null if no valid value was set. * null can be returned even if this object returns true for isValid(), * since the latter function does not check whether MediaWiki can really * make a Title out of the given data. * However, isValid() will return false *after* this function failed in * trying to create a title. * * @return Title */ public function getTitle() { if ( $this->m_title !== null ) { return $this->m_title; } if ( $this->isValid() ) { if ( ( $title = $this->m_dataitem->getTitle() ) !== null ) { return $this->m_title = $title; } // #3278, Special handling of `>` in the user namespace // added a prefix to users that originate from imported content if ( $this->m_dataitem->getNamespace() === NS_USER && strpos( $this->m_dataitem->getDBkey(), '>' ) !== false ) { $this->setOption( self::OPT_DISABLE_INFOLINKS, true ); $this->m_title = MediaWikiServices::getInstance()->getTitleFactory()->newFromText( $this->m_dataitem->getDBkey() ); return $this->m_title; } } $errArg = $this->m_caption; if ( $this->isValid() ) { $ns = Localizer::getInstance()->getNsText( $this->m_dataitem->getNamespace() ); $errArg = "$ns:" . $this->m_dataitem->getDBkey(); } // Should not normally happen, but anyway ... $this->addErrorMsg( [ 'smw_notitle', $errArg ] ); } /** * @deprecated since 3.1 * * Get MediaWiki's ID for this value or 0 if not available. * * @return integer */ private function getArticleID() { if ( $this->m_id === false ) { $this->m_id = $this->getTitle() !== null ? $this->m_title->getArticleID() : 0; } return $this->m_id; } /** * @deprecated since 3.1 * * Get namespace constant for this value. * * @return integer */ private function getNamespace() { return $this->m_dataitem->getNamespace(); } /** * @deprecated since 3.1 * * Get DBKey for this value. Subclasses that allow for values that do not * correspond to wiki pages may choose a DB key that is not a legal title * DB key but rather another suitable internal ID. Thus it is not suitable * to use this method in places where only MediaWiki Title keys are allowed. * * @return string */ private function getDBkey() { return $this->m_dataitem->getDBkey(); } /** * Get text label for this value, just like Title::getText(). * * @return string */ public function getText() { if ( $this->getOption( self::NO_TEXT_TRANSFORMATION, false ) ) { return $this->m_dataitem->getDBkey(); } return str_replace( '_', ' ', $this->m_dataitem->getDBkey() ); } /** * Get the prefixed text for this value, including a localized namespace * prefix. * * @return string */ public function getPrefixedText() { if ( $this->m_prefixedtext !== '' ) { return $this->m_prefixedtext; } // In case something went wrong (invalid NS etc.), hint the ID to aid the // investigation if ( $this->m_dataitem !== null && $this->m_dataitem->getId() > 0 ) { $this->m_prefixedtext = 'NO_VALID_VALUE (ID: ' . $this->m_dataitem->getId() . ')'; } else { $this->m_prefixedtext = 'NO_VALID_VALUE'; } if ( $this->isValid() ) { $nstext = Localizer::getInstance()->getNsText( $this->m_dataitem->getNamespace() ); $this->m_prefixedtext = ( $this->m_dataitem->getInterwiki() !== '' ? $this->m_dataitem->getInterwiki() . ':' : '' ) . ( $nstext !== '' ? "$nstext:" : '' ) . $this->getText(); } return $this->m_prefixedtext; } /** * @deprecated since 3.1 * * Get interwiki prefix or empty string. * * @return string */ private function getInterwiki() { return $this->m_dataitem->getInterwiki(); } /** * @see SMWDataValue::getPreferredCaption * * @since 2.4 * * @return string */ public function getPreferredCaption() { if ( ( $preferredCaption = parent::getPreferredCaption() ) !== '' && $preferredCaption !== false ) { return $preferredCaption; } $preferredCaption = $this->getDisplayTitle(); if ( $preferredCaption === '' && $this->getOption( 'prefixed.preferred.caption' ) ) { $preferredCaption = $this->getPrefixedText(); } elseif ( $preferredCaption === '' ) { $preferredCaption = $this->getText(); } return $preferredCaption; } /** * Get a short caption used to label this value. In particular, this * omits namespace and interwiki prefixes (similar to the MediaWiki * "pipe trick"). Fragments are included unless they start with an * underscore (used for generated fragment names that are not helpful * for users and that might change easily). * * @since 1.7 * @return string */ protected function getShortCaptionText() { if ( $this->m_fragment !== '' && $this->m_fragment[0] != '_' ) { $fragmentText = '#' . str_replace( '_', ' ', $this->m_fragment ); } else { $fragmentText = ''; } if ( $this->m_caption && $this->m_caption !== '' ) { return $this->m_caption; } $displayTitle = $this->getDisplayTitle(); if ( $displayTitle === '' ) { $displayTitle = $this->getText(); } // #3945 (Remove context as per pipe trick) if ( $this->hasFeature( SMW_DV_WPV_PIPETRICK ) ) { $displayTitle = Pipetrick::apply( $displayTitle ); } return $displayTitle . $fragmentText; } /** * Get a long caption used to label this value. In particular, this * includes namespace and interwiki prefixes, while fragments are only * included if they do not start with an underscore (used for generated * fragment names that are not helpful for users and that might change * easily). * * @since 1.7 * @return string */ protected function getLongCaptionText() { if ( $this->m_fragment !== '' && $this->m_fragment[0] != '_' ) { $fragmentText = '#' . str_replace( '_', ' ', $this->m_fragment ); } else { $fragmentText = ''; } if ( $this->m_caption && $this->m_caption !== '' ) { return $this->m_caption; } $displayTitle = $this->getDisplayTitle(); if ( $displayTitle === '' ) { $displayTitle = $this->m_fixNamespace == NS_MAIN ? $this->getPrefixedText() : $this->getText(); } return $displayTitle . $fragmentText; } /** * Compute a text that can be used in wiki text to link to this * datavalue. Processing includes some escaping and adding the * fragment. * * @since 1.7 * @return string */ protected function getWikiLinkTarget() { return str_replace( "'", ''', $this->getPrefixedText() ) . ( $this->m_fragment !== '' ? "#{$this->m_fragment}" : '' ); } /** * Find the sortkey for this object. * * @deprecated Use \SMW\Store::getWikiPageSortKey(). Will vanish before SMW 1.7 * * @return string sortkey */ private function getSortKey() { return ApplicationFactory::getInstance()->getStore()->getWikiPageSortKey( $this->m_dataitem ); } /** * @since 2.4 * * @return string */ public function getDisplayTitle() { if ( $this->m_dataitem === null || !$this->hasFeature( SMW_DV_WPV_DTITLE ) ) { return ''; } // Should come from the DataValueServiceFactory $displayTitleFinder = ApplicationFactory::getInstance()->singleton( 'DisplayTitleFinder' ); return $displayTitleFinder->findDisplayTitle( $this->m_dataitem ); } }