Completed
Branch master (8ef871)
by
unknown
29:40
created

Status::getMessage()   D

Complexity

Conditions 9
Paths 27

Size

Total Lines 45
Code Lines 33

Duplication

Lines 24
Ratio 53.33 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 24
loc 45
rs 4.909
cc 9
eloc 33
nc 27
nop 3
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
23
/**
24
 * Generic operation result class
25
 * Has warning/error list, boolean status and arbitrary value
26
 *
27
 * "Good" means the operation was completed with no warnings or errors.
28
 *
29
 * "OK" means the operation was partially or wholly completed.
30
 *
31
 * An operation which is not OK should have errors so that the user can be
32
 * informed as to what went wrong. Calling the fatal() function sets an error
33
 * message and simultaneously switches off the OK flag.
34
 *
35
 * The recommended pattern for Status objects is to return a Status object
36
 * unconditionally, i.e. both on success and on failure -- so that the
37
 * developer of the calling code is reminded that the function can fail, and
38
 * so that a lack of error-handling will be explicit.
39
 */
40
class Status {
41
	/** @var StatusValue */
42
	protected $sv;
43
44
	/** @var mixed */
45
	public $value;
46
	/** @var array Map of (key => bool) to indicate success of each part of batch operations */
47
	public $success = [];
48
	/** @var int Counter for batch operations */
49
	public $successCount = 0;
50
	/** @var int Counter for batch operations */
51
	public $failCount = 0;
52
53
	/** @var callable */
54
	public $cleanCallback = false;
55
56
	/**
57
	 * @param StatusValue $sv [optional]
58
	 */
59
	public function __construct( StatusValue $sv = null ) {
60
		$this->sv = ( $sv === null ) ? new StatusValue() : $sv;
61
		// B/C field aliases
62
		$this->value =& $this->sv->value;
63
		$this->successCount =& $this->sv->successCount;
64
		$this->failCount =& $this->sv->failCount;
65
		$this->success =& $this->sv->success;
66
	}
67
68
	/**
69
	 * Succinct helper method to wrap a StatusValue
70
	 *
71
	 * This is is useful when formatting StatusValue objects:
72
	 * @code
73
	 *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
74
	 * @endcode
75
	 *
76
	 * @param StatusValue|Status $sv
77
	 * @return Status
78
	 */
79
	public static function wrap( $sv ) {
80
		return $sv instanceof Status ? $sv : new self( $sv );
81
	}
82
83
	/**
84
	 * Factory function for fatal errors
85
	 *
86
	 * @param string|Message $message Message name or object
87
	 * @return Status
88
	 */
89
	public static function newFatal( $message /*, parameters...*/ ) {
90
		return new self( call_user_func_array(
91
			[ 'StatusValue', 'newFatal' ], func_get_args()
92
		) );
93
	}
94
95
	/**
96
	 * Factory function for good results
97
	 *
98
	 * @param mixed $value
99
	 * @return Status
100
	 */
101
	public static function newGood( $value = null ) {
102
		$sv = new StatusValue();
103
		$sv->value = $value;
104
105
		return new self( $sv );
106
	}
107
108
	/**
109
	 * Change operation result
110
	 *
111
	 * @param bool $ok Whether the operation completed
112
	 * @param mixed $value
113
	 */
114
	public function setResult( $ok, $value = null ) {
115
		$this->sv->setResult( $ok, $value );
116
	}
117
118
	/**
119
	 * Returns whether the operation completed and didn't have any error or
120
	 * warnings
121
	 *
122
	 * @return bool
123
	 */
124
	public function isGood() {
125
		return $this->sv->isGood();
126
	}
127
128
	/**
129
	 * Returns whether the operation completed
130
	 *
131
	 * @return bool
132
	 */
133
	public function isOK() {
134
		return $this->sv->isOK();
135
	}
136
137
	/**
138
	 * Add a new warning
139
	 *
140
	 * @param string|Message $message Message name or object
141
	 */
142
	public function warning( $message /*, parameters... */ ) {
143
		call_user_func_array( [ $this->sv, 'warning' ], func_get_args() );
144
	}
145
146
	/**
147
	 * Add an error, do not set fatal flag
148
	 * This can be used for non-fatal errors
149
	 *
150
	 * @param string|Message $message Message name or object
151
	 */
152
	public function error( $message /*, parameters... */ ) {
153
		call_user_func_array( [ $this->sv, 'error' ], func_get_args() );
154
	}
155
156
	/**
157
	 * Add an error and set OK to false, indicating that the operation
158
	 * as a whole was fatal
159
	 *
160
	 * @param string|Message $message Message name or object
161
	 */
162
	public function fatal( $message /*, parameters... */ ) {
163
		call_user_func_array( [ $this->sv, 'fatal' ], func_get_args() );
164
	}
165
166
	/**
167
	 * @param array $params
168
	 * @return array
169
	 */
170
	protected function cleanParams( array $params ) {
171
		if ( !$this->cleanCallback ) {
172
			return $params;
173
		}
174
		$cleanParams = [];
175
		foreach ( $params as $i => $param ) {
176
			$cleanParams[$i] = call_user_func( $this->cleanCallback, $param );
177
		}
178
		return $cleanParams;
179
	}
180
181
	/**
182
	 * @param string|Language|null $lang Language to use for processing
183
	 *  messages, or null to default to the user language.
184
	 * @return Language
185
	 */
186
	protected function languageFromParam( $lang ) {
187
		global $wgLang;
188
189
		if ( $lang === null ) {
190
			// @todo: Use RequestContext::getMain()->getLanguage() instead
191
			return $wgLang;
192
		} elseif ( $lang instanceof Language || $lang instanceof StubUserLang ) {
193
			return $lang;
194
		} else {
195
			return Language::factory( $lang );
196
		}
197
	}
198
199
	/**
200
	 * Get the error list as a wikitext formatted list
201
	 *
202
	 * @param string|bool $shortContext A short enclosing context message name, to
203
	 *        be used when there is a single error
204
	 * @param string|bool $longContext A long enclosing context message name, for a list
205
	 * @param string|Language $lang Language to use for processing messages
206
	 * @return string
207
	 */
208
	public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
209
		$lang = $this->languageFromParam( $lang );
210
211
		$rawErrors = $this->sv->getErrors();
212 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
213
			if ( $this->sv->isOK() ) {
214
				$this->sv->fatal( 'internalerror_info',
215
					__METHOD__ . " called for a good result, this is incorrect\n" );
216
			} else {
217
				$this->sv->fatal( 'internalerror_info',
218
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
219
			}
220
			$rawErrors = $this->sv->getErrors(); // just added a fatal
221
		}
222
		if ( count( $rawErrors ) == 1 ) {
223
			$s = $this->getErrorMessage( $rawErrors[0], $lang )->plain();
224 View Code Duplication
			if ( $shortContext ) {
225
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang )->plain();
226
			} elseif ( $longContext ) {
227
				$s = wfMessage( $longContext, "* $s\n" )->inLanguage( $lang )->plain();
228
			}
229
		} else {
230
			$errors = $this->getErrorMessageArray( $rawErrors, $lang );
231
			foreach ( $errors as &$error ) {
232
				$error = $error->plain();
233
			}
234
			$s = '* ' . implode( "\n* ", $errors ) . "\n";
235 View Code Duplication
			if ( $longContext ) {
236
				$s = wfMessage( $longContext, $s )->inLanguage( $lang )->plain();
237
			} elseif ( $shortContext ) {
238
				$s = wfMessage( $shortContext, "\n$s\n" )->inLanguage( $lang )->plain();
239
			}
240
		}
241
		return $s;
242
	}
243
244
	/**
245
	 * Get the error list as a Message object
246
	 *
247
	 * @param string|string[] $shortContext A short enclosing context message name (or an array of
248
	 * message names), to be used when there is a single error.
249
	 * @param string|string[] $longContext A long enclosing context message name (or an array of
250
	 * message names), for a list.
251
	 * @param string|Language $lang Language to use for processing messages
252
	 * @return Message
253
	 */
254
	public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
255
		$lang = $this->languageFromParam( $lang );
256
257
		$rawErrors = $this->sv->getErrors();
258 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
259
			if ( $this->sv->isOK() ) {
260
				$this->sv->fatal( 'internalerror_info',
261
					__METHOD__ . " called for a good result, this is incorrect\n" );
262
			} else {
263
				$this->sv->fatal( 'internalerror_info',
264
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
265
			}
266
			$rawErrors = $this->sv->getErrors(); // just added a fatal
267
		}
268
		if ( count( $rawErrors ) == 1 ) {
269
			$s = $this->getErrorMessage( $rawErrors[0], $lang );
270 View Code Duplication
			if ( $shortContext ) {
271
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang );
272
			} elseif ( $longContext ) {
273
				$wrapper = new RawMessage( "* \$1\n" );
274
				$wrapper->params( $s )->parse();
275
				$s = wfMessage( $longContext, $wrapper )->inLanguage( $lang );
276
			}
277
		} else {
278
			$msgs = $this->getErrorMessageArray( $rawErrors, $lang );
279
			$msgCount = count( $msgs );
280
281
			if ( $shortContext ) {
282
				$msgCount++;
283
			}
284
285
			$s = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
286
			$s->params( $msgs )->parse();
287
288 View Code Duplication
			if ( $longContext ) {
289
				$s = wfMessage( $longContext, $s )->inLanguage( $lang );
290
			} elseif ( $shortContext ) {
291
				$wrapper = new RawMessage( "\n\$1\n", [ $s ] );
292
				$wrapper->parse();
293
				$s = wfMessage( $shortContext, $wrapper )->inLanguage( $lang );
294
			}
295
		}
296
297
		return $s;
298
	}
299
300
	/**
301
	 * Return the message for a single error.
302
	 * @param mixed $error With an array & two values keyed by
303
	 * 'message' and 'params', use those keys-value pairs.
304
	 * Otherwise, if its an array, just use the first value as the
305
	 * message and the remaining items as the params.
306
	 * @param string|Language $lang Language to use for processing messages
307
	 * @return Message
308
	 */
309
	protected function getErrorMessage( $error, $lang = null ) {
310
		if ( is_array( $error ) ) {
311
			if ( isset( $error['message'] ) && $error['message'] instanceof Message ) {
312
				$msg = $error['message'];
313
			} elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
314
				$msg = wfMessage( $error['message'],
315
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
316
			} else {
317
				$msgName = array_shift( $error );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $msgName is correct as array_shift($error) (which targets array_shift()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
318
				$msg = wfMessage( $msgName,
319
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
320
			}
321
		} else {
322
			$msg = wfMessage( $error );
323
		}
324
325
		$msg->inLanguage( $this->languageFromParam( $lang ) );
326
		return $msg;
327
	}
328
329
	/**
330
	 * Get the error message as HTML. This is done by parsing the wikitext error
331
	 * message.
332
	 * @param string $shortContext A short enclosing context message name, to
333
	 *        be used when there is a single error
334
	 * @param string $longContext A long enclosing context message name, for a list
335
	 * @param string|Language $lang Language to use for processing messages
336
	 * @return string
337
	 */
338
	public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
339
		$lang = $this->languageFromParam( $lang );
340
		$text = $this->getWikiText( $shortContext, $longContext, $lang );
341
		$out = MessageCache::singleton()->parse( $text, null, true, true, $lang );
342
		return $out instanceof ParserOutput ? $out->getText() : $out;
343
	}
344
345
	/**
346
	 * Return an array with a Message object for each error.
347
	 * @param array $errors
348
	 * @param string|Language $lang Language to use for processing messages
349
	 * @return Message[]
350
	 */
351
	protected function getErrorMessageArray( $errors, $lang = null ) {
352
		$lang = $this->languageFromParam( $lang );
353
		return array_map( function ( $e ) use ( $lang ) {
354
			return $this->getErrorMessage( $e, $lang );
355
		}, $errors );
356
	}
357
358
	/**
359
	 * Merge another status object into this one
360
	 *
361
	 * @param Status $other Other Status object
362
	 * @param bool $overwriteValue Whether to override the "value" member
363
	 */
364
	public function merge( $other, $overwriteValue = false ) {
365
		$this->sv->merge( $other->sv, $overwriteValue );
366
	}
367
368
	/**
369
	 * Get the list of errors (but not warnings)
370
	 *
371
	 * @return array A list in which each entry is an array with a message key as its first element.
372
	 *         The remaining array elements are the message parameters.
373
	 * @deprecated 1.25
374
	 */
375
	public function getErrorsArray() {
376
		return $this->getStatusArray( 'error' );
377
	}
378
379
	/**
380
	 * Get the list of warnings (but not errors)
381
	 *
382
	 * @return array A list in which each entry is an array with a message key as its first element.
383
	 *         The remaining array elements are the message parameters.
384
	 * @deprecated 1.25
385
	 */
386
	public function getWarningsArray() {
387
		return $this->getStatusArray( 'warning' );
388
	}
389
390
	/**
391
	 * Returns a list of status messages of the given type (or all if false)
392
	 *
393
	 * @note: this handles RawMessage poorly
394
	 *
395
	 * @param string|bool $type
396
	 * @return array
397
	 */
398
	protected function getStatusArray( $type = false ) {
399
		$result = [];
400
401
		foreach ( $this->sv->getErrors() as $error ) {
402
			if ( $type === false || $error['type'] === $type ) {
403
				if ( $error['message'] instanceof MessageSpecifier ) {
404
					$result[] = array_merge(
405
						[ $error['message']->getKey() ],
406
						$error['message']->getParams()
407
					);
408
				} elseif ( $error['params'] ) {
409
					$result[] = array_merge( [ $error['message'] ], $error['params'] );
410
				} else {
411
					$result[] = [ $error['message'] ];
412
				}
413
			}
414
		}
415
416
		return $result;
417
	}
418
419
	/**
420
	 * Returns a list of status messages of the given type, with message and
421
	 * params left untouched, like a sane version of getStatusArray
422
	 *
423
	 * @param string $type
424
	 *
425
	 * @return array
426
	 */
427
	public function getErrorsByType( $type ) {
428
		return $this->sv->getErrorsByType( $type );
429
	}
430
431
	/**
432
	 * Returns true if the specified message is present as a warning or error
433
	 *
434
	 * @param string|Message $message Message key or object to search for
435
	 *
436
	 * @return bool
437
	 */
438
	public function hasMessage( $message ) {
439
		return $this->sv->hasMessage( $message );
440
	}
441
442
	/**
443
	 * If the specified source message exists, replace it with the specified
444
	 * destination message, but keep the same parameters as in the original error.
445
	 *
446
	 * Note, due to the lack of tools for comparing Message objects, this
447
	 * function will not work when using a Message object as the search parameter.
448
	 *
449
	 * @param Message|string $source Message key or object to search for
450
	 * @param Message|string $dest Replacement message key or object
451
	 * @return bool Return true if the replacement was done, false otherwise.
452
	 */
453
	public function replaceMessage( $source, $dest ) {
454
		return $this->sv->replaceMessage( $source, $dest );
0 ignored issues
show
Bug introduced by
It seems like $source defined by parameter $source on line 453 can also be of type object<Message>; however, StatusValue::replaceMessage() does only seem to accept object<IStatusMessage>|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $dest defined by parameter $dest on line 453 can also be of type object<Message>; however, StatusValue::replaceMessage() does only seem to accept object<IStatusMessage>|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
455
	}
456
457
	/**
458
	 * @return mixed
459
	 */
460
	public function getValue() {
461
		return $this->sv->getValue();
462
	}
463
464
	/**
465
	 * Backwards compatibility logic
466
	 *
467
	 * @param string $name
468
	 */
469
	function __get( $name ) {
470
		if ( $name === 'ok' ) {
471
			return $this->sv->isOK();
472
		} elseif ( $name === 'errors' ) {
473
			return $this->sv->getErrors();
474
		}
475
		throw new Exception( "Cannot get '$name' property." );
476
	}
477
478
	/**
479
	 * Backwards compatibility logic
480
	 *
481
	 * @param string $name
482
	 * @param mixed $value
483
	 */
484
	function __set( $name, $value ) {
485
		if ( $name === 'ok' ) {
486
			$this->sv->setOK( $value );
487
		} elseif ( !property_exists( $this, $name ) ) {
488
			// Caller is using undeclared ad-hoc properties
489
			$this->$name = $value;
490
		} else {
491
			throw new Exception( "Cannot set '$name' property." );
492
		}
493
	}
494
495
	/**
496
	 * @return string
497
	 */
498
	public function __toString() {
499
		return $this->sv->__toString();
500
	}
501
502
	/**
503
	 * Don't save the callback when serializing, because Closures can't be
504
	 * serialized and we're going to clear it in __wakeup anyway.
505
	 */
506
	function __sleep() {
507
		$keys = array_keys( get_object_vars( $this ) );
508
		return array_diff( $keys, [ 'cleanCallback' ] );
509
	}
510
511
	/**
512
	 * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
513
	 */
514
	function __wakeup() {
515
		$this->cleanCallback = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type callable of property $cleanCallback.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
516
	}
517
}
518