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