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