Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
SubmissionHandler
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 3
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 handleSubmit
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
156
 commit
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace Flow;
4
5use Flow\Block\AbstractBlock;
6use Flow\Block\Block;
7use Flow\Data\ManagerGroup;
8use Flow\Exception\FailCommitException;
9use Flow\Exception\InvalidActionException;
10use Flow\Exception\InvalidDataException;
11use Flow\Model\Workflow;
12use FormatJson;
13use IContextSource;
14use MediaWiki\Deferred\DeferredUpdates;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\MediaWikiServices;
17use SplQueue;
18
19class SubmissionHandler {
20
21    /**
22     * @var ManagerGroup
23     */
24    protected $storage;
25
26    /**
27     * @var DbFactory
28     */
29    protected $dbFactory;
30
31    /**
32     * @var SplQueue Updates to add to DeferredUpdates post-commit
33     */
34    protected $deferredQueue;
35
36    public function __construct(
37        ManagerGroup $storage,
38        DbFactory $dbFactory,
39        SplQueue $deferredQueue
40    ) {
41        $this->storage = $storage;
42        $this->dbFactory = $dbFactory;
43        $this->deferredQueue = $deferredQueue;
44    }
45
46    /**
47     * @param Workflow $workflow
48     * @param IContextSource $context
49     * @param AbstractBlock[] $blocks
50     * @param string $action
51     * @param array $parameters
52     * @return AbstractBlock[]
53     * @throws InvalidActionException
54     * @throws InvalidDataException
55     */
56    public function handleSubmit(
57        Workflow $workflow,
58        IContextSource $context,
59        array $blocks,
60        $action,
61        array $parameters
62    ) {
63        // since this is a submit force dbFactory to always return primary
64        $this->dbFactory->forcePrimary();
65
66        /** @var Block[] $interestedBlocks */
67        $interestedBlocks = [];
68        foreach ( $blocks as $block ) {
69            // This is just a check whether the block understands the action,
70            // Doesn't consider permissions
71            if ( $block->canSubmit( $action ) ) {
72                $block->init( $context, $action );
73                $interestedBlocks[] = $block;
74            }
75        }
76
77        if ( !$interestedBlocks ) {
78            if ( !$blocks ) {
79                throw new InvalidDataException( 'No Blocks?!?', 'fail-load-data' );
80            }
81            $type = [];
82            foreach ( $blocks as $block ) {
83                $type[] = get_class( $block );
84            }
85            // All blocks returned null, nothing knows how to handle this action
86            throw new InvalidActionException( "No block accepted the '$action' action: " .
87                implode( ',', array_unique( $type ) ), 'invalid-action' );
88        }
89
90        // Check mediawiki core permissions for title protection, blocked
91        // status, etc.
92        $errors = $workflow->getPermissionErrors( 'edit', $context->getUser(), 'secure' );
93        if ( count( $errors ) ) {
94            LoggerFactory::getInstance( 'Flow' )->debug( 'Got permission errors for user {user} attempting action "{action}".',
95                [
96                    'action' => $action,
97                    'user' => $context->getUser()->getName(),
98                    'errors' => FormatJson::encode( $errors )
99                ]
100            );
101            foreach ( $errors as $errorMsgArgs ) {
102                $msg = wfMessage( array_shift( $errorMsgArgs ) );
103                if ( $errorMsgArgs ) {
104                    $msg->params( $errorMsgArgs );
105                }
106                // I guess this is the "user block" meaning of 'block'.  If
107                // so, this is misleading, since it could be protection,
108                // etc.  The specific error message (protect, block, etc.)
109                // will still be output, though.
110                // In theory, something could be relying on the string 'block',
111                // since it's exposed to the API, but probably not.
112                reset( $interestedBlocks )->addError( 'block', $msg );
113            }
114            return [];
115        }
116
117        $success = true;
118        foreach ( $interestedBlocks as $block ) {
119            $name = $block->getName();
120            $data = $parameters[$name] ?? [];
121            $success = $block->onSubmit( $data ) && $success;
122        }
123
124        return $success ? $interestedBlocks : [];
125    }
126
127    /**
128     * @param Workflow $workflow
129     * @param AbstractBlock[] $blocks
130     * @return array Map from committed block name to an array of metadata returned
131     *  about inserted objects.  This must be non-empty.  An empty block array
132     *  indicates there were errors, in which case this method should not be called.
133     * @throws \Exception
134     */
135    public function commit( Workflow $workflow, array $blocks ) {
136        $dbw = $this->dbFactory->getDB( DB_PRIMARY );
137
138        /** @var OccupationController $occupationController */
139        $occupationController = Container::get( 'occupation_controller' );
140        $title = $workflow->getOwnerTitle();
141
142        if ( count( $blocks ) === 0 ) {
143            // This is a logic error in the code, but we need to preserve
144            // consistent state.
145            throw new FailCommitException(
146                __METHOD__ . ' was called with $blocks set to an empty ' .
147                'array or a falsy value.  This indicates the blocks are ' .
148                'not able to commit, so ' . __METHOD__ . ' should not be ' .
149                'called.',
150                'fail-commit'
151            );
152        }
153
154        try {
155            $dbw->startAtomic( __METHOD__ );
156            $services = MediaWikiServices::getInstance();
157            // Create the occupation page/revision if needed
158            $occupationController->ensureFlowRevision(
159                $services->getWikiPageFactory()->newFromTitle( $title ),
160                $workflow
161            );
162            // Create/modify each Flow block as requested
163            $results = [];
164            foreach ( $blocks as $block ) {
165                $results[$block->getName()] = $block->commit();
166            }
167            $dbw->endAtomic( __METHOD__ );
168
169            while ( !$this->deferredQueue->isEmpty() ) {
170                DeferredUpdates::addCallableUpdate( $this->deferredQueue->dequeue() );
171            }
172            $htmlCache = $services->getHtmlCacheUpdater();
173            $htmlCache->purgeTitleUrls( $workflow->getArticleTitle(), $htmlCache::PURGE_INTENT_TXROUND_REFLECTED );
174
175            return $results;
176        } finally {
177            while ( !$this->deferredQueue->isEmpty() ) {
178                $this->deferredQueue->dequeue();
179            }
180        }
181    }
182}