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