Completed
Branch master (d1effa)
by
unknown
26:14
created

Status::getMessage()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 41
Code Lines 31

Duplication

Lines 24
Ratio 58.54 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 8
eloc 31
nc 18
nop 3
dl 24
loc 41
rs 5.3846
c 1
b 1
f 0
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 the wrapped StatusValue object
120
	 * @return StatusValue
121
	 * @since 1.27
122
	 */
123
	public function getStatusValue() {
124
		return $this->sv;
125
	}
126
127
	/**
128
	 * Returns whether the operation completed and didn't have any error or
129
	 * warnings
130
	 *
131
	 * @return bool
132
	 */
133
	public function isGood() {
134
		return $this->sv->isGood();
135
	}
136
137
	/**
138
	 * Returns whether the operation completed
139
	 *
140
	 * @return bool
141
	 */
142
	public function isOK() {
143
		return $this->sv->isOK();
144
	}
145
146
	/**
147
	 * Add a new warning
148
	 *
149
	 * @param string|Message $message Message name or object
150
	 */
151
	public function warning( $message /*, parameters... */ ) {
152
		call_user_func_array( [ $this->sv, 'warning' ], func_get_args() );
153
	}
154
155
	/**
156
	 * Add an error, do not set fatal flag
157
	 * This can be used for non-fatal errors
158
	 *
159
	 * @param string|Message $message Message name or object
160
	 */
161
	public function error( $message /*, parameters... */ ) {
162
		call_user_func_array( [ $this->sv, 'error' ], func_get_args() );
163
	}
164
165
	/**
166
	 * Add an error and set OK to false, indicating that the operation
167
	 * as a whole was fatal
168
	 *
169
	 * @param string|Message $message Message name or object
170
	 */
171
	public function fatal( $message /*, parameters... */ ) {
172
		call_user_func_array( [ $this->sv, 'fatal' ], func_get_args() );
173
	}
174
175
	/**
176
	 * @param array $params
177
	 * @return array
178
	 */
179
	protected function cleanParams( array $params ) {
180
		if ( !$this->cleanCallback ) {
181
			return $params;
182
		}
183
		$cleanParams = [];
184
		foreach ( $params as $i => $param ) {
185
			$cleanParams[$i] = call_user_func( $this->cleanCallback, $param );
186
		}
187
		return $cleanParams;
188
	}
189
190
	/**
191
	 * @param string|Language|null $lang Language to use for processing
192
	 *  messages, or null to default to the user language.
193
	 * @return Language
194
	 */
195
	protected function languageFromParam( $lang ) {
196
		global $wgLang;
197
198
		if ( $lang === null ) {
199
			// @todo: Use RequestContext::getMain()->getLanguage() instead
200
			return $wgLang;
201
		} elseif ( $lang instanceof Language || $lang instanceof StubUserLang ) {
202
			return $lang;
203
		} else {
204
			return Language::factory( $lang );
205
		}
206
	}
207
208
	/**
209
	 * Get the error list as a wikitext formatted list
210
	 *
211
	 * @param string|bool $shortContext A short enclosing context message name, to
212
	 *        be used when there is a single error
213
	 * @param string|bool $longContext A long enclosing context message name, for a list
214
	 * @param string|Language $lang Language to use for processing messages
215
	 * @return string
216
	 */
217
	public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
218
		$lang = $this->languageFromParam( $lang );
219
220
		$rawErrors = $this->sv->getErrors();
221 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
222
			if ( $this->sv->isOK() ) {
223
				$this->sv->fatal( 'internalerror_info',
224
					__METHOD__ . " called for a good result, this is incorrect\n" );
225
			} else {
226
				$this->sv->fatal( 'internalerror_info',
227
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
228
			}
229
			$rawErrors = $this->sv->getErrors(); // just added a fatal
230
		}
231
		if ( count( $rawErrors ) == 1 ) {
232
			$s = $this->getErrorMessage( $rawErrors[0], $lang )->plain();
233 View Code Duplication
			if ( $shortContext ) {
234
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang )->plain();
235
			} elseif ( $longContext ) {
236
				$s = wfMessage( $longContext, "* $s\n" )->inLanguage( $lang )->plain();
237
			}
238
		} else {
239
			$errors = $this->getErrorMessageArray( $rawErrors, $lang );
240
			foreach ( $errors as &$error ) {
241
				$error = $error->plain();
242
			}
243
			$s = '* ' . implode( "\n* ", $errors ) . "\n";
244 View Code Duplication
			if ( $longContext ) {
245
				$s = wfMessage( $longContext, $s )->inLanguage( $lang )->plain();
246
			} elseif ( $shortContext ) {
247
				$s = wfMessage( $shortContext, "\n$s\n" )->inLanguage( $lang )->plain();
248
			}
249
		}
250
		return $s;
251
	}
252
253
	/**
254
	 * Get a bullet list of the errors as a Message object.
255
	 *
256
	 * $shortContext and $longContext can be used to wrap the error list in some text.
257
	 * $shortContext will be preferred when there is a single error; $longContext will be
258
	 * preferred when there are multiple ones. In either case, $1 will be replaced with
259
	 * the list of errors.
260
	 *
261
	 * $shortContext is assumed to use $1 as an inline parameter: if there is a single item,
262
	 * it will not be made into a list; if there are multiple items, newlines will be inserted
263
	 * around the list.
264
	 * $longContext is assumed to use $1 as a standalone parameter; it will always receive a list.
265
	 *
266
	 * If both parameters are missing, and there is only one error, no bullet will be added.
267
	 *
268
	 * @param string|string[] $shortContext A message name or an array of message names.
269
	 * @param string|string[] $longContext A message name or an array of message names.
270
	 * @param string|Language $lang Language to use for processing messages
271
	 * @return Message
272
	 */
273
	public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
274
		$lang = $this->languageFromParam( $lang );
275
276
		$rawErrors = $this->sv->getErrors();
277 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
278
			if ( $this->sv->isOK() ) {
279
				$this->sv->fatal( 'internalerror_info',
280
					__METHOD__ . " called for a good result, this is incorrect\n" );
281
			} else {
282
				$this->sv->fatal( 'internalerror_info',
283
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
284
			}
285
			$rawErrors = $this->sv->getErrors(); // just added a fatal
286
		}
287
		if ( count( $rawErrors ) == 1 ) {
288
			$s = $this->getErrorMessage( $rawErrors[0], $lang );
289 View Code Duplication
			if ( $shortContext ) {
290
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang );
291
			} elseif ( $longContext ) {
292
				$wrapper = new RawMessage( "* \$1\n" );
293
				$wrapper->params( $s )->parse();
294
				$s = wfMessage( $longContext, $wrapper )->inLanguage( $lang );
295
			}
296
		} else {
297
			$msgs = $this->getErrorMessageArray( $rawErrors, $lang );
298
			$msgCount = count( $msgs );
299
300
			$s = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
301
			$s->params( $msgs )->parse();
302
303 View Code Duplication
			if ( $longContext ) {
304
				$s = wfMessage( $longContext, $s )->inLanguage( $lang );
305
			} elseif ( $shortContext ) {
306
				$wrapper = new RawMessage( "\n\$1\n", [ $s ] );
307
				$wrapper->parse();
308
				$s = wfMessage( $shortContext, $wrapper )->inLanguage( $lang );
309
			}
310
		}
311
312
		return $s;
313
	}
314
315
	/**
316
	 * Return the message for a single error.
317
	 * @param mixed $error With an array & two values keyed by
318
	 * 'message' and 'params', use those keys-value pairs.
319
	 * Otherwise, if its an array, just use the first value as the
320
	 * message and the remaining items as the params.
321
	 * @param string|Language $lang Language to use for processing messages
322
	 * @return Message
323
	 */
324
	protected function getErrorMessage( $error, $lang = null ) {
325
		if ( is_array( $error ) ) {
326
			if ( isset( $error['message'] ) && $error['message'] instanceof Message ) {
327
				$msg = $error['message'];
328
			} elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
329
				$msg = wfMessage( $error['message'],
330
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
331
			} else {
332
				$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...
333
				$msg = wfMessage( $msgName,
334
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
335
			}
336
		} else {
337
			$msg = wfMessage( $error );
338
		}
339
340
		$msg->inLanguage( $this->languageFromParam( $lang ) );
341
		return $msg;
342
	}
343
344
	/**
345
	 * Get the error message as HTML. This is done by parsing the wikitext error
346
	 * message.
347
	 * @param string $shortContext A short enclosing context message name, to
348
	 *        be used when there is a single error
349
	 * @param string $longContext A long enclosing context message name, for a list
350
	 * @param string|Language $lang Language to use for processing messages
351
	 * @return string
352
	 */
353
	public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
354
		$lang = $this->languageFromParam( $lang );
355
		$text = $this->getWikiText( $shortContext, $longContext, $lang );
356
		$out = MessageCache::singleton()->parse( $text, null, true, true, $lang );
357
		return $out instanceof ParserOutput ? $out->getText() : $out;
358
	}
359
360
	/**
361
	 * Return an array with a Message object for each error.
362
	 * @param array $errors
363
	 * @param string|Language $lang Language to use for processing messages
364
	 * @return Message[]
365
	 */
366
	protected function getErrorMessageArray( $errors, $lang = null ) {
367
		$lang = $this->languageFromParam( $lang );
368
		return array_map( function ( $e ) use ( $lang ) {
369
			return $this->getErrorMessage( $e, $lang );
370
		}, $errors );
371
	}
372
373
	/**
374
	 * Merge another status object into this one
375
	 *
376
	 * @param Status $other Other Status object
377
	 * @param bool $overwriteValue Whether to override the "value" member
378
	 */
379
	public function merge( $other, $overwriteValue = false ) {
380
		$this->sv->merge( $other->sv, $overwriteValue );
381
	}
382
383
	/**
384
	 * Get the list of errors (but not warnings)
385
	 *
386
	 * @return array A list in which each entry is an array with a message key as its first element.
387
	 *         The remaining array elements are the message parameters.
388
	 * @deprecated 1.25
389
	 */
390
	public function getErrorsArray() {
391
		return $this->getStatusArray( 'error' );
392
	}
393
394
	/**
395
	 * Get the list of warnings (but not errors)
396
	 *
397
	 * @return array A list in which each entry is an array with a message key as its first element.
398
	 *         The remaining array elements are the message parameters.
399
	 * @deprecated 1.25
400
	 */
401
	public function getWarningsArray() {
402
		return $this->getStatusArray( 'warning' );
403
	}
404
405
	/**
406
	 * Returns a list of status messages of the given type (or all if false)
407
	 *
408
	 * @note: this handles RawMessage poorly
409
	 *
410
	 * @param string|bool $type
411
	 * @return array
412
	 */
413
	protected function getStatusArray( $type = false ) {
414
		$result = [];
415
416
		foreach ( $this->sv->getErrors() as $error ) {
417
			if ( $type === false || $error['type'] === $type ) {
418
				if ( $error['message'] instanceof MessageSpecifier ) {
419
					$result[] = array_merge(
420
						[ $error['message']->getKey() ],
421
						$error['message']->getParams()
422
					);
423
				} elseif ( $error['params'] ) {
424
					$result[] = array_merge( [ $error['message'] ], $error['params'] );
425
				} else {
426
					$result[] = [ $error['message'] ];
427
				}
428
			}
429
		}
430
431
		return $result;
432
	}
433
434
	/**
435
	 * Returns a list of status messages of the given type, with message and
436
	 * params left untouched, like a sane version of getStatusArray
437
	 *
438
	 * Each entry is a map of:
439
	 *   - message: string message key or MessageSpecifier
440
	 *   - params: array list of parameters
441
	 *
442
	 * @param string $type
443
	 * @return array
444
	 */
445
	public function getErrorsByType( $type ) {
446
		return $this->sv->getErrorsByType( $type );
447
	}
448
449
	/**
450
	 * Returns true if the specified message is present as a warning or error
451
	 *
452
	 * @param string|Message $message Message key or object to search for
453
	 *
454
	 * @return bool
455
	 */
456
	public function hasMessage( $message ) {
457
		return $this->sv->hasMessage( $message );
458
	}
459
460
	/**
461
	 * If the specified source message exists, replace it with the specified
462
	 * destination message, but keep the same parameters as in the original error.
463
	 *
464
	 * Note, due to the lack of tools for comparing Message objects, this
465
	 * function will not work when using a Message object as the search parameter.
466
	 *
467
	 * @param Message|string $source Message key or object to search for
468
	 * @param Message|string $dest Replacement message key or object
469
	 * @return bool Return true if the replacement was done, false otherwise.
470
	 */
471
	public function replaceMessage( $source, $dest ) {
472
		return $this->sv->replaceMessage( $source, $dest );
0 ignored issues
show
Bug introduced by
It seems like $source defined by parameter $source on line 471 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 471 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...
473
	}
474
475
	/**
476
	 * @return mixed
477
	 */
478
	public function getValue() {
479
		return $this->sv->getValue();
480
	}
481
482
	/**
483
	 * Backwards compatibility logic
484
	 *
485
	 * @param string $name
486
	 */
487
	function __get( $name ) {
488
		if ( $name === 'ok' ) {
489
			return $this->sv->isOK();
490
		} elseif ( $name === 'errors' ) {
491
			return $this->sv->getErrors();
492
		}
493
		throw new Exception( "Cannot get '$name' property." );
494
	}
495
496
	/**
497
	 * Backwards compatibility logic
498
	 *
499
	 * @param string $name
500
	 * @param mixed $value
501
	 */
502
	function __set( $name, $value ) {
503
		if ( $name === 'ok' ) {
504
			$this->sv->setOK( $value );
505
		} elseif ( !property_exists( $this, $name ) ) {
506
			// Caller is using undeclared ad-hoc properties
507
			$this->$name = $value;
508
		} else {
509
			throw new Exception( "Cannot set '$name' property." );
510
		}
511
	}
512
513
	/**
514
	 * @return string
515
	 */
516
	public function __toString() {
517
		return $this->sv->__toString();
518
	}
519
520
	/**
521
	 * Don't save the callback when serializing, because Closures can't be
522
	 * serialized and we're going to clear it in __wakeup anyway.
523
	 */
524
	function __sleep() {
525
		$keys = array_keys( get_object_vars( $this ) );
526
		return array_diff( $keys, [ 'cleanCallback' ] );
527
	}
528
529
	/**
530
	 * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
531
	 */
532
	function __wakeup() {
533
		$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...
534
	}
535
}
536