Completed
Push — 2.3 ( e6f9d2...a69256 )
by Jeroen
09:57 queued 11s
created

elgg_normalize_site_url()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
ccs 0
cts 5
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Output functions
4
 * Processing text for output such as pulling out URLs and extracting excerpts
5
 *
6
 * @package Elgg
7
 * @subpackage Core
8
 */
9
10
/**
11
 * Takes a string and turns any URLs into formatted links
12
 *
13
 * @param string $text The input string
14
 *
15
 * @return string The output string with formatted links
16
 */
17
function parse_urls($text) {
18
19
	$linkify = new \Misd\Linkify\Linkify();
20
		
21
	return $linkify->processUrls($text, ['attr' => ['rel' => 'nofollow']]);
22
}
23
24
/**
25
 * Takes a string and turns any email addresses into formatted links
26
 *
27
 * @param string $text The input string
28
 *
29
 * @return string The output string with formatted links
30
 *
31
 * @since 2.3
32
 */
33
function elgg_parse_emails($text) {
34
	$linkify = new \Misd\Linkify\Linkify();
35
		
36
	return $linkify->processEmails($text, ['attr' => ['rel' => 'nofollow']]);
37
}
38
39
/**
40
 * Create paragraphs from text with line spacing
41
 *
42
 * @param string $string The string
43
 *
44
 * @return string
45
 **/
46
function elgg_autop($string) {
47
	return _elgg_services()->autoP->process($string);
0 ignored issues
show
Bug Best Practice introduced by
The expression return _elgg_services()->autoP->process($string) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
48
}
49
50
/**
51
 * Returns an excerpt.
52
 * Will return up to n chars stopping at the nearest space.
53
 * If no spaces are found (like in Japanese) will crop off at the
54
 * n char mark. Adds ... if any text was chopped.
55
 *
56
 * @param string $text      The full text to excerpt
57
 * @param int    $num_chars Return a string up to $num_chars long
58
 *
59
 * @return string
60
 * @since 1.7.2
61
 */
62
function elgg_get_excerpt($text, $num_chars = 250) {
63
	$view = 'output/excerpt';
64
	$vars = [
65
		'text' => $text,
66
		'num_chars' => $num_chars,
67
	];
68
	$viewtype = elgg_view_exists($view) ? '' : 'default';
69
70
	return _elgg_view_under_viewtype($view, $vars, $viewtype);
71
}
72
73
/**
74
 * Handles formatting of ampersands in urls
75
 *
76
 * @param string $url The URL
77
 *
78
 * @return string
79
 * @since 1.7.1
80
 */
81
function elgg_format_url($url) {
82 1
	return preg_replace('/&(?!amp;)/', '&amp;', $url);
83
}
84
85
/**
86
 * Format bytes to a human readable format
87
 *
88
 * @param int $size      File size in bytes to format
89
 *
90
 * @param int $precision Precision to round formatting bytes to
91
 *
92
 * @return string
93
 * @since 1.9.0
94
 */
95
function elgg_format_bytes($size, $precision = 2) {
96
	if (!$size || $size < 0) {
97
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
98
	}
99
100
	$base = log($size) / log(1024);
101
	$suffixes = array('B', 'kB', 'MB', 'GB', 'TB');
102
103
	return round(pow(1024, $base - floor($base)), $precision) . ' ' . $suffixes[floor($base)];
104
}
105
106
/**
107
 * Converts an associative array into a string of well-formed HTML/XML attributes
108
 * Returns a concatenated string of HTML attributes to be inserted into a tag (e.g., <tag $attrs>)
109
 *
110
 * @see elgg_format_element
111
 *
112
 * @param array $attrs Attributes
113
 *                     An array of attribute => value pairs
114
 *                     Attribute value can be a scalar value, an array of scalar values, or true
115
 *                     <code>
116
 *                     $attrs = array(
117
 *                         'class' => ['elgg-input', 'elgg-input-text'], // will be imploded with spaces
118
 *                         'style' => ['margin-left:10px;', 'color: #666;'], // will be imploded with spaces
119
 *                         'alt' => 'Alt text', // will be left as is
120
 *                         'disabled' => true, // will be converted to disabled="disabled"
121
 *                         'data-options' => json_encode(['foo' => 'bar']), // will be output as an escaped JSON string
122
 *                         'batch' => <\ElggBatch>, // will be ignored
123
 *                         'items' => [<\ElggObject>], // will be ignored
124
 *                     );
125
 *                     </code>
126
 *
127
 * @return string
128
 */
129
function elgg_format_attributes(array $attrs = array()) {
130 7
	if (!is_array($attrs) || empty($attrs)) {
0 ignored issues
show
introduced by
The condition is_array($attrs) is always true.
Loading history...
131
		return '';
132
	}
133
134 7
	$attributes = [];
135
136 7
	foreach ($attrs as $attr => $val) {
137 7
		if (0 !== strpos($attr, 'data-') && false !== strpos($attr, '_')) {
138
			// this is probably a view $vars variable not meant for output
139 1
			continue;
140
		}
141
142 7
		$attr = strtolower($attr);
143
144 7
		if (!isset($val) || $val === false) {
145 1
			continue;
146
		}
147
148 7
		if ($val === true) {
149 2
			$val = $attr; //e.g. checked => true ==> checked="checked"
150
		}
151
152 7
		if (is_scalar($val)) {
153 7
			$val = [$val];
154
		}
155
156 7
		if (!is_array($val)) {
157 1
			continue;
158
		}
159
160
		// Check if array contains non-scalar values and bail if so
161
		$filtered_val = array_filter($val, function($e) {
162 7
			return is_scalar($e);
163 7
		});
164
165 7
		if (count($val) != count($filtered_val)) {
166 1
			continue;
167
		}
168
169 7
		$val = implode(' ', $val);
170
171 7
		$val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
172 7
		$attributes[] = "$attr=\"$val\"";
173
	}
174
175 7
	return implode(' ', $attributes);
176
}
177
178
/**
179
 * Format an HTML element
180
 *
181
 * @param string|array $tag_name   The element tagName. e.g. "div". This will not be validated.
182
 *                                 All function arguments can be given as a single array: The array will be used
183
 *                                 as $attributes, except for the keys "#tag_name", "#text", and "#options", which
184
 *                                 will be extracted as the other arguments.
185
 *
186
 * @param array        $attributes The element attributes. This is passed to elgg_format_attributes().
187
 *
188
 * @param string       $text       The contents of the element. Assumed to be HTML unless encode_text is true.
189
 *
190
 * @param array        $options    Options array with keys:
191
 *
192
 *   encode_text   => (bool, default false) If true, $text will be HTML-escaped. Already-escaped entities
193
 *                    will not be double-escaped.
194
 *
195
 *   double_encode => (bool, default false) If true, the $text HTML escaping will be allowed to double
196
 *                    encode HTML entities: '&times;' will become '&amp;times;'
197
 *
198
 *   is_void       => (bool) If given, this determines whether the function will return just the open tag.
199
 *                    Otherwise this will be determined by the tag name according to this list:
200
 *                    http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements
201
 *
202
 *   is_xml        => (bool, default false) If true, void elements will be formatted like "<tag />"
203
 *
204
 * @return string
205
 * @throws InvalidArgumentException
206
 * @since 1.9.0
207
 */
208
function elgg_format_element($tag_name, array $attributes = array(), $text = '', array $options = array()) {
209 9
	if (is_array($tag_name)) {
210 6
		$args = $tag_name;
211
212 6
		if ($attributes !== [] || $text !== '' || $options !== []) {
213
			throw new \InvalidArgumentException('If $tag_name is an array, the other arguments must not be set');
214
		}
215
216 6
		if (isset($args['#tag_name'])) {
217 5
			$tag_name = $args['#tag_name'];
218
		}
219 6
		if (isset($args['#text'])) {
220 4
			$text = $args['#text'];
221
		}
222 6
		if (isset($args['#options'])) {
223 5
			$options = $args['#options'];
224
		}
225
226 6
		unset($args['#tag_name'], $args['#text'], $args['#options']);
227 6
		$attributes = $args;
228
	}
229
230 9
	if (!is_string($tag_name) || $tag_name === '') {
231 1
		throw new \InvalidArgumentException('$tag_name is required');
232
	}
233
234 8
	if (isset($options['is_void'])) {
235 1
		$is_void = $options['is_void'];
236
	} else {
237
		// from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
238 7
		$is_void = in_array(strtolower($tag_name), array(
239 7
			'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
240
			'meta', 'param', 'source', 'track', 'wbr'
241
		));
242
	}
243
244 8
	if (!empty($options['encode_text'])) {
245 2
		$double_encode = empty($options['double_encode']) ? false : true;
246 2
		$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8', $double_encode);
247
	}
248
249 8
	if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes 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...
250 4
		$attrs = elgg_format_attributes($attributes);
251 4
		if ($attrs !== '') {
252 4
			$attrs = " $attrs";
253
		}
254
	} else {
255 5
		$attrs = '';
256
	}
257
258 8
	if ($is_void) {
259 2
		return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
260
	} else {
261 6
		return "<{$tag_name}{$attrs}>$text</$tag_name>";
262
	}
263
}
264
265
/**
266
 * Converts shorthand urls to absolute urls.
267
 *
268
 * No change is made if the URL: is absolute, protocol-relative, starts with a protocol/fragment/query.
269
 *
270
 * @example
271
 * elgg_normalize_url('');                   // 'http://my.site.com/'
272
 * elgg_normalize_url('dashboard');          // 'http://my.site.com/dashboard'
273
 * elgg_normalize_url('http://google.com/'); // no change
274
 * elgg_normalize_url('//google.com/');      // no change
275
 *
276
 * @param string $url The URL to normalize
277
 *
278
 * @return string The absolute url
279
 */
280
function elgg_normalize_url($url) {
281 217
	$url = str_replace(' ', '%20', $url);
282
283 217
	if (_elgg_sane_validate_url($url)) {
284 57
		return $url;
285
	}
286
287 211
	if (preg_match("#^([a-z]+)\\:#", $url, $m)) {
288
		// we don't let http/https: URLs fail filter_var(), but anything else starting with a protocol
289
		// is OK
290 1
		if ($m[1] !== 'http' && $m[1] !== 'https') {
291 1
			return $url;
292
		}
293
	}
294
295 210
	if (preg_match("#^(\\#|\\?|//)#", $url)) {
296
		// starts with '//' (protocol-relative link), query, or fragment
297 2
		return $url;
298
	}
299
300 208
	if (preg_match("#^[^/]*\\.php(\\?.*)?$#", $url)) {
301
		// root PHP scripts: 'install.php', 'install.php?step=step'. We don't want to confuse these
302
		// for domain names.
303 2
		return elgg_get_site_url() . $url;
304
	}
305
306 206
	if (preg_match("#^[^/?]*\\.#", $url)) {
307
		// URLs starting with domain: 'example.com', 'example.com/subpage'
308 2
		return "http://$url";
309
	}
310
311
	// 'page/handler', 'mod/plugin/file.php'
312
	// trim off any leading / because the site URL is stored
313
	// with a trailing /
314 204
	return elgg_get_site_url() . ltrim($url, '/');
315
}
316
317
/**
318
 * From untrusted input, get a site URL safe for forwarding.
319
 *
320
 * @param string $unsafe_url URL from untrusted input
321
 *
322
 * @return bool|string Normalized URL or false if given URL was not a path.
323
 *
324
 * @since 1.12.18
325
 */
326
function elgg_normalize_site_url($unsafe_url) {
327
	if (!is_string($unsafe_url)) {
1 ignored issue
show
introduced by
The condition is_string($unsafe_url) is always true.
Loading history...
328
		return false;
329
	}
330
331
	$unsafe_url = elgg_normalize_url($unsafe_url);
332
	if (0 === strpos($unsafe_url, elgg_get_site_url())) {
333
		return $unsafe_url;
334
	}
335
336
	return false;
337
}
338
339
/**
340
 * When given a title, returns a version suitable for inclusion in a URL
341
 *
342
 * @param string $title The title
343
 *
344
 * @return string The optimized title
345
 * @since 1.7.2
346
 */
347
function elgg_get_friendly_title($title) {
348
349
	// return a URL friendly title to short circuit normal title formatting
350
	$params = array('title' => $title);
351
	$result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, null);
352
	if ($result) {
353
		return $result;
354
	}
355 7
356
	// titles are often stored HTML encoded
357
	$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
358
	
359
	$title = \Elgg\Translit::urlize($title);
360 7
361 7
	return $title;
362 7
}
363
364
/**
365
 * Formats a UNIX timestamp in a friendly way (eg "less than a minute ago")
366 7
 *
367
 * @see elgg_view_friendly_time()
368 7
 *
369 7
 * @param int $time         A UNIX epoch timestamp
370 7
 * @param int $current_time Current UNIX epoch timestamp (optional)
371
 *
372 7
 * @return string The friendly time string
373 1
 * @since 1.7.2
374
 */
375
function elgg_get_friendly_time($time, $current_time = null) {
376 6
	
377 3
	if (!$current_time) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $current_time of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
378 3
		$current_time = time();
379 3
	}
380 1
381 1
	// return a time string to short circuit normal time formatting
382
	$params = array('time' => $time, 'current_time' => $current_time);
383 2
	$result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, null);
384 2
	if ($result) {
385
		return $result;
386
	}
387 6
388
	$diff = abs((int)$current_time - (int)$time);
389
390
	$minute = 60;
391 6
	$hour = $minute * 60;
392 6
	$day = $hour * 24;
393
394 6
	if ($diff < $minute) {
395
		return elgg_echo("friendlytime:justnow");
396
	}
397
	
398
	if ($diff < $hour) {
399
		$granularity = ':minutes';
400
		$diff = round($diff / $minute);
401
	} else if ($diff < $day) {
402
		$granularity = ':hours';
403
		$diff = round($diff / $hour);
404
	} else {
405
		$granularity = ':days';
406
		$diff = round($diff / $day);
407
	}
408
409
	if ($diff == 0) {
410
		$diff = 1;
411
	}
412
	
413
	$future = ((int)$current_time - (int)$time < 0) ? ':future' : '';
414
	$singular = ($diff == 1) ? ':singular' : '';
415
416
	return elgg_echo("friendlytime{$future}{$granularity}{$singular}", array($diff));
417
}
418
419
/**
420
 * Returns a human-readable message for PHP's upload error codes
421
 *
422
 * @param int $error_code The code as stored in $_FILES['name']['error']
423
 * @return string
424
 */
425
function elgg_get_friendly_upload_error($error_code) {
426
	switch ($error_code) {
427
		case UPLOAD_ERR_OK:
428
			return '';
429
			
430
		case UPLOAD_ERR_INI_SIZE:
431
			$key = 'ini_size';
432
			break;
433
		
434
		case UPLOAD_ERR_FORM_SIZE:
435
			$key = 'form_size';
436
			break;
437
438
		case UPLOAD_ERR_PARTIAL:
439
			$key = 'partial';
440
			break;
441
442
		case UPLOAD_ERR_NO_FILE:
443
			$key = 'no_file';
444
			break;
445
446
		case UPLOAD_ERR_NO_TMP_DIR:
447
			$key = 'no_tmp_dir';
448
			break;
449
450
		case UPLOAD_ERR_CANT_WRITE:
451
			$key = 'cant_write';
452
			break;
453
454
		case UPLOAD_ERR_EXTENSION:
455
			$key = 'extension';
456 2
			break;
457 2
		
458
		default:
459 2
			$key = 'unknown';
460 2
			break;
461
	}
462 2
463
	return elgg_echo("upload:error:$key");
464
}
465
466
467
/**
468
 * Strip tags and offer plugins the chance.
469
 * Plugins register for output:strip_tags plugin hook.
470
 * Original string included in $params['original_string']
471
 *
472
 * @param string $string         Formatted string
473
 * @param string $allowable_tags Optional parameter to specify tags which should not be stripped
474
 *
475
 * @return string String run through strip_tags() and any plugin hooks.
476
 */
477
function elgg_strip_tags($string, $allowable_tags = null) {
478
	$params['original_string'] = $string;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
479
	$params['allowable_tags'] = $allowable_tags;
480
481
	$string = strip_tags($string, $allowable_tags);
482
	$string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string);
483
484
	return $string;
485
}
486
487
/**
488
 * Decode HTML markup into a raw text string
489
 *
490
 * This applies html_entity_decode() to a string while re-entitising HTML
491
 * special char entities to prevent them from being decoded back to their
492
 * unsafe original forms.
493
 *
494
 * This relies on html_entity_decode() not translating entities when
495
 * doing so leaves behind another entity, e.g. &amp;gt; if decoded would
496
 * create &gt; which is another entity itself. This seems to escape the
497
 * usual behaviour where any two paired entities creating a HTML tag are
498
 * usually decoded, i.e. a lone &gt; is not decoded, but &lt;foo&gt; would
499
 * be decoded to <foo> since it creates a full tag.
500
 *
501
 * Note: html_entity_decode() is poorly explained in the manual - which is really
502
 * bad given its potential for misuse on user input already escaped elsewhere.
503
 * Stackoverflow is littered with advice to use this function in the precise
504
 * way that would lead to user input being capable of injecting arbitrary HTML.
505
 *
506
 * @param string $string Encoded HTML
507
 *
508
 * @return string
509
 *
510
 * @author Pádraic Brady
511
 * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
512
 * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady
513
 */
514
function elgg_html_decode($string) {
515
	$string = str_replace(
516
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
517
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
518
		$string
519
	);
520
	$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
521
	$string = str_replace(
522
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
523
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
524
		$string
525
	);
526
	return $string;
527
}
528
529
/**
530
 * Alias of elgg_html_decode
531
 *
532
 * This is kept in 2.0 because it was used in public views and might have been copied into plugins.
533
 *
534
 * @param string $string Encoded HTML
535
 *
536
 * @return string
537
 * @see elgg_html_decode
538
 * @deprecated
539
 */
540
function _elgg_html_decode($string) {
541
	elgg_deprecated_notice(__FUNCTION__ . ' is deprecated. Use elgg_html_decode()', '2.0');
542
	return elgg_html_decode($string);
543
}
544
545
/**
546
 * Prepares query string for output to prevent CSRF attacks.
547
 *
548
 * @param string $string
549
 * @return string
550
 *
551 217
 * @access private
552 217
 */
553 57
function _elgg_get_display_query($string) {
554
	//encode <,>,&, quotes and characters above 127
555
	if (function_exists('mb_convert_encoding')) {
556
		$display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
557 211
	} else {
558 211
		// if no mbstring extension, we just strip characters
559 211
		$display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
560
	}
561
	return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false);
562
}
563 1
564 1
/**
565 1
 * Use a "fixed" filter_var() with FILTER_VALIDATE_URL that handles multi-byte chars.
566 1
 *
567
 * @param string $url URL to validate
568
 * @return string|false
569
 * @access private
570 1
 */
571
function _elgg_sane_validate_url($url) {
572
	// based on http://php.net/manual/en/function.filter-var.php#104160
573
	$res = filter_var($url, FILTER_VALIDATE_URL);
574
	if ($res) {
575
		return $res;
576
	}
577
578
	// Check if it has unicode chars.
579
	$l = elgg_strlen($url);
580
	if (strlen($url) == $l) {
581
		return $res;
582
	}
583
584
	// Replace wide chars by “X”.
585
	$s = '';
586
	for ($i = 0; $i < $l; ++$i) {
587
		$ch = elgg_substr($url, $i, 1);
588
		$s .= (strlen($ch) > 1) ? 'X' : $ch;
589
	}
590
591
	// Re-check now.
592
	return filter_var($s, FILTER_VALIDATE_URL) ? $url : false;
593
}
594
595
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
0 ignored issues
show
Unused Code introduced by
The parameter $hooks is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

595
return function(\Elgg\EventsService $events, /** @scrutinizer ignore-unused */ \Elgg\HooksRegistrationService $hooks) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $events is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

595
return function(/** @scrutinizer ignore-unused */ \Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
596
597
};
598