]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/functions.php
Remove potential warning
[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         if( property_exists( $ydb, 'option' ) ) {
998                 $ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option );
999         }
1000 }
1001
1002 /**
1003  * Update (add if doesn't exist) an option to DB
1004  *
1005  */
1006 function yourls_update_option( $option_name, $newvalue ) {
1007         global $ydb;
1008         $table = YOURLS_DB_TABLE_OPTIONS;
1009
1010         $safe_option_name = yourls_escape( $option_name );
1011
1012         $oldvalue = yourls_get_option( $safe_option_name );
1013
1014         // If the new and old values are the same, no need to update.
1015         if ( $newvalue === $oldvalue )
1016                 return false;
1017
1018         if ( false === $oldvalue ) {
1019                 yourls_add_option( $option_name, $newvalue );
1020                 return true;
1021         }
1022
1023         $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
1024         
1025         yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
1026
1027         $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" );
1028
1029         if ( $ydb->rows_affected == 1 ) {
1030                 $ydb->option[ $option_name ] = $newvalue;
1031                 return true;
1032         }
1033         return false;
1034 }
1035
1036 /**
1037  * Add an option to the DB
1038  *
1039  */
1040 function yourls_add_option( $name, $value = '' ) {
1041         global $ydb;
1042         $table = YOURLS_DB_TABLE_OPTIONS;
1043         $safe_name = yourls_escape( $name );
1044
1045         // Make sure the option doesn't already exist
1046         if ( false !== yourls_get_option( $safe_name ) )
1047                 return;
1048
1049         $_value = yourls_escape( yourls_maybe_serialize( $value ) );
1050
1051         yourls_do_action( 'add_option', $safe_name, $_value );
1052
1053         $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
1054         $ydb->option[ $name ] = $value;
1055         return;
1056 }
1057
1058
1059 /**
1060  * Delete an option from the DB
1061  *
1062  */
1063 function yourls_delete_option( $name ) {
1064         global $ydb;
1065         $table = YOURLS_DB_TABLE_OPTIONS;
1066         $name = yourls_escape( $name );
1067
1068         // Get the ID, if no ID then return
1069         $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
1070         if ( is_null( $option ) || !$option->option_id )
1071                 return false;
1072                 
1073         yourls_do_action( 'delete_option', $name );
1074                 
1075         $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
1076         return true;
1077 }
1078
1079
1080
1081 /**
1082  * Serialize data if needed. Stolen from WordPress
1083  *
1084  */
1085 function yourls_maybe_serialize( $data ) {
1086         if ( is_array( $data ) || is_object( $data ) )
1087                 return serialize( $data );
1088
1089         if ( yourls_is_serialized( $data ) )
1090                 return serialize( $data );
1091
1092         return $data;
1093 }
1094
1095 /**
1096  * Check value to find if it was serialized. Stolen from WordPress
1097  *
1098  */
1099 function yourls_is_serialized( $data ) {
1100         // if it isn't a string, it isn't serialized
1101         if ( !is_string( $data ) )
1102                 return false;
1103         $data = trim( $data );
1104         if ( 'N;' == $data )
1105                 return true;
1106         if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
1107                 return false;
1108         switch ( $badions[1] ) {
1109                 case 'a' :
1110                 case 'O' :
1111                 case 's' :
1112                         if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
1113                                 return true;
1114                         break;
1115                 case 'b' :
1116                 case 'i' :
1117                 case 'd' :
1118                         if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
1119                                 return true;
1120                         break;
1121         }
1122         return false;
1123 }
1124
1125 /**
1126  * Unserialize value only if it was serialized. Stolen from WP
1127  *
1128  */
1129 function yourls_maybe_unserialize( $original ) {
1130         if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
1131                 return @unserialize( $original );
1132         return $original;
1133 }
1134
1135 /**
1136  * Determine if the current page is private
1137  *
1138  */
1139 function yourls_is_private() {
1140         $private = false;
1141
1142         if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
1143
1144                 // Allow overruling for particular pages:
1145                 
1146                 // API
1147                 if( yourls_is_API() ) {
1148                         if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
1149                                 $private = true;                
1150
1151                 // Infos
1152                 } elseif( yourls_is_infos() ) {
1153                         if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
1154                                 $private = true;
1155                 
1156                 // Others
1157                 } else {
1158                         $private = true;
1159                 }
1160                 
1161         }
1162                         
1163         return yourls_apply_filter( 'is_private', $private );
1164 }
1165
1166 /**
1167  * Show login form if required
1168  *
1169  */
1170 function yourls_maybe_require_auth() {
1171         if( yourls_is_private() ) {
1172                 yourls_do_action( 'require_auth' );
1173                 require_once( YOURLS_INC.'/auth.php' );
1174         } else {
1175                 yourls_do_action( 'require_no_auth' );
1176         }
1177 }
1178
1179 /**
1180  * Allow several short URLs for the same long URL ?
1181  *
1182  */
1183 function yourls_allow_duplicate_longurls() {
1184         // special treatment if API to check for WordPress plugin requests
1185         if( yourls_is_API() ) {
1186                 if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' ) 
1187                         return false;
1188         }
1189         return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
1190 }
1191
1192 /**
1193  * Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords.
1194  *
1195  */
1196 function yourls_get_duplicate_keywords( $longurl ) {
1197         if( !yourls_allow_duplicate_longurls() )
1198                 return NULL;
1199         
1200         global $ydb;
1201         $longurl = yourls_escape( yourls_sanitize_url($longurl) );
1202         $table = YOURLS_DB_TABLE_URL;
1203         
1204         $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" );
1205         return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl );
1206 }
1207
1208 /**
1209  * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
1210  *
1211  */
1212 function yourls_check_IP_flood( $ip = '' ) {
1213
1214         // Allow plugins to short-circuit the whole function
1215         $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
1216         if ( false !== $pre )
1217                 return $pre;
1218
1219         yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
1220
1221         if(
1222                 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
1223                 !defined('YOURLS_FLOOD_DELAY_SECONDS')
1224         )
1225                 return true;
1226
1227         $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
1228
1229         // Don't throttle whitelist IPs
1230         if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
1231                 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
1232                 foreach( (array)$whitelist_ips as $whitelist_ip ) {
1233                         $whitelist_ip = trim( $whitelist_ip );
1234                         if ( $whitelist_ip == $ip )
1235                                 return true;
1236                 }
1237         }
1238         
1239         // Don't throttle logged in users
1240         if( yourls_is_private() ) {
1241                  if( yourls_is_valid_user() === true )
1242                         return true;
1243         }
1244         
1245         yourls_do_action( 'check_ip_flood', $ip );
1246         
1247         global $ydb;
1248         $table = YOURLS_DB_TABLE_URL;
1249         
1250         $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
1251         if( $lasttime ) {
1252                 $now = date( 'U' );
1253                 $then = date( 'U', strtotime( $lasttime ) );
1254                 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
1255                         // Flood!
1256                         yourls_do_action( 'ip_flood', $ip, $now - $then );
1257                         yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
1258                 }
1259         }
1260         
1261         return true;
1262 }
1263
1264 /**
1265  * Check if YOURLS is installing
1266  *
1267  * @return bool
1268  * @since 1.6
1269  */
1270 function yourls_is_installing() {
1271         $installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true;
1272         return yourls_apply_filter( 'is_installing', $installing );
1273 }
1274
1275 /**
1276  * Check if YOURLS is upgrading
1277  *
1278  * @return bool
1279  * @since 1.6
1280  */
1281 function yourls_is_upgrading() {
1282         $upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true;
1283         return yourls_apply_filter( 'is_upgrading', $upgrading );
1284 }
1285
1286
1287 /**
1288  * Check if YOURLS is installed
1289  *
1290  */
1291 function yourls_is_installed() {
1292         static $is_installed = false;
1293         if ( $is_installed === false ) {
1294                 $check_14 = $check_13 = false;
1295                 global $ydb;
1296                 if( defined('YOURLS_DB_TABLE_NEXTDEC') )
1297                         $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC);
1298                 $check_14 = yourls_get_option( 'version' );
1299                 $is_installed = $check_13 || $check_14;
1300         }
1301         return yourls_apply_filter( 'is_installed', $is_installed );
1302 }
1303
1304 /**
1305  * Generate random string of (int)$length length and type $type (see function for details)
1306  *
1307  */
1308 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
1309         $str = '';
1310         $length = intval( $length );
1311
1312         // define possible characters
1313         switch ( $type ) {
1314
1315                 // custom char list, or comply to charset as defined in config
1316                 case '0':
1317                         $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
1318                         break;
1319         
1320                 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
1321                 case '1':
1322                         $possible = "23456789bcdfghjkmnpqrstvwxyz";
1323                         break;
1324                 
1325                 // Same, with lower + upper
1326                 case '2':
1327                         $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
1328                         break;
1329                 
1330                 // all letters, lowercase
1331                 case '3':
1332                         $possible = "abcdefghijklmnopqrstuvwxyz";
1333                         break;
1334                 
1335                 // all letters, lowercase + uppercase
1336                 case '4':
1337                         $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1338                         break;
1339                 
1340                 // all digits & letters lowercase 
1341                 case '5':
1342                         $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
1343                         break;
1344                 
1345                 // all digits & letters lowercase + uppercase
1346                 case '6':
1347                         $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1348                         break;
1349                 
1350         }
1351
1352         $i = 0;
1353         while ($i < $length) {
1354                 $str .= substr( $possible, mt_rand( 0, strlen( $possible )-1 ), 1 );
1355                 $i++;
1356         }
1357         
1358         return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
1359 }
1360
1361 /**
1362  * Return salted string
1363  *
1364  */
1365 function yourls_salt( $string ) {
1366         $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
1367         return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
1368 }
1369
1370 /**
1371  * Add a query var to a URL and return URL. Completely stolen from WP.
1372  * 
1373  * Works with one of these parameter patterns:
1374  *     array( 'var' => 'value' )
1375  *     array( 'var' => 'value' ), $url
1376  *     'var', 'value'
1377  *     'var', 'value', $url 
1378  * If $url omitted, uses $_SERVER['REQUEST_URI']
1379  *
1380  */
1381 function yourls_add_query_arg() {
1382         $ret = '';
1383         if ( is_array( func_get_arg(0) ) ) {
1384                 if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
1385                         $uri = $_SERVER['REQUEST_URI'];
1386                 else
1387                         $uri = @func_get_arg( 1 );
1388         } else {
1389                 if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
1390                         $uri = $_SERVER['REQUEST_URI'];
1391                 else
1392                         $uri = @func_get_arg( 2 );
1393         }
1394         
1395         $uri = str_replace( '&amp;', '&', $uri );
1396
1397         
1398         if ( $frag = strstr( $uri, '#' ) )
1399                 $uri = substr( $uri, 0, -strlen( $frag ) );
1400         else
1401                 $frag = '';
1402
1403         if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
1404                 $protocol = $matches[0];
1405                 $uri = substr( $uri, strlen( $protocol ) );
1406         } else {
1407                 $protocol = '';
1408         }
1409
1410         if ( strpos( $uri, '?' ) !== false ) {
1411                 $parts = explode( '?', $uri, 2 );
1412                 if ( 1 == count( $parts ) ) {
1413                         $base = '?';
1414                         $query = $parts[0];
1415                 } else {
1416                         $base = $parts[0] . '?';
1417                         $query = $parts[1];
1418                 }
1419         } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
1420                 $base = $uri . '?';
1421                 $query = '';
1422         } else {
1423                 $base = '';
1424                 $query = $uri;
1425         }
1426
1427         parse_str( $query, $qs );
1428         $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
1429         if ( is_array( func_get_arg( 0 ) ) ) {
1430                 $kayvees = func_get_arg( 0 );
1431                 $qs = array_merge( $qs, $kayvees );
1432         } else {
1433                 $qs[func_get_arg( 0 )] = func_get_arg( 1 );
1434         }
1435
1436         foreach ( (array) $qs as $k => $v ) {
1437                 if ( $v === false )
1438                         unset( $qs[$k] );
1439         }
1440
1441         $ret = http_build_query( $qs );
1442         $ret = trim( $ret, '?' );
1443         $ret = preg_replace( '#=(&|$)#', '$1', $ret );
1444         $ret = $protocol . $base . $ret . $frag;
1445         $ret = rtrim( $ret, '?' );
1446         return $ret;
1447 }
1448
1449 /**
1450  * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
1451  *
1452  */
1453 function yourls_urlencode_deep( $value ) {
1454         $value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
1455         return $value;
1456 }
1457
1458 /**
1459  * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
1460  *
1461  */
1462 function yourls_remove_query_arg( $key, $query = false ) {
1463         if ( is_array( $key ) ) { // removing multiple keys
1464                 foreach ( $key as $k )
1465                         $query = yourls_add_query_arg( $k, false, $query );
1466                 return $query;
1467         }
1468         return yourls_add_query_arg( $key, false, $query );
1469 }
1470
1471 /**
1472  * Return a time-dependent string for nonce creation
1473  *
1474  */
1475 function yourls_tick() {
1476         return ceil( time() / YOURLS_NONCE_LIFE );
1477 }
1478
1479 /**
1480  * Create a time limited, action limited and user limited token
1481  *
1482  */
1483 function yourls_create_nonce( $action, $user = false ) {
1484         if( false == $user )
1485                 $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1486         $tick = yourls_tick();
1487         return substr( yourls_salt($tick . $action . $user), 0, 10 );
1488 }
1489
1490 /**
1491  * Create a nonce field for inclusion into a form
1492  *
1493  */
1494 function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
1495         $field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
1496         if( $echo )
1497                 echo $field."\n";
1498         return $field;
1499 }
1500
1501 /**
1502  * Add a nonce to a URL. If URL omitted, adds nonce to current URL
1503  *
1504  */
1505 function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
1506         $nonce = yourls_create_nonce( $action, $user );
1507         return yourls_add_query_arg( $name, $nonce, $url );
1508 }
1509
1510 /**
1511  * Check validity of a nonce (ie time span, user and action match).
1512  * 
1513  * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
1514  * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
1515  *
1516  */
1517 function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
1518         // get user
1519         if( false == $user )
1520                 $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
1521                 
1522         // get current nonce value
1523         if( false == $nonce && isset( $_REQUEST['nonce'] ) )
1524                 $nonce = $_REQUEST['nonce'];
1525
1526         // what nonce should be
1527         $valid = yourls_create_nonce( $action, $user );
1528         
1529         if( $nonce == $valid ) {
1530                 return true;
1531         } else {
1532                 if( $return )
1533                         die( $return );
1534                 yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
1535         }
1536 }
1537
1538 /**
1539  * Converts keyword into short link (prepend with YOURLS base URL)
1540  *
1541  */
1542 function yourls_link( $keyword = '' ) {
1543         $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
1544         return yourls_apply_filter( 'yourls_link', $link, $keyword );
1545 }
1546
1547 /**
1548  * Converts keyword into stat link (prepend with YOURLS base URL, append +)
1549  *
1550  */
1551 function yourls_statlink( $keyword = '' ) {
1552         $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
1553         if( yourls_is_ssl() )
1554                 $link = str_replace( 'http://', 'https://', $link );
1555         return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
1556 }
1557
1558 /**
1559  * Check if we're in API mode. Returns bool
1560  *
1561  */
1562 function yourls_is_API() {
1563         if ( defined( 'YOURLS_API' ) && YOURLS_API == true )
1564                 return true;
1565         return false;
1566 }
1567
1568 /**
1569  * Check if we're in Ajax mode. Returns bool
1570  *
1571  */
1572 function yourls_is_Ajax() {
1573         if ( defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true )
1574                 return true;
1575         return false;
1576 }
1577
1578 /**
1579  * Check if we're in GO mode (yourls-go.php). Returns bool
1580  *
1581  */
1582 function yourls_is_GO() {
1583         if ( defined( 'YOURLS_GO' ) && YOURLS_GO == true )
1584                 return true;
1585         return false;
1586 }
1587
1588 /**
1589  * Check if we're displaying stats infos (yourls-infos.php). Returns bool
1590  *
1591  */
1592 function yourls_is_infos() {
1593         if ( defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true )
1594                 return true;
1595         return false;
1596 }
1597
1598 /**
1599  * Check if we'll need interface display function (ie not API or redirection)
1600  *
1601  */
1602 function yourls_has_interface() {
1603         if( yourls_is_API() or yourls_is_GO() )
1604                 return false;
1605         return true;
1606 }
1607
1608 /**
1609  * Check if we're in the admin area. Returns bool
1610  *
1611  */
1612 function yourls_is_admin() {
1613         if ( defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true )
1614                 return true;
1615         return false;
1616 }
1617
1618 /**
1619  * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
1620  *
1621  */
1622 function yourls_is_windows() {
1623         return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
1624 }
1625
1626 /**
1627  * Check if SSL is required. Returns bool.
1628  *
1629  */
1630 function yourls_needs_ssl() {
1631         if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true )
1632                 return true;
1633         return false;
1634 }
1635
1636 /**
1637  * Return admin link, with SSL preference if applicable.
1638  *
1639  */
1640 function yourls_admin_url( $page = '' ) {
1641         $admin = YOURLS_SITE . '/admin/' . $page;
1642         if( yourls_is_ssl() or yourls_needs_ssl() )
1643                 $admin = str_replace('http://', 'https://', $admin);
1644         return yourls_apply_filter( 'admin_url', $admin, $page );
1645 }
1646
1647 /**
1648  * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
1649  *
1650  */
1651 function yourls_site_url( $echo = true, $url = '' ) {
1652         $url = yourls_get_relative_url( $url );
1653         $url = trim( YOURLS_SITE . '/' . $url, '/' );
1654         
1655         // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
1656         if( yourls_is_ssl() )
1657                 $url = str_replace( 'http://', 'https://', $url );
1658         $url = yourls_apply_filter( 'site_url', $url );
1659         if( $echo )
1660                 echo $url;
1661         return $url;
1662 }
1663
1664 /**
1665  * Check if SSL is used, returns bool. Stolen from WP.
1666  *
1667  */
1668 function yourls_is_ssl() {
1669         $is_ssl = false;
1670         if ( isset( $_SERVER['HTTPS'] ) ) {
1671                 if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
1672                         $is_ssl = true;
1673                 if ( '1' == $_SERVER['HTTPS'] )
1674                         $is_ssl = true;
1675         } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
1676                 $is_ssl = true;
1677         }
1678         return yourls_apply_filter( 'is_ssl', $is_ssl );
1679 }
1680
1681
1682 /**
1683  * Get a remote page <title>, return a string (either title or url)
1684  *
1685  */
1686 function yourls_get_remote_title( $url ) {
1687         // Allow plugins to short-circuit the whole function
1688         $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
1689         if ( false !== $pre )
1690                 return $pre;
1691
1692         require_once( YOURLS_INC.'/functions-http.php' );
1693
1694         $url = yourls_sanitize_url( $url );
1695
1696         $title = $charset = false;
1697         
1698         $content = yourls_get_remote_content( $url );
1699         
1700         // If false, return url as title.
1701         // Todo: improve this with temporary title when shorturl_meta available?
1702         if( false === $content )
1703                 return $url;
1704
1705         if( $content !== false ) {
1706                 // look for <title>
1707                 if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
1708                         $title = $found[1];
1709                         unset( $found );
1710                 }
1711
1712                 // look for charset
1713                 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1714                 if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) {
1715                         $charset = trim($found[1], '"\' ');
1716                         unset( $found );
1717                 }
1718         }
1719         
1720         // if title not found, guess if returned content was actually an error message
1721         if( $title == false && strpos( $content, 'Error' ) === 0 ) {
1722                 $title = $content;
1723         }
1724         
1725         if( $title == false )
1726                 $title = $url;
1727         
1728         /*
1729         if( !yourls_seems_utf8( $title ) )
1730                 $title = utf8_encode( $title );
1731         */
1732         
1733         // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars)
1734         if( function_exists( 'mb_convert_encoding' ) ) {
1735                 if( $charset ) {
1736                         $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
1737                 } else {
1738                         $title = @mb_convert_encoding( $title, 'UTF-8' );
1739                 }
1740         }
1741         
1742         // Remove HTML entities
1743         $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
1744         
1745         // Strip out evil things
1746         $title = yourls_sanitize_title( $title );
1747         
1748         return yourls_apply_filter( 'get_remote_title', $title, $url );
1749 }
1750
1751 /**
1752  * Quick UA check for mobile devices. Return boolean.
1753  *
1754  */
1755 function yourls_is_mobile_device() {
1756         // Strings searched
1757         $mobiles = array(
1758                 'android', 'blackberry', 'blazer',
1759                 'compal', 'elaine', 'fennec', 'hiptop',
1760                 'iemobile', 'iphone', 'ipod', 'ipad',
1761                 'iris', 'kindle', 'opera mobi', 'opera mini',
1762                 'palm', 'phone', 'pocket', 'psp', 'symbian',
1763                 'treo', 'wap', 'windows ce', 'windows phone'
1764         );
1765         
1766         // Current user-agent
1767         $current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
1768         
1769         // Check and return
1770         $is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
1771         return yourls_apply_filter( 'is_mobile_device', $is_mobile );
1772 }
1773
1774 /**
1775  * Get request in YOURLS base (eg in 'http://site.com/yourls/abcd' get 'abdc')
1776  *
1777  */
1778 function yourls_get_request() {
1779         // Allow plugins to short-circuit the whole function
1780         $pre = yourls_apply_filter( 'shunt_get_request', false );
1781         if ( false !== $pre )
1782                 return $pre;
1783                 
1784         static $request = null;
1785
1786         yourls_do_action( 'pre_get_request', $request );
1787         
1788         if( $request !== null )
1789                 return $request;
1790         
1791         // Ignore protocol & www. prefix
1792         $root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE );
1793         // Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah
1794         $request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 );
1795
1796         // Unless request looks like a full URL (ie request is a simple keyword) strip query string
1797         if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
1798                 $request = current( explode( '?', $request ) );
1799         }
1800         
1801         return yourls_apply_filter( 'get_request', $request );
1802 }
1803
1804 /**
1805  * Change protocol to match current scheme used (http or https)
1806  *
1807  */
1808 function yourls_match_current_protocol( $url, $normal = 'http', $ssl = 'https' ) {
1809         if( yourls_is_ssl() )
1810                 $url = str_replace( $normal, $ssl, $url );
1811         return yourls_apply_filter( 'match_current_protocol', $url );
1812 }
1813
1814 /**
1815  * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
1816  *
1817  */
1818 function yourls_fix_request_uri() {
1819
1820         $default_server_values = array(
1821                 'SERVER_SOFTWARE' => '',
1822                 'REQUEST_URI' => '',
1823         );
1824         $_SERVER = array_merge( $default_server_values, $_SERVER );
1825
1826         // Fix for IIS when running with PHP ISAPI
1827         if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
1828
1829                 // IIS Mod-Rewrite
1830                 if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
1831                         $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
1832                 }
1833                 // IIS Isapi_Rewrite
1834                 else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
1835                         $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
1836                 } else {
1837                         // Use ORIG_PATH_INFO if there is no PATH_INFO
1838                         if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
1839                                 $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
1840
1841                         // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
1842                         if ( isset( $_SERVER['PATH_INFO'] ) ) {
1843                                 if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
1844                                         $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
1845                                 else
1846                                         $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
1847                         }
1848
1849                         // Append the query string if it exists and isn't null
1850                         if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
1851                                 $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
1852                         }
1853                 }
1854         }
1855 }
1856
1857 /**
1858  * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
1859  *
1860  */
1861 function yourls_shutdown() {
1862         yourls_do_action( 'shutdown' );
1863 }
1864
1865 /**
1866  * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
1867  *
1868  */
1869 function yourls_favicon( $echo = true ) {
1870         static $favicon = null;
1871         if( $favicon !== null )
1872                 return $favicon;
1873         
1874         $custom = null;
1875         // search for favicon.(gif|ico|png|jpg|svg)
1876         foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
1877                 if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
1878                         $custom = 'favicon.' . $ext;
1879                         break;
1880                 }
1881         }
1882         
1883         if( $custom ) {
1884                 $favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
1885         } else {
1886                 $favicon = yourls_site_url( false ) . '/images/favicon.gif';
1887         }
1888         if( $echo )
1889                 echo $favicon;
1890         return $favicon;
1891 }
1892
1893 /**
1894  * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
1895  *
1896  */
1897 function yourls_check_maintenance_mode() {
1898
1899         $file = YOURLS_ABSPATH . '/.maintenance' ;
1900         if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
1901                 return;
1902         
1903         global $maintenance_start;
1904
1905         include( $file );
1906         // If the $maintenance_start timestamp is older than 10 minutes, don't die.
1907         if ( ( time() - $maintenance_start ) >= 600 )
1908                 return;
1909
1910         // Use any /user/maintenance.php file
1911         if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
1912                 include( YOURLS_USERDIR.'/maintenance.php' );
1913                 die();
1914         }
1915         
1916         // https://www.youtube.com/watch?v=Xw-m4jEY-Ns
1917         $title   = yourls__( 'Service temporarily unavailable' );
1918         $message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
1919         yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
1920         yourls_die( $message, $title , 503 );
1921
1922 }
1923
1924 /**
1925  * Return current admin page, or null if not an admin page
1926  *
1927  * @return mixed string if admin page, null if not an admin page
1928  * @since 1.6
1929  */
1930 function yourls_current_admin_page() {
1931         if( yourls_is_admin() ) {
1932                 $current = substr( yourls_get_request(), 6 );
1933                 if( $current === false ) 
1934                         $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
1935                         
1936                 return $current;
1937         }
1938         return null;
1939 }
1940
1941 /**
1942  * Check if a URL protocol is allowed
1943  *
1944  * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
1945  * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
1946  * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
1947  *
1948  * @since 1.6
1949  *
1950  * @param string $url URL to be check
1951  * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
1952  * @return boolean true if protocol allowed, false otherwise
1953  */
1954 function yourls_is_allowed_protocol( $url, $protocols = array() ) {
1955         if( ! $protocols ) {
1956                 global $yourls_allowedprotocols;
1957                 $protocols = $yourls_allowedprotocols;
1958         }
1959         
1960         $protocol = yourls_get_protocol( $url );
1961         return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
1962 }
1963
1964 /**
1965  * Get protocol from a URL (eg mailto:, http:// ...)
1966  *
1967  * @since 1.6
1968  *
1969  * @param string $url URL to be check
1970  * @return string Protocol, with slash slash if applicable. Empty string if no protocol
1971  */
1972 function yourls_get_protocol( $url ) {
1973         preg_match( '!^[a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
1974         /*
1975         http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
1976         The scheme name consists of a sequence of characters beginning with a letter and followed by any
1977         combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
1978         case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
1979         with lowercase letters. It is followed by a colon (":").
1980         */
1981         $protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
1982         return yourls_apply_filter( 'get_protocol', $protocol, $url );
1983 }
1984
1985 /**
1986  * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
1987  *
1988  * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
1989  * or return empty string if $strict is true
1990  *
1991  * @since 1.6
1992  * @param string $url URL to relativize
1993  * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
1994  * @return string URL 
1995  */
1996 function yourls_get_relative_url( $url, $strict = true ) {
1997         $url = yourls_sanitize_url( $url );
1998         
1999         // Remove protocols to make it easier
2000         $noproto_url  = str_replace( 'https:', 'http:', $url );
2001         $noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
2002         
2003         // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
2004         $_url = str_replace( $noproto_site . '/', '', $noproto_url );
2005         if( $_url == $noproto_url )
2006                 $_url = ( $strict ? '' : $url );
2007
2008         return yourls_apply_filter( 'get_relative_url', $_url, $url );
2009 }
2010
2011 /**
2012  * Marks a function as deprecated and informs when it has been used. Stolen from WP.
2013  *
2014  * There is a hook deprecated_function that will be called that can be used
2015  * to get the backtrace up to what file and function called the deprecated
2016  * function.
2017  *
2018  * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
2019  *
2020  * This function is to be used in every function that is deprecated.
2021  *
2022  * @since 1.6
2023  * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
2024  *   and the version the function was deprecated in.
2025  * @uses yourls_apply_filters() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
2026  *   trigger or false to not trigger error.
2027  *
2028  * @param string $function The function that was called
2029  * @param string $version The version of WordPress that deprecated the function
2030  * @param string $replacement Optional. The function that should have been called
2031  */
2032 function yourls_deprecated_function( $function, $version, $replacement = null ) {
2033
2034         yourls_do_action( 'deprecated_function', $function, $replacement, $version );
2035
2036         // Allow plugin to filter the output error trigger
2037         if ( YOURLS_DEBUG && yourls_apply_filters( 'deprecated_function_trigger_error', true ) ) {
2038                 if ( ! is_null( $replacement ) )
2039                         trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
2040                 else
2041                         trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
2042         }
2043 }
2044
2045 /**
2046  * Return the value if not an empty string
2047  *
2048  * Used with array_filter(), to remove empty keys but not keys with value 0 or false
2049  *
2050  * @since 1.6
2051  * @param mixed $val Value to test against ''
2052  * @return bool True if not an empty string
2053  */
2054 function yourls_return_if_not_empty_string( $val ) {
2055         return( $val !== '' );
2056 }