Mwjames has uploaded a new change for review. https://gerrit.wikimedia.org/r/61171
Change subject: Refactor SMWFactbox, make it an instantiable class ...................................................................... Refactor SMWFactbox, make it an instantiable class Factbox logic has not been altered (aside from making the interface somewhat sane) + Move to SMW\Factbox + Eliminate all static methods + Re-group code for readability + Move hook related code into SMWHooks + Table formatting has been moved to an extra TableFormatter class to decouple data capturing from formatting + Introduces smwgFactboxUseCache in order for onOutputPageParserOutput() to fetch factbox text from cache instead of regenerating content on each page request. If smwgFactboxUseCache = true only after a page received a new revision (after it is saved) the factbox content will be re-parsed. This enables pages to serve content faster, consume less memory and work more efficient. The Profiler showed two issues when executing onOutputPageParserOutput() without cache, depending on how many properties/values are assigned it takes considerable amount of time identifying properties/values plus the additional text parsing that is needed to convert the text into an output representation. Name Calls Total Each % Mem Non-cached SMWHooks::onOutputPageParserOutput 1 461.743 461.743 9.946% 184492 ( 461.743 - 461.743) [872] Parser::parse-SMWHooks::onOutputPageParserOutput 1 374.598 374.598 8.069% 57957 ( 374.598 - 374.598) [806] Cached SMWHooks::onOutputPageParserOutput 1 27.051 27.051 0.859% 168323 ( 27.051 - 27.051) [2] Using the cache also shields against the loss of data attached to the outputpage object which is responsible that on some occasions the factbox disappears and only after a page purge (as the data are re-attached to the outputpage) appears again. + Introduces smwgFactboxCacheRefreshOnPurge which will force the cache to be purged after an action=purge event otherwise only a save event will invalidate the factbox cache object. Change-Id: I5eb8a3e648aa4a4610882776cb6c469a48f88ffc --- M SemanticMediaWiki.hooks.php M SemanticMediaWiki.settings.php A includes/Cache.php A includes/Factbox.php D includes/SMW_Factbox.php M includes/Settings.php M includes/Setup.php M tests/phpunit/includes/FactboxTest.php 8 files changed, 824 insertions(+), 247 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/SemanticMediaWiki refs/changes/71/61171/1 diff --git a/SemanticMediaWiki.hooks.php b/SemanticMediaWiki.hooks.php index e282a0e..4ba4a8d 100644 --- a/SemanticMediaWiki.hooks.php +++ b/SemanticMediaWiki.hooks.php @@ -452,7 +452,8 @@ * @return true */ public static function onParserAfterTidy( &$parser, &$text ) { - $cache = ObjectCache::getInstance( $GLOBALS['smwgCacheType'] ); + $pageId = $parser->getTitle()->getArticleID(); + $cache = SMW\Cache::newFromGlobals()->setKey( 'autorefresh', $pageId ); // Separate globals from local state // FIXME Do a new SMW\Settings( $GLOBALS ); @@ -469,11 +470,10 @@ // If an article was was manually purged/moved ensure that the store is // updated as well for all other cases onLinksUpdateConstructed will // initiate the store update - if( $cache->get( 'smw:autorefresh:' . $parser->getTitle()->getPrefixedDBkey() ) ){ + if( $cache->get() ) { $parserData->updateStore(); } - $cache->delete( 'smw:autorefresh:' . $parser->getTitle()->getPrefixedDBkey() ); - + $cache->delete(); return true; } @@ -536,10 +536,16 @@ * @return true */ public static function onArticlePurge( &$wikiPage ) { - ObjectCache::getInstance( $GLOBALS['smwgCacheType'] )->set( - 'smw:autorefresh:' . $wikiPage->getTitle()->getPrefixedDBkey(), - $GLOBALS['smwgAutoRefreshOnPurge'] - ); + $pageId = $wikiPage->getTitle()->getArticleID(); + $cache = SMW\Cache::newFromGlobals(); + + $cache->setKey( 'autorefresh', $pageId )->set( $GLOBALS['smwgAutoRefreshOnPurge'] ); + + // Depending on the setting, invalidate the factbox cache + $cache->setCacheEnabled( $GLOBALS['smwgFactboxCacheRefreshOnPurge'] + )->setKey( 'factbox', $pageId + )->delete(); + return true; } @@ -563,10 +569,9 @@ * @return true */ public static function onTitleMoveComplete( &$oldTitle, &$newTitle, &$user, $oldId, $newId ) { - ObjectCache::getInstance( $GLOBALS['smwgCacheType'] )->set( - 'smw:autorefresh:' . $newTitle->getPrefixedDBkey(), - $GLOBALS['smwgAutoRefreshOnPageMove'] - ); + SMW\Cache::newFromGlobals( + )->setKey( 'autorefresh', $newTitle->getArticleID() + )->set( $GLOBALS['smwgAutoRefreshOnPageMove'] ); smwfGetStore()->changeTitle( $oldTitle, $newTitle, $oldId, $newId ); return true; @@ -648,4 +653,95 @@ $processor->parse( $text ); return true; } + + /** + * Hook: Called after parse, before the HTML is added to the output + * + * @note This hook copies SMW's custom data from the given ParserOutput object to + * the given OutputPage object, since otherwise it is not possible to access + * it later on to build a Factbox. + * + * @note Use ($smwgFactboxUseCache) to avoid non-changed content being + * re-parsed every time the hook is executed where in reality page content + * has not been altered. Altered content is tracked using the revision id + * (getTouched() is not a good measure for comparison where getLatestRevID() + * is only changed after a real content change has occurred). Cached content + * is stored in an associative array following schema: + * { 'id' => (...), 'text' => (...) } + * + * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput + * + * @param OutputPage $outputPage + * @param ParserOutput $parserOutput + * + * @return true + */ + public static function onOutputPageParserOutput( OutputPage &$outputPage, ParserOutput $parserOutput ) { + $title = $outputPage->getTitle(); + $pageId = $title->getArticleID(); + $revId = $title->getLatestRevID(); + + $cache = SMW\Cache::newFromGlobals()->setCacheEnabled( $GLOBALS['smwgFactboxUseCache'] ); + $fCache = $cache->setKey( 'factbox', $pageId )->get(); + + // Assert if cached content is available for the current revision + if ( isset( $fCache['id'] ) && ( $fCache['id'] === $revId ) ) { + $outputPage->mSMWFactboxText = $fCache['text']; + return true; + } + + $parserData = new SMW\ParserData( $title, $parserOutput ); + $settings = SMW\Settings::newFromArray( array( + 'smwgShowFactboxEdit' => $GLOBALS['smwgShowFactboxEdit'], + 'smwgShowFactbox' => $GLOBALS['smwgShowFactbox'] + ) ); + + $factbox = new SMW\Factbox( $parserData, $settings, $outputPage->getContext() ); + $factboxText = $factbox->getText(); + + if ( $factbox->isVisible() ) { + + // Can't be avoided but parsing is a costly operation + $popts = new ParserOptions(); + $po = $GLOBALS['wgParser']->parse( $factboxText, $title, $popts ); + $outputPage->mSMWFactboxText = $po->getText(); + + // Add output header items + SMWOutputs::requireFromParserOutput( $po ); + SMWOutputs::commitToOutputPage( $outputPage ); + + // Update cache with parsed text only if revision id > 0 + $cache->setCacheEnabled( $revId > 0 )->set( array( 'id' => $revId, 'text' => $po->getText() ) ); + } + + return true; + } + + /** + * Hook: Allows extensions to add text after the page content and article + * metadata. + * + * @note This hook is used for inserting the Factbox text after the + * article contents (including categories). + * + * @see http://www.mediawiki.org/wiki/Manual:Hooks/SkinAfterContent + * + * @param string $data + * @param Skin|null $skin + * + * @return true + */ + public static function onSkinAfterContent( &$data, Skin $skin = null ) { + + if ( isset( $skin->getOutput()->mSMWFactboxText ) ) { + $data .= $skin->getOutput()->mSMWFactboxText; + } else if ( $GLOBALS['smwgFactboxUseCache'] ) { + $cache = SMW\Cache::newFromGlobals(); + $dCache = $cache->setKey( 'factbox', $skin->getTitle()->getArticleID() )->get(); + $data .= isset( $dCache['text'] ) ? $dCache['text'] : ''; + } + + return true; + } + } diff --git a/SemanticMediaWiki.settings.php b/SemanticMediaWiki.settings.php index 36fc19c..f4fe429 100644 --- a/SemanticMediaWiki.settings.php +++ b/SemanticMediaWiki.settings.php @@ -542,18 +542,57 @@ ### # Sets whether or not to refresh semantic data in the store when a page is -# manually purged (requires $smwgCacheType be set) +# manually purged # # @since 1.9 +# +# @requires $smwgCacheType be set +# @default true ## $smwgAutoRefreshOnPurge = true; ## ### # Sets whether or not to refresh semantic data in the store when a page was -# moved (requires $smwgCacheType be set) +# moved # # @since 1.9 +# +# @requires $smwgCacheType be set +# @default true ## $smwgAutoRefreshOnPageMove = true; ## + +### +# Sets whether or not a factbox content should be stored in cache. This will +# considerable improve page response time as non-changed page content will +# not cause re-parsing of factbox content and instead is served directly from +# cache while only a new revision will trigger to re-parse the factbox. +# +# If smwgFactboxUseCache is set false (equals legacy behaviour) then every page +# request will bind the factbox to be re-parsed. +# +# @since 1.9 +# +# @requires $smwgCacheType be set +# @default true +## +$smwgFactboxUseCache = true; +## + +### +# Sets whether or not a cached factbox should be invalidated on an action=purge +# event +# +# If set false the factbox cache will be only reset after a new page revision +# but if set true each purge request (no new page revision) will invalidate +# the factbox cache +# +# @since 1.9 +# +# @requires $smwgCacheType be set +# @default true +## +$smwgFactboxCacheRefreshOnPurge = true; +## \ No newline at end of file diff --git a/includes/Cache.php b/includes/Cache.php new file mode 100644 index 0000000..37701d4 --- /dev/null +++ b/includes/Cache.php @@ -0,0 +1,248 @@ +<?php + +namespace SMW; + +use ObjectCache; +use BagOStuff; + +/** + * Interface handling BagOStuff object instances (APC, ObjectCache etc.) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @since 1.9 + * + * @file + * @ingroup SMW + * + * @author mwjames + */ +interface ICache { + + /** + * Returns invoked cache key + * + * @since 1.9 + * + * @return Title + */ + public function getKey(); + + /** + * Generates a cache key and sets it for further internal cache processing + * + * @since 1.9 + * + * @return Title + */ + public function setKey(); + + /** + * Sets values the be cached for an invoked key + * + * @since 1.9 + */ + public function set( $value ); + + /** + * Returns cache value for an invoked key + * + * @since 1.9 + */ + public function get(); + + /** + * Deletes cache object for an invoked key + * + * @since 1.9 + */ + public function delete(); + + /** + * Returns status of the current invoked cache instance + * + * @since 1.9 + */ + public function isEnabled(); + +} + +/** + * Class that manages access to a BagOStuff cache object + * + * @ingroup SMW + * + * @author mwjames + */ +class Cache implements ICache { + + /** + * Represents the BagOStuff instance + * @var BagOStuff + */ + protected $cache; + + /** + * Represents invoked cache key + * @var string + */ + protected $key = null; + + /** + * Represents custom value for the cache key prefix (see $wgCachePrefix) + * @var string + */ + protected $prefix; + + /** + * Represents the instance status + * @var boolean + */ + protected $cacheEnabled = true; + + /** + * @since 1.9 + * + * @param BagOStuff|null $cache + */ + public function __construct( BagOStuff $cache = null ) { + $this->cache = $cache; + } + + /** + * Convenience factory method that enables to create a BagOStuff instance + * from global settings (cache type, prefix etc.) + * + * @note If a BagOStuff instance is not available setCacheEnabled() is set + * false which prevents us from running into an exception while trying to + * access BagOStuff functions + * + * @since 1.9 + * + * @param ObjectCache $cache + * + * @return ICache + */ + public static function newFromGlobals( $cache = false ) { + $cacheType = $cache ? $cache : $GLOBALS['smwgCacheType']; + $instance = new self( $cacheType ? ObjectCache::getInstance( $cacheType ) : null ); + $instance->prefix = $GLOBALS['wgCachePrefix'] === false ? wfWikiID() : $GLOBALS['wgCachePrefix']; + + if ( !( $instance->getCache() instanceof BagOStuff ) ) { + $instance->setCacheEnabled( false ); + wfDebug( __METHOD__ . " SMW caching was disabled due to setting {$cacheType}" . "\n" ); + } + + return $instance; + } + + /** + * Sets cache key which is used for further internal processing + * + * @since 1.9 + * + * @param varargs + */ + public function setKey( /* ... */ ) { + $this->key = $this->prefix . ':' . 'smw' . ':' . str_replace( ' ', '_', implode( ':', func_get_args() ) ); + return $this; + } + + /** + * Returns invoked cache key + * + * @since 1.9 + * + * @param + */ + public function getKey() { + return $this->key; + } + + /** + * Returns invoked BagOStuff instance + * + * @since 1.9 + * + * @return BagOStuff + */ + public function getCache() { + return $this->cache; + } + + /** + * Sets value to cache + * + * @since 1.9 + * + * @return mixed|false + */ + public function set( $value ) { + return $this->isEnabled() && $this->getKey() ? $this->getCache()->set( $this->getKey(), $value ) : false; + } + + /** + * Returns value from cache + * + * @since 1.9 + * + * @return mixed|false + */ + public function get() { + return $this->isEnabled() && $this->getKey() ? $this->getCache()->get( $this->getKey() ) : false; + } + + /** + * Deletes cache object + * + * @since 1.9 + * + * @return boolean + */ + public function delete() { + return $this->isEnabled() && $this->getKey() ? $this->getCache()->delete( $this->getKey() ) : false; + } + + /** + * Sets availability status of the caching instance + * + * @note The method will assert current availability of the BagOStuff + * instance and return false independently from the parameter invoked (this + * safeguards against overriding availability status of a non-BagOStuff + * instance) + * + * @since 1.9 + * + * @param boolean $cacheEnabled + * + * @return ICache + */ + public function setCacheEnabled( $cacheEnabled ) { + $this->cacheEnabled = $this->getCache() instanceof BagOStuff ? (bool)$cacheEnabled : false; + return $this; + } + + /** + * Returns current status of the invoked cache instance + * + * @since 1.9 + * + * @return boolean + */ + public function isEnabled() { + return $this->cacheEnabled; + } +} diff --git a/includes/Factbox.php b/includes/Factbox.php new file mode 100644 index 0000000..d6b5529 --- /dev/null +++ b/includes/Factbox.php @@ -0,0 +1,389 @@ +<?php + +namespace SMW; + +use ParserOutput; +use IContextSource; +use Title; +use Html; + +use SMWSemanticData; +use SMWInfolink; +use SMWOutputs; +use SMWDIWikiPage; + +/** + * The class in this file provides means of rendering a "Factbox" in articles. + * @file + * @ingroup SMW + * @author Markus Krötzsch + */ + +/** + * Static class for printing semantic data in a "Factbox". + * @ingroup SMW + */ +class Factbox { + + /** + * Represents IParserData + * @var IParserData + */ + protected $parserData; + + /** + * Represents Settings + * @var Settings + */ + protected $settings; + + /** + * Represents IContextSource + * @var IContextSource + */ + protected $context; + + /** + * Represents a boolean value and specifies if content is visible + * @var boolean + */ + protected $isVisible = false; + + /** + * @since 1.9 + * + * @param IParserData $parserData + * @param Settings $settings + * @param IContextSource $context + */ + public function __construct( IParserData $parserData, Settings $settings, IContextSource $context ) { + $this->parserData = $parserData; + $this->settings = $settings; + $this->context = $context; + } + + /** + * Returns wiki text suitable for rendering a Factbox. + * + * @since 1.9 + * + * @return string|null + */ + public function getText() { + return $this->getContent( $this->getMagicWords() ); + } + + /** + * Returns if content is visible + * + * @since 1.9 + * + * @return boolean + */ + public function isVisible() { + return $this->isVisible; + } + + /** + * Returns magic words attached to the ParserOutput object + * + * @since 1.9 + * + * @return string|null + */ + protected function getMagicWords() { + $parserOutput = $this->parserData->getOutput(); + + // Prior MW 1.21 use mSMWMagicWords (see SMW\ParserTextProcessor) + if ( method_exists( $parserOutput, 'getExtensionData' ) ) { + $smwMagicWords = $parserOutput->getExtensionData( 'smwmagicwords' ); + $mws = $smwMagicWords === null ? array() : $smwMagicWords; + } else { + $mws = isset( $parserOutput->mSMWMagicWords ) ? $parserOutput->mSMWMagicWords : array(); + } + + if ( in_array( 'SMW_SHOWFACTBOX', $mws ) ) { + $showfactbox = SMW_FACTBOX_NONEMPTY; + } elseif ( in_array( 'SMW_NOFACTBOX', $mws ) ) { + $showfactbox = SMW_FACTBOX_HIDDEN; + } elseif ( $this->context->getRequest()->getCheck( 'wpPreview' ) ) { + $showfactbox = $this->settings->get( 'smwgShowFactboxEdit' ); + } else { + $showfactbox = $this->settings->get( 'smwgShowFactbox' ); + } + + return $showfactbox; + } + + /** + * Returns required resource modules + * + * @since 1.9 + * + * @return array + */ + protected function getModules() { + return array( + 'ext.smw.style' + ); + } + + /** + * Returns content found in a given ParserOutput object and if the required + * custom data was not available in the given ParserOutput, then semantic + * data are retrieved from the store for a given subject. + * + * The method checks whether the given setting of $showfactbox requires + * displaying the given data at all. + * + * @since 1.9 + * + * @return integer $showFactbox + * + * @return string|null + */ + protected function getContent( $showFactbox = SMW_FACTBOX_NONEMPTY ) { + + if ( $showFactbox === SMW_FACTBOX_HIDDEN ) { + return ''; + } + + $semanticData = $this->parserData->getData(); + + if ( $semanticData === null || $semanticData->stubObject ) { + $semanticData = smwfGetStore()->getSemanticData( $this->parserData->getSubject() ); + } + + if ( $showFactbox === SMW_FACTBOX_SPECIAL && !$semanticData->hasVisibleSpecialProperties() ) { + // show only if there are special properties + return ''; + } else if ( $showFactbox === SMW_FACTBOX_NONEMPTY && !$semanticData->hasVisibleProperties() ) { + // show only if non-empty + return ''; + } + + $content = $this->getTable( $semanticData ); + + if ( $content !== '' ) { + $this->parserData->getOutput()->addModules( $this->getModules() ); + $this->parserData->updateOutput(); + $this->isVisible = true; + } + + return $content; + } + + /** + * Returns a formatted factbox table + * + * @since 1.9 + * + * @param SMWSemanticData $semanticData + * + * @return string|null + */ + protected function getTable( SMWSemanticData $semanticData ) { + wfProfileIn( __METHOD__ ); + $text = ''; + + if ( wfRunHooks( 'smwShowFactbox', array( &$text, $semanticData ) ) ) { + $tableFormatter = new TableFormatter(); + $this->getTableContent( $semanticData, $tableFormatter ); + + $text .= Html::rawElement( 'div', + array( 'class' => 'smwfact' ), + $tableFormatter->getHeader() . + $tableFormatter->getTable( array( 'class' => 'smwfacttable' ) ) + ); + } + + wfProfileOut( __METHOD__ ); + return $text; + } + + /** + * Rendering a table for a given SMWSemanticData object that holds all + * relevant data. + * + * @since 1.9 + * + * @return string + */ + protected function getTableContent( SMWSemanticData $semanticData, TableFormatter $tableFormatter ) { + $attributes = array(); + + $subject = $semanticData->getSubject(); + $dataValue = DataValueFactory::newDataItemValue( $subject, null ); + + $browselink = SMWInfolink::newBrowsingLink( + $dataValue->getText(), + $dataValue->getWikiValue(), + 'swmfactboxheadbrowse' + ); + + $tableFormatter->addHeaderItem( 'span', + $this->context->msg( 'smw_factbox_head', $browselink->getWikiText() )->inContentLanguage()->text(), + array( 'class' => 'smwfactboxhead' ) + ); + + $rdflink = SMWInfolink::newInternalLink( + $this->context->msg( 'smw_viewasrdf' )->inContentLanguage()->text(), + $subject->getTitle()->getPageLanguage()->getNsText( NS_SPECIAL ) . ':ExportRDF/' . $dataValue->getWikiValue(), + 'rdflink' + ); + + $tableFormatter->addHeaderItem( 'span', + $rdflink->getWikiText(), + array( 'class' => 'smwrdflink' ) + ); + + foreach ( $semanticData->getProperties() as $propertyDi ) { + $propertyDv = DataValueFactory::newDataItemValue( $propertyDi, null ); + $rowId = $propertyDv->getWikiValue(); + + if ( !$propertyDi->isShown() ) { + // showing this is not desired, hide + continue; + } elseif ( $propertyDi->isUserDefined() ) { + // User defined property (@note the preg_replace is a slight + // hack to ensure that the left column does not get too narrow) + $propertyDv->setCaption( preg_replace( '/[ ]/u', ' ', $propertyDv->getWikiValue(), 2 ) ); + $attributes['property'] = array( 'class' => 'smwpropname' ); + $attributes['values'] = array( 'class' => 'smwprops' ); + } elseif ( $propertyDv->isVisible() ) { + // Predefined property + $attributes['property'] = array( 'class' => 'smwspecname' ); + $attributes['values'] = array( 'class' => 'smwspecs' ); + } else { + // predefined, internal property + continue; + } + + $valuesHtml = array(); + foreach ( $semanticData->getPropertyValues( $propertyDi ) as $dataItem ) { + $dataValue = DataValueFactory::newDataItemValue( $dataItem, $propertyDi ); + + if ( $dataValue->isValid() ) { + $valuesHtml[] = $dataValue->getLongWikiText( true ) . + $dataValue->getInfolinkText( SMW_OUTPUT_WIKI ); + } + } + + $tableFormatter->addTableRow( + $tableFormatter->addTableCell( $propertyDv->getShortWikiText( true ), $attributes['property'] ) . + $tableFormatter->addTableCell( $this->context->getLanguage()->listToText( $valuesHtml ), $attributes['values'] ) + ); + } + } +} + +class TableFormatter { + + /** + * Represents header items + * @var array + */ + protected $headerItems = array(); + + /** + * Represents table rows + * @var array + */ + protected $tableRows = array(); + + /** + * Adds a header item to an internal array + * + * @since 1.9 + * + * @param string $element + * @param string $content + * @param array $attributes + * + * @return string + */ + public function addHeaderItem( $element, $content = '', $attributes = array() ) { + $this->headerItems[] = Html::rawElement( $element, $attributes, $content ); + } + + /** + * Adds a table row to an internal array + * + * @since 1.9 + * + * @param string $content + * @param array $attributes + * + * @return string + */ + public function addTableRow( $content = '', $attributes = array() ) { + $alternate = count( $this->tableRows ) % 2 == 0 ? 'row-odd' : 'row-even'; + + if ( isset( $attributes['class'] ) ) { + $attributes['class'] += ' ' . $alternate; + } else { + $attributes['class'] = $alternate; + } + + $this->tableRows[] = Html::rawElement( 'tr', $attributes , $content ); + } + + /** + * Returns a table cell + * + * @since 1.9 + * + * @param string $content + * @param array $attributes + * + * @return string + */ + public function addTableCell( $content = '', $attributes = array() ) { + return Html::rawElement( 'td', $attributes, $content ); + } + + /** + * Returns concatenated header items + * + * @since 1.9 + * + * @return string + */ + public function getHeader() { + return implode( '', $this->headerItems ); + } + + /** + * Returns concatenated table rows + * + * @since 1.9 + * + * @return string + */ + public function getTablesRows() { + return implode( '', $this->tableRows ); + } + + /** + * Returns a formatted table + * + * @since 1.9 + * + * @param array $attributes + * + * @return string + */ + public function getTable( $attributes = array() ) { + return Html::rawElement( 'table', + $attributes, + $this->getTablesRows() + ); + } +} + +/** + * SMWResultPrinter + * + * @deprecated since SMW 1.9 + */ +class_alias( 'SMW\Factbox', 'SMWFactbox' ); diff --git a/includes/SMW_Factbox.php b/includes/SMW_Factbox.php deleted file mode 100644 index cb4d03b..0000000 --- a/includes/SMW_Factbox.php +++ /dev/null @@ -1,218 +0,0 @@ -<?php -/** - * The class in this file provides means of rendering a "Factbox" in articles. - * @file - * @ingroup SMW - * @author Markus Krötzsch - */ - -/** - * Static class for printing semantic data in a "Factbox". - * @ingroup SMW - */ -class SMWFactbox { - - /** - * This function creates wiki text suitable for rendering a Factbox for a given - * SMWSemanticData object that holds all relevant data. It also checks whether the - * given setting of $showfactbox requires displaying the given data at all. - * - * @param SMWSemanticData $semdata - * @param boolean $showfactbox - * - * @return string - */ - static public function getFactboxText( SMWSemanticData $semdata, $showfactbox = SMW_FACTBOX_NONEMPTY ) { - global $wgContLang; - - wfProfileIn( 'SMWFactbox::printFactbox (SMW)' ); - - switch ( $showfactbox ) { - case SMW_FACTBOX_HIDDEN: // never show - wfProfileOut( 'SMWFactbox::printFactbox (SMW)' ); - return ''; - case SMW_FACTBOX_SPECIAL: // show only if there are special properties - if ( !$semdata->hasVisibleSpecialProperties() ) { - wfProfileOut( 'SMWFactbox::printFactbox (SMW)' ); - return ''; - } - break; - case SMW_FACTBOX_NONEMPTY: // show only if non-empty - if ( !$semdata->hasVisibleProperties() ) { - wfProfileOut( 'SMWFactbox::printFactbox (SMW)' ); - return ''; - } - break; - // case SMW_FACTBOX_SHOWN: // just show ... - } - - // actually build the Factbox text: - $text = ''; - if ( wfRunHooks( 'smwShowFactbox', array( &$text, $semdata ) ) ) { - $subjectDv = SMWDataValueFactory::newDataItemValue( $semdata->getSubject(), null ); - - SMWOutputs::requireResource( 'ext.smw.style' ); - - $rdflink = SMWInfolink::newInternalLink( - wfMessage( 'smw_viewasrdf' )->inContentLanguage()->text(), - $wgContLang->getNsText( NS_SPECIAL ) . ':ExportRDF/' . - $subjectDv->getWikiValue(), - 'rdflink' - ); - - $browselink = SMWInfolink::newBrowsingLink( - $subjectDv->getText(), - $subjectDv->getWikiValue(), - 'swmfactboxheadbrowse' - ); - - $text .= '<div class="smwfact">' . - '<span class="smwfactboxhead">' . - wfMessage( 'smw_factbox_head', $browselink->getWikiText() )->inContentLanguage()->text() . '</span>' . - '<span class="smwrdflink">' . $rdflink->getWikiText() . '</span>' . - '<table class="smwfacttable">' . "\n"; - - foreach ( $semdata->getProperties() as $propertyDi ) { - $propertyDv = SMWDataValueFactory::newDataItemValue( $propertyDi, null ); - if ( !$propertyDi->isShown() ) { // showing this is not desired, hide - continue; - } elseif ( $propertyDi->isUserDefined() ) { // user defined property - $propertyDv->setCaption( preg_replace( '/[ ]/u', ' ', $propertyDv->getWikiValue(), 2 ) ); - /// NOTE: the preg_replace is a slight hack to ensure that the left column does not get too narrow - $text .= '<tr><td class="smwpropname">' . $propertyDv->getShortWikiText( true ) . '</td><td class="smwprops">'; - } elseif ( $propertyDv->isVisible() ) { // predefined property - $text .= '<tr><td class="smwspecname">' . $propertyDv->getShortWikiText( true ) . '</td><td class="smwspecs">'; - } else { // predefined, internal property - continue; - } - - $propvalues = $semdata->getPropertyValues( $propertyDi ); - - $valuesHtml = array(); - - foreach ( $propvalues as $dataItem ) { - $dataValue = SMWDataValueFactory::newDataItemValue( $dataItem, $propertyDi ); - - if ( $dataValue->isValid() ) { - $valuesHtml[] = $dataValue->getLongWikiText( true ) . - $dataValue->getInfolinkText( SMW_OUTPUT_WIKI ); - } - } - - $text .= $GLOBALS['wgLang']->listToText( $valuesHtml ); - - $text .= '</td></tr>'; - } - - $text .= '</table></div>'; - } - - wfProfileOut( 'SMWFactbox::printFactbox (SMW)' ); - return $text; - } - - /** - * This function creates wiki text suitable for rendering a Factbox based on the - * information found in a given ParserOutput object. If the required custom data - * is not found in the given ParserOutput, then semantic data for the provided Title - * object is retreived from the store. - * - * @param ParserOutput $parseroutput - * @param Title $title - * - * @return string - */ - static public function getFactboxTextFromOutput( ParserOutput $parseroutput, Title $title ) { - global $wgRequest, $smwgShowFactboxEdit, $smwgShowFactbox; - - // Prior MW 1.21 use mSMWMagicWords (see SMW\ParserTextProcessor) - if ( method_exists( $parseroutput, 'getExtensionData' ) ) { - $smwMagicWords = $parseroutput->getExtensionData( 'smwmagicwords' ); - $mws = $smwMagicWords === null ? array() : $smwMagicWords; - } else { - $mws = isset( $parseroutput->mSMWMagicWords ) ? $parseroutput->mSMWMagicWords : array(); - } - - if ( in_array( 'SMW_SHOWFACTBOX', $mws ) ) { - $showfactbox = SMW_FACTBOX_NONEMPTY; - } elseif ( in_array( 'SMW_NOFACTBOX', $mws ) ) { - $showfactbox = SMW_FACTBOX_HIDDEN; - } elseif ( $wgRequest->getCheck( 'wpPreview' ) ) { - $showfactbox = $smwgShowFactboxEdit; - } else { - $showfactbox = $smwgShowFactbox; - } - - if ( $showfactbox == SMW_FACTBOX_HIDDEN ) { // use shortcut - return ''; - } - - // Deal with complete dataset only if needed: - $smwData = SMWParseData::getSMWDataFromParserOutput( $parseroutput ); - - if ( $smwData === null || $smwData->stubObject ) { - $smwData = smwfGetStore()->getSemanticData( SMWDIWikiPage::newFromTitle( $title ) ); - } - - return SMWFactbox::getFactboxText( $smwData, $showfactbox ); - } - - /** - * This hook copies SMW's custom data from the given ParserOutput object to - * the given OutputPage object, since otherwise it is not possible to access - * it later on to build a Factbox. - * - * @param OutputPage $outputpage - * @param ParserOutput $parseroutput - * - * @return true - */ - static public function onOutputPageParserOutput( OutputPage $outputpage, ParserOutput $parseroutput ) { - global $wgParser; - $factbox = SMWFactbox::getFactboxTextFromOutput( $parseroutput, $outputpage->getTitle() ); - - if ( $factbox !== '' ) { - $popts = new ParserOptions(); - $po = $wgParser->parse( $factbox, $outputpage->getTitle(), $popts ); - $outputpage->mSMWFactboxText = $po->getText(); - // do not forget to grab the outputs header items - SMWOutputs::requireFromParserOutput( $po ); - SMWOutputs::commitToOutputPage( $outputpage ); - } // else: nothing shown, don't even set any text - - return true; - } - - /** - * This hook is used for inserting the Factbox text directly after the wiki page. - * - * @param OutputPage $outputpage - * @param string $text - * - * @return true - */ - static public function onOutputPageBeforeHTML( OutputPage $outputpage, &$text ) { - if ( isset( $outputpage->mSMWFactboxText ) ) { - $text .= $outputpage->mSMWFactboxText; - } - return true; - } - - /** - * This hook is used for inserting the Factbox text after the article contents (including - * categories). - * - * @param string $data - * @param Skin|null $skin - * - * @return true - */ - static public function onSkinAfterContent( &$data, Skin $skin = null ) { - global $wgOut; - if ( isset( $wgOut->mSMWFactboxText ) ) { - $data .= $wgOut->mSMWFactboxText; - } - return true; - } - -} diff --git a/includes/Settings.php b/includes/Settings.php index c000102..16f7aad 100644 --- a/includes/Settings.php +++ b/includes/Settings.php @@ -150,6 +150,8 @@ 'smwgNamespace' => $GLOBALS['smwgNamespace'], 'smwgMasterStore' => $GLOBALS['smwgMasterStore'], 'smwgIQRunningNumber' => $GLOBALS['smwgIQRunningNumber'], + 'smwgFactboxUseCache' => $GLOBALS['smwgFactboxUseCache'], + 'smwgFactboxCacheRefreshOnPurge' => $GLOBALS['smwgFactboxCacheRefreshOnPurge'] ); return self::newFromArray( $settings ); diff --git a/includes/Setup.php b/includes/Setup.php index 693f0e8..56b1ef9 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -75,7 +75,6 @@ // Parsing [[link::syntax]] and resolves property annotations $wgHooks['InternalParseBeforeLinks'][] = 'SMWHooks::onInternalParseBeforeLinks'; - $wgHooks['OutputPageParserOutput'][] = 'SMWFactbox::onOutputPageParserOutput'; // copy some data for later Factbox display $wgHooks['ArticleFromTitle'][] = 'SMWHooks::onArticleFromTitle'; // special implementations for property/type articles $wgHooks['ParserFirstCallInit'][] = 'SMWHooks::onParserFirstCallInit'; @@ -100,7 +99,11 @@ $wgHooks['SkinTemplateToolboxEnd'][] = 'SMWHooks::showBrowseLink'; } - $wgHooks['SkinAfterContent'][] = 'SMWFactbox::onSkinAfterContent'; // draw Factbox below categories + // Draw Factbox below categories + $wgHooks['SkinAfterContent'][] = 'SMWHooks::onSkinAfterContent'; + + // Copy some data for later Factbox display + $wgHooks['OutputPageParserOutput'][] = 'SMWHooks::onOutputPageParserOutput'; $wgHooks['ExtensionTypes'][] = 'SMWHooks::addSemanticExtensionType'; } @@ -120,7 +123,9 @@ $wgAutoloadClasses['SMW\ParameterInput'] = $incDir . 'ParameterInput.php'; $wgAutoloadClasses['SMW\MessageReporter'] = $incDir . 'MessageReporter.php'; $wgAutoloadClasses['SMW\ObservableMessageReporter'] = $incDir . 'MessageReporter.php'; - $wgAutoloadClasses['SMWFactbox'] = $incDir . 'SMW_Factbox.php'; + $wgAutoloadClasses['SMW\Factbox'] = $incDir . 'Factbox.php'; + $wgAutoloadClasses['SMW\TableFormatter'] = $incDir . 'Factbox.php'; + $wgAutoloadClasses['SMWFactbox'] = $incDir . 'Factbox.php'; $wgAutoloadClasses['SMWInfolink'] = $incDir . 'SMW_Infolink.php'; $wgAutoloadClasses['SMWOutputs'] = $incDir . 'SMW_Outputs.php'; $wgAutoloadClasses['SMW\ParserTextProcessor'] = $incDir . 'ParserTextProcessor.php'; @@ -141,6 +146,8 @@ $wgAutoloadClasses['SMW\ParserParameterFormatter'] = $incDir . 'ParserParameterFormatter.php'; $wgAutoloadClasses['SMW\Settings'] = $incDir . 'Settings.php'; + $wgAutoloadClasses['SMW\ICache'] = $incDir . 'Cache.php'; + $wgAutoloadClasses['SMW\Cache'] = $incDir . 'Cache.php'; // Article pages $apDir = $smwgIP . 'includes/articlepages/'; diff --git a/tests/phpunit/includes/FactboxTest.php b/tests/phpunit/includes/FactboxTest.php index 82867bd..26e1c14 100644 --- a/tests/phpunit/includes/FactboxTest.php +++ b/tests/phpunit/includes/FactboxTest.php @@ -2,13 +2,15 @@ namespace SMW\Test; -use SMWFactbox; +use SMW\Factbox; use SMW\ParserData; use SMW\Settings; use SMW\ParserTextProcessor; use Title; +use ReflectionClass; use ParserOutput; +use RequestContext; /** * Tests for the SMWFactbox class @@ -65,6 +67,7 @@ ' __NOFACTBOX__ ', array( 'magicWords' => array( 'SMW_NOFACTBOX' ), + 'constants' => SMW_FACTBOX_HIDDEN, 'textOutput' => '' ) ), @@ -77,6 +80,7 @@ ' __SHOWFACTBOX__', array( 'magicWords' => array( 'SMW_SHOWFACTBOX' ), + 'constants' => SMW_FACTBOX_NONEMPTY, 'textOutput' => 'smwfactboxhead' // lazy check because we use assertContains ) ), @@ -179,7 +183,7 @@ } /** - * @test SMWFactbox::getFactboxTextFromOutput + * @test Factbox::getMagicWords * @dataProvider getTextProvider * * @since 1.9 @@ -187,22 +191,32 @@ * @param $text * @param $expected */ - public function testGetFactboxTextFromOutput( $text, array $expected ) { + public function testGetMagicWords( $text, array $expected ) { $parserOutput = $this->getParserOutput(); $title = $this->getTitle(); - $textProcessor = $this->getTextProcessor( $title, $parserOutput ); // Use the text processor to add text sample + $textProcessor = $this->getTextProcessor( $title, $parserOutput ); $textProcessor->parse( $text ); - $result = SMWFactbox::getFactboxTextFromOutput( $parserOutput, $title ); - $this->assertInternalType( 'string', $result ); + $settings = Settings::newFromArray( array( + 'smwgShowFactboxEdit' => true, + 'smwgShowFactbox' => true + ) ); - // Doing a lazy sanity check on the result - if ( $expected['textOutput'] !== '' ) { - $this->assertContains( $expected['textOutput'], $result ); - } else { - $this->assertEmpty( $result ); - } + $reflection = new ReflectionClass( "SMW\Factbox" ); + $method = $reflection->getMethod( "getMagicWords" ); + $method->setAccessible( true ); + $context = RequestContext::getMain(); + + $instance = new Factbox( + $this->getParserData( $title, $parserOutput ), + $settings, + $context + ); + + $result = $method->invoke( $instance ); + $this->assertInternalType( 'integer', $result ); + $this->assertEquals( $expected['constants'], $result ); } } -- To view, visit https://gerrit.wikimedia.org/r/61171 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I5eb8a3e648aa4a4610882776cb6c469a48f88ffc Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/SemanticMediaWiki Gerrit-Branch: master Gerrit-Owner: Mwjames <jamesin.hongkon...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits