Legoktm has uploaded a new change for review.

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


Change subject: [WIP] Revamp API
......................................................................

[WIP] Revamp API

The API is now a modular module, similar to
action=query. This means that each submodule
can properly provide help messages and hints for
extensions like ApiSandbox.

The old API was renamed to action=flow-old, and
should be removed after the new one is complete.

Change-Id: I09f53f8288ad86fee30dc0a4ce4b9af748aea792
---
M Flow.php
M includes/api/ApiFlow.php
A includes/api/ApiFlowBase.php
A includes/api/ApiFlowNewTopic.php
A includes/api/ApiFlowOld.php
M modules/base/ext.flow.base.js
6 files changed, 547 insertions(+), 92 deletions(-)


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

diff --git a/Flow.php b/Flow.php
index 551538b..e357937 100755
--- a/Flow.php
+++ b/Flow.php
@@ -156,9 +156,13 @@
 $wgAutoloadClasses['ApiQueryFlow'] = "$dir/includes/api/ApiQueryFlow.php";
 $wgAutoloadClasses['ApiParsoidUtilsFlow'] = 
"$dir/includes/api/ApiParsoidUtilsFlow.php";
 $wgAutoloadClasses['ApiFlow'] = "$dir/includes/api/ApiFlow.php";
+$wgAutoloadClasses['ApiFlowBase'] = "$dir/includes/api/ApiFlowBase.php";
+$wgAutoloadClasses['ApiFlowNewTopic'] = 
"$dir/includes/api/ApiFlowNewTopic.php";
+
+$wgAutoloadClasses['ApiFlowOld'] = "$dir/includes/api/ApiFlowOld.php";
 $wgAPIListModules['flow'] = 'ApiQueryFlow';
 $wgAPIModules['flow-parsoid-utils'] = 'ApiParsoidUtilsFlow';
-$wgAPIModules['flow'] = 'ApiFlow';
+$wgAPIModules['flow-old'] = 'ApiFlowOld';
 
 // Housekeeping hooks
 $wgHooks['LoadExtensionSchemaUpdates'][] = 'FlowHooks::getSchemaUpdates';
diff --git a/includes/api/ApiFlow.php b/includes/api/ApiFlow.php
index 53c23e5..b7cfee5 100644
--- a/includes/api/ApiFlow.php
+++ b/includes/api/ApiFlow.php
@@ -7,99 +7,60 @@
        /** @var Flow\Container $container */
        protected $container;
 
+       /** @var \Flow\WorkflowLoader $loader */
+       protected $loader;
+
+       /** @var ApiModuleManager $modulemanager */
+       protected $modulemanager;
+
+       private $params;
+
+       static $modules = array(
+               'new-topic' => 'ApiFlowNewTopic',
+               'edit-header' => 'ApiFlowEditHeader',
+               'edit-post' => 'ApiFlowEditPost',
+               'reply' => 'ApiFlowReply',
+               'moderate-topic' => 'ApiFlowModerateTopic',
+               'moderate-post' => 'ApiFlowModeratePost',
+               'hide-post' => 'ApiFlowHidePost',
+               'delete-post' => 'ApiFlowDeletePost',
+               'suppress-post' => 'ApiFlowSuppressPost',
+               'restore-post' => 'ApiFlowRestorePost',
+               'edit-title' => 'ApiFlowEditTitle',
+       );
+
+       public static function getActionFromClass( $classname ) {
+               $flip = array_flip( self::$modules );
+               return $flip[$classname];
+       }
+
+       public function __construct( $main, $action ) {
+               parent::__construct( $main, $action );
+               $this->modulemanager = new ApiModuleManager( $this );
+               $this->modulemanager->addModules( self::$modules, 'submodule' );
+       }
+
+       public function getModuleManager() {
+               return $this->modulemanager;
+       }
+
        public function execute() {
-               $this->container = Flow\Container::getContainer();
-               $params = $this->extractRequestParams();
-               $output = array();
-
-               if ( ! $params['workflow'] && ! $params['page'] ) {
-                       $this->dieUsage( 'One of workflow or page parameters 
must be provided', 'badparams' );
-                       return;
-               }
-
-               $id = UUID::create( $params['workflow'] );
-               $page = false;
-               if ( $params['page'] ) {
-                       $page = Title::newFromText( $params['page'] );
-               }
-               $this->loader = $this->container['factory.loader.workflow']
-                       ->createWorkflowLoader( $page, $id );
-               $occupationController = 
$this->container['occupation_controller'];
-               $workflow = $this->loader->getWorkflow();
-               $article = new Article( $workflow->getArticleTitle(), 0 );
-
-               // @todo: this is a hack; see ParsoidUtils::convert
-               global $wgFlowParsoidTitle;
-               $wgFlowParsoidTitle = $workflow->getArticleTitle();
-
-               $isNew = $workflow->isNew();
-               // Is this making unnecesary db round trips?
-               if ( !$isNew ) {
-                       $occupationController->ensureFlowRevision( $article );
-               }
-               $requestParams = json_decode( $params['params'], true );
-
-               if ( ! $requestParams ) {
-                       $this->dieUsage( 'The params parameter must be a valid 
JSON string', 'badparams' );
-                       return;
-               }
-
-               $request = new DerivativeRequest( $this->getRequest(), 
$requestParams, true );
-
-               $blocks = $this->loader->createBlocks();
-               $action = $params['flowaction'];
-               $user = $this->getUser();
-
-               foreach( $blocks as $block ) {
-                       $block->init( $action, $user );
-               }
-
-               $blocksToCommit = $this->loader->handleSubmit( $action, 
$blocks, $user, $request );
-               if ( $blocksToCommit ) {
-                       $commitResults = $this->loader->commit( 
$this->loader->getWorkflow(), $blocksToCommit );
-
-                       $savedBlocks = array( '_element' => 'block' );
-
-                       foreach( $blocksToCommit as $block ) {
-                               $savedBlocks[] = $block->getName();
+               $this->params = $this->extractRequestParams();
+               $modules = array();
+               $this->instantiateModules( $modules, 'submodule' );
+               // This is some of the logic from ApiQuery::initModules
+               $wasPosted = $this->getRequest()->wasPosted();
+               /** @var $module ApiFlowBase */
+               foreach ( $modules as $moduleName => $module ) {
+                       if ( !$wasPosted && $module->mustBePosted() ) {
+                               $this->dieUsageMsg( array( 'mustbeposted', 
$moduleName ) );
                        }
-
-                       $output[$action] = array(
-                               'result' => array(),
-                       );
-
-                       $doRender = ( $params['render'] != false );
-
-                       foreach( $commitResults as $key => $value ) {
-                               $output[$action]['result'][$key] = 
$this->processCommitResult( $value, $doRender );
-                       }
-                       if ( $isNew && !$workflow->isNew() ) {
-                               // Workflow was just created, ensure its 
underlying page is owned by flow
-                               $occupationController->ensureFlowRevision( 
$article );
-                       }
-               } else {
-                       $output[$action] = array(
-                               'result' => 'error',
-                               'errors' => array(),
-                       );
-
-                       foreach( $blocks as $block ) {
-                               if ( $block->hasErrors() ) {
-                                       $errors = $block->getErrors();
-                                       $nativeErrors = array();
-
-                                       foreach( $errors as $key ) {
-                                               $nativeErrors[$key]['message'] 
= $block->getErrorMessage( $key )->plain();
-                                               $nativeErrors[$key]['extra'] = 
$block->getErrorExtra( $key );
-                                       }
-
-                                       
$output[$action]['errors'][$block->getName()] = $nativeErrors;
-                               }
-                       }
+                       $module->profileIn();
+                       $module->execute();
+                       wfRunHooks( 'APIFlowAfterExecute', $module );
+                       $module->profileOut();
                }
 
-               $this->getResult()->addValue( null, $this->getModuleName(), 
$output );
-               return true;
        }
 
        protected function processCommitResult( $result, $render = true ) {
@@ -135,14 +96,39 @@
                return $output;
        }
 
+       /**
+        * Create instances of all modules requested by the client
+        * @fixme Copied from ApiQuery::instantiateModules, don't duplicate code
+        * @param array $modules to append instantiated modules to
+        * @param string $param Parameter name to read modules from
+        */
+       private function instantiateModules( &$modules, $param ) {
+               if ( isset( $this->params[$param] ) ) {
+                       foreach ( $this->params[$param] as $moduleName ) {
+                               $instance = $this->modulemanager->getModule( 
$moduleName, $param );
+                               if ( $instance === null ) {
+                                       ApiBase::dieDebug( __METHOD__, 'Error 
instantiating module' );
+                               }
+                               // Ignore duplicates. TODO 2.0: die()?
+                               if ( !array_key_exists( $moduleName, $modules ) 
) {
+                                       $modules[$moduleName] = $instance;
+                               }
+                       }
+               }
+       }
+
+       // @fixme need help generation code from ApiQuery
+
+
        public function getDescription() {
                 return 'Allows actions to be taken on Flow Workflows';
        }
 
        public function getAllowedParams() {
                return array(
-                       'flowaction' => array(
+                       'submodule' => array(
                                ApiBase::PARAM_REQUIRED => true,
+                               ApiBase::PARAM_TYPE => 
$this->modulemanager->getNames( 'submodules' ),
                        ),
                        'workflow' => array(
                                ApiBase::PARAM_DFLT => null,
diff --git a/includes/api/ApiFlowBase.php b/includes/api/ApiFlowBase.php
new file mode 100644
index 0000000..b4fcf98
--- /dev/null
+++ b/includes/api/ApiFlowBase.php
@@ -0,0 +1,222 @@
+<?php
+
+use Flow\WorkflowLoader;
+use Flow\Model\UUID;
+use Flow\Container;
+use Flow\TalkpageManager;
+use Flow\Block\AbstractBlock;
+
+
+abstract class ApiFlowBase extends ApiBase {
+
+       /**
+        * Use $this->getContainer() to access this
+        * @var Container $container
+        */
+       private $container;
+
+       /** @var Title|bool $page */
+       protected $page;
+
+       /** @var WorkflowLoader $loader */
+       protected $loader;
+
+       /** @var TalkpageManager $controller */
+       protected $controller;
+
+       public function __construct( $api, $modName, $prefix = '' ) {
+               parent::__construct( $api->getMain(), $modName, $prefix );
+       }
+
+       /*
+        * Return the name of the flow action
+        * @return string
+        */
+       protected function getAction() {
+               return ApiFlow::getActionFromClass( __CLASS__ );
+       }
+
+
+       /**
+        *
+        * @return bool|UUID
+        */
+       protected function getId() {
+               $params = $this->extractRequestParams();
+               if ( isset( $params['workflow'] ) ) {
+                       return UUID::create( $params['workflow'] );
+               }
+
+               return false;
+       }
+
+       /**
+        * @return Title|bool false if no title provided
+        */
+       protected function getPage() {
+               if ( $this->page === null ) {
+                       $params = $this->extractRequestParams();
+                       if ( isset( $params['page'] ) ) {
+                               $this->page = Title::newFromText( 
$params['page'] );
+                               if ( !$this->page ) {
+                                       $this->dieUsage( 'Invalid page 
provided', 'invalid-page' );
+                               }
+                       } else {
+                               $this->page = false;
+                       }
+               }
+
+               return $this->page;
+       }
+
+       /**
+        * @return Container
+        */
+       protected function getContainer() {
+               if ( is_null( $this->container ) ) {
+                       $this->container = Container::getContainer();
+               }
+
+               return $this->container;
+       }
+
+       /**
+        * @return WorkflowLoader
+        */
+       protected function getLoader() {
+               if ( $this->loader === null ) {
+                       $container = $this->getContainer();
+                       $this->loader = $container['factory.loader.workflow']
+                               ->createWorkflowLoader( $this->getPage(), 
$this->getId() );
+               }
+
+               return $this->loader;
+       }
+
+       /**
+        * @return TalkpageManager
+        */
+       protected function getOccupationController() {
+               if ( $this->controller === null ) {
+                       $container = $this->getContainer();
+                       $this->controller = $container['occupation_controller'];
+               }
+
+               return $this->controller;
+       }
+
+       abstract protected function getUsedParameters();
+
+       /**
+        * @return DerivativeRequest
+        */
+       protected function getModifiedRequest() {
+               $params = array();
+               $extracted = $this->extractRequestParams();
+               foreach ( $this->getUsedParameters() as $name ) {
+                       // @todo check if we can just use $extracted directly
+                       $params[$name] = $extracted[$name];
+               }
+
+               return new DerivativeRequest(
+                       $this->getRequest(),
+                       $params,
+                       $this->getRequest()->wasPosted() // Note that we also 
enforce this at the API level.
+               );
+       }
+
+       public function execute() {
+               $loader = $this->getLoader();
+               $blocks = $loader->createBlocks();
+               /** @var \Flow\Model\Workflow $workflow */
+               $workflow = $loader->getWorkflow();
+               $action = $this->getAction();
+               $controller = $this->getOccupationController();
+               $user = $this->getUser();
+               $params = $this->extractRequestParams();
+
+               $isNew = $workflow->isNew();
+               $article = new Article( $workflow->getArticleTitle(), 0 );
+
+               // @fixme: this is a hack; see ParsoidUtils::convert
+               global $wgFlowParsoidTitle;
+               $wgFlowParsoidTitle = $workflow->getArticleTitle();
+
+               $isNew = $workflow->isNew();
+               // Is this making unnecesary db round trips?
+               if ( !$isNew ) {
+                       $controller->ensureFlowRevision( $article );
+               }
+
+               /** @var AbstractBlock $block */
+               foreach ( $blocks as $block ) {
+                       $block->init( $action, $user );
+               }
+
+               $blocksToCommit = $loader->handleSubmit( $action, $blocks, 
$user, $this->getModifiedRequest() );
+               if ( $blocksToCommit ) {
+                       $commitResults = $loader->commit( $workflow, 
$blocksToCommit );
+                       $savedBlocks = array( '_element' => 'block' );
+
+                       foreach( $blocksToCommit as $block ) {
+                               $savedBlocks[] = $block->getName();
+                       }
+
+                       $output[$action] = array(
+                               'result' => array(),
+                       );
+
+                       $doRender = ( $params['render'] != false );
+
+                       foreach( $commitResults as $key => $value ) {
+                               $output[$action]['result'][$key] = 
$this->processCommitResult( $value, $doRender );
+                       }
+                       if ( $isNew && !$workflow->isNew() ) {
+                               // Workflow was just created, ensure its 
underlying page is owned by flow
+                               $controller->ensureFlowRevision( $article );
+                       }
+               } else {
+                       $output[$action] = array(
+                               'result' => 'error',
+                               'errors' => array(),
+                       );
+
+                       foreach( $blocks as $block ) {
+                               if ( $block->hasErrors() ) {
+                                       $errors = $block->getErrors();
+                                       $nativeErrors = array();
+
+                                       foreach( $errors as $key ) {
+                                               $nativeErrors[$key]['message'] 
= $block->getErrorMessage( $key )->plain();
+                                               $nativeErrors[$key]['extra'] = 
$block->getErrorExtra( $key );
+                                       }
+
+                                       
$output[$action]['errors'][$block->getName()] = $nativeErrors;
+                               }
+                       }
+               }
+
+               $this->getResult()->addValue( null, $this->getModuleName(), 
$output );
+       }
+
+       protected function processCommitResult( $result, $render = true ) {
+               $container = $this->getContainer();
+               $templating = $container['templating'];
+               $output = array();
+               foreach( $result as $key => $value ) {
+                       if ( $value instanceof UUID ) {
+                               $output[$key] = $value->getHex();
+                       } elseif ( $key === 'render-function' ) {
+                               if ( $render ) {
+                                       $function = $value;
+                                       $output['rendered'] = $function( 
$templating );
+                               }
+                       } else {
+                               $output[$key] = $value;
+                       }
+               }
+
+               return $output;
+       }
+
+}
\ No newline at end of file
diff --git a/includes/api/ApiFlowNewTopic.php b/includes/api/ApiFlowNewTopic.php
new file mode 100644
index 0000000..31c26d5
--- /dev/null
+++ b/includes/api/ApiFlowNewTopic.php
@@ -0,0 +1,47 @@
+<?php
+
+class ApiFlowNewTopic extends ApiFlowBase {
+
+       public function __construct( $api ) {
+               parent::__construct( $api, 'new-topic', 'nt' );
+       }
+
+       /**
+        * Taken from ext.flow.base.js
+        * @return array
+        */
+       protected function getUsedParameters() {
+               // @todo can we just use getAllowedParams?
+               return array( 'topic', 'content' );
+       }
+
+       public function getAllowedParams() {
+               // @todo do we need to array_merge with parent?
+               return array(
+                       'topic' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'content' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+               );
+       }
+
+       public function getParamDescription() {
+               return array(
+                       'topic' => 'Text for new topic header',
+                       'content' => 'Content for new topic',
+               );
+       }
+
+       public function getDescription() {
+               return 'Creates a new Flow topic on the given workflow';
+       }
+
+       public function getExamples() {
+               return array(
+                       
'api.php?action=flow&submodule=new-topic&nttopic=Hi&ntcontent=Nice%20to&20meet%20you&workflow=',
+               );
+       }
+
+}
\ No newline at end of file
diff --git a/includes/api/ApiFlowOld.php b/includes/api/ApiFlowOld.php
new file mode 100644
index 0000000..4119bf0
--- /dev/null
+++ b/includes/api/ApiFlowOld.php
@@ -0,0 +1,196 @@
+<?php
+
+use Flow\Model\UUID;
+
+class ApiFlowOld extends ApiBase {
+
+       /** @var Flow\Container $container */
+       protected $container;
+
+       /** @var \Flow\WorkflowLoader $loader */
+       protected $loader;
+
+       public function execute() {
+               $this->container = Flow\Container::getContainer();
+               $params = $this->extractRequestParams();
+               $output = array();
+
+               if ( ! $params['workflow'] && ! $params['page'] ) {
+                       $this->dieUsage( 'One of workflow or page parameters 
must be provided', 'badparams' );
+                       return;
+               }
+
+               $id = UUID::create( $params['workflow'] );
+               $page = false;
+               if ( $params['page'] ) {
+                       $page = Title::newFromText( $params['page'] );
+               }
+               $this->loader = $this->container['factory.loader.workflow']
+                       ->createWorkflowLoader( $page, $id );
+               $occupationController = 
$this->container['occupation_controller'];
+               $workflow = $this->loader->getWorkflow();
+               $article = new Article( $workflow->getArticleTitle(), 0 );
+
+               // @todo: this is a hack; see ParsoidUtils::convert
+               global $wgFlowParsoidTitle;
+               $wgFlowParsoidTitle = $workflow->getArticleTitle();
+
+               $isNew = $workflow->isNew();
+               // Is this making unnecesary db round trips?
+               if ( !$isNew ) {
+                       $occupationController->ensureFlowRevision( $article );
+               }
+               $requestParams = json_decode( $params['params'], true );
+
+               if ( ! $requestParams ) {
+                       $this->dieUsage( 'The params parameter must be a valid 
JSON string', 'badparams' );
+                       return;
+               }
+
+               $request = new DerivativeRequest( $this->getRequest(), 
$requestParams, true );
+
+               $blocks = $this->loader->createBlocks();
+               $action = $params['flowaction'];
+               $user = $this->getUser();
+
+               foreach( $blocks as $block ) {
+                       $block->init( $action, $user );
+               }
+
+               $blocksToCommit = $this->loader->handleSubmit( $action, 
$blocks, $user, $request );
+               if ( $blocksToCommit ) {
+                       $commitResults = $this->loader->commit( 
$this->loader->getWorkflow(), $blocksToCommit );
+
+                       $savedBlocks = array( '_element' => 'block' );
+
+                       foreach( $blocksToCommit as $block ) {
+                               $savedBlocks[] = $block->getName();
+                       }
+
+                       $output[$action] = array(
+                               'result' => array(),
+                       );
+
+                       $doRender = ( $params['render'] != false );
+
+                       foreach( $commitResults as $key => $value ) {
+                               $output[$action]['result'][$key] = 
$this->processCommitResult( $value, $doRender );
+                       }
+                       if ( $isNew && !$workflow->isNew() ) {
+                               // Workflow was just created, ensure its 
underlying page is owned by flow
+                               $occupationController->ensureFlowRevision( 
$article );
+                       }
+               } else {
+                       $output[$action] = array(
+                               'result' => 'error',
+                               'errors' => array(),
+                       );
+
+                       foreach( $blocks as $block ) {
+                               if ( $block->hasErrors() ) {
+                                       $errors = $block->getErrors();
+                                       $nativeErrors = array();
+
+                                       foreach( $errors as $key ) {
+                                               $nativeErrors[$key]['message'] 
= $block->getErrorMessage( $key )->plain();
+                                               $nativeErrors[$key]['extra'] = 
$block->getErrorExtra( $key );
+                                       }
+
+                                       
$output[$action]['errors'][$block->getName()] = $nativeErrors;
+                               }
+                       }
+               }
+
+               $this->getResult()->addValue( null, $this->getModuleName(), 
$output );
+               return true;
+       }
+
+       protected function processCommitResult( $result, $render = true ) {
+               $templating = $this->container['templating'];
+               $output = array();
+               foreach( $result as $key => $value ) {
+                       if ( $value instanceof UUID ) {
+                               $output[$key] = $value->getHex();
+                       } elseif ( $key === 'render-function' ) {
+                               if ( $render ) {
+                                       $function = $value;
+                                       $output['rendered'] = $function( 
$templating );
+                               }
+                       } else {
+                               $output[$key] = $value;
+                       }
+               }
+
+               return $output;
+       }
+
+       protected function doRerender( $blocks ) {
+               $templating = $this->container['templating'];
+
+               $output = array();
+
+               $output['count'] = count( $blocks );
+
+               foreach( $blocks as $block ) {
+                       $output[$block->getName()] = $block->render( 
$templating, array() );
+               }
+
+               return $output;
+       }
+
+       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_DFLT => null,
+                       ),
+                       'page' => null,
+                       'params' => array(
+                               ApiBase::PARAM_DFLT => '{}',
+                       ),
+                       'token' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'render' => 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 from 
api.php?action=tokens&type=flow',
+                       'render' => 'Set this to something to include a 
block-specific rendering in the output',
+               );
+       }
+
+       public function getExamples() {
+               // @todo: fill out an example
+                return array(
+                        ''
+                        => ''
+                );
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function needsToken() {
+               return true;
+       }
+
+       public function getTokenSalt() {
+               global $wgFlowTokenSalt;
+               return $wgFlowTokenSalt;
+       }
+}
diff --git a/modules/base/ext.flow.base.js b/modules/base/ext.flow.base.js
index 192b1a9..e128f50 100644
--- a/modules/base/ext.flow.base.js
+++ b/modules/base/ext.flow.base.js
@@ -25,7 +25,7 @@
                                )
                                .done( function ( data ) {
                                        var request = {
-                                               'action' : 'flow',
+                                               'action' : 'flow-old',
                                                'flowaction' : action,
                                                'params' : $.toJSON( options ),
                                                'token' : data.tokens.flowtoken

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

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

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

Reply via email to