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

includes/functions.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * YOURLS
4
 * Function library
5
 */
6
7
/**
8
 * Determine the allowed character set in short URLs
9
 *
10
 */
11
function yourls_get_shorturl_charset() {
12
	static $charset = null;
13
	if( $charset !== null )
14
		return $charset;
15
16
    if( defined('YOURLS_URL_CONVERT') && in_array( YOURLS_URL_CONVERT, array( 62, 64 ) ) ) {
17
        $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
18
    } else {
19
        // defined to 36, or wrongly defined
20
        $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
21
    }
22
23
	$charset = yourls_apply_filter( 'get_shorturl_charset', $charset );
24
	return $charset;
25
}
26
27
/**
28
 * Make an optimized regexp pattern from a string of characters
29
 *
30
 */
31
function yourls_make_regexp_pattern( $string ) {
32
	$pattern = preg_quote( $string, '@' ); // add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php
33
	// Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc...
34
	return $pattern;
35
}
36
37
/**
38
 * Is a URL a short URL? Accept either 'http://sho.rt/abc' or 'abc'
39
 *
40
 */
41
function yourls_is_shorturl( $shorturl ) {
42
	// TODO: make sure this function evolves with the feature set.
43
44
	$is_short = false;
45
46
	// Is $shorturl a URL (http://sho.rt/abc) or a keyword (abc) ?
47
	if( yourls_get_protocol( $shorturl ) ) {
48
		$keyword = yourls_get_relative_url( $shorturl );
49
	} else {
50
		$keyword = $shorturl;
51
	}
52
53
	// Check if it's a valid && used keyword
54
	if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
55
		$is_short = true;
56
	}
57
58
	return yourls_apply_filter( 'is_shorturl', $is_short, $shorturl );
59
}
60
61
/**
62
 * Check to see if a given keyword is reserved (ie reserved URL or an existing page). Returns bool
63
 *
64
 */
65
function yourls_keyword_is_reserved( $keyword ) {
66
	global $yourls_reserved_URL;
67
	$keyword = yourls_sanitize_keyword( $keyword );
68
	$reserved = false;
69
70
	if ( in_array( $keyword, $yourls_reserved_URL)
71
		or file_exists( YOURLS_ABSPATH ."/pages/$keyword.php" )
72
		or is_dir( YOURLS_ABSPATH ."/$keyword" )
73
	)
74
		$reserved = true;
75
76
	return yourls_apply_filter( 'keyword_is_reserved', $reserved, $keyword );
77
}
78
79
/**
80
 * Function: Get client IP Address. Returns a DB safe string.
81
 *
82
 */
83
function yourls_get_IP() {
84
	$ip = '';
85
86
	// Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
87
	$headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' );
88
	foreach( $headers as $header ) {
89
		if ( !empty( $_SERVER[ $header ] ) ) {
90
			$ip = $_SERVER[ $header ];
91
			break;
92
		}
93
	}
94
95
	// headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
96
	if ( strpos( $ip, ',' ) !== false )
97
		$ip = substr( $ip, 0, strpos( $ip, ',' ) );
98
99
	return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
100
}
101
102
/**
103
 * Get next id a new link will have if no custom keyword provided
104
 *
105
 */
106
function yourls_get_next_decimal() {
107
	return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
108
}
109
110
/**
111
 * Update id for next link with no custom keyword
112
 *
113
 */
114
function yourls_update_next_decimal( $int = '' ) {
115
	$int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
116
	$update = yourls_update_option( 'next_id', $int );
117
	yourls_do_action( 'update_next_decimal', $int, $update );
118
	return $update;
119
}
120
121
/**
122
 * Delete a link in the DB
123
 *
124
 */
125
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';
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 ) ) {
205
		if( yourls_is_shorturl( $url ) ) {
206
			$return['status']    = 'fail';
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 );
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 ) ) {
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) ) {
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 );
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 ) {
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 ) );
347
			$return['status']  = 'success';
348
			$return['message'] = yourls__( 'Link updated in database' );
349
		} else {
350
			$return['status']  = 'fail';
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';
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 ) {
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 ) {
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();
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();
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
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_safe( $_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 ) );
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 );
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...)
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
    // Allow plugins to stick to YOURLS internals but provide another DB
869
    $db = yourls_apply_filter('geo_ip_path_to_db', YOURLS_INC.'/geo/GeoLite2-Country.mmdb');
870
    if (!is_readable($db)) {
871
        return $default;
872
    }
873
874
    $reader = new \GeoIp2\Database\Reader($db);
875
    try {
876
        $record = $reader->country($ip);
877
        $location = $record->country->isoCode; // eg 'US'
878
    } catch (\Exception $e) {
879
        /*
880
        Unused for now, Exception and $e->getMessage() can be one of :
881
882
        - Exception: \GeoIp2\Exception\AddressNotFoundException
883
          When: valid IP not found
884
          Error message: "The address 10.0.0.30 is not in the database"
885
886
        - Exception: \InvalidArgumentException
887
          When: IP is not valid, or DB not readable
888
          Error message: "The value "10.0.0.300" is not a valid IP address", "The file "/path/to/GeoLite2-Country.mmdb" does not exist or is not readable"
889
890
        - Exception: \MaxMind\Db\Reader\InvalidDatabaseException
891
          When: DB is readable but is corrupt or invalid
892
          Error message: "The MaxMind DB file's search tree is corrupt"
893
894
        - or obviously \Exception for any other error (?)
895
        */
896
        $location = $default;
897
    }
898
899
    return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
900
}
901
902
/**
903
 * Converts a 2 letter country code to long name (ie AU -> Australia)
904
 *
905
 * This associative array is the one used by MaxMind internal functions, it may differ from other lists (eg "A1" does not universally stand for "Anon proxy")
906
 *
907
 * @since 1.4
908
 * @param string $code 2 letter country code, eg 'FR'
909
 * @return string Country long name (eg 'France') or an empty string if not found
910
 */
911
function yourls_geo_countrycode_to_countryname( $code ) {
912
	// Allow plugins to short-circuit the function
913
	$country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code );
914
	if ( false !== $country )
915
		return $country;
916
917
    // Weeeeeeeeeeee
918
    $countries = array(
919
        'A1' => 'Anonymous Proxy', 'A2' => 'Satellite Provider', 'AD' => 'Andorra', 'AE' => 'United Arab Emirates', 'AF' => 'Afghanistan',
920
        'AG' => 'Antigua and Barbuda', 'AI' => 'Anguilla', 'AL' => 'Albania', 'AM' => 'Armenia', 'AO' => 'Angola',
921
        'AP' => 'Asia/Pacific Region', 'AQ' => 'Antarctica', 'AR' => 'Argentina', 'AS' => 'American Samoa', 'AT' => 'Austria',
922
        'AU' => 'Australia', 'AW' => 'Aruba', 'AX' => 'Aland Islands', 'AZ' => 'Azerbaijan', 'BA' => 'Bosnia and Herzegovina',
923
        'BB' => 'Barbados', 'BD' => 'Bangladesh', 'BE' => 'Belgium', 'BF' => 'Burkina Faso', 'BG' => 'Bulgaria',
924
        'BH' => 'Bahrain', 'BI' => 'Burundi', 'BJ' => 'Benin', 'BL' => 'Saint Barthelemy', 'BM' => 'Bermuda',
925
        'BN' => 'Brunei Darussalam', 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Saint Eustatius and Saba', 'BR' => 'Brazil', 'BS' => 'Bahamas',
926
        'BT' => 'Bhutan', 'BV' => 'Bouvet Island', 'BW' => 'Botswana', 'BY' => 'Belarus', 'BZ' => 'Belize',
927
        'CA' => 'Canada', 'CC' => 'Cocos (Keeling) Islands', 'CD' => 'Congo, The Democratic Republic of the', 'CF' => 'Central African Republic', 'CG' => 'Congo',
928
        'CH' => 'Switzerland', 'CI' => 'Cote D\'Ivoire', 'CK' => 'Cook Islands', 'CL' => 'Chile', 'CM' => 'Cameroon',
929
        'CN' => 'China', 'CO' => 'Colombia', 'CR' => 'Costa Rica', 'CU' => 'Cuba', 'CV' => 'Cape Verde',
930
        'CW' => 'Curacao', 'CX' => 'Christmas Island', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DE' => 'Germany',
931
        'DJ' => 'Djibouti', 'DK' => 'Denmark', 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'DZ' => 'Algeria',
932
        'EC' => 'Ecuador', 'EE' => 'Estonia', 'EG' => 'Egypt', 'EH' => 'Western Sahara', 'ER' => 'Eritrea',
933
        'ES' => 'Spain', 'ET' => 'Ethiopia', 'EU' => 'Europe', 'FI' => 'Finland', 'FJ' => 'Fiji',
934
        'FK' => 'Falkland Islands (Malvinas)', 'FM' => 'Micronesia, Federated States of', 'FO' => 'Faroe Islands', 'FR' => 'France', 'GA' => 'Gabon',
935
        'GB' => 'United Kingdom', 'GD' => 'Grenada', 'GE' => 'Georgia', 'GF' => 'French Guiana', 'GG' => 'Guernsey',
936
        'GH' => 'Ghana', 'GI' => 'Gibraltar', 'GL' => 'Greenland', 'GM' => 'Gambia', 'GN' => 'Guinea',
937
        'GP' => 'Guadeloupe', 'GQ' => 'Equatorial Guinea', 'GR' => 'Greece', 'GS' => 'South Georgia and the South Sandwich Islands', 'GT' => 'Guatemala',
938
        'GU' => 'Guam', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HK' => 'Hong Kong', 'HM' => 'Heard Island and McDonald Islands',
939
        'HN' => 'Honduras', 'HR' => 'Croatia', 'HT' => 'Haiti', 'HU' => 'Hungary', 'ID' => 'Indonesia',
940
        'IE' => 'Ireland', 'IL' => 'Israel', 'IM' => 'Isle of Man', 'IN' => 'India', 'IO' => 'British Indian Ocean Territory',
941
        'IQ' => 'Iraq', 'IR' => 'Iran, Islamic Republic of', 'IS' => 'Iceland', 'IT' => 'Italy', 'JE' => 'Jersey',
942
        'JM' => 'Jamaica', 'JO' => 'Jordan', 'JP' => 'Japan', 'KE' => 'Kenya', 'KG' => 'Kyrgyzstan',
943
        'KH' => 'Cambodia', 'KI' => 'Kiribati', 'KM' => 'Comoros', 'KN' => 'Saint Kitts and Nevis', 'KP' => 'Korea, Democratic People\'s Republic of',
944
        'KR' => 'Korea, Republic of', 'KW' => 'Kuwait', 'KY' => 'Cayman Islands', 'KZ' => 'Kazakhstan', 'LA' => 'Lao People\'s Democratic Republic',
945
        'LB' => 'Lebanon', 'LC' => 'Saint Lucia', 'LI' => 'Liechtenstein', 'LK' => 'Sri Lanka', 'LR' => 'Liberia',
946
        'LS' => 'Lesotho', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'LV' => 'Latvia', 'LY' => 'Libya',
947
        'MA' => 'Morocco', 'MC' => 'Monaco', 'MD' => 'Moldova, Republic of', 'ME' => 'Montenegro', 'MF' => 'Saint Martin',
948
        'MG' => 'Madagascar', 'MH' => 'Marshall Islands', 'MK' => 'Macedonia', 'ML' => 'Mali', 'MM' => 'Myanmar',
949
        'MN' => 'Mongolia', 'MO' => 'Macau', 'MP' => 'Northern Mariana Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania',
950
        'MS' => 'Montserrat', 'MT' => 'Malta', 'MU' => 'Mauritius', 'MV' => 'Maldives', 'MW' => 'Malawi',
951
        'MX' => 'Mexico', 'MY' => 'Malaysia', 'MZ' => 'Mozambique', 'NA' => 'Namibia', 'NC' => 'New Caledonia',
952
        'NE' => 'Niger', 'NF' => 'Norfolk Island', 'NG' => 'Nigeria', 'NI' => 'Nicaragua', 'NL' => 'Netherlands',
953
        'NO' => 'Norway', 'NP' => 'Nepal', 'NR' => 'Nauru', 'NU' => 'Niue', 'NZ' => 'New Zealand',
954
        'O1' => 'Other', 'OM' => 'Oman', 'PA' => 'Panama', 'PE' => 'Peru', 'PF' => 'French Polynesia',
955
        'PG' => 'Papua New Guinea', 'PH' => 'Philippines', 'PK' => 'Pakistan', 'PL' => 'Poland', 'PM' => 'Saint Pierre and Miquelon',
956
        'PN' => 'Pitcairn Islands', 'PR' => 'Puerto Rico', 'PS' => 'Palestinian Territory', 'PT' => 'Portugal', 'PW' => 'Palau',
957
        'PY' => 'Paraguay', 'QA' => 'Qatar', 'RE' => 'Reunion', 'RO' => 'Romania', 'RS' => 'Serbia',
958
        'RU' => 'Russian Federation', 'RW' => 'Rwanda', 'SA' => 'Saudi Arabia', 'SB' => 'Solomon Islands', 'SC' => 'Seychelles',
959
        'SD' => 'Sudan', 'SE' => 'Sweden', 'SG' => 'Singapore', 'SH' => 'Saint Helena', 'SI' => 'Slovenia',
960
        'SJ' => 'Svalbard and Jan Mayen', 'SK' => 'Slovakia', 'SL' => 'Sierra Leone', 'SM' => 'San Marino', 'SN' => 'Senegal',
961
        'SO' => 'Somalia', 'SR' => 'Suriname', 'SS' => 'South Sudan', 'ST' => 'Sao Tome and Principe', 'SV' => 'El Salvador',
962
        'SX' => 'Sint Maarten (Dutch part)', 'SY' => 'Syrian Arab Republic', 'SZ' => 'Swaziland', 'TC' => 'Turks and Caicos Islands', 'TD' => 'Chad',
963
        'TF' => 'French Southern Territories', 'TG' => 'Togo', 'TH' => 'Thailand', 'TJ' => 'Tajikistan', 'TK' => 'Tokelau',
964
        'TL' => 'Timor-Leste', 'TM' => 'Turkmenistan', 'TN' => 'Tunisia', 'TO' => 'Tonga', 'TR' => 'Turkey',
965
        'TT' => 'Trinidad and Tobago', 'TV' => 'Tuvalu', 'TW' => 'Taiwan', 'TZ' => 'Tanzania, United Republic of', 'UA' => 'Ukraine',
966
        'UG' => 'Uganda', 'UM' => 'United States Minor Outlying Islands', 'US' => 'United States', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan',
967
        'VA' => 'Holy See (Vatican City State)', 'VC' => 'Saint Vincent and the Grenadines', 'VE' => 'Venezuela', 'VG' => 'Virgin Islands, British', 'VI' => 'Virgin Islands, U.S.',
968
        'VN' => 'Vietnam', 'VU' => 'Vanuatu', 'WF' => 'Wallis and Futuna', 'WS' => 'Samoa', 'YE' => 'Yemen',
969
        'YT' => 'Mayotte', 'ZA' => 'South Africa', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
970
    );
971
972
    $code = strtoupper($code);
973
    if(array_key_exists($code, $countries)) {
974
        $name = $countries[$code];
975
    } else {
976
        $name = '';
977
    }
978
979
    return yourls_apply_filter( 'geo_countrycode_to_countryname', $name );
980
}
981
982
/**
983
 * Return flag URL from 2 letter country code
984
 *
985
 */
986
function yourls_geo_get_flag( $code ) {
987
	if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) {
988
		$img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' );
989
	} else {
990
		$img = false;
991
	}
992
	return yourls_apply_filter( 'geo_get_flag', $img, $code );
993
}
994
995
996
/**
997
 * Check if an upgrade is needed
998
 *
999
 */
1000
function yourls_upgrade_is_needed() {
1001
	// check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
1002
	list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
1003
	if( $currentsql < YOURLS_DB_VERSION )
1004
		return true;
1005
1006
	return false;
1007
}
1008
1009
/**
1010
 * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
1011
 *
1012
 */
1013
function yourls_get_current_version_from_sql() {
1014
	$currentver = yourls_get_option( 'version' );
1015
	$currentsql = yourls_get_option( 'db_version' );
1016
1017
	// Values if version is 1.3
1018
	if( !$currentver )
1019
		$currentver = '1.3';
1020
	if( !$currentsql )
1021
		$currentsql = '100';
1022
1023
	return array( $currentver, $currentsql);
1024
}
1025
1026
/**
1027
 * Read an option from DB (or from cache if available). Return value or $default if not found
1028
 *
1029
 * Pretty much stolen from WordPress
1030
 *
1031
 * @since 1.4
1032
 * @param string $option Option name. Expected to not be SQL-escaped.
1033
 * @param mixed $default Optional value to return if option doesn't exist. Default false.
1034
 * @return mixed Value set for the option.
1035
 */
1036
function yourls_get_option( $option_name, $default = false ) {
1037
	global $ydb;
1038
1039
	// Allow plugins to short-circuit options
1040
	$pre = yourls_apply_filter( 'shunt_option_'.$option_name, false );
1041
	if ( false !== $pre )
1042
		return $pre;
1043
1044
	// If option not cached already, get its value from the DB
1045
	if ( !isset( $ydb->option[$option_name] ) ) {
1046
		$table = YOURLS_DB_TABLE_OPTIONS;
1047
		$option_name = yourls_escape( $option_name );
1048
		$row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
1049
		if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
1050
			$value = $row->option_value;
1051
		} else { // option does not exist, so we must cache its non-existence
1052
			$value = $default;
1053
		}
1054
		$ydb->option[ $option_name ] = yourls_maybe_unserialize( $value );
1055
	}
1056
1057
	return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
1058
}
1059
1060
/**
1061
 * Read all options from DB at once
1062
 *
1063
 * The goal is to read all options at once and then populate array $ydb->option, to prevent further
1064
 * SQL queries if we need to read an option value later.
1065
 * It's also a simple check whether YOURLS is installed or not (no option = assuming not installed) after
1066
 * a check for DB server reachability has been performed
1067
 *
1068
 * @since 1.4
1069
 */
1070
function yourls_get_all_options() {
1071
	global $ydb;
1072
1073
	// Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options)
1074
	$pre = yourls_apply_filter( 'shunt_all_options', false );
1075
	if ( false !== $pre )
1076
		return $pre;
1077
1078
	$table = YOURLS_DB_TABLE_OPTIONS;
1079
1080
	$allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" );
1081
1082
	foreach( (array)$allopt as $option ) {
1083
		$ydb->option[ $option->option_name ] = yourls_maybe_unserialize( $option->option_value );
1084
	}
1085
1086
	if( property_exists( $ydb, 'option' ) ) {
1087
		$ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option );
1088
		$ydb->installed = true;
1089
	} else {
1090
		// Zero option found: either YOURLS is not installed or DB server is dead
1091
        if( !yourls_is_db_alive() ) {
1092
            yourls_db_dead(); // YOURLS will die here
1093
        }
1094
        $ydb->installed = false;
1095
	}
1096
}
1097
1098
/**
1099
 * Update (add if doesn't exist) an option to DB
1100
 *
1101
 * Pretty much stolen from WordPress
1102
 *
1103
 * @since 1.4
1104
 * @param string $option Option name. Expected to not be SQL-escaped.
1105
 * @param mixed $newvalue Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1106
 * @return bool False if value was not updated, true otherwise.
1107
 */
1108
function yourls_update_option( $option_name, $newvalue ) {
1109
	global $ydb;
1110
	$table = YOURLS_DB_TABLE_OPTIONS;
1111
1112
	$option_name = trim( $option_name );
1113
	if ( empty( $option_name ) )
1114
		return false;
1115
1116
	// Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
1117
	if ( is_object( $newvalue ) )
1118
		$newvalue = clone $newvalue;
1119
1120
	$option_name = yourls_escape( $option_name );
1121
1122
	$oldvalue = yourls_get_option( $option_name );
1123
1124
	// If the new and old values are the same, no need to update.
1125
	if ( $newvalue === $oldvalue )
1126
		return false;
1127
1128
	if ( false === $oldvalue ) {
1129
		yourls_add_option( $option_name, $newvalue );
1130
		return true;
1131
	}
1132
1133
	$_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
1134
1135
	yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
1136
1137
	$ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" );
1138
1139
	if ( $ydb->rows_affected == 1 ) {
1140
		$ydb->option[ $option_name ] = $newvalue;
1141
		return true;
1142
	}
1143
	return false;
1144
}
1145
1146
/**
1147
 * Add an option to the DB
1148
 *
1149
 * Pretty much stolen from WordPress
1150
 *
1151
 * @since 1.4
1152
 * @param string $option Name of option to add. Expected to not be SQL-escaped.
1153
 * @param mixed $value Optional option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
1154
 * @return bool False if option was not added and true otherwise.
1155
 */
1156
function yourls_add_option( $name, $value = '' ) {
1157
	global $ydb;
1158
	$table = YOURLS_DB_TABLE_OPTIONS;
1159
1160
	$name = trim( $name );
1161
	if ( empty( $name ) )
1162
		return false;
1163
1164
	// Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
1165
	if ( is_object( $value ) )
1166
		$value = clone $value;
1167
1168
	$name = yourls_escape( $name );
1169
1170
	// Make sure the option doesn't already exist
1171
	if ( false !== yourls_get_option( $name ) )
1172
		return false;
1173
1174
	$_value = yourls_escape( yourls_maybe_serialize( $value ) );
1175
1176
	yourls_do_action( 'add_option', $name, $_value );
1177
1178
	$ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
1179
	$ydb->option[ $name ] = $value;
1180
	return true;
1181
}
1182
1183
1184
/**
1185
 * Delete an option from the DB
1186
 *
1187
 * Pretty much stolen from WordPress
1188
 *
1189
 * @since 1.4
1190
 * @param string $option Option name to delete. Expected to not be SQL-escaped.
1191
 * @return bool True, if option is successfully deleted. False on failure.
1192
 */
1193
function yourls_delete_option( $name ) {
1194
	global $ydb;
1195
	$table = YOURLS_DB_TABLE_OPTIONS;
1196
	$name = yourls_escape( $name );
1197
1198
	// Get the ID, if no ID then return
1199
	$option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
1200
	if ( is_null( $option ) || !$option->option_id )
1201
		return false;
1202
1203
	yourls_do_action( 'delete_option', $name );
1204
1205
	$ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
1206
	unset( $ydb->option[ $name ] );
1207
	return true;
1208
}
1209
1210
1211
/**
1212
 * Serialize data if needed. Stolen from WordPress
1213
 *
1214
 * @since 1.4
1215
 * @param mixed $data Data that might be serialized.
1216
 * @return mixed A scalar data
1217
 */
1218
function yourls_maybe_serialize( $data ) {
1219
	if ( is_array( $data ) || is_object( $data ) )
1220
		return serialize( $data );
1221
1222
	if ( yourls_is_serialized( $data, false ) )
1223
		return serialize( $data );
1224
1225
	return $data;
1226
}
1227
1228
/**
1229
 * Check value to find if it was serialized. Stolen from WordPress
1230
 *
1231
 * @since 1.4
1232
 * @param mixed $data Value to check to see if was serialized.
1233
 * @param bool $strict Optional. Whether to be strict about the end of the string. Defaults true.
1234
 * @return bool False if not serialized and true if it was.
1235
 */
1236
function yourls_is_serialized( $data, $strict = true ) {
1237
	// if it isn't a string, it isn't serialized
1238
	if ( ! is_string( $data ) )
1239
		return false;
1240
	$data = trim( $data );
1241
	 if ( 'N;' == $data )
1242
		return true;
1243
	$length = strlen( $data );
1244
	if ( $length < 4 )
1245
		return false;
1246
	if ( ':' !== $data[1] )
1247
		return false;
1248
	if ( $strict ) {
1249
		$lastc = $data[ $length - 1 ];
1250
		if ( ';' !== $lastc && '}' !== $lastc )
1251
			return false;
1252
	} else {
1253
		$semicolon = strpos( $data, ';' );
1254
		$brace	 = strpos( $data, '}' );
1255
		// Either ; or } must exist.
1256
		if ( false === $semicolon && false === $brace )
1257
			return false;
1258
		// But neither must be in the first X characters.
1259
		if ( false !== $semicolon && $semicolon < 3 )
1260
			return false;
1261
		if ( false !== $brace && $brace < 4 )
1262
			return false;
1263
	}
1264
	$token = $data[0];
1265
	switch ( $token ) {
1266
		case 's' :
1267
			if ( $strict ) {
1268
				if ( '"' !== $data[ $length - 2 ] )
1269
					return false;
1270
			} elseif ( false === strpos( $data, '"' ) ) {
1271
				return false;
1272
			}
1273
			// or else fall through
1274
		case 'a' :
1275
		case 'O' :
1276
			return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
1277
		case 'b' :
1278
		case 'i' :
1279
		case 'd' :
1280
			$end = $strict ? '$' : '';
1281
			return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
1282
	}
1283
	return false;
1284
}
1285
1286
/**
1287
 * Unserialize value only if it was serialized. Stolen from WP
1288
 *
1289
 * @since 1.4
1290
 * @param string $original Maybe unserialized original, if is needed.
1291
 * @return mixed Unserialized data can be any type.
1292
 */
1293
function yourls_maybe_unserialize( $original ) {
1294
	if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
1295
		return @unserialize( $original );
1296
	return $original;
1297
}
1298
1299
/**
1300
 * Determine if the current page is private
1301
 *
1302
 */
1303
function yourls_is_private() {
1304
	$private = false;
1305
1306
	if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
1307
1308
		// Allow overruling for particular pages:
1309
1310
		// API
1311
		if( yourls_is_API() ) {
1312
			if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
1313
				$private = true;
1314
1315
		// Infos
1316
		} elseif( yourls_is_infos() ) {
1317
			if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
1318
				$private = true;
1319
1320
		// Others
1321
		} else {
1322
			$private = true;
1323
		}
1324
1325
	}
1326
1327
	return yourls_apply_filter( 'is_private', $private );
1328
}
1329
1330
/**
1331
 * Show login form if required
1332
 *
1333
 */
1334
function yourls_maybe_require_auth() {
1335
	if( yourls_is_private() ) {
1336
		yourls_do_action( 'require_auth' );
1337
		require_once( YOURLS_INC.'/auth.php' );
1338
	} else {
1339
		yourls_do_action( 'require_no_auth' );
1340
	}
1341
}
1342
1343
/**
1344
 * Allow several short URLs for the same long URL ?
1345
 *
1346
 */
1347
function yourls_allow_duplicate_longurls() {
1348
	// special treatment if API to check for WordPress plugin requests
1349
	if( yourls_is_API() ) {
1350
		if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' )
1351
			return false;
1352
	}
1353
	return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
1354
}
1355
1356
/**
1357
 * Return array of keywords that redirect to the submitted long URL
1358
 *
1359
 * @since 1.7
1360
 * @param string $longurl long url
1361
 * @param string $sort Optional ORDER BY order (can be 'keyword', 'title', 'timestamp' or'clicks')
1362
 * @param string $order Optional SORT order (can be 'ASC' or 'DESC')
1363
 * @return array array of keywords
1364
 */
1365
function yourls_get_longurl_keywords( $longurl, $sort = 'none', $order = 'ASC' ) {
1366
	global $ydb;
1367
	$longurl = yourls_escape( yourls_sanitize_url( $longurl ) );
1368
	$table   = YOURLS_DB_TABLE_URL;
1369
	$query   = "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'";
1370
1371
	// Ensure sort is a column in database (@TODO: update verification array if database changes)
1372
	if ( in_array( $sort, array('keyword','title','timestamp','clicks') ) ) {
1373
		$query .= " ORDER BY '".$sort."'";
1374
		if ( in_array( $order, array( 'ASC','DESC' ) ) ) {
1375
			$query .= " ".$order;
1376
		}
1377
	}
1378
	return yourls_apply_filter( 'get_longurl_keywords', $ydb->get_col( $query ), $longurl );
1379
}
1380
1381
/**
1382
 * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
1383
 *
1384
 */
1385
function yourls_check_IP_flood( $ip = '' ) {
1386
1387
	// Allow plugins to short-circuit the whole function
1388
	$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
1389
	if ( false !== $pre )
1390
		return $pre;
1391
1392
	yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
1393
1394
	// Raise white flag if installing or if no flood delay defined
1395
	if(
1396
		( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
1397
		!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
1398
		yourls_is_installing()
1399
	)
1400
		return true;
1401
1402
	// Don't throttle logged in users
1403
	if( yourls_is_private() ) {
1404
		 if( yourls_is_valid_user() === true )
1405
			return true;
1406
	}
1407
1408
	// Don't throttle whitelist IPs
1409
	if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
1410
		$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
1411
		foreach( (array)$whitelist_ips as $whitelist_ip ) {
1412
			$whitelist_ip = trim( $whitelist_ip );
1413
			if ( $whitelist_ip == $ip )
1414
				return true;
1415
		}
1416
	}
1417
1418
	$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
1419
	$ip = yourls_escape( $ip );
1420
1421
	yourls_do_action( 'check_ip_flood', $ip );
1422
1423
	global $ydb;
1424
	$table = YOURLS_DB_TABLE_URL;
1425
1426
	$lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
1427
	if( $lasttime ) {
1428
		$now = date( 'U' );
1429
		$then = date( 'U', strtotime( $lasttime ) );
1430
		if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
1431
			// Flood!
1432
			yourls_do_action( 'ip_flood', $ip, $now - $then );
1433
			yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
1434
		}
1435
	}
1436
1437
	return true;
1438
}
1439
1440
/**
1441
 * Check if YOURLS is installing
1442
 *
1443
 * @return bool
1444
 * @since 1.6
1445
 */
1446
function yourls_is_installing() {
1447
	$installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true;
1448
	return yourls_apply_filter( 'is_installing', $installing );
1449
}
1450
1451
/**
1452
 * Check if YOURLS is upgrading
1453
 *
1454
 * @return bool
1455
 * @since 1.6
1456
 */
1457
function yourls_is_upgrading() {
1458
	$upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true;
1459
	return yourls_apply_filter( 'is_upgrading', $upgrading );
1460
}
1461
1462
1463
/**
1464
 * Check if YOURLS is installed
1465
 *
1466
 * Checks property $ydb->installed that is created by yourls_get_all_options()
1467
 *
1468
 * See inline comment for updating from 1.3 or prior.
1469
 *
1470
 */
1471
function yourls_is_installed() {
1472
	global $ydb;
1473
	$is_installed = ( property_exists( $ydb, 'installed' ) && $ydb->installed == true );
1474
	return yourls_apply_filter( 'is_installed', $is_installed );
1475
1476
	/* Note: this test won't work on YOURLS 1.3 or older (Aug 2009...)
1477
	   Should someone complain that they cannot upgrade directly from
1478
	   1.3 to 1.7: first, laugh at them, then ask them to install 1.6 first.
1479
	*/
1480
}
1481
1482
/**
1483
 * Generate random string of (int)$length length and type $type (see function for details)
1484
 *
1485
 */
1486
function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
1487
	$str = '';
1488
	$length = intval( $length );
1489
1490
	// define possible characters
1491
	switch ( $type ) {
1492
1493
		// custom char list, or comply to charset as defined in config
1494
		case '0':
1495
			$possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
1496
			break;
1497
1498
		// no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
1499
		case '1':
1500
			$possible = "23456789bcdfghjkmnpqrstvwxyz";
1501
			break;
1502
1503
		// Same, with lower + upper
1504
		case '2':
1505
			$possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
1506
			break;
1507
1508
		// all letters, lowercase
1509
		case '3':
1510
			$possible = "abcdefghijklmnopqrstuvwxyz";
1511
			break;
1512
1513
		// all letters, lowercase + uppercase
1514
		case '4':
1515
			$possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1516
			break;
1517
1518
		// all digits & letters lowercase
1519
		case '5':
1520
			$possible = "0123456789abcdefghijklmnopqrstuvwxyz";
1521
			break;
1522
1523
		// all digits & letters lowercase + uppercase
1524
		case '6':
1525
			$possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1526
			break;
1527
1528
	}
1529
1530
    $str = substr( str_shuffle( $possible ), 0, $length );
1531
1532
	return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
1533
}
1534
1535
/**
1536
 * Return salted string
1537
 *
1538
 */
1539
function yourls_salt( $string ) {
1540
	$salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
1541
	return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
1542
}
1543
1544
/**
1545
 * Add a query var to a URL and return URL. Completely stolen from WP.
1546
 *
1547
 * Works with one of these parameter patterns:
1548
 *     array( 'var' => 'value' )
1549
 *     array( 'var' => 'value' ), $url
1550
 *     'var', 'value'
1551
 *     'var', 'value', $url
1552
 * If $url omitted, uses $_SERVER['REQUEST_URI']
1553
 *
1554
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1555
 *
1556
 * @since 1.5
1557
 * @param string|array $param1 Either newkey or an associative_array.
1558
 * @param string       $param2 Either newvalue or oldquery or URI.
1559
 * @param string       $param3 Optional. Old query or URI.
1560
 * @return string New URL query string.
1561
 */
1562
function yourls_add_query_arg() {
1563
	$ret = '';
1564
	if ( is_array( func_get_arg(0) ) ) {
1565 View Code Duplication
		if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
1566
			$uri = $_SERVER['REQUEST_URI'];
1567
		else
1568
			$uri = @func_get_arg( 1 );
1569 View Code Duplication
	} else {
1570
		if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
1571
			$uri = $_SERVER['REQUEST_URI'];
1572
		else
1573
			$uri = @func_get_arg( 2 );
1574
	}
1575
1576
	$uri = str_replace( '&amp;', '&', $uri );
1577
1578
1579
	if ( $frag = strstr( $uri, '#' ) )
1580
		$uri = substr( $uri, 0, -strlen( $frag ) );
1581
	else
1582
		$frag = '';
1583
1584
	if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
1585
		$protocol = $matches[0];
1586
		$uri = substr( $uri, strlen( $protocol ) );
1587
	} else {
1588
		$protocol = '';
1589
	}
1590
1591
	if ( strpos( $uri, '?' ) !== false ) {
1592
		$parts = explode( '?', $uri, 2 );
1593
		if ( 1 == count( $parts ) ) {
1594
			$base = '?';
1595
			$query = $parts[0];
1596
		} else {
1597
			$base = $parts[0] . '?';
1598
			$query = $parts[1];
1599
		}
1600
	} elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
1601
		$base = $uri . '?';
1602
		$query = '';
1603
	} else {
1604
		$base = '';
1605
		$query = $uri;
1606
	}
1607
1608
	parse_str( $query, $qs );
1609
	$qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1610
	if ( is_array( func_get_arg( 0 ) ) ) {
1611
		$kayvees = func_get_arg( 0 );
1612
		$qs = array_merge( $qs, $kayvees );
1613
	} else {
1614
		$qs[func_get_arg( 0 )] = func_get_arg( 1 );
1615
	}
1616
1617
	foreach ( (array) $qs as $k => $v ) {
1618
		if ( $v === false )
1619
			unset( $qs[$k] );
1620
	}
1621
1622
	$ret = http_build_query( $qs );
1623
	$ret = trim( $ret, '?' );
1624
	$ret = preg_replace( '#=(&|$)#', '$1', $ret );
1625
	$ret = $protocol . $base . $ret . $frag;
1626
	$ret = rtrim( $ret, '?' );
1627
	return $ret;
1628
}
1629
1630
/**
1631
 * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
1632
 *
1633
 */
1634
function yourls_urlencode_deep( $value ) {
1635
	$value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
1636
	return $value;
1637
}
1638
1639
/**
1640
 * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
1641
 *
1642
 * The result of this function call is a URL : it should be escaped before being printed as HTML
1643
 *
1644
 * @since 1.5
1645
 * @param string|array $key   Query key or keys to remove.
1646
 * @param bool|string  $query Optional. When false uses the $_SERVER value. Default false.
1647
 * @return string New URL query string.
1648
 */
1649
function yourls_remove_query_arg( $key, $query = false ) {
1650
	if ( is_array( $key ) ) { // removing multiple keys
1651
		foreach ( $key as $k )
1652
			$query = yourls_add_query_arg( $k, false, $query );
1653
		return $query;
1654
	}
1655
	return yourls_add_query_arg( $key, false, $query );
1656
}
1657
1658
/**
1659
 * Return a time-dependent string for nonce creation
1660
 *
1661
 */
1662
function yourls_tick() {
1663
	return ceil( time() / YOURLS_NONCE_LIFE );
1664
}
1665
1666
/**
1667
 * Create a time limited, action limited and user limited token
1668
 *
1669
 */
1670
function yourls_create_nonce( $action, $user = false ) {
1671
	if( false == $user )
1672
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1673
	$tick = yourls_tick();
1674
	return substr( yourls_salt($tick . $action . $user), 0, 10 );
1675
}
1676
1677
/**
1678
 * Create a nonce field for inclusion into a form
1679
 *
1680
 */
1681
function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
1682
	$field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
1683
	if( $echo )
1684
		echo $field."\n";
1685
	return $field;
1686
}
1687
1688
/**
1689
 * Add a nonce to a URL. If URL omitted, adds nonce to current URL
1690
 *
1691
 */
1692
function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
1693
	$nonce = yourls_create_nonce( $action, $user );
1694
	return yourls_add_query_arg( $name, $nonce, $url );
1695
}
1696
1697
/**
1698
 * Check validity of a nonce (ie time span, user and action match).
1699
 *
1700
 * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
1701
 * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
1702
 *
1703
 */
1704
function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
1705
	// get user
1706
	if( false == $user )
1707
		$user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1708
1709
	// get current nonce value
1710
	if( false == $nonce && isset( $_REQUEST['nonce'] ) )
1711
		$nonce = $_REQUEST['nonce'];
1712
1713
	// what nonce should be
1714
	$valid = yourls_create_nonce( $action, $user );
1715
1716
	if( $nonce == $valid ) {
1717
		return true;
1718
	} else {
1719
		if( $return )
1720
			die( $return );
1721
		yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
1722
	}
1723
}
1724
1725
/**
1726
 * Converts keyword into short link (prepend with YOURLS base URL)
1727
 *
1728
 */
1729
function yourls_link( $keyword = '' ) {
1730
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
1731
	return yourls_apply_filter( 'yourls_link', $link, $keyword );
1732
}
1733
1734
/**
1735
 * Converts keyword into stat link (prepend with YOURLS base URL, append +)
1736
 *
1737
 */
1738
function yourls_statlink( $keyword = '' ) {
1739
	$link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
1740
	if( yourls_is_ssl() )
1741
        $link = yourls_set_url_scheme( $link, 'https' );
1742
	return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
1743
}
1744
1745
/**
1746
 * Check if we're in API mode. Returns bool
1747
 *
1748
 */
1749
function yourls_is_API() {
1750
    $return = defined( 'YOURLS_API' ) && YOURLS_API == true;
1751
    return yourls_apply_filter( 'is_API', $return );
1752
}
1753
1754
/**
1755
 * Check if we're in Ajax mode. Returns bool
1756
 *
1757
 */
1758
function yourls_is_Ajax() {
1759
    $return = defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true;
1760
    return yourls_apply_filter( 'is_Ajax', $return );
1761
}
1762
1763
/**
1764
 * Check if we're in GO mode (yourls-go.php). Returns bool
1765
 *
1766
 */
1767
function yourls_is_GO() {
1768
    $return = defined( 'YOURLS_GO' ) && YOURLS_GO == true;
1769
    return yourls_apply_filter( 'is_GO', $return );
1770
}
1771
1772
/**
1773
 * Check if we're displaying stats infos (yourls-infos.php). Returns bool
1774
 *
1775
 */
1776
function yourls_is_infos() {
1777
    $return = defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true;
1778
    return yourls_apply_filter( 'is_infos', $return );
1779
}
1780
1781
/**
1782
 * Check if we're in the admin area. Returns bool
1783
 *
1784
 */
1785
function yourls_is_admin() {
1786
    $return = defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true;
1787
    return yourls_apply_filter( 'is_admin', $return );
1788
}
1789
1790
/**
1791
 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
1792
 *
1793
 */
1794
function yourls_is_windows() {
1795
	return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
1796
}
1797
1798
/**
1799
 * Check if SSL is required. Returns bool.
1800
 *
1801
 */
1802
function yourls_needs_ssl() {
1803
    $return = defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true;
1804
    return yourls_apply_filter( 'needs_ssl', $return );
1805
}
1806
1807
/**
1808
 * Return admin link, with SSL preference if applicable.
1809
 *
1810
 */
1811
function yourls_admin_url( $page = '' ) {
1812
	$admin = YOURLS_SITE . '/admin/' . $page;
1813
	if( yourls_is_ssl() or yourls_needs_ssl() ) {
1814
        $admin = yourls_set_url_scheme( $admin, 'https' );
1815
    }
1816
	return yourls_apply_filter( 'admin_url', $admin, $page );
1817
}
1818
1819
/**
1820
 * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
1821
 *
1822
 */
1823
function yourls_site_url( $echo = true, $url = '' ) {
1824
	$url = yourls_get_relative_url( $url );
1825
	$url = trim( YOURLS_SITE . '/' . $url, '/' );
1826
1827
	// Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
1828
	if( yourls_is_ssl() ) {
1829
		$url = yourls_set_url_scheme( $url, 'https' );
1830
    }
1831
	$url = yourls_apply_filter( 'site_url', $url );
1832
	if( $echo ) {
1833
		echo $url;
1834
    }
1835
	return $url;
1836
}
1837
1838
/**
1839
 * Check if SSL is used, returns bool. Stolen from WP.
1840
 *
1841
 */
1842
function yourls_is_ssl() {
1843
	$is_ssl = false;
1844
	if ( isset( $_SERVER['HTTPS'] ) ) {
1845
		if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
1846
			$is_ssl = true;
1847
		if ( '1' == $_SERVER['HTTPS'] )
1848
			$is_ssl = true;
1849
	} elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
1850
		$is_ssl = true;
1851
	}
1852
	return yourls_apply_filter( 'is_ssl', $is_ssl );
1853
}
1854
1855
/**
1856
 * Get a remote page title
1857
 *
1858
 * This function returns a string: either the page title as defined in HTML, or the URL if not found
1859
 * The function tries to convert funky characters found in titles to UTF8, from the detected charset.
1860
 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response.
1861
 *
1862
 * @param string $url URL
1863
 * @return string Title (sanitized) or the URL if no title found
1864
 */
1865
function yourls_get_remote_title( $url ) {
1866
	// Allow plugins to short-circuit the whole function
1867
	$pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
1868
	if ( false !== $pre )
1869
		return $pre;
1870
1871
	$url = yourls_sanitize_url( $url );
1872
1873
	// Only deal with http(s)://
1874
	if( !in_array( yourls_get_protocol( $url ), array( 'http://', 'https://' ) ) )
1875
		return $url;
1876
1877
	$title = $charset = false;
1878
1879
    $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag
1880
1881
	$response = yourls_http_get( $url, array(), array(), array( 'max_bytes' => $max_bytes ) ); // can be a Request object or an error string
1882
	if( is_string( $response ) ) {
1883
		return $url;
1884
	}
1885
1886
	// Page content. No content? Return the URL
1887
	$content = $response->body;
1888
	if( !$content )
1889
		return $url;
1890
1891
	// look for <title>. No title found? Return the URL
1892
	if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
1893
		$title = $found[1];
1894
		unset( $found );
1895
	}
1896
	if( !$title )
1897
		return $url;
1898
1899
	// Now we have a title. We'll try to get proper utf8 from it.
1900
1901
	// Get charset as (and if) defined by the HTML meta tag. We should match
1902
	// <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1903
	// or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236
1904
	if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) {
1905
		$charset = $found[1];
1906
		unset( $found );
1907
	} else {
1908
		// No charset found in HTML. Get charset as (and if) defined by the server response
1909
		$_charset = current( $response->headers->getValues( 'content-type' ) );
1910
		if( preg_match( '/charset=(\S+)/', $_charset, $found ) ) {
1911
			$charset = trim( $found[1], ';' );
1912
			unset( $found );
1913
		}
1914
	}
1915
1916
	// Conversion to utf-8 if what we have is not utf8 already
1917
	if( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) {
1918
		// We use @ to remove warnings because mb_ functions are easily bitching about illegal chars
1919
		if( $charset ) {
1920
			$title = @mb_convert_encoding( $title, 'UTF-8', $charset );
1921
		} else {
1922
			$title = @mb_convert_encoding( $title, 'UTF-8' );
1923
		}
1924
	}
1925
1926
	// Remove HTML entities
1927
	$title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
1928
1929
	// Strip out evil things
1930
	$title = yourls_sanitize_title( $title, $url );
1931
1932
	return yourls_apply_filter( 'get_remote_title', $title, $url );
1933
}
1934
1935
/**
1936
 * Quick UA check for mobile devices. Return boolean.
1937
 *
1938
 */
1939
function yourls_is_mobile_device() {
1940
	// Strings searched
1941
	$mobiles = array(
1942
		'android', 'blackberry', 'blazer',
1943
		'compal', 'elaine', 'fennec', 'hiptop',
1944
		'iemobile', 'iphone', 'ipod', 'ipad',
1945
		'iris', 'kindle', 'opera mobi', 'opera mini',
1946
		'palm', 'phone', 'pocket', 'psp', 'symbian',
1947
		'treo', 'wap', 'windows ce', 'windows phone'
1948
	);
1949
1950
	// Current user-agent
1951
	$current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1952
1953
	// Check and return
1954
	$is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
1955
	return yourls_apply_filter( 'is_mobile_device', $is_mobile );
1956
}
1957
1958
/**
1959
 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc')
1960
 *
1961
 * With no parameter passed, this function will guess current page and consider
1962
 * it is the current page requested.
1963
 * For testing purposes, parameters can be passed.
1964
 *
1965
 * @since 1.5
1966
 * @param string $yourls_site   Optional, YOURLS installation URL (default to constant YOURLS_SITE)
1967
 * @param string $uri           Optional, page requested (default to $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] eg 'sho.rt/yourls/abcd' )
1968
 * @return string               request relative to YOURLS base (eg 'abdc')
1969
 */
1970
function yourls_get_request($yourls_site = false, $uri = false) {
1971
	// Allow plugins to short-circuit the whole function
1972
	$pre = yourls_apply_filter( 'shunt_get_request', false );
1973
	if ( false !== $pre )
1974
		return $pre;
1975
1976
	yourls_do_action( 'pre_get_request', $yourls_site, $uri );
1977
1978
    // Default values
1979
    if (false === $yourls_site) {
1980
        $yourls_site = YOURLS_SITE;
1981
    }
1982
    if (false === $uri) {
1983
        $uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
1984
    }
1985
1986
    // Even though the config sample states YOURLS_SITE should be set without trailing slash...
1987
    $yourls_site = rtrim($yourls_site,'/');
1988
1989
    // Ignore protocol & www. prefix
1990
	$root = str_replace( array( 'https://www.', 'http://www.', 'https://', 'http://'  ), '', $yourls_site );
1991
	// Case insensitive comparison of the YOURLS root with the requested URL, to match http://Sho.rt/blah, http://sho.rt/blah, http://www.Sho.rt/blah ...
1992
	$request = preg_replace( "!(?:www\.)?$root/!i", '', $uri, 1 );
1993
1994
	// Unless request looks like a full URL (ie request is a simple keyword) strip query string
1995
	if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
1996
		$request = current( explode( '?', $request ) );
1997
	}
1998
1999
	return yourls_apply_filter( 'get_request', $request );
2000
}
2001
2002
/**
2003
 * Change protocol to match current scheme used (http or https)
2004
 *
2005
 */
2006
function yourls_match_current_protocol( $url, $normal = 'http://', $ssl = 'https://' ) {
2007
	if( yourls_is_ssl() )
2008
		$url = str_replace( $normal, $ssl, $url );
2009
	return yourls_apply_filter( 'match_current_protocol', $url );
2010
}
2011
2012
/**
2013
 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
2014
 *
2015
 */
2016
function yourls_fix_request_uri() {
2017
2018
	$default_server_values = array(
2019
		'SERVER_SOFTWARE' => '',
2020
		'REQUEST_URI' => '',
2021
	);
2022
	$_SERVER = array_merge( $default_server_values, $_SERVER );
2023
2024
	// Fix for IIS when running with PHP ISAPI
2025
	if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
2026
2027
		// IIS Mod-Rewrite
2028
		if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
2029
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
2030
		}
2031
		// IIS Isapi_Rewrite
2032
		else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
2033
			$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
2034
		} else {
2035
			// Use ORIG_PATH_INFO if there is no PATH_INFO
2036
			if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
2037
				$_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
2038
2039
			// Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
2040
			if ( isset( $_SERVER['PATH_INFO'] ) ) {
2041
				if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
2042
					$_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
2043
				else
2044
					$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
2045
			}
2046
2047
			// Append the query string if it exists and isn't null
2048
			if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
2049
				$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
2050
			}
2051
		}
2052
	}
2053
}
2054
2055
/**
2056
 * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
2057
 *
2058
 */
2059
function yourls_shutdown() {
2060
	yourls_do_action( 'shutdown' );
2061
}
2062
2063
/**
2064
 * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
2065
 *
2066
 */
2067
function yourls_favicon( $echo = true ) {
2068
	static $favicon = null;
2069
2070
	if( $favicon !== null ) {
2071
        if( $echo ) {
2072
            echo $favicon;
2073
        }
2074
        return $favicon;
2075
    }
2076
2077
	$custom = null;
2078
	// search for favicon.(gif|ico|png|jpg|svg)
2079
	foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
2080
		if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
2081
			$custom = 'favicon.' . $ext;
2082
			break;
2083
		}
2084
	}
2085
2086
	if( $custom ) {
2087
		$favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
2088
	} else {
2089
		$favicon = yourls_site_url( false ) . '/images/favicon.gif';
2090
	}
2091
2092
	if( $echo ) {
2093
		echo $favicon;
2094
    }
2095
	return $favicon;
2096
}
2097
2098
/**
2099
 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
2100
 *
2101
 */
2102
function yourls_check_maintenance_mode() {
2103
2104
	$file = YOURLS_ABSPATH . '/.maintenance' ;
2105
	if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
2106
		return;
2107
2108
	global $maintenance_start;
2109
2110
	include_once( $file );
2111
	// If the $maintenance_start timestamp is older than 10 minutes, don't die.
2112
	if ( ( time() - $maintenance_start ) >= 600 )
2113
		return;
2114
2115
	// Use any /user/maintenance.php file
2116
	if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
2117
		include_once( YOURLS_USERDIR.'/maintenance.php' );
2118
		die();
2119
	}
2120
2121
	// https://www.youtube.com/watch?v=Xw-m4jEY-Ns
2122
	$title   = yourls__( 'Service temporarily unavailable' );
2123
	$message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
2124
	yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
2125
	yourls_die( $message, $title , 503 );
2126
2127
}
2128
2129
/**
2130
 * Return current admin page, or null if not an admin page
2131
 *
2132
 * @return mixed string if admin page, null if not an admin page
2133
 * @since 1.6
2134
 */
2135
function yourls_current_admin_page() {
2136
	if( yourls_is_admin() ) {
2137
		$current = substr( yourls_get_request(), 6 );
2138
		if( $current === false )
2139
			$current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
2140
2141
		return $current;
2142
	}
2143
	return null;
2144
}
2145
2146
/**
2147
 * Check if a URL protocol is allowed
2148
 *
2149
 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
2150
 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
2151
 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
2152
 *
2153
 * @since 1.6
2154
 * @see yourls_get_protocol()
2155
 *
2156
 * @param string $url URL to be check
2157
 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
2158
 * @return boolean true if protocol allowed, false otherwise
2159
 */
2160
function yourls_is_allowed_protocol( $url, $protocols = array() ) {
2161
	if( ! $protocols ) {
2162
		global $yourls_allowedprotocols;
2163
		$protocols = $yourls_allowedprotocols;
2164
	}
2165
2166
	$protocol = yourls_get_protocol( $url );
2167
	return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
2168
}
2169
2170
/**
2171
 * Get protocol from a URL (eg mailto:, http:// ...)
2172
 *
2173
 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples:
2174
 * "something://blah" -> "something://"
2175
 * "something:blah"   -> "something:"
2176
 * "something:/blah"  -> "something:"
2177
 *
2178
 * Unit Tests for this function are located in tests/format/urls.php
2179
 *
2180
 * @since 1.6
2181
 *
2182
 * @param string $url URL to be check
2183
 * @return string Protocol, with slash slash if applicable. Empty string if no protocol
2184
 */
2185
function yourls_get_protocol( $url ) {
2186
	preg_match( '!^[a-zA-Z][a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
2187
	/*
2188
	http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
2189
	The scheme name consists of a sequence of characters beginning with a letter and followed by any
2190
	combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
2191
	case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
2192
	with lowercase letters. It is followed by a colon (":").
2193
	*/
2194
	$protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
2195
	return yourls_apply_filter( 'get_protocol', $protocol, $url );
2196
}
2197
2198
/**
2199
 * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
2200
 *
2201
 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
2202
 * or return empty string if $strict is true
2203
 *
2204
 * @since 1.6
2205
 * @param string $url URL to relativize
2206
 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
2207
 * @return string URL
2208
 */
2209
function yourls_get_relative_url( $url, $strict = true ) {
2210
	$url = yourls_sanitize_url( $url );
2211
2212
	// Remove protocols to make it easier
2213
	$noproto_url  = str_replace( 'https:', 'http:', $url );
2214
	$noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
2215
2216
	// Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
2217
	$_url = str_replace( $noproto_site . '/', '', $noproto_url );
2218
	if( $_url == $noproto_url )
2219
		$_url = ( $strict ? '' : $url );
2220
2221
	return yourls_apply_filter( 'get_relative_url', $_url, $url );
2222
}
2223
2224
/**
2225
 * Marks a function as deprecated and informs when it has been used. Stolen from WP.
2226
 *
2227
 * There is a hook deprecated_function that will be called that can be used
2228
 * to get the backtrace up to what file and function called the deprecated
2229
 * function.
2230
 *
2231
 * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
2232
 *
2233
 * This function is to be used in every function that is deprecated.
2234
 *
2235
 * @since 1.6
2236
 * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
2237
 *   and the version the function was deprecated in.
2238
 * @uses yourls_apply_filter() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
2239
 *   trigger or false to not trigger error.
2240
 *
2241
 * @param string $function The function that was called
2242
 * @param string $version The version of WordPress that deprecated the function
2243
 * @param string $replacement Optional. The function that should have been called
2244
 */
2245
function yourls_deprecated_function( $function, $version, $replacement = null ) {
2246
2247
	yourls_do_action( 'deprecated_function', $function, $replacement, $version );
2248
2249
	// Allow plugin to filter the output error trigger
2250
	if ( YOURLS_DEBUG && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) {
2251
		if ( ! is_null( $replacement ) )
2252
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
2253
		else
2254
			trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
2255
	}
2256
}
2257
2258
/**
2259
 * Return the value if not an empty string
2260
 *
2261
 * Used with array_filter(), to remove empty keys but not keys with value 0 or false
2262
 *
2263
 * @since 1.6
2264
 * @param mixed $val Value to test against ''
2265
 * @return bool True if not an empty string
2266
 */
2267
function yourls_return_if_not_empty_string( $val ) {
2268
	return( $val !== '' );
2269
}
2270
2271
/**
2272
 * Returns true.
2273
 *
2274
 * Useful for returning true to filters easily.
2275
 *
2276
 * @since 1.7.1
2277
 * @return bool True.
2278
 */
2279
function yourls_return_true() {
2280
    return true;
2281
}
2282
2283
/**
2284
 * Returns false.
2285
 *
2286
 * Useful for returning false to filters easily.
2287
 *
2288
 * @since 1.7.1
2289
 * @return bool False.
2290
 */
2291
function yourls_return_false() {
2292
    return false;
2293
}
2294
2295
/**
2296
 * Returns 0.
2297
 *
2298
 * Useful for returning 0 to filters easily.
2299
 *
2300
 * @since 1.7.1
2301
 * @return int 0.
2302
 */
2303
function yourls_return_zero() {
2304
    return 0;
2305
}
2306
2307
/**
2308
 * Returns an empty array.
2309
 *
2310
 * Useful for returning an empty array to filters easily.
2311
 *
2312
 * @since 1.7.1
2313
 * @return array Empty array.
2314
 */
2315
function yourls_return_empty_array() {
2316
    return array();
2317
}
2318
2319
/**
2320
 * Returns null.
2321
 *
2322
 * Useful for returning null to filters easily.
2323
 *
2324
 * @since 1.7.1
2325
 * @return null Null value.
2326
 */
2327
function yourls_return_null() {
2328
    return null;
2329
}
2330
2331
/**
2332
 * Returns an empty string.
2333
 *
2334
 * Useful for returning an empty string to filters easily.
2335
 *
2336
 * @since 1.7.1
2337
 * @return string Empty string.
2338
 */
2339
function yourls_return_empty_string() {
2340
    return '';
2341
}
2342
2343
/**
2344
 * Add a message to the debug log
2345
 *
2346
 * When in debug mode ( YOURLS_DEBUG == true ) the debug log is echoed in yourls_html_footer()
2347
 * Log messages are appended to $ydb->debug_log array, which is instanciated within class ezSQLcore_YOURLS
2348
 *
2349
 * @since 1.7
2350
 * @param string $msg Message to add to the debug log
2351
 * @return string The message itself
2352
 */
2353
function yourls_debug_log( $msg ) {
2354
	global $ydb;
2355
	$ydb->debug_log[] = $msg;
2356
	return $msg;
2357
}
2358
2359
/**
2360
 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' )
2361
 *
2362
 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg
2363
 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com,
2364
 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com
2365
 *
2366
 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example
2367
 * if rest = blah.com/file.php?url=http://foo.com
2368
 *
2369
 * Sample returns:
2370
 *
2371
 *   with 'mailto:[email protected]?subject=hey' :
2372
 *   array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' )
2373
 *
2374
 *   with 'http://example.com/blah.html' :
2375
 *   array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' )
2376
 *
2377
 * @since 1.7
2378
 * @param string $url URL to be parsed
2379
 * @param array $array Optional, array of key names to be used in returned array
2380
 * @return mixed false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise
2381
 */
2382
function yourls_get_protocol_slashes_and_rest( $url, $array = array( 'protocol', 'slashes', 'rest' ) ) {
2383
	$proto = yourls_get_protocol( $url );
2384
2385
	if( !$proto or count( $array ) != 3 )
2386
		return false;
2387
2388
	list( $null, $rest ) = explode( $proto, $url, 2 );
2389
2390
	list( $proto, $slashes ) = explode( ':', $proto );
2391
2392
	return array( $array[0] => $proto . ':', $array[1] => $slashes, $array[2] => $rest );
2393
}
2394
2395
/**
2396
 * Set URL scheme (to HTTP or HTTPS)
2397
 *
2398
 * @since 1.7.1
2399
 * @param string $url URL
2400
 * @param string $scheme scheme, either 'http' or 'https'
2401
 * @return string URL with chosen scheme
2402
 */
2403
function yourls_set_url_scheme( $url, $scheme = false ) {
2404
    if( $scheme != 'http' && $scheme != 'https' ) {
2405
        return $url;
2406
    }
2407
    return preg_replace( '!^[a-zA-Z0-9\+\.-]+://!', $scheme . '://', $url );
2408
}
2409
2410
/**
2411
 * Tell if there is a new YOURLS version
2412
 *
2413
 * This function checks, if needed, if there's a new version of YOURLS and, if applicable, display
2414
 * an update notice.
2415
 *
2416
 * @since 1.7.3
2417
 */
2418
function yourls_tell_if_new_version() {
2419
    $check = yourls_maybe_check_core_version();
2420
    yourls_debug_log( 'Check for new version: ' . ($check ? 'yes' : 'no') );
2421
    yourls_new_core_version_notice();
2422
}
2423