Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.92% covered (warning)
56.92%
37 / 65
53.33% covered (warning)
53.33%
8 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Status
57.81% covered (warning)
57.81%
37 / 64
53.33% covered (warning)
53.33%
8 / 15
67.25
0.00% covered (danger)
0.00%
0 / 1
 wrap
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 __get
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 __set
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 setMessageLocalizer
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFormatter
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 splitByErrorType
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
3.79
 getStatusValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWikiText
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getMessage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getPsr3MessageAndContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHTML
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getErrorsArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWarningsArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __sleep
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 __wakeup
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Generic operation result.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace MediaWiki\Status;
24
25use Language;
26use MediaWiki\Context\IContextSource;
27use MediaWiki\Context\RequestContext;
28use MediaWiki\MediaWikiServices;
29use MediaWiki\Message\Message;
30use MediaWiki\StubObject\StubUserLang;
31use MessageLocalizer;
32use RuntimeException;
33use StatusValue;
34
35/**
36 * Generic operation result class
37 * Has warning/error list, boolean status and arbitrary value
38 *
39 * "Good" means the operation was completed with no warnings or errors.
40 *
41 * "OK" means the operation was partially or wholly completed.
42 *
43 * An operation which is not OK should have errors so that the user can be
44 * informed as to what went wrong. Calling the fatal() function sets an error
45 * message and simultaneously switches off the OK flag.
46 *
47 * The recommended pattern for Status objects is to return a Status object
48 * unconditionally, i.e. both on success and on failure -- so that the
49 * developer of the calling code is reminded that the function can fail, and
50 * so that a lack of error-handling will be explicit.
51 *
52 * @newable
53 */
54class Status extends StatusValue {
55    /** @var callable|false */
56    public $cleanCallback = false;
57
58    /** @var MessageLocalizer|null */
59    protected $messageLocalizer;
60
61    private ?StatusFormatter $formatter = null;
62
63    /**
64     * Succinct helper method to wrap a StatusValue
65     *
66     * This is useful when formatting StatusValue objects:
67     * @code
68     *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
69     * @endcode
70     *
71     * @param StatusValue|Status $sv
72     * @return Status
73     */
74    public static function wrap( $sv ) {
75        if ( $sv instanceof static ) {
76            return $sv;
77        }
78
79        $result = new static();
80        $result->ok =& $sv->ok;
81        $result->errors =& $sv->errors;
82        $result->value =& $sv->value;
83        $result->successCount =& $sv->successCount;
84        $result->failCount =& $sv->failCount;
85        $result->success =& $sv->success;
86        $result->statusData =& $sv->statusData;
87
88        return $result;
89    }
90
91    /**
92     * Backwards compatibility logic
93     *
94     * @param string $name
95     * @return mixed
96     * @throws RuntimeException
97     */
98    public function __get( $name ) {
99        if ( $name === 'ok' ) {
100            return $this->isOK();
101        }
102        if ( $name === 'errors' ) {
103            return $this->getErrors();
104        }
105
106        throw new RuntimeException( "Cannot get '$name' property." );
107    }
108
109    /**
110     * Change operation result
111     * Backwards compatibility logic
112     *
113     * @param string $name
114     * @param mixed $value
115     * @throws RuntimeException
116     */
117    public function __set( $name, $value ) {
118        if ( $name === 'ok' ) {
119            $this->setOK( $value );
120        } else {
121            throw new RuntimeException( "Cannot set '$name' property." );
122        }
123    }
124
125    /**
126     * Makes this Status object use the given localizer instead of the global one.
127     * If it is an IContextSource or a ResourceLoader Context, it will also be used to
128     * determine the interface language.
129     * @note This setting does not survive serialization. That's usually for the best
130     *   (there's no guarantee we'll still have the same localization settings after
131     *   unserialization); it is the caller's responsibility to set the localizer again
132     *   if needed.
133     * @param MessageLocalizer $messageLocalizer
134     *
135     * @deprecated since 1.42, use FormatterFactory::getStatusFormatter instead.
136     */
137    public function setMessageLocalizer( MessageLocalizer $messageLocalizer ) {
138        // TODO: hard deprecate after switching callers to StatusFormatter
139        $this->messageLocalizer = $messageLocalizer;
140        $this->formatter = null;
141    }
142
143    private function getFormatter(): StatusFormatter {
144        if ( !$this->formatter ) {
145            $context = RequestContext::getMain();
146
147            // HACK: only works for IContextSource objects.
148            if ( $this->messageLocalizer && $this->messageLocalizer instanceof IContextSource ) {
149                $context = $this->messageLocalizer;
150            }
151
152            $formatterFactory = MediaWikiServices::getInstance()->getFormatterFactory();
153            $this->formatter = $formatterFactory->getStatusFormatter( $context );
154        }
155
156        return $this->formatter;
157    }
158
159    /**
160     * Splits this Status object into two new Status objects, one which contains only
161     * the error messages, and one that contains the warnings, only. The returned array is
162     * defined as:
163     * [
164     *     0 => object(Status) # The Status with error messages, only
165     *     1 => object(Status) # The Status with warning messages, only
166     * ]
167     *
168     * @return Status[]
169     */
170    public function splitByErrorType() {
171        [ $errorsOnlyStatus, $warningsOnlyStatus ] = parent::splitByErrorType();
172        // phan/phan#2133?
173        '@phan-var Status $errorsOnlyStatus';
174        '@phan-var Status $warningsOnlyStatus';
175
176        if ( $this->messageLocalizer ) {
177            $errorsOnlyStatus->setMessageLocalizer = $this->messageLocalizer;
178            $warningsOnlyStatus->setMessageLocalizer = $this->messageLocalizer;
179        }
180
181        if ( $this->formatter ) {
182            $errorsOnlyStatus->formatter = $this->formatter;
183            $warningsOnlyStatus->formatter = $this->formatter;
184        }
185
186        $errorsOnlyStatus->cleanCallback = $warningsOnlyStatus->cleanCallback = $this->cleanCallback;
187
188        return [ $errorsOnlyStatus, $warningsOnlyStatus ];
189    }
190
191    /**
192     * Returns the wrapped StatusValue object
193     * @return StatusValue
194     * @since 1.27
195     */
196    public function getStatusValue() {
197        return $this;
198    }
199
200    /**
201     * Get the error list as a wikitext formatted list
202     *
203     * @deprecated since 1.42, use StatusFormatter instead.
204     *
205     * @param string|false $shortContext A short enclosing context message name, to
206     *        be used when there is a single error
207     * @param string|false $longContext A long enclosing context message name, for a list
208     * @param string|Language|StubUserLang|null $lang Language to use for processing messages
209     * @return string
210     */
211    public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
212        return $this->getFormatter()->getWikiText( $this, [
213            'shortContext' => $shortContext,
214            'longContext' => $longContext,
215            'lang' => $lang,
216            'cleanCallback' => $this->cleanCallback
217        ] );
218    }
219
220    /**
221     * Get a bullet list of the errors as a Message object.
222     *
223     * $shortContext and $longContext can be used to wrap the error list in some text.
224     * $shortContext will be preferred when there is a single error; $longContext will be
225     * preferred when there are multiple ones. In either case, $1 will be replaced with
226     * the list of errors.
227     *
228     * $shortContext is assumed to use $1 as an inline parameter: if there is a single item,
229     * it will not be made into a list; if there are multiple items, newlines will be inserted
230     * around the list.
231     * $longContext is assumed to use $1 as a standalone parameter; it will always receive a list.
232     *
233     * If both parameters are missing, and there is only one error, no bullet will be added.
234     *
235     * @deprecated since 1.42, use StatusFormatter instead.
236     *
237     * @param string|string[]|false $shortContext A message name or an array of message names.
238     * @param string|string[]|false $longContext A message name or an array of message names.
239     * @param string|Language|StubUserLang|null $lang Language to use for processing messages
240     * @return Message
241     */
242    public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
243        return $this->getFormatter()->getMessage( $this, [
244            'shortContext' => $shortContext,
245            'longContext' => $longContext,
246            'lang' => $lang,
247            'cleanCallback' => $this->cleanCallback
248        ] );
249    }
250
251    /**
252     * Try to convert the status to a PSR-3 friendly format. The output will be similar to
253     * getWikiText( false, false, 'en' ), but message parameters will be extracted into the
254     * context array with parameter names 'parameter1' etc. when possible.
255     *
256     * @deprecated since 1.42, use StatusFormatter instead.
257     *
258     * @return array A pair of (message, context) suitable for passing to a PSR-3 logger.
259     * @phan-return array{0:string,1:(int|float|string)[]}
260     */
261    public function getPsr3MessageAndContext(): array {
262        return $this->getFormatter()->getPsr3MessageAndContext( $this );
263    }
264
265    /**
266     * Get the error message as HTML. This is done by parsing the wikitext error message
267     * @deprecated since 1.42, use StatusFormatter instead.
268     *
269     * @param string|false $shortContext A short enclosing context message name, to
270     *        be used when there is a single error
271     * @param string|false $longContext A long enclosing context message name, for a list
272     * @param string|Language|StubUserLang|null $lang Language to use for processing messages
273     * @return string
274     */
275    public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
276        return $this->getFormatter()->getHTML( $this, [
277            'shortContext' => $shortContext,
278            'longContext' => $longContext,
279            'lang' => $lang,
280            'cleanCallback' => $this->cleanCallback
281        ] );
282    }
283
284    /**
285     * Get the list of errors (but not warnings)
286     *
287     * @deprecated since 1.43 Use `->getMessages( 'error' )` instead
288     * @return array[] A list in which each entry is an array with a message key as its first element.
289     *         The remaining array elements are the message parameters.
290     * @phan-return non-empty-array[]
291     */
292    public function getErrorsArray() {
293        return $this->getStatusArray( 'error' );
294    }
295
296    /**
297     * Get the list of warnings (but not errors)
298     *
299     * @deprecated since 1.43 Use `->getMessages( 'warning' )` instead
300     * @return array[] A list in which each entry is an array with a message key as its first element.
301     *         The remaining array elements are the message parameters.
302     * @phan-return non-empty-array[]
303     */
304    public function getWarningsArray() {
305        return $this->getStatusArray( 'warning' );
306    }
307
308    /**
309     * Don't save the callback when serializing, because Closures can't be
310     * serialized and we're going to clear it in __wakeup anyway.
311     * Don't save the localizer, because it can be pretty much anything. Restoring it is
312     * the caller's responsibility (otherwise it will just fall back to the global request context).
313     * Same for the formatter, which is likely to contain a localizer.
314     * @return array
315     */
316    public function __sleep() {
317        $keys = array_keys( get_object_vars( $this ) );
318        return array_diff( $keys, [ 'cleanCallback', 'messageLocalizer', 'formatter' ] );
319    }
320
321    /**
322     * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
323     */
324    public function __wakeup() {
325        $this->cleanCallback = false;
326        $this->messageLocalizer = null;
327        $this->formatter = null;
328    }
329
330}
331
332/** @deprecated class alias since 1.41 */
333class_alias( Status::class, 'Status' );