Completed
Pull Request — master (#2066)
by
unknown
03:04
created

functions.php ➔ yourls_add_query_arg()   D

Complexity

Conditions 15
Paths 384

Size

Total Lines 67
Code Lines 52

Duplication

Lines 10
Ratio 14.93 %
Metric Value
cc 15
eloc 52
nc 384
nop 0
dl 10
loc 67
rs 4.3207

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 characters -- this is fixed in PHP 5.3
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
function yourls_delete_link_by_keyword( $keyword ) {
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_escape( yourls_sanitize_string( $keyword ) );
135
	$delete = $ydb->query("DELETE FROM `$table` WHERE `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_escape( yourls_sanitize_url( $url ) );
148
	$keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
149
	$title   = yourls_escape( yourls_sanitize_title( $title ) );
150
151
	$table = YOURLS_DB_TABLE_URL;
152
	$timestamp = date('Y-m-d H:i:s');
153
	$ip = yourls_get_IP();
154
	$insert = $ydb->query("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);");
155
	
156
	yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
157
	
158
	return (bool)$insert;
159
}
160
161
/**
162
 * Check if a URL already exists in the DB. Return NULL (doesn't exist) or an object with URL informations.
163
 *
164
 */
165
function yourls_url_exists( $url ) {
166
	// Allow plugins to short-circuit the whole function
167
	$pre = yourls_apply_filter( 'shunt_url_exists', false, $url );
168
	if ( false !== $pre )
169
		return $pre;
170
171
	global $ydb;
172
	$table = YOURLS_DB_TABLE_URL;
173
	$url   = yourls_escape( yourls_sanitize_url( $url) );
174
	$url_exists = $ydb->get_row( "SELECT * FROM `$table` WHERE `url` = '".$url."';" );
175
	
176
	return yourls_apply_filter( 'url_exists', $url_exists, $url );
177
}
178
179
/**
180
 * Add a new link in the DB, either with custom keyword, or find one
181
 *
182
 */
183
function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
184
	// Allow plugins to short-circuit the whole function
185
	$pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title );
186
	if ( false !== $pre )
187
		return $pre;
188
		
189
	$url = yourls_encodeURI( $url );
190
	$url = yourls_escape( yourls_sanitize_url( $url ) );
191
	if ( !$url || $url == 'http://' || $url == 'https://' ) {
192
		$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...
193
		$return['code']      = 'error:nourl';
194
		$return['message']   = yourls__( 'Missing or malformed URL' );
195
		$return['errorCode'] = '400';
196
		return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title );
197
	}
198
	
199
	// Prevent DB flood
200
	$ip = yourls_get_IP();
201
	yourls_check_IP_flood( $ip );
202
	
203
	// Prevent internal redirection loops: cannot shorten a shortened URL
204
	if( yourls_get_relative_url( $url ) ) {
0 ignored issues
show
Bug introduced by
It seems like $url defined by yourls_escape(yourls_sanitize_url($url)) on line 190 can also be of type array; however, yourls_get_relative_url() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
205
		if( yourls_is_shorturl( $url ) ) {
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:noloop';
208
			$return['message']   = yourls__( 'URL is a short URL' );
209
			$return['errorCode'] = '400';
210
			return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title );
211
		}
212
	}
213
214
	yourls_do_action( 'pre_add_new_link', $url, $keyword, $title );
215
	
216
	$strip_url = stripslashes( $url );
217
	$return = array();
218
219
	// duplicates allowed or new URL => store it
220
	if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) {
221
	
222
		if( isset( $title ) && !empty( $title ) ) {
223
			$title = yourls_sanitize_title( $title );
224
		} else {
225
			$title = yourls_get_remote_title( $url );
0 ignored issues
show
Bug introduced by
It seems like $url defined by yourls_escape(yourls_sanitize_url($url)) on line 190 can also be of type array; however, yourls_get_remote_title() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
226
		}
227
		$title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword );
228
229
		// Custom keyword provided
230
		if ( $keyword ) {
231
			
232
			yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title );
233
		
234
			$keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
235
			$keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title );
236 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...
237
				// This shorturl either reserved or taken already
238
				$return['status']  = 'fail';
239
				$return['code']    = 'error:keyword';
240
				$return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword );
241
			} else {
242
				// all clear, store !
243
				yourls_insert_link_in_db( $url, $keyword, $title );
244
				$return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
245
				$return['status']   = 'success';
246
				$return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
247
				$return['title']    = $title;
248
				$return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
249
				$return['shorturl'] = YOURLS_SITE .'/'. $keyword;
250
			}
251
252
		// Create random keyword	
253
		} else {
254
			
255
			yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title );
256
		
257
			$timestamp = date( 'Y-m-d H:i:s' );
258
			$id = yourls_get_next_decimal();
259
			$ok = false;
260
			do {
261
				$keyword = yourls_int2string( $id );
262
				$keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title );
263 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...
264
					if( @yourls_insert_link_in_db( $url, $keyword, $title ) ){
265
						// everything ok, populate needed vars
266
						$return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
267
						$return['status']   = 'success';
268
						$return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
269
						$return['title']    = $title;
270
						$return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
271
						$return['shorturl'] = YOURLS_SITE .'/'. $keyword;
272
					}else{
273
						// database error, couldnt store result
274
						$return['status']   = 'fail';
275
						$return['code']     = 'error:db';
276
						$return['message']  = yourls_s( 'Error saving url to database' );
277
					}
278
					$ok = true;
279
				}
280
				$id++;
281
			} while ( !$ok );
282
			@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...
283
		}
284
285
	// URL was already stored
286
	} else {
287
			
288
		yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title );
289
		
290
		$return['status']   = 'fail';
291
		$return['code']     = 'error:url';
292
		$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 );
293
		$return['message']  = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) );
294
		$return['title']    = $url_exists->title; 
295
		$return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
296
	}
297
	
298
	yourls_do_action( 'post_add_new_link', $url, $keyword, $title );
299
300
	$return['statusCode'] = 200; // regardless of result, this is still a valid request
301
	return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title );
302
}
303
304
305
/**
306
 * Edit a link
307
 *
308
 */
309
function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
310
	// Allow plugins to short-circuit the whole function
311
	$pre = yourls_apply_filter( 'shunt_edit_link', null, $keyword, $url, $keyword, $newkeyword, $title );
312
	if ( null !== $pre )
313
		return $pre;
314
315
	global $ydb;
316
317
	$table = YOURLS_DB_TABLE_URL;
318
	$url = yourls_escape (yourls_sanitize_url( $url ) );
319
	$keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
320
	$title = yourls_escape( yourls_sanitize_title( $title ) );
321
	$newkeyword = yourls_escape( yourls_sanitize_string( $newkeyword ) );
322
	$strip_url = stripslashes( $url );
323
	$strip_title = stripslashes( $title );
324
	$old_url = $ydb->get_var( "SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';" );
325
	
326
	// Check if new URL is not here already
327
	if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
328
		$new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$url';"));
329
	} else {
330
		$new_url_already_there = false;
331
	}
332
	
333
	// Check if the new keyword is not here already
334
	if ( $newkeyword != $keyword ) {
335
		$keyword_is_ok = yourls_keyword_is_free( $newkeyword );
336
	} else {
337
		$keyword_is_ok = true;
338
	}
339
	
340
	yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
341
	
342
	// All clear, update
343
	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...
344
			$update_url = $ydb->query( "UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';" );
345
		if( $update_url ) {
346
			$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...
347
			$return['status']  = 'success';
348
			$return['message'] = yourls__( 'Link updated in database' );
349
		} else {
350
			$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...
351
			$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 ) ;
352
		}
353
	
354
	// Nope
355
	} else {
356
		$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...
357
		$return['message'] = yourls__( 'URL or keyword already exists in database' );
358
	}
359
	
360
	return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
361
}
362
363
/**
364
 * Update a title link (no checks for duplicates etc..)
365
 *
366
 */
367 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...
368
	// Allow plugins to short-circuit the whole function
369
	$pre = yourls_apply_filter( 'shunt_edit_link_title', null, $keyword, $title );
370
	if ( null !== $pre )
371
		return $pre;
372
373
	global $ydb;
374
	
375
	$keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
376
	$title = yourls_escape( yourls_sanitize_title( $title ) );
377
	
378
	$table = YOURLS_DB_TABLE_URL;
379
	$update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';");
380
381
	return $update;
382
}
383
384
385
/**
386
 * Check if keyword id is free (ie not already taken, and not reserved). Return bool.
387
 *
388
 */
389
function yourls_keyword_is_free( $keyword ) {
390
	$free = true;
391
	if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
392
		$free = false;
393
		
394
	return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
395
}
396
397
/**
398
 * Check if a keyword is taken (ie there is already a short URL with this id). Return bool.		
399
 *
400
 */
401 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...
402
403
	// Allow plugins to short-circuit the whole function
404
	$pre = yourls_apply_filter( 'shunt_keyword_is_taken', false, $keyword );
405
	if ( false !== $pre )
406
		return $pre;
407
	
408
	global $ydb;
409
	$keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
410
	$taken = false;
411
	$table = YOURLS_DB_TABLE_URL;
412
	$already_exists = $ydb->get_var( "SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';" );
413
	if ( $already_exists )
414
		$taken = true;
415
416
	return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
417
}
418
419
/**
420
 * Return XML output.
421
 *
422
 */
423
function yourls_xml_encode( $array ) {
424
	require_once( YOURLS_INC.'/functions-xml.php' );
425
	$converter= new yourls_array2xml;
426
	return $converter->array2xml( $array );
427
}
428
429
/**
430
 * 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
431
 *
432
 */
433
function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
434
	global $ydb;
435
	$keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
436
437
	yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
438
439
	if( isset( $ydb->infos[$keyword] ) && $use_cache == 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...
440
		return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
441
	}
442
	
443
	yourls_do_action( 'get_keyword_not_cached', $keyword );
444
	
445
	$table = YOURLS_DB_TABLE_URL;
446
	$infos = $ydb->get_row( "SELECT * FROM `$table` WHERE `keyword` = '$keyword'" );
447
	
448
	if( $infos ) {
449
		$infos = (array)$infos;
450
		$ydb->infos[ $keyword ] = $infos;
451
	} else {
452
		$ydb->infos[ $keyword ] = false;
453
	}
454
		
455
	return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
456
}
457
458
/**
459
 * Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
460
 *
461
 */
462
function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
463
464
	// Allow plugins to short-circuit the whole function
465
	$pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound );
466
	if ( false !== $pre )
467
		return $pre;
468
469
	$keyword = yourls_sanitize_string( $keyword );
470
	$infos = yourls_get_keyword_infos( $keyword );
471
	
472
	$return = $notfound;
473
	if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false )
474
		$return = $infos[ $field ];
475
476
	return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound );	
477
}
478
479
/**
480
 * Return title associated with keyword. Optional $notfound = string default message if nothing found
481
 *
482
 */
483
function yourls_get_keyword_title( $keyword, $notfound = false ) {
484
	return yourls_get_keyword_info( $keyword, 'title', $notfound );
485
}
486
487
/**
488
 * Return long URL associated with keyword. Optional $notfound = string default message if nothing found
489
 *
490
 */
491
function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
492
	return yourls_get_keyword_info( $keyword, 'url', $notfound );
493
}
494
495
/**
496
 * Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
497
 *
498
 */
499
function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
500
	return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
501
}
502
503
/**
504
 * Return IP that added a keyword. Optional $notfound = string default message if nothing found
505
 *
506
 */
507
function yourls_get_keyword_IP( $keyword, $notfound = false ) {
508
	return yourls_get_keyword_info( $keyword, 'ip', $notfound );
509
}
510
511
/**
512
 * Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
513
 *
514
 */
515
function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
516
	return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
517
}
518
519
/**
520
 * Update click count on a short URL. Return 0/1 for error/success.
521
 *
522
 */
523
function yourls_update_clicks( $keyword, $clicks = false ) {
524
	// Allow plugins to short-circuit the whole function
525
	$pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
526
	if ( false !== $pre )
527
		return $pre;
528
529
	global $ydb;
530
	$keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
531
	$table = YOURLS_DB_TABLE_URL;
532
	if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
533
		$update = $ydb->query( "UPDATE `$table` SET `clicks` = $clicks WHERE `keyword` = '$keyword'" );
534
	else
535
		$update = $ydb->query( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'" );
536
537
	yourls_do_action( 'update_clicks', $keyword, $update, $clicks );
538
	return $update;
539
}
540
541
/**
542
 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
543
 *
544
 */
545
function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) {
546
	global $ydb;
547
548
	switch( $filter ) {
549
		case 'bottom':
550
			$sort_by    = 'clicks';
551
			$sort_order = 'asc';
552
			break;
553
		case 'last':
554
			$sort_by    = 'timestamp';
555
			$sort_order = 'desc';
556
			break;
557
		case 'rand':
558
		case 'random':
559
			$sort_by    = 'RAND()';
560
			$sort_order = '';
561
			break;
562
		case 'top':
563
		default:
564
			$sort_by    = 'clicks';
565
			$sort_order = 'desc';
566
			break;
567
	}
568
	
569
	// Fetch links
570
	$limit = intval( $limit );
571
	$start = intval( $start );
572
	if ( $limit > 0 ) {
573
574
		$table_url = YOURLS_DB_TABLE_URL;
575
		$results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" );
576
		
577
		$return = array();
578
		$i = 1;
579
		
580
		foreach ( (array)$results as $res ) {
581
			$return['links']['link_'.$i++] = array(
582
				'shorturl' => YOURLS_SITE .'/'. $res->keyword,
583
				'url'      => $res->url,
584
				'title'    => $res->title,
585
				'timestamp'=> $res->timestamp,
586
				'ip'       => $res->ip,
587
				'clicks'   => $res->clicks,
588
			);
589
		}
590
	}
591
592
	$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...
593
	
594
	$return['statusCode'] = 200;
595
596
	return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
597
}
598
599
/**
600
 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
601
 *
602
 */
603
function yourls_get_link_stats( $shorturl ) {
604
	global $ydb;
605
606
	$table_url = YOURLS_DB_TABLE_URL;
607
	$shorturl  = yourls_escape( yourls_sanitize_keyword( $shorturl ) );
608
	
609
	$res = $ydb->get_row( "SELECT * FROM `$table_url` WHERE keyword = '$shorturl';" );
610
	$return = array();
0 ignored issues
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...
611
612
	if( !$res ) {
613
		// non existent link
614
		$return = array(
615
			'statusCode' => 404,
616
			'message'    => 'Error: short URL not found',
617
		);
618
	} else {
619
		$return = array(
620
			'statusCode' => 200,
621
			'message'    => 'success',
622
			'link'       => array(
623
				'shorturl' => YOURLS_SITE .'/'. $res->keyword,
624
				'url'      => $res->url,
625
				'title'    => $res->title,
626
				'timestamp'=> $res->timestamp,
627
				'ip'       => $res->ip,
628
				'clicks'   => $res->clicks,
629
			)
630
		);
631
	}
632
633
	return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
634
}
635
636
/**
637
 * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
638
 *
639
 * IMPORTANT NOTE: make sure arguments for the $where clause have been sanitized and yourls_escape()'d
640
 * before calling this function.
641
 *
642
 */
643
function yourls_get_db_stats( $where = '' ) {
644
	global $ydb;
645
	$table_url = YOURLS_DB_TABLE_URL;
646
647
	$totals = $ydb->get_row( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where" );
648
	$return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
649
	
650
	return yourls_apply_filter( 'get_db_stats', $return, $where );
651
}
652
653
/**
654
 * Get number of SQL queries performed
655
 *
656
 */
657
function yourls_get_num_queries() {
658
	global $ydb;
659
660
	return yourls_apply_filter( 'get_num_queries', $ydb->num_queries );
661
}
662
663
/**
664
 * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
665
 *
666
 */
667
function yourls_get_user_agent() {
668
	if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
669
		return '-';
670
	
671
	$ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
672
	$ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
673
		
674
	return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
675
}
676
677
/**
678
 * Redirect to another page
679
 *
680
 */
681
function yourls_redirect( $location, $code = 301 ) {
682
	yourls_do_action( 'pre_redirect', $location, $code );
683
	$location = yourls_apply_filter( 'redirect_location', $location, $code );
684
	$code     = yourls_apply_filter( 'redirect_code', $code, $location );
685
	// Redirect, either properly if possible, or via Javascript otherwise
686
	if( !headers_sent() ) {
687
		yourls_status_header( $code );
688
		header( "Location: $location" );
689
	} else {
690
		yourls_redirect_javascript( $location );
691
	}
692
	die();
693
}
694
695
/**
696
 * Set HTTP status header
697
 *
698
 * @since 1.4
699
 * @param int $code  status header code
700
 * @return bool      whether header was sent
701
 */
702
function yourls_status_header( $code = 200 ) {
703
	yourls_do_action( 'status_header', $code );
704
    
705
	if( headers_sent() )
706
		return false;
707
		
708
	$protocol = $_SERVER['SERVER_PROTOCOL'];
709
	if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
710
		$protocol = 'HTTP/1.0';
711
712
	$code = intval( $code );
713
	$desc = yourls_get_HTTP_status( $code );
714
715
	@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...
716
    
717
    return true;
718
}
719
720
/**
721
 * 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)
722
 *
723
 */
724
function yourls_redirect_javascript( $location, $dontwait = true ) {
725
	yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
726
	$location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
727
	if( $dontwait ) {
728
		$message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
729
		echo <<<REDIR
730
		<script type="text/javascript">
731
		window.location="$location";
732
		</script>
733
		<small>($message)</small>
734
REDIR;
735
	} else {
736
		echo '<p>' . yourls_s( 'Please <a href="%s">click here</a>', $location ) . '</p>';
737
	}
738
	yourls_do_action( 'post_redirect_javascript', $location );
739
}
740
741
/**
742
 * Return a HTTP status code
743
 *
744
 */
745
function yourls_get_HTTP_status( $code ) {
746
	$code = intval( $code );
747
	$headers_desc = array(
748
		100 => 'Continue',
749
		101 => 'Switching Protocols',
750
		102 => 'Processing',
751
752
		200 => 'OK',
753
		201 => 'Created',
754
		202 => 'Accepted',
755
		203 => 'Non-Authoritative Information',
756
		204 => 'No Content',
757
		205 => 'Reset Content',
758
		206 => 'Partial Content',
759
		207 => 'Multi-Status',
760
		226 => 'IM Used',
761
762
		300 => 'Multiple Choices',
763
		301 => 'Moved Permanently',
764
		302 => 'Found',
765
		303 => 'See Other',
766
		304 => 'Not Modified',
767
		305 => 'Use Proxy',
768
		306 => 'Reserved',
769
		307 => 'Temporary Redirect',
770
771
		400 => 'Bad Request',
772
		401 => 'Unauthorized',
773
		402 => 'Payment Required',
774
		403 => 'Forbidden',
775
		404 => 'Not Found',
776
		405 => 'Method Not Allowed',
777
		406 => 'Not Acceptable',
778
		407 => 'Proxy Authentication Required',
779
		408 => 'Request Timeout',
780
		409 => 'Conflict',
781
		410 => 'Gone',
782
		411 => 'Length Required',
783
		412 => 'Precondition Failed',
784
		413 => 'Request Entity Too Large',
785
		414 => 'Request-URI Too Long',
786
		415 => 'Unsupported Media Type',
787
		416 => 'Requested Range Not Satisfiable',
788
		417 => 'Expectation Failed',
789
		422 => 'Unprocessable Entity',
790
		423 => 'Locked',
791
		424 => 'Failed Dependency',
792
		426 => 'Upgrade Required',
793
794
		500 => 'Internal Server Error',
795
		501 => 'Not Implemented',
796
		502 => 'Bad Gateway',
797
		503 => 'Service Unavailable',
798
		504 => 'Gateway Timeout',
799
		505 => 'HTTP Version Not Supported',
800
		506 => 'Variant Also Negotiates',
801
		507 => 'Insufficient Storage',
802
		510 => 'Not Extended'
803
	);
804
805
	if ( isset( $headers_desc[$code] ) )
806
		return $headers_desc[$code];
807
	else
808
		return '';
809
}
810
811
/**
812
 * Log a redirect (for stats)
813
 *
814
 * This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
815
 * exists before calling it.
816
 *
817
 * @since 1.4
818
 * @param string $keyword short URL keyword
819
 * @return mixed Result of the INSERT query (1 on success)
820
 */
821
function yourls_log_redirect( $keyword ) {
822
	// Allow plugins to short-circuit the whole function
823
	$pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
824
	if ( false !== $pre )
825
		return $pre;
826
827
	if ( !yourls_do_log_redirect() )
828
		return true;
829
830
	global $ydb;
831
	$table = YOURLS_DB_TABLE_LOG;
832
	
833
    $now      = date( 'Y-m-d H:i:s' );
834
	$keyword  = yourls_escape( yourls_sanitize_string( $keyword ) );
835
	$referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_escape( yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) ) : 'direct' );
836
	$ua       = yourls_escape( yourls_get_user_agent() );
837
	$ip       = yourls_escape( yourls_get_IP() );
838
	$location = yourls_escape( yourls_geo_ip_to_countrycode( $ip ) );
0 ignored issues
show
Bug introduced by
It seems like $ip defined by yourls_escape(yourls_get_IP()) on line 837 can also be of type array; however, yourls_geo_ip_to_countrycode() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
839
	
840
	return $ydb->query( "INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES ('$now', '$keyword', '$referrer', '$ua', '$ip', '$location')" );
841
}
842
843
/**
844
 * Check if we want to not log redirects (for stats)
845
 *
846
 */
847
function yourls_do_log_redirect() {
848
	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...
849
}
850
851
/**
852
 * Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
853
 *
854
 * @since 1.4
855
 * @param string $ip IP or, if empty string, will be current user IP
856
 * @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...
857
 * @return string 2 letter country code (eg 'US') or $default
858
 */
859
function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
860
	// Allow plugins to short-circuit the Geo IP API
861
	$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
862
	if ( false !== $location )
863
		return $location;
864
	
865
	if ( $ip == '' )
866
		$ip = yourls_get_IP();
867
	
868
	// Use IPv4 or IPv6 DB & functions
869
	if( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
870
		$db   = 'GeoIP.dat';
871
		$func = 'geoip_country_code_by_addr';
872
	} else {
873
		$db   = 'GeoIPv6.dat';
874
		$func = 'geoip_country_code_by_addr_v6';
875
	}
876
	
877
	if ( !file_exists( YOURLS_INC . '/geo/' . $db ) || !file_exists( YOURLS_INC .'/geo/geoip.inc' ) )
878
		return $default;
879
880
	require_once( YOURLS_INC . '/geo/geoip.inc' );
881
	$gi = geoip_open( YOURLS_INC . '/geo/' . $db, GEOIP_STANDARD );
882
	try {
883
		$location = call_user_func( $func, $gi, $ip );
884
	} catch ( Exception $e ) {
885
		$location = '';
886
	}
887
	geoip_close( $gi );
888
	
889
	if( '' == $location )
890
		$location = $default;
891
892
	return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
893
}
894
895
/**
896
 * Converts a 2 letter country code to long name (ie AU -> Australia)
897
 *
898
 */
899
function yourls_geo_countrycode_to_countryname( $code ) {
900
	// Allow plugins to short-circuit the Geo IP API
901
	$country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code );
902
	if ( false !== $country )
903
		return $country;
904
905
	// Load the Geo class if not already done
906
	if( !class_exists( 'GeoIP', false ) ) {
907
		$temp = yourls_geo_ip_to_countrycode( '127.0.0.1' );
0 ignored issues
show
Unused Code introduced by
$temp 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...
908
	}
909
	
910
	if( class_exists( 'GeoIP', false ) ) {
911
		$geo  = new GeoIP;
912
		$id   = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[ $code ];
913
		$long = $geo->GEOIP_COUNTRY_NAMES[ $id ];
914
		return $long;
915
	} else {
916
		return false;
917
	}
918
}
919
920
/**
921
 * Return flag URL from 2 letter country code
922
 *
923
 */
924
function yourls_geo_get_flag( $code ) {
925
	if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) {
926
		$img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' );
927
	} else {
928
		$img = false;
929
	}
930
	return yourls_apply_filter( 'geo_get_flag', $img, $code );
931
}
932
933
934
/**
935
 * Check if an upgrade is needed
936
 *
937
 */
938
function yourls_upgrade_is_needed() {
939
	// check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
940
	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...
941
	if( $currentsql < YOURLS_DB_VERSION )
942
		return true;
943
		
944
	return false;
945
}
946
947
/**
948
 * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
949
 *
950
 */
951
function yourls_get_current_version_from_sql() {
952
	$currentver = yourls_get_option( 'version' );
953
	$currentsql = yourls_get_option( 'db_version' );
954
955
	// Values if version is 1.3
956
	if( !$currentver )
957
		$currentver = '1.3';
958
	if( !$currentsql )
959
		$currentsql = '100';
960
		
961
	return array( $currentver, $currentsql);
962
}
963
964
/**
965
 * Read an option from DB (or from cache if available). Return value or $default if not found
966
 *
967
 * Pretty much stolen from WordPress
968
 *
969
 * @since 1.4
970
 * @param string $option Option name. Expected to not be SQL-escaped.
0 ignored issues
show
Documentation introduced by
There is no parameter named $option. Did you maybe mean $option_name?

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...
971
 * @param mixed $default Optional value to return if option doesn't exist. Default false.
972
 * @return mixed Value set for the option.
973
 */
974
function yourls_get_option( $option_name, $default = false ) {
975
	global $ydb;
976
	
977
	// Allow plugins to short-circuit options
978
	$pre = yourls_apply_filter( 'shunt_option_'.$option_name, false );
979
	if ( false !== $pre )
980
		return $pre;
981
982
	// If option not cached already, get its value from the DB
983
	if ( !isset( $ydb->option[$option_name] ) ) {
984
		$table = YOURLS_DB_TABLE_OPTIONS;
985
		$option_name = yourls_escape( $option_name );
986
		$row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
987
		if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
988
			$value = $row->option_value;
989
		} else { // option does not exist, so we must cache its non-existence
990
			$value = $default;
991
		}
992
		$ydb->option[ $option_name ] = yourls_maybe_unserialize( $value );
993
	}
994
995
	return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
996
}
997
998
/**
999
 * Read all options from DB at once
1000
 *
1001
 * The goal is to read all options at once and then populate array $ydb->option, to prevent further
1002
 * SQL queries if we need to read an option value later.
1003
 * It's also a simple check whether YOURLS is installed or not (no option = assuming not installed) after
1004
 * a check for DB server reachability has been performed
1005
 *
1006
 * @since 1.4
1007
 */
1008
function yourls_get_all_options() {
1009
	global $ydb;
1010
1011
	// Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options)
1012
	$pre = yourls_apply_filter( 'shunt_all_options', false );
1013
	if ( false !== $pre )
1014
		return $pre;
1015
1016
	$table = YOURLS_DB_TABLE_OPTIONS;
1017
	
1018
	$allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" );
1019
	
1020
	foreach( (array)$allopt as $option ) {
1021
		$ydb->option[ $option->option_name ] = yourls_maybe_unserialize( $option->option_value );
1022
	}
1023
1024
	if( property_exists( $ydb, 'option' ) ) {
1025
		$ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option );
1026
		$ydb->installed = true;
1027
	} else {
1028
		// Zero option found: either YOURLS is not installed or DB server is dead
1029
        if( !yourls_is_db_alive() ) {
1030
            yourls_db_dead(); // YOURLS will die here
1031
        }
1032
        $ydb->installed = false;
1033
	}
1034
}
1035
1036
/**
1037
 * Update (add if doesn't exist) an option to DB
1038
 *
1039
 * Pretty much stolen from WordPress
1040
 *
1041
 * @since 1.4
1042
 * @param string $option Option name. Expected to not be SQL-escaped.
0 ignored issues
show
Documentation introduced by
There is no parameter named $option. Did you maybe mean $option_name?

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...
1043
 * @param mixed $newvalue Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1044
 * @return bool False if value was not updated, true otherwise.
1045
 */
1046
function yourls_update_option( $option_name, $newvalue ) {
1047
	global $ydb;
1048
	$table = YOURLS_DB_TABLE_OPTIONS;
1049
	
1050
	$option_name = trim( $option_name );
1051
	if ( empty( $option_name ) )
1052
		return false;
1053
		
1054
	// Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
1055
	if ( is_object( $newvalue ) )
1056
		$newvalue = clone $newvalue;
1057
1058
	$option_name = yourls_escape( $option_name );
1059
1060
	$oldvalue = yourls_get_option( $option_name );
1061
1062
	// If the new and old values are the same, no need to update.
1063
	if ( $newvalue === $oldvalue )
1064
		return false;
1065
1066
	if ( false === $oldvalue ) {
1067
		yourls_add_option( $option_name, $newvalue );
1068
		return true;
1069
	}
1070
1071
	$_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
1072
	
1073
	yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
1074
1075
	$ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" );
1076
1077
	if ( $ydb->rows_affected == 1 ) {
1078
		$ydb->option[ $option_name ] = $newvalue;
1079
		return true;
1080
	}
1081
	return false;
1082
}
1083
1084
/**
1085
 * Add an option to the DB
1086
 *
1087
 * Pretty much stolen from WordPress
1088
 *
1089
 * @since 1.4
1090
 * @param string $option Name of option to add. Expected to not be SQL-escaped.
0 ignored issues
show
Bug introduced by
There is no parameter named $option. 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...
1091
 * @param mixed $value Optional option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1092
 * @return bool False if option was not added and true otherwise.
1093
 */
1094
function yourls_add_option( $name, $value = '' ) {
1095
	global $ydb;
1096
	$table = YOURLS_DB_TABLE_OPTIONS;
1097
	
1098
	$name = trim( $name );
1099
	if ( empty( $name ) )
1100
		return false;
1101
	
1102
	// Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
1103
	if ( is_object( $value ) )
1104
		$value = clone $value;
1105
	
1106
	$name = yourls_escape( $name );
1107
1108
	// Make sure the option doesn't already exist
1109
	if ( false !== yourls_get_option( $name ) )
1110
		return false;
1111
1112
	$_value = yourls_escape( yourls_maybe_serialize( $value ) );
1113
1114
	yourls_do_action( 'add_option', $name, $_value );
1115
1116
	$ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
1117
	$ydb->option[ $name ] = $value;
1118
	return true;
1119
}
1120
1121
1122
/**
1123
 * Delete an option from the DB
1124
 *
1125
 * Pretty much stolen from WordPress
1126
 *
1127
 * @since 1.4
1128
 * @param string $option Option name to delete. Expected to not be SQL-escaped.
0 ignored issues
show
Bug introduced by
There is no parameter named $option. 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...
1129
 * @return bool True, if option is successfully deleted. False on failure.
1130
 */
1131
function yourls_delete_option( $name ) {
1132
	global $ydb;
1133
	$table = YOURLS_DB_TABLE_OPTIONS;
1134
	$name = yourls_escape( $name );
1135
1136
	// Get the ID, if no ID then return
1137
	$option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
1138
	if ( is_null( $option ) || !$option->option_id )
1139
		return false;
1140
		
1141
	yourls_do_action( 'delete_option', $name );
1142
		
1143
	$ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
1144
	unset( $ydb->option[ $name ] );
1145
	return true;
1146
}
1147
1148
1149
/**
1150
 * Serialize data if needed. Stolen from WordPress
1151
 *
1152
 * @since 1.4
1153
 * @param mixed $data Data that might be serialized.
1154
 * @return mixed A scalar data
1155
 */
1156
function yourls_maybe_serialize( $data ) {
1157
	if ( is_array( $data ) || is_object( $data ) )
1158
		return serialize( $data );
1159
1160
	if ( yourls_is_serialized( $data, false ) )
1161
		return serialize( $data );
1162
1163
	return $data;
1164
}
1165
1166
/**
1167
 * Check value to find if it was serialized. Stolen from WordPress
1168
 *
1169
 * @since 1.4
1170
 * @param mixed $data Value to check to see if was serialized.
1171
 * @param bool $strict Optional. Whether to be strict about the end of the string. Defaults true.
1172
 * @return bool False if not serialized and true if it was.
1173
 */
1174
function yourls_is_serialized( $data, $strict = true ) {
1175
	// if it isn't a string, it isn't serialized
1176
	if ( ! is_string( $data ) )
1177
		return false;
1178
	$data = trim( $data );
1179
	 if ( 'N;' == $data )
1180
		return true;
1181
	$length = strlen( $data );
1182
	if ( $length < 4 )
1183
		return false;
1184
	if ( ':' !== $data[1] )
1185
		return false;
1186
	if ( $strict ) {
1187
		$lastc = $data[ $length - 1 ];
1188
		if ( ';' !== $lastc && '}' !== $lastc )
1189
			return false;
1190
	} else {
1191
		$semicolon = strpos( $data, ';' );
1192
		$brace	 = strpos( $data, '}' );
1193
		// Either ; or } must exist.
1194
		if ( false === $semicolon && false === $brace )
1195
			return false;
1196
		// But neither must be in the first X characters.
1197
		if ( false !== $semicolon && $semicolon < 3 )
1198
			return false;
1199
		if ( false !== $brace && $brace < 4 )
1200
			return false;
1201
	}
1202
	$token = $data[0];
1203
	switch ( $token ) {
1204
		case 's' :
1205
			if ( $strict ) {
1206
				if ( '"' !== $data[ $length - 2 ] )
1207
					return false;
1208
			} elseif ( false === strpos( $data, '"' ) ) {
1209
				return false;
1210
			}
1211
			// or else fall through
1212
		case 'a' :
1213
		case 'O' :
1214
			return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
1215
		case 'b' :
1216
		case 'i' :
1217
		case 'd' :
1218
			$end = $strict ? '$' : '';
1219
			return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
1220
	}
1221
	return false;
1222
}
1223
1224
/**
1225
 * Unserialize value only if it was serialized. Stolen from WP
1226
 *
1227
 * @since 1.4
1228
 * @param string $original Maybe unserialized original, if is needed.
1229
 * @return mixed Unserialized data can be any type.
1230
 */
1231
function yourls_maybe_unserialize( $original ) {
1232
	if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
1233
		return @unserialize( $original );
1234
	return $original;
1235
}
1236
1237
/**
1238
 * Determine if the current page is private
1239
 *
1240
 */
1241
function yourls_is_private() {
1242
	$private = false;
1243
1244
	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...
1245
1246
		// Allow overruling for particular pages:
1247
		
1248
		// API
1249
		if( yourls_is_API() ) {
1250
			if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
1251
				$private = true;		
1252
1253
		// Infos
1254
		} elseif( yourls_is_infos() ) {
1255
			if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
1256
				$private = true;
1257
		
1258
		// Others
1259
		} else {
1260
			$private = true;
1261
		}
1262
		
1263
	}
1264
			
1265
	return yourls_apply_filter( 'is_private', $private );
1266
}
1267
1268
/**
1269
 * Show login form if required
1270
 *
1271
 */
1272
function yourls_maybe_require_auth() {
1273
	if( yourls_is_private() ) {
1274
		yourls_do_action( 'require_auth' );
1275
		require_once( YOURLS_INC.'/auth.php' );
1276
	} else {
1277
		yourls_do_action( 'require_no_auth' );
1278
	}
1279
}
1280
1281
/**
1282
 * Allow several short URLs for the same long URL ?
1283
 *
1284
 */
1285
function yourls_allow_duplicate_longurls() {
1286
	// special treatment if API to check for WordPress plugin requests
1287
	if( yourls_is_API() ) {
1288
		if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' ) 
1289
			return false;
1290
	}
1291
	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...
1292
}
1293
1294
/**
1295
 * Return array of keywords that redirect to the submitted long URL
1296
 *
1297
 * @since 1.7
1298
 * @param string $longurl long url
1299
 * @param string $sort Optional ORDER BY order (can be 'keyword', 'title', 'timestamp' or'clicks')
1300
 * @param string $order Optional SORT order (can be 'ASC' or 'DESC')
1301
 * @return array array of keywords
1302
 */
1303
function yourls_get_longurl_keywords( $longurl, $sort = 'none', $order = 'ASC' ) {
1304
	global $ydb;
1305
	$longurl = yourls_escape( yourls_sanitize_url( $longurl ) );
1306
	$table   = YOURLS_DB_TABLE_URL;
1307
	$query   = "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'";
1308
	
1309
	// Ensure sort is a column in database (@TODO: update verification array if database changes)
1310
	if ( in_array( $sort, array('keyword','title','timestamp','clicks') ) ) {
1311
		$query .= " ORDER BY '".$sort."'";
1312
		if ( in_array( $order, array( 'ASC','DESC' ) ) ) {
1313
			$query .= " ".$order;
1314
		}
1315
	}
1316
	return yourls_apply_filter( 'get_longurl_keywords', $ydb->get_col( $query ), $longurl );
1317
}
1318
1319
/**
1320
 * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
1321
 *
1322
 */
1323
function yourls_check_IP_flood( $ip = '' ) {
1324
1325
	// Allow plugins to short-circuit the whole function
1326
	$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
1327
	if ( false !== $pre )
1328
		return $pre;
1329
1330
	yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
1331
1332
	// Raise white flag if installing or if no flood delay defined
1333
	if(
1334
		( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
1335
		!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
1336
		yourls_is_installing()
1337
	)
1338
		return true;
1339
1340
	// Don't throttle logged in users
1341
	if( yourls_is_private() ) {
1342
		 if( yourls_is_valid_user() === true )
1343
			return true;
1344
	}
1345
	
1346
	// Don't throttle whitelist IPs
1347
	if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
1348
		$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
1349
		foreach( (array)$whitelist_ips as $whitelist_ip ) {
1350
			$whitelist_ip = trim( $whitelist_ip );
1351
			if ( $whitelist_ip == $ip )
1352
				return true;
1353
		}
1354
	}
1355
	
1356
	$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
1357
	$ip = yourls_escape( $ip );
1358
1359
	yourls_do_action( 'check_ip_flood', $ip );
1360
	
1361
	global $ydb;
1362
	$table = YOURLS_DB_TABLE_URL;
1363
	
1364
	$lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
1365
	if( $lasttime ) {
1366
		$now = date( 'U' );
1367
		$then = date( 'U', strtotime( $lasttime ) );
1368
		if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
1369
			// Flood!
1370
			yourls_do_action( 'ip_flood', $ip, $now - $then );
1371
			yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
1372
		}
1373
	}
1374
	
1375
	return true;
1376
}
1377
1378
/**
1379
 * Check if YOURLS is installing
1380
 *
1381
 * @return bool
1382
 * @since 1.6
1383
 */
1384
function yourls_is_installing() {
1385
	$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...
1386
	return yourls_apply_filter( 'is_installing', $installing );
1387
}
1388
1389
/**
1390
 * Check if YOURLS is upgrading
1391
 *
1392
 * @return bool
1393
 * @since 1.6
1394
 */
1395
function yourls_is_upgrading() {
1396
	$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...
1397
	return yourls_apply_filter( 'is_upgrading', $upgrading );
1398
}
1399
1400
1401
/**
1402
 * Check if YOURLS is installed
1403
 *
1404
 * Checks property $ydb->installed that is created by yourls_get_all_options()
1405
 *
1406
 * See inline comment for updating from 1.3 or prior.
1407
 *
1408
 */
1409
function yourls_is_installed() {
1410
	global $ydb;
1411
	$is_installed = ( property_exists( $ydb, 'installed' ) && $ydb->installed == true );
1412
	return yourls_apply_filter( 'is_installed', $is_installed );
1413
	
1414
	/* Note: this test won't work on YOURLS 1.3 or older (Aug 2009...)
1415
	   Should someone complain that they cannot upgrade directly from
1416
	   1.3 to 1.7: first, laugh at them, then ask them to install 1.6 first.
1417
	*/
1418
}
1419
1420
/**
1421
 * Generate random string of (int)$length length and type $type (see function for details)
1422
 *
1423
 */
1424
function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
1425
	$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...
1426
	$length = intval( $length );
1427
1428
	// define possible characters
1429
	switch ( $type ) {
1430
1431
		// custom char list, or comply to charset as defined in config
1432
		case '0':
1433
			$possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
1434
			break;
1435
	
1436
		// no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
1437
		case '1':
1438
			$possible = "23456789bcdfghjkmnpqrstvwxyz";
1439
			break;
1440
		
1441
		// Same, with lower + upper
1442
		case '2':
1443
			$possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
1444
			break;
1445
		
1446
		// all letters, lowercase
1447
		case '3':
1448
			$possible = "abcdefghijklmnopqrstuvwxyz";
1449
			break;
1450
		
1451
		// all letters, lowercase + uppercase
1452
		case '4':
1453
			$possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1454
			break;
1455
		
1456
		// all digits & letters lowercase 
1457
		case '5':
1458
			$possible = "0123456789abcdefghijklmnopqrstuvwxyz";
1459
			break;
1460
		
1461
		// all digits & letters lowercase + uppercase
1462
		case '6':
1463
			$possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1464
			break;
1465
		
1466
	}
1467
1468
    $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...
1469
	
1470
	return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
1471
}
1472
1473
/**
1474
 * Return salted string
1475
 *
1476
 */
1477
function yourls_salt( $string ) {
1478
	$salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
1479
	return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
1480
}
1481
1482
/**
1483
 * Add a query var to a URL and return URL. Completely stolen from WP.
1484
 * 
1485
 * Works with one of these parameter patterns:
1486
 *     array( 'var' => 'value' )
1487
 *     array( 'var' => 'value' ), $url
1488
 *     'var', 'value'
1489
 *     'var', 'value', $url 
1490
 * If $url omitted, uses $_SERVER['REQUEST_URI']
1491
 *
1492
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1493
 *
1494
 * @since 1.5
1495
 * @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...
1496
 * @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...
1497
 * @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...
1498
 * @return string New URL query string.
1499
 */
1500
function yourls_add_query_arg() {
1501
	$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...
1502
	if ( is_array( func_get_arg(0) ) ) {
1503 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...
1504
			$uri = $_SERVER['REQUEST_URI'];
1505
		else
1506
			$uri = @func_get_arg( 1 );
1507 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...
1508
		if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
1509
			$uri = $_SERVER['REQUEST_URI'];
1510
		else
1511
			$uri = @func_get_arg( 2 );
1512
	}
1513
	
1514
	$uri = str_replace( '&amp;', '&', $uri );
1515
1516
	
1517
	if ( $frag = strstr( $uri, '#' ) )
1518
		$uri = substr( $uri, 0, -strlen( $frag ) );
1519
	else
1520
		$frag = '';
1521
1522
	if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
1523
		$protocol = $matches[0];
1524
		$uri = substr( $uri, strlen( $protocol ) );
1525
	} else {
1526
		$protocol = '';
1527
	}
1528
1529
	if ( strpos( $uri, '?' ) !== false ) {
1530
		$parts = explode( '?', $uri, 2 );
1531
		if ( 1 == count( $parts ) ) {
1532
			$base = '?';
1533
			$query = $parts[0];
1534
		} else {
1535
			$base = $parts[0] . '?';
1536
			$query = $parts[1];
1537
		}
1538
	} elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
1539
		$base = $uri . '?';
1540
		$query = '';
1541
	} else {
1542
		$base = '';
1543
		$query = $uri;
1544
	}
1545
1546
	parse_str( $query, $qs );
1547
	$qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1548
	if ( is_array( func_get_arg( 0 ) ) ) {
1549
		$kayvees = func_get_arg( 0 );
1550
		$qs = array_merge( $qs, $kayvees );
1551
	} else {
1552
		$qs[func_get_arg( 0 )] = func_get_arg( 1 );
1553
	}
1554
1555
	foreach ( (array) $qs as $k => $v ) {
1556
		if ( $v === false )
1557
			unset( $qs[$k] );
1558
	}
1559
1560
	$ret = http_build_query( $qs );
1561
	$ret = trim( $ret, '?' );
1562
	$ret = preg_replace( '#=(&|$)#', '$1', $ret );
1563
	$ret = $protocol . $base . $ret . $frag;
1564
	$ret = rtrim( $ret, '?' );
1565
	return $ret;
1566
}
1567
1568
/**
1569
 * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
1570
 *
1571
 */
1572
function yourls_urlencode_deep( $value ) {
1573
	$value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
1574
	return $value;
1575
}
1576
1577
/**
1578
 * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
1579
 *
1580
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1581
 *
1582
 * @since 1.5
1583
 * @param string|array $key   Query key or keys to remove.
1584
 * @param bool|string  $query Optional. When false uses the $_SERVER value. Default false.
1585
 * @return string New URL query string.
1586
 */
1587
function yourls_remove_query_arg( $key, $query = false ) {
1588
	if ( is_array( $key ) ) { // removing multiple keys
1589
		foreach ( $key as $k )
1590
			$query = yourls_add_query_arg( $k, false, $query );
1591
		return $query;
1592
	}
1593
	return yourls_add_query_arg( $key, false, $query );
1594
}
1595
1596
/**
1597
 * Return a time-dependent string for nonce creation
1598
 *
1599
 */
1600
function yourls_tick() {
1601
	return ceil( time() / YOURLS_NONCE_LIFE );
1602
}
1603
1604
/**
1605
 * Create a time limited, action limited and user limited token
1606
 *
1607
 */
1608
function yourls_create_nonce( $action, $user = false ) {
1609
	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...
1610
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1611
	$tick = yourls_tick();
1612
	return substr( yourls_salt($tick . $action . $user), 0, 10 );
1613
}
1614
1615
/**
1616
 * Create a nonce field for inclusion into a form
1617
 *
1618
 */
1619
function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
1620
	$field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
1621
	if( $echo )
1622
		echo $field."\n";
1623
	return $field;
1624
}
1625
1626
/**
1627
 * Add a nonce to a URL. If URL omitted, adds nonce to current URL
1628
 *
1629
 */
1630
function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
1631
	$nonce = yourls_create_nonce( $action, $user );
1632
	return yourls_add_query_arg( $name, $nonce, $url );
1633
}
1634
1635
/**
1636
 * Check validity of a nonce (ie time span, user and action match).
1637
 * 
1638
 * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
1639
 * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
1640
 *
1641
 */
1642
function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
1643
	// get user
1644
	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...
1645
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1646
		
1647
	// get current nonce value
1648
	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...
1649
		$nonce = $_REQUEST['nonce'];
1650
1651
	// what nonce should be
1652
	$valid = yourls_create_nonce( $action, $user );
1653
	
1654
	if( $nonce == $valid ) {
1655
		return true;
1656
	} else {
1657
		if( $return )
1658
			die( $return );
1659
		yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
1660
	}
1661
}
1662
1663
/**
1664
 * Converts keyword into short link (prepend with YOURLS base URL)
1665
 *
1666
 */
1667
function yourls_link( $keyword = '' ) {
1668
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
1669
	return yourls_apply_filter( 'yourls_link', $link, $keyword );
1670
}
1671
1672
/**
1673
 * Converts keyword into stat link (prepend with YOURLS base URL, append +)
1674
 *
1675
 */
1676
function yourls_statlink( $keyword = '' ) {
1677
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
1678
	if( yourls_is_ssl() )
1679
        $link = yourls_set_url_scheme( $link, 'https' );
1680
	return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
1681
}
1682
1683
/**
1684
 * Check if we're in API mode. Returns bool
1685
 *
1686
 */
1687
function yourls_is_API() {
1688
    $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...
1689
    return yourls_apply_filter( 'is_API', $return );
1690
}
1691
1692
/**
1693
 * Check if we're in Ajax mode. Returns bool
1694
 *
1695
 */
1696
function yourls_is_Ajax() {
1697
    $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...
1698
    return yourls_apply_filter( 'is_Ajax', $return );
1699
}
1700
1701
/**
1702
 * Check if we're in GO mode (yourls-go.php). Returns bool
1703
 *
1704
 */
1705
function yourls_is_GO() {
1706
    $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...
1707
    return yourls_apply_filter( 'is_GO', $return );
1708
}
1709
1710
/**
1711
 * Check if we're displaying stats infos (yourls-infos.php). Returns bool
1712
 *
1713
 */
1714
function yourls_is_infos() {
1715
    $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...
1716
    return yourls_apply_filter( 'is_infos', $return );
1717
}
1718
1719
/**
1720
 * Check if we're in the admin area. Returns bool
1721
 *
1722
 */
1723
function yourls_is_admin() {
1724
    $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...
1725
    return yourls_apply_filter( 'is_admin', $return );
1726
}
1727
1728
/**
1729
 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
1730
 *
1731
 */
1732
function yourls_is_windows() {
1733
	return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
1734
}
1735
1736
/**
1737
 * Check if SSL is required. Returns bool.
1738
 *
1739
 */
1740
function yourls_needs_ssl() {
1741
    $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...
1742
    return yourls_apply_filter( 'needs_ssl', $return );
1743
}
1744
1745
/**
1746
 * Return admin link, with SSL preference if applicable.
1747
 *
1748
 */
1749
function yourls_admin_url( $page = '' ) {
1750
	$admin = YOURLS_SITE . '/admin/' . $page;
1751
	if( yourls_is_ssl() or yourls_needs_ssl() ) {
1752
        $admin = yourls_set_url_scheme( $admin, 'https' );
1753
    }
1754
	return yourls_apply_filter( 'admin_url', $admin, $page );
1755
}
1756
1757
/**
1758
 * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
1759
 *
1760
 */
1761
function yourls_site_url( $echo = true, $url = '' ) {
1762
	$url = yourls_get_relative_url( $url );
1763
	$url = trim( YOURLS_SITE . '/' . $url, '/' );
1764
	
1765
	// Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
1766
	if( yourls_is_ssl() ) {
1767
		$url = yourls_set_url_scheme( $url, 'https' );
1768
    }
1769
	$url = yourls_apply_filter( 'site_url', $url );
1770
	if( $echo ) {
1771
		echo $url;
1772
    }
1773
	return $url;
1774
}
1775
1776
/**
1777
 * Check if SSL is used, returns bool. Stolen from WP.
1778
 *
1779
 */
1780
function yourls_is_ssl() {
1781
	$is_ssl = false;
1782
	if ( isset( $_SERVER['HTTPS'] ) ) {
1783
		if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
1784
			$is_ssl = true;
1785
		if ( '1' == $_SERVER['HTTPS'] )
1786
			$is_ssl = true;
1787
	} elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
1788
		$is_ssl = true;
1789
	}
1790
	return yourls_apply_filter( 'is_ssl', $is_ssl );
1791
}
1792
1793
/**
1794
 * Get a remote page title
1795
 *
1796
 * This function returns a string: either the page title as defined in HTML, or the URL if not found
1797
 * The function tries to convert funky characters found in titles to UTF8, from the detected charset.
1798
 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response.
1799
 *
1800
 * @param string $url URL
1801
 * @return string Title (sanitized) or the URL if no title found
1802
 */
1803
function yourls_get_remote_title( $url ) {
1804
	// Allow plugins to short-circuit the whole function
1805
	$pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
1806
	if ( false !== $pre )
1807
		return $pre;
1808
1809
	$url = yourls_sanitize_url( $url );
1810
	
1811
	// Only deal with http(s):// 
1812
	if( !in_array( yourls_get_protocol( $url ), array( 'http://', 'https://' ) ) )
1813
		return $url;	
1814
1815
	$title = $charset = false;
1816
    
1817
    $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag
1818
	
1819
	$response = yourls_http_get( $url, array(), array(), array( 'max_bytes' => $max_bytes ) ); // can be a Request object or an error string
1820
	if( is_string( $response ) ) {
1821
		return $url;
1822
	}
1823
	
1824
	// Page content. No content? Return the URL
1825
	$content = $response->body;
1826
	if( !$content )
1827
		return $url;
1828
	
1829
	// look for <title>. No title found? Return the URL
1830
	if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
1831
		$title = $found[1];
1832
		unset( $found );
1833
	}
1834
	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...
1835
		return $url;
1836
		
1837
	// Now we have a title. We'll try to get proper utf8 from it.
1838
	
1839
	// Get charset as (and if) defined by the HTML meta tag. We should match
1840
	// <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1841
	// or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236
1842
	if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) {
1843
		$charset = $found[1];
1844
		unset( $found );
1845
	} else {
1846
		// No charset found in HTML. Get charset as (and if) defined by the server response
1847
		$_charset = current( $response->headers->getValues( 'content-type' ) );
1848
		if( preg_match( '/charset=(\S+)/', $_charset, $found ) ) {
1849
			$charset = trim( $found[1], ';' );
1850
			unset( $found );
1851
		}
1852
	}
1853
1854
	// Conversion to utf-8 if what we have is not utf8 already
1855
	if( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) {
1856
		// We use @ to remove warnings because mb_ functions are easily bitching about illegal chars
1857
		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...
1858
			$title = @mb_convert_encoding( $title, 'UTF-8', $charset );
1859
		} else {
1860
			$title = @mb_convert_encoding( $title, 'UTF-8' );
1861
		}
1862
	}
1863
	
1864
	// Remove HTML entities
1865
	$title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
1866
	
1867
	// Strip out evil things
1868
	$title = yourls_sanitize_title( $title, $url );
1869
		
1870
	return yourls_apply_filter( 'get_remote_title', $title, $url );
1871
}
1872
1873
/**
1874
 * Quick UA check for mobile devices. Return boolean.
1875
 *
1876
 */
1877
function yourls_is_mobile_device() {
1878
	// Strings searched
1879
	$mobiles = array(
1880
		'android', 'blackberry', 'blazer',
1881
		'compal', 'elaine', 'fennec', 'hiptop',
1882
		'iemobile', 'iphone', 'ipod', 'ipad',
1883
		'iris', 'kindle', 'opera mobi', 'opera mini',
1884
		'palm', 'phone', 'pocket', 'psp', 'symbian',
1885
		'treo', 'wap', 'windows ce', 'windows phone'
1886
	);
1887
	
1888
	// Current user-agent
1889
	$current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1890
	
1891
	// Check and return
1892
	$is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
1893
	return yourls_apply_filter( 'is_mobile_device', $is_mobile );
1894
}
1895
1896
/**
1897
 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc')
1898
 *
1899
 */
1900
function yourls_get_request() {
1901
	// Allow plugins to short-circuit the whole function
1902
	$pre = yourls_apply_filter( 'shunt_get_request', false );
1903
	if ( false !== $pre )
1904
		return $pre;
1905
		
1906
	static $request = null;
1907
1908
	yourls_do_action( 'pre_get_request', $request );
1909
	
1910
	if( $request !== null )
1911
		return $request;
1912
	
1913
	// Ignore protocol & www. prefix
1914
	$root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE );
1915
	// Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah
1916
	$request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 );
1917
1918
	// Unless request looks like a full URL (ie request is a simple keyword) strip query string
1919
	if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
1920
		$request = current( explode( '?', $request ) );
1921
	}
1922
	
1923
	return yourls_apply_filter( 'get_request', $request );
1924
}
1925
1926
/**
1927
 * Change protocol to match current scheme used (http or https)
1928
 *
1929
 */
1930
function yourls_match_current_protocol( $url, $normal = 'http://', $ssl = 'https://' ) {
1931
	if( yourls_is_ssl() )
1932
		$url = str_replace( $normal, $ssl, $url );
1933
	return yourls_apply_filter( 'match_current_protocol', $url );
1934
}
1935
1936
/**
1937
 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
1938
 *
1939
 */
1940
function yourls_fix_request_uri() {
1941
1942
	$default_server_values = array(
1943
		'SERVER_SOFTWARE' => '',
1944
		'REQUEST_URI' => '',
1945
	);
1946
	$_SERVER = array_merge( $default_server_values, $_SERVER );
1947
1948
	// Fix for IIS when running with PHP ISAPI
1949
	if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
1950
1951
		// IIS Mod-Rewrite
1952
		if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
1953
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
1954
		}
1955
		// IIS Isapi_Rewrite
1956
		else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
1957
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
1958
		} else {
1959
			// Use ORIG_PATH_INFO if there is no PATH_INFO
1960
			if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
1961
				$_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
1962
1963
			// Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
1964
			if ( isset( $_SERVER['PATH_INFO'] ) ) {
1965
				if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
1966
					$_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
1967
				else
1968
					$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
1969
			}
1970
1971
			// Append the query string if it exists and isn't null
1972
			if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
1973
				$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
1974
			}
1975
		}
1976
	}
1977
}
1978
1979
/**
1980
 * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
1981
 *
1982
 */
1983
function yourls_shutdown() {
1984
	yourls_do_action( 'shutdown' );
1985
}
1986
1987
/**
1988
 * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
1989
 *
1990
 */
1991
function yourls_favicon( $echo = true ) {
1992
	static $favicon = null;
1993
    
1994
	if( $favicon !== null ) {
1995
        if( $echo ) {
1996
            echo $favicon;
1997
        }
1998
        return $favicon;    
1999
    }
2000
	
2001
	$custom = null;
2002
	// search for favicon.(gif|ico|png|jpg|svg)
2003
	foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
2004
		if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
2005
			$custom = 'favicon.' . $ext;
2006
			break;
2007
		}
2008
	}
2009
	
2010
	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...
2011
		$favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
2012
	} else {
2013
		$favicon = yourls_site_url( false ) . '/images/favicon.gif';
2014
	}
2015
2016
	if( $echo ) {
2017
		echo $favicon;
2018
    }
2019
	return $favicon;
2020
}
2021
2022
/**
2023
 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
2024
 *
2025
 */
2026
function yourls_check_maintenance_mode() {
2027
2028
	$file = YOURLS_ABSPATH . '/.maintenance' ;
2029
	if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
2030
		return;
2031
	
2032
	global $maintenance_start;
2033
2034
	include_once( $file );
2035
	// If the $maintenance_start timestamp is older than 10 minutes, don't die.
2036
	if ( ( time() - $maintenance_start ) >= 600 )
2037
		return;
2038
2039
	// Use any /user/maintenance.php file
2040
	if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
2041
		include_once( YOURLS_USERDIR.'/maintenance.php' );
2042
		die();
2043
	}
2044
	
2045
	// https://www.youtube.com/watch?v=Xw-m4jEY-Ns
2046
	$title   = yourls__( 'Service temporarily unavailable' );
2047
	$message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
2048
	yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
2049
	yourls_die( $message, $title , 503 );
2050
2051
}
2052
2053
/**
2054
 * Return current admin page, or null if not an admin page
2055
 *
2056
 * @return mixed string if admin page, null if not an admin page
2057
 * @since 1.6
2058
 */
2059
function yourls_current_admin_page() {
2060
	if( yourls_is_admin() ) {
2061
		$current = substr( yourls_get_request(), 6 );
2062
		if( $current === false ) 
2063
			$current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
2064
			
2065
		return $current;
2066
	}
2067
	return null;
2068
}
2069
2070
/**
2071
 * Check if a URL protocol is allowed
2072
 *
2073
 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
2074
 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
2075
 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
2076
 *
2077
 * @since 1.6
2078
 * @see yourls_get_protocol()
2079
 *
2080
 * @param string $url URL to be check
2081
 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
2082
 * @return boolean true if protocol allowed, false otherwise
2083
 */
2084
function yourls_is_allowed_protocol( $url, $protocols = array() ) {
2085
	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...
2086
		global $yourls_allowedprotocols;
2087
		$protocols = $yourls_allowedprotocols;
2088
	}
2089
	
2090
	$protocol = yourls_get_protocol( $url );
2091
	return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
2092
}
2093
2094
/**
2095
 * Get protocol from a URL (eg mailto:, http:// ...)
2096
 *
2097
 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples:
2098
 * "something://blah" -> "something://"
2099
 * "something:blah"   -> "something:"
2100
 * "something:/blah"  -> "something:"
2101
 *
2102
 * Unit Tests for this function are located in tests/format/urls.php
2103
 *
2104
 * @since 1.6
2105
 *
2106
 * @param string $url URL to be check
2107
 * @return string Protocol, with slash slash if applicable. Empty string if no protocol
2108
 */
2109
function yourls_get_protocol( $url ) {
2110
	preg_match( '!^[a-zA-Z][a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
2111
	/*
2112
	http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
2113
	The scheme name consists of a sequence of characters beginning with a letter and followed by any
2114
	combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
2115
	case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
2116
	with lowercase letters. It is followed by a colon (":").
2117
	*/
2118
	$protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
2119
	return yourls_apply_filter( 'get_protocol', $protocol, $url );
2120
}
2121
2122
/**
2123
 * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
2124
 *
2125
 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
2126
 * or return empty string if $strict is true
2127
 *
2128
 * @since 1.6
2129
 * @param string $url URL to relativize
2130
 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
2131
 * @return string URL 
2132
 */
2133
function yourls_get_relative_url( $url, $strict = true ) {
2134
	$url = yourls_sanitize_url( $url );
2135
	
2136
	// Remove protocols to make it easier
2137
	$noproto_url  = str_replace( 'https:', 'http:', $url );
2138
	$noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
2139
	
2140
	// Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
2141
	$_url = str_replace( $noproto_site . '/', '', $noproto_url );
2142
	if( $_url == $noproto_url )
2143
		$_url = ( $strict ? '' : $url );
2144
2145
	return yourls_apply_filter( 'get_relative_url', $_url, $url );
2146
}
2147
2148
/**
2149
 * Marks a function as deprecated and informs when it has been used. Stolen from WP.
2150
 *
2151
 * There is a hook deprecated_function that will be called that can be used
2152
 * to get the backtrace up to what file and function called the deprecated
2153
 * function.
2154
 *
2155
 * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
2156
 *
2157
 * This function is to be used in every function that is deprecated.
2158
 *
2159
 * @since 1.6
2160
 * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
2161
 *   and the version the function was deprecated in.
2162
 * @uses yourls_apply_filter() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
2163
 *   trigger or false to not trigger error.
2164
 *
2165
 * @param string $function The function that was called
2166
 * @param string $version The version of WordPress that deprecated the function
2167
 * @param string $replacement Optional. The function that should have been called
2168
 */
2169
function yourls_deprecated_function( $function, $version, $replacement = null ) {
2170
2171
	yourls_do_action( 'deprecated_function', $function, $replacement, $version );
2172
2173
	// Allow plugin to filter the output error trigger
2174
	if ( YOURLS_DEBUG && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) {
2175
		if ( ! is_null( $replacement ) )
2176
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
2177
		else
2178
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
2179
	}
2180
}
2181
2182
/**
2183
 * Return the value if not an empty string
2184
 *
2185
 * Used with array_filter(), to remove empty keys but not keys with value 0 or false
2186
 *
2187
 * @since 1.6
2188
 * @param mixed $val Value to test against ''
2189
 * @return bool True if not an empty string
2190
 */
2191
function yourls_return_if_not_empty_string( $val ) {
2192
	return( $val !== '' );
2193
}
2194
2195
/**
2196
 * Returns true.
2197
 *
2198
 * Useful for returning true to filters easily.
2199
 *
2200
 * @since 1.7.1
2201
 * @return bool True.
2202
 */
2203
function yourls_return_true() {
2204
    return true;
2205
}
2206
2207
/**
2208
 * Returns false.
2209
 *
2210
 * Useful for returning false to filters easily.
2211
 *
2212
 * @since 1.7.1
2213
 * @return bool False.
2214
 */
2215
function yourls_return_false() {
2216
    return false;
2217
}
2218
2219
/**
2220
 * Returns 0.
2221
 *
2222
 * Useful for returning 0 to filters easily.
2223
 *
2224
 * @since 1.7.1
2225
 * @return int 0.
2226
 */
2227
function yourls_return_zero() {
2228
    return 0;
2229
}
2230
2231
/**
2232
 * Returns an empty array.
2233
 *
2234
 * Useful for returning an empty array to filters easily.
2235
 *
2236
 * @since 1.7.1
2237
 * @return array Empty array.
2238
 */
2239
function yourls_return_empty_array() {
2240
    return array();
2241
}
2242
2243
/**
2244
 * Returns null.
2245
 *
2246
 * Useful for returning null to filters easily.
2247
 *
2248
 * @since 1.7.1
2249
 * @return null Null value.
2250
 */
2251
function yourls_return_null() {
2252
    return null;
2253
}
2254
2255
/**
2256
 * Returns an empty string.
2257
 *
2258
 * Useful for returning an empty string to filters easily.
2259
 *
2260
 * @since 1.7.1
2261
 * @return string Empty string.
2262
 */
2263
function yourls_return_empty_string() {
2264
    return '';
2265
}
2266
2267
/**
2268
 * Add a message to the debug log
2269
 *
2270
 * When in debug mode ( YOURLS_DEBUG == true ) the debug log is echoed in yourls_html_footer()
2271
 * Log messages are appended to $ydb->debug_log array, which is instanciated within class ezSQLcore_YOURLS
2272
 *
2273
 * @since 1.7
2274
 * @param string $msg Message to add to the debug log
2275
 * @return string The message itself
2276
 */
2277
function yourls_debug_log( $msg ) {
2278
	global $ydb;
2279
	$ydb->debug_log[] = $msg;
2280
	return $msg;
2281
}
2282
2283
/**
2284
 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' )
2285
 *
2286
 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg
2287
 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com,
2288
 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com
2289
 *
2290
 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example
2291
 * if rest = blah.com/file.php?url=http://foo.com
2292
 *
2293
 * Sample returns:
2294
 *
2295
 *   with 'mailto:[email protected]?subject=hey' :
2296
 *   array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' )
2297
 *
2298
 *   with 'http://example.com/blah.html' :
2299
 *   array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' )
2300
 *
2301
 * @since 1.7
2302
 * @param string $url URL to be parsed
2303
 * @param array $array Optional, array of key names to be used in returned array
2304
 * @return mixed false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise
2305
 */
2306
function yourls_get_protocol_slashes_and_rest( $url, $array = array( 'protocol', 'slashes', 'rest' ) ) {
2307
	$proto = yourls_get_protocol( $url );
2308
	
2309
	if( !$proto or count( $array ) != 3 )
2310
		return false;
2311
	
2312
	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...
2313
	
2314
	list( $proto, $slashes ) = explode( ':', $proto );
2315
	
2316
	return array( $array[0] => $proto . ':', $array[1] => $slashes, $array[2] => $rest );
2317
}
2318
2319
/**
2320
 * Set URL scheme (to HTTP or HTTPS)
2321
 *
2322
 * @since 1.7.1
2323
 * @param string $url URL
2324
 * @param string $scheme scheme, either 'http' or 'https'
2325
 * @return string URL with chosen scheme
2326
 */
2327
function yourls_set_url_scheme( $url, $scheme = false ) {
2328
    if( $scheme != 'http' && $scheme != 'https' ) {
2329
        return $url;
2330
    }
2331
    return preg_replace( '!^[a-zA-Z0-9\+\.-]+://!', $scheme . '://', $url );
2332
}
2333
2334