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 <[email protected]>
Gerrit-Reviewer: EBernhardson (WMF) <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Werdna <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits