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