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