Matthias Mullie has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/102674


Change subject: (bug 58645) Merge RecentChanges & Contributions' formatters 
extend from 1 same base class
......................................................................

(bug 58645) Merge RecentChanges & Contributions' formatters extend from 1 same 
base class

Bug: 58645
Change-Id: I8392302e016673f02bf495fd6ae156a9334262ab
---
M Flow.php
M container.php
M includes/Contributions/Formatter.php
A includes/Formatter.php
M includes/RecentChanges/Formatter.php
5 files changed, 483 insertions(+), 589 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow 
refs/changes/74/102674/1

diff --git a/Flow.php b/Flow.php
index 7c33908..3d85ca0 100755
--- a/Flow.php
+++ b/Flow.php
@@ -121,6 +121,7 @@
 $wgAutoloadClasses['Flow\Data\HeaderRecentChanges'] = $dir . 
'includes/Data/RecentChanges.php';
 $wgAutoloadClasses['Flow\Data\Merger'] = $dir . 
'includes/Data/RevisionStorage.php';
 $wgAutoloadClasses['Flow\Data\RawSql'] = $dir . 
'includes/Data/ObjectManager.php';
+$wgAutoloadClasses['Flow\AbstractFormatter'] = $dir . 'includes/Formatter.php';
 $wgAutoloadClasses['Flow\RecentChanges\Formatter'] = $dir . 
'includes/RecentChanges/Formatter.php';
 $wgAutoloadClasses['Flow\Log\Logger'] = $dir . 'includes/Log/Logger.php';
 $wgAutoloadClasses['Flow\Log\Formatter'] = $dir . 'includes/Log/Formatter.php';
diff --git a/container.php b/container.php
index ed299b3..cddabdb 100644
--- a/container.php
+++ b/container.php
@@ -385,13 +385,11 @@
 } );
 
 $c['recentchanges.formatter'] = $c->share( function( $c ) {
-       global $wgLang;
        return new Flow\RecentChanges\Formatter(
                $c['storage'],
                $c['factory.loader.workflow'],
                $c['flow_actions'],
-               $c['templating'],
-               $wgLang
+               $c['templating']
        );
 } );
 
diff --git a/includes/Contributions/Formatter.php 
b/includes/Contributions/Formatter.php
index 7db6fe3..4e675f9 100644
--- a/includes/Contributions/Formatter.php
+++ b/includes/Contributions/Formatter.php
@@ -2,69 +2,11 @@
 
 namespace Flow\Contributions;
 
+use Flow\AbstractFormatter;
 use ContribsPager;
-use Flow\Model\AbstractRevision;
-use Flow\Model\Workflow;
-use Flow\Block\AbstractBlock;
-use Flow\Data\ManagerGroup;
-use Flow\FlowActions;
-use Flow\Model\UUID;
-use Flow\Templating;
-use Flow\UrlGenerator;
-use Flow\WorkflowLoaderFactory;
-use Language;
 use Html;
-use Title;
-use User;
-use ChangesList;
 
-class Formatter {
-       /**
-        * @var ManagerGroup
-        */
-       protected $storage;
-
-       /**
-        * @var WorkflowLoaderFactory
-        */
-       protected $workflowLoaderFactory;
-
-       /**
-        * @var FlowActions
-        */
-       protected $actions;
-
-       /**
-        * @var Templating
-        */
-       protected $templating;
-
-       /**
-        * @var UrlGenerator;
-        */
-       protected $urlGenerator;
-
-       /**
-        * @var Language
-        */
-       protected $lang;
-
-       /**
-        * @param ManagerGroup $storage
-        * @param WorkflowLoaderFactory $workflowLoaderFactory
-        * @param FlowActions $actions
-        * @param Templating $templating
-        * @param Language $lang
-        */
-       public function __construct( ManagerGroup $storage, 
WorkflowLoaderFactory $workflowLoaderFactory, FlowActions $actions, Templating 
$templating ) {
-               $this->actions = $actions;
-               $this->storage = $storage;
-               $this->workflowLoaderFactory = $workflowLoaderFactory;
-               $this->templating = $templating;
-
-               $this->urlGenerator = $this->templating->getUrlGenerator();
-       }
-
+class Formatter extends AbstractFormatter {
        /**
         * @param ContribsPager $pager
         * @param stdClass $row
@@ -98,12 +40,12 @@
                // Format timestamp: add link
                $formattedTime = $dateFormats['timeAndDate'];
                if ( $links ) {
-                       list( $url, $message ) = $links[count( $links ) - 1];
+                       list( $url, $text ) = $links[count( $links ) - 1];
                        $formattedTime = Html::element(
                                'a',
                                array(
                                        'href' => $url,
-                                       'title' => $message->text()
+                                       'title' => $text
                                ),
                                $formattedTime
                        );
@@ -118,14 +60,14 @@
 
                // Format links
                foreach ( $links as &$link ) {
-                       list( $url, $message ) = $link;
+                       list( $url, $text ) = $link;
                        $link = Html::element(
                                'a',
                                array(
                                        'href' => $url,
-                                       'title' => $message->text()
+                                       'title' => $text
                                ),
-                               $message->text()
+                               $text
                        );
                }
                $linksContent = $lang->pipeList( $links );
@@ -139,224 +81,5 @@
                        $linksContent . ' . . ' .
                        $charDiff . ' . . ' .
                        $description;
-       }
-
-       protected function buildActionLinks( Title $title, $action, UUID 
$workflowId, UUID $postId = null ) {
-               $links = array();
-               switch( $action ) {
-                       case 'reply':
-                               $links[] = $this->topicLink( $title, 
$workflowId );
-                               break;
-
-                       case 'new-post': // fall through
-                       case 'edit-post':
-                               $links[] = $this->topicLink( $title, 
$workflowId );
-                               $links[] = $this->postLink( $title, 
$workflowId, $postId );
-                               break;
-
-                       case 'suppress-post':
-                       case 'delete-post':
-                       case 'hide-post':
-                       case 'restore-post':
-                               $links[] = $this->topicLink( $title, 
$workflowId );
-                               $links[] = $this->postHistoryLink( $title, 
$workflowId, $postId );
-                               break;
-
-                       case 'suppress-topic':
-                       case 'delete-topic':
-                       case 'hide-topic':
-                       case 'restore-topic':
-                               $links[] = $this->topicLink( $title, 
$workflowId );
-                               $links[] = $this->topicHistoryLink( $title, 
$workflowId );
-                               break;
-
-                       case 'edit-title':
-                               $links[] = $this->topicLink( $title, 
$workflowId );
-                               // This links to the history of the topic title
-                               $links[] = $this->postHistoryLink( $title, 
$workflowId, $postId );
-                               break;
-
-                       case 'create-header': // fall through
-                       case 'edit-header':
-                               //$links[] = $this->workflowLink( $title, 
$workflowId );
-                               break;
-
-                       case null:
-                               wfWarn( __METHOD__ . ': Flow change has null 
change type' );
-                               return false;
-
-                       default:
-                               wfWarn( __METHOD__ . ': Unknown Flow action: ' 
. $action );
-                               return false;
-               }
-
-               return $links;
-       }
-
-       /**
-        * @param AbstractRevision $revision
-        * @param User $user
-        * @param Lang $lang
-        * @return array Contains [timeAndDate, date, time]
-        */
-       protected function getDateFormats( AbstractRevision $revision, User 
$user, Language $lang ) {
-               // date & time
-               $timestamp = 
$revision->getRevisionId()->getTimestampObj()->getTimestamp( TS_MW );
-               $dateFormats = array();
-               $dateFormats['timeAndDate'] = $lang->userTimeAndDate( 
$timestamp, $user );
-               $dateFormats['date'] = $lang->userDate( $timestamp, $user );
-               $dateFormats['time'] = $lang->userTime( $timestamp, $user );
-
-               return $dateFormats;
-       }
-
-       public function topicHistoryLink( Title $title, UUID $workflowId ) {
-               return array(
-                       $this->urlGenerator->buildUrl(
-                               $title,
-                               'topic-history',
-                               array( 'workflow' => $workflowId->getHex() )
-                       ),
-                       wfMessage( 'flow-link-history' )
-               );
-       }
-
-       public function postHistoryLink( Title $title, UUID $workflowId, UUID 
$postId ) {
-               return array(
-                       $this->urlGenerator->buildUrl(
-                               $title,
-                               'post-history',
-                               array(
-                                       'workflow' => $workflowId->getHex(),
-                                       'topic' => array( 'postId' => 
$postId->getHex() ),
-                               )
-                       ),
-                       wfMessage( 'flow-link-history' )
-               );
-       }
-
-       public function topicLink( Title $title, UUID $workflowId ) {
-               return array(
-                       $this->urlGenerator->buildUrl(
-                               $title,
-                               'view',
-                               array( 'workflow' => $workflowId->getHex() )
-                       ),
-                       wfMessage( 'flow-link-topic' )
-               );
-       }
-
-       public function postLink( Title $title, UUID $workflowId, UUID $postId 
) {
-               return array(
-                       $this->urlGenerator->buildUrl(
-                               $title,
-                               'view',
-                               array(
-                                       'workflow' => $workflowId->getHex(),
-                                       'topic' => array( 'postId' => 
$postId->getHex() ),
-                               )
-                       ),
-                       wfMessage( 'flow-link-post' )
-               );
-       }
-
-       protected function workflowLink( Title $title, UUID $workflowId ) {
-               list( $linkTitle, $query ) = $this->urlGenerator->buildUrlData(
-                       $title,
-                       'view'
-               );
-
-               return array(
-                       $linkTitle->getFullUrl( $query ),
-                       $linkTitle->getPrefixedText()
-               );
-       }
-
-       /**
-        * Build textual description for Flow's Contributions entries. These 
piggy-
-        * back on the i18n messages also used for Flow history, as defined in
-        * FlowActions.
-        *
-        * @param Workflow $workflow
-        * @param AbstractBlock $block
-        * @param AbstractRevision $revision
-        * @param User $user
-        * @return string
-        */
-       public function getActionDescription( Workflow $workflow, AbstractBlock 
$block, AbstractRevision $revision, User $user ) {
-               // Build description message, piggybacking on history i18n
-               $changeType = $revision->getChangeType();
-               $msg = $this->actions->getValue( $changeType, 'history', 
'i18n-message' );
-               $params = $this->actions->getValue( $changeType, 'history', 
'i18n-params' );
-               $message = $this->buildMessage( $msg, (array) $params, array(
-                       $revision,
-                       $this->templating,
-                       $user,
-                       $block
-               ) )->parse();
-
-               return \Html::rawElement(
-                       'span',
-                       array( 'class' => 'plainlinks' ),
-                       $message
-               );
-       }
-
-       /**
-        * @param AbstractRevision $revision
-        * @return string|bool Chardiff or false on failure
-        */
-       protected function getCharDiff( AbstractRevision $revision, 
AbstractRevision $previousRevision = null ) {
-               $previousContent = '';
-
-               if ( $previousRevision ) {
-                       $previousContent = $previousRevision->getContentRaw();
-               }
-
-               return ChangesList::showCharacterDifference( strlen( 
$previousContent ), strlen( $revision->getContentRaw() ) );
-       }
-
-       /**
-        * @param Title $title
-        * @param UUID $workflowId
-        * @param string $name Block name (e.g. "topic", "header")
-        * @return AbstractBlock|bool Requested block or false on failure
-        */
-       protected function loadBlock( Title $title, UUID $workflowId, $name ) {
-               $loader = $this->workflowLoaderFactory
-                       ->createWorkflowLoader( $title, $workflowId );
-               $blocks = $loader->createBlocks();
-
-               if ( !isset( $blocks[$name] ) ) {
-                       wfWarn( __METHOD__ . ': Could not load block ' . $name 
. ' for workflow ' . $workflowId->getHex() );
-                       return false;
-               }
-
-               return $blocks[$name];
-       }
-
-       /**
-        * Returns i18n message for $msg; piggybacking on History i18n.
-        *
-        * Complex parameters can be injected in the i18n messages. Anything in
-        * $params will be call_user_func'ed, with these given $arguments.
-        * Those results will be used as message parameters.
-        *
-        * Note: return array( 'raw' => $value ) or array( 'num' => $value ) for
-        * raw or numeric parameter input.
-        *
-        * @param string $msg i18n key
-        * @param array[optional] $params Callbacks for parameters
-        * @param array[optional] $arguments Arguments for the callbacks
-        * @return Message
-        */
-       protected function buildMessage( $msg, array $params = array(), array 
$arguments = array() ) {
-               foreach ( $params as &$param ) {
-                       if ( is_callable( $param ) ) {
-                               $param = call_user_func_array( $param, 
$arguments );
-                       }
-               }
-
-               return wfMessage( $msg, $params );
        }
 }
diff --git a/includes/Formatter.php b/includes/Formatter.php
new file mode 100644
index 0000000..24b2694
--- /dev/null
+++ b/includes/Formatter.php
@@ -0,0 +1,411 @@
+<?php
+
+namespace Flow;
+
+use Flow\Model\AbstractRevision;
+use Flow\Model\Workflow;
+use Flow\Block\AbstractBlock;
+use Flow\Data\ManagerGroup;
+use Flow\Model\UUID;
+use Language;
+use Html;
+use Title;
+use User;
+use ChangesList;
+
+/**
+ * This is a "utility" class that might come in useful to generate to generate
+ * some output per Flow entry, e.g. for RecentChanges, Contributions, ...
+ * These share a lot of common characteristics (like displaying a date, links 
to
+ * the posts, some description of the action, ...)
+ *
+ * Just extend from this class to use these common util methods, and make sure
+ * to pass the correct parameters to these methods. Basically, you'll need to
+ * create a new method that'll accept the objects for your specific
+ * implementation (like ChangesList & RecentChange objects for RecentChanges, 
or
+ * ContribsPager and a DB row for Contributions). From those rows, you should 
be
+ * able to derive the objects needed to pass to these utility functions (mainly
+ * Workflow, AbstractRevision, Title, User and Language objects) and return the
+ * output.
+ *
+ * For implementation examples, check Flow\RecentChanges\Formatter or
+ * Flow\Contributions\Formatter.
+ */
+abstract class AbstractFormatter {
+       /**
+        * @var ManagerGroup
+        */
+       protected $storage;
+
+       /**
+        * @var WorkflowLoaderFactory
+        */
+       protected $workflowLoaderFactory;
+
+       /**
+        * @var FlowActions
+        */
+       protected $actions;
+
+       /**
+        * @var Templating
+        */
+       protected $templating;
+
+       /**
+        * @var UrlGenerator;
+        */
+       protected $urlGenerator;
+
+       /**
+        * @var Language
+        */
+       protected $lang;
+
+       /**
+        * @var array Array of Workflow objects
+        */
+       protected $workflows = array();
+
+       /**
+        * @var array Array of AbstractRevision objects
+        */
+       protected $revisions = array();
+
+       /**
+        * @param ManagerGroup $storage
+        * @param WorkflowLoaderFactory $workflowLoaderFactory
+        * @param FlowActions $actions
+        * @param Templating $templating
+        * @param Language $lang
+        */
+       public function __construct( ManagerGroup $storage, 
WorkflowLoaderFactory $workflowLoaderFactory, FlowActions $actions, Templating 
$templating ) {
+               $this->actions = $actions;
+               $this->storage = $storage;
+               $this->workflowLoaderFactory = $workflowLoaderFactory;
+               $this->templating = $templating;
+
+               $this->urlGenerator = $this->templating->getUrlGenerator();
+       }
+
+       protected function buildActionLinks( Title $title, $action, UUID 
$workflowId, UUID $postId = null ) {
+               $links = array();
+               switch( $action ) {
+                       case 'reply':
+                               $links[] = $this->topicLink( $title, 
$workflowId );
+                               break;
+
+                       case 'new-post': // fall through
+                       case 'edit-post':
+                               $links[] = $this->topicLink( $title, 
$workflowId );
+                               $links[] = $this->postLink( $title, 
$workflowId, $postId );
+                               break;
+
+                       case 'suppress-post':
+                       case 'delete-post':
+                       case 'hide-post':
+                       case 'restore-post':
+                               $links[] = $this->topicLink( $title, 
$workflowId );
+                               $links[] = $this->postHistoryLink( $title, 
$workflowId, $postId );
+                               break;
+
+                       case 'suppress-topic':
+                       case 'delete-topic':
+                       case 'hide-topic':
+                       case 'restore-topic':
+                               $links[] = $this->topicLink( $title, 
$workflowId );
+                               $links[] = $this->topicHistoryLink( $title, 
$workflowId );
+                               break;
+
+                       case 'edit-title':
+                               $links[] = $this->topicLink( $title, 
$workflowId );
+                               // This links to the history of the topic title
+                               $links[] = $this->postHistoryLink( $title, 
$workflowId, $postId );
+                               break;
+
+                       case 'create-header': // fall through
+                       case 'edit-header':
+                               //$links[] = $this->workflowLink( $title, 
$workflowId );
+                               break;
+
+                       case null:
+                               wfWarn( __METHOD__ . ': Flow change has null 
change type' );
+                               return false;
+
+                       default:
+                               wfWarn( __METHOD__ . ': Unknown Flow action: ' 
. $action );
+                               return false;
+               }
+
+               return $links;
+       }
+
+       /**
+        * @param AbstractRevision $revision
+        * @param User $user
+        * @param Lang $lang
+        * @return array Contains [timeAndDate, date, time]
+        */
+       protected function getDateFormats( AbstractRevision $revision, User 
$user, Language $lang ) {
+               // date & time
+               $timestamp = 
$revision->getRevisionId()->getTimestampObj()->getTimestamp( TS_MW );
+               $dateFormats = array();
+               $dateFormats['timeAndDate'] = $lang->userTimeAndDate( 
$timestamp, $user );
+               $dateFormats['date'] = $lang->userDate( $timestamp, $user );
+               $dateFormats['time'] = $lang->userTime( $timestamp, $user );
+
+               return $dateFormats;
+       }
+
+       public function topicHistoryLink( Title $title, UUID $workflowId ) {
+               return array(
+                       $this->urlGenerator->buildUrl(
+                               $title,
+                               'topic-history',
+                               array( 'workflow' => $workflowId->getHex() )
+                       ),
+                       wfMessage( 'flow-link-history' )->text()
+               );
+       }
+
+       public function postHistoryLink( Title $title, UUID $workflowId, UUID 
$postId ) {
+               return array(
+                       $this->urlGenerator->buildUrl(
+                               $title,
+                               'post-history',
+                               array(
+                                       'workflow' => $workflowId->getHex(),
+                                       'topic' => array( 'postId' => 
$postId->getHex() ),
+                               )
+                       ),
+                       wfMessage( 'flow-link-history' )->text()
+               );
+       }
+
+       public function topicLink( Title $title, UUID $workflowId ) {
+               return array(
+                       $this->urlGenerator->buildUrl(
+                               $title,
+                               'view',
+                               array( 'workflow' => $workflowId->getHex() )
+                       ),
+                       wfMessage( 'flow-link-topic' )->text()
+               );
+       }
+
+       public function postLink( Title $title, UUID $workflowId, UUID $postId 
) {
+               return array(
+                       $this->urlGenerator->buildUrl(
+                               $title,
+                               'view',
+                               array(
+                                       'workflow' => $workflowId->getHex(),
+                                       'topic' => array( 'postId' => 
$postId->getHex() ),
+                               )
+                       ),
+                       wfMessage( 'flow-link-post' )->text()
+               );
+       }
+
+       protected function workflowLink( Title $title, UUID $workflowId ) {
+               list( $linkTitle, $query ) = $this->urlGenerator->buildUrlData(
+                       $title,
+                       'view'
+               );
+
+               return array(
+                       $linkTitle->getFullUrl( $query ),
+                       $linkTitle->getPrefixedText()
+               );
+       }
+
+       /**
+        * Build textual description for Flow's Contributions entries. These 
piggy-
+        * back on the i18n messages also used for Flow history, as defined in
+        * FlowActions.
+        *
+        * @param Workflow $workflow
+        * @param AbstractBlock $block
+        * @param AbstractRevision $revision
+        * @param User $user
+        * @return string
+        */
+       public function getActionDescription( Workflow $workflow, AbstractBlock 
$block, AbstractRevision $revision, User $user ) {
+               // Build description message, piggybacking on history i18n
+               $changeType = $revision->getChangeType();
+               $msg = $this->actions->getValue( $changeType, 'history', 
'i18n-message' );
+               $params = $this->actions->getValue( $changeType, 'history', 
'i18n-params' );
+               $message = $this->buildMessage( $msg, (array) $params, array(
+                       $revision,
+                       $this->templating,
+                       $user,
+                       $block
+               ) )->parse();
+
+               return \Html::rawElement(
+                       'span',
+                       array( 'class' => 'plainlinks' ),
+                       $message
+               );
+       }
+
+       /**
+        * @param AbstractRevision $revision
+        * @param AbstractRevision[optional] $previousRevision
+        * @return string|bool Chardiff or false on failure
+        */
+       protected function getCharDiff( AbstractRevision $revision, 
AbstractRevision $previousRevision = null ) {
+               $previousContent = '';
+
+               if ( $previousRevision ) {
+                       $previousContent = $previousRevision->getContentRaw();
+               }
+
+               return ChangesList::showCharacterDifference( strlen( 
$previousContent ), strlen( $revision->getContentRaw() ) );
+       }
+
+       /**
+        * @param Title $title
+        * @param UUID $workflowId
+        * @param string $name Block name (e.g. "topic", "header")
+        * @return AbstractBlock|bool Requested block or false on failure
+        */
+       protected function loadBlock( Title $title, UUID $workflowId, $name ) {
+               $loader = $this->workflowLoaderFactory
+                       ->createWorkflowLoader( $title, $workflowId );
+               $blocks = $loader->createBlocks();
+
+               if ( !isset( $blocks[$name] ) ) {
+                       wfWarn( __METHOD__ . ': Could not load block ' . $name 
. ' for workflow ' . $workflowId->getHex() );
+                       return false;
+               }
+
+               return $blocks[$name];
+       }
+
+       /**
+        * Load 1 specific workflow.
+        *
+        * @param UUID $workflowId
+        * @return Workflow|bool Requested workflow or false on failure
+        */
+       protected function loadWorkflow( UUID $workflowId ) {
+               $results = $this->loadWorkflows( array( $workflowId ) );
+               if ( !isset( $results[$workflowId->getHex()] ) ) {
+                       wfWarn( __METHOD__ . ': Could not load workflow ' . 
$workflowId->getHex() );
+                       return false;
+               }
+
+               return $results[$workflowId->getHex()];
+       }
+
+       /**
+        * Load 1 specific revision.
+        *
+        * @param UUID $revisionId
+        * @param string $revisionType Type of revision to load (e.g. Header, 
PostRevision)
+        * @return AbstractRevision|bool Requested revision or false on failure
+        */
+       protected function loadRevision( UUID $revisionId, $revisionType ) {
+               $results = $this->loadRevisions( array( $revisionType => array( 
$revisionId ) ) );
+               if ( !isset( $results[$revisionId->getHex()] ) ) {
+                       wfWarn( __METHOD__ . ': Could not load workflow ' . 
$revisionId->getHex() );
+                       return false;
+               }
+
+               return $results[$revisionId->getHex()];
+       }
+
+       /**
+        * Returns i18n message for $msg; piggybacking on History i18n.
+        *
+        * Complex parameters can be injected in the i18n messages. Anything in
+        * $params will be call_user_func'ed, with these given $arguments.
+        * Those results will be used as message parameters.
+        *
+        * Note: return array( 'raw' => $value ) or array( 'num' => $value ) for
+        * raw or numeric parameter input.
+        *
+        * @param string $msg i18n key
+        * @param array[optional] $params Callbacks for parameters
+        * @param array[optional] $arguments Arguments for the callbacks
+        * @return Message
+        */
+       protected function buildMessage( $msg, array $params = array(), array 
$arguments = array() ) {
+               foreach ( $params as &$param ) {
+                       if ( is_callable( $param ) ) {
+                               $param = call_user_func_array( $param, 
$arguments );
+                       }
+               }
+
+               return wfMessage( $msg, $params );
+       }
+
+       /**
+        * Batch-loads multiple workflows at once (and cached results in object)
+        *
+        * @param array $workflowIds
+        * @return array
+        */
+       public function loadWorkflows( array $workflowIds ) {
+               $results = array();
+
+               // make sure all ids are UUID objects
+               $workflowIds = array_map( array( 'Flow\Model\UUID', 'create' ), 
$workflowIds );
+
+               foreach ( $workflowIds as $i => $workflowId ) {
+                       // don't query for workflows already in cache
+                       if ( isset( $this->workflows[$workflowId->getHex()] ) ) 
{
+                               $results[$workflowId->getHex()] = 
$this->workflows[$workflowId->getHex()];
+                               unset( $workflowIds[$i] );
+                       }
+               }
+
+               // fetch missing workflows
+               $workflows = (array) $this->storage->getMulti( 'Workflow', 
$workflowIds );
+               foreach ( $workflows as $workflow ) {
+                       $results[$workflow->getId()->getHex()] = $workflow;
+               }
+
+               // cache in object
+               $this->workflows += $results;
+
+               return $results;
+       }
+
+       /**
+        * Batch-loads multiple revisions at once (and cached results in object)
+        *
+        * @param array $revisions Multi-dimensional array of revisions to 
fetch,
+        * where the revisionType (e.g. Header, PostRevision) is the key, and an
+        * array of revisionIds (UUID objects) is the value
+        * @return array
+        */
+       public function loadRevisions( array $revisions ) {
+               $results = array();
+
+               foreach ( $revisions as $revisionType => $revisionIds ) {
+                       // make sure all ids are UUID objects
+                       $revisionIds = array_map( array( 'Flow\Model\UUID', 
'create' ), $revisionIds );
+
+                       foreach ( $revisionIds as $i => $revisionId ) {
+                               // don't query for revisions already in cache
+                               if ( isset( 
$this->revisions[$revisionId->getHex()] ) ) {
+                                       $results[$revisionId->getHex()] = 
$this->revisions[$revisionId->getHex()];
+                                       unset( $revisionIds[$i] );
+                               }
+                       }
+
+                       // fetch missing revisions
+                       $revisions = (array) $this->storage->getMulti( 
$revisionType, $revisionIds );
+                       foreach ( $revisions as $revision ) {
+                               $results[$revision->getRevisionId()->getHex()] 
= $revision;
+                       }
+               }
+
+               // cache in object
+               $this->revisions += $results;
+
+               return $results;
+       }
+}
diff --git a/includes/RecentChanges/Formatter.php 
b/includes/RecentChanges/Formatter.php
index 274cfbd..4bb2af2 100644
--- a/includes/RecentChanges/Formatter.php
+++ b/includes/RecentChanges/Formatter.php
@@ -2,69 +2,13 @@
 
 namespace Flow\RecentChanges;
 
-use Flow\Container;
-use Flow\Data\ManagerGroup;
-use Flow\FlowActions;
+use Flow\AbstractFormatter;
 use Flow\Model\UUID;
 use ChangesList;
-use Flow\Templating;
-use Flow\UrlGenerator;
-use Flow\WorkflowLoaderFactory;
-use Language;
-use Linker;
 use Html;
 use RecentChange;
-use Title;
-use User;
 
-class Formatter {
-       /**
-        * @var ManagerGroup
-        */
-       protected $storage;
-
-       /**
-        * @var WorkflowLoaderFactory
-        */
-       protected $workflowLoaderFactory;
-
-       /**
-        * @var FlowActions
-        */
-       protected $actions;
-
-       /**
-        * @var Templating
-        */
-       protected $templating;
-
-       /**
-        * @var UrlGenerator;
-        */
-       protected $urlGenerator;
-
-       /**
-        * @var Language
-        */
-       protected $lang;
-
-       /**
-        * @param ManagerGroup $storage
-        * @param WorkflowLoaderFactory $workflowLoaderFactory
-        * @param FlowActions $actions
-        * @param Templating $templating
-        * @param Language $lang
-        */
-       public function __construct( ManagerGroup $storage, 
WorkflowLoaderFactory $workflowLoaderFactory, FlowActions $actions, Templating 
$templating, Language $lang ) {
-               $this->actions = $actions;
-               $this->storage = $storage;
-               $this->workflowLoaderFactory = $workflowLoaderFactory;
-               $this->templating = $templating;
-               $this->lang = $lang;
-
-               $this->urlGenerator = $this->templating->getUrlGenerator();
-       }
-
+class Formatter extends AbstractFormatter {
        public function format( ChangesList $cl, RecentChange $rc ) {
                $params = unserialize( $rc->getAttribute( 'rc_params' ) );
                $changeData = $params['flow-workflow-change'];
@@ -74,264 +18,81 @@
                        return false;
                }
 
-               // used in $this->buildActionLinks()
-               if ( !array_key_exists( 'action', $changeData ) ) {
-                       wfWarn( __METHOD__ . ': Flow action missing' );
+               $title = $rc->getTitle();
+               $user = $cl->getUser();
+               $lang = $cl->getLanguage();
 
-                       // Backward compatibility; get newer action name based 
on old type.
-                       if ( array_key_exists( 'type', $changeData ) ) {
-                               try {
-                                       $action = $this->actions->getValue( 
$changeData['type'] );
-                                       if ( is_string( $action ) ) {
-                                               $changeData['action'] = $action;
-                                       } else {
-                                               return false;
-                                       }
-                               } catch ( \MWException $e ) {
-                                       return false;
-                               }
-                       } else {
-                               return false;
-                       }
+               /*
+                * @todo:
+                * We should some day introduce a hook in core that allows us 
to hook
+                * into the full resultset, so we can first loop all entries 
and pre-
+                * load workflows & revisions all at once.
+                *
+                * Since that's not yet the case, multiple queries (up to 
$wgFeedLimit)
+                * may occur. Assuming revisions (yes) and workflow (less 
likely) are
+                * all different, there can be $wgFeedLimit * 2 queries to DB 
or cache.
+                */
+               $workflow = $this->loadWorkflow( UUID::create( 
$changeData['workflow'] ) );
+               if ( !$workflow ) {
+                       return false;
                }
 
-               $line = '';
-               $title = $rc->getTitle();
-               $links = $this->buildActionLinks( $title, $changeData );
+               $revision = $this->loadRevision( UUID::create( 
$changeData['revision'] ), $changeData['revision_type'] );
+               if ( !$revision ) {
+                       return false;
+               }
 
-               if ( $links ) {
-                       $linksContent = $cl->getLanguage()->pipeList( $links );
-                       $line .= wfMessage( 'parentheses' )->rawParams( 
$linksContent )->escaped()
+               $block = $this->loadBlock( $title, $workflow->getId(), 
$changeData['block'] );
+               if ( !$block ) {
+                       return false;
+               }
+
+               $links = (array) $this->buildActionLinks(
+                       $title,
+                       $revision->getChangeType(),
+                       $workflow->getId(),
+                       method_exists( $revision, 'getPostId' ) ? 
$revision->getPostId() : null
+               );
+
+               // Format links
+               foreach ( $links as &$link ) {
+                       list( $url, $text ) = $link;
+                       $link = Html::element(
+                               'a',
+                               array(
+                                       'href' => $url,
+                                       'title' => $text
+                               ),
+                               $text
+                       );
+               }
+               $linksContent = $lang->pipeList( $links );
+               if ( $linksContent ) {
+                       $linksContent = wfMessage( 'parentheses' )->rawParams( 
$linksContent )->escaped()
                                . $this->changeSeparator();
                }
 
-               $line .= $this->workflowLink( $title, $changeData )
+               $dateFormats = $this->getDateFormats( $revision, $user, $lang );
+               $formattedTime = '<span class="mw-changeslist-date">' . 
$dateFormats['time'] . '</span>';
+
+               $workflowLink = $this->workflowLink( $title, $workflow->getId() 
);
+               $workflowLink = Html::element(
+                       'a',
+                       array( 'href' => $workflowLink[0] ),
+                       $workflowLink[1]
+               );
+
+               return $linksContent
+                       . $workflowLink
                        . wfMessage( 'semicolon-separator' )->escaped()
-                       . $this->getTimestamp( $cl, $rc )
+                       . $formattedTime
                        . ' '
                        . $this->changeSeparator()
                        . ' '
-                       . $this->getActionDescription( $changeData, $cl, $rc );
-
-               return $line;
-       }
-
-       protected function buildActionLinks( Title $title, array $changeData ) {
-               $links = array();
-               switch( $changeData['action'] ) {
-                       case 'reply':
-                               $links[] = $this->topicLink( $title, 
$changeData );
-                               break;
-
-                       case 'new-post': // fall through
-                       case 'edit-post':
-                               $links[] = $this->topicLink( $title, 
$changeData );
-                               $links[] = $this->postLink( $title, $changeData 
);
-                               break;
-
-                       case 'suppress-post':
-                       case 'delete-post':
-                       case 'hide-post':
-                       case 'restore-post':
-                               $links[] = $this->topicLink( $title, 
$changeData );
-                               $links[] = $this->postHistoryLink( $title, 
$changeData );
-                               break;
-
-                       case 'suppress-topic':
-                       case 'delete-topic':
-                       case 'hide-topic':
-                       case 'restore-topic':
-                               $links[] = $this->topicLink( $title, 
$changeData );
-                               $links[] = $this->topicHistoryLink( $title, 
$changeData );
-                               break;
-
-                       case 'edit-title':
-                               $links[] = $this->topicLink( $title, 
$changeData );
-                               // This links to the history of the topic title
-                               $links[] = $this->postHistoryLink( $title, 
$changeData );
-                               break;
-
-                       case 'create-header': // fall through
-                       case 'edit-header':
-                               //$links[] = $this->workflowLink( $title, 
$changeData );
-                               break;
-
-                       case null:
-                               wfWarn( __METHOD__ . ': Flow change has null 
change type' );
-                               return false;
-
-                       default:
-                               wfWarn( __METHOD__ . ': Unknown Flow action: ' 
. $changeData['action'] );
-                               return false;
-               }
-
-               return $links;
+                       . $this->getActionDescription( $workflow, $block, 
$revision, $user );
        }
 
        protected function changeSeparator() {
                return ' <span class="mw-changeslist-separator">. .</span> ';
-       }
-
-       protected function getTimestamp( $cl, $rc ) {
-               return '<span class="mw-changeslist-date">'
-                               . $cl->getLanguage()->userTime( 
$rc->mAttribs['rc_timestamp'], $cl->getUser() )
-                       . '</span> ';
-       }
-
-       public function topicHistoryLink( Title $title, array $changeData ) {
-               return Html::rawElement(
-                       'a',
-                       array(
-                               'href' => $this->urlGenerator->buildUrl(
-                                       $title,
-                                       'topic-history',
-                                       array( 'workflow' => 
$changeData['workflow'] )
-                               ),
-                       ),
-                       wfMessage( 'flow-link-history' )->escaped()
-               );
-       }
-
-       public function postHistoryLink( Title $title, array $changeData ) {
-               return Html::rawElement(
-                       'a',
-                       array(
-                               'href' => $this->urlGenerator->buildUrl(
-                                       $title,
-                                       'post-history',
-                                       array(
-                                               'workflow' => 
$changeData['workflow'],
-                                               'topic' => array( 'postId' => 
$changeData['post'] ),
-                                       )
-                               ),
-                       ),
-                       wfMessage( 'flow-link-history' )->escaped()
-               );
-       }
-
-       public function topicLink( Title $title, array $changeData ) {
-               return Html::rawElement(
-                       'a',
-                       array(
-                               'href' => $this->urlGenerator->buildUrl(
-                                       $title,
-                                       'view',
-                                       array( 'workflow' => 
$changeData['workflow'] )
-                               ),
-                       ),
-                       wfMessage( 'flow-link-topic' )->escaped()
-               );
-       }
-
-       public function postLink( Title $title, array $changeData ) {
-               return Html::rawElement(
-                       'a',
-                       array(
-                               'href' => $this->urlGenerator->buildUrl(
-                                       $title,
-                                       'view',
-                                       array(
-                                               'workflow' => 
$changeData['workflow'],
-                                               'topic' => array( 'postId' => 
$changeData['post'] ),
-                                       )
-                               ),
-                       ),
-                       wfMessage( 'flow-link-post' )->escaped()
-               );
-       }
-
-       protected function workflowLink( Title $title, array $changeData ) {
-               list( $linkTitle, $query ) = $this->urlGenerator->buildUrlData(
-                       $title,
-                       'view'
-               );
-
-               return Html::element(
-                       'a',
-                       array( 'href' => $linkTitle->getFullUrl( $query ) ),
-                       $linkTitle->getPrefixedText()
-               );
-       }
-
-       /**
-        * Build textual description for Flow's RecentChanges entries. These 
piggy-
-        * back on the i18n messages also used for Flow history, as defined in
-        * FlowActions.
-        *
-        * @param array $changeData
-        * @param ChangesList $cl
-        * @param RecentChange $rc
-        * @return string
-        */
-       public function getActionDescription( array $changeData, ChangesList 
$cl, RecentChange $rc ) {
-               // Fetch Block object
-               $title = Title::newFromText( $rc->getAttribute( 'rc_title' ), 
(int) $rc->getAttribute( 'rc_namespace' ) );
-               $block = $this->loadBlock( $title, $changeData['workflow'], 
$changeData['block'] );
-               if ( !$block ) {
-                       wfWarn( __METHOD__ . ': Could not load block ' . 
$changeData['block'] . ' for workflow ' . $changeData['workflow'] );
-                       return '';
-               }
-
-               // Fetch requested Revision
-               $revision = $this->storage->get( $changeData['revision_type'], 
UUID::create( $changeData['revision'] ) );
-               if ( !$revision ) {
-                       wfWarn( __METHOD__ . ': Could not load revision ' . 
$changeData['revision'] );
-                       return '';
-               }
-
-               // Build description message, piggybacking on history i18n
-               $msg = $this->actions->getValue( $changeData['action'], 
'history', 'i18n-message' );
-               $params = $this->actions->getValue( $changeData['action'], 
'history', 'i18n-params' );
-               $message = $this->buildMessage( $msg, (array) $params, array(
-                       $revision,
-                       $this->templating,
-                       $cl->getUser(),
-                       $block
-               ) )->parse();
-
-               return \Html::rawElement(
-                       'span',
-                       array( 'class' => 'plainlinks' ),
-                       $message
-               );
-       }
-
-       /**
-        * @param Title $title
-        * @param string $definitionId
-        * @param string $workflowId
-        * @param string $name Block name
-        * @return AbstractBlock|false Requested block or false on failure
-        */
-       protected function loadBlock( Title $title, $workflowId, $name ) {
-               $loader = $this->workflowLoaderFactory
-                       ->createWorkflowLoader( $title, UUID::create( 
$workflowId ) );
-               $blocks = $loader->createBlocks();
-               return isset( $blocks[$name] ) ? $blocks[$name] : false;
-       }
-
-       /**
-        * Returns i18n message for $msg; piggybacking on History i18n.
-        *
-        * Complex parameters can be injected in the i18n messages. Anything in
-        * $params will be call_user_func'ed, with these given $arguments.
-        * Those results will be used as message parameters.
-        *
-        * Note: return array( 'raw' => $value ) or array( 'num' => $value ) for
-        * raw or numeric parameter input.
-        *
-        * @param string $msg i18n key
-        * @param array[optional] $params Callbacks for parameters
-        * @param array[optional] $arguments Arguments for the callbacks
-        * @return Message
-        */
-       protected function buildMessage( $msg, array $params = array(), array 
$arguments = array() ) {
-               foreach ( $params as &$param ) {
-                       if ( is_callable( $param ) ) {
-                               $param = call_user_func_array( $param, 
$arguments );
-                       }
-               }
-
-               return wfMessage( $msg, $params );
        }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/102674
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I8392302e016673f02bf495fd6ae156a9334262ab
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to