Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.57% covered (danger)
3.57%
2 / 56
5.00% covered (danger)
5.00%
1 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractBlock
3.57% covered (danger)
3.57%
2 / 56
5.00% covered (danger)
5.00%
1 / 20
730.96
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 validate
n/a
0 / 0
n/a
0 / 0
0
 commit
n/a
0 / 0
n/a
0 / 0
0
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getContext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canSubmit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canRender
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getTemplate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 onSubmit
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 wasSubmitted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasErrors
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getErrors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getErrorMessage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getErrorExtra
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addError
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getWorkflow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWorkflowId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStorage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getActionName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 checkSpamFilters
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getEditToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPageTitle
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Flow\Block;
4
5use Flow\Container;
6use Flow\Conversion\Utils;
7use Flow\Data\ManagerGroup;
8use Flow\Exception\InvalidInputException;
9use Flow\FlowActions;
10use Flow\Model\AbstractRevision;
11use Flow\Model\Workflow;
12use Flow\RevisionActionPermissions;
13use IContextSource;
14use MediaWiki\Output\OutputPage;
15
16abstract class AbstractBlock implements Block {
17
18    /** @var Workflow */
19    protected $workflow;
20    /** @var ManagerGroup */
21    protected $storage;
22
23    /** @var IContextSource */
24    protected $context;
25    /** @var array|null */
26    protected $submitted = null;
27    /** @var array */
28    protected $errors = [];
29
30    /**
31     * @var string|null The commitable action being submitted, or null
32     *  for read-only actions.
33     */
34    protected $action;
35
36    /** @var RevisionActionPermissions */
37    protected $permissions;
38
39    /**
40     * A list of supported post actions
41     * @var array
42     */
43    protected $supportedPostActions = [];
44
45    /**
46     * A list of supported get actions
47     * @var array
48     */
49    protected $supportedGetActions = [];
50
51    /**
52     * Templates for each view actions
53     * @var string[]
54     */
55    protected $templates = [];
56
57    public function __construct( Workflow $workflow, ManagerGroup $storage ) {
58        $this->workflow = $workflow;
59        $this->storage = $storage;
60    }
61
62    /**
63     * Called by $this->onSubmit to populate $this->errors based
64     * on $this->action and $this->submitted.
65     */
66    abstract protected function validate();
67
68    /**
69     * @inheritDoc
70     */
71    abstract public function commit();
72
73    /**
74     * @param IContextSource $context
75     * @param string $action
76     */
77    public function init( IContextSource $context, $action ) {
78        $this->context = $context;
79        $this->action = $action;
80        $this->permissions = new RevisionActionPermissions( Container::get( 'flow_actions' ), $context->getUser() );
81    }
82
83    /**
84     * @return IContextSource
85     */
86    public function getContext() {
87        return $this->context;
88    }
89
90    /**
91     * Returns true if the block can submit the requested action, or false
92     * otherwise.
93     *
94     * @param string $action
95     * @return bool
96     */
97    public function canSubmit( $action ) {
98        return in_array( $this->getActionName( $action ), $this->supportedPostActions );
99    }
100
101    /**
102     * Returns true if the block can render the requested action, or false
103     * otherwise.
104     *
105     * @param string $action
106     * @return bool
107     */
108    public function canRender( $action ) {
109        return // GET actions can be rendered
110            in_array( $this->getActionName( $action ), $this->supportedGetActions ) ||
111            // POST actions are usually redirected to 'view' after successfully
112            // completing the request, but can also be rendered (e.g. to show
113            // error message after unsuccessful submission)
114            $this->canSubmit( $action );
115    }
116
117    /**
118     * Get the template name for a specific action or an array of template
119     * for all possible view actions in this block
120     *
121     * @param string|null $action
122     * @return string|array
123     * @throws InvalidInputException
124     */
125    public function getTemplate( $action = null ) {
126        if ( $action === null ) {
127            return $this->templates;
128        }
129        if ( !isset( $this->templates[$action] ) ) {
130            throw new InvalidInputException( 'Template is not defined for action: ' . $action, 'invalid-input' );
131        }
132        return $this->templates[$action];
133    }
134
135    /**
136     * @param array $data
137     * @return bool|null true when accepted, false when not accepted.
138     *  null when this action does not support submission.
139     */
140    public function onSubmit( array $data ) {
141        if ( !$this->canSubmit( $this->action ) ) {
142            return null;
143        }
144
145        $this->submitted = $data;
146        $this->validate();
147
148        return !$this->hasErrors();
149    }
150
151    public function wasSubmitted() {
152        return $this->submitted !== null;
153    }
154
155    /**
156     * Checks if any errors have occurred in the block (no argument), or if a
157     * specific error has occurred (argument being the error type)
158     *
159     * @param string|null $type
160     * @return bool
161     */
162    public function hasErrors( $type = null ) {
163        if ( $type === null ) {
164            return (bool)$this->errors;
165        }
166        return isset( $this->errors[$type] );
167    }
168
169    /**
170     * Returns an array of all error types encountered in this block. The values
171     * in the returned array can be used to pass to getErrorMessage() or
172     * getErrorExtra() to respectively fetch the specific error message or
173     * additional details.
174     *
175     * @return array
176     */
177    public function getErrors() {
178        return array_keys( $this->errors );
179    }
180
181    /**
182     * @param string $type
183     * @return \Message
184     */
185    public function getErrorMessage( $type ) {
186        return $this->errors[$type]['message'] ?? null;
187    }
188
189    /**
190     * @param string $type
191     * @return mixed
192     */
193    public function getErrorExtra( $type ) {
194        return $this->errors[$type]['extra'] ?? null;
195    }
196
197    /**
198     * @param string $type
199     * @param \Message $message
200     * @param mixed|null $extra
201     */
202    public function addError( $type, \Message $message, $extra = null ) {
203        $this->errors[$type] = [
204            'message' => $message,
205            'extra' => $extra,
206        ];
207    }
208
209    public function getWorkflow() {
210        return $this->workflow;
211    }
212
213    public function getWorkflowId() {
214        return $this->workflow->getId();
215    }
216
217    public function getStorage() {
218        return $this->storage;
219    }
220
221    /**
222     * Given a certain action name, this returns the valid action name. This is
223     * meant for BC compatibility with renamed actions.
224     *
225     * @param string $action
226     * @return string
227     */
228    public function getActionName( $action ) {
229        // BC for renamed actions
230        /** @var FlowActions $actions */
231        $actions = Container::get( 'flow_actions' );
232        $alias = $actions->getValue( $action );
233        if ( is_string( $alias ) ) {
234            // All proper actions return arrays, but aliases return a string
235            $action = $alias;
236        }
237
238        return $action;
239    }
240
241    /**
242     * Run through AbuseFilter and friends.
243     * @todo Having to call spamFilter in each place that creates a revision
244     *  is error-prone.
245     *
246     * @param AbstractRevision|null $old null when $new is first revision
247     * @param AbstractRevision $new
248     * @return bool True when content is allowed by spam filter
249     */
250    protected function checkSpamFilters( ?AbstractRevision $old, AbstractRevision $new ) {
251        /** @var \Flow\SpamFilter\Controller $spamFilter */
252        $spamFilter = Container::get( 'controller.spamfilter' );
253        $status = $spamFilter->validate( $this->context, $new, $old, $this->workflow->getArticleTitle(), $this->workflow->getOwnerTitle() );
254        if ( $status->isOK() ) {
255            return true;
256        }
257
258        $message = $status->getMessage();
259
260        $details = $status->getValue();
261
262        $this->addError( 'spamfilter', $message, [
263            'messageKey' => $message->getKey(),
264            'details' => $details,
265        ] );
266        return false;
267    }
268
269    /**
270     * @return string The new edit token
271     */
272    public function getEditToken() {
273        return $this->context->getUser()->getEditToken();
274    }
275
276    /**
277     * @param OutputPage $out
278     */
279    public function setPageTitle( OutputPage $out ) {
280        if ( $out->getPageTitle() ) {
281            // Don't override page title if another block has already set it.
282            // If this should *really* be done, the specific block extending
283            // this AbstractBlock should just implement this itself ;)
284            return;
285        }
286
287        $title = $this->workflow->getArticleTitle();
288        $convertedTitle = Utils::getConvertedTitle( $title );
289        $out->setPageTitle( $convertedTitle );
290    }
291}