Issues (2756)

includes/functions.php (55 issues)

1
<?php
2
/*
3
 * YOURLS general functions
4
 *
5
 */
6
7
/**
8
 * Make an optimized regexp pattern from a string of characters
9
 * @param $string
10
 * @return string
11
 */
12
function yourls_make_regexp_pattern( $string ) {
13
    // Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc...
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
14
    // add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php
15 7
    return preg_quote( $string, '@' );
16
}
17
18
/**
19
 * Function: Get client IP Address. Returns a DB safe string.
20
 * @return string
21
 */
22
function yourls_get_IP() {
23 3
	$ip = '';
24
25
	// Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
26 3
	$headers = [ 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ];
0 ignored issues
show
Arrays with multiple values should not be declared on a single line.
Loading history...
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
27 3
	foreach( $headers as $header ) {
28 3
		if ( !empty( $_SERVER[ $header ] ) ) {
29 3
			$ip = $_SERVER[ $header ];
30 3
			break;
31
		}
32
	}
33
34
	// headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
35 3
	if ( strpos( $ip, ',' ) !== false )
36
		$ip = substr( $ip, 0, strpos( $ip, ',' ) );
37
38 3
	return (string)yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
39
}
40
41
/**
42
 * Get next id a new link will have if no custom keyword provided
43
 *
44
 * @since 1.0
45
 * @return int            id of next link
46
 */
47
function yourls_get_next_decimal() {
48 3
	return (int)yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
49
}
50
51
/**
52
 * Update id for next link with no custom keyword
53
 *
54
 * Note: this function relies upon yourls_update_option(), which will return either true or false
55
 * depending if there has been an actual MySQL query updating the DB.
56
 * In other words, this function may return false yet this would not mean it has functionnaly failed
57
 * In other words I'm not sure we really need this function to return something :face_with_eyes_looking_up:
58
 * See issue 2621 for more on this.
59
 *
60
 * @since 1.0
61
 * @param integer $int     id for next link
62
 * @return bool            true or false depending on if there has been an actual MySQL query. See note above.
63
 */
64
function yourls_update_next_decimal( $int = '' ) {
65 2
	$int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
66 2
	$update = yourls_update_option( 'next_id', $int );
67 2
	yourls_do_action( 'update_next_decimal', $int, $update );
68 2
	return $update;
69
}
70
71
/**
72
 * Return XML output.
73
 * @param array $array
74
 * @return string
75
 */
76
function yourls_xml_encode( $array ) {
77 1
    return (\Spatie\ArrayToXml\ArrayToXml::convert($array));
78
}
79
80
/**
81
 * Update click count on a short URL. Return 0/1 for error/success.
82
 * @param string $keyword
83
 * @param bool   $clicks
84
 * @return mixed|string
85
 */
86
function yourls_update_clicks( $keyword, $clicks = false ) {
87
	// Allow plugins to short-circuit the whole function
88 2
	$pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
89 2
	if ( false !== $pre )
90
		return $pre;
91
92 2
	$keyword = yourls_sanitize_keyword( $keyword );
93 2
	$table = YOURLS_DB_TABLE_URL;
94 2
	if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
95
		$update = yourls_get_db()->fetchAffected( "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword", [ 'clicks' => $clicks, 'keyword' => $keyword ] );
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $table instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
This line exceeds maximum limit of 100 characters; contains 162 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
96
	else
97 2
		$update = yourls_get_db()->fetchAffected( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = :keyword", [ 'keyword' => $keyword ] );
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $table instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
This line exceeds maximum limit of 100 characters; contains 144 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
98
99 2
	yourls_do_action( 'update_clicks', $keyword, $update, $clicks );
100 2
	return $update;
101
}
102
103
/**
104
 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
105
 *
106
 */
107
function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) {
108 1
	switch( $filter ) {
109 1
		case 'bottom':
110
			$sort_by    = '`clicks`';
111
			$sort_order = 'asc';
112
			break;
113 1
		case 'last':
114
			$sort_by    = '`timestamp`';
115
			$sort_order = 'desc';
116
			break;
117 1
		case 'rand':
118 1
		case 'random':
119
			$sort_by    = 'RAND()';
120
			$sort_order = '';
121
			break;
122 1
		case 'top':
123
		default:
124 1
			$sort_by    = '`clicks`';
125 1
			$sort_order = 'desc';
126 1
			break;
127
	}
128
129
	// Fetch links
130 1
	$limit = intval( $limit );
131 1
	$start = intval( $start );
132 1
	if ( $limit > 0 ) {
133
134
		$table_url = YOURLS_DB_TABLE_URL;
135
		$results = yourls_get_db()->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY $sort_by $sort_order LIMIT $start, $limit;" );
136
137
		$return = [];
138
		$i = 1;
139
140
		foreach ( (array)$results as $res ) {
141
			$return['links']['link_'.$i++] = [
0 ignored issues
show
Increment and decrement operators must be bracketed when used in string concatenation
Loading history...
142
				'shorturl' => yourls_link($res->keyword),
143
				'url'      => $res->url,
144
				'title'    => $res->title,
145
				'timestamp'=> $res->timestamp,
146
				'ip'       => $res->ip,
147
				'clicks'   => $res->clicks,
148
            ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 36 space(s), but found 12.
Loading history...
149
		}
150
	}
151
152 1
	$return['stats'] = yourls_get_db_stats();
153
154 1
	$return['statusCode'] = 200;
155
156 1
	return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
157
}
158
159
/**
160
 * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
161
 *
162
 * The $where parameter will contain additional SQL arguments:
163
 *   $where['sql'] will concatenate SQL clauses: $where['sql'] = ' AND something = :value AND otherthing < :othervalue';
164
 *   $where['binds'] will hold the (name => value) placeholder pairs: $where['binds'] = array('value' => $value, 'othervalue' => $value2)
165
 *
166
 * @param  $where array  See comment above
167
 * @return array
168
 */
169
function yourls_get_db_stats( $where = [ 'sql' => '', 'binds' => [] ] ) {
170 2
	$table_url = YOURLS_DB_TABLE_URL;
171
172 2
	$totals = yourls_get_db()->fetchObject( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 " . $where['sql'] , $where['binds'] );
173 2
	$return = [ 'total_links' => $totals->count, 'total_clicks' => $totals->sum ];
0 ignored issues
show
Arrays with multiple values should not be declared on a single line.
Loading history...
174
175 2
	return yourls_apply_filter( 'get_db_stats', $return, $where );
176
}
177
178
/**
179
 * Get number of SQL queries performed
180
 *
181
 */
182
function yourls_get_num_queries() {
183 2
	return yourls_apply_filter( 'get_num_queries', yourls_get_db()->get_num_queries() );
184
}
185
186
/**
187
 * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
188
 *
189
 */
190
function yourls_get_user_agent() {
191 2
    $ua = '-';
192
193 2
    if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
194 2
        $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
195 2
        $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
196
    }
197
198 2
    return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 255 ) );
199
}
200
201
/**
202
 * Returns the sanitized referrer submitted by the browser.
203
 *
204
 * @return string               HTTP Referrer or 'direct' if no referrer was provided
205
 */
206
function yourls_get_referrer() {
207
    $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url_safe( $_SERVER['HTTP_REFERER'] ) : 'direct';
208
209
    return yourls_apply_filter( 'get_referrer', substr( $referrer, 0, 200 ) );
210
}
211
212
/**
213
 * Redirect to another page
214
 *
215
 * YOURLS redirection, either to internal or external URLs. If headers have not been sent, redirection
216
 * is achieved with PHP's header(). If headers have been sent already and we're not in a command line
217
 * client, redirection occurs with Javascript.
218
 *
219
 * @since 1.4
220
 * @param string $location      URL to redirect to
221
 * @param int    $code          HTTP status code to send
222
 * @return int                  1 for header redirection, 2 for js redirection, 3 otherwise
223
 */
224
function yourls_redirect( $location, $code = 301 ) {
225
	yourls_do_action( 'pre_redirect', $location, $code );
226
	$location = yourls_apply_filter( 'redirect_location', $location, $code );
227
	$code     = yourls_apply_filter( 'redirect_code', $code, $location );
228
	// Redirect, either properly if possible, or via Javascript otherwise
229
	if( !headers_sent() ) {
230
		yourls_status_header( $code );
231
		header( "Location: $location" );
232
        return 1;
233
	}
234
235
	if( php_sapi_name() !== 'cli') {
236
        yourls_redirect_javascript( $location );
237
        return 2;
238
	}
239
240
	return 3;
241
}
242
243
/**
244
 * Redirect to an existing short URL
245
 *
246
 * Redirect client to an existing short URL (no check performed) and execute misc tasks: update
247
 * clicks for short URL, update logs, and send a nocache header to prevent bots indexing short
248
 * URLS (see #2202)
249
 *
250
 * @since  1.7.3
251
 * @param  string $url
252
 * @param  string $keyword
253
 */
254
function yourls_redirect_shorturl($url, $keyword) {
255
    yourls_do_action( 'redirect_shorturl', $url, $keyword );
256
257
    // Update click count in main table
258
    yourls_update_clicks( $keyword );
259
260
    // Update detailed log for stats
261
    yourls_log_redirect( $keyword );
262
263
    // Tell (Google)bots not to index this short URL, see #2202
264
    if ( !headers_sent() ) {
265
        header( "X-Robots-Tag: noindex", true );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal X-Robots-Tag: noindex does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
266
    }
267
268
    yourls_redirect( $url, 301 );
269
}
270
271
/**
272
 * Send headers to explicitely tell browser not to cache content or redirection
273
 *
274
 * @since 1.7.10
275
 * @return void
276
 */
277
function yourls_no_cache_headers() {
278
    if( !headers_sent() ) {
279
        header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' );
280
        header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
281
        header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
282
        header( 'Pragma: no-cache' );
283
    }
284
}
285
286
/**
287
 * Send a filerable content type header
288
 *
289
 * @since 1.7
290
 * @param string $type content type ('text/html', 'application/json', ...)
291
 * @return bool whether header was sent
292
 */
293
function yourls_content_type_header( $type ) {
294 8
    yourls_do_action( 'content_type_header', $type );
295 8
	if( !headers_sent() ) {
296
		$charset = yourls_apply_filter( 'content_type_header_charset', 'utf-8' );
297
		header( "Content-Type: $type; charset=$charset" );
298
		return true;
299
	}
300 8
	return false;
301
}
302
303
/**
304
 * Set HTTP status header
305
 *
306
 * @since 1.4
307
 * @param int $code  status header code
308
 * @return bool      whether header was sent
309
 */
310
function yourls_status_header( $code = 200 ) {
311 8
	yourls_do_action( 'status_header', $code );
312
313 8
	if( headers_sent() )
314 8
		return false;
315
316
	$protocol = $_SERVER['SERVER_PROTOCOL'];
317
	if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
318
		$protocol = 'HTTP/1.0';
319
320
	$code = intval( $code );
321
	$desc = yourls_get_HTTP_status( $code );
322
323
	@header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
324
325
    return true;
326
}
327
328
/**
329
 * Redirect to another page using Javascript.
330
 * Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user)
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 112 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
331
 *
332
 * @param string $location
333
 * @param bool   $dontwait
334
 */
335
function yourls_redirect_javascript( $location, $dontwait = true ) {
336 1
    yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
337 1
    $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
338 1
    if ( $dontwait ) {
339 1
        $message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
340
        echo <<<REDIR
341 1
		<script type="text/javascript">
342 1
		window.location="$location";
343
		</script>
344 1
		<small>($message)</small>
345
REDIR;
346
    }
347
    else {
348
        echo '<p>'.yourls_s( 'Please <a href="%s">click here</a>', $location ).'</p>';
349
    }
350 1
    yourls_do_action( 'post_redirect_javascript', $location );
351 1
}
352
353
/**
354
 * Return an HTTP status code
355
 * @param int $code
356
 * @return string
357
 */
358
function yourls_get_HTTP_status( $code ) {
359 52
	$code = intval( $code );
360
	$headers_desc = [
361 52
		100 => 'Continue',
362
		101 => 'Switching Protocols',
363
		102 => 'Processing',
364
365
		200 => 'OK',
366
		201 => 'Created',
367
		202 => 'Accepted',
368
		203 => 'Non-Authoritative Information',
369
		204 => 'No Content',
370
		205 => 'Reset Content',
371
		206 => 'Partial Content',
372
		207 => 'Multi-Status',
373
		226 => 'IM Used',
374
375
		300 => 'Multiple Choices',
376
		301 => 'Moved Permanently',
377
		302 => 'Found',
378
		303 => 'See Other',
379
		304 => 'Not Modified',
380
		305 => 'Use Proxy',
381
		306 => 'Reserved',
382
		307 => 'Temporary Redirect',
383
384
		400 => 'Bad Request',
385
		401 => 'Unauthorized',
386
		402 => 'Payment Required',
387
		403 => 'Forbidden',
388
		404 => 'Not Found',
389
		405 => 'Method Not Allowed',
390
		406 => 'Not Acceptable',
391
		407 => 'Proxy Authentication Required',
392
		408 => 'Request Timeout',
393
		409 => 'Conflict',
394
		410 => 'Gone',
395
		411 => 'Length Required',
396
		412 => 'Precondition Failed',
397
		413 => 'Request Entity Too Large',
398
		414 => 'Request-URI Too Long',
399
		415 => 'Unsupported Media Type',
400
		416 => 'Requested Range Not Satisfiable',
401
		417 => 'Expectation Failed',
402
		422 => 'Unprocessable Entity',
403
		423 => 'Locked',
404
		424 => 'Failed Dependency',
405
		426 => 'Upgrade Required',
406
407
		500 => 'Internal Server Error',
408
		501 => 'Not Implemented',
409
		502 => 'Bad Gateway',
410
		503 => 'Service Unavailable',
411
		504 => 'Gateway Timeout',
412
		505 => 'HTTP Version Not Supported',
413
		506 => 'Variant Also Negotiates',
414
		507 => 'Insufficient Storage',
415
		510 => 'Not Extended'
416
    ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 17 space(s), but found 4.
Loading history...
417
418 52
    return isset( $headers_desc[ $code ] ) ? $headers_desc[ $code ] : '';
419
}
420
421
/**
422
 * Log a redirect (for stats)
423
 *
424
 * This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
425
 * exists before calling it.
426
 *
427
 * @since 1.4
428
 * @param string $keyword short URL keyword
429
 * @return mixed Result of the INSERT query (1 on success)
430
 */
431
function yourls_log_redirect( $keyword ) {
432
	// Allow plugins to short-circuit the whole function
433
	$pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
434
	if ( false !== $pre )
435
		return $pre;
436
437
	if ( !yourls_do_log_redirect() )
438
		return true;
439
440
	$table = YOURLS_DB_TABLE_LOG;
441
    $ip = yourls_get_IP();
442
    $binds = [
443
        'now' => date( 'Y-m-d H:i:s' ),
444
        'keyword'  => yourls_sanitize_keyword($keyword),
445
        'referrer' => substr( yourls_get_referrer(), 0, 200 ),
446
        'ua'       => substr(yourls_get_user_agent(), 0, 255),
447
        'ip'       => $ip,
448
        'location' => yourls_geo_ip_to_countrycode($ip),
449
    ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 13 space(s), but found 4.
Loading history...
450
451
    return yourls_get_db()->fetchAffected("INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (:now, :keyword, :referrer, :ua, :ip, :location)", $binds );
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $table instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
This line exceeds maximum limit of 100 characters; contains 202 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
452
}
453
454
/**
455
 * Check if we want to not log redirects (for stats)
456
 *
457
 */
458
function yourls_do_log_redirect() {
459 1
	return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
460
}
461
462
/**
463
 * Check if an upgrade is needed
464
 * @return bool
465
 */
466
function yourls_upgrade_is_needed() {
467
    // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
468
    list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
469
    if ( $currentsql < YOURLS_DB_VERSION ) {
470
        return true;
471
    }
472
473
    // Check if YOURLS_VERSION exist && match value stored in YOURLS_DB_TABLE_OPTIONS, update DB if required
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 108 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
474
    if ( $currentver < YOURLS_VERSION ) {
475
        yourls_update_option( 'version', YOURLS_VERSION );
476
    }
477
478
    return false;
479
}
480
481
/**
482
 * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
483
 * @return array
484
 */
485
function yourls_get_current_version_from_sql() {
486
    $currentver = yourls_get_option( 'version' );
487
    $currentsql = yourls_get_option( 'db_version' );
488
489
    // Values if version is 1.3
490
    if ( !$currentver ) {
491
        $currentver = '1.3';
492
    }
493
    if ( !$currentsql ) {
494
        $currentsql = '100';
495
    }
496
497
    return [ $currentver, $currentsql ];
0 ignored issues
show
Arrays with multiple values should not be declared on a single line.
Loading history...
498
}
499
500
/**
501
 * Determine if the current page is private
502
 *
503
 */
504
function yourls_is_private() {
505 1
    $private = false;
506
507 1
    if ( defined( 'YOURLS_PRIVATE' ) && YOURLS_PRIVATE ) {
508
509 1
        $private = true;
510
511
        // Allow overruling for particular pages:
512
513
        // API
514 1
        if ( yourls_is_API() ) {
515
            if ( !defined( 'YOURLS_PRIVATE_API' ) || YOURLS_PRIVATE_API ) {
516
                $private = true;
517
            }
518
        }
519 1
        elseif ( yourls_is_infos() ) {
520
            if ( !defined( 'YOURLS_PRIVATE_INFOS' ) || YOURLS_PRIVATE_INFOS ) {
521
                $private = true;
522
            }
523
            // Others
524
        }
525
    }
526
527 1
    return yourls_apply_filter( 'is_private', $private );
528
}
529
530
/**
531
 * Allow several short URLs for the same long URL ?
532
 * @return bool
533
 */
534
function yourls_allow_duplicate_longurls() {
535
    // special treatment if API to check for WordPress plugin requests
536 4
    if ( yourls_is_API() && isset( $_REQUEST[ 'source' ] ) && $_REQUEST[ 'source' ] == 'plugin' ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
537
            return false;
538
    }
539 4
    return defined( 'YOURLS_UNIQUE_URLS' ) && !YOURLS_UNIQUE_URLS;
540
}
541
542
/**
543
 * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
544
 * @param string $ip
545
 * @return bool|mixed|string
546
 */
547
function yourls_check_IP_flood( $ip = '' ) {
548
549
	// Allow plugins to short-circuit the whole function
550 3
	$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
551 3
	if ( false !== $pre )
552
		return $pre;
553
554 3
	yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
555
556
	// Raise white flag if installing or if no flood delay defined
557
	if(
558 3
		( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
559
		!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
560
		yourls_is_installing()
561
	)
562 3
		return true;
563
564
	// Don't throttle logged in users
565
	if( yourls_is_private() ) {
566
		 if( yourls_is_valid_user() === true )
567
			return true;
568
	}
569
570
	// Don't throttle whitelist IPs
571
	if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
572
		$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
573
		foreach( (array)$whitelist_ips as $whitelist_ip ) {
574
			$whitelist_ip = trim( $whitelist_ip );
575
			if ( $whitelist_ip == $ip )
576
				return true;
577
		}
578
	}
579
580
	$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
581
582
	yourls_do_action( 'check_ip_flood', $ip );
583
584
	$table = YOURLS_DB_TABLE_URL;
585
	$lasttime = yourls_get_db()->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", [ 'ip' => $ip ] );
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $table instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
This line exceeds maximum limit of 100 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
586
	if( $lasttime ) {
587
		$now = date( 'U' );
588
		$then = date( 'U', strtotime( $lasttime ) );
589
		if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
590
			// Flood!
591
			yourls_do_action( 'ip_flood', $ip, $now - $then );
592
			yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Too Many Requests' ), 429 );
593
		}
594
	}
595
596
	return true;
597
}
598
599
/**
600
 * Check if YOURLS is installing
601
 *
602
 * @since 1.6
603
 * @return bool
604
 */
605
function yourls_is_installing() {
606 1
	return (bool)yourls_apply_filter( 'is_installing', defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 106 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
607
}
608
609
/**
610
 * Check if YOURLS is upgrading
611
 *
612
 * @since 1.6
613
 * @return bool
614
 */
615
function yourls_is_upgrading() {
616 1
    return (bool)yourls_apply_filter( 'is_upgrading', defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 106 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
617
}
618
619
/**
620
 * Check if YOURLS is installed
621
 *
622
 * Checks property $ydb->installed that is created by yourls_get_all_options()
623
 *
624
 * See inline comment for updating from 1.3 or prior.
625
 *
626
 * @return bool
627
 */
628
function yourls_is_installed() {
629 2
	return (bool)yourls_apply_filter( 'is_installed', yourls_get_db()->is_installed() );
630
}
631
632
/**
633
 * Set installed state
634
 *
635
 * @since  1.7.3
636
 * @param bool $bool whether YOURLS is installed or not
637
 * @return void
638
 */
639
function yourls_set_installed( $bool ) {
640
    yourls_get_db()->set_installed( $bool );
641
}
642
643
/**
644
 * Generate random string of (int)$length length and type $type (see function for details)
645
 *
646
 * @param int    $length
647
 * @param int    $type
648
 * @param string $charlist
649
 * @return mixed|string
650
 */
651
function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
652 1
    $length = intval( $length );
653
654
    // define possible characters
655
    switch ( $type ) {
656
657
        // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
658 1
        case '1':
659
            $possible = "23456789bcdfghjkmnpqrstvwxyz";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal 23456789bcdfghjkmnpqrstvwxyz does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
660
            break;
661
662
        // Same, with lower + upper
663 1
        case '2':
664
            $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal 23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
665
            break;
666
667
        // all letters, lowercase
668 1
        case '3':
669
            $possible = "abcdefghijklmnopqrstuvwxyz";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal abcdefghijklmnopqrstuvwxyz does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
670
            break;
671
672
        // all letters, lowercase + uppercase
673 1
        case '4':
674
            $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal abcdefghijklmnopqrstuvwx...DEFGHIJKLMNOPQRSTUVWXYZ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
675
            break;
676
677
        // all digits & letters lowercase
678 1
        case '5':
679
            $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal 0123456789abcdefghijklmnopqrstuvwxyz does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
680
            break;
681
682
        // all digits & letters lowercase + uppercase
683 1
        case '6':
684 1
            $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal 0123456789abcdefghijklmn...DEFGHIJKLMNOPQRSTUVWXYZ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
685 1
            break;
686
687
        // custom char list, or comply to charset as defined in config
688
        default:
689
        case '0':
690
            $possible = $charlist ? $charlist : yourls_get_shorturl_charset();
691
            break;
692
    }
693
694 1
    $str = substr( str_shuffle( $possible ), 0, $length );
695 1
    return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
696
}
697
698
/**
699
 * Check if we're in API mode.
700
 * @return bool
701
 */
702
function yourls_is_API() {
703 24
    return (bool)yourls_apply_filter( 'is_API', defined( 'YOURLS_API' ) && YOURLS_API );
704
}
705
706
/**
707
 * Check if we're in Ajax mode.
708
 * @return bool
709
 */
710
function yourls_is_Ajax() {
711
    return (bool)yourls_apply_filter( 'is_Ajax', defined( 'YOURLS_AJAX' ) && YOURLS_AJAX );
712
}
713
714
/**
715
 * Check if we're in GO mode (yourls-go.php).
716
 * @return bool
717
 */
718
function yourls_is_GO() {
719
    return (bool)yourls_apply_filter( 'is_GO', defined( 'YOURLS_GO' ) && YOURLS_GO );
720
}
721
722
/**
723
 * Check if we're displaying stats infos (yourls-infos.php). Returns bool
724
 *
725
 */
726
function yourls_is_infos() {
727 1
    return (bool)yourls_apply_filter( 'is_infos', defined( 'YOURLS_INFOS' ) && YOURLS_INFOS );
728
}
729
730
/**
731
 * Check if we're in the admin area. Returns bool
732
 *
733
 */
734
function yourls_is_admin() {
735 11
    return (bool)yourls_apply_filter( 'is_admin', defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN );
736
}
737
738
/**
739
 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
740
 *
741
 */
742
function yourls_is_windows() {
743
	return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
744
}
745
746
/**
747
 * Check if SSL is required.
748
 * @return bool
749
 */
750
function yourls_needs_ssl() {
751 5
    return (bool)yourls_apply_filter( 'needs_ssl', defined( 'YOURLS_ADMIN_SSL' ) && YOURLS_ADMIN_SSL );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
752
}
753
754
/**
755
 * Check if SSL is used. Stolen from WP.
756
 * @return bool
757
 */
758
function yourls_is_ssl() {
759 38
    $is_ssl = false;
760 38
    if ( isset( $_SERVER[ 'HTTPS' ] ) ) {
761
        if ( 'on' == strtolower( $_SERVER[ 'HTTPS' ] ) ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
762
            $is_ssl = true;
763
        }
764
        if ( '1' == $_SERVER[ 'HTTPS' ] ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
765
            $is_ssl = true;
766
        }
767
    }
768 38
    elseif ( isset( $_SERVER[ 'SERVER_PORT' ] ) && ( '443' == $_SERVER[ 'SERVER_PORT' ] ) ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
769
        $is_ssl = true;
770
    }
771 38
    return (bool)yourls_apply_filter( 'is_ssl', $is_ssl );
772
}
773
774
/**
775
 * Get a remote page title
776
 *
777
 * This function returns a string: either the page title as defined in HTML, or the URL if not found
778
 * The function tries to convert funky characters found in titles to UTF8, from the detected charset.
779
 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response.
780
 *
781
 * @param string $url URL
782
 * @return string Title (sanitized) or the URL if no title found
783
 */
784
function yourls_get_remote_title( $url ) {
785
    // Allow plugins to short-circuit the whole function
786
    $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
787
    if ( false !== $pre ) {
788
        return $pre;
789
    }
790
791
    $url = yourls_sanitize_url( $url );
792
793
    // Only deal with http(s)://
794
    if ( !in_array( yourls_get_protocol( $url ), [ 'http://', 'https://' ] ) ) {
795
        return $url;
796
    }
797
798
    $title = $charset = false;
799
800
    $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag
801
802
    $response = yourls_http_get( $url, [], [], [ 'max_bytes' => $max_bytes ] ); // can be a Request object or an error string
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 125 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
803
    if ( is_string( $response ) ) {
804
        return $url;
805
    }
806
807
    // Page content. No content? Return the URL
808
    $content = $response->body;
809
    if ( !$content ) {
810
        return $url;
811
    }
812
813
    // look for <title>. No title found? Return the URL
814
    if ( preg_match( '/<title>(.*?)<\/title>/is', $content, $found ) ) {
815
        $title = $found[ 1 ];
816
        unset( $found );
817
    }
818
    if ( !$title ) {
819
        return $url;
820
    }
821
822
    // Now we have a title. We'll try to get proper utf8 from it.
823
824
    // Get charset as (and if) defined by the HTML meta tag. We should match
825
    // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
826
    // or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 101 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
827
    if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) {
828
        $charset = $found[ 1 ];
829
        unset( $found );
830
    }
831
    else {
832
        // No charset found in HTML. Get charset as (and if) defined by the server response
833
        $_charset = current( $response->headers->getValues( 'content-type' ) );
834
        if ( preg_match( '/charset=(\S+)/', $_charset, $found ) ) {
835
            $charset = trim( $found[ 1 ], ';' );
836
            unset( $found );
837
        }
838
    }
839
840
    // Conversion to utf-8 if what we have is not utf8 already
841
    if ( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) {
0 ignored issues
show
Operator != prohibited; use !== instead
Loading history...
842
        // We use @ to remove warnings because mb_ functions are easily bitching about illegal chars
843
        if ( $charset ) {
844
            $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
845
        }
846
        else {
847
            $title = @mb_convert_encoding( $title, 'UTF-8' );
848
        }
849
    }
850
851
    // Remove HTML entities
852
    $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
853
854
    // Strip out evil things
855
    $title = yourls_sanitize_title( $title, $url );
856
857
    return (string)yourls_apply_filter( 'get_remote_title', $title, $url );
858
}
859
860
/**
861
 * Quick UA check for mobile devices.
862
 * @return bool
863
 */
864
function yourls_is_mobile_device() {
865
	// Strings searched
866
	$mobiles = [
867 1
		'android', 'blackberry', 'blazer',
868
		'compal', 'elaine', 'fennec', 'hiptop',
869
		'iemobile', 'iphone', 'ipod', 'ipad',
870
		'iris', 'kindle', 'opera mobi', 'opera mini',
871
		'palm', 'phone', 'pocket', 'psp', 'symbian',
872
		'treo', 'wap', 'windows ce', 'windows phone'
873
    ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 12 space(s), but found 4.
Loading history...
874
875
	// Current user-agent
876 1
	$current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
877
878
	// Check and return
879 1
	$is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
880 1
	return (bool)yourls_apply_filter( 'is_mobile_device', $is_mobile );
881
}
882
883
/**
884
 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc')
885
 *
886
 * With no parameter passed, this function will guess current page and consider
887
 * it is the requested page.
888
 * For testing purposes, parameters can be passed.
889
 *
890
 * @since 1.5
891
 * @param string $yourls_site   Optional, YOURLS installation URL (default to constant YOURLS_SITE)
892
 * @param string $uri           Optional, page requested (default to $_SERVER['REQUEST_URI'] eg '/yourls/abcd' )
893
 * @return string               request relative to YOURLS base (eg 'abdc')
894
 */
895
function yourls_get_request($yourls_site = false, $uri = false) {
896
    // Allow plugins to short-circuit the whole function
897 47
    $pre = yourls_apply_filter( 'shunt_get_request', false );
898 47
    if ( false !== $pre ) {
899
        return $pre;
900
    }
901
902 47
    yourls_do_action( 'pre_get_request', $yourls_site, $uri );
903
904
    // Default values
905 47
    if ( false === $yourls_site ) {
906
        $yourls_site = yourls_get_yourls_site();
907
    }
908 47
    if ( false === $uri ) {
909
        $uri = $_SERVER[ 'REQUEST_URI' ];
910
    }
911
912
    // Even though the config sample states YOURLS_SITE should be set without trailing slash...
913 47
    $yourls_site = rtrim( $yourls_site, '/' );
914
915
    // Now strip the YOURLS_SITE path part out of the requested URI, and get the request relative to YOURLS base
916
    // +---------------------------+-------------------------+---------------------+--------------+
917
    // |       if we request       | and YOURLS is hosted on | YOURLS path part is | "request" is |
918
    // +---------------------------+-------------------------+---------------------+--------------+
919
    // | http://sho.rt/abc         | http://sho.rt           | /                   | abc          |
920
    // | https://SHO.rt/subdir/abc | https://shor.rt/subdir/ | /subdir/            | abc          |
921
    // +---------------------------+-------------------------+---------------------+--------------+
922
    // and so on. You can find various test cases in /tests/tests/utilities/get_request.php
923
924
    // Take only the URL_PATH part of YOURLS_SITE (ie "https://sho.rt:1337/path/to/yourls" -> "/path/to/yourls")
925 47
    $yourls_site = parse_url( $yourls_site, PHP_URL_PATH ).'/';
926
927
    // Strip path part from request if exists
928 47
    $request = $uri;
929 47
    if ( substr( $uri, 0, strlen( $yourls_site ) ) == $yourls_site ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
930 46
        $request = ltrim( substr( $uri, strlen( $yourls_site ) ), '/' );
931
    }
932
933
    // Unless request looks like a full URL (ie request is a simple keyword) strip query string
934 47
    if ( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal @^[a-zA-Z]+://.+@ does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
935 42
        $request = current( explode( '?', $request ) );
936
    }
937
938 47
    $request = yourls_sanitize_url( $request );
939
940 47
    return (string)yourls_apply_filter( 'get_request', $request );
941
}
942
943
/**
944
 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
945
 *
946
 */
947
function yourls_fix_request_uri() {
948
949
    $default_server_values = [
950
        'SERVER_SOFTWARE' => '',
951
        'REQUEST_URI'     => '',
952
    ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 29 space(s), but found 4.
Loading history...
953
    $_SERVER = array_merge( $default_server_values, $_SERVER );
954
955
    // Fix for IIS when running with PHP ISAPI
956
    if ( empty( $_SERVER[ 'REQUEST_URI' ] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER[ 'SERVER_SOFTWARE' ] ) ) ) {
0 ignored issues
show
Operator != prohibited; use !== instead
Loading history...
This line exceeds maximum limit of 100 characters; contains 153 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
957
958
        // IIS Mod-Rewrite
959
        if ( isset( $_SERVER[ 'HTTP_X_ORIGINAL_URL' ] ) ) {
960
            $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_ORIGINAL_URL' ];
961
        }
962
        // IIS Isapi_Rewrite
963
        elseif ( isset( $_SERVER[ 'HTTP_X_REWRITE_URL' ] ) ) {
964
            $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_REWRITE_URL' ];
965
        }
966
        else {
967
            // Use ORIG_PATH_INFO if there is no PATH_INFO
968
            if ( !isset( $_SERVER[ 'PATH_INFO' ] ) && isset( $_SERVER[ 'ORIG_PATH_INFO' ] ) ) {
969
                $_SERVER[ 'PATH_INFO' ] = $_SERVER[ 'ORIG_PATH_INFO' ];
970
            }
971
972
            // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 111 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
973
            if ( isset( $_SERVER[ 'PATH_INFO' ] ) ) {
974
                if ( $_SERVER[ 'PATH_INFO' ] == $_SERVER[ 'SCRIPT_NAME' ] ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
975
                    $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'PATH_INFO' ];
976
                }
977
                else {
978
                    $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'SCRIPT_NAME' ].$_SERVER[ 'PATH_INFO' ];
979
                }
980
            }
981
982
            // Append the query string if it exists and isn't null
983
            if ( !empty( $_SERVER[ 'QUERY_STRING' ] ) ) {
984
                $_SERVER[ 'REQUEST_URI' ] .= '?'.$_SERVER[ 'QUERY_STRING' ];
985
            }
986
        }
987
    }
988
}
989
990
/**
991
 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
992
 *
993
 */
994
function yourls_check_maintenance_mode() {
995
996
	$file = YOURLS_ABSPATH . '/.maintenance' ;
997
	if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
998
		return;
999
1000
	global $maintenance_start;
1001
1002
	include_once( $file );
1003
	// If the $maintenance_start timestamp is older than 10 minutes, don't die.
1004
	if ( ( time() - $maintenance_start ) >= 600 )
1005
		return;
1006
1007
	// Use any /user/maintenance.php file
1008
	if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
1009
		include_once( YOURLS_USERDIR.'/maintenance.php' );
1010
		die();
1011
	}
1012
1013
	// https://www.youtube.com/watch?v=Xw-m4jEY-Ns
1014
	$title   = yourls__( 'Service temporarily unavailable' );
1015
	$message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
1016
	yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
1017
	yourls_die( $message, $title , 503 );
1018
1019
}
1020
1021
/**
1022
 * Return current admin page, or null if not an admin page
1023
 *
1024
 * @return mixed string if admin page, null if not an admin page
1025
 * @since 1.6
1026
 */
1027
function yourls_current_admin_page() {
1028
	if( yourls_is_admin() ) {
1029
		$current = substr( yourls_get_request(), 6 );
1030
		if( $current === false )
1031
			$current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
1032
1033
		return $current;
1034
	}
1035
	return null;
1036
}
1037
1038
/**
1039
 * Check if a URL protocol is allowed
1040
 *
1041
 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
1042
 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
1043
 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
1044
 *
1045
 * @since 1.6
1046
 * @see yourls_get_protocol()
1047
 *
1048
 * @param string $url URL to be check
1049
 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
1050
 * @return bool true if protocol allowed, false otherwise
1051
 */
1052
function yourls_is_allowed_protocol( $url, $protocols = [] ) {
1053 90
    if ( empty( $protocols ) ) {
1054
        global $yourls_allowedprotocols;
1055
        $protocols = $yourls_allowedprotocols;
1056
    }
1057
1058 90
    return yourls_apply_filter( 'is_allowed_protocol', in_array( yourls_get_protocol( $url ), $protocols ), $url, $protocols );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1059
}
1060
1061
/**
1062
 * Get protocol from a URL (eg mailto:, http:// ...)
1063
 *
1064
 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples:
1065
 * "something://blah" -> "something://"
1066
 * "something:blah"   -> "something:"
1067
 * "something:/blah"  -> "something:"
1068
 *
1069
 * Unit Tests for this function are located in tests/format/urls.php
1070
 *
1071
 * @since 1.6
1072
 *
1073
 * @param string $url URL to be check
1074
 * @return string Protocol, with slash slash if applicable. Empty string if no protocol
1075
 */
1076
function yourls_get_protocol( $url ) {
1077
	/*
1078
	http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
1079
	The scheme name consists of a sequence of characters beginning with a letter and followed by any
1080
	combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
1081
	case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
1082
	with lowercase letters. It is followed by a colon (":").
1083
	*/
1084 182
    preg_match( '!^[a-zA-Z][a-zA-Z0-9+.-]+:(//)?!', $url, $matches );
1085 182
	return (string)yourls_apply_filter( 'get_protocol', isset( $matches[0] ) ? $matches[0] : '', $url );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 101 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1086
}
1087
1088
/**
1089
 * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
1090
 *
1091
 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
1092
 * or return empty string if $strict is true
1093
 *
1094
 * @since 1.6
1095
 * @param string $url URL to relativize
1096
 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
1097
 * @return string URL
1098
 */
1099
function yourls_get_relative_url( $url, $strict = true ) {
1100 5
    $url = yourls_sanitize_url( $url );
1101
1102
    // Remove protocols to make it easier
1103 5
    $noproto_url = str_replace( 'https:', 'http:', $url );
1104 5
    $noproto_site = str_replace( 'https:', 'http:', yourls_get_yourls_site() );
1105
1106
    // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
1107 5
    $_url = str_replace( $noproto_site.'/', '', $noproto_url );
1108 5
    if ( $_url == $noproto_url ) {
0 ignored issues
show
Operator == prohibited; use === instead
Loading history...
1109 4
        $_url = ( $strict ? '' : $url );
1110
    }
1111 5
    return yourls_apply_filter( 'get_relative_url', $_url, $url );
1112
}
1113
1114
/**
1115
 * Marks a function as deprecated and informs when it has been used. Stolen from WP.
1116
 *
1117
 * There is a hook deprecated_function that will be called that can be used
1118
 * to get the backtrace up to what file and function called the deprecated
1119
 * function.
1120
 *
1121
 * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
1122
 *
1123
 * This function is to be used in every function that is deprecated.
1124
 *
1125
 * @since 1.6
1126
 * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
1127
 *   and the version the function was deprecated in.
1128
 * @uses yourls_apply_filter() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
1129
 *   trigger or false to not trigger error.
1130
 *
1131
 * @param string $function The function that was called
1132
 * @param string $version The version of WordPress that deprecated the function
1133
 * @param string $replacement Optional. The function that should have been called
1134
 */
1135
function yourls_deprecated_function( $function, $version, $replacement = null ) {
1136
1137
	yourls_do_action( 'deprecated_function', $function, $replacement, $version );
1138
1139
	// Allow plugin to filter the output error trigger
1140
	if ( yourls_get_debug_mode() && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) {
1141
		if ( ! is_null( $replacement ) )
1142
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
1143
		else
1144
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
1145
	}
1146
}
1147
1148
/**
1149
 * Return the value if not an empty string
1150
 *
1151
 * Used with array_filter(), to remove empty keys but not keys with value 0 or false
1152
 *
1153
 * @since 1.6
1154
 * @param mixed $val Value to test against ''
1155
 * @return bool True if not an empty string
1156
 */
1157
function yourls_return_if_not_empty_string( $val ) {
1158
    return ( $val !== '' );
1159
}
1160
1161
/**
1162
 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' )
1163
 *
1164
 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg
1165
 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com,
1166
 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com
1167
 *
1168
 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example
1169
 * if rest = blah.com/file.php?url=http://foo.com
1170
 *
1171
 * Sample returns:
1172
 *
1173
 *   with 'mailto:[email protected]?subject=hey' :
1174
 *   array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' )
1175
 *
1176
 *   with 'http://example.com/blah.html' :
1177
 *   array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' )
1178
 *
1179
 * @since 1.7
1180
 * @param string $url URL to be parsed
1181
 * @param array $array Optional, array of key names to be used in returned array
1182
 * @return array|false false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 102 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1183
 */
1184
function yourls_get_protocol_slashes_and_rest( $url, $array = [ 'protocol', 'slashes', 'rest' ] ) {
1185
    $proto = yourls_get_protocol( $url );
1186
1187
    if ( !$proto or count( $array ) != 3 ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
Operator != prohibited; use !== instead
Loading history...
1188
        return false;
1189
    }
1190
1191
    list( $null, $rest ) = explode( $proto, $url, 2 );
1192
1193
    list( $proto, $slashes ) = explode( ':', $proto );
1194
1195
    return [
1196
        $array[ 0 ] => $proto.':',
1197
        $array[ 1 ] => $slashes,
1198
        $array[ 2 ] => $rest
0 ignored issues
show
Each line in an array declaration must end in a comma
Loading history...
1199
    ];
0 ignored issues
show
The closing parenthesis does not seem to be aligned correctly; expected 11 space(s), but found 4.
Loading history...
1200
}
1201
1202
/**
1203
 * Set URL scheme (to HTTP or HTTPS)
1204
 *
1205
 * @since 1.7.1
1206
 * @param string $url    URL
1207
 * @param string $scheme scheme, either 'http' or 'https'
1208
 * @return string URL with chosen scheme
1209
 */
1210
function yourls_set_url_scheme( $url, $scheme = false ) {
1211 10
    if ( in_array( $scheme, [ 'http', 'https' ] ) ) {
1212 10
        $url = preg_replace( '!^[a-zA-Z0-9+.-]+://!', $scheme.'://', $url );
1213
    }
1214 10
    return $url;
1215
}
1216
1217
/**
1218
 * Tell if there is a new YOURLS version
1219
 *
1220
 * This function checks, if needed, if there's a new version of YOURLS and, if applicable, display
1221
 * an update notice.
1222
 *
1223
 * @since 1.7.3
1224
 */
1225
function yourls_tell_if_new_version() {
1226
    yourls_debug_log( 'Check for new version: '.( yourls_maybe_check_core_version() ? 'yes' : 'no' ) );
0 ignored issues
show
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
1227
    yourls_new_core_version_notice();
1228
}
1229