EBernhardson (WMF) has submitted this change and it was merged.

Change subject: Add API modules to Flow.
......................................................................


Add API modules to Flow.

For now, just the one Query module, which will bring up the blocks
that comprise a workflow (given the workflow page name and possibly
its ID) in a machine readable format. Still definitely needs paging
and so on.

Includes a write API as well, and a bare bones base JS module
with access to the API actions. Eventually the JS module will have
shortcuts to the most common actions (post, reply, etc).

Change-Id: I3115567c29af31c6754e297f1f3fc3485edb9deb
---
M Flow.php
M container.php
M includes/Block/Block.php
M includes/Block/Summary.php
M includes/Block/Topic.php
M includes/Block/TopicList.php
A includes/WorkflowLoader.php
A includes/api/ApiFlow.php
A includes/api/ApiQueryFlow.php
M modules/base/ext.flow.base.js
M special/SpecialFlow.php
11 files changed, 657 insertions(+), 146 deletions(-)

Approvals:
  EBernhardson (WMF): Verified; Looks good to me, approved



diff --git a/Flow.php b/Flow.php
index b55e54e..a037658 100755
--- a/Flow.php
+++ b/Flow.php
@@ -53,6 +53,8 @@
 $wgAutoloadClasses['Flow\DbFactory'] = $dir . 'includes/DbFactory.php';
 $wgAutoloadClasses['Flow\Templating'] = $dir . 'includes/Templating.php';
 $wgAutoloadClasses['Flow\UrlGenerator'] = $dir . 'includes/UrlGenerator.php';
+$wgAutoloadClasses['Flow\WorkflowLoader'] = $dir . 
'includes/WorkflowLoader.php';
+$wgAutoloadClasses['Flow\WorkflowLoaderFactory'] = $dir . 
'includes/WorkflowLoader.php';
 
 // Classes that model our data
 $wgAutoloadClasses['Flow\Model\Definition'] = $dir . 
'includes/Model/Definition.php';
@@ -105,6 +107,12 @@
 $wgSpecialPages['Flow'] = 'SpecialFlow';
 $wgSpecialPageGroups['Flow'] = 'unknown';
 
+// API modules
+$wgAutoloadClasses['ApiQueryFlow'] = "$dir/includes/api/ApiQueryFlow.php";
+$wgAutoloadClasses['ApiFlow'] = "$dir/includes/api/ApiFlow.php";
+$wgAPIListModules['flow'] = 'ApiQueryFlow';
+$wgAPIModules['flow'] = 'ApiFlow';
+
 // Housekeeping hooks
 $wgHooks['LoadExtensionSchemaUpdates'][] = 'FlowHooks::getSchemaUpdates';
 //$wgHooks['GetPreferences'][] = 'FlowHooks::getPreferences';
@@ -121,10 +129,12 @@
 
 $wgResourceModules += array(
        'ext.flow.base' => $flowResourceTemplate + array(
-               'styles' => 'base/ext.flow.base.css',
+               // 'styles' => 'base/ext.flow.base.css',
                'scripts' => 'base/ext.flow.base.js',
                'dependencies' => array(
                        'ext.visualEditor.standalone',
+                       'mediawiki.api',
+                       'jquery.json',
                ),
                'messages' => array(
                ),
diff --git a/container.php b/container.php
index 6b4bddb..d2665cc 100644
--- a/container.php
+++ b/container.php
@@ -267,4 +267,11 @@
        );
 } );
 
+$c['factory.loader.workflow'] = $c->share( function( $c ) {
+       return new Flow\WorkflowLoaderFactory(
+               $c['storage'],
+               $c['loader.root_post']
+       );
+} );
+
 return $c;
diff --git a/includes/Block/Block.php b/includes/Block/Block.php
index d60bdbd..7975d6a 100644
--- a/includes/Block/Block.php
+++ b/includes/Block/Block.php
@@ -51,6 +51,7 @@
 
        abstract protected function validate();
        abstract public function render( Templating $templating, array $options 
);
+       abstract public function renderAPI( array $options );
        abstract public function commit();
 
        public function init( $action ) {
diff --git a/includes/Block/Summary.php b/includes/Block/Summary.php
index 2f6b785..8724616 100644
--- a/includes/Block/Summary.php
+++ b/includes/Block/Summary.php
@@ -86,6 +86,24 @@
                ) );
        }
 
+       public function renderAPI( array $options ) {
+               $output = array();
+               $output['type'] = 'summary';
+               if ( $this->summary !== null ) {
+                       $output['*'] = $this->summary->getContent();
+                       $output['summary-id'] = 
$this->summary->getRevisionId()->getHex();
+               } else {
+                       $output['missing'] = 'missing';
+               }
+
+               $output = array(
+                       '_element' => 'summary',
+                       0 => $output,
+               );
+
+               return $output;
+       }
+
        public function getName() {
                return 'summary';
        }
diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php
index 3599ce0..3a5df60 100644
--- a/includes/Block/Topic.php
+++ b/includes/Block/Topic.php
@@ -188,14 +188,10 @@
                        if ( empty( $options['postId'] ) ) {
                                var_dump( $this->getName() );
                                var_dump( $options );
-                               throw new \Exception( 'Could not locate post' );
+                               throw new \Exception( 'No postId specified' );
                                $history = array();
                        } else {
-                               $history = $this->storage->find(
-                                       'PostRevision',
-                                       array( 'tree_rev_descendant_id' => 
UUID::create( $options['postId'] ) ),
-                                       array( 'sort' => 'rev_id', 'order' => 
'DESC', 'limit' => 100 )
-                               );
+                               $history = $this->getHistory( 
$options['postId'] );
                        }
                        return $templating->render( 
"flow:post-history.html.php", array(
                                'block' => $this,
@@ -213,6 +209,135 @@
                ), $return );
        }
 
+       public function renderAPI ( array $options ) {
+               $output = array();
+               $rootPost = $this->loadRootPost();
+               $topic = $this->workflow;
+
+               $output = array(
+                       '_element' => 'post',
+                       'title' => $rootPost->getContent(),
+                       'topic-id' => $topic->getId()->getHex(),
+               );
+
+               if ( isset( $options['showhistoryfor'] ) ) {
+                       $options['history'] = array();
+
+                       $historyBatch = $this->getHistoryBatch( 
(array)$options['showhistoryfor'] );
+
+                       foreach( $historyBatch as $historyGroup ) {
+                               foreach( $historyGroup as $historyEntry ) {
+                                       $postId = 
$historyEntry->getPostId()->getHex();
+                                       if ( ! isset( 
$options['history'][$postId] ) ) {
+                                               $options['history'][$postId] = 
array();
+                                       }
+
+                                       $options['history'][$postId][] = 
$historyEntry;
+                               }
+                       }
+               }
+
+               foreach( $rootPost->getChildren() as $child ) {
+                       $output[] = $this->renderPostAPI( $child, $options );
+               }
+
+               return $output;
+       }
+
+       protected function renderPostAPI( PostRevision $post, array $options ) {
+               $output = array();
+
+               $output['post-id'] = $post->getPostId()->getHex();
+
+               if ( $post->isFlagged( 'deleted' ) ) {
+                       $output['post-deleted'] = 'post-deleted';
+               } else {
+                       $output['content'] = array( '*' => $post->getContent() 
);
+                       $output['user'] = $post->getUserText();
+               }
+
+               $children = array( '_element' => 'post' );
+
+               foreach( $post->getChildren() as $child ) {
+                       $children[] = $this->renderPostAPI( $child, $options );
+               }
+
+               if ( count($children) > 1 ) {
+                       $output['replies'] = $children;
+               }
+
+               $postId = $post->getPostId()->getHex();
+               if ( isset( $options['history'][$postId] ) ) {
+                       $output['revisions'] = $this->getAPIHistory( $postId, 
$options['history'][$postId] );
+               }
+
+               return $output;
+       }
+
+       protected function getAPIHistory( /*string*/ $postId, array $history ) {
+               $output = array();
+
+               $output['_element'] = 'revision';
+               $output['post-id'] = $postId;
+
+               foreach( $history as $revision ) {
+                       $output[] = array(
+                               'revision-id' => 
$revision->getRevisionId()->getHex(),
+                               'revision-author' => $revision->getUserText(),
+                               'revision-comment' => $revision->getComment(),
+                       );
+               }
+
+               return $output;
+       }
+
+       protected function getHistory( $postId ) {
+               return $this->storage->find(
+                       'PostRevision',
+                       array( 'tree_rev_descendant_id' => UUID::create( 
$postId ) ),
+                       array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' 
=> 100 )
+               );
+       }
+
+       protected function getHistoryBatch( $postIds ) {
+               $searchItems = array();
+
+               // Make list of candidate conditions
+               foreach( $postIds as $postId ) {
+                       $uuid = UUID::create( $postId );
+                       $searchItems[$uuid->getHex()] = array(
+                               'tree_rev_descendant_id' => $uuid,
+                       );
+               }
+
+               // Filter conditions so that only relevant ones are requested
+               $searchConditions = array();
+               $traversalQueue = array( $this->root );
+
+               while( count( $traversalQueue ) > 0 ) {
+                       $cur = array_shift( $traversalQueue );
+
+                       foreach( $cur->getChildren() as $child ) {
+                               array_push( $traversalQueue, $child );
+                       }
+
+                       $postId = $cur->getPostId()->getHex();
+                       if ( isset( $searchItems[$postId] ) ) {
+                               $searchConditions[] = $searchItems[$postId];
+                       }
+               }
+
+               if ( count($searchConditions) === 0 ) {
+                       return array();
+               }
+
+               return $this->storage->findMulti(
+                       'PostRevision',
+                       $searchConditions,
+                       array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' 
=> 100 )
+               );
+       }
+
        protected function loadRootPost() {
                if ( $this->root !== null ) {
                        return $this->root;
diff --git a/includes/Block/TopicList.php b/includes/Block/TopicList.php
index 504523c..e480297 100644
--- a/includes/Block/TopicList.php
+++ b/includes/Block/TopicList.php
@@ -58,17 +58,30 @@
        }
 
        public function render( Templating $templating, array $options ) {
-               // New workflows cant have content yet
-               if ( $this->workflow->isNew() ) {
-                       $topics = array();
-               } else {
-                       $topics = $this->loadAllRelatedTopics();
-               }
-
                $templating->render( "flow:topiclist.html.php", array(
                        'topicList' => $this,
-                       'topics' => $topics,
+                       'topics' => $this->getTopics(),
                ) );
+       }
+
+       public function renderAPI( array $options ) {
+               $output = array( '_element' => 'topic' );
+               $topics = $this->getTopics();
+
+               foreach( $topics as $topic ) {
+                       $output[] = $topic->renderAPI( $options );
+               }
+
+               return $output;
+       }
+
+       protected function getTopics() {
+               // New workflows cant have content yet
+               if ( $this->workflow->isNew() ) {
+                       return array();
+               } else {
+                       return $this->loadAllRelatedTopics();
+               }
        }
 
        public function getName() {
@@ -91,6 +104,7 @@
                foreach ( $this->storage->getMulti( 'Workflow', $topicIds ) as 
$workflow ) {
                        $hexId = $workflow->getId()->getHex();
                        $topics[$hexId] = new TopicBlock( $workflow, 
$this->storage, $roots[$hexId] );
+                       $topics[$hexId]->init( $this->action );
                }
 
                return $topics;
diff --git a/includes/WorkflowLoader.php b/includes/WorkflowLoader.php
new file mode 100644
index 0000000..463ca7c
--- /dev/null
+++ b/includes/WorkflowLoader.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Flow;
+
+use Flow\Block\SummaryBlock;
+use Flow\Block\TopicBlock;
+use Flow\Block\TopicListBlock;
+use Flow\Model\Definition;
+use Flow\Model\UUID;
+use Flow\Model\Workflow;
+use Flow\Data\ManagerGroup;
+use Flow\Data\ObjectStorage;
+use Flow\Data\RootPostLoader;
+
+class WorkflowLoader {
+       protected $workflow, $definition;
+
+       public function __construct(
+                       $pageTitle,
+                       /*UUID or NULL*/ $workflowId,
+                       $definitionRequest,
+                       ManagerGroup $storage,
+                       RootPostLoader $rootPostLoader
+       ) {
+               if ( $pageTitle === null ) {
+                       throw new \MWException( 'Invalid article requested' );
+               }
+               if ( $pageTitle && $pageTitle->mInterwiki ) {
+                       throw new \MWException( 'Interwiki not implemented yet' 
);
+               }
+               if ( $pageTitle && $pageTitle->getArticleID() === 0 ) {
+                       throw new \MWException( 'Can only load workflows for 
existing page' );
+               }
+
+               $this->storage = $storage;
+               $this->rootPostLoader = $rootPostLoader;
+
+               $this->definitionRequest = $definitionRequest;
+
+               $workflow = null;
+
+               if ( $workflowId !== null ) {
+                       list( $workflow, $definition ) = 
$this->loadWorkflowById( $pageTitle, $workflowId );
+               } else {
+                       list( $workflow, $definition ) = $this->loadWorkflow( 
$pageTitle );
+               }
+
+               if ( ! $workflow || ! $definition ) {
+                       throw new \MWException( "Unable to load workflow and 
definition" );
+               }
+
+               $this->workflow = $workflow;
+               $this->definition = $definition;
+       }
+
+       public function getDefinition() {
+               return $this->definition;
+       }
+
+       public function getWorkflow() {
+               return $this->workflow;
+       }
+
+       protected function loadWorkflow( \Title $title ) {
+               global $wgContLang, $wgUser;
+               $storage = $this->storage->getStorage('Workflow');
+
+               $definition = $this->loadDefinition();
+               if ( !$definition->getOption( 'unique' ) ) {
+                       throw new \MWException( 'Workflow is non-unique, can 
only fetch object by title + id' );
+               }
+               $found = $storage->find( array(
+                       'workflow_definition_id' => $definition->getId(),
+                       'workflow_wiki' => $title->isLocal() ? wfWikiId() : 
$title->getTransWikiID(),
+                       'workflow_page_id' => $title->getArticleID(),
+               ) );
+               if ( $found ) {
+                       $workflow = reset( $found );
+               } else {
+                       $workflow = Workflow::create( $definition, $wgUser, 
$title );
+               }
+
+               return array( $workflow, $definition );
+       }
+
+       protected function loadWorkflowById( /* Title or false */ $title, 
$workflowId ) {
+               $workflow = $this->storage->getStorage('Workflow')->get( 
$workflowId );
+               if ( !$workflow ) {
+                       throw new \MWException( 'Invalid workflow requested by 
id' );
+               }
+               if ( $title !== false && !$workflow->matchesTitle( $title ) ) {
+                       // todo: redirect?
+                       throw new \MWException( 'Flow workflow is for different 
page' );
+               }
+               $definition = $this->storage->getStorage('Definition')->get( 
$workflow->getDefinitionId() );
+               if ( !$definition ) {
+                       throw new \MWException( 'Flow workflow references 
unknown definition id: ' . $workflow->getDefinitionId()->getHex() );
+               }
+
+               return array( $workflow, $definition );
+       }
+
+       protected function loadDefinition() {
+               global $wgFlowDefaultWorkflow;
+
+               $repo = $this->storage->getStorage('Definition');
+               $id = $this->definitionRequest;
+               if ( $id instanceof UUID ) {
+                       $definition = $repo->get( $id );
+                       if ( $definition === null ) {
+                               throw new MWException( "Unknown flow id '$id' 
requested" );
+                       }
+               } else {
+                       $workflowName = $id ? $id : $wgFlowDefaultWorkflow;
+                       $found = $repo->find( array(
+                               'definition_name' => strtolower( $workflowName 
),
+                               'definition_wiki' => wfWikiId(),
+                       ) );
+                       if ( $found ) {
+                               $definition = reset( $found );
+                       } else {
+                               throw new \MWException( "Unknown flow type 
'$workflowName' requested" );
+                       }
+               }
+               return $definition;
+       }
+
+       public function createBlocks( ) {
+               switch( $this->definition->getType() ) {
+               case 'discussion':
+                       return array(
+                               'summary' => new SummaryBlock( $this->workflow, 
$this->storage ),
+                               'topics' => new TopicListBlock( 
$this->workflow, $this->storage, $this->rootPostLoader ),
+                       );
+
+               case 'topic':
+                       return array(
+                               'topic' => new TopicBlock( $this->workflow, 
$this->storage, $this->rootPostLoader ),
+                       );
+
+               default:
+                       throw new \MWException( 'Not Implemented' );
+               }
+       }
+
+       public function handleSubmit( $action, array $blocks, $user, 
\WebRequest $request ) {
+               $success = true;
+               $interestedBlocks = array();
+               $workflow = $this->getWorkflow();
+
+               foreach ( $blocks as $block ) {
+                       $data = $request->getArray( $block->getName(), array() 
);
+                       $result = $block->onSubmit( $action, $user, $data );
+                       if ( $result !== null ) {
+                               $interestedBlocks[] = $block;
+                               $success &= $result;
+                       }
+               }
+               if ( !$interestedBlocks ) {
+                       if ( !$blocks ) {
+                               throw new \MWException( 'No Blocks?!?' );
+                       }
+                       $type = array();
+                       foreach ( $blocks as $block ) {
+                               $type[] = get_class( $block );
+                       }
+                       // All blocks returned null, nothing knows how to 
handle this action
+                       throw new \MWException( "No block accepted the 
'$action' action: " .  implode( ',', array_unique( $type ) ) );
+               }
+               return $success ? $interestedBlocks : array();
+       }
+
+       public function commit( Workflow $workflow, array $blocks ) {
+               $this->storage->getStorage('Workflow')->put( $workflow );
+               foreach ( $blocks as $block ) {
+                       $block->commit();
+               }
+       }
+
+}
+
+class WorkflowLoaderFactory {
+       protected $storage, $rootPostLoader;
+
+       function __construct( ManagerGroup $storage, RootPostLoader 
$rootPostLoader ) {
+               $this->storage = $storage;
+               $this->rootPostLoader = $rootPostLoader;
+       }
+
+       public function createWorkflowLoader( $pageTitle, $workflowId = null, 
$definitionRequest = false ) {
+               return new WorkflowLoader(
+                       $pageTitle,
+                       $workflowId,
+                       $definitionRequest,
+                       $this->storage,
+                       $this->rootPostLoader
+               );
+       }
+}
\ No newline at end of file
diff --git a/includes/api/ApiFlow.php b/includes/api/ApiFlow.php
new file mode 100644
index 0000000..b872087
--- /dev/null
+++ b/includes/api/ApiFlow.php
@@ -0,0 +1,108 @@
+<?php
+
+use Flow\Model\UUID;
+
+class ApiFlow extends ApiBase {
+       public function execute() {
+               $this->container = include __DIR__ . "/../../container.php";
+               $params = $this->extractRequestParams();
+               $output = array();
+
+               if ( $params['gettoken'] ) {
+                       $this->getResult()->addValue( null, 
$this->getModuleName(), array(
+                               'token' => 
$this->getContext()->getUser()->getEditToken( 'flow' ),
+                       ) );
+                       return true;
+               }
+
+               $id = UUID::create( $params['workflow'] );
+
+               $this->loader = $this->container['factory.loader.workflow']
+                       ->createWorkflowLoader( false, $id );
+
+               $requestParams = json_decode( $params['params'], true );
+               $request = new DerivativeRequest( 
$this->getContext()->getRequest(), $requestParams, true );
+
+               $blocks = $this->loader->createBlocks();
+               $action = $params['flowaction'];
+               $user = $this->getContext()->getUser();
+
+               foreach( $blocks as $block ) {
+                       $block->init( $action );
+               }
+
+               $blocksToCommit = $this->loader->handleSubmit( $action, 
$blocks, $user, $request );
+               if ( $blocksToCommit ) {
+                       $this->loader->commit( $this->loader->getWorkflow(), 
$blocksToCommit );
+
+                       $savedBlocks = array( '_element' => 'block' );
+
+                       foreach( $blocksToCommit as $block ) {
+                               $savedBlocks[] = $block->getName();
+                       }
+
+                       $output[$action] = array(
+                               'result' => 'success',
+                               'saved-blocks' => $savedBlocks,
+                       );
+               } else {
+                       $output[$action] = array(
+                               'result' => 'nop',
+                       );
+               }
+
+               $this->getResult()->addValue( null, $this->getModuleName(), 
$output );
+               return true;
+       }
+
+       public function getDescription() {
+                return 'Allows actions to be taken on Flow Workflows';
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'flowaction' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'workflow' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'params' => array(
+                               ApiBase::PARAM_DFLT => '{}',
+                       ),
+                       'token' => null,
+                       'gettoken' => array(
+                               ApiBase::PARAM_DFLT => false,
+                       ),
+               );
+       }
+
+       public function getParamDescription() {
+               return array(
+                       'action' => 'The action to take',
+                       'workflow' => 'The Workflow to take an action on',
+                       'params' => 'The parameters to pass',
+                       'token' => 'A token retrieved by calling this module 
with the gettoken parameter set.',
+                       'gettoken' => 'Set this to something to retrieve a 
token',
+               );
+       }
+
+       public function getExamples() {
+                return array(
+                        ''
+                        => ''
+                );
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function needsToken() {
+               return true;
+       }
+
+       public function getTokenSalt() {
+               return 'flow';
+       }
+}
\ No newline at end of file
diff --git a/includes/api/ApiQueryFlow.php b/includes/api/ApiQueryFlow.php
new file mode 100644
index 0000000..5d7f2c5
--- /dev/null
+++ b/includes/api/ApiQueryFlow.php
@@ -0,0 +1,87 @@
+<?php
+
+use Flow\WorkflowLoader;
+use Flow\Model\UUID;
+
+class ApiQueryFlow extends ApiQueryBase {
+       protected $loader, $workflow, $definition;
+
+       public function __construct( $query, $moduleName ) {
+               parent::__construct( $query, $moduleName, 'flow' );
+       }
+
+       public function execute() {
+               $this->container = include __DIR__ . "/../../container.php";
+               // Get the parameters
+               $params = $this->extractRequestParams();
+               $passedParams = json_decode( $params['params'], true );
+
+               $pageTitle = Title::newFromText( $params['page'] );
+               $id = $params['workflow'] ? new UUID( $params['workflow'] ) : 
null;
+
+               $this->loader = $this->container['factory.loader.workflow']
+                       ->createWorkflowLoader( $pageTitle, $id );
+
+               $blocks = $this->loader->createBlocks();
+               $blockOutput = array();
+               foreach( $blocks as $block ) {
+                       $block->init( $params['action'] );
+
+                       $blockParams = array();
+                       if ( isset($passedParams[$block->getName()]) ) {
+                               $blockParams = $passedParams[$block->getName()];
+                       }
+
+                       $blockOutput[] = $block->renderAPI( $blockParams ) +
+                               array( 'block-name' => $block->getName() );
+               }
+
+               $result = array(
+                       '_element' => 'block',
+                       'workflow-id' => 
$this->loader->getWorkflow()->getId()->getHex(),
+               ) + $blockOutput;
+
+               $this->getResult()->addValue( 'query', $this->getModuleName(), 
$result );
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'workflow' => array(
+                       ),
+                       'page' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'action' => array(
+                               ApiBase::PARAM_DFLT => 'view',
+                       ),
+                       'params' => array(
+                               ApiBase::PARAM_DFLT => '{}',
+                       ),
+               );
+       }
+
+       public function getParamDescription() {
+               return array(
+                       'workflow' => 'Hex-encoded ID of the workflow to query',
+                       'page' => 'Title of the page to query'
+               );
+       }
+
+       public function getDescription() {
+               return 'Queries the Flow subsystem for data';
+       }
+
+       public function getExamples() {
+               return array(
+                       
'api.php?action=query&list=flow&flowpage=Talk:Main_Page',
+               );
+       }
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/Flow_Portal';
+       }
+
+       public function getVersion() {
+               return __CLASS__ . '-0.1';
+       }
+}
\ No newline at end of file
diff --git a/modules/base/ext.flow.base.js b/modules/base/ext.flow.base.js
index 02405dd..5fd2205 100644
--- a/modules/base/ext.flow.base.js
+++ b/modules/base/ext.flow.base.js
@@ -1 +1,54 @@
-alert( 'foo' );
+( function($, mw) {
+$( function() {
+mw.flow = {
+       'api' : {
+               'executeAction' : function( workflowId, action, options ) {
+                       var api = new mw.Api();
+                       var deferredObject = $.Deferred();
+
+                       api.post(
+                               {
+                                       'action' : 'flow',
+                                       'workflow' : workflowId,
+                                       'flowaction' : action,
+                                       'gettoken' : 'gettoken'
+                               }
+                       )
+                               .done( function(data) {
+                                       api.post( {
+                                               'action' : 'flow',
+                                               'workflow' : workflowId,
+                                               'flowaction' : action,
+                                               'params' : $.toJSON( options ),
+                                               'token' : data.flow.token
+                                       } )
+                                               .done( function( data, 
textStatus, xhr ) {
+                                                       deferredObject.resolve( 
data, textStatus, xhr );
+                                               } )
+                                               .fail( function( xhr, 
textStatus, errorThrown ) {
+                                                       deferredObject.reject( 
xhr, textStatus, errorThrown );
+                                               } );
+                               } )
+                               .fail(function( xhr, textStatus, errorThrown ) {
+                                       deferredObject.reject( xhr, textStatus, 
errorThrown );
+                               } );
+
+                       return deferredObject.promise();
+               },
+               'read' : function( pageName, workflowId, options ) {
+                       var api = new mw.Api();
+
+                       return api.get(
+                               {
+                                       'action' : 'query',
+                                       'list' : 'flow',
+                                       'flowpage' : pageName,
+                                       'flowworkflow' : workflowId,
+                                       'flowparams' : $.toJSON( options )
+                               }
+                       );
+               }
+       }
+};
+});
+})( jQuery, mediaWiki );
\ No newline at end of file
diff --git a/special/SpecialFlow.php b/special/SpecialFlow.php
index 47fba3c..fe17db2 100644
--- a/special/SpecialFlow.php
+++ b/special/SpecialFlow.php
@@ -6,6 +6,7 @@
 use Flow\Model\Definition;
 use Flow\Model\UUID;
 use Flow\Model\Workflow;
+use Flow\WorkflowLoader;
 
 /**
  * SpecialFlow is intended to bootstrap flow.  It sets up the generic parts of 
flow that apply
@@ -24,6 +25,8 @@
        public function execute( $subPage ) {
                $this->setHeaders();
                $this->getOutput()->setPageTitle( $this->msg( 
'flow-specialpage' )->text() );
+               $this->getOutput()->addModules( array('ext.flow.base') );
+               
                if ( empty( $subPage ) ) {
                        // If no specific article was requested, render the 
users flow
                        throw new \MWException( 'TODO: Redirect to users 
board?' );
@@ -35,24 +38,30 @@
                $action = $request->getVal( 'action', 'view' );
                $workflowId = $request->getVal( 'workflow' );
 
-               if ( $title->getArticleID() === 0 ) {
-                       throw new \MWException( 'Can only load workflows for 
existing page' );
-               }
-               if ( $workflowId ) {
-                       list( $workflow, $definition ) = 
$this->loadWorkflowById( $title, $workflowId );
+               $definitionRequest = $request->getVal( 'definition', null );
+               if ( $definitionRequest !== null ) {
+                       $definitionRequest = UUID::create( $definitionRequest );
                } else {
-                       list( $workflow, $definition ) = $this->loadWorkflow( 
$title );
+                       $definitionRequest = $request->getVal( 'flow', null );
                }
 
-               $blocks = $this->createBlocks( $workflow, $definition );
+               $this->loader = $this->container['factory.loader.workflow']
+                       ->createWorkflowLoader( $title, UUID::create( 
$workflowId ) );
+
+               $workflow = $this->loader->getWorkflow();
+               $definition = $this->loader->getDefinition();
+
+               $blocks = $this->loader->createBlocks();
                foreach ( $blocks as $block ) {
                        $block->init( $action );
                }
 
                if ( $request->getMethod() === 'POST' ) {
-                       $blocksToCommit = $this->handleSubmit( $workflow, 
$definition, $action, $blocks );
+                       $user = $this->container['user'];
+                       $request = $this->getRequest();
+                       $blocksToCommit = $this->loader->handleSubmit( $action, 
$blocks, $user, $request );
                        if ( $blocksToCommit ) {
-                               $this->commit( $workflow, $blocksToCommit );
+                               $this->loader->commit( $workflow, 
$blocksToCommit );
                                $this->redirect( $workflow, 'view' );
                                return;
                        }
@@ -71,33 +80,7 @@
 
                return $container;
        }
-
-       protected function loadDefinition() {
-               global $wgFlowDefaultWorkflow;
-
-               $repo = $this->container['storage.definition'];
-               $id = $this->getRequest()->getVal( 'definition' );
-               if ( $id !== null ) {
-                       $id = UUID::create( $id );
-                       $definition = $repo->get( $id );
-                       if ( $definition === null ) {
-                               throw new MWException( "Unknown flow id '$id' 
requested" );
-                       }
-               } else {
-                       $workflowName = $this->getRequest()->getVal( 'flow', 
$wgFlowDefaultWorkflow );
-                       $found = $repo->find( array(
-                               'definition_name' => strtolower( $workflowName 
),
-                               'definition_wiki' => wfWikiId(),
-                       ) );
-                       if ( $found ) {
-                               $definition = reset( $found );
-                       } else {
-                               throw new MWException( "Unknown flow type 
'$workflowName' requested" );
-                       }
-               }
-               return $definition;
-       }
-
+       
        protected function loadTitle( $text ) {
                $title = Title::newFromText( $text );
                if ( $title === null ) {
@@ -108,100 +91,6 @@
                }
 
                return $title;
-       }
-
-
-       protected function loadWorkflow( Title $title ) {
-               global $wgContLang, $wgUser;
-               $storage = $this->container['storage.workflow'];
-
-               $definition = $this->loadDefinition();
-               if ( !$definition->getOption( 'unique' ) ) {
-                       throw new \MWException( 'Workflow is non-unique, can 
only fetch object by title + id' );
-               }
-               $found = $storage->find( array(
-                       'workflow_definition_id' => $definition->getId(),
-                       'workflow_wiki' => $title->isLocal() ? wfWikiId() : 
$title->getTransWikiID(),
-                       'workflow_page_id' => $title->getArticleID(),
-               ) );
-               if ( $found ) {
-                       $workflow = reset( $found );
-               } else {
-                       $workflow = Workflow::create( $definition, $wgUser, 
$title );
-               }
-
-               return array( $workflow, $definition );
-       }
-
-       protected function loadWorkflowById( \Title $title, $workflowId ) {
-               $workflow = $this->container['storage.workflow']->get( 
UUID::create( $workflowId ) );
-               if ( !$workflow ) {
-                       throw new \MWException( 'Invalid workflow requested by 
id' );
-               }
-               if ( !$workflow->matchesTitle( $title ) ) {
-                       // todo: redirect?
-                       throw new \MWException( 'Flow workflow is for different 
page' );
-               }
-               $definition = $this->container['storage.definition']->get( 
$workflow->getDefinitionId() );
-               if ( !$definition ) {
-                       throw new \MWException( 'Flow workflow references 
unknown definition id: ' . $workflow->getDefinitionId() );
-               }
-
-               return array( $workflow, $definition );
-       }
-
-       protected function createBlocks( Workflow $workflow, Definition 
$definition ) {
-               switch( $definition->getType() ) {
-               case 'discussion':
-                       return array(
-                               'summary' => new SummaryBlock( $workflow, 
$this->container['storage'] ),
-                               'topics' => new TopicListBlock( $workflow, 
$this->container['storage'], $this->container['loader.root_post'] ),
-                       );
-
-               case 'topic':
-                       return array(
-                               'topic' => new TopicBlock( $workflow, 
$this->container['storage'], $this->container['loader.root_post'] ),
-                       );
-
-               default:
-                       throw new \MWException( 'Not Implemented' );
-               }
-       }
-
-       protected function handleSubmit( Workflow $workflow, Definition 
$definition, $action, array $blocks ) {
-
-               $request = $this->getRequest();
-               $user = $this->container['user'];
-               $success = true;
-               $interestedBlocks = array();
-
-               foreach ( $blocks as $block ) {
-                       $data = $request->getArray( $block->getName(), array() 
);
-                       $result = $block->onSubmit( $action, $user, $data );
-                       if ( $result !== null ) {
-                               $interestedBlocks[] = $block;
-                               $success &= $result;
-                       }
-               }
-               if ( !$interestedBlocks ) {
-                       if ( !$blocks ) {
-                               throw new \MWException( 'No Blocks?!?' );
-                       }
-                       $type = array();
-                       foreach ( $blocks as $block ) {
-                               $type[] = get_class( $block );
-                       }
-                       // All blocks returned null, nothing knows how to 
handle this action
-                       throw new \MWException( "No block accepted the 
'$action' action: " .  implode( ',', array_unique( $type ) ) );
-               }
-               return $success ? $interestedBlocks : array();
-       }
-
-       public function commit( Workflow $workflow, array $blocks ) {
-               $this->container['storage.workflow']->put( $workflow );
-               foreach ( $blocks as $block ) {
-                       $block->commit();
-               }
        }
 
        protected function redirect( Workflow $workflow, $action = 'view', 
array $query = array() ) {

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I3115567c29af31c6754e297f1f3fc3485edb9deb
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Werdna <agarr...@wikimedia.org>
Gerrit-Reviewer: EBernhardson (WMF) <ebernhard...@wikimedia.org>
Gerrit-Reviewer: Matthias Mullie <mmul...@wikimedia.org>
Gerrit-Reviewer: Werdna <agarr...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to