Use HTML::hidden to create input fields
[lhc/web/wiklou.git] / includes / libs / StatusValue.php
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21 /**
22 * Generic operation result class
23 * Has warning/error list, boolean status and arbitrary value
24 *
25 * "Good" means the operation was completed with no warnings or errors.
26 *
27 * "OK" means the operation was partially or wholly completed.
28 *
29 * An operation which is not OK should have errors so that the user can be
30 * informed as to what went wrong. Calling the fatal() function sets an error
31 * message and simultaneously switches off the OK flag.
32 *
33 * The recommended pattern for Status objects is to return a StatusValue
34 * unconditionally, i.e. both on success and on failure -- so that the
35 * developer of the calling code is reminded that the function can fail, and
36 * so that a lack of error-handling will be explicit.
37 *
38 * The use of Message objects should be avoided when serializability is needed.
39 *
40 * @since 1.25
41 */
42 class StatusValue {
43 /** @var bool */
44 protected $ok = true;
45 /** @var array */
46 protected $errors = [];
47
48 /** @var mixed */
49 public $value;
50 /** @var array Map of (key => bool) to indicate success of each part of batch operations */
51 public $success = [];
52 /** @var int Counter for batch operations */
53 public $successCount = 0;
54 /** @var int Counter for batch operations */
55 public $failCount = 0;
56
57 /**
58 * Factory function for fatal errors
59 *
60 * @param string|MessageSpecifier $message Message key or object
61 * @return static
62 */
63 public static function newFatal( $message /*, parameters...*/ ) {
64 $params = func_get_args();
65 $result = new static();
66 call_user_func_array( [ &$result, 'fatal' ], $params );
67 return $result;
68 }
69
70 /**
71 * Factory function for good results
72 *
73 * @param mixed $value
74 * @return static
75 */
76 public static function newGood( $value = null ) {
77 $result = new static();
78 $result->value = $value;
79 return $result;
80 }
81
82 /**
83 * Splits this StatusValue object into two new StatusValue objects, one which contains only
84 * the error messages, and one that contains the warnings, only. The returned array is
85 * defined as:
86 * [
87 * 0 => object(StatusValue) # the StatusValue with error messages, only
88 * 1 => object(StatusValue) # The StatusValue with warning messages, only
89 * ]
90 *
91 * @return StatusValue[]
92 */
93 public function splitByErrorType() {
94 $errorsOnlyStatusValue = clone $this;
95 $warningsOnlyStatusValue = clone $this;
96 $warningsOnlyStatusValue->ok = true;
97
98 $errorsOnlyStatusValue->errors = $warningsOnlyStatusValue->errors = [];
99 foreach ( $this->errors as $item ) {
100 if ( $item['type'] === 'warning' ) {
101 $warningsOnlyStatusValue->errors[] = $item;
102 } else {
103 $errorsOnlyStatusValue->errors[] = $item;
104 }
105 };
106
107 return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
108 }
109
110 /**
111 * Returns whether the operation completed and didn't have any error or
112 * warnings
113 *
114 * @return bool
115 */
116 public function isGood() {
117 return $this->ok && !$this->errors;
118 }
119
120 /**
121 * Returns whether the operation completed
122 *
123 * @return bool
124 */
125 public function isOK() {
126 return $this->ok;
127 }
128
129 /**
130 * @return mixed
131 */
132 public function getValue() {
133 return $this->value;
134 }
135
136 /**
137 * Get the list of errors
138 *
139 * Each error is a (message:string or MessageSpecifier,params:array) map
140 *
141 * @return array
142 */
143 public function getErrors() {
144 return $this->errors;
145 }
146
147 /**
148 * Change operation status
149 *
150 * @param bool $ok
151 */
152 public function setOK( $ok ) {
153 $this->ok = $ok;
154 }
155
156 /**
157 * Change operation result
158 *
159 * @param bool $ok Whether the operation completed
160 * @param mixed $value
161 */
162 public function setResult( $ok, $value = null ) {
163 $this->ok = (bool)$ok;
164 $this->value = $value;
165 }
166
167 /**
168 * Add a new warning
169 *
170 * @param string|MessageSpecifier $message Message key or object
171 */
172 public function warning( $message /*, parameters... */ ) {
173 $this->errors[] = [
174 'type' => 'warning',
175 'message' => $message,
176 'params' => array_slice( func_get_args(), 1 )
177 ];
178 }
179
180 /**
181 * Add an error, do not set fatal flag
182 * This can be used for non-fatal errors
183 *
184 * @param string|MessageSpecifier $message Message key or object
185 */
186 public function error( $message /*, parameters... */ ) {
187 $this->errors[] = [
188 'type' => 'error',
189 'message' => $message,
190 'params' => array_slice( func_get_args(), 1 )
191 ];
192 }
193
194 /**
195 * Add an error and set OK to false, indicating that the operation
196 * as a whole was fatal
197 *
198 * @param string|MessageSpecifier $message Message key or object
199 */
200 public function fatal( $message /*, parameters... */ ) {
201 $this->errors[] = [
202 'type' => 'error',
203 'message' => $message,
204 'params' => array_slice( func_get_args(), 1 )
205 ];
206 $this->ok = false;
207 }
208
209 /**
210 * Merge another status object into this one
211 *
212 * @param StatusValue $other Other StatusValue object
213 * @param bool $overwriteValue Whether to override the "value" member
214 */
215 public function merge( $other, $overwriteValue = false ) {
216 $this->errors = array_merge( $this->errors, $other->errors );
217 $this->ok = $this->ok && $other->ok;
218 if ( $overwriteValue ) {
219 $this->value = $other->value;
220 }
221 $this->successCount += $other->successCount;
222 $this->failCount += $other->failCount;
223 }
224
225 /**
226 * Returns a list of status messages of the given type
227 *
228 * Each entry is a map of:
229 * - message: string message key or MessageSpecifier
230 * - params: array list of parameters
231 *
232 * @param string $type
233 * @return array
234 */
235 public function getErrorsByType( $type ) {
236 $result = [];
237 foreach ( $this->errors as $error ) {
238 if ( $error['type'] === $type ) {
239 $result[] = $error;
240 }
241 }
242
243 return $result;
244 }
245
246 /**
247 * Returns true if the specified message is present as a warning or error
248 *
249 * @param string|MessageSpecifier $message Message key or object to search for
250 *
251 * @return bool
252 */
253 public function hasMessage( $message ) {
254 if ( $message instanceof MessageSpecifier ) {
255 $message = $message->getKey();
256 }
257 foreach ( $this->errors as $error ) {
258 if ( $error['message'] instanceof MessageSpecifier
259 && $error['message']->getKey() === $message
260 ) {
261 return true;
262 } elseif ( $error['message'] === $message ) {
263 return true;
264 }
265 }
266
267 return false;
268 }
269
270 /**
271 * If the specified source message exists, replace it with the specified
272 * destination message, but keep the same parameters as in the original error.
273 *
274 * Note, due to the lack of tools for comparing IStatusMessage objects, this
275 * function will not work when using such an object as the search parameter.
276 *
277 * @param MessageSpecifier|string $source Message key or object to search for
278 * @param MessageSpecifier|string $dest Replacement message key or object
279 * @return bool Return true if the replacement was done, false otherwise.
280 */
281 public function replaceMessage( $source, $dest ) {
282 $replaced = false;
283
284 foreach ( $this->errors as $index => $error ) {
285 if ( $error['message'] === $source ) {
286 $this->errors[$index]['message'] = $dest;
287 $replaced = true;
288 }
289 }
290
291 return $replaced;
292 }
293
294 /**
295 * @return string
296 */
297 public function __toString() {
298 $status = $this->isOK() ? "OK" : "Error";
299 if ( count( $this->errors ) ) {
300 $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
301 } else {
302 $errorcount = "no errors detected";
303 }
304 if ( isset( $this->value ) ) {
305 $valstr = gettype( $this->value ) . " value set";
306 if ( is_object( $this->value ) ) {
307 $valstr .= "\"" . get_class( $this->value ) . "\" instance";
308 }
309 } else {
310 $valstr = "no value set";
311 }
312 $out = sprintf( "<%s, %s, %s>",
313 $status,
314 $errorcount,
315 $valstr
316 );
317 if ( count( $this->errors ) > 0 ) {
318 $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
319 $i = 1;
320 $out .= "\n";
321 $out .= $hdr;
322 foreach ( $this->errors as $error ) {
323 if ( $error['message'] instanceof MessageSpecifier ) {
324 $key = $error['message']->getKey();
325 $params = $error['message']->getParams();
326 } elseif ( $error['params'] ) {
327 $key = $error['message'];
328 $params = $error['params'];
329 } else {
330 $key = $error['message'];
331 $params = [];
332 }
333
334 $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
335 $i,
336 $key,
337 implode( " ", $params )
338 );
339 $i += 1;
340 }
341 $out .= $hdr;
342 }
343
344 return $out;
345 }
346 }