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