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