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