Issues (165)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Plugins/Xml.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of Peachy MediaWiki Bot API
4
 *
5
 * Peachy 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 3 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
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
/**
20
 * Module of static functions for generating XML
21
 */
22
class Xml {
23
	/**
24
	 * Format an XML element with given attributes and, optionally, text content.
25
	 * Element and attribute names are assumed to be ready for literal inclusion.
26
	 * Strings are assumed to not contain XML-illegal characters; special
27
	 * characters (<, >, &) are escaped but illegals are not touched.
28
	 *
29
	 * @param string|null $element element name
30
	 * @param array $attribs Name=>value pairs. Values will be escaped.
31
	 * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
32
	 * @param bool $allowShortTag whether '' in $contents will result in a contentless closed tag
33
	 * @return string
34
	 */
35
	public static function element( $element = null, $attribs = null, $contents = '', $allowShortTag = true ) {
36
		$out = '<' . $element;
37
		if( !is_null( $attribs ) ) {
38
			$out .= self::expandAttributes( $attribs );
39
		}
40
		if( is_null( $contents ) ) {
41
			$out .= '>';
42
		} else {
43
			if( $allowShortTag && $contents === '' ) {
44
				$out .= ' />';
45
			} else {
46
				$out .= '>' . htmlspecialchars( $contents ) . "</$element>";
47
			}
48
		}
49
		return $out;
50
	}
51
52
	static function encodeAttribute( $text ) {
53
		$encValue = htmlspecialchars( $text, ENT_QUOTES );
54
55
		// Whitespace is normalized during attribute decoding,
56
		// so if we've been passed non-spaces we must encode them
57
		// ahead of time or they won't be preserved.
58
		$encValue = strtr(
59
			$encValue, array(
60
				"\n" => '&#10;',
61
				"\r" => '&#13;',
62
				"\t" => '&#9;',
63
			)
64
		);
65
66
		return $encValue;
67
	}
68
69
	/**
70
	 * Given an array of ('attributename' => 'value'), it generates the code
71
	 * to set the XML attributes : attributename="value".
72
	 * The values are passed to self::encodeAttribute.
73
	 * Return null if no attributes given.
74
	 * @param array|null $attribs of attributes for an XML element
75
	 * @throws Exception
76
	 * @return null|string
77
	 */
78
	public static function expandAttributes( $attribs = null ) {
79
		$out = '';
80
		if( is_null( $attribs ) ) {
81
			return null;
82
		} elseif( is_array( $attribs ) ) {
83
			foreach( $attribs as $name => $val ){
84
				$out .= " {$name}=\"" . self::encodeAttribute( $val ) . '"';
85
			}
86
			return $out;
87
		} else {
88
			throw new Exception( 'Expected attribute array, got something else in ' . __METHOD__ );
89
		}
90
	}
91
92
	/**
93
	 * Format an XML element as with self::element(), but run text through the
94
	 * $wgContLang->normalize() validator first to ensure that no invalid UTF-8
95
	 * is passed.
96
	 *
97
	 * @param $element String:
98
	 * @param array $attribs Name=>value pairs. Values will be escaped.
99
	 * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
100
	 * @return string
101
	 */
102
	public static function elementClean( $element, $attribs = array(), $contents = '' ) {
103
		if( $attribs ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attribs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
104
			$attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
105
		}
106
		if( $contents ) {
107
			$contents = WebRequest::normalize_static( $contents );
108
		}
109
		return self::element( $element, $attribs, $contents );
110
	}
111
112
	/**
113
	 * This opens an XML element
114
	 *
115
	 * @param string $element name of the element
116
	 * @param array $attribs of attributes, see self::expandAttributes()
117
	 * @return string
118
	 */
119
	public static function openElement( $element, $attribs = null ) {
120
		return '<' . $element . self::expandAttributes( $attribs ) . '>';
121
	}
122
123
	/**
124
	 * Shortcut to close an XML element
125
	 * @param string $element element name
126
	 * @return string
127
	 */
128
	public static function closeElement( $element ) {
129
		return "</$element>";
130
	}
131
132
	/**
133
	 * Same as self::element(), but does not escape contents. Handy when the
134
	 * content you have is already valid xml.
135
	 *
136
	 * @param string $element element name
137
	 * @param array $attribs of attributes
138
	 * @param string $contents content of the element
139
	 * @return string
140
	 */
141
	public static function tags( $element, $attribs = null, $contents ) {
142
		return self::openElement( $element, $attribs ) . $contents . "</$element>";
143
	}
144
145
	/**
146
	 * Shortcut to make a span element
147
	 * @param string $text content of the element, will be escaped
148
	 * @param string $class class name of the span element
149
	 * @param array $attribs other attributes
150
	 * @return string
151
	 */
152
	public static function span( $text, $class, $attribs = array() ) {
153
		return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
154
	}
155
156
	/**
157
	 * Shortcut to make a specific element with a class attribute
158
	 * @param string $text content of the element, will be escaped
159
	 * @param string $class class name of the span element
160
	 * @param string $pgTag element name
161
	 * @param array $attribs other attributes
162
	 * @return string
163
	 */
164
	public static function wrapClass( $text, $class, $pgTag = 'span', $attribs = array() ) {
165
		return self::tags( $pgTag, array( 'class' => $class ) + $attribs, $text );
166
	}
167
168
	/**
169
	 * Convenience function to build an HTML text input field
170
	 * @param string $name value of the name attribute
171
	 * @param bool|int $size value of the size attribute
172
	 * @param mixed $value mixed value of the value attribute
173
	 * @param array $attribs other attributes
174
	 * @return string HTML
175
	 */
176
	public static function input( $name, $size = false, $value = false, $attribs = array() ) {
177
		$attributes = array( 'name' => $name );
178
179
		if( $size ) {
180
			$attributes['size'] = $size;
181
		}
182
183
		if( $value !== false ) { // maybe 0
184
			$attributes['value'] = $value;
185
		}
186
187
		return self::element( 'input', $attributes + $attribs );
188
	}
189
190
	/**
191
	 * Convenience function to build an HTML password input field
192
	 * @param string $name value of the name attribute
193
	 * @param bool|int $size value of the size attribute
194
	 * @param $value mixed value of the value attribute
195
	 * @param array $attribs other attributes
196
	 * @return string HTML
197
	 */
198
	public static function password( $name, $size = false, $value = false, $attribs = array() ) {
199
		return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
200
	}
201
202
	/**
203
	 * Internal function for use in checkboxes and radio buttons and such.
204
	 *
205
	 * @param $name string
206
	 * @param $present bool
207
	 *
208
	 * @return array
209
	 */
210
	public static function attrib( $name, $present = true ) {
211
		return $present ? array( $name => $name ) : array();
212
	}
213
214
	/**
215
	 * Convenience function to build an HTML checkbox
216
	 * @param string $name value of the name attribute
217
	 * @param bool $checked Whether the checkbox is checked or not
218
	 * @param array $attribs other attributes
219
	 * @return string HTML
220
	 */
221
	public static function check( $name, $checked = false, $attribs = array() ) {
222
		return self::element(
223
			'input', array_merge(
224
				array(
225
					'name'  => $name,
226
					'type'  => 'checkbox',
227
					'value' => 1
228
				),
229
				self::attrib( 'checked', $checked ),
230
				$attribs
231
			)
232
		);
233
	}
234
235
	/**
236
	 * Convenience function to build an HTML radio button
237
	 * @param string $name value of the name attribute
238
	 * @param string $value value of the value attribute
239
	 * @param bool $checked Whether the checkbox is checked or not
240
	 * @param array $attribs other attributes
241
	 * @return string HTML
242
	 */
243
	public static function radio( $name, $value, $checked = false, $attribs = array() ) {
244
		return self::element(
245
			'input', array(
246
						 'name'  => $name,
247
						 'type'  => 'radio',
248
						 'value' => $value
249
					 ) + self::attrib( 'checked', $checked ) + $attribs
250
		);
251
	}
252
253
	/**
254
	 * Convenience function to build an HTML form label
255
	 * @param string $label text of the label
256
	 * @param $id
257
	 * @param array $attribs an attribute array.  This will usually be
258
	 *     the same array as is passed to the corresponding input element,
259
	 *     so this function will cherry-pick appropriate attributes to
260
	 *     apply to the label as well; only class and title are applied.
261
	 * @return string HTML
262
	 */
263
	public static function label( $label, $id, $attribs = array() ) {
264
		$a = array( 'for' => $id );
265
266
		// FIXME avoid copy pasting below:
267
		if( isset( $attribs['class'] ) ) {
268
			$a['class'] = $attribs['class'];
269
		}
270
		if( isset( $attribs['title'] ) ) {
271
			$a['title'] = $attribs['title'];
272
		}
273
274
		return self::element( 'label', $a, $label );
275
	}
276
277
	/**
278
	 * Convenience function to build an HTML text input field with a label
279
	 * @param string $label text of the label
280
	 * @param string $name value of the name attribute
281
	 * @param string $id id of the input
282
	 * @param int|Bool $size value of the size attribute
283
	 * @param string|Bool $value value of the value attribute
284
	 * @param array $attribs other attributes
285
	 * @return string HTML
286
	 */
287
	public static function inputLabel( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
288
		list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
0 ignored issues
show
It seems like $size can also be of type integer; however, Xml::inputLabelSep() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
It seems like $value can also be of type string; however, Xml::inputLabelSep() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
289
		return $label . '&#160;' . $input;
290
	}
291
292
	/**
293
	 * Same as self::inputLabel() but return input and label in an array
294
	 *
295
	 * @param $label String
296
	 * @param $name String
297
	 * @param $id String
298
	 * @param $size Int|Bool
299
	 * @param $value String|Bool
300
	 * @param $attribs array
301
	 *
302
	 * @return array
303
	 */
304
	public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
305
		return array(
306
			self::label( $label, $id, $attribs ),
307
			self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
308
		);
309
	}
310
311
	/**
312
	 * Convenience function to build an HTML checkbox with a label
313
	 *
314
	 * @param $label
315
	 * @param $name
316
	 * @param $id
317
	 * @param $checked bool
318
	 * @param $attribs array
319
	 *
320
	 * @return string HTML
321
	 */
322
	public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
323
		return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
324
			   '&#160;' .
325
			   self::label( $label, $id, $attribs );
326
	}
327
328
	/**
329
	 * Convenience function to build an HTML radio button with a label
330
	 *
331
	 * @param $label
332
	 * @param $name
333
	 * @param $value
334
	 * @param $id
335
	 * @param $checked bool
336
	 * @param $attribs array
337
	 *
338
	 * @return string HTML
339
	 */
340
	public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
341
		return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
342
			   '&#160;' .
343
			   self::label( $label, $id, $attribs );
344
	}
345
346
	/**
347
	 * Convenience function to build an HTML submit button
348
	 * @param string $value label text for the button
349
	 * @param array $attribs optional custom attributes
350
	 * @return string HTML
351
	 */
352
	public static function submitButton( $value, $attribs = array() ) {
353
		return self::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
354
	}
355
356
	/**
357
	 * Convenience function to build an HTML drop-down list item.
358
	 * @param string $text text for this item. Will be HTML escaped
359
	 * @param string $value form submission value; if empty, use text
360
	 * @param $selected boolean: if true, will be the default selected item
361
	 * @param array $attribs optional additional HTML attributes
362
	 * @return string HTML
363
	 */
364
	public static function option( $text, $value = null, $selected = false,
365
								   $attribs = array() ) {
366
		if( !is_null( $value ) ) {
367
			$attribs['value'] = $value;
368
		}
369
		if( $selected ) {
370
			$attribs['selected'] = 'selected';
371
		}
372
		return self::element( 'option', $attribs, $text );
373
	}
374
375
	/**
376
	 * Build a drop-down box from a textual list.
377
	 *
378
	 * @param $name Mixed: Name and id for the drop-down
379
	 * @param $list Mixed: Correctly formatted text (newline delimited) to be used to generate the options
380
	 * @param $other Mixed: Text for the "Other reasons" option
381
	 * @param $selected Mixed: Option which should be pre-selected
382
	 * @param $class Mixed: CSS classes for the drop-down
383
	 * @param $tabindex Mixed: Value of the tabindex attribute
384
	 * @return string
385
	 */
386
	public static function listDropDown( $name = '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
387
		$optgroup = false;
388
389
		$options = self::option( $other, 'other', $selected === 'other' );
390
391
		foreach( explode( "\n", $list ) as $option ){
392
			$value = trim( $option );
393
			if( $value == '' ) {
394
				continue;
395
			} elseif( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
396
				// A new group is starting ...
397
				$value = trim( substr( $value, 1 ) );
398
				if( $optgroup ) {
399
					$options .= self::closeElement( 'optgroup' );
400
				}
401
				$options .= self::openElement( 'optgroup', array( 'label' => $value ) );
402
				$optgroup = true;
403
			} elseif( substr( $value, 0, 2 ) == '**' ) {
404
				// groupmember
405
				$value = trim( substr( $value, 2 ) );
406
				$options .= self::option( $value, $value, $selected === $value );
407
			} else {
408
				// groupless reason list
409
				if( $optgroup ) {
410
					$options .= self::closeElement( 'optgroup' );
411
				}
412
				$options .= self::option( $value, $value, $selected === $value );
413
				$optgroup = false;
414
			}
415
		}
416
417
		if( $optgroup ) {
418
			$options .= self::closeElement( 'optgroup' );
419
		}
420
421
		$attribs = array();
422
423
		if( $name ) {
424
			$attribs['id'] = $name;
425
			$attribs['name'] = $name;
426
		}
427
428
		if( $class ) {
429
			$attribs['class'] = $class;
430
		}
431
432
		if( $tabindex ) {
433
			$attribs['tabindex'] = $tabindex;
434
		}
435
436
		return self::openElement( 'select', $attribs )
437
			   . "\n"
438
			   . $options
439
			   . "\n"
440
			   . self::closeElement( 'select' );
441
	}
442
443
	/**
444
	 * Shortcut for creating fieldsets.
445
	 *
446
	 * @param string|bool $legend Legend of the fieldset. If evaluates to false, legend is not added.
447
	 * @param string|bool $content Pre-escaped content for the fieldset. If false, only open fieldset is returned.
448
	 * @param array $attribs Any attributes to fieldset-element.
449
	 *
450
	 * @return string
451
	 */
452
	public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
453
		$s = self::openElement( 'fieldset', $attribs ) . "\n";
454
455
		if( $legend ) {
456
			$s .= self::element( 'legend', null, $legend ) . "\n";
0 ignored issues
show
It seems like $legend defined by parameter $legend on line 452 can also be of type boolean; however, Xml::element() does only seem to accept 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...
457
		}
458
459
		if( $content !== false ) {
460
			$s .= $content . "\n";
461
			$s .= self::closeElement( 'fieldset' ) . "\n";
462
		}
463
464
		return $s;
465
	}
466
467
	/**
468
	 * Shortcut for creating textareas.
469
	 *
470
	 * @param string $name The 'name' for the textarea
471
	 * @param string $content Content for the textarea
472
	 * @param int $cols The number of columns for the textarea
473
	 * @param int $rows The number of rows for the textarea
474
	 * @param array $attribs Any other attributes for the textarea
475
	 *
476
	 * @return string
477
	 */
478
	public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
479
		return self::element(
480
			'textarea',
481
			array(
482
				'name' => $name,
483
				'id'   => $name,
484
				'cols' => $cols,
485
				'rows' => $rows
486
			) + $attribs,
487
			$content, false
488
		);
489
	}
490
491
	/**
492
	 * Returns an escaped string suitable for inclusion in a string literal
493
	 * for JavaScript source code.
494
	 * Illegal control characters are assumed not to be present.
495
	 *
496
	 * @param string $string to escape
497
	 * @return String
498
	 */
499
	public static function escapeJsString( $string ) {
500
		// See ECMA 262 section 7.8.4 for string literal format
501
		$pairs = array(
502
			"\\"           => "\\\\",
503
			"\""           => "\\\"",
504
			'\''           => '\\\'',
505
			"\n"           => "\\n",
506
			"\r"           => "\\r",
507
508
			# To avoid closing the element or CDATA section
509
			"<"            => "\\x3c",
510
			">"            => "\\x3e",
511
512
			# To avoid any complaints about bad entity refs
513
			"&"            => "\\x26",
514
515
			# Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
516
			# Encode certain Unicode formatting chars so affected
517
			# versions of Gecko don't misinterpret our strings;
518
			# this is a common problem with Farsi text.
519
			"\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
520
			"\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
521
		);
522
523
		return strtr( $string, $pairs );
524
	}
525
526
	/**
527
	 * Encode a variable of unknown type to JavaScript.
528
	 * Arrays are converted to JS arrays, objects are converted to JS associative
529
	 * arrays (objects). So cast your PHP associative arrays to objects before
530
	 * passing them to here.
531
	 *
532
	 * @param $value
533
	 * @return int|string
534
	 */
535
	public static function encodeJsVar( $value ) {
536
		if( is_bool( $value ) ) {
537
			$s = $value ? 'true' : 'false';
538
		} elseif( is_null( $value ) ) {
539
			$s = 'null';
540
		} elseif( is_int( $value ) ) {
541
			$s = $value;
542
		} elseif( is_array( $value ) && // Make sure it's not associative.
543
				  array_keys( $value ) === range( 0, count( $value ) - 1 ) ||
544
				  count( $value ) == 0
545
		) {
546
			$s = '[';
547
			foreach( $value as $elt ){
548
				if( $s != '[' ) {
549
					$s .= ', ';
550
				}
551
				$s .= self::encodeJsVar( $elt );
552
			}
553
			$s .= ']';
554
		} elseif( is_object( $value ) || is_array( $value ) ) {
555
			// Objects and associative arrays
556
			$s = '{';
557
			foreach( (array)$value as $name => $elt ){
558
				if( $s != '{' ) {
559
					$s .= ', ';
560
				}
561
				$s .= '"' . self::escapeJsString( $name ) . '": ' .
562
					  self::encodeJsVar( $elt );
563
			}
564
			$s .= '}';
565
		} else {
566
			$s = '"' . self::escapeJsString( $value ) . '"';
567
		}
568
		return $s;
569
	}
570
571
	/**
572
	 * Create a call to a JavaScript function. The supplied arguments will be
573
	 * encoded using self::encodeJsVar().
574
	 *
575
	 * @since 1.17
576
	 * @param string $name The name of the function to call, or a JavaScript expression
577
	 *    which evaluates to a function object which is called.
578
	 * @param array $args The arguments to pass to the function.
579
	 * @param bool $pretty If true, add non-significant whitespace to improve readability.
580
	 * @return string|bool: String if successful; false upon failure
581
	 */
582
	public static function encodeJsCall( $name, $args, $pretty = false ) {
583
		foreach( $args as &$arg ){
584
			$arg = self::encodeJsVar($arg);
585
			if( $arg === false ) {
586
				return false;
587
			}
588
		}
589
590
		return "$name(" . ( $pretty
591
			? ( ' ' . implode( ', ', $args ) . ' ' )
592
			: implode( ',', $args )
593
		) . ");";
594
	}
595
596
	/**
597
	 * Check if a string is well-formed XML.
598
	 * Must include the surrounding tag.
599
	 *
600
	 * @param string $text string to test.
601
	 * @return bool
602
	 *
603
	 * @todo Error position reporting return
604
	 */
605
	public static function isWellFormed( $text ) {
606
		$parser = xml_parser_create( "UTF-8" );
607
608
		# case folding violates XML standard, turn it off
609
		xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
610
611
		if( !xml_parse( $parser, $text, true ) ) {
612
			//$err = xml_error_string( xml_get_error_code( $parser ) );
613
			//$position = xml_get_current_byte_index( $parser );
614
			//$fragment = $this->extractFragment( $html, $position );
615
			//$this->mXmlError = "$err at byte $position:\n$fragment";
616
			xml_parser_free( $parser );
617
			return false;
618
		}
619
620
		xml_parser_free( $parser );
621
622
		return true;
623
	}
624
625
	/**
626
	 * Check if a string is a well-formed XML fragment.
627
	 * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
628
	 * and can use HTML named entities.
629
	 *
630
	 * @param $text String:
631
	 * @return bool
632
	 */
633
	public static function isWellFormedXmlFragment( $text ) {
634
		$html =
635
			self::hackDocType() .
636
			'<html>' .
637
			$text .
638
			'</html>';
639
640
		return self::isWellFormed( $html );
641
	}
642
643
	static function hackDocType() {
644
		$wgHtmlEntities = array(
645
			'Aacute'   => 193,
646
			'aacute'   => 225,
647
			'Acirc'    => 194,
648
			'acirc'    => 226,
649
			'acute'    => 180,
650
			'AElig'    => 198,
651
			'aelig'    => 230,
652
			'Agrave'   => 192,
653
			'agrave'   => 224,
654
			'alefsym'  => 8501,
655
			'Alpha'    => 913,
656
			'alpha'    => 945,
657
			'amp'      => 38,
658
			'and'      => 8743,
659
			'ang'      => 8736,
660
			'Aring'    => 197,
661
			'aring'    => 229,
662
			'asymp'    => 8776,
663
			'Atilde'   => 195,
664
			'atilde'   => 227,
665
			'Auml'     => 196,
666
			'auml'     => 228,
667
			'bdquo'    => 8222,
668
			'Beta'     => 914,
669
			'beta'     => 946,
670
			'brvbar'   => 166,
671
			'bull'     => 8226,
672
			'cap'      => 8745,
673
			'Ccedil'   => 199,
674
			'ccedil'   => 231,
675
			'cedil'    => 184,
676
			'cent'     => 162,
677
			'Chi'      => 935,
678
			'chi'      => 967,
679
			'circ'     => 710,
680
			'clubs'    => 9827,
681
			'cong'     => 8773,
682
			'copy'     => 169,
683
			'crarr'    => 8629,
684
			'cup'      => 8746,
685
			'curren'   => 164,
686
			'dagger'   => 8224,
687
			'Dagger'   => 8225,
688
			'darr'     => 8595,
689
			'dArr'     => 8659,
690
			'deg'      => 176,
691
			'Delta'    => 916,
692
			'delta'    => 948,
693
			'diams'    => 9830,
694
			'divide'   => 247,
695
			'Eacute'   => 201,
696
			'eacute'   => 233,
697
			'Ecirc'    => 202,
698
			'ecirc'    => 234,
699
			'Egrave'   => 200,
700
			'egrave'   => 232,
701
			'empty'    => 8709,
702
			'emsp'     => 8195,
703
			'ensp'     => 8194,
704
			'Epsilon'  => 917,
705
			'epsilon'  => 949,
706
			'equiv'    => 8801,
707
			'Eta'      => 919,
708
			'eta'      => 951,
709
			'ETH'      => 208,
710
			'eth'      => 240,
711
			'Euml'     => 203,
712
			'euml'     => 235,
713
			'euro'     => 8364,
714
			'exist'    => 8707,
715
			'fnof'     => 402,
716
			'forall'   => 8704,
717
			'frac12'   => 189,
718
			'frac14'   => 188,
719
			'frac34'   => 190,
720
			'frasl'    => 8260,
721
			'Gamma'    => 915,
722
			'gamma'    => 947,
723
			'ge'       => 8805,
724
			'gt'       => 62,
725
			'harr'     => 8596,
726
			'hArr'     => 8660,
727
			'hearts'   => 9829,
728
			'hellip'   => 8230,
729
			'Iacute'   => 205,
730
			'iacute'   => 237,
731
			'Icirc'    => 206,
732
			'icirc'    => 238,
733
			'iexcl'    => 161,
734
			'Igrave'   => 204,
735
			'igrave'   => 236,
736
			'image'    => 8465,
737
			'infin'    => 8734,
738
			'int'      => 8747,
739
			'Iota'     => 921,
740
			'iota'     => 953,
741
			'iquest'   => 191,
742
			'isin'     => 8712,
743
			'Iuml'     => 207,
744
			'iuml'     => 239,
745
			'Kappa'    => 922,
746
			'kappa'    => 954,
747
			'Lambda'   => 923,
748
			'lambda'   => 955,
749
			'lang'     => 9001,
750
			'laquo'    => 171,
751
			'larr'     => 8592,
752
			'lArr'     => 8656,
753
			'lceil'    => 8968,
754
			'ldquo'    => 8220,
755
			'le'       => 8804,
756
			'lfloor'   => 8970,
757
			'lowast'   => 8727,
758
			'loz'      => 9674,
759
			'lrm'      => 8206,
760
			'lsaquo'   => 8249,
761
			'lsquo'    => 8216,
762
			'lt'       => 60,
763
			'macr'     => 175,
764
			'mdash'    => 8212,
765
			'micro'    => 181,
766
			'middot'   => 183,
767
			'minus'    => 8722,
768
			'Mu'       => 924,
769
			'mu'       => 956,
770
			'nabla'    => 8711,
771
			'nbsp'     => 160,
772
			'ndash'    => 8211,
773
			'ne'       => 8800,
774
			'ni'       => 8715,
775
			'not'      => 172,
776
			'notin'    => 8713,
777
			'nsub'     => 8836,
778
			'Ntilde'   => 209,
779
			'ntilde'   => 241,
780
			'Nu'       => 925,
781
			'nu'       => 957,
782
			'Oacute'   => 211,
783
			'oacute'   => 243,
784
			'Ocirc'    => 212,
785
			'ocirc'    => 244,
786
			'OElig'    => 338,
787
			'oelig'    => 339,
788
			'Ograve'   => 210,
789
			'ograve'   => 242,
790
			'oline'    => 8254,
791
			'Omega'    => 937,
792
			'omega'    => 969,
793
			'Omicron'  => 927,
794
			'omicron'  => 959,
795
			'oplus'    => 8853,
796
			'or'       => 8744,
797
			'ordf'     => 170,
798
			'ordm'     => 186,
799
			'Oslash'   => 216,
800
			'oslash'   => 248,
801
			'Otilde'   => 213,
802
			'otilde'   => 245,
803
			'otimes'   => 8855,
804
			'Ouml'     => 214,
805
			'ouml'     => 246,
806
			'para'     => 182,
807
			'part'     => 8706,
808
			'permil'   => 8240,
809
			'perp'     => 8869,
810
			'Phi'      => 934,
811
			'phi'      => 966,
812
			'Pi'       => 928,
813
			'pi'       => 960,
814
			'piv'      => 982,
815
			'plusmn'   => 177,
816
			'pound'    => 163,
817
			'prime'    => 8242,
818
			'Prime'    => 8243,
819
			'prod'     => 8719,
820
			'prop'     => 8733,
821
			'Psi'      => 936,
822
			'psi'      => 968,
823
			'quot'     => 34,
824
			'radic'    => 8730,
825
			'rang'     => 9002,
826
			'raquo'    => 187,
827
			'rarr'     => 8594,
828
			'rArr'     => 8658,
829
			'rceil'    => 8969,
830
			'rdquo'    => 8221,
831
			'real'     => 8476,
832
			'reg'      => 174,
833
			'rfloor'   => 8971,
834
			'Rho'      => 929,
835
			'rho'      => 961,
836
			'rlm'      => 8207,
837
			'rsaquo'   => 8250,
838
			'rsquo'    => 8217,
839
			'sbquo'    => 8218,
840
			'Scaron'   => 352,
841
			'scaron'   => 353,
842
			'sdot'     => 8901,
843
			'sect'     => 167,
844
			'shy'      => 173,
845
			'Sigma'    => 931,
846
			'sigma'    => 963,
847
			'sigmaf'   => 962,
848
			'sim'      => 8764,
849
			'spades'   => 9824,
850
			'sub'      => 8834,
851
			'sube'     => 8838,
852
			'sum'      => 8721,
853
			'sup'      => 8835,
854
			'sup1'     => 185,
855
			'sup2'     => 178,
856
			'sup3'     => 179,
857
			'supe'     => 8839,
858
			'szlig'    => 223,
859
			'Tau'      => 932,
860
			'tau'      => 964,
861
			'there4'   => 8756,
862
			'Theta'    => 920,
863
			'theta'    => 952,
864
			'thetasym' => 977,
865
			'thinsp'   => 8201,
866
			'THORN'    => 222,
867
			'thorn'    => 254,
868
			'tilde'    => 732,
869
			'times'    => 215,
870
			'trade'    => 8482,
871
			'Uacute'   => 218,
872
			'uacute'   => 250,
873
			'uarr'     => 8593,
874
			'uArr'     => 8657,
875
			'Ucirc'    => 219,
876
			'ucirc'    => 251,
877
			'Ugrave'   => 217,
878
			'ugrave'   => 249,
879
			'uml'      => 168,
880
			'upsih'    => 978,
881
			'Upsilon'  => 933,
882
			'upsilon'  => 965,
883
			'Uuml'     => 220,
884
			'uuml'     => 252,
885
			'weierp'   => 8472,
886
			'Xi'       => 926,
887
			'xi'       => 958,
888
			'Yacute'   => 221,
889
			'yacute'   => 253,
890
			'yen'      => 165,
891
			'Yuml'     => 376,
892
			'yuml'     => 255,
893
			'Zeta'     => 918,
894
			'zeta'     => 950,
895
			'zwj'      => 8205,
896
			'zwnj'     => 8204
897
		);
898
		$out = "<!DOCTYPE html [\n";
899
		foreach( $wgHtmlEntities as $entity => $codepoint ){
900
			$out .= "<!ENTITY $entity \"&#$codepoint;\">";
901
		}
902
		$out .= "]>\n";
903
		return $out;
904
	}
905
906
	/**
907
	 * Replace " > and < with their respective HTML entities ( &quot;,
908
	 * &gt;, &lt;)
909
	 *
910
	 * @param string $in text that might contain HTML tags.
911
	 * @return string Escaped string
912
	 */
913
	public static function escapeTagsOnly( $in ) {
914
		return str_replace(
915
			array( '"', '>', '<' ),
916
			array( '&quot;', '&gt;', '&lt;' ),
917
			$in
918
		);
919
	}
920
921
	/**
922
	 * Build a table of data
923
	 * @param array $rows An array of arrays of strings, each to be a row in a table
924
	 * @param array $attribs An array of attributes to apply to the table tag [optional]
925
	 * @param array $headers An array of strings to use as table headers [optional]
926
	 * @return string
927
	 */
928
	public static function buildTable( $rows, $attribs = array(), $headers = null ) {
929
		$s = self::openElement( 'table', $attribs );
930
931
		if( is_array( $headers ) ) {
932
			$s .= self::openElement( 'thead', $attribs );
933
934
			foreach( $headers as $id => $header ){
935
				$attribs = array();
936
937
				if( is_string( $id ) ) {
938
					$attribs['id'] = $id;
939
				}
940
941
				$s .= self::element( 'th', $attribs, $header );
942
			}
943
			$s .= self::closeElement( 'thead' );
944
		}
945
946
		foreach( $rows as $id => $row ){
947
			$attribs = array();
948
949
			if( is_string( $id ) ) {
950
				$attribs['id'] = $id;
951
			}
952
953
			$s .= self::buildTableRow( $attribs, $row );
954
		}
955
956
		$s .= self::closeElement( 'table' );
957
958
		return $s;
959
	}
960
961
	/**
962
	 * Build a row for a table
963
	 * @param array $attribs An array of attributes to apply to the tr tag
964
	 * @param array $cells An array of strings to put in <td>
965
	 * @return string
966
	 */
967
	public static function buildTableRow( $attribs, $cells ) {
968
		$s = self::openElement( 'tr', $attribs );
969
970
		foreach( $cells as $id => $cell ){
971
			$attribs = array();
972
973
			if( is_string( $id ) ) {
974
				$attribs['id'] = $id;
975
			}
976
977
			$s .= self::element( 'td', $attribs, $cell );
978
		}
979
980
		$s .= self::closeElement( 'tr' );
981
982
		return $s;
983
	}
984
}
985
986
class XmlSelect {
987
	protected $options = array();
988
	protected $default = false;
989
	protected $attributes = array();
990
991
	public function __construct( $name = false, $id = false, $default = false ) {
992
		if( $name ) {
993
			$this->setAttribute( 'name', $name );
994
		}
995
996
		if( $id ) {
997
			$this->setAttribute( 'id', $id );
998
		}
999
1000
		if( $default !== false ) {
1001
			$this->default = $default;
1002
		}
1003
	}
1004
1005
	/**
1006
	 * @param $default
1007
	 */
1008
	public function setDefault( $default ) {
1009
		$this->default = $default;
1010
	}
1011
1012
	/**
1013
	 * @param $name string
1014
	 * @param $value
1015
	 */
1016
	public function setAttribute( $name, $value ) {
1017
		$this->attributes[$name] = $value;
1018
	}
1019
1020
	/**
1021
	 * @param $name
1022
	 * @return array|null
1023
	 */
1024
	public function getAttribute( $name ) {
1025
		if( isset( $this->attributes[$name] ) ) {
1026
			return $this->attributes[$name];
1027
		} else {
1028
			return null;
1029
		}
1030
	}
1031
1032
	/**
1033
	 * @param $name
1034
	 * @param $value bool
1035
	 */
1036
	public function addOption( $name, $value = false ) {
1037
		// Stab stab stab
1038
		$value = $value !== false ? $value : $name;
1039
1040
		$this->options[] = array( $name => $value );
1041
	}
1042
1043
	/**
1044
	 * This accepts an array of form
1045
	 * label => value
1046
	 * label => ( label => value, label => value )
1047
	 *
1048
	 * @param  $options
1049
	 */
1050
	public function addOptions( $options ) {
1051
		$this->options[] = $options;
1052
	}
1053
1054
	/**
1055
	 * This accepts an array of form
1056
	 * label => value
1057
	 * label => ( label => value, label => value )
1058
	 *
1059
	 * @param  $options
1060
	 * @param bool $default
1061
	 * @return string
1062
	 */
1063
	static function formatOptions( $options, $default = false ) {
1064
		$data = '';
1065
1066
		foreach( $options as $label => $value ){
1067
			if( is_array( $value ) ) {
1068
				$contents = self::formatOptions( $value, $default );
1069
				$data .= self::tags( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
0 ignored issues
show
The method tags() does not seem to exist on object<XmlSelect>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1070
			} else {
1071
				$data .= self::option( $label, $value, $value === $default ) . "\n";
0 ignored issues
show
The method option() does not exist on XmlSelect. Did you maybe mean addOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1072
			}
1073
		}
1074
1075
		return $data;
1076
	}
1077
1078
	/**
1079
	 * @return string
1080
	 */
1081
	public function getHTML() {
1082
		$contents = '';
1083
1084
		foreach( $this->options as $options ){
1085
			$contents .= self::formatOptions( $options, $this->default );
1086
		}
1087
1088
		return self::tags( 'select', $this->attributes, rtrim( $contents ) );
0 ignored issues
show
The method tags() does not seem to exist on object<XmlSelect>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1089
	}
1090
}