]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/functions.php
- new constants: YOURLS_COOKIE_LIFE and YOURLS_NONCE_LIFE
[Github/YOURLS.git] / includes / functions.php
1 <?php\r
2 /*\r
3  * YOURLS\r
4  * Function library\r
5  */\r
6 \r
7 if (defined('YOURLS_DEBUG') && YOURLS_DEBUG == true) {\r
8         error_reporting(E_ALL);\r
9 } else {\r
10         error_reporting(E_ERROR | E_PARSE);\r
11 }\r
12 \r
13 // function to convert an integer (1337) to a string (3jk). Input integer processed as a string to beat PHP's int max value\r
14 function yourls_int2string( $id ) {\r
15         $str = yourls_base2base(trim(strval($id)), 10, YOURLS_URL_CONVERT);\r
16         if (YOURLS_URL_CONVERT <= 37)\r
17                 $str = strtolower($str);\r
18         return $str;\r
19 }\r
20 \r
21 // function to convert a string (3jk) to an integer (1337)\r
22 function yourls_string2int( $str ) {\r
23         if (YOURLS_URL_CONVERT <= 37)\r
24                 $str = strtolower($str);\r
25         return yourls_base2base(trim($str), YOURLS_URL_CONVERT, 10);\r
26 }\r
27 \r
28 // Make sure a link keyword (ie "1fv" as in "site.com/1fv") is valid.\r
29 function yourls_sanitize_string($in) {\r
30         if (YOURLS_URL_CONVERT <= 37)\r
31                 $in = strtolower($in);\r
32         return substr(preg_replace('/[^a-zA-Z0-9]/', '', $in), 0, 199);\r
33 }\r
34 \r
35 // A few sanity checks on the URL\r
36 function yourls_sanitize_url($url) {\r
37         // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://')\r
38         $url = str_replace('http://http://', 'http://', $url);\r
39 \r
40         // make sure there's a protocol, add http:// if not\r
41         if ( !preg_match('!^([a-zA-Z]+://)!', $url ) )\r
42                 $url = 'http://'.$url;\r
43         \r
44         $url = yourls_clean_url($url);\r
45         \r
46         return substr( $url, 0, 1999 );\r
47 }\r
48 \r
49 // Function to filter all invalid characters from a URL. Stolen from WP's clean_url()\r
50 function yourls_clean_url( $url ) {\r
51         $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'"()\\x80-\\xff]|i', '', $url );\r
52         $strip = array('%0d', '%0a', '%0D', '%0A');\r
53         $url = yourls_deep_replace($strip, $url);\r
54         $url = str_replace(';//', '://', $url);\r
55         $url = str_replace('&amp;', '&', $url); // Revert & not to break query strings\r
56         \r
57         return $url;\r
58 }\r
59 \r
60 // Perform a replacement while a string is found, eg $subject = '%0%0%0DDD', $search ='%0D' -> $result =''\r
61 // Stolen from WP's _deep_replace\r
62 function yourls_deep_replace($search, $subject){\r
63         $found = true;\r
64         while($found) {\r
65                 $found = false;\r
66                 foreach( (array) $search as $val ) {\r
67                         while(strpos($subject, $val) !== false) {\r
68                                 $found = true;\r
69                                 $subject = str_replace($val, '', $subject);\r
70                         }\r
71                 }\r
72         }\r
73         \r
74         return $subject;\r
75 }\r
76 \r
77 // Make sure an integer is a valid integer (PHP's intval() limits to too small numbers)\r
78 // TODO FIXME FFS: unused ?\r
79 function yourls_sanitize_int($in) {\r
80         return ( substr(preg_replace('/[^0-9]/', '', strval($in) ), 0, 20) );\r
81 }\r
82 \r
83 // Make sure a integer is safe\r
84 // Note: this is not checking for integers, since integers on 32bits system are way too limited\r
85 // TODO: find a way to validate as integer\r
86 function yourls_intval($in) {\r
87         return mysql_real_escape_string($in);\r
88 }\r
89 \r
90 // Escape a string\r
91 function yourls_escape( $in ) {\r
92         return mysql_real_escape_string($in);\r
93 }\r
94 \r
95 // Check to see if a given keyword is reserved (ie reserved URL or an existing page)\r
96 // Returns bool\r
97 function yourls_keyword_is_reserved( $keyword ) {\r
98         global $yourls_reserved_URL;\r
99         \r
100         if ( in_array( $keyword, $yourls_reserved_URL)\r
101                 or file_exists(dirname(dirname(__FILE__))."/pages/$keyword.php")\r
102                 or is_dir(dirname(dirname(__FILE__))."/$keyword")\r
103         )\r
104                 return true;\r
105         \r
106         return false;\r
107 }\r
108 \r
109 // Function: Get IP Address. Returns a DB safe string.\r
110 function yourls_get_IP() {\r
111         if(!empty($_SERVER['HTTP_CLIENT_IP'])) {\r
112                 $ip_address = $_SERVER['HTTP_CLIENT_IP'];\r
113         } else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {\r
114                 $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];\r
115         } else if(!empty($_SERVER['REMOTE_ADDR'])) {\r
116                 $ip_address = $_SERVER['REMOTE_ADDR'];\r
117         } else {\r
118                 $ip_address = '';\r
119         }\r
120         if(strpos($ip_address, ',') !== false) {\r
121                 $ip_address = explode(',', $ip_address);\r
122                 $ip_address = $ip_address[0];\r
123         }\r
124         \r
125         $ip_address = yourls_sanitize_ip( $ip_address );\r
126 \r
127         return $ip_address;\r
128 }\r
129 \r
130 // Sanitize an IP address\r
131 function yourls_sanitize_ip( $ip ) {\r
132         return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );\r
133 }\r
134 \r
135 // Add the "Edit" row\r
136 function yourls_table_edit_row( $keyword ) {\r
137         global $ydb;\r
138         \r
139         $table = YOURLS_DB_TABLE_URL;\r
140         $keyword = yourls_sanitize_string( $keyword );\r
141         $id = yourls_string2int( $keyword ); // used as HTML #id\r
142         $url = $ydb->get_row("SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';");\r
143         $safe_url = stripslashes( $url->url );\r
144         $www = YOURLS_SITE;\r
145         \r
146         if( $url ) {\r
147                 $return = <<<RETURN\r
148 <tr id="edit-$id" class="edit-row"><td colspan="5"><strong>Original URL</strong>:<input type="text" id="edit-url-$id" name="edit-url-$id" value="$safe_url" class="text" size="70" /> <strong>Short URL</strong>: $www/<input type="text" id="edit-keyword-$id" name="edit-keyword-$id" value="$keyword" class="text" size="10" /></td><td colspan="1"><input type="button" id="edit-submit-$id" name="edit-submit-$id" value="Save" title="Save new values" class="button" onclick="edit_save('$id');" />&nbsp;<input type="button" id="edit-close-$id" name="edit-close-$id" value="X" title="Cancel editing" class="button" onclick="hide_edit('$id');" /><input type="hidden" id="old_keyword_$id" value="$keyword"/></td></tr>\r
149 RETURN;\r
150         } else {\r
151                 $return = '<tr><td colspan="6">Error, URL not found</td></tr>';\r
152         }\r
153         \r
154         return $return;\r
155 }\r
156 \r
157 // Add a link row\r
158 function yourls_table_add_row( $keyword, $url, $ip, $clicks, $timestamp ) {\r
159         $keyword = yourls_sanitize_string( $keyword );\r
160         $id = yourls_string2int( $keyword ); // used as HTML #id\r
161         $date = date( 'M d, Y H:i', $timestamp+( YOURLS_HOURS_OFFSET * 3600) );\r
162         $clicks = number_format($clicks, 0, '', '');\r
163         $shorturl = YOURLS_SITE.'/'.$keyword;\r
164         $display_url = htmlentities( yourls_trim_long_string( $url ) );\r
165         $statlink = $shorturl.'+';\r
166         $url = htmlentities( $url );\r
167         \r
168         return <<<ROW\r
169 <tr id="id-$id"><td id="keyword-$id"><a href="$shorturl">$keyword</a></td><td id="url-$id"><a href="$url" title="$url">$display_url</a></td><td id="timestamp-$id">$date</td><td id="ip-$id">$ip</td><td id="clicks-$id">$clicks</td><td class="actions" id="actions-$id"><a href="$statlink" id="statlink-$id" class="button button_stats">&nbsp;&nbsp;&nbsp;</a>&nbsp;<input type="button" id="edit-button-$id" name="edit-button" value="" title="Edit" class="button button_edit" onclick="edit('$id');" />&nbsp;<input type="button" id="delete-button-$id" name="delete-button" value="" title="Delete" class="button button_delete" onclick="remove('$id');" /><input type="hidden" id="keyword_$id" value="$keyword"/></td></tr>\r
170 ROW;\r
171 }\r
172 \r
173 // Get next id a new link will have if no custom keyword provided\r
174 function yourls_get_next_decimal() {\r
175         return (int)yourls_get_option( 'next_id' );\r
176 }\r
177 \r
178 // Update id for next link with no custom keyword\r
179 function yourls_update_next_decimal( $int = '' ) {\r
180         $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;\r
181         return yourls_update_option( 'next_id', $int );\r
182 }\r
183 \r
184 // Delete a link in the DB\r
185 function yourls_delete_link_by_keyword( $keyword ) {\r
186         global $ydb;\r
187 \r
188         $table = YOURLS_DB_TABLE_URL;\r
189         $keyword = yourls_sanitize_string( $keyword );\r
190         return $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';");\r
191 }\r
192 \r
193 // SQL query to insert a new link in the DB. Needs sanitized data. Returns boolean for success or failure of the inserting\r
194 function yourls_insert_link_in_db($url, $keyword) {\r
195         global $ydb;\r
196 \r
197         $table = YOURLS_DB_TABLE_URL;\r
198         $timestamp = date('Y-m-d H:i:s');\r
199         $ip = yourls_get_IP();\r
200         $insert = $ydb->query("INSERT INTO `$table` VALUES('$keyword', '$url', '$timestamp', '$ip', 0);");\r
201         \r
202         return (bool)$insert;\r
203 }\r
204 \r
205 // Add a new link in the DB, either with custom keyword, or find one\r
206 function yourls_add_new_link( $url, $keyword = '' ) {\r
207         global $ydb;\r
208 \r
209         if ( !$url || $url == 'http://' || $url == 'https://' ) {\r
210                 $return['status'] = 'fail';\r
211                 $return['code'] = 'error:nourl';\r
212                 $return['message'] = 'Missing URL input';\r
213                 $return['errorCode'] = '400';\r
214                 return $return;\r
215         }\r
216 \r
217         // Prevent DB flood\r
218         $ip = yourls_get_IP();\r
219         yourls_check_IP_flood( $ip );\r
220 \r
221         $table = YOURLS_DB_TABLE_URL;\r
222         $url = mysql_real_escape_string( yourls_sanitize_url($url) );\r
223         $strip_url = stripslashes($url);\r
224         $url_exists = $ydb->get_row("SELECT keyword,url FROM `$table` WHERE `url` = '".$strip_url."';");\r
225         $return = array();\r
226 \r
227         // New URL : store it -- or: URL exists, but duplicates allowed\r
228         if( !$url_exists || yourls_allow_duplicate_longurls() ) {\r
229 \r
230                 // Custom keyword provided\r
231                 if ( $keyword ) {\r
232                         $keyword = mysql_real_escape_string(yourls_sanitize_string($keyword));\r
233                         if ( !yourls_keyword_is_free($keyword) ) {\r
234                                 // This shorturl either reserved or taken already\r
235                                 $return['status'] = 'fail';\r
236                                 $return['code'] = 'error:keyword';\r
237                                 $return['message'] = 'Short URL '.$keyword.' already exists in database or is reserved';\r
238                         } else {\r
239                                 // all clear, store !\r
240                                 yourls_insert_link_in_db($url, $keyword);\r
241                                 $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );\r
242                                 $return['status'] = 'success';\r
243                                 $return['message'] = $strip_url.' added to database';\r
244                                 $return['html'] = yourls_table_add_row( $keyword, $url, $ip, 0, time() );\r
245                                 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;\r
246                         }\r
247 \r
248                 // Create random keyword        \r
249                 } else {\r
250                         $timestamp = date('Y-m-d H:i:s');\r
251                         $id = yourls_get_next_decimal();\r
252                         $ok = false;\r
253                         do {\r
254                                 $keyword = yourls_int2string( $id );\r
255                                 $free = yourls_keyword_is_free($keyword);\r
256                                 $add_url = @yourls_insert_link_in_db($url, $keyword);\r
257                                 $ok = ($free && $add_url);\r
258                                 if ( $ok === false && $add_url === 1 ) {\r
259                                         // we stored something, but shouldn't have (ie reserved id)\r
260                                         $delete = yourls_delete_link_by_keyword( $keyword );\r
261                                         $return['extra_info'] .= '(deleted '.$keyword.')';\r
262                                 } else {\r
263                                         // everything ok, populate needed vars\r
264                                         $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'date' => $timestamp, 'ip' => $ip );\r
265                                         $return['status'] = 'success';\r
266                                         $return['message'] = $strip_url.' added to database';\r
267                                         $return['html'] = yourls_table_add_row( $keyword, $url, $ip, 0, time() );\r
268                                         $return['shorturl'] = YOURLS_SITE .'/'. $keyword;\r
269                                 }\r
270                                 $id++;\r
271                         } while (!$ok);\r
272                         @yourls_update_next_decimal($id);\r
273                 }\r
274         } else {\r
275                 // URL was already stored\r
276                 $return['status'] = 'fail';\r
277                 $return['code'] = 'error:url';\r
278                 $return['message'] = $strip_url.' already exists in database';\r
279                 $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;\r
280         }\r
281 \r
282         $return['statusCode'] = 200; // regardless of result, this is still a valid request\r
283         return $return;\r
284 }\r
285 \r
286 \r
287 // Edit a link\r
288 function yourls_edit_link($url, $keyword, $newkeyword='') {\r
289         global $ydb;\r
290 \r
291         $table = YOURLS_DB_TABLE_URL;\r
292         $url = mysql_real_escape_string(yourls_sanitize_url($url));\r
293         $keyword = yourls_sanitize_string( $keyword );\r
294         $newkeyword = yourls_sanitize_string( $newkeyword );\r
295         $strip_url = stripslashes($url);\r
296         $old_url = $ydb->get_var("SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';");\r
297         $old_id = $id = yourls_string2int( $keyword );\r
298         $new_id = ( $newkeyword == '' ? $old_id : yourls_string2int( $newkeyword ) );\r
299         \r
300         // Check if new URL is not here already\r
301         if ($old_url != $url) {\r
302                 $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';"));\r
303         } else {\r
304                 $new_url_already_there = false;\r
305         }\r
306         \r
307         // Check if the new keyword is not here already\r
308         if ( $newkeyword != $keyword ) {\r
309                 $keyword_is_ok = yourls_keyword_is_free( $newkeyword );\r
310         } else {\r
311                 $keyword_is_ok = true;\r
312         }\r
313         \r
314         // All clear, update\r
315         if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {\r
316                         $update_url = $ydb->query("UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword' WHERE `keyword` = '$keyword';");\r
317                 if( $update_url ) {\r
318                         $return['url'] = array( 'keyword' => $newkeyword, 'shorturl' => YOURLS_SITE.'/'.$newkeyword, 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'new_id' => $new_id );\r
319                         $return['status'] = 'success';\r
320                         $return['message'] = 'Link updated in database';\r
321                 } else {\r
322                         $return['status'] = 'fail';\r
323                         $return['message'] = 'Error updating '.$strip_url.' (Short URL: '.$keyword.') to database';\r
324                 }\r
325         \r
326         // Nope\r
327         } else {\r
328                 $return['status'] = 'fail';\r
329                 $return['message'] = 'URL or keyword already exists in database';\r
330         }\r
331         \r
332         return $return;\r
333 }\r
334 \r
335 \r
336 // Check if keyword id is free (ie not already taken, and not reserved)\r
337 function yourls_keyword_is_free( $keyword ) {\r
338         global $ydb;\r
339         \r
340         if ( yourls_keyword_is_reserved($keyword) )\r
341                 return false;\r
342                 \r
343         $table = YOURLS_DB_TABLE_URL;\r
344         $already_exists = $ydb->get_var("SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';");\r
345         if ( $already_exists )\r
346                 return false;\r
347 \r
348         return true;\r
349 }\r
350 \r
351 \r
352 // Display a page\r
353 function yourls_page( $page ) {\r
354         $include = dirname(dirname(__FILE__))."/pages/$page.php";\r
355         if (!file_exists($include)) {\r
356                 yourls_die("Page '$page' not found", 'Not found', 404);\r
357         }\r
358         include($include);\r
359         die();  \r
360 }\r
361 \r
362 // Connect to DB\r
363 function yourls_db_connect() {\r
364         global $ydb;\r
365 \r
366         if (!defined('YOURLS_DB_USER')\r
367                 or !defined('YOURLS_DB_PASS')\r
368                 or !defined('YOURLS_DB_NAME')\r
369                 or !defined('YOURLS_DB_HOST')\r
370                 or !class_exists('ezSQL_mysql')\r
371         ) yourls_die ('DB config missigin, or could not find DB class', 'Fatal error', 503);\r
372         \r
373         // Are we standalone or in the WordPress environment?\r
374         if ( class_exists('wpdb') ) {\r
375                 $ydb =  new wpdb(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);\r
376         } else {\r
377                 $ydb =  new ezSQL_mysql(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);\r
378         }\r
379         if ( $ydb->last_error )\r
380                 yourls_die( $ydb->last_error, 'Fatal error', 503 );\r
381         \r
382         if ( defined('YOURLS_DEBUG') && YOURLS_DEBUG === true )\r
383                 $ydb->show_errors = true;\r
384         \r
385         return $ydb;\r
386 }\r
387 \r
388 // Return JSON output. Compatible with PHP prior to 5.2\r
389 function yourls_json_encode($array) {\r
390         if (function_exists('json_encode')) {\r
391                 return json_encode($array);\r
392         } else {\r
393                 require_once(dirname(__FILE__).'/functions-json.php');\r
394                 return yourls_array_to_json($array);\r
395         }\r
396 }\r
397 \r
398 // Return XML output.\r
399 function yourls_xml_encode($array) {\r
400         require_once(dirname(__FILE__).'/functions-xml.php');\r
401         $converter= new yourls_array2xml;\r
402         return $converter->array2xml($array);\r
403 }\r
404 \r
405 // 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
406 function yourls_get_keyword_infos( $keyword, $use_cache = true ) {\r
407         global $ydb;\r
408         $keyword = yourls_sanitize_string( $keyword );\r
409 \r
410         if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) {\r
411                 return $ydb->infos[$keyword];\r
412         }\r
413         \r
414         $table = YOURLS_DB_TABLE_URL;\r
415         $infos = $ydb->get_row("SELECT * FROM `$table` WHERE `keyword` = '$keyword'");\r
416         \r
417         if( $infos ) {\r
418                 $infos = (array)$infos;\r
419                 $ydb->infos[$keyword] = $infos;\r
420         } else {\r
421                 $ydb->infos[$keyword] = false;\r
422         }\r
423                 \r
424         return $ydb->infos[$keyword];\r
425 }\r
426 \r
427 // Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found\r
428 function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {\r
429         global $ydb;\r
430         \r
431         $keyword = yourls_sanitize_string( $keyword );\r
432         $infos = yourls_get_keyword_infos( $keyword );\r
433         \r
434         if ( isset($infos[$field]) && $infos[$field] !== false )\r
435                 return $infos[$field];\r
436 \r
437         return $notfound;       \r
438 }\r
439 \r
440 // Return long URL associated with keyword. Optional $notfound = string default message if nothing found\r
441 function yourls_get_keyword_longurl( $keyword, $notfound = false ) {\r
442         return yourls_get_keyword_info( $keyword, 'url', $notfound );\r
443 }\r
444 \r
445 // Return number of clicks on a keyword. Optional $notfound = string default message if nothing found\r
446 function yourls_get_keyword_clicks( $keyword, $notfound = false ) {\r
447         return yourls_get_keyword_info( $keyword, 'clicks', $notfound );\r
448 }\r
449 \r
450 // Return IP that added a keyword. Optional $notfound = string default message if nothing found\r
451 function yourls_get_keyword_IP( $keyword, $notfound = false ) {\r
452         return yourls_get_keyword_info( $keyword, 'ip', $notfound );\r
453 }\r
454 \r
455 // Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found\r
456 function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {\r
457         return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );\r
458 }\r
459 \r
460 // Update click count on a short URL\r
461 function yourls_update_clicks( $keyword ) {\r
462         global $ydb;\r
463         $keyword = yourls_sanitize_string( $keyword );\r
464         $table = YOURLS_DB_TABLE_URL;\r
465         return $ydb->query("UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'");\r
466 }\r
467 \r
468 // Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return\r
469 function yourls_get_links_stats( $filter = 'top', $limit = 10 ) {\r
470         global $ydb;\r
471 \r
472         switch( $filter ) {\r
473                 case 'bottom':\r
474                         $sort_by = 'clicks';\r
475                         $sort_order = 'asc';\r
476                         break;\r
477                 case 'last':\r
478                         $sort_by = 'timestamp';\r
479                         $sort_order = 'desc';\r
480                         break;\r
481                 case 'rand':\r
482                 case 'random':\r
483                         $sort_by = 'RAND()';\r
484                         $sort_order = '';\r
485                         break;\r
486                 case 'top':\r
487                 default:\r
488                         $sort_by = 'clicks';\r
489                         $sort_order = 'desc';\r
490                         break;\r
491         }\r
492         \r
493         $limit = intval( $limit );\r
494         if ( $limit == 0 )\r
495                 $limit = 1;\r
496         $table_url = YOURLS_DB_TABLE_URL;\r
497         $results = $ydb->get_results("SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT 0, $limit;");\r
498         \r
499         $return = array();\r
500         $i = 1;\r
501         \r
502         foreach ($results as $res) {\r
503                 $return['links']['link_'.$i++] = array(\r
504                         'shorturl' => YOURLS_SITE .'/'. $res->keyword,\r
505                         'url' => $res->url,\r
506                         'timestamp' => $res->timestamp,\r
507                         'ip' => $res->ip,\r
508                         'clicks' => $res->clicks,\r
509                 );\r
510         }\r
511 \r
512         $return['stats'] = yourls_get_db_stats();\r
513         \r
514         $return['statusCode'] = 200;\r
515 \r
516         return $return;\r
517 }\r
518 \r
519 \r
520 // Return array for API stat requests\r
521 function yourls_api_stats( $filter = 'top', $limit = 10 ) {\r
522         $return = yourls_get_links_stats( $filter, $limit );\r
523         $return['simple']  = 'Need either XML or JSON format for stats';\r
524         $return['message'] = 'success';\r
525         return $return;\r
526 }\r
527 \r
528 // Expand short url to long url\r
529 function yourls_api_expand( $shorturl ) {\r
530         $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'\r
531         $keyword = yourls_sanitize_string( $keyword );\r
532         \r
533         $longurl = yourls_get_keyword_longurl( $keyword );\r
534         \r
535         if( $longurl ) {\r
536                 return array(\r
537                         'keyword'  => $keyword,\r
538                         'shorturl' => YOURLS_SITE . "/$keyword",\r
539                         'longurl'  => $longurl,\r
540                         'simple'   => $longurl,\r
541                         'message'  => 'success',\r
542                         'statusCode' => 200,\r
543                 );\r
544         } else {\r
545                 return array(\r
546                         'keyword'  => $keyword,\r
547                         'simple'   => 'not found',\r
548                         'message'  => 'Error: short URL not found',\r
549                         'errorCode' => 404,\r
550                 );\r
551         }\r
552 }\r
553 \r
554 \r
555 // Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array\r
556 function yourls_get_db_stats( $where = '' ) {\r
557         global $ydb;\r
558         $table_url = YOURLS_DB_TABLE_URL;\r
559 \r
560         $totals = $ydb->get_row("SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where");\r
561         return array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );\r
562 }\r
563 \r
564 // Return API result. Dies after this\r
565 function yourls_api_output( $mode, $return ) {\r
566         if( isset( $return['simple'] ) ) {\r
567                 $simple = $return['simple'];\r
568                 unset( $return['simple'] );\r
569         }\r
570         switch ( $mode ) {\r
571                 case 'json':\r
572                         header('Content-type: application/json');\r
573                         echo yourls_json_encode($return);\r
574                         break;\r
575                 \r
576                 case 'xml':\r
577                         header('Content-type: application/xml');\r
578                         echo yourls_xml_encode($return);\r
579                         break;\r
580                         \r
581                 case 'simple':\r
582                 default:\r
583                         if( isset( $simple ) )\r
584                                 echo $simple;\r
585                         break;\r
586         }\r
587         die();\r
588 }\r
589 \r
590 // Display HTML head and <body> tag\r
591 function yourls_html_head( $context = 'index' ) {\r
592         // All components to false, except when specified true\r
593         $share = $insert = $tablesorter = $tabs = false;\r
594         // Load components as needed\r
595         switch ( $context ) {\r
596                 case 'infos':\r
597                         $share = $tabs = true;\r
598                         break;\r
599                         \r
600                 case 'bookmark':\r
601                         $share = $insert = $tablesorter = true;\r
602                         break;\r
603                         \r
604                 case 'index':\r
605                         $insert = $tablesorter = true;\r
606                         break;\r
607                 \r
608                 case 'install':\r
609                 case 'login':\r
610                 case 'new':\r
611                 case 'tools':\r
612                 case 'upgrade':\r
613                         break;\r
614         }\r
615         \r
616         ?>\r
617 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
618 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\r
619 <head>\r
620         <title>YOURLS &raquo; Your Own URL Shortener | <?php echo YOURLS_SITE; ?></title>\r
621         <link rel="icon" type="image/gif" href="<?php echo YOURLS_SITE; ?>/images/favicon.gif" />\r
622         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
623         <meta http-equiv="X-UA-Compatible" content="chrome=1" />\r
624         <meta name="copyright" content="Copyright &copy; 2008-<?php echo date('Y'); ?> YOURS" />\r
625         <meta name="author" content="Ozh Richard, Lester Chan" />\r
626         <meta name="description" content="Insert URL &laquo; YOURLS &raquo; Your Own URL Shortener' | <?php echo YOURLS_SITE; ?>" />\r
627         <script src="<?php echo YOURLS_SITE; ?>/js/jquery-1.3.2.min.js?v=<?php echo YOURLS_VERSION; ?>" type="text/javascript"></script>\r
628         <link rel="stylesheet" href="<?php echo YOURLS_SITE; ?>/css/style.css?v=<?php echo YOURLS_VERSION; ?>" type="text/css" media="screen" />\r
629         <?php if ($tabs) { ?>\r
630                 <link rel="stylesheet" href="<?php echo YOURLS_SITE; ?>/css/infos.css?v=<?php echo YOURLS_VERSION; ?>" type="text/css" media="screen" />\r
631                 <script src="<?php echo YOURLS_SITE; ?>/js/infos.js?v=<?php echo YOURLS_VERSION; ?>" type="text/javascript"></script>\r
632         <?php } ?>\r
633         <?php if ($tablesorter) { ?>\r
634                 <link rel="stylesheet" href="<?php echo YOURLS_SITE; ?>/css/tablesorter.css?v=<?php echo YOURLS_VERSION; ?>" type="text/css" media="screen" />\r
635                 <script src="<?php echo YOURLS_SITE; ?>/js/jquery.tablesorter.min.js?v=<?php echo YOURLS_VERSION; ?>" type="text/javascript"></script>\r
636         <?php } ?>\r
637         <?php if ($insert) { ?>\r
638                 <script src="<?php echo YOURLS_SITE; ?>/js/insert.js?v=<?php echo YOURLS_VERSION; ?>" type="text/javascript"></script>\r
639         <?php } ?>\r
640         <?php if ($share) { ?>\r
641                 <link rel="stylesheet" href="<?php echo YOURLS_SITE; ?>/css/share.css?v=<?php echo YOURLS_VERSION; ?>" type="text/css" media="screen" />\r
642                 <script src="<?php echo YOURLS_SITE; ?>/js/share.js?v=<?php echo YOURLS_VERSION; ?>" type="text/javascript"></script>\r
643         <?php } ?>\r
644 </head>\r
645 <body class="<?php echo $context; ?>">\r
646 <div id="wrap">\r
647         <?php\r
648 }\r
649 \r
650 // Display HTML footer (including closing body & html tags)\r
651 function yourls_html_footer() {\r
652         global $ydb;\r
653         \r
654         $num_queries = $ydb->num_queries > 1 ? $ydb->num_queries.' queries' : $ydb->num_queries.' query';\r
655         ?>\r
656         </div> <?php // wrap ?>\r
657         <div id="footer"><p>Powered by <a href="http://yourls.org/" title="YOURLS">YOURLS</a> v<?php echo YOURLS_VERSION; echo ' &ndash; '.$num_queries; ?></p></div>\r
658         <?php if( defined('YOURLS_DEBUG') && YOURLS_DEBUG == true ) {\r
659                 echo '<p>'. $ydb->all_queries .'<p>';\r
660         } ?>\r
661         </body>\r
662         </html>\r
663         <?php\r
664 }\r
665 \r
666 // Display "Add new URL" box\r
667 function yourls_html_addnew( $url = '', $keyword = '' ) {\r
668         $url = $url ? $url : 'http://';\r
669         ?>\r
670         <div id="new_url">\r
671                 <div>\r
672                         <form id="new_url_form" action="" method="get">\r
673                                 <div><strong>Enter the URL</strong>:<input type="text" id="add-url" name="url" value="<?php echo $url; ?>" class="text" size="80" />\r
674                                 Optional: <strong>Custom short URL</strong>:<input type="text" id="add-keyword" name="keyword" value="<?php echo $keyword; ?>" class="text" size="8" />\r
675                                 <input type="button" id="add-button" name="add-button" value="Shorten The URL" class="button" onclick="add();" /></div>\r
676                         </form>\r
677                         <div id="feedback" style="display:none"></div>\r
678                 </div>\r
679         </div>\r
680         <?php\r
681 }\r
682 \r
683 // Display main table's footer\r
684 function yourls_html_tfooter( $params = array() ) {\r
685         extract( $params ); // extract $search_text, $page, $search_in_sql ...\r
686 \r
687         ?>\r
688         <tfoot>\r
689                 <tr>\r
690                         <th colspan="4" style="text-align: left;">\r
691                                 <form action="" method="get">\r
692                                         <div>\r
693                                                 <div style="float:right;">\r
694                                                         <input type="submit" id="submit-sort" value="Filter" class="button primary" />\r
695                                                         &nbsp;\r
696                                                         <input type="button" id="submit-clear-filter" value="Clear Filter" class="button" onclick="window.parent.location.href = 'index.php'" />\r
697                                                 </div>\r
698 \r
699                                                 Search&nbsp;for&nbsp;\r
700                                                 <input type="text" name="s_search" class="text" size="20" value="<?php echo $search_text; ?>" />\r
701                                                 &nbsp;in&nbsp;\r
702                                                 <select name="s_in" size="1">\r
703                                                         <!-- <option value="id"<?php if($search_in_sql == 'id') { echo ' selected="selected"'; } ?>>ID</option> -->\r
704                                                         <option value="url"<?php if($search_in_sql == 'url') { echo ' selected="selected"'; } ?>>URL</option>\r
705                                                         <option value="ip"<?php if($search_in_sql == 'ip') { echo ' selected="selected"'; } ?>>IP</option>\r
706                                                 </select>\r
707                                                 &ndash;&nbsp;Order&nbsp;by&nbsp;\r
708                                                 <select name="s_by" size="1">\r
709                                                         <option value="id"<?php if($sort_by_sql == 'id') { echo ' selected="selected"'; } ?>>ID</option>\r
710                                                         <option value="url"<?php if($sort_by_sql == 'url') { echo ' selected="selected"'; } ?>>URL</option>\r
711                                                         <option value="timestamp"<?php if($sort_by_sql == 'timestamp') { echo ' selected="selected"'; } ?>>Date</option>\r
712                                                         <option value="ip"<?php if($sort_by_sql == 'ip') { echo ' selected="selected"'; } ?>>IP</option>\r
713                                                         <option value="clicks"<?php if($sort_by_sql == 'clicks') { echo ' selected="selected"'; } ?>>Clicks</option>\r
714                                                 </select>\r
715                                                 <select name="s_order" size="1">\r
716                                                         <option value="asc"<?php if($sort_order_sql == 'asc') { echo ' selected="selected"'; } ?>>Ascending</option>\r
717                                                         <option value="desc"<?php if($sort_order_sql == 'desc') { echo ' selected="selected"'; } ?>>Descending</option>\r
718                                                 </select>\r
719                                                 &ndash;&nbsp;Show&nbsp;\r
720                                                 <input type="text" name="perpage" class="text" size="2" value="<?php echo $perpage; ?>" />&nbsp;rows<br/>\r
721                                                 \r
722                                                 Show links with\r
723                                                 <select name="link_filter" size="1">\r
724                                                         <option value="more"<?php if($link_filter === 'more') { echo ' selected="selected"'; } ?>>more</option>\r
725                                                         <option value="less"<?php if($link_filter === 'less') { echo ' selected="selected"'; } ?>>less</option>\r
726                                                 </select>\r
727                                                 than\r
728                                                 <input type="text" name="link_limit" class="text" size="4" value="<?php echo $link_limit; ?>" />clicks\r
729 \r
730                                                 \r
731                                         </div>\r
732                                 </form>\r
733                         </th>\r
734                         <th colspan="3" style="text-align: right;">\r
735                                 Pages (<?php echo $total_pages; ?>):\r
736                                 <?php\r
737                                         if ($page >= 4) {\r
738                                                 echo '<b><a href="'.$base_page.'?s_by='.$sort_by_sql.'&amp;s_order='.$sort_order_sql.$search_url.'&amp;perpage='.$perpage.'&amp;page=1'.'" title="Go to First Page">&laquo; First</a></b> ... ';\r
739                                         }\r
740                                         if($page > 1) {\r
741                                                 echo ' <b><a href="'.$base_page.'?s_by='.$sort_by_sql.'&amp;s_order='.$sort_order_sql.$search_url.'&amp;perpage='.$perpage.'&amp;page='.($page-1).'" title="&laquo; Go to Page '.($page-1).'">&laquo;</a></b> ';\r
742                                         }\r
743                                         for($i = $page - 2 ; $i  <= $page +2; $i++) {\r
744                                                 if ($i >= 1 && $i <= $total_pages) {\r
745                                                         if($i == $page) {\r
746                                                                 echo "<strong>[$i]</strong> ";\r
747                                                         } else {\r
748                                                                 echo '<a href="'.$base_page.'?s_by='.$sort_by_sql.'&amp;s_order='.$sort_order_sql.$search_url.'&amp;perpage='.$perpage.'&amp;page='.($i).'" title="Page '.$i.'">'.$i.'</a> ';\r
749                                                         }\r
750                                                 }\r
751                                         }\r
752                                         if($page < $total_pages) {\r
753                                                 echo ' <b><a href="'.$base_page.'?s_by='.$sort_by_sql.'&amp;s_order='.$sort_order_sql.$search_url.'&amp;perpage='.$perpage.'&amp;page='.($page+1).'" title="Go to Page '.($page+1).' &raquo;">&raquo;</a></b> ';\r
754                                         }\r
755                                         if (($page+2) < $total_pages) {\r
756                                                 echo ' ... <b><a href="'.$base_page.'?s_by='.$sort_by_sql.'&amp;s_order='.$sort_order_sql.$search_url.'&amp;perpage='.$perpage.'&amp;page='.($total_pages).'" title="Go to Last Page">Last &raquo;</a></b>';\r
757                                         }\r
758                                 ?>\r
759                         </th>\r
760                 </tr>\r
761         </tfoot>\r
762         <?php\r
763 }\r
764 \r
765 // Display the Quick Share box of the tools.php page\r
766 function yourls_share_box( $longurl, $shorturl, $title='', $text='', $shortlink_title = '<h2>Your short link</h2>', $share_title = '<h2>Quick Share</h2>' ) {\r
767         $text = ( $text ? '"'.$text.'" ' : '' );\r
768         $title = ( $title ? "$title " : '' );\r
769         $share = htmlspecialchars_decode( $title.$text.$shorturl );\r
770         $_share = rawurlencode( $share );\r
771         $_url = rawurlencode( $shorturl );\r
772         $count = 140 - strlen( $share );\r
773         ?>\r
774         \r
775         <div id="shareboxes">\r
776 \r
777                 <div id="copybox" class="share">\r
778                 <?php echo $shortlink_title; ?>\r
779                         <p><input id="copylink" class="text" size="40" value="<?php echo $shorturl; ?>" /></p>\r
780                         <p><small>Original link: <a href="<?php echo $longurl; ?>"><?php echo $longurl; ?></a></small></p>\r
781                 </div>\r
782 \r
783                 <div id="sharebox" class="share">\r
784                         <?php echo $share_title; ?>\r
785                         <div id="tweet">\r
786                                 <span id="charcount"><?php echo $count; ?></span>\r
787                                 <textarea id="tweet_body"><?php echo $share; ?></textarea>\r
788                         </div>\r
789                         <p id="share_links">Share with \r
790                                 <a id="share_tw" href="http://twitter.com/home?status=<?php echo $_share; ?>" title="Tweet this!" onclick="share('tw');return false">Twitter</a>\r
791                                 <a id="share_fb" href="http://www.facebook.com/share.php?u=<?php echo $_url; ?>" title="Share on Facebook" onclick="share('fb');return false;">Facebook</a>\r
792                                 <a id="share_ff" href="http://friendfeed.com/share/bookmarklet/frame#title=<?php echo $_share; ?>" title="Share on Friendfeed" onclick="javascript:share('ff');return false;">FriendFeed</a>\r
793                         </p>\r
794                         </div>\r
795                 </div>\r
796         \r
797         </div>\r
798         \r
799         <?php\r
800 }\r
801 \r
802 // Get number of SQL queries performed\r
803 function yourls_get_num_queries() {\r
804         global $ydb;\r
805 \r
806         return $ydb->num_queries;\r
807 }\r
808 \r
809 // Compat http_build_query for PHP4\r
810 if (!function_exists('http_build_query')) {\r
811         function http_build_query($data, $prefix=null, $sep=null) {\r
812                 return yourls_http_build_query($data, $prefix, $sep);\r
813         }\r
814 }\r
815 \r
816 // from php.net (modified by Mark Jaquith to behave like the native PHP5 function)\r
817 function yourls_http_build_query($data, $prefix=null, $sep=null, $key='', $urlencode=true) {\r
818         $ret = array();\r
819 \r
820         foreach ( (array) $data as $k => $v ) {\r
821                 if ( $urlencode)\r
822                         $k = urlencode($k);\r
823                 if ( is_int($k) && $prefix != null )\r
824                         $k = $prefix.$k;\r
825                 if ( !empty($key) )\r
826                         $k = $key . '%5B' . $k . '%5D';\r
827                 if ( $v === NULL )\r
828                         continue;\r
829                 elseif ( $v === FALSE )\r
830                         $v = '0';\r
831 \r
832                 if ( is_array($v) || is_object($v) )\r
833                         array_push($ret,yourls_http_build_query($v, '', $sep, $k, $urlencode));\r
834                 elseif ( $urlencode )\r
835                         array_push($ret, $k.'='.urlencode($v));\r
836                 else\r
837                         array_push($ret, $k.'='.$v);\r
838         }\r
839 \r
840         if ( NULL === $sep )\r
841                 $sep = ini_get('arg_separator.output');\r
842 \r
843         return implode($sep, $ret);\r
844 }\r
845 \r
846 // Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.\r
847 function yourls_get_user_agent() {\r
848         if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )\r
849                 return '-';\r
850         \r
851         $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));\r
852         $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );\r
853                 \r
854         return substr( $ua, 0, 254 );\r
855 }\r
856 \r
857 // Redirect to another page\r
858 function yourls_redirect( $location, $code = 301 ) {\r
859         // Anti fool check: cannot redirect to the URL we currently are on\r
860         if( preg_replace('!^[^:]+://!', '', $location) != $_SERVER["SERVER_NAME"].$_SERVER['REQUEST_URI'] ) {\r
861                 yourls_status_header( $code );\r
862                 header("Location: $location");\r
863                 die();\r
864         }\r
865 }\r
866 \r
867 // Set HTTP status header\r
868 function yourls_status_header( $code = 200 ) {\r
869         if( headers_sent() )\r
870                 return;\r
871                 \r
872         $protocol = $_SERVER["SERVER_PROTOCOL"];\r
873         if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )\r
874                 $protocol = 'HTTP/1.0';\r
875 \r
876         $code = intval( $code );\r
877         $desc = yourls_get_HTTP_status($code);\r
878 \r
879         @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups\r
880 }\r
881 \r
882 // 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
883 function yourls_redirect_javascript( $location, $dontwait = true ) {\r
884         if( $dontwait ) {\r
885         echo <<<REDIR\r
886         <script type="text/javascript">\r
887         window.location="$location";\r
888         </script>\r
889         <small>(if you are not redirected after 10 seconds, please <a href="$location">click here</a>)</small>\r
890 REDIR;\r
891         } else {\r
892         echo <<<MANUAL\r
893         <p>Please <a href="$location">click here</a></p>\r
894 MANUAL;\r
895         }\r
896 }\r
897 \r
898 // Return a HTTP status code\r
899 function yourls_get_HTTP_status( $code ) {\r
900         $code = intval( $code );\r
901         $headers_desc = array(\r
902                 100 => 'Continue',\r
903                 101 => 'Switching Protocols',\r
904                 102 => 'Processing',\r
905 \r
906                 200 => 'OK',\r
907                 201 => 'Created',\r
908                 202 => 'Accepted',\r
909                 203 => 'Non-Authoritative Information',\r
910                 204 => 'No Content',\r
911                 205 => 'Reset Content',\r
912                 206 => 'Partial Content',\r
913                 207 => 'Multi-Status',\r
914                 226 => 'IM Used',\r
915 \r
916                 300 => 'Multiple Choices',\r
917                 301 => 'Moved Permanently',\r
918                 302 => 'Found',\r
919                 303 => 'See Other',\r
920                 304 => 'Not Modified',\r
921                 305 => 'Use Proxy',\r
922                 306 => 'Reserved',\r
923                 307 => 'Temporary Redirect',\r
924 \r
925                 400 => 'Bad Request',\r
926                 401 => 'Unauthorized',\r
927                 402 => 'Payment Required',\r
928                 403 => 'Forbidden',\r
929                 404 => 'Not Found',\r
930                 405 => 'Method Not Allowed',\r
931                 406 => 'Not Acceptable',\r
932                 407 => 'Proxy Authentication Required',\r
933                 408 => 'Request Timeout',\r
934                 409 => 'Conflict',\r
935                 410 => 'Gone',\r
936                 411 => 'Length Required',\r
937                 412 => 'Precondition Failed',\r
938                 413 => 'Request Entity Too Large',\r
939                 414 => 'Request-URI Too Long',\r
940                 415 => 'Unsupported Media Type',\r
941                 416 => 'Requested Range Not Satisfiable',\r
942                 417 => 'Expectation Failed',\r
943                 422 => 'Unprocessable Entity',\r
944                 423 => 'Locked',\r
945                 424 => 'Failed Dependency',\r
946                 426 => 'Upgrade Required',\r
947 \r
948                 500 => 'Internal Server Error',\r
949                 501 => 'Not Implemented',\r
950                 502 => 'Bad Gateway',\r
951                 503 => 'Service Unavailable',\r
952                 504 => 'Gateway Timeout',\r
953                 505 => 'HTTP Version Not Supported',\r
954                 506 => 'Variant Also Negotiates',\r
955                 507 => 'Insufficient Storage',\r
956                 510 => 'Not Extended'\r
957         );\r
958 \r
959         if ( isset( $headers_desc[$code] ) )\r
960                 return $headers_desc[$code];\r
961         else\r
962                 return '';\r
963 }\r
964 \r
965 \r
966 // Log a redirect (for stats)\r
967 function yourls_log_redirect( $keyword ) {\r
968         if ( !yourls_do_log_redirect() )\r
969                 return true;\r
970 \r
971         global $ydb;\r
972         $table = YOURLS_DB_TABLE_LOG;\r
973         \r
974         $keyword = yourls_sanitize_string( $keyword );\r
975         $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' );\r
976         $ua = yourls_get_user_agent();\r
977         $ip = yourls_get_IP();\r
978         $location = yourls_geo_ip_to_countrycode( $ip );\r
979         \r
980         return $ydb->query( "INSERT INTO `$table` VALUES ('', NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" );\r
981 }\r
982 \r
983 // Check if we want to not log redirects (for stats)\r
984 function yourls_do_log_redirect() {\r
985         return ( !defined('YOURLS_NOSTATS') || YOURLS_NOSTATS != true );\r
986 }\r
987 \r
988 // Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/\r
989 function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {\r
990         if ( !file_exists( dirname(__FILE__).'/geo/GeoIP.dat') || !file_exists( dirname(__FILE__).'/geo/geoip.inc') )\r
991                 return $default;\r
992 \r
993         if ( $ip == '' )\r
994                 $ip = yourls_get_IP();\r
995                 \r
996         require_once( dirname(__FILE__).'/geo/geoip.inc') ;\r
997         $gi = geoip_open( dirname(__FILE__).'/geo/GeoIP.dat', GEOIP_STANDARD);\r
998         $location = geoip_country_code_by_addr($gi, $ip);\r
999         geoip_close($gi);\r
1000 \r
1001         return $location;\r
1002 }\r
1003 \r
1004 // Converts a 2 letter country code to long name (ie AU -> Australia)\r
1005 function yourls_geo_countrycode_to_countryname( $code ) {\r
1006         // Load the Geo class if not already done\r
1007         if( !class_exists('GeoIP') ) {\r
1008                 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');\r
1009         }\r
1010         \r
1011         if( class_exists('GeoIP') ) {\r
1012                 $geo = new GeoIP;\r
1013                 $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[$code];\r
1014                 $long = $geo->GEOIP_COUNTRY_NAMES[$id];\r
1015                 return $long;\r
1016         } else {\r
1017                 return false;\r
1018         }\r
1019 }\r
1020 \r
1021 // Return flag URL from 2 letter country code\r
1022 function yourls_geo_get_flag( $code ) {\r
1023         // Load the Geo class if not already done\r
1024         if( !class_exists('GeoIP') ) {\r
1025                 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');\r
1026         }\r
1027         \r
1028         if( class_exists('GeoIP') ) {\r
1029                 return YOURLS_SITE.'/includes/geo/flags/flag_'.(strtolower($code)).'.gif';\r
1030         } else {\r
1031                 return false;\r
1032         }\r
1033 }\r
1034 \r
1035 \r
1036 // Check if an upgrade is needed\r
1037 function yourls_upgrade_is_needed() {\r
1038         // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS\r
1039         list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();\r
1040         if( $currentsql < YOURLS_DB_VERSION )\r
1041                 return true;\r
1042                 \r
1043         return false;\r
1044 }\r
1045 \r
1046 // Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.\r
1047 function yourls_get_current_version_from_sql() {\r
1048         $currentver = yourls_get_option( 'version' );\r
1049         $currentsql = yourls_get_option( 'db_version' );\r
1050 \r
1051         // Values if version is 1.3\r
1052         if( !$currentver )\r
1053                 $currentver = '1.3';\r
1054         if( !$currentsql )\r
1055                 $currentsql = '100';\r
1056                 \r
1057         return array( $currentver, $currentsql);\r
1058 }\r
1059 \r
1060 // Read an option from DB (or from cache if available). Return value or $default if not found\r
1061 function yourls_get_option( $option_name, $default = false ) {\r
1062         global $ydb;\r
1063         if ( !isset( $ydb->option[$option_name] ) ) {\r
1064                 $table = YOURLS_DB_TABLE_OPTIONS;\r
1065                 $option_name = yourls_escape( $option_name );\r
1066                 $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );\r
1067                 if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values\r
1068                         $value = $row->option_value;\r
1069                 } else { // option does not exist, so we must cache its non-existence\r
1070                         $value = $default;\r
1071                 }\r
1072                 $ydb->option[$option_name] = yourls_maybe_unserialize( $value );\r
1073         }\r
1074 \r
1075         return $ydb->option[$option_name];\r
1076 }\r
1077 \r
1078 // Read all options from DB at once\r
1079 function yourls_get_all_options() {\r
1080         global $ydb;\r
1081         $table = YOURLS_DB_TABLE_OPTIONS;\r
1082         \r
1083         $allopt = $ydb->get_results("SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1");\r
1084         \r
1085         foreach( (array)$allopt as $option ) {\r
1086                 $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value );\r
1087         }\r
1088 }\r
1089 \r
1090 // Update (add if doesn't exist) an option to DB\r
1091 function yourls_update_option( $option_name, $newvalue ) {\r
1092         global $ydb;\r
1093         $table = YOURLS_DB_TABLE_OPTIONS;\r
1094 \r
1095         $safe_option_name = yourls_escape( $option_name );\r
1096 \r
1097         $oldvalue = yourls_get_option( $safe_option_name );\r
1098 \r
1099         // If the new and old values are the same, no need to update.\r
1100         if ( $newvalue === $oldvalue )\r
1101                 return false;\r
1102 \r
1103         if ( false === $oldvalue ) {\r
1104                 yourls_add_option( $option_name, $newvalue );\r
1105                 return true;\r
1106         }\r
1107 \r
1108         $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );\r
1109 \r
1110         $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'");\r
1111 \r
1112         if ( $ydb->rows_affected == 1 ) {\r
1113                 $ydb->option[$option_name] = $newvalue;\r
1114                 return true;\r
1115         }\r
1116         return false;\r
1117 }\r
1118 \r
1119 // Add an option to the DB\r
1120 function yourls_add_option( $name, $value = '' ) {\r
1121         global $ydb;\r
1122         $table = YOURLS_DB_TABLE_OPTIONS;\r
1123         $safe_name = yourls_escape( $name );\r
1124 \r
1125         // Make sure the option doesn't already exist. We can check the 'notoptions' cache before we ask for a db query\r
1126         if ( false !== yourls_get_option( $safe_name ) )\r
1127                 return;\r
1128 \r
1129         $_value = yourls_escape( yourls_maybe_serialize( $value ) );\r
1130 \r
1131         $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );\r
1132         $ydb->option[$name] = $value;\r
1133         return;\r
1134 }\r
1135 \r
1136 \r
1137 // Delete an option from the DB\r
1138 function yourls_delete_option( $name ) {\r
1139         global $ydb;\r
1140         $table = YOURLS_DB_TABLE_OPTIONS;\r
1141         $name = yourls_escape( $name );\r
1142 \r
1143         // Get the ID, if no ID then return\r
1144         $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );\r
1145         if ( is_null($option) || !$option->option_id )\r
1146                 return false;\r
1147         $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );\r
1148         return true;\r
1149 }\r
1150 \r
1151 \r
1152 \r
1153 // Serialize data if needed. Stolen from WordPress\r
1154 function yourls_maybe_serialize( $data ) {\r
1155         if ( is_array( $data ) || is_object( $data ) )\r
1156                 return serialize( $data );\r
1157 \r
1158         if ( yourls_is_serialized( $data ) )\r
1159                 return serialize( $data );\r
1160 \r
1161         return $data;\r
1162 }\r
1163 \r
1164 // Check value to find if it was serialized. Stolen from WordPress\r
1165 function yourls_is_serialized( $data ) {\r
1166         // if it isn't a string, it isn't serialized\r
1167         if ( !is_string( $data ) )\r
1168                 return false;\r
1169         $data = trim( $data );\r
1170         if ( 'N;' == $data )\r
1171                 return true;\r
1172         if ( !preg_match( '/^([adObis]):/', $data, $badions ) )\r
1173                 return false;\r
1174         switch ( $badions[1] ) {\r
1175                 case 'a' :\r
1176                 case 'O' :\r
1177                 case 's' :\r
1178                         if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )\r
1179                                 return true;\r
1180                         break;\r
1181                 case 'b' :\r
1182                 case 'i' :\r
1183                 case 'd' :\r
1184                         if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )\r
1185                                 return true;\r
1186                         break;\r
1187         }\r
1188         return false;\r
1189 }\r
1190 \r
1191 // Unserialize value only if it was serialized. Stolen from WP\r
1192 function yourls_maybe_unserialize( $original ) {\r
1193         if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in\r
1194                 return @unserialize( $original );\r
1195         return $original;\r
1196 }\r
1197 \r
1198 // Determine if the current page is private\r
1199 function yourls_is_private() {\r
1200         if (defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true) {\r
1201 \r
1202                 // Allow overruling of particular pages\r
1203                 $current = basename( $_SERVER["SCRIPT_NAME"] );\r
1204 \r
1205                 switch( $current ) {\r
1206                 \r
1207                 case 'yourls-api.php':\r
1208                         if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )\r
1209                                 return true;\r
1210                         break;\r
1211                                 \r
1212                 case 'yourls-infos.php':\r
1213                         if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )\r
1214                                 return true;\r
1215                         break;\r
1216                 \r
1217                 default:\r
1218                         return true;\r
1219                         break;\r
1220                 }\r
1221         }\r
1222         \r
1223         return false;\r
1224 }\r
1225 \r
1226 // Show login form if required\r
1227 function yourls_maybe_require_auth() {\r
1228         if( yourls_is_private() )\r
1229                 require_once( dirname(__FILE__).'/auth.php' );\r
1230 }\r
1231 \r
1232 // Echo HTML tag for a link\r
1233 function yourls_html_link( $href, $title = '', $element = '' ) {\r
1234         if( !$title )\r
1235                 $title = $href;\r
1236         if( $element )\r
1237                 $element = "id='$element'";\r
1238         echo "<a href='$href' $element>$title</a>";\r
1239 }\r
1240 \r
1241 // Return word or words if more than one\r
1242 function yourls_plural( $word, $count=1 ) {\r
1243         return $word . ($count > 1 ? 's' : '');\r
1244 }\r
1245 \r
1246 // Return trimmed string\r
1247 function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) {\r
1248         if ( strlen( $string ) > $length ) {\r
1249                 $string = substr( $string, 0, $length - strlen( $append ) ) . $append;  \r
1250         }\r
1251         return $string;\r
1252 }\r
1253 \r
1254 // Allow several short URLs for the same long URL ?\r
1255 function yourls_allow_duplicate_longurls() {\r
1256         return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );\r
1257 }\r
1258 \r
1259 // Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords.\r
1260 function yourls_get_duplicate_keywords( $longurl ) {\r
1261         if( !yourls_allow_duplicate_longurls() )\r
1262                 return NULL;\r
1263         \r
1264         global $ydb;\r
1265         $longurl = mysql_real_escape_string( yourls_sanitize_url($longurl) );\r
1266         $table = YOURLS_DB_TABLE_URL;\r
1267         \r
1268         return $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" );\r
1269 }\r
1270 \r
1271 // Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.\r
1272 function yourls_check_IP_flood( $ip = '' ) {\r
1273         if(\r
1274                 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||\r
1275                 !defined('YOURLS_FLOOD_DELAY_SECONDS')\r
1276         )\r
1277                 return true;\r
1278 \r
1279         $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );\r
1280 \r
1281         // Don't throttle whitelist IPs\r
1282         if( defined('YOURLS_FLOOD_IP_WHITELIST' && YOURLS_FLOOD_IP_WHITELIST ) ) {\r
1283                 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );\r
1284                 foreach( $whitelist_ips as $whitelist_ip ) {\r
1285                         $whitelist_ip = trim( $whitelist_ip );\r
1286                         if ( $whitelist_ip == $ip )\r
1287                                 return true;\r
1288                 }\r
1289         }\r
1290         \r
1291         // Don't throttle logged in users\r
1292         if( yourls_is_private() ) {\r
1293                  if( yourls_is_valid_user() === true )\r
1294                         return true;\r
1295         }\r
1296 \r
1297         global $ydb;\r
1298         $table = YOURLS_DB_TABLE_URL;\r
1299         \r
1300         $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );\r
1301         if( $lasttime ) {\r
1302                 $now = date( 'U' );\r
1303                 $then = date( 'U', strtotime( $lasttime ) );\r
1304                 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {\r
1305                         // Flood!\r
1306                         yourls_die( 'Too many URLs added too fast. Slow down please.', 'Forbidden', 403 );\r
1307                 }\r
1308         }\r
1309         \r
1310         return true;\r
1311 }\r
1312 \r
1313 // Die die die\r
1314 function yourls_die( $message = '', $title = '', $header_code = 200 ) {\r
1315         yourls_status_header( $header_code );\r
1316         \r
1317         yourls_html_head();\r
1318         ?>\r
1319         <h1>\r
1320                 <a href="<?php echo $base_page; ?>" title="YOURLS"><span>YOURLS</span>: <span>Y</span>our <span>O</span>wn <span>URL</span> <span>S</span>hortener<br/>\r
1321                 <img src="<?php echo YOURLS_SITE; ?>/images/yourls-logo.png" alt="YOURLS" title="YOURLS" style="border: 0px;" /></a>\r
1322         </h1>\r
1323         <?php\r
1324         echo "<h2>$title</h2>";\r
1325         echo "<p>$message</p>";\r
1326         yourls_html_footer();\r
1327         die();\r
1328 }\r
1329 \r
1330 // Check if YOURLS is installed\r
1331 function yourls_is_installed() {\r
1332         static $is_installed = false;\r
1333         if ( $is_installed === false ) {\r
1334                 $check_14 = $check_13 = false;\r
1335                 global $ydb;\r
1336                 if( defined('YOURLS_DB_TABLE_NEXTDEC') )\r
1337                         $check_13 = $ydb->get_var('SELECT next_id FROM '.YOURLS_DB_TABLE_NEXTDEC);\r
1338                 $check_14 = yourls_get_option( 'version' );\r
1339                 $is_installed = $check_13 || $check_14;\r
1340         }\r
1341         return $is_installed;\r
1342 }\r
1343 \r
1344 // Compat for PHP < 5.1\r
1345 if ( !function_exists('htmlspecialchars_decode') ) {\r
1346         function htmlspecialchars_decode($text) {\r
1347                 return strtr($text, array_flip(get_html_translation_table(HTML_SPECIALCHARS)));\r
1348         }\r
1349 }\r
1350 \r
1351 // Generate random string of (int)$lenght length and type $type (see function for details)\r
1352 function yourls_rnd_string ( $length = 5, $type = 1 ) {\r
1353         $str = "";\r
1354         $length = intval( $length );\r
1355 \r
1356         // define possible characters\r
1357         switch ( $type ) {\r
1358                 // no vowels to make no offending word, no 0 or 1 to avoid confusion betwee letters & digits. Perfect for passwords.\r
1359                 case '1':\r
1360                         $possible = "23456789bcdfghjkmnpqrstvwxyz";\r
1361                         break;\r
1362                 \r
1363                 // all letters, lowercase\r
1364                 case '2':\r
1365                         $possible = "abcdefghijklmnopqrstuvwxyz";\r
1366                         break;\r
1367                 \r
1368                 // all letters, lowercase + uppercase\r
1369                 case '3':\r
1370                         $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";\r
1371                         break;\r
1372                 \r
1373                 // all digits & letters lowercase \r
1374                 case '4':\r
1375                         $possible = "0123456789abcdefghijklmnopqrstuvwxyz";\r
1376                         break;\r
1377                 \r
1378                 // all digits & letters lowercase + uppercase\r
1379                 case '5':\r
1380                         $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";\r
1381                         break;\r
1382                 \r
1383         }\r
1384 \r
1385         $i = 0;\r
1386         while ($i < $length) {\r
1387         $str .= substr($possible, mt_rand(0, strlen($possible)-1), 1);\r
1388                 $i++;\r
1389         }\r
1390         \r
1391         return $str;\r
1392 }\r
1393 \r
1394 // Return salted string\r
1395 function yourls_salt( $string ) {\r
1396         $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;\r
1397         return md5 ($string . $salt);\r
1398 }\r
1399 \r
1400 // Return a time-dependent string for nonce creation\r
1401 function yourls_tick() {\r
1402         if( !defined('YOURLS_NONCE_LIFE') )\r
1403                 define( 'YOURLS_NONCE_LIFE', 3600 );\r
1404         return ceil( time() / YOURLS_NONCE_LIFE );\r
1405 }\r
1406 \r
1407 // Create a time limited, action limited and user limited token\r
1408 function yourls_create_nonce( $action = '-1', $user = false ) {\r
1409         if( false == $user )\r
1410                 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';\r
1411         $tick = yourls_tick();\r
1412         return substr( yourls_salt($tick . $action . $user), 0, 10 );\r
1413 }\r
1414 \r
1415 // Check validity of a nonce (ie time span, user and action match)\r
1416 function yourls_verify_nonce( $nonce, $action = -1, $user = false ) {\r
1417         if( false == $user )\r
1418                 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';\r
1419         $valid = yourls_create_nonce( $action, $user );\r
1420         \r
1421         return $nonce == $valid ;\r
1422 }\r
1423 \r
1424 // Check if we're in API mode. Returns bool\r
1425 function yourls_is_API() {\r
1426         if ( defined('YOURLS_API') && YOURLS_API == true )\r
1427                 return true;\r
1428         return false;\r
1429 }