]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/functions.php
Merge pull request #1380 from ozh/typos
[Github/YOURLS.git] / includes / functions.php
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') ) {
17                 $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
18         } else {
19                 switch( YOURLS_URL_CONVERT ) {
20                         case 36:
21                                 $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
22                                 break;
23                         case 62:
24                         case 64: // just because some people get this wrong in their config.php
25                                 $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
26                                 break;
27                 }
28         }
29         
30         $charset = yourls_apply_filter( 'get_shorturl_charset', $charset );
31         return $charset;
32 }
33  
34 /**
35  * Make an optimized regexp pattern from a string of characters
36  * 
37  */
38 function yourls_make_regexp_pattern( $string ) {
39         $pattern = preg_quote( $string, '-' ); // add - as an escaped characters -- this is fixed in PHP 5.3
40         // TODO: replace char sequences by smart sequences such as 0-9, a-z, A-Z ... ?
41         return $pattern;
42 }
43
44 /**
45  * Is a URL a short URL? Accept either 'http://sho.rt/abc' or 'abc'
46  * 
47  */
48 function yourls_is_shorturl( $shorturl ) {
49         // TODO: make sure this function evolves with the feature set.
50         
51         $is_short = false;
52         
53         // Is $shorturl a URL (http://sho.rt/abc) or a keyword (abc) ?
54         if( yourls_get_protocol( $shorturl ) ) {
55                 $keyword = yourls_get_relative_url( $shorturl );
56         } else {
57                 $keyword = $shorturl;
58         }
59         
60         // Check if it's a valid && used keyword
61         if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
62                 $is_short = true;
63         }
64         
65         return yourls_apply_filter( 'is_shorturl', $is_short, $shorturl );
66 }
67
68 /**
69  * Check to see if a given keyword is reserved (ie reserved URL or an existing page). Returns bool
70  *
71  */
72 function yourls_keyword_is_reserved( $keyword ) {
73         global $yourls_reserved_URL;
74         $keyword = yourls_sanitize_keyword( $keyword );
75         $reserved = false;
76         
77         if ( in_array( $keyword, $yourls_reserved_URL)
78                 or file_exists( YOURLS_ABSPATH ."/pages/$keyword.php" )
79                 or is_dir( YOURLS_ABSPATH ."/$keyword" )
80         )
81                 $reserved = true;
82         
83         return yourls_apply_filter( 'keyword_is_reserved', $reserved, $keyword );
84 }
85
86 /**
87  * Function: Get client IP Address. Returns a DB safe string.
88  *
89  */
90 function yourls_get_IP() {
91         $ip = '';
92
93         // Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
94         $headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' );
95         foreach( $headers as $header ) {
96                 if ( !empty( $_SERVER[ $header ] ) ) {
97                         $ip = $_SERVER[ $header ];
98                         break;
99                 }
100         }
101         
102         // headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
103         if ( strpos( $ip, ',' ) !== false )
104                 $ip = substr( $ip, 0, strpos( $ip, ',' ) );
105         
106         return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
107 }
108
109 /**
110  * Get next id a new link will have if no custom keyword provided
111  *
112  */
113 function yourls_get_next_decimal() {
114         return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
115 }
116
117 /**
118  * Update id for next link with no custom keyword
119  *
120  */
121 function yourls_update_next_decimal( $int = '' ) {
122         $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
123         $update = yourls_update_option( 'next_id', $int );
124         yourls_do_action( 'update_next_decimal', $int, $update );
125         return $update;
126 }
127
128 /**
129  * Delete a link in the DB
130  *
131  */
132 function yourls_delete_link_by_keyword( $keyword ) {
133         global $ydb;
134
135         $table = YOURLS_DB_TABLE_URL;
136         $keyword = yourls_sanitize_string( $keyword );
137         $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';");
138         yourls_do_action( 'delete_link', $keyword, $delete );
139         return $delete;
140 }
141
142 /**
143  * SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting
144  *
145  */
146 function yourls_insert_link_in_db( $url, $keyword, $title = '' ) {
147         global $ydb;
148         
149         $url     = yourls_escape( yourls_sanitize_url( $url ) );
150         $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
151         $title   = yourls_escape( yourls_sanitize_title( $title ) );
152
153         $table = YOURLS_DB_TABLE_URL;
154         $timestamp = date('Y-m-d H:i:s');
155         $ip = yourls_get_IP();
156         $insert = $ydb->query("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);");
157         
158         yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
159         
160         return (bool)$insert;
161 }
162
163 /**
164  * Check if a URL already exists in the DB. Return NULL (doesn't exist) or an object with URL informations.
165  *
166  */
167 function yourls_url_exists( $url ) {
168         // Allow plugins to short-circuit the whole function
169         $pre = yourls_apply_filter( 'shunt_url_exists', false, $url );
170         if ( false !== $pre )
171                 return $pre;
172
173         global $ydb;
174         $table = YOURLS_DB_TABLE_URL;
175         $strip_url = stripslashes($url);
176         $url_exists = $ydb->get_row("SELECT * FROM `$table` WHERE `url` = '".$strip_url."';");
177         
178         return yourls_apply_filter( 'url_exists', $url_exists, $url );
179 }
180
181 /**
182  * Add a new link in the DB, either with custom keyword, or find one
183  *
184  */
185 function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
186         // Allow plugins to short-circuit the whole function
187         $pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title );
188         if ( false !== $pre )
189                 return $pre;
190                 
191         $url = yourls_encodeURI( $url );
192         $url = yourls_escape( yourls_sanitize_url( $url ) );
193         if ( !$url || $url == 'http://' || $url == 'https://' ) {
194                 $return['status']    = 'fail';
195                 $return['code']      = 'error:nourl';
196                 $return['message']   = yourls__( 'Missing or malformed URL' );
197                 $return['errorCode'] = '400';
198                 return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title );
199         }
200         
201         // Prevent DB flood
202         $ip = yourls_get_IP();
203         yourls_check_IP_flood( $ip );
204         
205         // Prevent internal redirection loops: cannot shorten a shortened URL
206         if( yourls_get_relative_url( $url ) ) {
207                 if( yourls_is_shorturl( $url ) ) {
208                         $return['status']    = 'fail';
209                         $return['code']      = 'error:noloop';
210                         $return['message']   = yourls__( 'URL is a short URL' );
211                         $return['errorCode'] = '400';
212                         return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title );
213                 }
214         }
215
216         yourls_do_action( 'pre_add_new_link', $url, $keyword, $title );
217         
218         $strip_url = stripslashes( $url );
219         $return = array();
220
221         // duplicates allowed or new URL => store it
222         if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) {
223         
224                 if( isset( $title ) && !empty( $title ) ) {
225                         $title = yourls_sanitize_title( $title );
226                 } else {
227                         $title = yourls_get_remote_title( $url );
228                 }
229                 $title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword );
230
231                 // Custom keyword provided
232                 if ( $keyword ) {
233                         
234                         yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title );
235                 
236                         $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
237                         $keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title );
238                         if ( !yourls_keyword_is_free( $keyword ) ) {
239                                 // This shorturl either reserved or taken already
240                                 $return['status']  = 'fail';
241                                 $return['code']    = 'error:keyword';
242                                 $return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword );
243                         } else {
244                                 // all clear, store !
245                                 yourls_insert_link_in_db( $url, $keyword, $title );
246                                 $return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
247                                 $return['status']   = 'success';
248                                 $return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
249                                 $return['title']    = $title;
250                                 $return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
251                                 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
252                         }
253
254                 // Create random keyword        
255                 } else {
256                         
257                         yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title );
258                 
259                         $timestamp = date( 'Y-m-d H:i:s' );
260                         $id = yourls_get_next_decimal();
261                         $ok = false;
262                         do {
263                                 $keyword = yourls_int2string( $id );
264                                 $keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title );
265                                 $free = yourls_keyword_is_free($keyword);
266                                 $add_url = @yourls_insert_link_in_db( $url, $keyword, $title );
267                                 $ok = ($free && $add_url);
268                                 if ( $ok === false && $add_url === 1 ) {
269                                         // we stored something, but shouldn't have (ie reserved id)
270                                         yourls_delete_link_by_keyword( $keyword );
271                                         $return['extra_info'] .= '(deleted '.$keyword.')';
272                                 } else {
273                                         // everything ok, populate needed vars
274                                         $return['url']      = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
275                                         $return['status']   = 'success';
276                                         $return['message']  = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
277                                         $return['title']    = $title;
278                                         $return['html']     = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
279                                         $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
280                                 }
281                                 $id++;
282                         } while ( !$ok );
283                         @yourls_update_next_decimal( $id );
284                 }
285
286         // URL was already stored
287         } else {
288                         
289                 yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title );
290                 
291                 $return['status']   = 'fail';
292                 $return['code']     = 'error:url';
293                 $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 );
294                 $return['message']  = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) );
295                 $return['title']    = $url_exists->title; 
296                 $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
297         }
298         
299         yourls_do_action( 'post_add_new_link', $url, $keyword, $title );
300
301         $return['statusCode'] = 200; // regardless of result, this is still a valid request
302         return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title );
303 }
304
305
306 /**
307  * Edit a link
308  *
309  */
310 function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
311         global $ydb;
312
313         $table = YOURLS_DB_TABLE_URL;
314         $url = yourls_escape (yourls_sanitize_url( $url ) );
315         $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
316         $title = yourls_escape( yourls_sanitize_title( $title ) );
317         $newkeyword = yourls_escape( yourls_sanitize_string( $newkeyword ) );
318         $strip_url = stripslashes( $url );
319         $strip_title = stripslashes( $title );
320         $old_url = $ydb->get_var( "SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';" );
321         
322         // Check if new URL is not here already
323         if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
324                 $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';"));
325         } else {
326                 $new_url_already_there = false;
327         }
328         
329         // Check if the new keyword is not here already
330         if ( $newkeyword != $keyword ) {
331                 $keyword_is_ok = yourls_keyword_is_free( $newkeyword );
332         } else {
333                 $keyword_is_ok = true;
334         }
335         
336         yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
337         
338         // All clear, update
339         if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
340                         $update_url = $ydb->query( "UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';" );
341                 if( $update_url ) {
342                         $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 ) );
343                         $return['status']  = 'success';
344                         $return['message'] = yourls__( 'Link updated in database' );
345                 } else {
346                         $return['status']  = 'fail';
347                         $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 ) ;
348                 }
349         
350         // Nope
351         } else {
352                 $return['status']  = 'fail';
353                 $return['message'] = yourls__( 'URL or keyword already exists in database' );
354         }
355         
356         return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
357 }
358
359 /**
360  * Update a title link (no checks for duplicates etc..)
361  *
362  */
363 function yourls_edit_link_title( $keyword, $title ) {
364         global $ydb;
365         
366         $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
367         $title = yourls_escape( yourls_sanitize_title( $title ) );
368         
369         $table = YOURLS_DB_TABLE_URL;
370         $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';");
371
372         return $update;
373 }
374
375
376 /**
377  * Check if keyword id is free (ie not already taken, and not reserved). Return bool.
378  *
379  */
380 function yourls_keyword_is_free( $keyword ) {
381         $free = true;
382         if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
383                 $free = false;
384                 
385         return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
386 }
387
388 /**
389  * Check if a keyword is taken (ie there is already a short URL with this id). Return bool.             
390  *
391  */
392 function yourls_keyword_is_taken( $keyword ) {
393
394         // Allow plugins to short-circuit the whole function
395         $pre = yourls_apply_filter( 'shunt_keyword_is_taken', false, $keyword );
396         if ( false !== $pre )
397                 return $pre;
398         
399         global $ydb;
400         $keyword = yourls_sanitize_keyword( $keyword );
401         $taken = false;
402         $table = YOURLS_DB_TABLE_URL;
403         $already_exists = $ydb->get_var( "SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';" );
404         if ( $already_exists )
405                 $taken = true;
406
407         return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
408 }
409
410
411 /**
412  * Connect to DB
413  *
414  */
415 function yourls_db_connect() {
416         global $ydb;
417
418         if (   !defined( 'YOURLS_DB_USER' )
419                 or !defined( 'YOURLS_DB_PASS' )
420                 or !defined( 'YOURLS_DB_NAME' )
421                 or !defined( 'YOURLS_DB_HOST' )
422         ) yourls_die ( yourls__( 'Incorrect DB config, or could not connect to DB' ), yourls__( 'Fatal error' ), 503 ); 
423
424         // Are we standalone or in the WordPress environment?
425         if ( class_exists( 'wpdb', false ) ) {
426                 /* TODO: should we deprecate this? Follow WP dev in that area */
427                 $ydb =  new wpdb( YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST );
428         } else {
429                 yourls_set_DB_driver();
430         }
431         
432         if ( $ydb->last_error )
433                 yourls_die( $ydb->last_error, yourls__( 'Fatal error' ), 503 );
434         
435         if ( defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG === true )
436                 $ydb->show_errors = true;
437         
438         return $ydb;
439 }
440
441 /**
442  * Return XML output.
443  *
444  */
445 function yourls_xml_encode( $array ) {
446         require_once( YOURLS_INC.'/functions-xml.php' );
447         $converter= new yourls_array2xml;
448         return $converter->array2xml( $array );
449 }
450
451 /**
452  * 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
453  *
454  */
455 function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
456         global $ydb;
457         $keyword = yourls_sanitize_string( $keyword );
458
459         yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
460
461         if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) {
462                 return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
463         }
464         
465         yourls_do_action( 'get_keyword_not_cached', $keyword );
466         
467         $table = YOURLS_DB_TABLE_URL;
468         $infos = $ydb->get_row( "SELECT * FROM `$table` WHERE `keyword` = '$keyword'" );
469         
470         if( $infos ) {
471                 $infos = (array)$infos;
472                 $ydb->infos[ $keyword ] = $infos;
473         } else {
474                 $ydb->infos[ $keyword ] = false;
475         }
476                 
477         return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
478 }
479
480 /**
481  * Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
482  *
483  */
484 function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
485
486         // Allow plugins to short-circuit the whole function
487         $pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound );
488         if ( false !== $pre )
489                 return $pre;
490
491         $keyword = yourls_sanitize_string( $keyword );
492         $infos = yourls_get_keyword_infos( $keyword );
493         
494         $return = $notfound;
495         if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false )
496                 $return = $infos[ $field ];
497
498         return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound ); 
499 }
500
501 /**
502  * Return title associated with keyword. Optional $notfound = string default message if nothing found
503  *
504  */
505 function yourls_get_keyword_title( $keyword, $notfound = false ) {
506         return yourls_get_keyword_info( $keyword, 'title', $notfound );
507 }
508
509 /**
510  * Return long URL associated with keyword. Optional $notfound = string default message if nothing found
511  *
512  */
513 function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
514         return yourls_get_keyword_info( $keyword, 'url', $notfound );
515 }
516
517 /**
518  * Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
519  *
520  */
521 function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
522         return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
523 }
524
525 /**
526  * Return IP that added a keyword. Optional $notfound = string default message if nothing found
527  *
528  */
529 function yourls_get_keyword_IP( $keyword, $notfound = false ) {
530         return yourls_get_keyword_info( $keyword, 'ip', $notfound );
531 }
532
533 /**
534  * Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
535  *
536  */
537 function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
538         return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
539 }
540
541 /**
542  * Update click count on a short URL. Return 0/1 for error/success.
543  *
544  */
545 function yourls_update_clicks( $keyword, $clicks = false ) {
546         // Allow plugins to short-circuit the whole function
547         $pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
548         if ( false !== $pre )
549                 return $pre;
550
551         global $ydb;
552         $keyword = yourls_sanitize_string( $keyword );
553         $table = YOURLS_DB_TABLE_URL;
554         if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
555                 $update = $ydb->query( "UPDATE `$table` SET `clicks` = $clicks WHERE `keyword` = '$keyword'" );
556         else
557                 $update = $ydb->query( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'" );
558
559         yourls_do_action( 'update_clicks', $keyword, $update, $clicks );
560         return $update;
561 }
562
563 /**
564  * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
565  *
566  */
567 function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) {
568         global $ydb;
569
570         switch( $filter ) {
571                 case 'bottom':
572                         $sort_by    = 'clicks';
573                         $sort_order = 'asc';
574                         break;
575                 case 'last':
576                         $sort_by    = 'timestamp';
577                         $sort_order = 'desc';
578                         break;
579                 case 'rand':
580                 case 'random':
581                         $sort_by    = 'RAND()';
582                         $sort_order = '';
583                         break;
584                 case 'top':
585                 default:
586                         $sort_by    = 'clicks';
587                         $sort_order = 'desc';
588                         break;
589         }
590         
591         // Fetch links
592         $limit = intval( $limit );
593         $start = intval( $start );
594         if ( $limit > 0 ) {
595
596                 $table_url = YOURLS_DB_TABLE_URL;
597                 $results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" );
598                 
599                 $return = array();
600                 $i = 1;
601                 
602                 foreach ( (array)$results as $res ) {
603                         $return['links']['link_'.$i++] = array(
604                                 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
605                                 'url'      => $res->url,
606                                 'title'    => $res->title,
607                                 'timestamp'=> $res->timestamp,
608                                 'ip'       => $res->ip,
609                                 'clicks'   => $res->clicks,
610                         );
611                 }
612         }
613
614         $return['stats'] = yourls_get_db_stats();
615         
616         $return['statusCode'] = 200;
617
618         return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
619 }
620
621 /**
622  * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
623  *
624  */
625 function yourls_get_link_stats( $shorturl ) {
626         global $ydb;
627
628         $table_url = YOURLS_DB_TABLE_URL;
629         $res = $ydb->get_row( "SELECT * FROM `$table_url` WHERE keyword = '$shorturl';" );
630         $return = array();
631
632         if( !$res ) {
633                 // non existent link
634                 $return = array(
635                         'statusCode' => 404,
636                         'message'    => 'Error: short URL not found',
637                 );
638         } else {
639                 $return = array(
640                         'statusCode' => 200,
641                         'message'    => 'success',
642                         'link'       => array(
643                                 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
644                                 'url'      => $res->url,
645                                 'title'    => $res->title,
646                                 'timestamp'=> $res->timestamp,
647                                 'ip'       => $res->ip,
648                                 'clicks'   => $res->clicks,
649                         )
650                 );
651         }
652
653         return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
654 }
655
656 /**
657  * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
658  *
659  */
660 function yourls_get_db_stats( $where = '' ) {
661         global $ydb;
662         $table_url = YOURLS_DB_TABLE_URL;
663
664         $totals = $ydb->get_row( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where" );
665         $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
666         
667         return yourls_apply_filter( 'get_db_stats', $return, $where );
668 }
669
670 /**
671  * Get number of SQL queries performed
672  *
673  */
674 function yourls_get_num_queries() {
675         global $ydb;
676
677         return yourls_apply_filter( 'get_num_queries', $ydb->num_queries );
678 }
679
680 /**
681  * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
682  *
683  */
684 function yourls_get_user_agent() {
685         if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
686                 return '-';
687         
688         $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
689         $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
690                 
691         return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
692 }
693
694 /**
695  * Redirect to another page
696  *
697  */
698 function yourls_redirect( $location, $code = 301 ) {
699         yourls_do_action( 'pre_redirect', $location, $code );
700         $location = yourls_apply_filter( 'redirect_location', $location, $code );
701         $code     = yourls_apply_filter( 'redirect_code', $code, $location );
702         // Redirect, either properly if possible, or via Javascript otherwise
703         if( !headers_sent() ) {
704                 yourls_status_header( $code );
705                 header( "Location: $location" );
706         } else {
707                 yourls_redirect_javascript( $location );
708         }
709         die();
710 }
711
712 /**
713  * Set HTTP status header
714  *
715  */
716 function yourls_status_header( $code = 200 ) {
717         if( headers_sent() )
718                 return;
719                 
720         $protocol = $_SERVER["SERVER_PROTOCOL"];
721         if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
722                 $protocol = 'HTTP/1.0';
723
724         $code = intval( $code );
725         $desc = yourls_get_HTTP_status( $code );
726
727         @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
728         yourls_do_action( 'status_header', $code );
729 }
730
731 /**
732  * 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)
733  *
734  */
735 function yourls_redirect_javascript( $location, $dontwait = true ) {
736         yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
737         $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
738         if( $dontwait ) {
739                 $message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
740                 echo <<<REDIR
741                 <script type="text/javascript">
742                 window.location="$location";
743                 </script>
744                 <small>($message)</small>
745 REDIR;
746         } else {
747                 echo '<p>' . yourls_s( 'Please <a href="%s">click here</a>', $location ) . '</p>';
748         }
749         yourls_do_action( 'post_redirect_javascript', $location );
750 }
751
752 /**
753  * Return a HTTP status code
754  *
755  */
756 function yourls_get_HTTP_status( $code ) {
757         $code = intval( $code );
758         $headers_desc = array(
759                 100 => 'Continue',
760                 101 => 'Switching Protocols',
761                 102 => 'Processing',
762
763                 200 => 'OK',
764                 201 => 'Created',
765                 202 => 'Accepted',
766                 203 => 'Non-Authoritative Information',
767                 204 => 'No Content',
768                 205 => 'Reset Content',
769                 206 => 'Partial Content',
770                 207 => 'Multi-Status',
771                 226 => 'IM Used',
772
773                 300 => 'Multiple Choices',
774                 301 => 'Moved Permanently',
775                 302 => 'Found',
776                 303 => 'See Other',
777                 304 => 'Not Modified',
778                 305 => 'Use Proxy',
779                 306 => 'Reserved',
780                 307 => 'Temporary Redirect',
781
782                 400 => 'Bad Request',
783                 401 => 'Unauthorized',
784                 402 => 'Payment Required',
785                 403 => 'Forbidden',
786                 404 => 'Not Found',
787                 405 => 'Method Not Allowed',
788                 406 => 'Not Acceptable',
789                 407 => 'Proxy Authentication Required',
790                 408 => 'Request Timeout',
791                 409 => 'Conflict',
792                 410 => 'Gone',
793                 411 => 'Length Required',
794                 412 => 'Precondition Failed',
795                 413 => 'Request Entity Too Large',
796                 414 => 'Request-URI Too Long',
797                 415 => 'Unsupported Media Type',
798                 416 => 'Requested Range Not Satisfiable',
799                 417 => 'Expectation Failed',
800                 422 => 'Unprocessable Entity',
801                 423 => 'Locked',
802                 424 => 'Failed Dependency',
803                 426 => 'Upgrade Required',
804
805                 500 => 'Internal Server Error',
806                 501 => 'Not Implemented',
807                 502 => 'Bad Gateway',
808                 503 => 'Service Unavailable',
809                 504 => 'Gateway Timeout',
810                 505 => 'HTTP Version Not Supported',
811                 506 => 'Variant Also Negotiates',
812                 507 => 'Insufficient Storage',
813                 510 => 'Not Extended'
814         );
815
816         if ( isset( $headers_desc[$code] ) )
817                 return $headers_desc[$code];
818         else
819                 return '';
820 }
821
822
823 /**
824  * Log a redirect (for stats)
825  *
826  */
827 function yourls_log_redirect( $keyword ) {
828         // Allow plugins to short-circuit the whole function
829         $pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
830         if ( false !== $pre )
831                 return $pre;
832
833         if ( !yourls_do_log_redirect() )
834                 return true;
835
836         global $ydb;
837         $table = YOURLS_DB_TABLE_LOG;
838         
839         $keyword = yourls_sanitize_string( $keyword );
840         $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' );
841         $ua = yourls_get_user_agent();
842         $ip = yourls_get_IP();
843         $location = yourls_geo_ip_to_countrycode( $ip );
844         
845         return $ydb->query( "INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" );
846 }
847
848 /**
849  * Check if we want to not log redirects (for stats)
850  *
851  */
852 function yourls_do_log_redirect() {
853         return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
854 }
855
856 /**
857  * Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
858  *
859  */
860 function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
861         // Allow plugins to short-circuit the Geo IP API
862         $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
863         if ( false !== $location )
864                 return $location;
865
866         if ( !file_exists( YOURLS_INC.'/geo/GeoIP.dat') || !file_exists( YOURLS_INC.'/geo/geoip.inc') )
867                 return $default;
868
869         if ( $ip == '' )
870                 $ip = yourls_get_IP();
871         
872         require_once( YOURLS_INC.'/geo/geoip.inc') ;
873         $gi = geoip_open( YOURLS_INC.'/geo/GeoIP.dat', GEOIP_STANDARD);
874         $location = geoip_country_code_by_addr($gi, $ip);
875         geoip_close($gi);
876
877         return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
878 }
879
880 /**
881  * Converts a 2 letter country code to long name (ie AU -> Australia)
882  *
883  */
884 function yourls_geo_countrycode_to_countryname( $code ) {
885         // Allow plugins to short-circuit the Geo IP API
886         $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code );
887         if ( false !== $country )
888                 return $country;
889
890         // Load the Geo class if not already done
891         if( !class_exists( 'GeoIP', false ) ) {
892                 $temp = yourls_geo_ip_to_countrycode( '127.0.0.1' );
893         }
894         
895         if( class_exists( 'GeoIP', false ) ) {
896                 $geo  = new GeoIP;
897                 $id   = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[ $code ];
898                 $long = $geo->GEOIP_COUNTRY_NAMES[ $id ];
899                 return $long;
900         } else {
901                 return false;
902         }
903 }
904
905 /**
906  * Return flag URL from 2 letter country code
907  *
908  */
909 function yourls_geo_get_flag( $code ) {
910         if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) {
911                 $img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' );
912         } else {
913                 $img = false;
914         }
915         return yourls_apply_filter( 'geo_get_flag', $img, $code );
916 }
917
918
919 /**
920  * Check if an upgrade is needed
921  *
922  */
923 function yourls_upgrade_is_needed() {
924         // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
925         list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
926         if( $currentsql < YOURLS_DB_VERSION )
927                 return true;
928                 
929         return false;
930 }
931
932 /**
933  * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
934  *
935  */
936 function yourls_get_current_version_from_sql() {
937         $currentver = yourls_get_option( 'version' );
938         $currentsql = yourls_get_option( 'db_version' );
939
940         // Values if version is 1.3
941         if( !$currentver )
942                 $currentver = '1.3';
943         if( !$currentsql )
944                 $currentsql = '100';
945                 
946         return array( $currentver, $currentsql);
947 }
948
949 /**
950  * Read an option from DB (or from cache if available). Return value or $default if not found
951  *
952  */
953 function yourls_get_option( $option_name, $default = false ) {
954         global $ydb;
955         
956         // Allow plugins to short-circuit options
957         $pre = yourls_apply_filter( 'shunt_option_'.$option_name, false );
958         if ( false !== $pre )
959                 return $pre;
960
961         // If option not cached already, get its value from the DB
962         if ( !isset( $ydb->option[$option_name] ) ) {
963                 $table = YOURLS_DB_TABLE_OPTIONS;
964                 $option_name = yourls_escape( $option_name );
965                 $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
966                 if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
967                         $value = $row->option_value;
968                 } else { // option does not exist, so we must cache its non-existence
969                         $value = $default;
970                 }
971                 $ydb->option[ $option_name ] = yourls_maybe_unserialize( $value );
972         }
973
974         return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
975 }
976
977 /**
978  * Read all options from DB at once
979  *
980  */
981 function yourls_get_all_options() {
982         global $ydb;
983
984         // Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options)
985         $pre = yourls_apply_filter( 'shunt_all_options', false );
986         if ( false !== $pre )
987                 return $pre;
988
989         $table = YOURLS_DB_TABLE_OPTIONS;
990         
991         $allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" );
992         
993         foreach( (array)$allopt as $option ) {
994                 $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value );
995         }
996         
997         $ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option );
998 }
999
1000 /**
1001  * Update (add if doesn't exist) an option to DB
1002  *
1003  */
1004 function yourls_update_option( $option_name, $newvalue ) {
1005         global $ydb;
1006         $table = YOURLS_DB_TABLE_OPTIONS;
1007
1008         $safe_option_name = yourls_escape( $option_name );
1009
1010         $oldvalue = yourls_get_option( $safe_option_name );
1011
1012         // If the new and old values are the same, no need to update.
1013         if ( $newvalue === $oldvalue )
1014                 return false;
1015
1016         if ( false === $oldvalue ) {
1017                 yourls_add_option( $option_name, $newvalue );
1018                 return true;
1019         }
1020
1021         $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
1022         
1023         yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
1024
1025         $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" );
1026
1027         if ( $ydb->rows_affected == 1 ) {
1028                 $ydb->option[ $option_name ] = $newvalue;
1029                 return true;
1030         }
1031         return false;
1032 }
1033
1034 /**
1035  * Add an option to the DB
1036  *
1037  */
1038 function yourls_add_option( $name, $value = '' ) {
1039         global $ydb;
1040         $table = YOURLS_DB_TABLE_OPTIONS;
1041         $safe_name = yourls_escape( $name );
1042
1043         // Make sure the option doesn't already exist
1044         if ( false !== yourls_get_option( $safe_name ) )
1045                 return;
1046
1047         $_value = yourls_escape( yourls_maybe_serialize( $value ) );
1048
1049         yourls_do_action( 'add_option', $safe_name, $_value );
1050
1051         $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
1052         $ydb->option[ $name ] = $value;
1053         return;
1054 }
1055
1056
1057 /**
1058  * Delete an option from the DB
1059  *
1060  */
1061 function yourls_delete_option( $name ) {
1062         global $ydb;
1063         $table = YOURLS_DB_TABLE_OPTIONS;
1064         $name = yourls_escape( $name );
1065
1066         // Get the ID, if no ID then return
1067         $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
1068         if ( is_null( $option ) || !$option->option_id )
1069                 return false;
1070                 
1071         yourls_do_action( 'delete_option', $name );
1072                 
1073         $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
1074         return true;
1075 }
1076
1077
1078
1079 /**
1080  * Serialize data if needed. Stolen from WordPress
1081  *
1082  */
1083 function yourls_maybe_serialize( $data ) {
1084         if ( is_array( $data ) || is_object( $data ) )
1085                 return serialize( $data );
1086
1087         if ( yourls_is_serialized( $data ) )
1088                 return serialize( $data );
1089
1090         return $data;
1091 }
1092
1093 /**
1094  * Check value to find if it was serialized. Stolen from WordPress
1095  *
1096  */
1097 function yourls_is_serialized( $data ) {
1098         // if it isn't a string, it isn't serialized
1099         if ( !is_string( $data ) )
1100                 return false;
1101         $data = trim( $data );
1102         if ( 'N;' == $data )
1103                 return true;
1104         if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
1105                 return false;
1106         switch ( $badions[1] ) {
1107                 case 'a' :
1108                 case 'O' :
1109                 case 's' :
1110                         if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
1111                                 return true;
1112                         break;
1113                 case 'b' :
1114                 case 'i' :
1115                 case 'd' :
1116                         if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
1117                                 return true;
1118                         break;
1119         }
1120         return false;
1121 }
1122
1123 /**
1124  * Unserialize value only if it was serialized. Stolen from WP
1125  *
1126  */
1127 function yourls_maybe_unserialize( $original ) {
1128         if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
1129                 return @unserialize( $original );
1130         return $original;
1131 }
1132
1133 /**
1134  * Determine if the current page is private
1135  *
1136  */
1137 function yourls_is_private() {
1138         $private = false;
1139
1140         if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
1141
1142                 // Allow overruling for particular pages:
1143                 
1144                 // API
1145                 if( yourls_is_API() ) {
1146                         if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
1147                                 $private = true;                
1148
1149                 // Infos
1150                 } elseif( yourls_is_infos() ) {
1151                         if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
1152                                 $private = true;
1153                 
1154                 // Others
1155                 } else {
1156                         $private = true;
1157                 }
1158                 
1159         }
1160                         
1161         return yourls_apply_filter( 'is_private', $private );
1162 }
1163
1164 /**
1165  * Show login form if required
1166  *
1167  */
1168 function yourls_maybe_require_auth() {
1169         if( yourls_is_private() ) {
1170                 yourls_do_action( 'require_auth' );
1171                 require_once( YOURLS_INC.'/auth.php' );
1172         } else {
1173                 yourls_do_action( 'require_no_auth' );
1174         }
1175 }
1176
1177 /**
1178  * Allow several short URLs for the same long URL ?
1179  *
1180  */
1181 function yourls_allow_duplicate_longurls() {
1182         // special treatment if API to check for WordPress plugin requests
1183         if( yourls_is_API() ) {
1184                 if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' ) 
1185                         return false;
1186         }
1187         return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
1188 }
1189
1190 /**
1191  * Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords.
1192  *
1193  */
1194 function yourls_get_duplicate_keywords( $longurl ) {
1195         if( !yourls_allow_duplicate_longurls() )
1196                 return NULL;
1197         
1198         global $ydb;
1199         $longurl = yourls_escape( yourls_sanitize_url($longurl) );
1200         $table = YOURLS_DB_TABLE_URL;
1201         
1202         $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" );
1203         return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl );
1204 }
1205
1206 /**
1207  * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
1208  *
1209  */
1210 function yourls_check_IP_flood( $ip = '' ) {
1211
1212         // Allow plugins to short-circuit the whole function
1213         $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
1214         if ( false !== $pre )
1215                 return $pre;
1216
1217         yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
1218
1219         if(
1220                 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
1221                 !defined('YOURLS_FLOOD_DELAY_SECONDS')
1222         )
1223                 return true;
1224
1225         $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
1226
1227         // Don't throttle whitelist IPs
1228         if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
1229                 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
1230                 foreach( (array)$whitelist_ips as $whitelist_ip ) {
1231                         $whitelist_ip = trim( $whitelist_ip );
1232                         if ( $whitelist_ip == $ip )
1233                                 return true;
1234                 }
1235         }
1236         
1237         // Don't throttle logged in users
1238         if( yourls_is_private() ) {
1239                  if( yourls_is_valid_user() === true )
1240                         return true;
1241         }
1242         
1243         yourls_do_action( 'check_ip_flood', $ip );
1244         
1245         global $ydb;
1246         $table = YOURLS_DB_TABLE_URL;
1247         
1248         $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
1249         if( $lasttime ) {
1250                 $now = date( 'U' );
1251                 $then = date( 'U', strtotime( $lasttime ) );
1252                 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
1253                         // Flood!
1254                         yourls_do_action( 'ip_flood', $ip, $now - $then );
1255                         yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
1256                 }
1257         }
1258         
1259         return true;
1260 }
1261
1262 /**
1263  * Check if YOURLS is installing
1264  *
1265  * @return bool
1266  * @since 1.6
1267  */
1268 function yourls_is_installing() {
1269         $installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true;
1270         return yourls_apply_filter( 'is_installing', $installing );
1271 }
1272
1273 /**
1274  * Check if YOURLS is upgrading
1275  *
1276  * @return bool
1277  * @since 1.6
1278  */
1279 function yourls_is_upgrading() {
1280         $upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true;
1281         return yourls_apply_filter( 'is_upgrading', $upgrading );
1282 }
1283
1284
1285 /**
1286  * Check if YOURLS is installed
1287  *
1288  */
1289 function yourls_is_installed() {
1290         static $is_installed = false;
1291         if ( $is_installed === false ) {
1292                 $check_14 = $check_13 = false;
1293                 global $ydb;
1294                 if( defined('YOURLS_DB_TABLE_NEXTDEC') )
1295                         $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC);
1296                 $check_14 = yourls_get_option( 'version' );
1297                 $is_installed = $check_13 || $check_14;
1298         }
1299         return yourls_apply_filter( 'is_installed', $is_installed );
1300 }
1301
1302 /**
1303  * Generate random string of (int)$length length and type $type (see function for details)
1304  *
1305  */
1306 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
1307         $str = '';
1308         $length = intval( $length );
1309
1310         // define possible characters
1311         switch ( $type ) {
1312
1313                 // custom char list, or comply to charset as defined in config
1314                 case '0':
1315                         $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
1316                         break;
1317         
1318                 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
1319                 case '1':
1320                         $possible = "23456789bcdfghjkmnpqrstvwxyz";
1321                         break;
1322                 
1323                 // Same, with lower + upper
1324                 case '2':
1325                         $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
1326                         break;
1327                 
1328                 // all letters, lowercase
1329                 case '3':
1330                         $possible = "abcdefghijklmnopqrstuvwxyz";
1331                         break;
1332                 
1333                 // all letters, lowercase + uppercase
1334                 case '4':
1335                         $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1336                         break;
1337                 
1338                 // all digits & letters lowercase 
1339                 case '5':
1340                         $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
1341                         break;
1342                 
1343                 // all digits & letters lowercase + uppercase
1344                 case '6':
1345                         $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1346                         break;
1347                 
1348         }
1349
1350         $i = 0;
1351         while ($i < $length) {
1352                 $str .= substr( $possible, mt_rand( 0, strlen( $possible )-1 ), 1 );
1353                 $i++;
1354         }
1355         
1356         return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
1357 }
1358
1359 /**
1360  * Return salted string
1361  *
1362  */
1363 function yourls_salt( $string ) {
1364         $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
1365         return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
1366 }
1367
1368 /**
1369  * Add a query var to a URL and return URL. Completely stolen from WP.
1370  * 
1371  * Works with one of these parameter patterns:
1372  *     array( 'var' => 'value' )
1373  *     array( 'var' => 'value' ), $url
1374  *     'var', 'value'
1375  *     'var', 'value', $url 
1376  * If $url omitted, uses $_SERVER['REQUEST_URI']
1377  *
1378  */
1379 function yourls_add_query_arg() {
1380         $ret = '';
1381         if ( is_array( func_get_arg(0) ) ) {
1382                 if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
1383                         $uri = $_SERVER['REQUEST_URI'];
1384                 else
1385                         $uri = @func_get_arg( 1 );
1386         } else {
1387                 if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
1388                         $uri = $_SERVER['REQUEST_URI'];
1389                 else
1390                         $uri = @func_get_arg( 2 );
1391         }
1392         
1393         $uri = str_replace( '&amp;', '&', $uri );
1394
1395         
1396         if ( $frag = strstr( $uri, '#' ) )
1397                 $uri = substr( $uri, 0, -strlen( $frag ) );
1398         else
1399                 $frag = '';
1400
1401         if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
1402                 $protocol = $matches[0];
1403                 $uri = substr( $uri, strlen( $protocol ) );
1404         } else {
1405                 $protocol = '';
1406         }
1407
1408         if ( strpos( $uri, '?' ) !== false ) {
1409                 $parts = explode( '?', $uri, 2 );
1410                 if ( 1 == count( $parts ) ) {
1411                         $base = '?';
1412                         $query = $parts[0];
1413                 } else {
1414                         $base = $parts[0] . '?';
1415                         $query = $parts[1];
1416                 }
1417         } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
1418                 $base = $uri . '?';
1419                 $query = '';
1420         } else {
1421                 $base = '';
1422                 $query = $uri;
1423         }
1424
1425         parse_str( $query, $qs );
1426         $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1427         if ( is_array( func_get_arg( 0 ) ) ) {
1428                 $kayvees = func_get_arg( 0 );
1429                 $qs = array_merge( $qs, $kayvees );
1430         } else {
1431                 $qs[func_get_arg( 0 )] = func_get_arg( 1 );
1432         }
1433
1434         foreach ( (array) $qs as $k => $v ) {
1435                 if ( $v === false )
1436                         unset( $qs[$k] );
1437         }
1438
1439         $ret = http_build_query( $qs );
1440         $ret = trim( $ret, '?' );
1441         $ret = preg_replace( '#=(&|$)#', '$1', $ret );
1442         $ret = $protocol . $base . $ret . $frag;
1443         $ret = rtrim( $ret, '?' );
1444         return $ret;
1445 }
1446
1447 /**
1448  * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
1449  *
1450  */
1451 function yourls_urlencode_deep( $value ) {
1452         $value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
1453         return $value;
1454 }
1455
1456 /**
1457  * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
1458  *
1459  */
1460 function yourls_remove_query_arg( $key, $query = false ) {
1461         if ( is_array( $key ) ) { // removing multiple keys
1462                 foreach ( $key as $k )
1463                         $query = yourls_add_query_arg( $k, false, $query );
1464                 return $query;
1465         }
1466         return yourls_add_query_arg( $key, false, $query );
1467 }
1468
1469 /**
1470  * Return a time-dependent string for nonce creation
1471  *
1472  */
1473 function yourls_tick() {
1474         return ceil( time() / YOURLS_NONCE_LIFE );
1475 }
1476
1477 /**
1478  * Create a time limited, action limited and user limited token
1479  *
1480  */
1481 function yourls_create_nonce( $action, $user = false ) {
1482         if( false == $user )
1483                 $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1484         $tick = yourls_tick();
1485         return substr( yourls_salt($tick . $action . $user), 0, 10 );
1486 }
1487
1488 /**
1489  * Create a nonce field for inclusion into a form
1490  *
1491  */
1492 function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
1493         $field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
1494         if( $echo )
1495                 echo $field."\n";
1496         return $field;
1497 }
1498
1499 /**
1500  * Add a nonce to a URL. If URL omitted, adds nonce to current URL
1501  *
1502  */
1503 function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
1504         $nonce = yourls_create_nonce( $action, $user );
1505         return yourls_add_query_arg( $name, $nonce, $url );
1506 }
1507
1508 /**
1509  * Check validity of a nonce (ie time span, user and action match).
1510  * 
1511  * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
1512  * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
1513  *
1514  */
1515 function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
1516         // get user
1517         if( false == $user )
1518                 $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1519                 
1520         // get current nonce value
1521         if( false == $nonce && isset( $_REQUEST['nonce'] ) )
1522                 $nonce = $_REQUEST['nonce'];
1523
1524         // what nonce should be
1525         $valid = yourls_create_nonce( $action, $user );
1526         
1527         if( $nonce == $valid ) {
1528                 return true;
1529         } else {
1530                 if( $return )
1531                         die( $return );
1532                 yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
1533         }
1534 }
1535
1536 /**
1537  * Converts keyword into short link (prepend with YOURLS base URL)
1538  *
1539  */
1540 function yourls_link( $keyword = '' ) {
1541         $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
1542         return yourls_apply_filter( 'yourls_link', $link, $keyword );
1543 }
1544
1545 /**
1546  * Converts keyword into stat link (prepend with YOURLS base URL, append +)
1547  *
1548  */
1549 function yourls_statlink( $keyword = '' ) {
1550         $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
1551         if( yourls_is_ssl() )
1552                 $link = str_replace( 'http://', 'https://', $link );
1553         return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
1554 }
1555
1556 /**
1557  * Check if we're in API mode. Returns bool
1558  *
1559  */
1560 function yourls_is_API() {
1561         if ( defined( 'YOURLS_API' ) && YOURLS_API == true )
1562                 return true;
1563         return false;
1564 }
1565
1566 /**
1567  * Check if we're in Ajax mode. Returns bool
1568  *
1569  */
1570 function yourls_is_Ajax() {
1571         if ( defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true )
1572                 return true;
1573         return false;
1574 }
1575
1576 /**
1577  * Check if we're in GO mode (yourls-go.php). Returns bool
1578  *
1579  */
1580 function yourls_is_GO() {
1581         if ( defined( 'YOURLS_GO' ) && YOURLS_GO == true )
1582                 return true;
1583         return false;
1584 }
1585
1586 /**
1587  * Check if we're displaying stats infos (yourls-infos.php). Returns bool
1588  *
1589  */
1590 function yourls_is_infos() {
1591         if ( defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true )
1592                 return true;
1593         return false;
1594 }
1595
1596 /**
1597  * Check if we'll need interface display function (ie not API or redirection)
1598  *
1599  */
1600 function yourls_has_interface() {
1601         if( yourls_is_API() or yourls_is_GO() )
1602                 return false;
1603         return true;
1604 }
1605
1606 /**
1607  * Check if we're in the admin area. Returns bool
1608  *
1609  */
1610 function yourls_is_admin() {
1611         if ( defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true )
1612                 return true;
1613         return false;
1614 }
1615
1616 /**
1617  * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
1618  *
1619  */
1620 function yourls_is_windows() {
1621         return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
1622 }
1623
1624 /**
1625  * Check if SSL is required. Returns bool.
1626  *
1627  */
1628 function yourls_needs_ssl() {
1629         if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true )
1630                 return true;
1631         return false;
1632 }
1633
1634 /**
1635  * Return admin link, with SSL preference if applicable.
1636  *
1637  */
1638 function yourls_admin_url( $page = '' ) {
1639         $admin = YOURLS_SITE . '/admin/' . $page;
1640         if( yourls_is_ssl() or yourls_needs_ssl() )
1641                 $admin = str_replace('http://', 'https://', $admin);
1642         return yourls_apply_filter( 'admin_url', $admin, $page );
1643 }
1644
1645 /**
1646  * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
1647  *
1648  */
1649 function yourls_site_url( $echo = true, $url = '' ) {
1650         $url = yourls_get_relative_url( $url );
1651         $url = trim( YOURLS_SITE . '/' . $url, '/' );
1652         
1653         // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
1654         if( yourls_is_ssl() )
1655                 $url = str_replace( 'http://', 'https://', $url );
1656         $url = yourls_apply_filter( 'site_url', $url );
1657         if( $echo )
1658                 echo $url;
1659         return $url;
1660 }
1661
1662 /**
1663  * Check if SSL is used, returns bool. Stolen from WP.
1664  *
1665  */
1666 function yourls_is_ssl() {
1667         $is_ssl = false;
1668         if ( isset( $_SERVER['HTTPS'] ) ) {
1669                 if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
1670                         $is_ssl = true;
1671                 if ( '1' == $_SERVER['HTTPS'] )
1672                         $is_ssl = true;
1673         } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
1674                 $is_ssl = true;
1675         }
1676         return yourls_apply_filter( 'is_ssl', $is_ssl );
1677 }
1678
1679
1680 /**
1681  * Get a remote page <title>, return a string (either title or url)
1682  *
1683  */
1684 function yourls_get_remote_title( $url ) {
1685         // Allow plugins to short-circuit the whole function
1686         $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
1687         if ( false !== $pre )
1688                 return $pre;
1689
1690         require_once( YOURLS_INC.'/functions-http.php' );
1691
1692         $url = yourls_sanitize_url( $url );
1693
1694         $title = $charset = false;
1695         
1696         $content = yourls_get_remote_content( $url );
1697         
1698         // If false, return url as title.
1699         // Todo: improve this with temporary title when shorturl_meta available?
1700         if( false === $content )
1701                 return $url;
1702
1703         if( $content !== false ) {
1704                 // look for <title>
1705                 if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
1706                         $title = $found[1];
1707                         unset( $found );
1708                 }
1709
1710                 // look for charset
1711                 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1712                 if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) {
1713                         $charset = trim($found[1], '"\' ');
1714                         unset( $found );
1715                 }
1716         }
1717         
1718         // if title not found, guess if returned content was actually an error message
1719         if( $title == false && strpos( $content, 'Error' ) === 0 ) {
1720                 $title = $content;
1721         }
1722         
1723         if( $title == false )
1724                 $title = $url;
1725         
1726         /*
1727         if( !yourls_seems_utf8( $title ) )
1728                 $title = utf8_encode( $title );
1729         */
1730         
1731         // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars)
1732         if( function_exists( 'mb_convert_encoding' ) ) {
1733                 if( $charset ) {
1734                         $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
1735                 } else {
1736                         $title = @mb_convert_encoding( $title, 'UTF-8' );
1737                 }
1738         }
1739         
1740         // Remove HTML entities
1741         $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
1742         
1743         // Strip out evil things
1744         $title = yourls_sanitize_title( $title );
1745         
1746         return yourls_apply_filter( 'get_remote_title', $title, $url );
1747 }
1748
1749 /**
1750  * Quick UA check for mobile devices. Return boolean.
1751  *
1752  */
1753 function yourls_is_mobile_device() {
1754         // Strings searched
1755         $mobiles = array(
1756                 'android', 'blackberry', 'blazer',
1757                 'compal', 'elaine', 'fennec', 'hiptop',
1758                 'iemobile', 'iphone', 'ipod', 'ipad',
1759                 'iris', 'kindle', 'opera mobi', 'opera mini',
1760                 'palm', 'phone', 'pocket', 'psp', 'symbian',
1761                 'treo', 'wap', 'windows ce', 'windows phone'
1762         );
1763         
1764         // Current user-agent
1765         $current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1766         
1767         // Check and return
1768         $is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
1769         return yourls_apply_filter( 'is_mobile_device', $is_mobile );
1770 }
1771
1772 /**
1773  * Get request in YOURLS base (eg in 'http://site.com/yourls/abcd' get 'abdc')
1774  *
1775  */
1776 function yourls_get_request() {
1777         // Allow plugins to short-circuit the whole function
1778         $pre = yourls_apply_filter( 'shunt_get_request', false );
1779         if ( false !== $pre )
1780                 return $pre;
1781                 
1782         static $request = null;
1783
1784         yourls_do_action( 'pre_get_request', $request );
1785         
1786         if( $request !== null )
1787                 return $request;
1788         
1789         // Ignore protocol & www. prefix
1790         $root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE );
1791         // Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah
1792         $request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 );
1793
1794         // Unless request looks like a full URL (ie request is a simple keyword) strip query string
1795         if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
1796                 $request = current( explode( '?', $request ) );
1797         }
1798         
1799         return yourls_apply_filter( 'get_request', $request );
1800 }
1801
1802 /**
1803  * Change protocol to match current scheme used (http or https)
1804  *
1805  */
1806 function yourls_match_current_protocol( $url, $normal = 'http', $ssl = 'https' ) {
1807         if( yourls_is_ssl() )
1808                 $url = str_replace( $normal, $ssl, $url );
1809         return yourls_apply_filter( 'match_current_protocol', $url );
1810 }
1811
1812 /**
1813  * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
1814  *
1815  */
1816 function yourls_fix_request_uri() {
1817
1818         $default_server_values = array(
1819                 'SERVER_SOFTWARE' => '',
1820                 'REQUEST_URI' => '',
1821         );
1822         $_SERVER = array_merge( $default_server_values, $_SERVER );
1823
1824         // Fix for IIS when running with PHP ISAPI
1825         if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
1826
1827                 // IIS Mod-Rewrite
1828                 if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
1829                         $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
1830                 }
1831                 // IIS Isapi_Rewrite
1832                 else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
1833                         $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
1834                 } else {
1835                         // Use ORIG_PATH_INFO if there is no PATH_INFO
1836                         if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
1837                                 $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
1838
1839                         // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
1840                         if ( isset( $_SERVER['PATH_INFO'] ) ) {
1841                                 if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
1842                                         $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
1843                                 else
1844                                         $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
1845                         }
1846
1847                         // Append the query string if it exists and isn't null
1848                         if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
1849                                 $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
1850                         }
1851                 }
1852         }
1853 }
1854
1855 /**
1856  * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
1857  *
1858  */
1859 function yourls_shutdown() {
1860         yourls_do_action( 'shutdown' );
1861 }
1862
1863 /**
1864  * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
1865  *
1866  */
1867 function yourls_favicon( $echo = true ) {
1868         static $favicon = null;
1869         if( $favicon !== null )
1870                 return $favicon;
1871         
1872         $custom = null;
1873         // search for favicon.(gif|ico|png|jpg|svg)
1874         foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
1875                 if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
1876                         $custom = 'favicon.' . $ext;
1877                         break;
1878                 }
1879         }
1880         
1881         if( $custom ) {
1882                 $favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
1883         } else {
1884                 $favicon = yourls_site_url( false ) . '/images/favicon.gif';
1885         }
1886         if( $echo )
1887                 echo $favicon;
1888         return $favicon;
1889 }
1890
1891 /**
1892  * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
1893  *
1894  */
1895 function yourls_check_maintenance_mode() {
1896
1897         $file = YOURLS_ABSPATH . '/.maintenance' ;
1898         if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
1899                 return;
1900         
1901         global $maintenance_start;
1902
1903         include( $file );
1904         // If the $maintenance_start timestamp is older than 10 minutes, don't die.
1905         if ( ( time() - $maintenance_start ) >= 600 )
1906                 return;
1907
1908         // Use any /user/maintenance.php file
1909         if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
1910                 include( YOURLS_USERDIR.'/maintenance.php' );
1911                 die();
1912         }
1913         
1914         // https://www.youtube.com/watch?v=Xw-m4jEY-Ns
1915         $title   = yourls__( 'Service temporarily unavailable' );
1916         $message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
1917         yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
1918         yourls_die( $message, $title , 503 );
1919
1920 }
1921
1922 /**
1923  * Return current admin page, or null if not an admin page
1924  *
1925  * @return mixed string if admin page, null if not an admin page
1926  * @since 1.6
1927  */
1928 function yourls_current_admin_page() {
1929         if( yourls_is_admin() ) {
1930                 $current = substr( yourls_get_request(), 6 );
1931                 if( $current === false ) 
1932                         $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
1933                         
1934                 return $current;
1935         }
1936         return null;
1937 }
1938
1939 /**
1940  * Check if a URL protocol is allowed
1941  *
1942  * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
1943  * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
1944  * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
1945  *
1946  * @since 1.6
1947  *
1948  * @param string $url URL to be check
1949  * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
1950  * @return boolean true if protocol allowed, false otherwise
1951  */
1952 function yourls_is_allowed_protocol( $url, $protocols = array() ) {
1953         if( ! $protocols ) {
1954                 global $yourls_allowedprotocols;
1955                 $protocols = $yourls_allowedprotocols;
1956         }
1957         
1958         $protocol = yourls_get_protocol( $url );
1959         return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
1960 }
1961
1962 /**
1963  * Get protocol from a URL (eg mailto:, http:// ...)
1964  *
1965  * @since 1.6
1966  *
1967  * @param string $url URL to be check
1968  * @return string Protocol, with slash slash if applicable. Empty string if no protocol
1969  */
1970 function yourls_get_protocol( $url ) {
1971         preg_match( '!^[a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
1972         /*
1973         http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
1974         The scheme name consists of a sequence of characters beginning with a letter and followed by any
1975         combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
1976         case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
1977         with lowercase letters. It is followed by a colon (":").
1978         */
1979         $protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
1980         return yourls_apply_filter( 'get_protocol', $protocol, $url );
1981 }
1982
1983 /**
1984  * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
1985  *
1986  * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
1987  * or return empty string if $strict is true
1988  *
1989  * @since 1.6
1990  * @param string $url URL to relativize
1991  * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
1992  * @return string URL 
1993  */
1994 function yourls_get_relative_url( $url, $strict = true ) {
1995         $url = yourls_sanitize_url( $url );
1996         
1997         // Remove protocols to make it easier
1998         $noproto_url  = str_replace( 'https:', 'http:', $url );
1999         $noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
2000         
2001         // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
2002         $_url = str_replace( $noproto_site . '/', '', $noproto_url );
2003         if( $_url == $noproto_url )
2004                 $_url = ( $strict ? '' : $url );
2005
2006         return yourls_apply_filter( 'get_relative_url', $_url, $url );
2007 }
2008
2009 /**
2010  * Marks a function as deprecated and informs when it has been used. Stolen from WP.
2011  *
2012  * There is a hook deprecated_function that will be called that can be used
2013  * to get the backtrace up to what file and function called the deprecated
2014  * function.
2015  *
2016  * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
2017  *
2018  * This function is to be used in every function that is deprecated.
2019  *
2020  * @since 1.6
2021  * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
2022  *   and the version the function was deprecated in.
2023  * @uses yourls_apply_filters() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
2024  *   trigger or false to not trigger error.
2025  *
2026  * @param string $function The function that was called
2027  * @param string $version The version of WordPress that deprecated the function
2028  * @param string $replacement Optional. The function that should have been called
2029  */
2030 function yourls_deprecated_function( $function, $version, $replacement = null ) {
2031
2032         yourls_do_action( 'deprecated_function', $function, $replacement, $version );
2033
2034         // Allow plugin to filter the output error trigger
2035         if ( YOURLS_DEBUG && yourls_apply_filters( 'deprecated_function_trigger_error', true ) ) {
2036                 if ( ! is_null( $replacement ) )
2037                         trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
2038                 else
2039                         trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
2040         }
2041 }
2042
2043 /**
2044  * Return the value if not an empty string
2045  *
2046  * Used with array_filter(), to remove empty keys but not keys with value 0 or false
2047  *
2048  * @since 1.6
2049  * @param mixed $val Value to test against ''
2050  * @return bool True if not an empty string
2051  */
2052 function yourls_return_if_not_empty_string( $val ) {
2053         return( $val !== '' );
2054 }