Status::getMessage()   C
last analyzed

Complexity

Conditions 8
Paths 18

Size

Total Lines 41
Code Lines 31

Duplication

Lines 24
Ratio 58.54 %

Importance

Changes 0
Metric Value
cc 8
eloc 31
nc 18
nop 3
dl 24
loc 41
rs 5.3846
c 0
b 0
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 extends StatusValue {
41
	/** @var callable */
42
	public $cleanCallback = false;
43
44
	/**
45
	 * Succinct helper method to wrap a StatusValue
46
	 *
47
	 * This is is useful when formatting StatusValue objects:
48
	 * @code
49
	 *     $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
50
	 * @endcode
51
	 *
52
	 * @param StatusValue|Status $sv
53
	 * @return Status
54
	 */
55
	public static function wrap( $sv ) {
56
		if ( $sv instanceof static ) {
57
			return $sv;
58
		}
59
60
		$result = new static();
61
		$result->ok =& $sv->ok;
62
		$result->errors =& $sv->errors;
63
		$result->value =& $sv->value;
64
		$result->successCount =& $sv->successCount;
65
		$result->failCount =& $sv->failCount;
66
		$result->success =& $sv->success;
67
68
		return $result;
69
	}
70
71
	/**
72
	 * Backwards compatibility logic
73
	 *
74
	 * @param string $name
75
	 * @return mixed
76
	 * @throws RuntimeException
77
	 */
78
	function __get( $name ) {
79
		if ( $name === 'ok' ) {
80
			return $this->isOK();
81
		} elseif ( $name === 'errors' ) {
82
			return $this->getErrors();
83
		}
84
85
		throw new RuntimeException( "Cannot get '$name' property." );
86
	}
87
88
	/**
89
	 * Change operation result
90
	 * Backwards compatibility logic
91
	 *
92
	 * @param string $name
93
	 * @param mixed $value
94
	 * @throws RuntimeException
95
	 */
96
	function __set( $name, $value ) {
97
		if ( $name === 'ok' ) {
98
			$this->setOK( $value );
99
		} elseif ( !property_exists( $this, $name ) ) {
100
			// Caller is using undeclared ad-hoc properties
101
			$this->$name = $value;
102
		} else {
103
			throw new RuntimeException( "Cannot set '$name' property." );
104
		}
105
	}
106
107
	/**
108
	 * Splits this Status object into two new Status objects, one which contains only
109
	 * the error messages, and one that contains the warnings, only. The returned array is
110
	 * defined as:
111
	 * [
112
	 *     0 => object(Status) # the Status with error messages, only
113
	 *     1 => object(Status) # The Status with warning messages, only
114
	 * ]
115
	 *
116
	 * @return Status[]
117
	 */
118
	public function splitByErrorType() {
119
		list( $errorsOnlyStatus, $warningsOnlyStatus ) = parent::splitByErrorType();
120
		$errorsOnlyStatus->cleanCallback =
0 ignored issues
show
Bug introduced by
The property cleanCallback does not seem to exist in StatusValue.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
121
			$warningsOnlyStatus->cleanCallback = $this->cleanCallback;
122
123
		return [ $errorsOnlyStatus, $warningsOnlyStatus ];
124
	}
125
126
	/**
127
	 * Returns the wrapped StatusValue object
128
	 * @return StatusValue
129
	 * @since 1.27
130
	 */
131
	public function getStatusValue() {
132
		return $this;
133
	}
134
135
	/**
136
	 * @param array $params
137
	 * @return array
138
	 */
139
	protected function cleanParams( array $params ) {
140
		if ( !$this->cleanCallback ) {
141
			return $params;
142
		}
143
		$cleanParams = [];
144
		foreach ( $params as $i => $param ) {
145
			$cleanParams[$i] = call_user_func( $this->cleanCallback, $param );
146
		}
147
		return $cleanParams;
148
	}
149
150
	/**
151
	 * @param string|Language|null $lang Language to use for processing
152
	 *  messages, or null to default to the user language.
153
	 * @return Language
154
	 */
155
	protected function languageFromParam( $lang ) {
156
		global $wgLang;
157
158
		if ( $lang === null ) {
159
			// @todo: Use RequestContext::getMain()->getLanguage() instead
160
			return $wgLang;
161
		} elseif ( $lang instanceof Language || $lang instanceof StubUserLang ) {
162
			return $lang;
163
		} else {
164
			return Language::factory( $lang );
165
		}
166
	}
167
168
	/**
169
	 * Get the error list as a wikitext formatted list
170
	 *
171
	 * @param string|bool $shortContext A short enclosing context message name, to
172
	 *        be used when there is a single error
173
	 * @param string|bool $longContext A long enclosing context message name, for a list
174
	 * @param string|Language $lang Language to use for processing messages
175
	 * @return string
176
	 */
177
	public function getWikiText( $shortContext = false, $longContext = false, $lang = null ) {
178
		$lang = $this->languageFromParam( $lang );
179
180
		$rawErrors = $this->getErrors();
181 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
182
			if ( $this->isOK() ) {
183
				$this->fatal( 'internalerror_info',
184
					__METHOD__ . " called for a good result, this is incorrect\n" );
185
			} else {
186
				$this->fatal( 'internalerror_info',
187
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
188
			}
189
			$rawErrors = $this->getErrors(); // just added a fatal
190
		}
191
		if ( count( $rawErrors ) == 1 ) {
192
			$s = $this->getErrorMessage( $rawErrors[0], $lang )->plain();
193 View Code Duplication
			if ( $shortContext ) {
194
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang )->plain();
195
			} elseif ( $longContext ) {
196
				$s = wfMessage( $longContext, "* $s\n" )->inLanguage( $lang )->plain();
197
			}
198
		} else {
199
			$errors = $this->getErrorMessageArray( $rawErrors, $lang );
200
			foreach ( $errors as &$error ) {
201
				$error = $error->plain();
202
			}
203
			$s = '* ' . implode( "\n* ", $errors ) . "\n";
204 View Code Duplication
			if ( $longContext ) {
205
				$s = wfMessage( $longContext, $s )->inLanguage( $lang )->plain();
206
			} elseif ( $shortContext ) {
207
				$s = wfMessage( $shortContext, "\n$s\n" )->inLanguage( $lang )->plain();
208
			}
209
		}
210
		return $s;
211
	}
212
213
	/**
214
	 * Get a bullet list of the errors as a Message object.
215
	 *
216
	 * $shortContext and $longContext can be used to wrap the error list in some text.
217
	 * $shortContext will be preferred when there is a single error; $longContext will be
218
	 * preferred when there are multiple ones. In either case, $1 will be replaced with
219
	 * the list of errors.
220
	 *
221
	 * $shortContext is assumed to use $1 as an inline parameter: if there is a single item,
222
	 * it will not be made into a list; if there are multiple items, newlines will be inserted
223
	 * around the list.
224
	 * $longContext is assumed to use $1 as a standalone parameter; it will always receive a list.
225
	 *
226
	 * If both parameters are missing, and there is only one error, no bullet will be added.
227
	 *
228
	 * @param string|string[]|bool $shortContext A message name or an array of message names.
229
	 * @param string|string[]|bool $longContext A message name or an array of message names.
230
	 * @param string|Language $lang Language to use for processing messages
231
	 * @return Message
232
	 */
233
	public function getMessage( $shortContext = false, $longContext = false, $lang = null ) {
234
		$lang = $this->languageFromParam( $lang );
235
236
		$rawErrors = $this->getErrors();
237 View Code Duplication
		if ( count( $rawErrors ) == 0 ) {
238
			if ( $this->isOK() ) {
239
				$this->fatal( 'internalerror_info',
240
					__METHOD__ . " called for a good result, this is incorrect\n" );
241
			} else {
242
				$this->fatal( 'internalerror_info',
243
					__METHOD__ . ": Invalid result object: no error text but not OK\n" );
244
			}
245
			$rawErrors = $this->getErrors(); // just added a fatal
246
		}
247
		if ( count( $rawErrors ) == 1 ) {
248
			$s = $this->getErrorMessage( $rawErrors[0], $lang );
249 View Code Duplication
			if ( $shortContext ) {
250
				$s = wfMessage( $shortContext, $s )->inLanguage( $lang );
251
			} elseif ( $longContext ) {
252
				$wrapper = new RawMessage( "* \$1\n" );
253
				$wrapper->params( $s )->parse();
254
				$s = wfMessage( $longContext, $wrapper )->inLanguage( $lang );
255
			}
256
		} else {
257
			$msgs = $this->getErrorMessageArray( $rawErrors, $lang );
258
			$msgCount = count( $msgs );
259
260
			$s = new RawMessage( '* $' . implode( "\n* \$", range( 1, $msgCount ) ) );
261
			$s->params( $msgs )->parse();
262
263 View Code Duplication
			if ( $longContext ) {
264
				$s = wfMessage( $longContext, $s )->inLanguage( $lang );
265
			} elseif ( $shortContext ) {
266
				$wrapper = new RawMessage( "\n\$1\n", [ $s ] );
267
				$wrapper->parse();
268
				$s = wfMessage( $shortContext, $wrapper )->inLanguage( $lang );
269
			}
270
		}
271
272
		return $s;
273
	}
274
275
	/**
276
	 * Return the message for a single error
277
	 *
278
	 * The code string can be used a message key with per-language versions.
279
	 * If $error is an array, the "params" field is a list of parameters for the message.
280
	 *
281
	 * @param array|string $error Code string or (key: code string, params: string[]) map
282
	 * @param string|Language $lang Language to use for processing messages
283
	 * @return Message
284
	 */
285
	protected function getErrorMessage( $error, $lang = null ) {
286
		if ( is_array( $error ) ) {
287
			if ( isset( $error['message'] ) && $error['message'] instanceof Message ) {
288
				$msg = $error['message'];
289
			} elseif ( isset( $error['message'] ) && isset( $error['params'] ) ) {
290
				$msg = wfMessage( $error['message'],
291
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
292
			} else {
293
				$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...
294
				$msg = wfMessage( $msgName,
295
					array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
296
			}
297
		} elseif ( is_string( $error ) ) {
298
			$msg = wfMessage( $error );
299
		} else {
300
			throw new UnexpectedValueException( "Got " . get_class( $error ) . " for key." );
301
		}
302
303
		$msg->inLanguage( $this->languageFromParam( $lang ) );
304
		return $msg;
305
	}
306
307
	/**
308
	 * Get the error message as HTML. This is done by parsing the wikitext error message
309
	 * @param string|bool $shortContext A short enclosing context message name, to
310
	 *        be used when there is a single error
311
	 * @param string|bool $longContext A long enclosing context message name, for a list
312
	 * @param string|Language|null $lang Language to use for processing messages
313
	 * @return string
314
	 */
315
	public function getHTML( $shortContext = false, $longContext = false, $lang = null ) {
316
		$lang = $this->languageFromParam( $lang );
317
		$text = $this->getWikiText( $shortContext, $longContext, $lang );
318
		$out = MessageCache::singleton()->parse( $text, null, true, true, $lang );
319
		return $out instanceof ParserOutput ? $out->getText() : $out;
320
	}
321
322
	/**
323
	 * Return an array with a Message object for each error.
324
	 * @param array $errors
325
	 * @param string|Language $lang Language to use for processing messages
326
	 * @return Message[]
327
	 */
328
	protected function getErrorMessageArray( $errors, $lang = null ) {
329
		$lang = $this->languageFromParam( $lang );
330
		return array_map( function ( $e ) use ( $lang ) {
331
			return $this->getErrorMessage( $e, $lang );
332
		}, $errors );
333
	}
334
335
	/**
336
	 * Get the list of errors (but not warnings)
337
	 *
338
	 * @return array A list in which each entry is an array with a message key as its first element.
339
	 *         The remaining array elements are the message parameters.
340
	 * @deprecated since 1.25
341
	 */
342
	public function getErrorsArray() {
343
		return $this->getStatusArray( 'error' );
344
	}
345
346
	/**
347
	 * Get the list of warnings (but not errors)
348
	 *
349
	 * @return array A list in which each entry is an array with a message key as its first element.
350
	 *         The remaining array elements are the message parameters.
351
	 * @deprecated since 1.25
352
	 */
353
	public function getWarningsArray() {
354
		return $this->getStatusArray( 'warning' );
355
	}
356
357
	/**
358
	 * Returns a list of status messages of the given type (or all if false)
359
	 *
360
	 * @note: this handles RawMessage poorly
361
	 *
362
	 * @param string|bool $type
363
	 * @return array
364
	 */
365
	protected function getStatusArray( $type = false ) {
366
		$result = [];
367
368
		foreach ( $this->getErrors() as $error ) {
369
			if ( $type === false || $error['type'] === $type ) {
370
				if ( $error['message'] instanceof MessageSpecifier ) {
371
					$result[] = array_merge(
372
						[ $error['message']->getKey() ],
373
						$error['message']->getParams()
374
					);
375
				} elseif ( $error['params'] ) {
376
					$result[] = array_merge( [ $error['message'] ], $error['params'] );
377
				} else {
378
					$result[] = [ $error['message'] ];
379
				}
380
			}
381
		}
382
383
		return $result;
384
	}
385
386
	/**
387
	 * Don't save the callback when serializing, because Closures can't be
388
	 * serialized and we're going to clear it in __wakeup anyway.
389
	 */
390
	function __sleep() {
391
		$keys = array_keys( get_object_vars( $this ) );
392
		return array_diff( $keys, [ 'cleanCallback' ] );
393
	}
394
395
	/**
396
	 * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
397
	 */
398
	function __wakeup() {
399
		$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...
400
	}
401
}
402