Completed
Pull Request — master (#2282)
by ྅༻ Ǭɀħ
01:41
created

functions.php ➔ yourls_add_option()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/*
3
 * YOURLS
4
 * Function library
5
 */
6
7
/**
8
 * Determine the allowed character set in short URLs
9
 *
10
 */
11
function yourls_get_shorturl_charset() {
12
	static $charset = null;
13
	if( $charset !== null )
14
		return $charset;
15
16
    if( defined('YOURLS_URL_CONVERT') && in_array( YOURLS_URL_CONVERT, array( 62, 64 ) ) ) {
17
        $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
18
    } else {
19
        // defined to 36, or wrongly defined
20
        $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
21
    }
22
23
	$charset = yourls_apply_filter( 'get_shorturl_charset', $charset );
24
	return $charset;
25
}
26
27
/**
28
 * Make an optimized regexp pattern from a string of characters
29
 *
30
 */
31
function yourls_make_regexp_pattern( $string ) {
32
	$pattern = preg_quote( $string, '@' ); // add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php
33
	// Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc...
34
	return $pattern;
35
}
36
37
/**
38
 * Is a URL a short URL? Accept either 'http://sho.rt/abc' or 'abc'
39
 *
40
 */
41
function yourls_is_shorturl( $shorturl ) {
42
	// TODO: make sure this function evolves with the feature set.
43
44
	$is_short = false;
45
46
	// Is $shorturl a URL (http://sho.rt/abc) or a keyword (abc) ?
47
	if( yourls_get_protocol( $shorturl ) ) {
48
		$keyword = yourls_get_relative_url( $shorturl );
49
	} else {
50
		$keyword = $shorturl;
51
	}
52
53
	// Check if it's a valid && used keyword
54
	if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
55
		$is_short = true;
56
	}
57
58
	return yourls_apply_filter( 'is_shorturl', $is_short, $shorturl );
59
}
60
61
/**
62
 * Check to see if a given keyword is reserved (ie reserved URL or an existing page). Returns bool
63
 *
64
 */
65
function yourls_keyword_is_reserved( $keyword ) {
66
	global $yourls_reserved_URL;
67
	$keyword = yourls_sanitize_keyword( $keyword );
68
	$reserved = false;
69
70
	if ( in_array( $keyword, $yourls_reserved_URL)
71
		or file_exists( YOURLS_ABSPATH ."/pages/$keyword.php" )
72
		or is_dir( YOURLS_ABSPATH ."/$keyword" )
73
	)
74
		$reserved = true;
75
76
	return yourls_apply_filter( 'keyword_is_reserved', $reserved, $keyword );
77
}
78
79
/**
80
 * Function: Get client IP Address. Returns a DB safe string.
81
 *
82
 */
83
function yourls_get_IP() {
84
	$ip = '';
85
86
	// Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
87
	$headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' );
88
	foreach( $headers as $header ) {
89
		if ( !empty( $_SERVER[ $header ] ) ) {
90
			$ip = $_SERVER[ $header ];
91
			break;
92
		}
93
	}
94
95
	// headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
96
	if ( strpos( $ip, ',' ) !== false )
97
		$ip = substr( $ip, 0, strpos( $ip, ',' ) );
98
99
	return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
100
}
101
102
/**
103
 * Get next id a new link will have if no custom keyword provided
104
 *
105
 */
106
function yourls_get_next_decimal() {
107
	return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
108
}
109
110
/**
111
 * Update id for next link with no custom keyword
112
 *
113
 */
114
function yourls_update_next_decimal( $int = '' ) {
115
	$int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
116
	$update = yourls_update_option( 'next_id', $int );
117
	yourls_do_action( 'update_next_decimal', $int, $update );
118
	return $update;
119
}
120
121
/**
122
 * Delete a link in the DB
123
 *
124
 */
125 View Code Duplication
function yourls_delete_link_by_keyword( $keyword ) {
1 ignored issue
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
	// Allow plugins to short-circuit the whole function
127
	$pre = yourls_apply_filter( 'shunt_delete_link_by_keyword', null, $keyword );
128
	if ( null !== $pre )
129
		return $pre;
130
131
	global $ydb;
132
133
	$table = YOURLS_DB_TABLE_URL;
134
    $keyword = yourls_sanitize_string($keyword);
135
    $delete = $ydb->fetchAffected("DELETE FROM `$table` WHERE `keyword` = :keyword", array('keyword' => $keyword));
136
	yourls_do_action( 'delete_link', $keyword, $delete );
137
	return $delete;
138
}
139
140
/**
141
 * SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting
142
 *
143
 */
144
function yourls_insert_link_in_db( $url, $keyword, $title = '' ) {
145
	global $ydb;
146
147
    $url       = yourls_sanitize_url($url);
148
    $keyword   = yourls_sanitize_keyword($keyword);
149
    $title     = yourls_sanitize_title($title);
150
    $timestamp = date('Y-m-d H:i:s');
151
    $ip        = yourls_get_IP();
152
153
    $table = YOURLS_DB_TABLE_URL;
154
    $binds = array(
155
        'keyword'   => $keyword,
156
        'url'       => $url,
157
        'title'     => $title,
158
        'timestamp' => $timestamp,
159
        'ip'        => $ip,
160
    );
161
    $insert = $ydb->fetchAffected("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES(:keyword, :url, :title, :timestamp, :ip, 0);", $binds);
162
163
	yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
164
165
	return (bool)$insert;
166
}
167
168
/**
169
 * Check if a long URL already exists in the DB. Return NULL (doesn't exist) or an object with URL informations.
170
 *
171
 * @since 1.5.1
172
 * @param  string $url  URL to check if already shortened
173
 * @return mixed        NULL if does not already exist in DB, or object with URL information as properties (eg keyword, url, title, ...)
174
 */
175 View Code Duplication
function yourls_url_exists( $url ) {
1 ignored issue
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
	// Allow plugins to short-circuit the whole function
177
	$pre = yourls_apply_filter( 'shunt_url_exists', false, $url );
178
	if ( false !== $pre )
179
		return $pre;
180
181
	global $ydb;
182
	$table = YOURLS_DB_TABLE_URL;
183
    $url   = yourls_sanitize_url($url);
184
	$url_exists = $ydb->fetchObject("SELECT * FROM `$table` WHERE `url` = :url", array('url'=>$url));
185
186
    if ($url_exists === false) {
187
        $url_exists = NULL;
188
    }
189
190
	return yourls_apply_filter( 'url_exists', $url_exists, $url );
191
}
192
193
/**
194
 * Add a new link in the DB, either with custom keyword, or find one
195
 *
196
 */
197
function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
198
	// Allow plugins to short-circuit the whole function
199
	$pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title );
200
	if ( false !== $pre )
201
		return $pre;
202
203
	$url = yourls_encodeURI( $url );
204
	$url = yourls_sanitize_url( $url );
205
	if ( !$url || $url == 'http://' || $url == 'https://' ) {
206
		$return['status']    = 'fail';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = 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...
207
		$return['code']      = 'error:nourl';
208
		$return['message']   = yourls__( 'Missing or malformed URL' );
209
		$return['errorCode'] = '400';
210
		return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title );
211
	}
212
213
	// Prevent DB flood
214
	$ip = yourls_get_IP();
215
	yourls_check_IP_flood( $ip );
216
217
	// Prevent internal redirection loops: cannot shorten a shortened URL
218
	if( yourls_get_relative_url( $url ) ) {
219
		if( yourls_is_shorturl( $url ) ) {
220
			$return['status']    = 'fail';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = 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...
221
			$return['code']      = 'error:noloop';
222
			$return['message']   = yourls__( 'URL is a short URL' );
223
			$return['errorCode'] = '400';
224
			return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title );
225
		}
226
	}
227
228
	yourls_do_action( 'pre_add_new_link', $url, $keyword, $title );
229
230
	$strip_url = stripslashes( $url );
231
	$return = array();
232
233
	// duplicates allowed or new URL => store it
234
	if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) {
235
236
		if( isset( $title ) && !empty( $title ) ) {
237
			$title = yourls_sanitize_title( $title );
238
		} else {
239
			$title = yourls_get_remote_title( $url );
240
		}
241
		$title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword );
242
243
		// Custom keyword provided
244
		if ( $keyword ) {
245
246
			yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title );
247
248
			$keyword = yourls_sanitize_string( $keyword );
249
			$keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title );
250 View Code Duplication
			if ( !yourls_keyword_is_free( $keyword ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
				// This shorturl either reserved or taken already
252
				$return['status']  = 'fail';
253
				$return['code']    = 'error:keyword';
254
				$return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword );
255
			} else {
256
				// all clear, store !
257
				yourls_insert_link_in_db( $url, $keyword, $title );
258
				$return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
259
				$return['status']   = 'success';
260
				$return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
261
				$return['title']    = $title;
262
				$return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
263
				$return['shorturl'] = YOURLS_SITE .'/'. $keyword;
264
			}
265
266
		// Create random keyword
267
		} else {
268
269
			yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title );
270
271
			$timestamp = date( 'Y-m-d H:i:s' );
272
			$id = yourls_get_next_decimal();
273
			$ok = false;
274
			do {
275
				$keyword = yourls_int2string( $id );
276
				$keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title );
277 View Code Duplication
				if ( yourls_keyword_is_free($keyword) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
					if (yourls_insert_link_in_db( $url, $keyword, $title )){
279
						// everything ok, populate needed vars
280
						$return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
281
						$return['status']   = 'success';
282
						$return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
283
						$return['title']    = $title;
284
						$return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
285
						$return['shorturl'] = YOURLS_SITE .'/'. $keyword;
286
					} else {
287
						// database error, couldnt store result
288
						$return['status']   = 'fail';
289
						$return['code']     = 'error:db';
290
						$return['message']  = yourls_s( 'Error saving url to database' );
291
					}
292
					$ok = true;
293
				}
294
				$id++;
295
			} while ( !$ok );
296
			@yourls_update_next_decimal( $id );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
297
		}
298
299
	// URL was already stored
300
	} else {
301
302
		yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title );
303
304
		$return['status']   = 'fail';
305
		$return['code']     = 'error:url';
306
		$return['url']      = array( 'keyword' => $url_exists->keyword, 'url' => $strip_url, 'title' => $url_exists->title, 'date' => $url_exists->timestamp, 'ip' => $url_exists->ip, 'clicks' => $url_exists->clicks );
307
		$return['message']  = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) );
308
		$return['title']    = $url_exists->title;
309
		$return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
310
	}
311
312
	yourls_do_action( 'post_add_new_link', $url, $keyword, $title );
313
314
	$return['statusCode'] = 200; // regardless of result, this is still a valid request
315
	return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title );
316
}
317
318
319
/**
320
 * Edit a link
321
 *
322
 */
323
function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
324
	// Allow plugins to short-circuit the whole function
325
	$pre = yourls_apply_filter( 'shunt_edit_link', null, $keyword, $url, $keyword, $newkeyword, $title );
326
	if ( null !== $pre )
327
		return $pre;
328
329
	global $ydb;
330
331
	$table = YOURLS_DB_TABLE_URL;
332
	$url = yourls_sanitize_url($url);
333
	$keyword = yourls_sanitize_string($keyword);
334
	$title = yourls_sanitize_title($title);
335
	$newkeyword = yourls_sanitize_string($newkeyword);
336
	$strip_url = stripslashes( $url );
337
	$strip_title = stripslashes( $title );
338
339
    $old_url = $ydb->fetchValue("SELECT `url` FROM `$table` WHERE `keyword` = :keyword", array('keyword' => $keyword));
340
341
	// Check if new URL is not here already
342
	if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
343
		$new_url_already_there = intval($ydb->fetchValue("SELECT COUNT(keyword) FROM `$table` WHERE `url` = :url;", array('url' => $url)));
344
	} else {
345
		$new_url_already_there = false;
346
	}
347
348
	// Check if the new keyword is not here already
349
	if ( $newkeyword != $keyword ) {
350
		$keyword_is_ok = yourls_keyword_is_free( $newkeyword );
351
	} else {
352
		$keyword_is_ok = true;
353
	}
354
355
	yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
356
357
	// All clear, update
358
	if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_url_already_there of type integer|false 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...
359
            $sql   = "UPDATE `$table` SET `url` = :url, `keyword` = :newkeyword, `title` = :title WHERE `keyword` = :keyword";
360
            $binds = array('url' => $url, 'newkeyword' => $newkeyword, 'title' => $title, 'keyword' => $keyword);
361
			$update_url = $ydb->fetchAffected($sql, $binds);
362
		if( $update_url ) {
363
			$return['url']     = array( 'keyword' => $newkeyword, 'shorturl' => YOURLS_SITE.'/'.$newkeyword, 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = 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...
364
			$return['status']  = 'success';
365
			$return['message'] = yourls__( 'Link updated in database' );
366
		} else {
367
			$return['status']  = 'fail';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = 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...
368
			$return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ;
369
		}
370
371
	// Nope
372
	} else {
373
		$return['status']  = 'fail';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$return was never initialized. Although not strictly required by PHP, it is generally a good practice to add $return = 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...
374
		$return['message'] = yourls__( 'URL or keyword already exists in database' );
375
	}
376
377
	return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
378
}
379
380
/**
381
 * Update a title link (no checks for duplicates etc..)
382
 *
383
 */
384 View Code Duplication
function yourls_edit_link_title( $keyword, $title ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
	// Allow plugins to short-circuit the whole function
386
	$pre = yourls_apply_filter( 'shunt_edit_link_title', null, $keyword, $title );
387
	if ( null !== $pre )
388
		return $pre;
389
390
	global $ydb;
391
392
	$keyword = yourls_sanitize_keyword( $keyword );
393
	$title = yourls_sanitize_title( $title );
394
395
	$table = YOURLS_DB_TABLE_URL;
396
	$update = $ydb->fetchAffected("UPDATE `$table` SET `title` = :title WHERE `keyword` = :keyword;", array('title' => $title, 'keyword' => $keyword));
397
398
	return $update;
399
}
400
401
402
/**
403
 * Check if keyword id is free (ie not already taken, and not reserved). Return bool.
404
 *
405
 */
406
function yourls_keyword_is_free( $keyword ) {
407
	$free = true;
408
	if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
409
		$free = false;
410
411
	return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
412
}
413
414
/**
415
 * Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
416
 *
417
 */
418 View Code Duplication
function yourls_keyword_is_taken( $keyword ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
420
	// Allow plugins to short-circuit the whole function
421
	$pre = yourls_apply_filter( 'shunt_keyword_is_taken', false, $keyword );
422
	if ( false !== $pre )
423
		return $pre;
424
425
	global $ydb;
426
    $keyword = yourls_sanitize_keyword($keyword);
427
	$taken = false;
428
	$table = YOURLS_DB_TABLE_URL;
429
430
	$already_exists = $ydb->fetchValue("SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = :keyword;", array('keyword' => $keyword));
431
	if ( $already_exists )
432
		$taken = true;
433
434
	return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
435
}
436
437
/**
438
 * Return XML output.
439
 *
440
 */
441
function yourls_xml_encode( $array ) {
442
	require_once( YOURLS_INC.'/functions-xml.php' );
443
	$converter= new yourls_array2xml;
444
	return $converter->array2xml( $array );
445
}
446
447
/**
448
 * Return array of all information associated with keyword. Returns false if keyword not found. Set optional $use_cache to false to force fetching from DB
449
 *
450
 * @since 1.4
451
 * @param  string $keyword    Short URL keyword
452
 * @param  bool   $use_cache  Default true, set to false to force fetching from DB
453
 * @return false|object       false if not found, object with URL properties if found
454
 */
455
function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
456
	global $ydb;
457
	$keyword = yourls_sanitize_string( $keyword );
458
459
	yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
460
461
	if( $ydb->has_infos($keyword) && $use_cache === true ) {
462
		return yourls_apply_filter( 'get_keyword_infos', $ydb->get_infos($keyword), $keyword );
463
	}
464
465
	yourls_do_action( 'get_keyword_not_cached', $keyword );
466
467
	$table = YOURLS_DB_TABLE_URL;
468
	$infos = $ydb->fetchObject("SELECT * FROM `$table` WHERE `keyword` = :keyword", array('keyword' => $keyword));
469
470
	if( $infos ) {
471
		$infos = (array)$infos;
472
		$ydb->set_infos($keyword, $infos);
473
	} else {
474
        // is NULL if not found
475
        $infos = false;
476
		$ydb->set_infos($keyword, false);
477
	}
478
479
	return yourls_apply_filter( 'get_keyword_infos', $infos, $keyword );
480
}
481
482
/**
483
 * Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
484
 *
485
 */
486
function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
487
488
	// Allow plugins to short-circuit the whole function
489
	$pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound );
490
	if ( false !== $pre )
491
		return $pre;
492
493
	$keyword = yourls_sanitize_string( $keyword );
494
	$infos = yourls_get_keyword_infos( $keyword );
495
496
	$return = $notfound;
497
	if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false )
498
		$return = $infos[ $field ];
499
500
	return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound );
501
}
502
503
/**
504
 * Return title associated with keyword. Optional $notfound = string default message if nothing found
505
 *
506
 */
507
function yourls_get_keyword_title( $keyword, $notfound = false ) {
508
	return yourls_get_keyword_info( $keyword, 'title', $notfound );
509
}
510
511
/**
512
 * Return long URL associated with keyword. Optional $notfound = string default message if nothing found
513
 *
514
 */
515
function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
516
	return yourls_get_keyword_info( $keyword, 'url', $notfound );
517
}
518
519
/**
520
 * Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
521
 *
522
 */
523
function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
524
	return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
525
}
526
527
/**
528
 * Return IP that added a keyword. Optional $notfound = string default message if nothing found
529
 *
530
 */
531
function yourls_get_keyword_IP( $keyword, $notfound = false ) {
532
	return yourls_get_keyword_info( $keyword, 'ip', $notfound );
533
}
534
535
/**
536
 * Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
537
 *
538
 */
539
function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
540
	return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
541
}
542
543
/**
544
 * Update click count on a short URL. Return 0/1 for error/success.
545
 *
546
 */
547
function yourls_update_clicks( $keyword, $clicks = false ) {
548
	// Allow plugins to short-circuit the whole function
549
	$pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
550
	if ( false !== $pre )
551
		return $pre;
552
553
	global $ydb;
554
	$keyword = yourls_sanitize_string( $keyword );
555
	$table = YOURLS_DB_TABLE_URL;
556
	if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
557
		$update = $ydb->fetchAffected( "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword", array('clicks' => $clicks, 'keyword' => $keyword) );
558
	else
559
		$update = $ydb->fetchAffected( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = :keyword", array('keyword' => $keyword) );
560
561
	yourls_do_action( 'update_clicks', $keyword, $update, $clicks );
562
	return $update;
563
}
564
565
/**
566
 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
567
 *
568
 */
569
function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) {
570
	global $ydb;
571
572
	switch( $filter ) {
573
		case 'bottom':
574
			$sort_by    = 'clicks';
575
			$sort_order = 'asc';
576
			break;
577
		case 'last':
578
			$sort_by    = 'timestamp';
579
			$sort_order = 'desc';
580
			break;
581
		case 'rand':
582
		case 'random':
583
			$sort_by    = 'RAND()';
584
			$sort_order = '';
585
			break;
586
		case 'top':
587
		default:
588
			$sort_by    = 'clicks';
589
			$sort_order = 'desc';
590
			break;
591
	}
592
593
	// Fetch links
594
	$limit = intval( $limit );
595
	$start = intval( $start );
596
	if ( $limit > 0 ) {
597
598
		$table_url = YOURLS_DB_TABLE_URL;
599
		$results = $ydb->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" );
600
601
		$return = array();
602
		$i = 1;
603
604
		foreach ( (array)$results as $res ) {
605
			$return['links']['link_'.$i++] = array(
606
				'shorturl' => YOURLS_SITE .'/'. $res->keyword,
607
				'url'      => $res->url,
608
				'title'    => $res->title,
609
				'timestamp'=> $res->timestamp,
610
				'ip'       => $res->ip,
611
				'clicks'   => $res->clicks,
612
			);
613
		}
614
	}
615
616
	$return['stats'] = yourls_get_db_stats();
0 ignored issues
show
Bug introduced by
The variable $return does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
617
618
	$return['statusCode'] = 200;
619
620
	return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
621
}
622
623
/**
624
 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
625
 *
626
 */
627
function yourls_get_link_stats( $shorturl ) {
628
	global $ydb;
629
630
	$table_url = YOURLS_DB_TABLE_URL;
631
	$shorturl  = yourls_sanitize_keyword( $shorturl );
632
633
    $res = $ydb->fetchObject("SELECT * FROM `$table_url` WHERE `keyword` = :keyword", array('keyword' => $shorturl));
634
	$return = array();
1 ignored issue
show
Unused Code introduced by
$return is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
635
636
	if( !$res ) {
637
		// non existent link
638
		$return = array(
639
			'statusCode' => 404,
640
			'message'    => 'Error: short URL not found',
641
		);
642
	} else {
643
		$return = array(
644
			'statusCode' => 200,
645
			'message'    => 'success',
646
			'link'       => array(
647
				'shorturl' => YOURLS_SITE .'/'. $res->keyword,
648
				'url'      => $res->url,
649
				'title'    => $res->title,
650
				'timestamp'=> $res->timestamp,
651
				'ip'       => $res->ip,
652
				'clicks'   => $res->clicks,
653
			)
654
		);
655
	}
656
657
	return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
658
}
659
660
/**
661
 * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
662
 *
663
 * The $where parameter will contain additional SQL arguments:
664
 *   $where['sql'] will concatenate SQL clauses: $where['sql'] = ' AND something = :value AND otherthing < :othervalue';
665
 *   $where['binds'] will hold the (name => value) placeholder pairs: $where['binds'] = array('value' => $value, 'othervalue' => $value2)
666
 *
667
 * @param  $where array  See comment above
668
 * @return array
669
 */
670
function yourls_get_db_stats( $where = array('sql' => '', 'binds' => array()) ) {
671
	global $ydb;
672
	$table_url = YOURLS_DB_TABLE_URL;
673
674
	$totals = $ydb->fetchObject( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 " . $where['sql'] , $where['binds'] );
675
	$return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
676
677
	return yourls_apply_filter( 'get_db_stats', $return, $where );
678
}
679
680
/**
681
 * Get number of SQL queries performed
682
 *
683
 */
684
function yourls_get_num_queries() {
685
	global $ydb;
686
687
	return yourls_apply_filter( 'get_num_queries', $ydb->get_num_queries() );
688
}
689
690
/**
691
 * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
692
 *
693
 */
694
function yourls_get_user_agent() {
695
	if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
696
		return '-';
697
698
	$ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
699
	$ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
700
701
	return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
702
}
703
704
/**
705
 * Redirect to another page
706
 *
707
 */
708
function yourls_redirect( $location, $code = 301 ) {
709
	yourls_do_action( 'pre_redirect', $location, $code );
710
	$location = yourls_apply_filter( 'redirect_location', $location, $code );
711
	$code     = yourls_apply_filter( 'redirect_code', $code, $location );
712
	// Redirect, either properly if possible, or via Javascript otherwise
713
	if( !headers_sent() ) {
714
		yourls_status_header( $code );
715
		header( "Location: $location" );
716
	} else {
717
		yourls_redirect_javascript( $location );
718
	}
719
	die();
720
}
721
722
/**
723
 * Set HTTP status header
724
 *
725
 * @since 1.4
726
 * @param int $code  status header code
727
 * @return bool      whether header was sent
728
 */
729
function yourls_status_header( $code = 200 ) {
730
	yourls_do_action( 'status_header', $code );
731
732
	if( headers_sent() )
733
		return false;
734
735
	$protocol = $_SERVER['SERVER_PROTOCOL'];
736
	if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
737
		$protocol = 'HTTP/1.0';
738
739
	$code = intval( $code );
740
	$desc = yourls_get_HTTP_status( $code );
741
742
	@header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
743
744
    return true;
745
}
746
747
/**
748
 * Redirect to another page using Javascript. Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user)
749
 *
750
 */
751
function yourls_redirect_javascript( $location, $dontwait = true ) {
752
	yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
753
	$location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
754
	if( $dontwait ) {
755
		$message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
756
		echo <<<REDIR
757
		<script type="text/javascript">
758
		window.location="$location";
759
		</script>
760
		<small>($message)</small>
761
REDIR;
762
	} else {
763
		echo '<p>' . yourls_s( 'Please <a href="%s">click here</a>', $location ) . '</p>';
764
	}
765
	yourls_do_action( 'post_redirect_javascript', $location );
766
}
767
768
/**
769
 * Return a HTTP status code
770
 *
771
 */
772
function yourls_get_HTTP_status( $code ) {
773
	$code = intval( $code );
774
	$headers_desc = array(
775
		100 => 'Continue',
776
		101 => 'Switching Protocols',
777
		102 => 'Processing',
778
779
		200 => 'OK',
780
		201 => 'Created',
781
		202 => 'Accepted',
782
		203 => 'Non-Authoritative Information',
783
		204 => 'No Content',
784
		205 => 'Reset Content',
785
		206 => 'Partial Content',
786
		207 => 'Multi-Status',
787
		226 => 'IM Used',
788
789
		300 => 'Multiple Choices',
790
		301 => 'Moved Permanently',
791
		302 => 'Found',
792
		303 => 'See Other',
793
		304 => 'Not Modified',
794
		305 => 'Use Proxy',
795
		306 => 'Reserved',
796
		307 => 'Temporary Redirect',
797
798
		400 => 'Bad Request',
799
		401 => 'Unauthorized',
800
		402 => 'Payment Required',
801
		403 => 'Forbidden',
802
		404 => 'Not Found',
803
		405 => 'Method Not Allowed',
804
		406 => 'Not Acceptable',
805
		407 => 'Proxy Authentication Required',
806
		408 => 'Request Timeout',
807
		409 => 'Conflict',
808
		410 => 'Gone',
809
		411 => 'Length Required',
810
		412 => 'Precondition Failed',
811
		413 => 'Request Entity Too Large',
812
		414 => 'Request-URI Too Long',
813
		415 => 'Unsupported Media Type',
814
		416 => 'Requested Range Not Satisfiable',
815
		417 => 'Expectation Failed',
816
		422 => 'Unprocessable Entity',
817
		423 => 'Locked',
818
		424 => 'Failed Dependency',
819
		426 => 'Upgrade Required',
820
821
		500 => 'Internal Server Error',
822
		501 => 'Not Implemented',
823
		502 => 'Bad Gateway',
824
		503 => 'Service Unavailable',
825
		504 => 'Gateway Timeout',
826
		505 => 'HTTP Version Not Supported',
827
		506 => 'Variant Also Negotiates',
828
		507 => 'Insufficient Storage',
829
		510 => 'Not Extended'
830
	);
831
832
	if ( isset( $headers_desc[$code] ) )
833
		return $headers_desc[$code];
834
	else
835
		return '';
836
}
837
838
/**
839
 * Log a redirect (for stats)
840
 *
841
 * This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
842
 * exists before calling it.
843
 *
844
 * @since 1.4
845
 * @param string $keyword short URL keyword
846
 * @return mixed Result of the INSERT query (1 on success)
847
 */
848
function yourls_log_redirect( $keyword ) {
849
	// Allow plugins to short-circuit the whole function
850
	$pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
851
	if ( false !== $pre )
852
		return $pre;
853
854
	if ( !yourls_do_log_redirect() )
855
		return true;
856
857
	global $ydb;
858
	$table = YOURLS_DB_TABLE_LOG;
859
    $ip = yourls_get_IP();
860
    $binds = array(
861
        'now' => date( 'Y-m-d H:i:s' ),
862
        'keyword'  => yourls_sanitize_string($keyword),
863
        'referrer' => isset($_SERVER['HTTP_REFERER']) ? yourls_sanitize_url_safe($_SERVER['HTTP_REFERER']) : 'direct',
864
        'ua'       => yourls_get_user_agent(),
865
        'ip'       => $ip,
866
        'location' => yourls_geo_ip_to_countrycode($ip),
867
    );
868
869
    return $ydb->fetchAffected("INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (:now, :keyword, :referrer, :ua, :ip, :location)", $binds );
870
}
871
872
/**
873
 * Check if we want to not log redirects (for stats)
874
 *
875
 */
876
function yourls_do_log_redirect() {
877
	return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
878
}
879
880
/**
881
 * Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
882
 *
883
 * @since 1.4
884
 * @param string $ip IP or, if empty string, will be current user IP
885
 * @param string $defaut Default string to return if IP doesn't resolve to a country (malformed, private IP...)
0 ignored issues
show
Documentation introduced by
There is no parameter named $defaut. Did you maybe mean $default?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
886
 * @return string 2 letter country code (eg 'US') or $default
887
 */
888
function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
889
	// Allow plugins to short-circuit the Geo IP API
890
	$location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', false, $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here
891
	if ( false !== $location )
892
		return $location;
893
894
	if ( $ip == '' )
895
		$ip = yourls_get_IP();
896
897
    // Allow plugins to stick to YOURLS internals but provide another DB
898
    $db = yourls_apply_filter('geo_ip_path_to_db', YOURLS_INC.'/geo/GeoLite2-Country.mmdb');
899
    if (!is_readable($db)) {
900
        return $default;
901
    }
902
903
    $reader = new \GeoIp2\Database\Reader($db);
904
    try {
905
        $record = $reader->country($ip);
906
        $location = $record->country->isoCode; // eg 'US'
907
    } catch (\Exception $e) {
908
        /*
909
        Unused for now, Exception and $e->getMessage() can be one of :
910
911
        - Exception: \GeoIp2\Exception\AddressNotFoundException
912
          When: valid IP not found
913
          Error message: "The address 10.0.0.30 is not in the database"
914
915
        - Exception: \InvalidArgumentException
916
          When: IP is not valid, or DB not readable
917
          Error message: "The value "10.0.0.300" is not a valid IP address", "The file "/path/to/GeoLite2-Country.mmdb" does not exist or is not readable"
918
919
        - Exception: \MaxMind\Db\Reader\InvalidDatabaseException
920
          When: DB is readable but is corrupt or invalid
921
          Error message: "The MaxMind DB file's search tree is corrupt"
922
923
        - or obviously \Exception for any other error (?)
924
        */
925
        $location = $default;
926
    }
927
928
    return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
929
}
930
931
/**
932
 * Converts a 2 letter country code to long name (ie AU -> Australia)
933
 *
934
 * This associative array is the one used by MaxMind internal functions, it may differ from other lists (eg "A1" does not universally stand for "Anon proxy")
935
 *
936
 * @since 1.4
937
 * @param string $code 2 letter country code, eg 'FR'
938
 * @return string Country long name (eg 'France') or an empty string if not found
939
 */
940
function yourls_geo_countrycode_to_countryname( $code ) {
941
	// Allow plugins to short-circuit the function
942
	$country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code );
943
	if ( false !== $country )
944
		return $country;
945
946
    // Weeeeeeeeeeee
947
    $countries = array(
948
        'A1' => 'Anonymous Proxy', 'A2' => 'Satellite Provider', 'AD' => 'Andorra', 'AE' => 'United Arab Emirates', 'AF' => 'Afghanistan',
949
        'AG' => 'Antigua and Barbuda', 'AI' => 'Anguilla', 'AL' => 'Albania', 'AM' => 'Armenia', 'AO' => 'Angola',
950
        'AP' => 'Asia/Pacific Region', 'AQ' => 'Antarctica', 'AR' => 'Argentina', 'AS' => 'American Samoa', 'AT' => 'Austria',
951
        'AU' => 'Australia', 'AW' => 'Aruba', 'AX' => 'Aland Islands', 'AZ' => 'Azerbaijan', 'BA' => 'Bosnia and Herzegovina',
952
        'BB' => 'Barbados', 'BD' => 'Bangladesh', 'BE' => 'Belgium', 'BF' => 'Burkina Faso', 'BG' => 'Bulgaria',
953
        'BH' => 'Bahrain', 'BI' => 'Burundi', 'BJ' => 'Benin', 'BL' => 'Saint Barthelemy', 'BM' => 'Bermuda',
954
        'BN' => 'Brunei Darussalam', 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Saint Eustatius and Saba', 'BR' => 'Brazil', 'BS' => 'Bahamas',
955
        'BT' => 'Bhutan', 'BV' => 'Bouvet Island', 'BW' => 'Botswana', 'BY' => 'Belarus', 'BZ' => 'Belize',
956
        'CA' => 'Canada', 'CC' => 'Cocos (Keeling) Islands', 'CD' => 'Congo, The Democratic Republic of the', 'CF' => 'Central African Republic', 'CG' => 'Congo',
957
        'CH' => 'Switzerland', 'CI' => 'Cote D\'Ivoire', 'CK' => 'Cook Islands', 'CL' => 'Chile', 'CM' => 'Cameroon',
958
        'CN' => 'China', 'CO' => 'Colombia', 'CR' => 'Costa Rica', 'CU' => 'Cuba', 'CV' => 'Cape Verde',
959
        'CW' => 'Curacao', 'CX' => 'Christmas Island', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DE' => 'Germany',
960
        'DJ' => 'Djibouti', 'DK' => 'Denmark', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'DZ' => 'Algeria',
961
        'EC' => 'Ecuador', 'EE' => 'Estonia', 'EG' => 'Egypt', 'EH' => 'Western Sahara', 'ER' => 'Eritrea',
962
        'ES' => 'Spain', 'ET' => 'Ethiopia', 'EU' => 'Europe', 'FI' => 'Finland', 'FJ' => 'Fiji',
963
        'FK' => 'Falkland Islands (Malvinas)', 'FM' => 'Micronesia, Federated States of', 'FO' => 'Faroe Islands', 'FR' => 'France', 'GA' => 'Gabon',
964
        'GB' => 'United Kingdom', 'GD' => 'Grenada', 'GE' => 'Georgia', 'GF' => 'French Guiana', 'GG' => 'Guernsey',
965
        'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GL' => 'Greenland', 'GM' => 'Gambia', 'GN' => 'Guinea',
966
        'GP' => 'Guadeloupe', 'GQ' => 'Equatorial Guinea', 'GR' => 'Greece', 'GS' => 'South Georgia and the South Sandwich Islands', 'GT' => 'Guatemala',
967
        'GU' => 'Guam', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HK' => 'Hong Kong', 'HM' => 'Heard Island and McDonald Islands',
968
        'HN' => 'Honduras', 'HR' => 'Croatia', 'HT' => 'Haiti', 'HU' => 'Hungary', 'ID' => 'Indonesia',
969
        'IE' => 'Ireland', 'IL' => 'Israel', 'IM' => 'Isle of Man', 'IN' => 'India', 'IO' => 'British Indian Ocean Territory',
970
        'IQ' => 'Iraq', 'IR' => 'Iran, Islamic Republic of', 'IS' => 'Iceland', 'IT' => 'Italy', 'JE' => 'Jersey',
971
        'JM' => 'Jamaica', 'JO' => 'Jordan', 'JP' => 'Japan', 'KE' => 'Kenya', 'KG' => 'Kyrgyzstan',
972
        'KH' => 'Cambodia', 'KI' => 'Kiribati', 'KM' => 'Comoros', 'KN' => 'Saint Kitts and Nevis', 'KP' => 'Korea, Democratic People\'s Republic of',
973
        'KR' => 'Korea, Republic of', 'KW' => 'Kuwait', 'KY' => 'Cayman Islands', 'KZ' => 'Kazakhstan', 'LA' => 'Lao People\'s Democratic Republic',
974
        'LB' => 'Lebanon', 'LC' => 'Saint Lucia', 'LI' => 'Liechtenstein', 'LK' => 'Sri Lanka', 'LR' => 'Liberia',
975
        'LS' => 'Lesotho', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'LV' => 'Latvia', 'LY' => 'Libya',
976
        'MA' => 'Morocco', 'MC' => 'Monaco', 'MD' => 'Moldova, Republic of', 'ME' => 'Montenegro', 'MF' => 'Saint Martin',
977
        'MG' => 'Madagascar', 'MH' => 'Marshall Islands', 'MK' => 'Macedonia', 'ML' => 'Mali', 'MM' => 'Myanmar',
978
        'MN' => 'Mongolia', 'MO' => 'Macau', 'MP' => 'Northern Mariana Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania',
979
        'MS' => 'Montserrat', 'MT' => 'Malta', 'MU' => 'Mauritius', 'MV' => 'Maldives', 'MW' => 'Malawi',
980
        'MX' => 'Mexico', 'MY' => 'Malaysia', 'MZ' => 'Mozambique', 'NA' => 'Namibia', 'NC' => 'New Caledonia',
981
        'NE' => 'Niger', 'NF' => 'Norfolk Island', 'NG' => 'Nigeria', 'NI' => 'Nicaragua', 'NL' => 'Netherlands',
982
        'NO' => 'Norway', 'NP' => 'Nepal', 'NR' => 'Nauru', 'NU' => 'Niue', 'NZ' => 'New Zealand',
983
        'O1' => 'Other', 'OM' => 'Oman', 'PA' => 'Panama', 'PE' => 'Peru', 'PF' => 'French Polynesia',
984
        'PG' => 'Papua New Guinea', 'PH' => 'Philippines', 'PK' => 'Pakistan', 'PL' => 'Poland', 'PM' => 'Saint Pierre and Miquelon',
985
        'PN' => 'Pitcairn Islands', 'PR' => 'Puerto Rico', 'PS' => 'Palestinian Territory', 'PT' => 'Portugal', 'PW' => 'Palau',
986
        'PY' => 'Paraguay', 'QA' => 'Qatar', 'RE' => 'Reunion', 'RO' => 'Romania', 'RS' => 'Serbia',
987
        'RU' => 'Russian Federation', 'RW' => 'Rwanda', 'SA' => 'Saudi Arabia', 'SB' => 'Solomon Islands', 'SC' => 'Seychelles',
988
        'SD' => 'Sudan', 'SE' => 'Sweden', 'SG' => 'Singapore', 'SH' => 'Saint Helena', 'SI' => 'Slovenia',
989
        'SJ' => 'Svalbard and Jan Mayen', 'SK' => 'Slovakia', 'SL' => 'Sierra Leone', 'SM' => 'San Marino', 'SN' => 'Senegal',
990
        'SO' => 'Somalia', 'SR' => 'Suriname', 'SS' => 'South Sudan', 'ST' => 'Sao Tome and Principe', 'SV' => 'El Salvador',
991
        'SX' => 'Sint Maarten (Dutch part)', 'SY' => 'Syrian Arab Republic', 'SZ' => 'Swaziland', 'TC' => 'Turks and Caicos Islands', 'TD' => 'Chad',
992
        'TF' => 'French Southern Territories', 'TG' => 'Togo', 'TH' => 'Thailand', 'TJ' => 'Tajikistan', 'TK' => 'Tokelau',
993
        'TL' => 'Timor-Leste', 'TM' => 'Turkmenistan', 'TN' => 'Tunisia', 'TO' => 'Tonga', 'TR' => 'Turkey',
994
        'TT' => 'Trinidad and Tobago', 'TV' => 'Tuvalu', 'TW' => 'Taiwan', 'TZ' => 'Tanzania, United Republic of', 'UA' => 'Ukraine',
995
        'UG' => 'Uganda', 'UM' => 'United States Minor Outlying Islands', 'US' => 'United States', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan',
996
        'VA' => 'Holy See (Vatican City State)', 'VC' => 'Saint Vincent and the Grenadines', 'VE' => 'Venezuela', 'VG' => 'Virgin Islands, British', 'VI' => 'Virgin Islands, U.S.',
997
        'VN' => 'Vietnam', 'VU' => 'Vanuatu', 'WF' => 'Wallis and Futuna', 'WS' => 'Samoa', 'YE' => 'Yemen',
998
        'YT' => 'Mayotte', 'ZA' => 'South Africa', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
999
    );
1000
1001
    $code = strtoupper($code);
1002
    if(array_key_exists($code, $countries)) {
1003
        $name = $countries[$code];
1004
    } else {
1005
        $name = '';
1006
    }
1007
1008
    return yourls_apply_filter( 'geo_countrycode_to_countryname', $name );
1009
}
1010
1011
/**
1012
 * Return flag URL from 2 letter country code
1013
 *
1014
 */
1015
function yourls_geo_get_flag( $code ) {
1016
	if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) {
1017
		$img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' );
1018
	} else {
1019
		$img = false;
1020
	}
1021
	return yourls_apply_filter( 'geo_get_flag', $img, $code );
1022
}
1023
1024
1025
/**
1026
 * Check if an upgrade is needed
1027
 *
1028
 */
1029
function yourls_upgrade_is_needed() {
1030
	// check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
1031
	list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
0 ignored issues
show
Unused Code introduced by
The assignment to $currentver is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1032
	if( $currentsql < YOURLS_DB_VERSION )
1033
		return true;
1034
1035
	return false;
1036
}
1037
1038
/**
1039
 * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
1040
 *
1041
 */
1042
function yourls_get_current_version_from_sql() {
1043
	$currentver = yourls_get_option( 'version' );
1044
	$currentsql = yourls_get_option( 'db_version' );
1045
1046
	// Values if version is 1.3
1047
	if( !$currentver )
1048
		$currentver = '1.3';
1049
	if( !$currentsql )
1050
		$currentsql = '100';
1051
1052
	return array( $currentver, $currentsql);
1053
}
1054
1055
/**
1056
 * Read an option from DB (or from cache if available). Return value or $default if not found
1057
 *
1058
 * Pretty much stolen from WordPress
1059
 *
1060
 * @since 1.4
1061
 * @param string $option_name Option name. Expected to not be SQL-escaped.
1062
 * @param mixed $default Optional value to return if option doesn't exist. Default false.
1063
 * @return mixed Value set for the option.
1064
 */
1065
function yourls_get_option( $option_name, $default = false ) {
1066
	// Allow plugins to short-circuit options
1067
	$pre = yourls_apply_filter( 'shunt_option_'.$option_name, false );
1068
	if ( false !== $pre )
1069
		return $pre;
1070
1071
	global $ydb;
1072
    $option = new \YOURLS\Database\Options($ydb);
1073
    $value  = $option->get($option_name, $default);
1074
1075
    return yourls_apply_filter( 'get_option_'.$option_name, $value );
1076
}
1077
1078
/**
1079
 * Read all options from DB at once
1080
 *
1081
 * The goal is to read all options at once and then populate array $ydb->option, to prevent further
1082
 * SQL queries if we need to read an option value later.
1083
 * It's also a simple check whether YOURLS is installed or not (no option = assuming not installed) after
1084
 * a check for DB server reachability has been performed
1085
 *
1086
 * @since 1.4
1087
 */
1088
function yourls_get_all_options() {
1089
	// Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options)
1090
	$pre = yourls_apply_filter( 'shunt_all_options', false );
1091
	if ( false !== $pre )
1092
		return $pre;
1093
1094
	global $ydb;
1095
    $options = new \YOURLS\Database\Options($ydb);
1096
1097
    if($options->get_all_options() === false) {
1098
		// Zero option found: either YOURLS is not installed or DB server is dead
1099
        if( !yourls_is_db_alive() ) {
1100
            yourls_db_dead(); // YOURLS will die here
1101
        }
1102
        yourls_set_installed(false);
1103
    }
1104
1105
	yourls_set_installed(true);
1106
}
1107
1108
/**
1109
 * Update (add if doesn't exist) an option to DB
1110
 *
1111
 * Pretty much stolen from WordPress
1112
 *
1113
 * @since 1.4
1114
 * @param string $option_name Option name. Expected to not be SQL-escaped.
1115
 * @param mixed $newvalue Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1116
 * @return bool False if value was not updated, true otherwise.
1117
 */
1118
function yourls_update_option( $option_name, $newvalue ) {
1119
	global $ydb;
1120
1121
    $option = new \YOURLS\Database\Options($ydb);
1122
    $update = $option->update($option_name, $newvalue);
1123
1124
    return $update;
1125
}
1126
1127
/**
1128
 * Add an option to the DB
1129
 *
1130
 * Pretty much stolen from WordPress
1131
 *
1132
 * @since 1.4
1133
 * @param string $name Name of option to add. Expected to not be SQL-escaped.
1134
 * @param mixed $value Optional option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1135
 * @return bool False if option was not added and true otherwise.
1136
 */
1137
function yourls_add_option( $name, $value = '' ) {
1138
	global $ydb;
1139
1140
    $option = new \YOURLS\Database\Options($ydb);
1141
    $add    = $option->add($name, $value);
1142
1143
    return $add;
1144
}
1145
1146
1147
/**
1148
 * Delete an option from the DB
1149
 *
1150
 * Pretty much stolen from WordPress
1151
 *
1152
 * @since 1.4
1153
 * @param string $name Option name to delete. Expected to not be SQL-escaped.
1154
 * @return bool True, if option is successfully deleted. False on failure.
1155
 */
1156
function yourls_delete_option( $name ) {
1157
	global $ydb;
1158
1159
    $option = new \YOURLS\Database\Options($ydb);
1160
    $delete = $option->delete($name);
1161
1162
    return $delete;
1163
}
1164
1165
1166
/**
1167
 * Serialize data if needed. Stolen from WordPress
1168
 *
1169
 * @since 1.4
1170
 * @param mixed $data Data that might be serialized.
1171
 * @return mixed A scalar data
1172
 */
1173
function yourls_maybe_serialize( $data ) {
1174
	if ( is_array( $data ) || is_object( $data ) )
1175
		return serialize( $data );
1176
1177
	if ( yourls_is_serialized( $data, false ) )
1178
		return serialize( $data );
1179
1180
	return $data;
1181
}
1182
1183
/**
1184
 * Check value to find if it was serialized. Stolen from WordPress
1185
 *
1186
 * @since 1.4
1187
 * @param mixed $data Value to check to see if was serialized.
1188
 * @param bool $strict Optional. Whether to be strict about the end of the string. Defaults true.
1189
 * @return bool False if not serialized and true if it was.
1190
 */
1191
function yourls_is_serialized( $data, $strict = true ) {
1192
	// if it isn't a string, it isn't serialized
1193
	if ( ! is_string( $data ) )
1194
		return false;
1195
	$data = trim( $data );
1196
	 if ( 'N;' == $data )
1197
		return true;
1198
	$length = strlen( $data );
1199
	if ( $length < 4 )
1200
		return false;
1201
	if ( ':' !== $data[1] )
1202
		return false;
1203
	if ( $strict ) {
1204
		$lastc = $data[ $length - 1 ];
1205
		if ( ';' !== $lastc && '}' !== $lastc )
1206
			return false;
1207
	} else {
1208
		$semicolon = strpos( $data, ';' );
1209
		$brace	 = strpos( $data, '}' );
1210
		// Either ; or } must exist.
1211
		if ( false === $semicolon && false === $brace )
1212
			return false;
1213
		// But neither must be in the first X characters.
1214
		if ( false !== $semicolon && $semicolon < 3 )
1215
			return false;
1216
		if ( false !== $brace && $brace < 4 )
1217
			return false;
1218
	}
1219
	$token = $data[0];
1220
	switch ( $token ) {
1221
		case 's' :
1222
			if ( $strict ) {
1223
				if ( '"' !== $data[ $length - 2 ] )
1224
					return false;
1225
			} elseif ( false === strpos( $data, '"' ) ) {
1226
				return false;
1227
			}
1228
			// or else fall through
1229
		case 'a' :
1230
		case 'O' :
1231
			return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
1232
		case 'b' :
1233
		case 'i' :
1234
		case 'd' :
1235
			$end = $strict ? '$' : '';
1236
			return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
1237
	}
1238
	return false;
1239
}
1240
1241
/**
1242
 * Unserialize value only if it was serialized. Stolen from WP
1243
 *
1244
 * @since 1.4
1245
 * @param string $original Maybe unserialized original, if is needed.
1246
 * @return mixed Unserialized data can be any type.
1247
 */
1248
function yourls_maybe_unserialize( $original ) {
1249
	if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
1250
		return @unserialize( $original );
1251
	return $original;
1252
}
1253
1254
/**
1255
 * Determine if the current page is private
1256
 *
1257
 */
1258
function yourls_is_private() {
1259
	$private = false;
1260
1261
	if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1262
1263
		// Allow overruling for particular pages:
1264
1265
		// API
1266
		if( yourls_is_API() ) {
1267
			if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
1268
				$private = true;
1269
1270
		// Infos
1271
		} elseif( yourls_is_infos() ) {
1272
			if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
1273
				$private = true;
1274
1275
		// Others
1276
		} else {
1277
			$private = true;
1278
		}
1279
1280
	}
1281
1282
	return yourls_apply_filter( 'is_private', $private );
1283
}
1284
1285
/**
1286
 * Show login form if required
1287
 *
1288
 */
1289
function yourls_maybe_require_auth() {
1290
	if( yourls_is_private() ) {
1291
		yourls_do_action( 'require_auth' );
1292
		require_once( YOURLS_INC.'/auth.php' );
1293
	} else {
1294
		yourls_do_action( 'require_no_auth' );
1295
	}
1296
}
1297
1298
/**
1299
 * Allow several short URLs for the same long URL ?
1300
 *
1301
 */
1302
function yourls_allow_duplicate_longurls() {
1303
	// special treatment if API to check for WordPress plugin requests
1304
	if( yourls_is_API() ) {
1305
		if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' )
1306
			return false;
1307
	}
1308
	return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1309
}
1310
1311
/**
1312
 * Return array of keywords that redirect to the submitted long URL
1313
 *
1314
 * @since 1.7
1315
 * @param string $longurl long url
1316
 * @param string $order Optional SORT order (can be 'ASC' or 'DESC')
1317
 * @return array array of keywords
1318
 */
1319
function yourls_get_longurl_keywords( $longurl, $order = 'ASC' ) {
1320
	global $ydb;
1321
	$longurl = yourls_sanitize_url($longurl);
1322
	$table   = YOURLS_DB_TABLE_URL;
1323
    $sql     = "SELECT `keyword` FROM `$table` WHERE `url` = :url";
1324
1325
    if (in_array($order, array('ASC','DESC'))) {
1326
        $sql .= " ORDER BY `keyword` ".$order;
1327
    }
1328
1329
    return yourls_apply_filter( 'get_longurl_keywords', $ydb->fetchCol($sql, array('url'=>$longurl)), $longurl );
1330
}
1331
1332
/**
1333
 * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
1334
 *
1335
 */
1336
function yourls_check_IP_flood( $ip = '' ) {
1337
1338
	// Allow plugins to short-circuit the whole function
1339
	$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
1340
	if ( false !== $pre )
1341
		return $pre;
1342
1343
	yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
1344
1345
	// Raise white flag if installing or if no flood delay defined
1346
	if(
1347
		( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
1348
		!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
1349
		yourls_is_installing()
1350
	)
1351
		return true;
1352
1353
	// Don't throttle logged in users
1354
	if( yourls_is_private() ) {
1355
		 if( yourls_is_valid_user() === true )
1356
			return true;
1357
	}
1358
1359
	// Don't throttle whitelist IPs
1360
	if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
1361
		$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
1362
		foreach( (array)$whitelist_ips as $whitelist_ip ) {
1363
			$whitelist_ip = trim( $whitelist_ip );
1364
			if ( $whitelist_ip == $ip )
1365
				return true;
1366
		}
1367
	}
1368
1369
	$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
1370
1371
	yourls_do_action( 'check_ip_flood', $ip );
1372
1373
	global $ydb;
1374
	$table = YOURLS_DB_TABLE_URL;
1375
1376
	$lasttime = $ydb->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", array('ip' => $ip) );
1377
	if( $lasttime ) {
1378
		$now = date( 'U' );
1379
		$then = date( 'U', strtotime( $lasttime ) );
1380
		if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
1381
			// Flood!
1382
			yourls_do_action( 'ip_flood', $ip, $now - $then );
1383
			yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
1384
		}
1385
	}
1386
1387
	return true;
1388
}
1389
1390
/**
1391
 * Check if YOURLS is installing
1392
 *
1393
 * @return bool
1394
 * @since 1.6
1395
 */
1396
function yourls_is_installing() {
1397
	$installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1398
	return yourls_apply_filter( 'is_installing', $installing );
1399
}
1400
1401
/**
1402
 * Check if YOURLS is upgrading
1403
 *
1404
 * @return bool
1405
 * @since 1.6
1406
 */
1407
function yourls_is_upgrading() {
1408
	$upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1409
	return yourls_apply_filter( 'is_upgrading', $upgrading );
1410
}
1411
1412
1413
/**
1414
 * Check if YOURLS is installed
1415
 *
1416
 * Checks property $ydb->installed that is created by yourls_get_all_options()
1417
 *
1418
 * See inline comment for updating from 1.3 or prior.
1419
 *
1420
 */
1421
function yourls_is_installed() {
1422
	global $ydb;
1423
	return yourls_apply_filter( 'is_installed', $ydb->is_installed() );
1424
}
1425
1426
/**
1427
 * Set installed state
1428
 *
1429
 * @since  1.7.3
1430
 * @param  bool $bool  whether YOURLS is installed or not
1431
 * @return void
1432
 */
1433
function yourls_set_installed($bool) {
1434
    global $ydb;
1435
    $ydb->set_installed($bool);
1436
}
1437
1438
/**
1439
 * Generate random string of (int)$length length and type $type (see function for details)
1440
 *
1441
 */
1442
function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
1443
	$str = '';
0 ignored issues
show
Unused Code introduced by
$str is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1444
	$length = intval( $length );
1445
1446
	// define possible characters
1447
	switch ( $type ) {
1448
1449
		// custom char list, or comply to charset as defined in config
1450
		case '0':
1451
			$possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
1452
			break;
1453
1454
		// no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
1455
		case '1':
1456
			$possible = "23456789bcdfghjkmnpqrstvwxyz";
1457
			break;
1458
1459
		// Same, with lower + upper
1460
		case '2':
1461
			$possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
1462
			break;
1463
1464
		// all letters, lowercase
1465
		case '3':
1466
			$possible = "abcdefghijklmnopqrstuvwxyz";
1467
			break;
1468
1469
		// all letters, lowercase + uppercase
1470
		case '4':
1471
			$possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1472
			break;
1473
1474
		// all digits & letters lowercase
1475
		case '5':
1476
			$possible = "0123456789abcdefghijklmnopqrstuvwxyz";
1477
			break;
1478
1479
		// all digits & letters lowercase + uppercase
1480
		case '6':
1481
			$possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1482
			break;
1483
1484
	}
1485
1486
    $str = substr( str_shuffle( $possible ), 0, $length );
0 ignored issues
show
Bug introduced by
The variable $possible does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1487
1488
	return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
1489
}
1490
1491
/**
1492
 * Return salted string
1493
 *
1494
 */
1495
function yourls_salt( $string ) {
1496
	$salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
1497
	return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
1498
}
1499
1500
/**
1501
 * Add a query var to a URL and return URL. Completely stolen from WP.
1502
 *
1503
 * Works with one of these parameter patterns:
1504
 *     array( 'var' => 'value' )
1505
 *     array( 'var' => 'value' ), $url
1506
 *     'var', 'value'
1507
 *     'var', 'value', $url
1508
 * If $url omitted, uses $_SERVER['REQUEST_URI']
1509
 *
1510
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1511
 *
1512
 * @since 1.5
1513
 * @param string|array $param1 Either newkey or an associative_array.
0 ignored issues
show
Bug introduced by
There is no parameter named $param1. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1514
 * @param string       $param2 Either newvalue or oldquery or URI.
0 ignored issues
show
Bug introduced by
There is no parameter named $param2. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1515
 * @param string       $param3 Optional. Old query or URI.
0 ignored issues
show
Bug introduced by
There is no parameter named $param3. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1516
 * @return string New URL query string.
1517
 */
1518
function yourls_add_query_arg() {
1519
	$ret = '';
0 ignored issues
show
Unused Code introduced by
$ret is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1520
	if ( is_array( func_get_arg(0) ) ) {
1521 View Code Duplication
		if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1522
			$uri = $_SERVER['REQUEST_URI'];
1523
		else
1524
			$uri = @func_get_arg( 1 );
1525 View Code Duplication
	} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1526
		if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
1527
			$uri = $_SERVER['REQUEST_URI'];
1528
		else
1529
			$uri = @func_get_arg( 2 );
1530
	}
1531
1532
	$uri = str_replace( '&amp;', '&', $uri );
1533
1534
1535
	if ( $frag = strstr( $uri, '#' ) )
1536
		$uri = substr( $uri, 0, -strlen( $frag ) );
1537
	else
1538
		$frag = '';
1539
1540
	if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
1541
		$protocol = $matches[0];
1542
		$uri = substr( $uri, strlen( $protocol ) );
1543
	} else {
1544
		$protocol = '';
1545
	}
1546
1547
	if ( strpos( $uri, '?' ) !== false ) {
1548
		$parts = explode( '?', $uri, 2 );
1549
		if ( 1 == count( $parts ) ) {
1550
			$base = '?';
1551
			$query = $parts[0];
1552
		} else {
1553
			$base = $parts[0] . '?';
1554
			$query = $parts[1];
1555
		}
1556
	} elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
1557
		$base = $uri . '?';
1558
		$query = '';
1559
	} else {
1560
		$base = '';
1561
		$query = $uri;
1562
	}
1563
1564
	parse_str( $query, $qs );
1565
	$qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1566
	if ( is_array( func_get_arg( 0 ) ) ) {
1567
		$kayvees = func_get_arg( 0 );
1568
		$qs = array_merge( $qs, $kayvees );
1569
	} else {
1570
		$qs[func_get_arg( 0 )] = func_get_arg( 1 );
1571
	}
1572
1573
	foreach ( (array) $qs as $k => $v ) {
1574
		if ( $v === false )
1575
			unset( $qs[$k] );
1576
	}
1577
1578
	$ret = http_build_query( $qs );
1579
	$ret = trim( $ret, '?' );
1580
	$ret = preg_replace( '#=(&|$)#', '$1', $ret );
1581
	$ret = $protocol . $base . $ret . $frag;
1582
	$ret = rtrim( $ret, '?' );
1583
	return $ret;
1584
}
1585
1586
/**
1587
 * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
1588
 *
1589
 */
1590
function yourls_urlencode_deep( $value ) {
1591
	$value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
1592
	return $value;
1593
}
1594
1595
/**
1596
 * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
1597
 *
1598
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1599
 *
1600
 * @since 1.5
1601
 * @param string|array $key   Query key or keys to remove.
1602
 * @param bool|string  $query Optional. When false uses the $_SERVER value. Default false.
1603
 * @return string New URL query string.
1604
 */
1605
function yourls_remove_query_arg( $key, $query = false ) {
1606
	if ( is_array( $key ) ) { // removing multiple keys
1607
		foreach ( $key as $k )
1608
			$query = yourls_add_query_arg( $k, false, $query );
1609
		return $query;
1610
	}
1611
	return yourls_add_query_arg( $key, false, $query );
1612
}
1613
1614
/**
1615
 * Return a time-dependent string for nonce creation
1616
 *
1617
 */
1618
function yourls_tick() {
1619
	return ceil( time() / YOURLS_NONCE_LIFE );
1620
}
1621
1622
/**
1623
 * Create a time limited, action limited and user limited token
1624
 *
1625
 */
1626
function yourls_create_nonce( $action, $user = false ) {
1627
	if( false == $user )
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1628
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1629
	$tick = yourls_tick();
1630
	return substr( yourls_salt($tick . $action . $user), 0, 10 );
1631
}
1632
1633
/**
1634
 * Create a nonce field for inclusion into a form
1635
 *
1636
 */
1637
function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
1638
	$field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
1639
	if( $echo )
1640
		echo $field."\n";
1641
	return $field;
1642
}
1643
1644
/**
1645
 * Add a nonce to a URL. If URL omitted, adds nonce to current URL
1646
 *
1647
 */
1648
function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
1649
	$nonce = yourls_create_nonce( $action, $user );
1650
	return yourls_add_query_arg( $name, $nonce, $url );
1651
}
1652
1653
/**
1654
 * Check validity of a nonce (ie time span, user and action match).
1655
 *
1656
 * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
1657
 * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
1658
 *
1659
 */
1660
function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
1661
	// get user
1662
	if( false == $user )
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1663
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1664
1665
	// get current nonce value
1666
	if( false == $nonce && isset( $_REQUEST['nonce'] ) )
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1667
		$nonce = $_REQUEST['nonce'];
1668
1669
	// what nonce should be
1670
	$valid = yourls_create_nonce( $action, $user );
1671
1672
	if( $nonce == $valid ) {
1673
		return true;
1674
	} else {
1675
		if( $return )
1676
			die( $return );
1677
		yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
1678
	}
1679
}
1680
1681
/**
1682
 * Converts keyword into short link (prepend with YOURLS base URL)
1683
 *
1684
 */
1685
function yourls_link( $keyword = '' ) {
1686
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
1687
	return yourls_apply_filter( 'yourls_link', $link, $keyword );
1688
}
1689
1690
/**
1691
 * Converts keyword into stat link (prepend with YOURLS base URL, append +)
1692
 *
1693
 */
1694
function yourls_statlink( $keyword = '' ) {
1695
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
1696
	if( yourls_is_ssl() )
1697
        $link = yourls_set_url_scheme( $link, 'https' );
1698
	return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
1699
}
1700
1701
/**
1702
 * Check if we're in API mode. Returns bool
1703
 *
1704
 */
1705
function yourls_is_API() {
1706
    $return = defined( 'YOURLS_API' ) && YOURLS_API == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1707
    return yourls_apply_filter( 'is_API', $return );
1708
}
1709
1710
/**
1711
 * Check if we're in Ajax mode. Returns bool
1712
 *
1713
 */
1714
function yourls_is_Ajax() {
1715
    $return = defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1716
    return yourls_apply_filter( 'is_Ajax', $return );
1717
}
1718
1719
/**
1720
 * Check if we're in GO mode (yourls-go.php). Returns bool
1721
 *
1722
 */
1723
function yourls_is_GO() {
1724
    $return = defined( 'YOURLS_GO' ) && YOURLS_GO == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1725
    return yourls_apply_filter( 'is_GO', $return );
1726
}
1727
1728
/**
1729
 * Check if we're displaying stats infos (yourls-infos.php). Returns bool
1730
 *
1731
 */
1732
function yourls_is_infos() {
1733
    $return = defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1734
    return yourls_apply_filter( 'is_infos', $return );
1735
}
1736
1737
/**
1738
 * Check if we're in the admin area. Returns bool
1739
 *
1740
 */
1741
function yourls_is_admin() {
1742
    $return = defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1743
    return yourls_apply_filter( 'is_admin', $return );
1744
}
1745
1746
/**
1747
 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
1748
 *
1749
 */
1750
function yourls_is_windows() {
1751
	return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
1752
}
1753
1754
/**
1755
 * Check if SSL is required. Returns bool.
1756
 *
1757
 */
1758
function yourls_needs_ssl() {
1759
    $return = defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1760
    return yourls_apply_filter( 'needs_ssl', $return );
1761
}
1762
1763
/**
1764
 * Return admin link, with SSL preference if applicable.
1765
 *
1766
 */
1767
function yourls_admin_url( $page = '' ) {
1768
	$admin = YOURLS_SITE . '/admin/' . $page;
1769
	if( yourls_is_ssl() or yourls_needs_ssl() ) {
1770
        $admin = yourls_set_url_scheme( $admin, 'https' );
1771
    }
1772
	return yourls_apply_filter( 'admin_url', $admin, $page );
1773
}
1774
1775
/**
1776
 * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
1777
 *
1778
 */
1779
function yourls_site_url( $echo = true, $url = '' ) {
1780
	$url = yourls_get_relative_url( $url );
1781
	$url = trim( YOURLS_SITE . '/' . $url, '/' );
1782
1783
	// Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
1784
	if( yourls_is_ssl() ) {
1785
		$url = yourls_set_url_scheme( $url, 'https' );
1786
    }
1787
	$url = yourls_apply_filter( 'site_url', $url );
1788
	if( $echo ) {
1789
		echo $url;
1790
    }
1791
	return $url;
1792
}
1793
1794
/**
1795
 * Check if SSL is used, returns bool. Stolen from WP.
1796
 *
1797
 */
1798
function yourls_is_ssl() {
1799
	$is_ssl = false;
1800
	if ( isset( $_SERVER['HTTPS'] ) ) {
1801
		if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
1802
			$is_ssl = true;
1803
		if ( '1' == $_SERVER['HTTPS'] )
1804
			$is_ssl = true;
1805
	} elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
1806
		$is_ssl = true;
1807
	}
1808
	return yourls_apply_filter( 'is_ssl', $is_ssl );
1809
}
1810
1811
/**
1812
 * Get a remote page title
1813
 *
1814
 * This function returns a string: either the page title as defined in HTML, or the URL if not found
1815
 * The function tries to convert funky characters found in titles to UTF8, from the detected charset.
1816
 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response.
1817
 *
1818
 * @param string $url URL
1819
 * @return string Title (sanitized) or the URL if no title found
1820
 */
1821
function yourls_get_remote_title( $url ) {
1822
	// Allow plugins to short-circuit the whole function
1823
	$pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
1824
	if ( false !== $pre )
1825
		return $pre;
1826
1827
	$url = yourls_sanitize_url( $url );
1828
1829
	// Only deal with http(s)://
1830
	if( !in_array( yourls_get_protocol( $url ), array( 'http://', 'https://' ) ) )
1831
		return $url;
1832
1833
	$title = $charset = false;
1834
1835
    $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag
1836
1837
	$response = yourls_http_get( $url, array(), array(), array( 'max_bytes' => $max_bytes ) ); // can be a Request object or an error string
1838
	if( is_string( $response ) ) {
1839
		return $url;
1840
	}
1841
1842
	// Page content. No content? Return the URL
1843
	$content = $response->body;
1844
	if( !$content )
1845
		return $url;
1846
1847
	// look for <title>. No title found? Return the URL
1848
	if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
1849
		$title = $found[1];
1850
		unset( $found );
1851
	}
1852
	if( !$title )
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1853
		return $url;
1854
1855
	// Now we have a title. We'll try to get proper utf8 from it.
1856
1857
	// Get charset as (and if) defined by the HTML meta tag. We should match
1858
	// <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1859
	// or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236
1860
	if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) {
1861
		$charset = $found[1];
1862
		unset( $found );
1863
	} else {
1864
		// No charset found in HTML. Get charset as (and if) defined by the server response
1865
		$_charset = current( $response->headers->getValues( 'content-type' ) );
1866
		if( preg_match( '/charset=(\S+)/', $_charset, $found ) ) {
1867
			$charset = trim( $found[1], ';' );
1868
			unset( $found );
1869
		}
1870
	}
1871
1872
	// Conversion to utf-8 if what we have is not utf8 already
1873
	if( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) {
1874
		// We use @ to remove warnings because mb_ functions are easily bitching about illegal chars
1875
		if( $charset ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $charset of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1876
			$title = @mb_convert_encoding( $title, 'UTF-8', $charset );
1877
		} else {
1878
			$title = @mb_convert_encoding( $title, 'UTF-8' );
1879
		}
1880
	}
1881
1882
	// Remove HTML entities
1883
	$title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
1884
1885
	// Strip out evil things
1886
	$title = yourls_sanitize_title( $title, $url );
1887
1888
	return yourls_apply_filter( 'get_remote_title', $title, $url );
1889
}
1890
1891
/**
1892
 * Quick UA check for mobile devices. Return boolean.
1893
 *
1894
 */
1895
function yourls_is_mobile_device() {
1896
	// Strings searched
1897
	$mobiles = array(
1898
		'android', 'blackberry', 'blazer',
1899
		'compal', 'elaine', 'fennec', 'hiptop',
1900
		'iemobile', 'iphone', 'ipod', 'ipad',
1901
		'iris', 'kindle', 'opera mobi', 'opera mini',
1902
		'palm', 'phone', 'pocket', 'psp', 'symbian',
1903
		'treo', 'wap', 'windows ce', 'windows phone'
1904
	);
1905
1906
	// Current user-agent
1907
	$current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1908
1909
	// Check and return
1910
	$is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
1911
	return yourls_apply_filter( 'is_mobile_device', $is_mobile );
1912
}
1913
1914
/**
1915
 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc')
1916
 *
1917
 * With no parameter passed, this function will guess current page and consider
1918
 * it is the current page requested.
1919
 * For testing purposes, parameters can be passed.
1920
 *
1921
 * @since 1.5
1922
 * @param string $yourls_site   Optional, YOURLS installation URL (default to constant YOURLS_SITE)
1923
 * @param string $uri           Optional, page requested (default to $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] eg 'sho.rt/yourls/abcd' )
1924
 * @return string               request relative to YOURLS base (eg 'abdc')
1925
 */
1926
function yourls_get_request($yourls_site = false, $uri = false) {
1927
	// Allow plugins to short-circuit the whole function
1928
	$pre = yourls_apply_filter( 'shunt_get_request', false );
1929
	if ( false !== $pre )
1930
		return $pre;
1931
1932
	yourls_do_action( 'pre_get_request', $yourls_site, $uri );
1933
1934
    // Default values
1935
    if (false === $yourls_site) {
1936
        $yourls_site = YOURLS_SITE;
1937
    }
1938
    if (false === $uri) {
1939
        $uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
1940
    }
1941
1942
    // Even though the config sample states YOURLS_SITE should be set without trailing slash...
1943
    $yourls_site = rtrim($yourls_site,'/');
1944
1945
    // Ignore protocol & www. prefix
1946
	$root = str_replace( array( 'https://www.', 'http://www.', 'https://', 'http://'  ), '', $yourls_site );
1947
	// Case insensitive comparison of the YOURLS root with the requested URL, to match http://Sho.rt/blah, http://sho.rt/blah, http://www.Sho.rt/blah ...
1948
	$request = preg_replace( "!(?:www\.)?$root/!i", '', $uri, 1 );
1949
1950
	// Unless request looks like a full URL (ie request is a simple keyword) strip query string
1951
	if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
1952
		$request = current( explode( '?', $request ) );
1953
	}
1954
1955
	return yourls_apply_filter( 'get_request', $request );
1956
}
1957
1958
/**
1959
 * Change protocol to match current scheme used (http or https)
1960
 *
1961
 */
1962
function yourls_match_current_protocol( $url, $normal = 'http://', $ssl = 'https://' ) {
1963
	if( yourls_is_ssl() )
1964
		$url = str_replace( $normal, $ssl, $url );
1965
	return yourls_apply_filter( 'match_current_protocol', $url );
1966
}
1967
1968
/**
1969
 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
1970
 *
1971
 */
1972
function yourls_fix_request_uri() {
1973
1974
	$default_server_values = array(
1975
		'SERVER_SOFTWARE' => '',
1976
		'REQUEST_URI' => '',
1977
	);
1978
	$_SERVER = array_merge( $default_server_values, $_SERVER );
1979
1980
	// Fix for IIS when running with PHP ISAPI
1981
	if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
1982
1983
		// IIS Mod-Rewrite
1984
		if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
1985
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
1986
		}
1987
		// IIS Isapi_Rewrite
1988
		else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
1989
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
1990
		} else {
1991
			// Use ORIG_PATH_INFO if there is no PATH_INFO
1992
			if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
1993
				$_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
1994
1995
			// Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
1996
			if ( isset( $_SERVER['PATH_INFO'] ) ) {
1997
				if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
1998
					$_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
1999
				else
2000
					$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
2001
			}
2002
2003
			// Append the query string if it exists and isn't null
2004
			if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
2005
				$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2006
			}
2007
		}
2008
	}
2009
}
2010
2011
/**
2012
 * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
2013
 *
2014
 */
2015
function yourls_shutdown() {
2016
	yourls_do_action( 'shutdown' );
2017
}
2018
2019
/**
2020
 * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
2021
 *
2022
 */
2023
function yourls_favicon( $echo = true ) {
2024
	static $favicon = null;
2025
2026
	if( $favicon !== null ) {
2027
        if( $echo ) {
2028
            echo $favicon;
2029
        }
2030
        return $favicon;
2031
    }
2032
2033
	$custom = null;
2034
	// search for favicon.(gif|ico|png|jpg|svg)
2035
	foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
2036
		if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
2037
			$custom = 'favicon.' . $ext;
2038
			break;
2039
		}
2040
	}
2041
2042
	if( $custom ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $custom of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2043
		$favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
2044
	} else {
2045
		$favicon = yourls_site_url( false ) . '/images/favicon.gif';
2046
	}
2047
2048
	if( $echo ) {
2049
		echo $favicon;
2050
    }
2051
	return $favicon;
2052
}
2053
2054
/**
2055
 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
2056
 *
2057
 */
2058
function yourls_check_maintenance_mode() {
2059
2060
	$file = YOURLS_ABSPATH . '/.maintenance' ;
2061
	if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
2062
		return;
2063
2064
	global $maintenance_start;
2065
2066
	include_once( $file );
2067
	// If the $maintenance_start timestamp is older than 10 minutes, don't die.
2068
	if ( ( time() - $maintenance_start ) >= 600 )
2069
		return;
2070
2071
	// Use any /user/maintenance.php file
2072
	if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
2073
		include_once( YOURLS_USERDIR.'/maintenance.php' );
2074
		die();
2075
	}
2076
2077
	// https://www.youtube.com/watch?v=Xw-m4jEY-Ns
2078
	$title   = yourls__( 'Service temporarily unavailable' );
2079
	$message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
2080
	yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
2081
	yourls_die( $message, $title , 503 );
2082
2083
}
2084
2085
/**
2086
 * Return current admin page, or null if not an admin page
2087
 *
2088
 * @return mixed string if admin page, null if not an admin page
2089
 * @since 1.6
2090
 */
2091
function yourls_current_admin_page() {
2092
	if( yourls_is_admin() ) {
2093
		$current = substr( yourls_get_request(), 6 );
2094
		if( $current === false )
2095
			$current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
2096
2097
		return $current;
2098
	}
2099
	return null;
2100
}
2101
2102
/**
2103
 * Check if a URL protocol is allowed
2104
 *
2105
 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
2106
 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
2107
 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
2108
 *
2109
 * @since 1.6
2110
 * @see yourls_get_protocol()
2111
 *
2112
 * @param string $url URL to be check
2113
 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
2114
 * @return boolean true if protocol allowed, false otherwise
2115
 */
2116
function yourls_is_allowed_protocol( $url, $protocols = array() ) {
2117
	if( ! $protocols ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $protocols 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...
2118
		global $yourls_allowedprotocols;
2119
		$protocols = $yourls_allowedprotocols;
2120
	}
2121
2122
	$protocol = yourls_get_protocol( $url );
2123
	return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
2124
}
2125
2126
/**
2127
 * Get protocol from a URL (eg mailto:, http:// ...)
2128
 *
2129
 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples:
2130
 * "something://blah" -> "something://"
2131
 * "something:blah"   -> "something:"
2132
 * "something:/blah"  -> "something:"
2133
 *
2134
 * Unit Tests for this function are located in tests/format/urls.php
2135
 *
2136
 * @since 1.6
2137
 *
2138
 * @param string $url URL to be check
2139
 * @return string Protocol, with slash slash if applicable. Empty string if no protocol
2140
 */
2141
function yourls_get_protocol( $url ) {
2142
	preg_match( '!^[a-zA-Z][a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
2143
	/*
2144
	http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
2145
	The scheme name consists of a sequence of characters beginning with a letter and followed by any
2146
	combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
2147
	case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
2148
	with lowercase letters. It is followed by a colon (":").
2149
	*/
2150
	$protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
2151
	return yourls_apply_filter( 'get_protocol', $protocol, $url );
2152
}
2153
2154
/**
2155
 * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
2156
 *
2157
 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
2158
 * or return empty string if $strict is true
2159
 *
2160
 * @since 1.6
2161
 * @param string $url URL to relativize
2162
 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
2163
 * @return string URL
2164
 */
2165
function yourls_get_relative_url( $url, $strict = true ) {
2166
	$url = yourls_sanitize_url( $url );
2167
2168
	// Remove protocols to make it easier
2169
	$noproto_url  = str_replace( 'https:', 'http:', $url );
2170
	$noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
2171
2172
	// Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
2173
	$_url = str_replace( $noproto_site . '/', '', $noproto_url );
2174
	if( $_url == $noproto_url )
2175
		$_url = ( $strict ? '' : $url );
2176
2177
	return yourls_apply_filter( 'get_relative_url', $_url, $url );
2178
}
2179
2180
/**
2181
 * Marks a function as deprecated and informs when it has been used. Stolen from WP.
2182
 *
2183
 * There is a hook deprecated_function that will be called that can be used
2184
 * to get the backtrace up to what file and function called the deprecated
2185
 * function.
2186
 *
2187
 * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
2188
 *
2189
 * This function is to be used in every function that is deprecated.
2190
 *
2191
 * @since 1.6
2192
 * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
2193
 *   and the version the function was deprecated in.
2194
 * @uses yourls_apply_filter() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
2195
 *   trigger or false to not trigger error.
2196
 *
2197
 * @param string $function The function that was called
2198
 * @param string $version The version of WordPress that deprecated the function
2199
 * @param string $replacement Optional. The function that should have been called
2200
 */
2201
function yourls_deprecated_function( $function, $version, $replacement = null ) {
2202
2203
	yourls_do_action( 'deprecated_function', $function, $replacement, $version );
2204
2205
	// Allow plugin to filter the output error trigger
2206
	if ( YOURLS_DEBUG && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) {
2207
		if ( ! is_null( $replacement ) )
2208
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
2209
		else
2210
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
2211
	}
2212
}
2213
2214
/**
2215
 * Return the value if not an empty string
2216
 *
2217
 * Used with array_filter(), to remove empty keys but not keys with value 0 or false
2218
 *
2219
 * @since 1.6
2220
 * @param mixed $val Value to test against ''
2221
 * @return bool True if not an empty string
2222
 */
2223
function yourls_return_if_not_empty_string( $val ) {
2224
	return( $val !== '' );
2225
}
2226
2227
/**
2228
 * Returns true.
2229
 *
2230
 * Useful for returning true to filters easily.
2231
 *
2232
 * @since 1.7.1
2233
 * @return bool True.
2234
 */
2235
function yourls_return_true() {
2236
    return true;
2237
}
2238
2239
/**
2240
 * Returns false.
2241
 *
2242
 * Useful for returning false to filters easily.
2243
 *
2244
 * @since 1.7.1
2245
 * @return bool False.
2246
 */
2247
function yourls_return_false() {
2248
    return false;
2249
}
2250
2251
/**
2252
 * Returns 0.
2253
 *
2254
 * Useful for returning 0 to filters easily.
2255
 *
2256
 * @since 1.7.1
2257
 * @return int 0.
2258
 */
2259
function yourls_return_zero() {
2260
    return 0;
2261
}
2262
2263
/**
2264
 * Returns an empty array.
2265
 *
2266
 * Useful for returning an empty array to filters easily.
2267
 *
2268
 * @since 1.7.1
2269
 * @return array Empty array.
2270
 */
2271
function yourls_return_empty_array() {
2272
    return array();
2273
}
2274
2275
/**
2276
 * Returns null.
2277
 *
2278
 * Useful for returning null to filters easily.
2279
 *
2280
 * @since 1.7.1
2281
 * @return null Null value.
2282
 */
2283
function yourls_return_null() {
2284
    return null;
2285
}
2286
2287
/**
2288
 * Returns an empty string.
2289
 *
2290
 * Useful for returning an empty string to filters easily.
2291
 *
2292
 * @since 1.7.1
2293
 * @return string Empty string.
2294
 */
2295
function yourls_return_empty_string() {
2296
    return '';
2297
}
2298
2299
/**
2300
 * Add a message to the debug log
2301
 *
2302
 * When in debug mode ( YOURLS_DEBUG == true ) the debug log is echoed in yourls_html_footer()
2303
 * Log messages are appended to $ydb->debug_log array, which is instanciated within class ezSQLcore_YOURLS
2304
 *
2305
 * @since 1.7
2306
 * @param string $msg Message to add to the debug log
2307
 * @return string The message itself
2308
 */
2309
function yourls_debug_log( $msg ) {
2310
	global $ydb;
2311
    $ydb->getProfiler()->log($msg);
2312
	return $msg;
2313
}
2314
2315
/**
2316
 * Get the debug log
2317
 *
2318
 * @since  1.7.3
2319
 * @return array
2320
 */
2321
function yourls_get_debug_log() {
2322
	global $ydb;
2323
    return $ydb->getProfiler()->get_log();
2324
}
2325
2326
/**
2327
 * Debug mode toggle
2328
 *
2329
 * @since 1.7.3
2330
 * @param bool $bool  Debug on or off
2331
 */
2332
function yourls_debug_mode($bool) {
2333
    global $ydb;
2334
    $bool = (bool)$bool;
2335
    $ydb->getProfiler()->setActive($bool);
2336
}
2337
2338
/**
2339
 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' )
2340
 *
2341
 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg
2342
 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com,
2343
 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com
2344
 *
2345
 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example
2346
 * if rest = blah.com/file.php?url=http://foo.com
2347
 *
2348
 * Sample returns:
2349
 *
2350
 *   with 'mailto:[email protected]?subject=hey' :
2351
 *   array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' )
2352
 *
2353
 *   with 'http://example.com/blah.html' :
2354
 *   array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' )
2355
 *
2356
 * @since 1.7
2357
 * @param string $url URL to be parsed
2358
 * @param array $array Optional, array of key names to be used in returned array
2359
 * @return mixed false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise
2360
 */
2361
function yourls_get_protocol_slashes_and_rest( $url, $array = array( 'protocol', 'slashes', 'rest' ) ) {
2362
	$proto = yourls_get_protocol( $url );
2363
2364
	if( !$proto or count( $array ) != 3 )
2365
		return false;
2366
2367
	list( $null, $rest ) = explode( $proto, $url, 2 );
0 ignored issues
show
Unused Code introduced by
The assignment to $null is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
2368
2369
	list( $proto, $slashes ) = explode( ':', $proto );
2370
2371
	return array( $array[0] => $proto . ':', $array[1] => $slashes, $array[2] => $rest );
2372
}
2373
2374
/**
2375
 * Set URL scheme (to HTTP or HTTPS)
2376
 *
2377
 * @since 1.7.1
2378
 * @param string $url URL
2379
 * @param string $scheme scheme, either 'http' or 'https'
2380
 * @return string URL with chosen scheme
2381
 */
2382
function yourls_set_url_scheme( $url, $scheme = false ) {
2383
    if( $scheme != 'http' && $scheme != 'https' ) {
2384
        return $url;
2385
    }
2386
    return preg_replace( '!^[a-zA-Z0-9\+\.-]+://!', $scheme . '://', $url );
2387
}
2388
2389
/**
2390
 * Tell if there is a new YOURLS version
2391
 *
2392
 * This function checks, if needed, if there's a new version of YOURLS and, if applicable, display
2393
 * an update notice.
2394
 *
2395
 * @since 1.7.3
2396
 */
2397
function yourls_tell_if_new_version() {
2398
    $check = yourls_maybe_check_core_version();
2399
    yourls_debug_log( 'Check for new version: ' . ($check ? 'yes' : 'no') );
2400
    yourls_new_core_version_notice();
2401
}
2402
2403