Bsitu has uploaded a new change for review.

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


Change subject: Board History
......................................................................

Board History

Items not  completed yet:

1. Add a history link to the board page
2. Code cleanup, testing and documentation

Change-Id: Ie48edfa4b8f85eaffa282b136663e95353c17215
---
M Flow.i18n.php
M Flow.php
M HistoryActions.php
M Hooks.php
M container.php
A db_patches/patch-topic_list_topic_id_idx.sql
M includes/Block/Header.php
M includes/Block/TopicList.php
A includes/Data/BoardHistoryStorage.php
M includes/Data/ObjectManager.php
M includes/Data/RevisionStorage.php
M includes/Model/AbstractRevision.php
A includes/Model/BoardHistoryEntry.php
M includes/Model/TopicListEntry.php
M includes/Repository/TreeRepository.php
M includes/View/History/HistoryRecord.php
M modules/history/styles/history.less
A templates/board-history.html.php
18 files changed, 469 insertions(+), 41 deletions(-)


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

diff --git a/Flow.i18n.php b/Flow.i18n.php
index bb940da..4f1dd17 100644
--- a/Flow.i18n.php
+++ b/Flow.i18n.php
@@ -89,8 +89,8 @@
        'flow-rev-message-new-post' => '[[User:$1|$1]] {{GENDER:$1|created}} 
the topic <span class="plainlinks">[$2 $3]</span>.',
        'flow-rev-message-hid-post' => '[[User:$1|$1]] {{GENDER:$1|hid}} a 
<span class="plainlinks">[$3 comment]</span>.',
        'flow-rev-message-edit-title' => '[[User:$1|$1]] {{GENDER:$1|edited}} 
the topic title to <span class="plainlinks">[$2 $3]</span>.',
-       'flow-rev-message-create-header' => 'Created header',
-       'flow-rev-message-edit-header' => 'Edited header',
+       'flow-rev-message-create-header' => "[[User:$1|$1]] 
{{GENDER:$1|created}} the header '''$2'''.",
+       'flow-rev-message-edit-header' => "[[User:$1|$1]] {{GENDER:$1|edited}} 
the header '''$2'''.",
        'flow-rev-message-restored-post' => '[[User:$1|$1]] 
{{GENDER:$1|restored}} a <span class="plainlinks">[$3 comment]</span>.',
        'flow-rev-message-deleted-post' => '[[User:$1|$1]] 
{{GENDER:$1|deleted}} a <span class="plainlinks">[$3 comment]</span>.',
        'flow-rev-message-censored-post' => '[[User:$1|$1]] 
{{GENDER:$1|suppressed}} a <span class="plainlinks">[$3 comment]</span>.',
@@ -104,6 +104,8 @@
        'flow-history-pages-post' => 'Appears on <span class="plainlinks">[$1 
$2]</span>',
        'flow-topic-participants' => '{{PLURAL:$1|$3 started this 
topic|{{GENDER:$3|$3}}, {{GENDER:$4|$4}} and {{PLURAL:$2|other|others}}|0=No 
participation yet|2={{GENDER:$3|$3}} and {{GENDER:$4|$4}}}}',
        'flow-topic-comments' => '{{PLURAL:$1|0=Be the first to 
comment!|Comment ($1)}}',
+
+       'flow-board-history' => '"$1" History',
 
        'flow-comment-restored' => 'Restored comment',
        'flow-comment-deleted' => 'Deleted comment',
@@ -357,8 +359,16 @@
 * $1: Username of the user who edited the title. Can be used for GENDER
 * $2: The url of the topic
 * $3: The topic title",
-       'flow-rev-message-create-header' => 'Used as revision comment when a 
header has been created',
-       'flow-rev-message-edit-header' => 'Used as revision comment when a 
header has been edited',
+       'flow-rev-message-create-header' => 'Used as revision comment when the 
header has been created.
+       
+Parameters:
+* $1: Username of the user who created the header.  Can be used for GENDER
+* $2: The header text',
+       'flow-rev-message-edit-header' => 'Used as revision comment when the 
header has been edited.
+       
+Parameters:
+* $1: Username of the user who edited the header.  Can be used for GENDER
+* $2: The header text',
        'flow-rev-message-restored-post' => 'Used as revision comment when a 
post has been restored (un-hidden).
 
 Parameters:
@@ -408,6 +418,10 @@
 
 Parameters:
 * $1 - The amount of comments on this topic, can be used for PLURAL',
+       'flow-board-history' => 'Used as <code><nowiki><h1></nowiki></code> 
heading and HTML title in the "Board history" page.
+
+Parameters:
+* $1: The title to which the flow board belongs',
        'flow-comment-restored' => 'Used as revision comment when the post has 
been restored.
 
 See also:
diff --git a/Flow.php b/Flow.php
index 0afd59c..6ba653b 100755
--- a/Flow.php
+++ b/Flow.php
@@ -103,6 +103,11 @@
 $wgAutoloadClasses['Flow\Data\UniqueFeatureIndex'] = $dir . 
'includes/Data/ObjectManager.php';
 $wgAutoloadClasses['Flow\Data\TopKIndex'] = $dir . 
'includes/Data/ObjectManager.php';
 $wgAutoloadClasses['Flow\Data\TopicHistoryIndex'] = $dir . 
'includes/Data/ObjectManager.php';
+$wgAutoloadClasses['Flow\Data\BoardHistoryStorage'] = $dir . 
'includes/Data/BoardHistoryStorage.php';
+$wgAutoloadClasses['Flow\Data\BoardHistoryIndex'] = $dir . 
'includes/Data/BoardHistoryStorage.php';
+$wgAutoloadClasses['Flow\Data\BoardHistoryHeaderIndex'] = $dir . 
'includes/Data/BoardHistoryStorage.php';
+$wgAutoloadClasses['Flow\Data\BoardHistoryTopicListIndex'] = $dir . 
'includes/Data/BoardHistoryStorage.php';
+$wgAutoloadClasses['Flow\Model\BoardHistoryEntry'] = $dir . 
'includes/Model/BoardHistoryEntry.php';
 $wgAutoloadClasses['Flow\Data\ObjectStorage'] = $dir . 
'includes/Data/ObjectManager.php';
 $wgAutoloadClasses['Flow\Data\BasicDbStorage'] = $dir . 
'includes/Data/ObjectManager.php';
 $wgAutoloadClasses['Flow\Data\ObjectMapper'] = $dir . 
'includes/Data/ObjectManager.php';
diff --git a/HistoryActions.php b/HistoryActions.php
index 6e6c087..d186218 100644
--- a/HistoryActions.php
+++ b/HistoryActions.php
@@ -2,6 +2,7 @@
 
 use Flow\View\History;
 use Flow\Model\PostRevision;
+use Flow\Model\Header;
 use Flow\Block\Block;
 use Flow\UrlGenerator;
 
@@ -18,6 +19,31 @@
  *   the list-item & clicking on it will reveal the individual history entries)
  */
 $wgFlowHistoryActions = array(
+       'flow-edit-header' => array(
+               'i18n-message' => 'flow-rev-message-edit-header',
+               'i18n-params' => array(
+                       function ( Header $revision, UrlGenerator 
$urlGenerator, User $user, Block $block ) {
+                               return $revision->getUserText( $user );
+                       },
+                       function ( Header $revision, UrlGenerator 
$urlGenerator, User $user, Block $block ) {
+                               return $revision->getContent( $user, 'wikitext' 
);
+                       },
+                       // @todo: find previous revision & return header of 
that revision
+               ),
+               'class' => 'flow-rev-message-edit-header',
+       ),
+       'flow-create-header' => array(
+               'i18n-message' => 'flow-rev-message-create-header',
+               'i18n-params' => array(
+                       function ( Header $revision, UrlGenerator 
$urlGenerator, User $user, Block $block ) {
+                               return $revision->getUserText( $user );
+                       },
+                       function ( Header $revision, UrlGenerator 
$urlGenerator, User $user, Block $block ) {
+                               return $revision->getContent( $user, 'wikitext' 
);
+                       },
+               ),
+               'class' => 'flow-rev-message-create-header',
+       ),
        'flow-rev-message-edit-post' => array(
                'i18n-message' => 'flow-rev-message-edit-post',
                'i18n-params' => array(
@@ -83,16 +109,6 @@
                        // @todo: find previous revision & return title of that 
revision
                ),
                'class' => 'flow-rev-message-edit-title',
-       ),
-       'flow-rev-message-create-header' => array(
-               'i18n-message' => 'flow-rev-message-create-header',
-               // @todo: AFAIK, we don't have a board history yet, where this 
will be surfaced
-               'class' => 'flow-rev-message-create-header',
-       ),
-       'flow-rev-message-edit-header' => array(
-               'i18n-message' => 'flow-rev-message-edit-header',
-               // @todo: AFAIK, we don't have a board history yet, where this 
will be surfaced
-               'class' => 'flow-rev-message-edit-header',
        ),
        'flow-rev-message-restored-post' => array(
                'i18n-message' => 'flow-rev-message-restored-post',
diff --git a/Hooks.php b/Hooks.php
index 513c452..f7f6d11 100644
--- a/Hooks.php
+++ b/Hooks.php
@@ -41,6 +41,7 @@
                }
 
                $updater->addExtensionIndex( 'flow_workflow', 
'flow_workflow_lookup', "$dir/db_patches/patch-workflow_lookup_idx.sql" );
+               $updater->addExtensionIndex( 'flow_topic_list', 
'flow_topic_list_topic_id', "$dir/db_patches/patch-topic_list_topic_id_idx.sql" 
);
 
                require_once 
__DIR__.'/maintenance/FlowInsertDefaultDefinitions.php';
                $updater->addPostDatabaseUpdateMaintenance( 
'FlowInsertDefaultDefinitions' );
diff --git a/container.php b/container.php
index 1681fc3..e619441 100644
--- a/container.php
+++ b/container.php
@@ -61,6 +61,10 @@
 use Flow\Data\UniqueFeatureIndex;
 use Flow\Data\TopKIndex;
 use Flow\Data\TopicHistoryIndex;
+use Flow\Data\BoardHistoryStorage;
+use Flow\Data\BoardHistoryTopicListIndex;
+use Flow\Data\BoardHistoryHeaderIndex;
+use Flow\Data\BoardHistoryIndex;
 use Flow\Data\ObjectMapper;
 use Flow\Data\ObjectManager;
 
@@ -112,6 +116,24 @@
 
        return new ObjectManager( $mapper, $storage, $indexes, $lifecycle );
 } );
+
+$c['storage.board_history'] = $c->share( function( $c ) {
+       $cache = $c['memcache.buffered'];
+       $mapper = BasicObjectMapper::model( 'Flow\\Model\\BoardHistoryEntry' );
+       $storage = new BoardHistoryStorage( $c['db.factory'] );
+
+       $indexes = array(
+               new BoardHistoryIndex( $cache, $storage, $c['repository.tree'], 
'flow_revision:topic_list_history',
+                       array( 'topic_list_id' ),
+                       array(
+                               'limit' => 500,
+                               'sort' => 'rev_id',
+                               'order' => 'DESC'
+               ) ),
+       );
+       return new ObjectManager( $mapper, $storage, $indexes );
+} );
+
 // Arbitrary bit of revisioned wiki-text attached to a workflow
 $c['storage.header'] = $c->share( function( $c ) {
        global $wgFlowExternalStore, $wgContLang;
@@ -144,6 +166,13 @@
                        'flow_header:latest', array( 'header_workflow_id' ),
                        array( 'limit'  => 1 ) + $workflowIndexOptions
                ),
+               new BoardHistoryHeaderIndex( $cache, new BoardHistoryStorage( 
$c['db.factory'] ), $c['repository.tree'], 'flow_revision:topic_list_history',
+                       array( 'topic_list_id' ),
+                       array(
+                               'limit' => 500,
+                               'sort' => 'rev_id',
+                               'order' => 'DESC'
+               ) ),
        );
 
        $handlers = array(
@@ -232,7 +261,14 @@
                                        return $row['tree_parent_id'] === null
                                                && $row['rev_parent_id'] === 
null;
                                },
-               ) )
+               ) ),
+               new BoardHistoryTopicListIndex( $cache, new 
BoardHistoryStorage( $c['db.factory'] ), $c['repository.tree'], 
'flow_revision:topic_list_history',
+                       array( 'topic_list_id' ),
+                       array(
+                               'limit' => 500,
+                               'sort' => 'rev_id',
+                               'order' => 'DESC'
+               ) ),
        );
 
        $handlers = array(
@@ -293,6 +329,9 @@
 
                        'Flow\\Model\\Header' => 'storage.header',
                        'Header' => 'storage.header',
+                       
+                       'Flow\\Model\\BoardHistoryEntry' => 
'storage.board_history',
+                       'BoardHistoryEntry' => 'storage.board_history',
                )
        );
 } );
diff --git a/db_patches/patch-topic_list_topic_id_idx.sql 
b/db_patches/patch-topic_list_topic_id_idx.sql
new file mode 100644
index 0000000..2d0d8df
--- /dev/null
+++ b/db_patches/patch-topic_list_topic_id_idx.sql
@@ -0,0 +1 @@
+CREATE INDEX /*i*/flow_topic_list_topic_id ON /*_*/flow_topic_list (topic_id);
diff --git a/includes/Block/Header.php b/includes/Block/Header.php
index 14f9540..7d35965 100644
--- a/includes/Block/Header.php
+++ b/includes/Block/Header.php
@@ -85,14 +85,17 @@
        }
 
        public function render( Templating $templating, array $options ) {
-               $templating->getOutput()->addModules( 'ext.flow.header' );
-               $templateName = ( $this->action == 'edit-header' ) ? 
'edit-header' : 'header';
-               $templating->render( "flow:$templateName.html.php", array(
-                       'block' => $this,
-                       'workflow' => $this->workflow,
-                       'header' => $this->header,
-                       'user' => $this->user,
-               ) );
+               // Don't show header in board history page
+               if ( $this->action !== 'board-history' ) {
+                       $templating->getOutput()->addModules( 'ext.flow.header' 
);
+                       $templateName = ( $this->action == 'edit-header' ) ? 
'edit-header' : 'header';
+                       $templating->render( "flow:$templateName.html.php", 
array(
+                               'block' => $this,
+                               'workflow' => $this->workflow,
+                               'header' => $this->header,
+                               'user' => $this->user,
+                       ) );
+               }
        }
 
        public function renderAPI( Templating $templating, array $options ) {
diff --git a/includes/Block/TopicList.php b/includes/Block/TopicList.php
index c9bffa1..d7a52a3 100644
--- a/includes/Block/TopicList.php
+++ b/includes/Block/TopicList.php
@@ -2,6 +2,8 @@
 
 namespace Flow\Block;
 
+use Flow\View\History\History;
+use Flow\View\History\HistoryRenderer;
 use Flow\DbFactory;
 use Flow\Data\ManagerGroup;
 use Flow\Data\ObjectManager;
@@ -62,9 +64,10 @@
                $firstPost->setChildren( array() );
 
                $storage->put( $topicWorkflow );
+               $storage->put( $topicListEntry );
                $storage->put( $topicPost );
                $storage->put( $firstPost );
-               $storage->put( $topicListEntry );
+               
 
                $this->notificationController->notifyNewTopic( array(
                        'board-workflow' => $this->workflow,
@@ -101,16 +104,25 @@
                                'page' => false,
                        ) );
                } else {
-                       $findOptions = $this->getFindOptions( $options );
-                       $page = $this->getPage( $findOptions );
-                       $topics = $this->getTopics( $page );
-
-                       $templating->render( "flow:topiclist.html.php", array(
-                               'block' => $this,
-                               'topics' => $topics,
-                               'user' => $this->user,
-                               'page' => $page,
-                       ) );
+                       if ( $this->action == 'board-history' ) {
+                               $templating->getOutput()->addModules( 
'ext.flow.history' );
+                               $templating->render( 
"flow:board-history.html.php", array(
+                                       'title' => wfMessage( 
'flow-board-history', $this->workflow->getArticleTitle() )->escaped(),
+                                       'history' => new History( 
$this->getTopicListHistory() ),
+                                       'historyRenderer' => new 
HistoryRenderer( $templating, $this ),
+                               ) );
+                       } else {
+                               $findOptions = $this->getFindOptions( $options 
);
+                               $page = $this->getPage( $findOptions );
+                               $topics = $this->getTopics( $page );
+       
+                               $templating->render( "flow:topiclist.html.php", 
array(
+                                       'block' => $this,
+                                       'topics' => $topics,
+                                       'user' => $this->user,
+                                       'page' => $page,
+                               ) );
+                       }
                }
        }
 
@@ -204,5 +216,19 @@
 
                return $topics;
        }
+
+       protected function getTopicListHistory() {
+               $found = $this->storage->find(
+                       'BoardHistoryEntry',
+                       array( 'topic_list_id' => $this->workflow->getId() ),
+                       array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' 
=> 300 )
+               );
+
+               if ( $found === false ) {
+                       throw new \MWException( "Unable to load topic list 
history for " . $this->workflow->getId()->getHex() );
+               }
+
+               return $found;
+       }
 }
 
diff --git a/includes/Data/BoardHistoryStorage.php 
b/includes/Data/BoardHistoryStorage.php
new file mode 100644
index 0000000..32d6b8b
--- /dev/null
+++ b/includes/Data/BoardHistoryStorage.php
@@ -0,0 +1,232 @@
+<?php
+
+namespace Flow\Data;
+
+use Flow\Model\UUID;
+use Flow\Model\PostRevision;
+use Flow\Model\Header;
+use Flow\DbFactory;
+use Flow\Repository\TreeRepository;
+use Flow\Container;
+
+class BoardHistoryStorage implements WritableObjectStorage {
+
+       protected $dbFactory;
+
+       public function __construct( DbFactory $dbFactory ) {
+               $this->dbFactory = $dbFactory;
+       }
+
+       public function getIterator() {
+               throw new \MWException( 'Not Implemented' );
+       }
+
+       function find( array $attributes, array $options = array() ) {
+               $multi = $this->findMulti( $attributes, $options );
+               if ( $multi ) {
+                       return reset( $multi );
+               }
+               return null;
+       }
+
+       function findMulti( array $queries, array $options = array() ) {
+               $res =  RevisionStorage::mergeExternalContent(
+                        array( $this->findHeaderHistory( $queries, $options ) 
+ $this->findTopicListHistory( $queries, $options ) )
+               );
+
+               return $res;
+       }
+
+       function findHeaderHistory( array $queries, array $options = array() ) {
+               $queries = current( $queries );
+               
+               $res = $this->dbFactory->getDB( DB_SLAVE )->select(
+                       array( 'flow_header_revision', 'flow_revision' ),
+                       array( '*' ),
+                       array( 'header_rev_id = rev_id' ) + UUID::convertUUIDs( 
array( 'header_workflow_id' => $queries['topic_list_id'] ) ),
+                       __METHOD__,
+                       $options
+               );
+
+               $retval = array();
+
+               if ( $res ) {
+                       foreach ( $res as $row ) {
+                               $retval[UUID::create( $row->rev_id )->getHex()] 
= (array) $row;
+                       }
+               }
+               return $retval;
+       }
+       
+       function findTopicListHistory( array $queries, array $options = array() 
) {
+               $res = $this->dbFactory->getDB( DB_SLAVE )->select(
+                       array( 'flow_topic_list', 'flow_tree_revision', 
'flow_revision' ),
+                       array( '*' ),
+                       array( 'tree_rev_id = rev_id', 'tree_rev_descendant_id 
= topic_id' ) + UUID::convertUUIDs( current( $queries ) ),
+                       __METHOD__,
+                       $options
+               );
+
+               $retval = array();
+
+               if ( $res ) {
+                       foreach ( $res as $row ) {
+                               $retval[UUID::create( $row->rev_id )->getHex()] 
= (array) $row;
+                       }
+               }
+               return $retval; 
+       }
+
+       public function getPrimaryKeyColumns() {
+               return array( 'topic_list_id' );
+       }
+
+       public function insert( array $row ) {
+               throw new \MWException( __CLASS__ . ' does not support insert 
action' );        
+       }
+
+       public function update( array $old, array $new ) {
+               throw new \MWException( __CLASS__ . ' does not support update 
action' );
+       }
+
+       public function remove( array $row ) {
+               throw new \MWException( __CLASS__ . ' does not support remove 
action' );
+       }
+
+}
+
+class BoardHistoryIndex extends topKIndex {
+
+       protected $treeRepository;
+
+       public function __construct( BufferedCache $cache, BoardHistoryStorage 
$storage, TreeRepository $treeRepo, $prefix, array $indexed, array $options = 
array() ) {
+               if ( $indexed !== array( 'topic_list_id' ) ) {
+                       throw new \Exception( __CLASS__ . ' is hardcoded to 
only index topic_list_id: ' . print_r( $indexed, true ) );
+               }
+               parent::__construct( $cache, $storage, $prefix, $indexed, 
$options );
+               $this->treeRepository = $treeRepo;
+       }
+
+       public function isBuildIndex() {
+               return false;   
+       }
+
+       public function backingStoreFindMulti( array $queries, array $idxToKey, 
array $retval = array() ) {
+               $res = $this->storage->findMulti( $queries, 
$this->queryOptions() );
+               if  ( !$res ) {
+                       return false;
+               }
+
+               $this->cache->add( current( $idxToKey ), 
$this->rowCompactor->compactRows( $res[0] ) );
+               $retval[] = $res[0];
+
+               return $retval;
+       }
+}
+
+class BoardHistoryTopicListIndex extends TopKIndex {
+
+       protected $treeRepository;
+
+       public function __construct( BufferedCache $cache, BoardHistoryStorage 
$storage, TreeRepository $treeRepo, $prefix, array $indexed, array $options = 
array() ) {
+               if ( $indexed !== array( 'topic_list_id' ) ) {
+                       throw new \Exception( __CLASS__ . ' is hardcoded to 
only index topic_list_id: ' . print_r( $indexed, true ) );
+               }
+               parent::__construct( $cache, $storage, $prefix, $indexed, 
$options );
+               $this->treeRepository = $treeRepo;
+       }
+
+       public function isSearchIndex() {
+               return false;   
+       }
+
+       public function onAfterInsert( $object, array $new ) {
+               $post = UUID::create( $new['tree_rev_descendant_id'] );
+               if ( $this->isRootPost( $post ) ) {
+                       $topicListId = $this->findTopicListId( $post );
+                       if ( $topicListId ) {
+                               $new['topic_list_id'] = $topicListId;
+                               parent::onAfterInsert( $object, $new );
+                       }
+               }
+       }
+
+       public function onAfterUpdate( $object, array $old, array $new ) {
+               $post = UUID::create( $old['tree_rev_descendant_id'] );
+               if ( $this->isRootPost( $post ) ) {
+                       $topicListId = $this->findTopicListId( $post );
+                       if ( $topicListId ) {
+                               $new['topic_list_id'] = $old['topic_list_id'] = 
$topicListId;
+                               parent::onAfterUpdate( $object, $old, $new );
+                       }
+               }
+       }
+
+       public function onAfterRemove( $object, array $old ) {
+               $post = UUID::create( $old['tree_rev_descendant_id'] );
+               if ( $this->isRootPost( $post ) ) {
+                       $topicListId = $this->findTopicListId( $post );
+                       if ( $topicListId ) {
+                               $old['topic_list_id'] = $topicListId;
+                               parent::onAfterRemove( $object, $old );
+                       }
+               }
+       }
+
+       protected function isRootPost( $postId ) {
+               $parent = $this->treeRepository->findParent( $postId );
+               if ( $parent ) {
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+
+       protected function findTopicListId( $post ) {
+               $topicListEntry = Container::get( 'storage' )->find(
+                       'TopicListEntry',
+                       array( 'topic_id' => $post->getBinary() )
+               );
+
+               if ( $topicListEntry ) {
+                       $topicListEntry = current( $topicListEntry );
+                       return $topicListEntry->getListId()->getBinary();
+               } else {
+                       return false;
+               }
+       }
+
+}
+
+class BoardHistoryHeaderIndex extends TopKIndex {
+
+       protected $treeRepository;
+
+       public function __construct( BufferedCache $cache, BoardHistoryStorage 
$storage, TreeRepository $treeRepo, $prefix, array $indexed, array $options = 
array() ) {
+               if ( $indexed !== array( 'topic_list_id' ) ) {
+                       throw new \Exception( __CLASS__ . ' is hardcoded to 
only index topic_list_id: ' . print_r( $indexed, true ) );
+               }
+               parent::__construct( $cache, $storage, $prefix, $indexed, 
$options );
+               $this->treeRepository = $treeRepo;
+       }
+
+       public function isSearchIndex() {
+               return false;
+       }
+       
+       public function onAfterInsert( $object, array $new ) {
+               $new['topic_list_id'] = $new['header_workflow_id'];
+               parent::onAfterInsert( $object, $new );
+       }
+
+       public function onAfterUpdate( $object, array $old, array $new ) {
+               $new['topic_list_id'] = $old['topic_list_id'] = 
$new['header_workflow_id'];
+               parent::onAfterUpdate( $object, $old, $new );
+       }
+
+       public function onAfterRemove( $object, array $old ) {
+               $old['topic_list_id'] = $old['header_workflow_id'];
+               parent::onAfterRemove( $object, $old );
+       }
+
+}
diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index e217311..b951da9 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -234,6 +234,9 @@
                }
 
                $index = $this->getIndexFor( $keys, $options );
+               if ( !$index->isSearchIndex() ) {
+                       throw new \MWException( 'Index: ' . get_class( $index ) 
. ' is not a search index' );
+               }
                $res = $index->findMulti( $queries );
 
                if ( $res === null ) {
@@ -766,6 +769,23 @@
        }
 
        /**
+        * Return true if this index can be used for data search, false 
otherwise
+        * @return boolean
+        */
+       public function isSearchIndex() {
+               return true;    
+       }
+
+       /**
+        * Determine if this index can be used for building data cache, false
+        * otherwise
+        * @return boolean
+        */
+       public function isBuildIndex() {
+               return true;    
+       }
+
+       /**
         * This must be in the provided order so portions of the application can
         * array_combine( $index->getPrimaryKeyColumns(), $primaryKeyValues )
         */
@@ -819,6 +839,9 @@
        }
 
        public function onAfterInsert( $object, array $new ) {
+               if ( !$this->isBuildIndex() ) {
+                       throw new \MWException( 'Index: ' . __CLASS__ . ' is 
not a build index' );
+               }
                $indexed = ObjectManager::splitFromRow( $new , $this->indexed );
                // is un-indexable a bail-worthy occasion? Probably not but 
makes debugging easier
                if ( !$indexed ) {
@@ -833,6 +856,9 @@
        }
 
        public function onAfterUpdate( $object, array $old, array $new ) {
+               if ( !$this->isBuildIndex() ) {
+                       throw new \MWException( 'Index: ' . __CLASS__ . ' is 
not a build index' );
+               }
                $oldIndexed = ObjectManager::splitFromRow( $old, $this->indexed 
);
                $newIndexed = ObjectManager::splitFromRow( $new, $this->indexed 
);
                if ( !$oldIndexed ) {
@@ -858,6 +884,9 @@
        }
 
        public function onAfterRemove( $object, array $old ) {
+               if ( !$this->isBuildIndex() ) {
+                       throw new \MWException( 'Index: ' . __CLASS__ . ' is 
not a build index' );
+               }
                $indexed = ObjectManager::splitFromRow( $old, $this->indexed );
                if ( !$indexed ) {
                        throw new \MWException( 'Unindexable row: ' 
.json_encode( $old ) );
diff --git a/includes/Data/RevisionStorage.php 
b/includes/Data/RevisionStorage.php
index a13b48a..c8847af 100644
--- a/includes/Data/RevisionStorage.php
+++ b/includes/Data/RevisionStorage.php
@@ -66,7 +66,7 @@
                        $res = $this->findMultiInternal( $queries, $options );
                }
                // Fetches content for all revisions flagged 'external'
-               return $this->mergeExternalContent( $res );
+               return self::mergeExternalContent( $res );
        }
 
        protected function fallbackFindMulti( array $queries, array $options ) {
@@ -181,7 +181,7 @@
         * @param array $cacheResult 2d array of rows
         * @return array 2d array of rows with content merged and 
rev_content_url populated
         */
-       protected function mergeExternalContent( array $cacheResult ) {
+       public static function mergeExternalContent( array $cacheResult ) {
                foreach ( $cacheResult as &$source ) {
                        foreach ( $source as &$row ) {
                                $flags = explode( ',', $row['rev_flags'] );
diff --git a/includes/Model/AbstractRevision.php 
b/includes/Model/AbstractRevision.php
index 15d5bcd..eb989ba 100644
--- a/includes/Model/AbstractRevision.php
+++ b/includes/Model/AbstractRevision.php
@@ -121,7 +121,7 @@
                $obj->flags = array_filter( explode( ',', $row['rev_flags'] ) );
                $obj->content = $row['rev_content'];
                // null if external store is not being used
-               $obj->contentUrl = $row['rev_content_url'];
+               $obj->contentUrl = isset( $row['rev_content_url'] ) ? 
$row['rev_content_url'] : null;
                $obj->decompressedContent = null;
 
                $obj->moderationState = $row['rev_mod_state'];
diff --git a/includes/Model/BoardHistoryEntry.php 
b/includes/Model/BoardHistoryEntry.php
new file mode 100644
index 0000000..8741df8
--- /dev/null
+++ b/includes/Model/BoardHistoryEntry.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Flow\Model;
+
+/**
+ * This is a virutal model for Board history entry, which is a wrapper for
+ * Header and PostRevision
+ */
+class BoardHistoryEntry {
+
+       /**
+        * The revision for a board history entry
+        * @var Header/PostRevision
+        */
+       protected $revision;
+
+       /**
+        * Wrapper function for Header/PostRevision fromStorageRow method
+        */
+       public static function fromStorageRow( array $row, $obj = null ) {
+               if ( $row['rev_type'] === 'header' ) {
+                       return Header::fromStorageRow( $row );
+               } elseif ( $row['rev_type'] === 'post' ) {
+                       return PostRevision::fromStorageRow( $row );
+               } else {
+                       throw new \MWException( 'Invalid rev_type for board 
history entry: ' . $row['rev_type'] );      
+               }
+       }
+
+       /**
+        * Wrapper function for toStoragerow method
+        */
+       public static function toStorageRow( $rev ) {
+               return $rev->toStorageRow( $rev );
+       }
+
+}
diff --git a/includes/Model/TopicListEntry.php 
b/includes/Model/TopicListEntry.php
index 0521d89..27a9eb3 100644
--- a/includes/Model/TopicListEntry.php
+++ b/includes/Model/TopicListEntry.php
@@ -41,7 +41,7 @@
        }
 
        public function getListId() {
-               return $this-topicListId;
+               return $this->topicListId;
        }
 }
 
diff --git a/includes/Repository/TreeRepository.php 
b/includes/Repository/TreeRepository.php
index 2d6e98a..716133e 100644
--- a/includes/Repository/TreeRepository.php
+++ b/includes/Repository/TreeRepository.php
@@ -267,6 +267,12 @@
        }
 
        public function fetchParentMapFromDb( array $nodes ) {
+               foreach ( $nodes as $key => $node ) {
+                       if ( $node instanceof UUID ) {
+                               $node = $node->getBinary();
+                       }
+                       $nodes[$key] = $node;
+               }
                // Find out who the parent is for those nodes
                $dbr = $this->dbFactory->getDB( DB_SLAVE );
                $res = $dbr->select(
diff --git a/includes/View/History/HistoryRecord.php 
b/includes/View/History/HistoryRecord.php
index 3457abe..2f178ed 100644
--- a/includes/View/History/HistoryRecord.php
+++ b/includes/View/History/HistoryRecord.php
@@ -3,6 +3,7 @@
 namespace Flow\View\History;
 
 use Flow\Model\PostRevision;
+use Flow\Model\AbstractRevision;
 use MWException;
 use MWTimestamp;
 use Message;
@@ -21,7 +22,7 @@
        /**
         * @param PostRevision $revision
         */
-       public function __construct( PostRevision $revision ) {
+       public function __construct( AbstractRevision $revision ) {
                $this->data = $revision;
        }
 
diff --git a/modules/history/styles/history.less 
b/modules/history/styles/history.less
index 0917156..bb8f329 100644
--- a/modules/history/styles/history.less
+++ b/modules/history/styles/history.less
@@ -67,7 +67,7 @@
                                        }
                                }
 
-                               &.flow-rev-message-edit-title {
+                               &.flow-rev-message-edit-title, 
&.flow-rev-message-edit-header {
                                        
.background-image-svg('../../base/images/edit_normal.svg', 
'../../base/images/edit_normal.png');
                                }
 
@@ -75,7 +75,7 @@
                                        
.background-image-svg('../../base/images/edit_normal.svg', 
'../../base/images/edit_normal.png');
                                }
 
-                               &.flow-rev-message-new-post {
+                               &.flow-rev-message-new-post, 
&.flow-rev-message-create-header {
                                        
.background-image-svg('../../base/images/added_normal.svg', 
'../../base/images/added_normal.png');
                                }
 
diff --git a/templates/board-history.html.php b/templates/board-history.html.php
new file mode 100644
index 0000000..03a206f
--- /dev/null
+++ b/templates/board-history.html.php
@@ -0,0 +1,18 @@
+<?php
+$this->getOutput()->setHtmlTitle( $title );
+$this->getOutput()->setPageTitle( $title );
+$timespans = $historyRenderer->getTimespans( $history );
+?>
+<div class="flow-history-container">
+       <div class="flow-history-log">
+               <?php
+                       foreach ( $timespans as $text => $timespan ) {
+                               $timespan = $history->getTimespan( 
$timespan['from'], $timespan['to'] );
+                               if ( $timespan->numRows() ) {
+                                       echo "<h2>$text</h2>";
+                                       echo $historyRenderer->render( 
$timespan );
+                               }
+                       }
+               ?>
+       </div>
+</div>

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

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

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

Reply via email to