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', '&#160;', $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', '&#160;', $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

Reply via email to