output.php ➔ elgg_get_friendly_time()   B
last analyzed

Complexity

Conditions 9
Paths 52

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
nc 52
nop 2
dl 0
loc 43
ccs 0
cts 28
cp 0
crap 90
rs 7.6764
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
	// URI specification: http://www.ietf.org/rfc/rfc3986.txt
20
	// This varies from the specification in the following ways:
21
	//  * Supports non-ascii characters
22
	//  * Does not allow parentheses and single quotes
23
	//  * Cuts off commas, exclamation points, and periods off as last character
24
25
	// @todo this causes problems with <attr = "val">
26
	// must be in <attr="val"> format (no space).
27
	// By default htmlawed rewrites tags to this format.
28
	// if PHP supported conditional negative lookbehinds we could use this:
29
	// $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i',
30
	$r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i',
31
	create_function(
32
		'$matches',
33
		'
34
			$url = $matches[1];
35
			$punc = "";
36
			$last = substr($url, -1, 1);
37
			if (in_array($last, array(".", "!", ",", "(", ")"))) {
38
				$punc = $last;
39
				$url = rtrim($url, ".!,()");
40
			}
41
			$urltext = str_replace("/", "/<wbr />", $url);
42
			return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc";
43
		'
44
	), $text);
45
46
	return $r;
47
}
48
49
/**
50
 * Create paragraphs from text with line spacing
51
 *
52
 * @param string $string The string
53
 *
54
 * @return string
55
 **/
56
function elgg_autop($string) {
57
	return _elgg_services()->autoP->process($string);
58
}
59
60
/**
61
 * Returns an excerpt.
62
 * Will return up to n chars stopping at the nearest space.
63
 * If no spaces are found (like in Japanese) will crop off at the
64
 * n char mark. Adds ... if any text was chopped.
65
 *
66
 * @param string $text      The full text to excerpt
67
 * @param int    $num_chars Return a string up to $num_chars long
68
 *
69
 * @return string
70
 * @since 1.7.2
71
 */
72
function elgg_get_excerpt($text, $num_chars = 250) {
73
	$text = trim(elgg_strip_tags($text));
74
	$string_length = elgg_strlen($text);
75
76
	if ($string_length <= $num_chars) {
77
		return $text;
78
	}
79
80
	// handle cases
81
	$excerpt = elgg_substr($text, 0, $num_chars);
82
	$space = elgg_strrpos($excerpt, ' ', 0);
83
84
	// don't crop if can't find a space.
85
	if ($space === false) {
86
		$space = $num_chars;
87
	}
88
	$excerpt = trim(elgg_substr($excerpt, 0, $space));
89
90
	if ($string_length != elgg_strlen($excerpt)) {
91
		$excerpt .= '...';
92
	}
93
94
	return $excerpt;
95
}
96
97
/**
98
 * Handles formatting of ampersands in urls
99
 *
100
 * @param string $url The URL
101
 *
102
 * @return string
103
 * @since 1.7.1
104
 */
105
function elgg_format_url($url) {
106 1
	return preg_replace('/&(?!amp;)/', '&amp;', $url);
107
}
108
109
/**
110
 * Format bytes to a human readable format
111
 *
112
 * @param int $size      File size in bytes to format
113
 *
114
 * @param int $precision Precision to round formatting bytes to
115
 *
116
 * @return string
117
 * @since 1.9.0
118
 */
119
function elgg_format_bytes($size, $precision = 2) {
120
	if (!$size || $size < 0) {
121
		return false;
122
	}
123
124
	$base = log($size) / log(1024);
125
	$suffixes = array('B', 'kB', 'MB', 'GB', 'TB');   
126
127
	return round(pow(1024, $base - floor($base)), $precision) . ' ' . $suffixes[floor($base)];
128
}
129
130
/**
131
 * Converts an associative array into a string of well-formed attributes
132
 *
133
 * @note usually for HTML, but could be useful for XML too...
134
 *
135
 * @param array $attrs An associative array of attr => val pairs
136
 *
137
 * @return string HTML attributes to be inserted into a tag (e.g., <tag $attrs>)
138
 */
139
function elgg_format_attributes(array $attrs = array()) {
140
	if (!is_array($attrs) || !count($attrs)) {
141
		return '';
142
	}
143
144
	$attrs = _elgg_clean_vars($attrs);
145
	$attributes = array();
146
147
	if (isset($attrs['js'])) {
148
		elgg_deprecated_notice('Use associative array of attr => val pairs instead of $vars[\'js\']', 1.8);
149
150
		if (!empty($attrs['js'])) {
151
			$attributes[] = $attrs['js'];
152
		}
153
154
		unset($attrs['js']);
155
	}
156
157
	foreach ($attrs as $attr => $val) {
158
		$attr = strtolower($attr);
159
160
		if ($val === true) {
161
			$val = $attr; //e.g. checked => true ==> checked="checked"
162
		}
163
164
		/**
165
		 * Ignore non-array values and allow attribute values to be an array
166
		 *  <code>
167
		 *  $attrs = array(
168
		 *		'entity' => <\ElggObject>, // will be ignored
169
		 * 		'class' => array('elgg-input', 'elgg-input-text'), // will be imploded with spaces
170
		 * 		'style' => array('margin-left:10px;', 'color: #666;'), // will be imploded with spaces
171
		 *		'alt' => 'Alt text', // will be left as is
172
		 *  );
173
		 *  </code>
174
		 */
175
		if ($val !== NULL && $val !== false && (is_array($val) || !is_object($val))) {
176
			if (is_array($val)) {
177
				$val = implode(' ', $val);
178
			}
179
180
			$val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
181
			$attributes[] = "$attr=\"$val\"";
182
		}
183
	}
184
185
	return implode(' ', $attributes);
186
}
187
188
/**
189
 * Format an HTML element
190
 *
191
 * @param string $tag_name   The element tagName. e.g. "div". This will not be validated.
192
 *
193
 * @param array  $attributes The element attributes. This is passed to elgg_format_attributes().
194
 *
195
 * @param string $text       The contents of the element. Assumed to be HTML unless encode_text is true.
196
 *
197
 * @param array  $options    Options array with keys:
198
 *
199
 *   encode_text   => (bool, default false) If true, $text will be HTML-escaped. Already-escaped entities
200
 *                    will not be double-escaped.
201
 *
202
 *   double_encode => (bool, default false) If true, the $text HTML escaping will be allowed to double
203
 *                    encode HTML entities: '&times;' will become '&amp;times;'
204
 *
205
 *   is_void       => (bool) If given, this determines whether the function will return just the open tag.
206
 *                    Otherwise this will be determined by the tag name according to this list:
207
 *                    http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements
208
 *
209
 *   is_xml        => (bool, default false) If true, void elements will be formatted like "<tag />"
210
 *
211
 * @return string
212
 * @throws InvalidArgumentException
213
 * @since 1.9.0
214
 */
215
function elgg_format_element($tag_name, array $attributes = array(), $text = '', array $options = array()) {
216
	if (!is_string($tag_name)) {
217
		throw new \InvalidArgumentException('$tag_name is required');
218
	}
219
220
	if (isset($options['is_void'])) {
221
		$is_void = $options['is_void'];
222
	} else {
223
		// from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
224
		$is_void = in_array(strtolower($tag_name), array(
225
			'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
226
			'meta', 'param', 'source', 'track', 'wbr'
227
		));
228
	}
229
230
	if (!empty($options['encode_text'])) {
231
		$double_encode = empty($options['double_encode']) ? false : true;
232
		$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8', $double_encode);
233
	}
234
235
	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...
236
		$attrs = elgg_format_attributes($attributes);
237
		if ($attrs !== '') {
238
			$attrs = " $attrs";
239
		}
240
	} else {
241
		$attrs = '';
242
	}
243
244
	if ($is_void) {
245
		return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
246
	} else {
247
		return "<{$tag_name}{$attrs}>$text</$tag_name>";
248
	}
249
}
250
251
/**
252
 * Preps an associative array for use in {@link elgg_format_attributes()}.
253
 *
254
 * Removes all the junk that {@link elgg_view()} puts into $vars.
255
 * Maintains backward compatibility with attributes like 'internalname' and 'internalid'
256
 *
257
 * @note This function is called automatically by elgg_format_attributes(). No need to
258
 *       call it yourself before using elgg_format_attributes().
259
 *
260
 * @param array $vars The raw $vars array with all it's dirtiness (config, url, etc.)
261
 *
262
 * @return array The array, ready to be used in elgg_format_attributes().
263
 * @access private
264
 */
265
function _elgg_clean_vars(array $vars = array()) {
266
	unset($vars['config']);
267
	unset($vars['url']);
268
	unset($vars['user']);
269
270
	// backwards compatibility code
271
	if (isset($vars['internalname'])) {
272
		if (!isset($vars['__ignoreInternalname'])) {
273
			$vars['name'] = $vars['internalname'];
274
		}
275
		unset($vars['internalname']);
276
	}
277
278
	if (isset($vars['internalid'])) {
279
		if (!isset($vars['__ignoreInternalid'])) {
280
			$vars['id'] = $vars['internalid'];
281
		}
282
		unset($vars['internalid']);
283
	}
284
285
	if (isset($vars['__ignoreInternalid'])) {
286
		unset($vars['__ignoreInternalid']);
287
	}
288
289
	if (isset($vars['__ignoreInternalname'])) {
290
		unset($vars['__ignoreInternalname']);
291
	}
292
293
	return $vars;
294
}
295
296
/**
297
 * Converts shorthand urls to absolute urls.
298
 *
299
 * If the url is already absolute or protocol-relative, no change is made.
300
 *
301
 * @example
302
 * elgg_normalize_url('');                   // 'http://my.site.com/'
303
 * elgg_normalize_url('dashboard');          // 'http://my.site.com/dashboard'
304
 * elgg_normalize_url('http://google.com/'); // no change
305
 * elgg_normalize_url('//google.com/');      // no change
306
 *
307
 * @param string $url The URL to normalize
308
 *
309
 * @return string The absolute url
310
 */
311
function elgg_normalize_url($url) {
312
	// see https://bugs.php.net/bug.php?id=51192
313
	// from the bookmarks save action.
314 26
	$php_5_2_13_and_below = version_compare(PHP_VERSION, '5.2.14', '<');
315 26
	$php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') &&
316 26
			version_compare(PHP_VERSION, '5.3.3', '<');
317
318 26 View Code Duplication
	if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) {
319
		$tmp_address = str_replace("-", "", $url);
320
		$validated = filter_var($tmp_address, FILTER_VALIDATE_URL);
321
	} else {
322 26
		$validated = filter_var($url, FILTER_VALIDATE_URL);
323
	}
324
325
	// work around for handling absoluate IRIs (RFC 3987) - see #4190
326 26
	if (!$validated && (strpos($url, 'http:') === 0) || (strpos($url, 'https:') === 0)) {
327
		$validated = true;
328
	}
329
330 26
	if ($validated) {
331
		// all normal URLs including mailto:
332 1
		return $url;
333
334 26
	} elseif (preg_match("#^(\#|\?|//)#i", $url)) {
335
		// '//example.com' (Shortcut for protocol.)
336
		// '?query=test', #target
337 1
		return $url;
338
	
339 25
	} elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) {
340
		// 'javascript:' and 'mailto:'
341
		// Not covered in FILTER_VALIDATE_URL
342
		return $url;
343
344 25
	} elseif (preg_match("#^[^/]*\.php(\?.*)?$#i", $url)) {
345
		// 'install.php', 'install.php?step=step'
346
		return elgg_get_site_url() . $url;
347
348 25
	} elseif (preg_match("#^[^/?]*\.#i", $url)) {
349
		// 'example.com', 'example.com/subpage'
350
		return "http://$url";
351
352
	} else {
353
		// 'page/handler', 'mod/plugin/file.php'
354
355
		// trim off any leading / because the site URL is stored
356
		// with a trailing /
357 25
		return elgg_get_site_url() . ltrim($url, '/');
358
	}
359
}
360
361
/**
362
 * When given a title, returns a version suitable for inclusion in a URL
363
 *
364
 * @param string $title The title
365
 *
366
 * @return string The optimized title
367
 * @since 1.7.2
368
 */
369
function elgg_get_friendly_title($title) {
370
371
	// return a URL friendly title to short circuit normal title formatting
372
	$params = array('title' => $title);
373
	$result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, null);
374
	if ($result) {
375
		return $result;
376
	}
377
378
	// titles are often stored HTML encoded
379
	$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
380
	
381
	$title = \Elgg\Translit::urlize($title);
382
383
	return $title;
384
}
385
386
/**
387
 * Formats a UNIX timestamp in a friendly way (eg "less than a minute ago")
388
 *
389
 * @see elgg_view_friendly_time()
390
 *
391
 * @param int $time         A UNIX epoch timestamp
392
 * @param int $current_time Current UNIX epoch timestamp (optional)
393
 *
394
 * @return string The friendly time string
395
 * @since 1.7.2
396
 */
397
function elgg_get_friendly_time($time, $current_time = null) {
398
	
399
	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 zero. 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...
400
		$current_time = time();
401
	}
402
403
	// return a time string to short circuit normal time formatting
404
	$params = array('time' => $time, 'current_time' => $current_time);
405
	$result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, null);
406
	if ($result) {
407
		return $result;
408
	}
409
410
	$diff = abs((int)$current_time - (int)$time);
411
412
	$minute = 60;
413
	$hour = $minute * 60;
414
	$day = $hour * 24;
415
416
	if ($diff < $minute) {
417
		return elgg_echo("friendlytime:justnow");
418
	}
419
	
420
	if ($diff < $hour) {
421
		$granularity = ':minutes';
422
		$diff = round($diff / $minute);
423
	} else if ($diff < $day) {
424
		$granularity = ':hours';
425
		$diff = round($diff / $hour);
426
	} else {
427
		$granularity = ':days';
428
		$diff = round($diff / $day);
429
	}
430
431
	if ($diff == 0) {
432
		$diff = 1;
433
	}
434
	
435
	$future = ((int)$current_time - (int)$time < 0) ? ':future' : '';
436
	$singular = ($diff == 1) ? ':singular' : '';
437
438
	return elgg_echo("friendlytime{$future}{$granularity}{$singular}", array($diff));
439
}
440
441
/**
442
 * Returns a human-readable message for PHP's upload error codes
443
 *
444
 * @param int $error_code The code as stored in $_FILES['name']['error']
445
 * @return string
446
 */
447
function elgg_get_friendly_upload_error($error_code) {
448
	switch ($error_code) {
449
		case UPLOAD_ERR_OK:
450
			return '';
451
			
452
		case UPLOAD_ERR_INI_SIZE:
453
			$key = 'ini_size';
454
			break;
455
		
456
		case UPLOAD_ERR_FORM_SIZE:
457
			$key = 'form_size';
458
			break;
459
460
		case UPLOAD_ERR_PARTIAL:
461
			$key = 'partial';
462
			break;
463
464
		case UPLOAD_ERR_NO_FILE:
465
			$key = 'no_file';
466
			break;
467
468
		case UPLOAD_ERR_NO_TMP_DIR:
469
			$key = 'no_tmp_dir';
470
			break;
471
472
		case UPLOAD_ERR_CANT_WRITE:
473
			$key = 'cant_write';
474
			break;
475
476
		case UPLOAD_ERR_EXTENSION:
477
			$key = 'extension';
478
			break;
479
		
480
		default:
481
			$key = 'unknown';
482
			break;
483
	}
484
485
	return elgg_echo("upload:error:$key");
486
}
487
488
489
/**
490
 * Strip tags and offer plugins the chance.
491
 * Plugins register for output:strip_tags plugin hook.
492
 * Original string included in $params['original_string']
493
 *
494
 * @param string $string         Formatted string
495
 * @param string $allowable_tags Optional parameter to specify tags which should not be stripped
496
 *
497
 * @return string String run through strip_tags() and any plugin hooks.
498
 */
499
function elgg_strip_tags($string, $allowable_tags = null) {
500
	$params['original_string'] = $string;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
501
	$params['allowable_tags'] = $allowable_tags;
502
503
	$string = strip_tags($string, $allowable_tags);
504
	$string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string);
505
506
	return $string;
507
}
508
509
/**
510
 * Apply html_entity_decode() to a string while re-entitising HTML
511
 * special char entities to prevent them from being decoded back to their
512
 * unsafe original forms.
513
 *
514
 * This relies on html_entity_decode() not translating entities when
515
 * doing so leaves behind another entity, e.g. &amp;gt; if decoded would
516
 * create &gt; which is another entity itself. This seems to escape the
517
 * usual behaviour where any two paired entities creating a HTML tag are
518
 * usually decoded, i.e. a lone &gt; is not decoded, but &lt;foo&gt; would
519
 * be decoded to <foo> since it creates a full tag.
520
 *
521
 * Note: This function is poorly explained in the manual - which is really
522
 * bad given its potential for misuse on user input already escaped elsewhere.
523
 * Stackoverflow is littered with advice to use this function in the precise
524
 * way that would lead to user input being capable of injecting arbitrary HTML.
525
 *
526
 * @param string $string
527
 *
528
 * @return string
529
 *
530
 * @author Pádraic Brady
531
 * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
532
 * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady
533
 *
534
 * @access private
535
 */
536
function _elgg_html_decode($string) {
537
	$string = str_replace(
538
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
539
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
540
		$string
541
	);
542
	$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
543
	$string = str_replace(
544
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
545
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
546
		$string
547
	);
548
	return $string;
549
}
550
551
/**
552
 * Prepares query string for output to prevent CSRF attacks.
553
 * 
554
 * @param string $string
555
 * @return string
556
 *
557
 * @access private
558
 */
559
function _elgg_get_display_query($string) {
560
	//encode <,>,&, quotes and characters above 127
561 View Code Duplication
	if (function_exists('mb_convert_encoding')) {
562
		$display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
563
	} else {
564
		// if no mbstring extension, we just strip characters
565
		$display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
566
	}
567
	return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false);
568
}
569
570
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
571
572
};
573