Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiFlow
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 10
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getModuleManager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 getPage
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 isWriteMode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mustBePosted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow\Api;
4
5use Flow\Container;
6use Flow\Hooks\HookRunner;
7use MediaWiki\Api\ApiBase;
8use MediaWiki\Api\ApiMain;
9use MediaWiki\Api\ApiModuleManager;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\Title\Title;
12use Wikimedia\ParamValidator\ParamValidator;
13
14class ApiFlow extends ApiBase {
15
16    /**
17     * @var ApiModuleManager
18     */
19    private $moduleManager;
20
21    private const ALWAYS_ENABLED_MODULES = [
22        // POST
23        'new-topic' => \Flow\Api\ApiFlowNewTopic::class,
24        'edit-header' => \Flow\Api\ApiFlowEditHeader::class,
25        'edit-post' => \Flow\Api\ApiFlowEditPost::class,
26        'edit-topic-summary' => \Flow\Api\ApiFlowEditTopicSummary::class,
27        'reply' => \Flow\Api\ApiFlowReply::class,
28        'moderate-post' => \Flow\Api\ApiFlowModeratePost::class,
29        'moderate-topic' => \Flow\Api\ApiFlowModerateTopic::class,
30        'edit-title' => \Flow\Api\ApiFlowEditTitle::class,
31        'lock-topic' => \Flow\Api\ApiFlowLockTopic::class,
32        'close-open-topic' => \Flow\Api\ApiFlowLockTopic::class, // BC: has been renamed to lock-topic
33        'undo-edit-header' => \Flow\Api\ApiFlowUndoEditHeader::class,
34        'undo-edit-post' => \Flow\Api\ApiFlowUndoEditPost::class,
35        'undo-edit-topic-summary' => \Flow\Api\ApiFlowUndoEditTopicSummary::class,
36
37        // GET
38        // action 'view' exists in Topic.php & TopicList.php, for topic, post &
39        // topiclist - we'll want to know topic-/post- or topiclist-view ;)
40        'view-topiclist' => \Flow\Api\ApiFlowViewTopicList::class,
41        'view-post' => \Flow\Api\ApiFlowViewPost::class,
42        'view-post-history' => \Flow\Api\ApiFlowViewPostHistory::class,
43        'view-topic' => \Flow\Api\ApiFlowViewTopic::class,
44        'view-topic-history' => \Flow\Api\ApiFlowViewTopicHistory::class,
45        'view-header' => \Flow\Api\ApiFlowViewHeader::class,
46        'view-topic-summary' => \Flow\Api\ApiFlowViewTopicSummary::class,
47    ];
48
49    /**
50     * @param ApiMain $main
51     * @param string $action
52     */
53    public function __construct( $main, $action ) {
54        parent::__construct( $main, $action );
55        $this->moduleManager = new ApiModuleManager(
56            $this,
57            MediaWikiServices::getInstance()->getObjectFactory()
58        );
59
60        $enabledModules = self::ALWAYS_ENABLED_MODULES;
61
62        $this->moduleManager->addModules( $enabledModules, 'submodule' );
63    }
64
65    public function getModuleManager() {
66        return $this->moduleManager;
67    }
68
69    public function execute() {
70        // To avoid API warning, register the parameter used to bust browser cache
71        $this->getMain()->getVal( '_' );
72
73        $params = $this->extractRequestParams();
74        /** @var ApiFlowBase $module */
75        $module = $this->moduleManager->getModule( $params['submodule'], 'submodule' );
76        '@phan-var ApiFlowBase $module';
77
78        // The checks for POST and tokens are the same as ApiMain.php
79        $wasPosted = $this->getRequest()->wasPosted();
80        if ( !$wasPosted && $module->mustBePosted() ) {
81            $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $params['submodule'] ] );
82        }
83
84        if ( $module->needsToken() ) {
85            if ( !isset( $params['token'] ) ) {
86                $this->dieWithError( [ 'apierror-missingparam', 'token' ] );
87            }
88
89            $module->requirePostedParameters( [ 'token' ] );
90
91            if ( !$module->validateToken( $params['token'], $params ) ) {
92                $this->dieWithError( 'apierror-badtoken' );
93            }
94        }
95
96        $module->extractRequestParams();
97        if ( $module->needsPage() ) {
98            $module->setPage( $this->getPage( $params ) );
99        }
100        $module->execute();
101        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onAPIFlowAfterExecute( $module );
102    }
103
104    /**
105     * @param array $params
106     * @return Title
107     */
108    protected function getPage( $params ) {
109        $page = Title::newFromText( $params['page'] );
110        if ( !$page ) {
111            $this->dieWithError(
112                [ 'apierror-invalidtitle', wfEscapeWikiText( $params['page'] ) ], 'invalid-page'
113            );
114        }
115
116        if ( $page->getNamespace() < 0 ) {
117            $this->dieWithError(
118                [ 'apierror-invalidtitle', wfEscapeWikiText( $params['page'] ) ], 'invalid-page-negative-namespace'
119            );
120        }
121
122        /** @var \Flow\TalkpageManager $controller */
123        $controller = Container::get( 'occupation_controller' );
124        if ( $page->getContentModel() !== CONTENT_MODEL_FLOW_BOARD ) {
125            // Just check for permissions, nothing else to do. The Flow board
126            // will be put in place right before the rest of the data is stored
127            // (in SubmissionHandler::commit), after everything's been validated.
128            $status = $controller->safeAllowCreation( $page, $this->getUser(),
129                /* $mustNotExist = */ true, /* $forWrite = */ false );
130            if ( !$status->isGood() ) {
131                $this->dieWithError( [ 'apierror-flow-safeallowcreationfailed', $status->getMessage() ], 'invalid-page' );
132            }
133        }
134
135        // @phan-suppress-next-line PhanTypeMismatchReturnNullable T240141
136        return $page;
137    }
138
139    public function getAllowedParams() {
140        return [
141            'submodule' => [
142                ParamValidator::PARAM_REQUIRED => true,
143                ParamValidator::PARAM_TYPE => 'submodule',
144            ],
145            'page' => [
146                // supply bogus default - not every action may *need* ?page=
147                ParamValidator::PARAM_DEFAULT => Title::newFromText( 'Flow-enabled page', NS_TOPIC )->getPrefixedDBkey(),
148            ],
149            'token' => '',
150        ];
151    }
152
153    public function isWriteMode() {
154        // We can't use extractRequestParams() here because getHelpFlags() calls this function,
155        // and we'd error out because the submodule parameter isn't set.
156        $moduleName = $this->getMain()->getVal( 'submodule' );
157        $module = $this->moduleManager->getModule( $moduleName, 'submodule' );
158        return $module && $module->isWriteMode();
159    }
160
161    public function getHelpUrls() {
162        return [
163            'https://www.mediawiki.org/wiki/Extension:Flow/API',
164        ];
165    }
166
167    /**
168     * @inheritDoc
169     */
170    protected function getExamplesMessages() {
171        return [
172            'action=flow&submodule=edit-header&page=Talk:Sandbox&ehprev_revision=???&ehcontent=Nice%20to&20meet%20you'
173                => 'apihelp-flow-example-1',
174        ];
175    }
176
177    public function mustBePosted() {
178        return false;
179    }
180
181    public function needsToken() {
182        return false;
183    }
184}