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
13 if( !defined('YOURLS_URL_CONVERT') ) {
\r
14 $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
\r
16 switch( YOURLS_URL_CONVERT ) {
\r
18 $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
\r
21 case 64: // just because some people get this wrong in their config.php
\r
22 $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
\r
27 $charset = yourls_apply_filter( 'get_shorturl_charset', $charset );
\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
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
42 $string = $chars[$num] . $string;
\r
44 return yourls_apply_filter( 'int2string', $string, $num, $chars );
\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
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
59 return yourls_apply_filter( 'string2int', $integer, $string, $chars );
\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
69 return yourls_apply_filter( 'sanitize_string', $valid, $string );
\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
79 // Alias function. I was always getting it wrong.
\r
80 function yourls_sanitize_keyword( $keyword ) {
\r
81 return yourls_sanitize_string( $keyword );
\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 // Remove extra white space
\r
89 $title = preg_replace( "/\s+/", ' ', trim( $title ) );
\r
93 // Is an URL a short URL?
\r
94 function yourls_is_shorturl( $shorturl ) {
\r
95 // TODO: make sure this function evolves with the feature set.
\r
98 $keyword = preg_replace( '!^'.YOURLS_SITE.'/!', '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
\r
99 if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
\r
103 return yourls_apply_filter( 'is_shorturl', $is_short, $shorturl );
\r
106 // A few sanity checks on the URL
\r
107 function yourls_sanitize_url($url) {
\r
108 // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://')
\r
109 $url = str_replace('http://http://', 'http://', $url);
\r
111 // make sure there's a protocol, add http:// if not
\r
112 if ( !preg_match('!^([a-zA-Z]+://)!', $url ) )
\r
113 $url = 'http://'.$url;
\r
115 $url = yourls_clean_url($url);
\r
117 return substr( $url, 0, 1999 );
\r
120 // Function to filter all invalid characters from a URL. Stolen from WP's clean_url()
\r
121 function yourls_clean_url( $url ) {
\r
122 $url = preg_replace('|[^a-z0-9-~+_.?\[\]\^#=!&;,/:%@$\|*\'"()\\x80-\\xff]|i', '', $url );
\r
123 $strip = array('%0d', '%0a', '%0D', '%0A');
\r
124 $url = yourls_deep_replace($strip, $url);
\r
125 $url = str_replace(';//', '://', $url);
\r
126 $url = str_replace('&', '&', $url); // Revert & not to break query strings
\r
131 // Perform a replacement while a string is found, eg $subject = '%0%0%0DDD', $search ='%0D' -> $result =''
\r
132 // Stolen from WP's _deep_replace
\r
133 function yourls_deep_replace($search, $subject){
\r
137 foreach( (array) $search as $val ) {
\r
138 while(strpos($subject, $val) !== false) {
\r
140 $subject = str_replace($val, '', $subject);
\r
148 // Make sure an integer is a valid integer (PHP's intval() limits to too small numbers)
\r
149 // TODO FIXME FFS: unused ?
\r
150 function yourls_sanitize_int($in) {
\r
151 return ( substr(preg_replace('/[^0-9]/', '', strval($in) ), 0, 20) );
\r
154 // Make sure a integer is safe
\r
155 // Note: this is not checking for integers, since integers on 32bits system are way too limited
\r
156 // TODO: find a way to validate as integer
\r
157 function yourls_intval($in) {
\r
158 return yourls_escape($in);
\r
162 function yourls_escape( $in ) {
\r
163 return mysql_real_escape_string($in);
\r
166 // Check to see if a given keyword is reserved (ie reserved URL or an existing page)
\r
168 function yourls_keyword_is_reserved( $keyword ) {
\r
169 global $yourls_reserved_URL;
\r
170 $keyword = yourls_sanitize_keyword( $keyword );
\r
173 if ( in_array( $keyword, $yourls_reserved_URL)
\r
174 or file_exists( YOURLS_ABSPATH ."/pages/$keyword.php" )
\r
175 or is_dir( YOURLS_ABSPATH ."/$keyword" )
\r
179 return yourls_apply_filter( 'keyword_is_reserved', $reserved, $keyword );
\r
182 // Function: Get IP Address. Returns a DB safe string.
\r
183 function yourls_get_IP() {
\r
184 if( !empty( $_SERVER['REMOTE_ADDR'] ) ) {
\r
185 $ip = $_SERVER['REMOTE_ADDR'];
\r
187 if(!empty($_SERVER['HTTP_CLIENT_IP'])) {
\r
188 $ip = $_SERVER['HTTP_CLIENT_IP'];
\r
189 } else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
\r
190 $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
\r
191 } else if(!empty($_SERVER['HTTP_VIA '])) {
\r
192 $ip = $_SERVER['HTTP_VIA '];
\r
196 return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
\r
199 // Sanitize an IP address
\r
200 function yourls_sanitize_ip( $ip ) {
\r
201 return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );
\r
204 // Make sure a date is m(m)/d(d)/yyyy, return false otherwise
\r
205 function yourls_sanitize_date( $date ) {
\r
206 if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) {
\r
212 // Sanitize a date for SQL search. Return false if malformed input.
\r
213 function yourls_sanitize_date_for_sql( $date ) {
\r
214 if( !yourls_sanitize_date( $date ) )
\r
216 return date('Y-m-d', strtotime( $date ) );
\r
219 // Add the "Edit" row
\r
220 function yourls_table_edit_row( $keyword ) {
\r
223 $table = YOURLS_DB_TABLE_URL;
\r
224 $keyword = yourls_sanitize_string( $keyword );
\r
225 $id = yourls_string2int( $keyword ); // used as HTML #id
\r
226 $url = yourls_get_keyword_longurl( $keyword );
\r
227 $title = htmlspecialchars( yourls_get_keyword_title( $keyword ) );
\r
228 $safe_url = stripslashes( $url );
\r
229 $safe_title = stripslashes( $title );
\r
230 $www = YOURLS_SITE;
\r
233 $return = <<<RETURN
\r
234 <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');" /> <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
237 $return = '<tr><td colspan="6">Error, URL not found</td></tr>';
\r
240 $return = yourls_apply_filter( 'table_edit_row', $return, $keyword, $url, $title );
\r
246 function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $timestamp ) {
\r
247 $keyword = yourls_sanitize_string( $keyword );
\r
248 $display_keyword = htmlentities( $keyword );
\r
250 $url = yourls_sanitize_url( $url );
\r
251 $display_url = htmlentities( yourls_trim_long_string( $url ) );
\r
253 $title = yourls_sanitize_title( $title ) ;
\r
254 $display_title = yourls_trim_long_string( $title );
\r
255 $title = htmlspecialchars( $title );
\r
257 $id = yourls_string2int( $keyword ); // used as HTML #id
\r
258 $date = date( 'M d, Y H:i', $timestamp+( YOURLS_HOURS_OFFSET * 3600) );
\r
259 $clicks = number_format($clicks, 0, '', '');
\r
261 $shorturl = YOURLS_SITE.'/'.$keyword;
\r
262 $statlink = $shorturl.'+';
\r
265 $display_link = "<a href=\"$url\" title=\"$title\">$display_title</a><br/><small><a href=\"$url\">$display_url</a></small>";
\r
267 $display_link = "<a href=\"$url\" title=\"$url\">$display_url</a>";
\r
270 $actions = <<<ACTION
\r
271 <a href="$statlink" id="statlink-$id" title="Stats" class="button button_stats"> </a> <input type="button" id="share-button-$id" name="share-button" value="" title="Share" class="button button_share" onclick="toggle_share('$id');" /> <input type="button" id="edit-button-$id" name="edit-button" value="" title="Edit" class="button button_edit" onclick="edit('$id');" /> <input type="button" id="delete-button-$id" name="delete-button" value="" title="Delete" class="button button_delete" onclick="remove('$id');" />
\r
273 $actions = yourls_apply_filter( 'action_links', $actions, $keyword, $url, $ip, $clicks, $timestamp );
\r
276 <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
278 $row = yourls_apply_filter( 'table_add_row', $row, $keyword, $url, $title, $ip, $clicks, $timestamp );
\r
283 // Get next id a new link will have if no custom keyword provided
\r
284 function yourls_get_next_decimal() {
\r
285 return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
\r
288 // Update id for next link with no custom keyword
\r
289 function yourls_update_next_decimal( $int = '' ) {
\r
290 $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
\r
291 $update = yourls_update_option( 'next_id', $int );
\r
292 yourls_do_action( 'update_next_decimal', $int, $update );
\r
296 // Delete a link in the DB
\r
297 function yourls_delete_link_by_keyword( $keyword ) {
\r
300 $table = YOURLS_DB_TABLE_URL;
\r
301 $keyword = yourls_sanitize_string( $keyword );
\r
302 $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';");
\r
303 yourls_do_action( 'delete_link', $keyword, $delete );
\r
307 // SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting
\r
308 function yourls_insert_link_in_db( $url, $keyword, $title = '' ) {
\r
311 $url = addslashes( yourls_sanitize_url( $url ) );
\r
312 $keyword = addslashes( yourls_sanitize_keyword( $keyword ) );
\r
313 $title = addslashes( yourls_sanitize_title( $title ) );
\r
315 $table = YOURLS_DB_TABLE_URL;
\r
316 $timestamp = date('Y-m-d H:i:s');
\r
317 $ip = yourls_get_IP();
\r
318 $insert = $ydb->query("INSERT INTO `$table` VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);");
\r
320 yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
\r
322 return (bool)$insert;
\r
325 // Add a new link in the DB, either with custom keyword, or find one
\r
326 function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
\r
329 if ( !$url || $url == 'http://' || $url == 'https://' ) {
\r
330 $return['status'] = 'fail';
\r
331 $return['code'] = 'error:nourl';
\r
332 $return['message'] = 'Missing URL input';
\r
333 $return['errorCode'] = '400';
\r
334 yourls_do_action( 'add_new_link_fail_nourl' );
\r
338 // Prevent DB flood
\r
339 $ip = yourls_get_IP();
\r
340 yourls_check_IP_flood( $ip );
\r
342 // Prevent internal redirection loops: cannot shorten a shortened URL
\r
343 $url = yourls_escape( yourls_sanitize_url($url) );
\r
344 if( preg_match( '!^'.YOURLS_SITE.'/!', $url ) ) {
\r
345 if( yourls_is_shorturl( $url ) ) {
\r
346 $return['status'] = 'fail';
\r
347 $return['code'] = 'error:noloop';
\r
348 $return['message'] = 'URL is a short URL';
\r
349 $return['errorCode'] = '400';
\r
350 yourls_do_action( 'add_new_link_fail_noloop' );
\r
355 yourls_do_action( 'pre_add_new_link', $url, $keyword );
\r
357 $table = YOURLS_DB_TABLE_URL;
\r
358 $strip_url = stripslashes($url);
\r
359 $url_exists = $ydb->get_row("SELECT * FROM `$table` WHERE `url` = '".$strip_url."';");
\r
362 // New URL : store it -- or: URL exists, but duplicates allowed
\r
363 if( !$url_exists || yourls_allow_duplicate_longurls() ) {
\r
365 if( isset( $title ) && !empty( $title ) ) {
\r
366 $title = yourls_sanitize_title( $title );
\r
368 $title = yourls_get_remote_title( $url );
\r
370 $title = yourls_apply_filter( 'add_new_title', $title );
\r
372 // Custom keyword provided
\r
374 $keyword = yourls_escape( yourls_sanitize_string($keyword) );
\r
375 $keyword = yourls_apply_filter( 'custom_keyword', $keyword );
\r
376 if ( !yourls_keyword_is_free($keyword) ) {
\r
377 // This shorturl either reserved or taken already
\r
378 $return['status'] = 'fail';
\r
379 $return['code'] = 'error:keyword';
\r
380 $return['message'] = 'Short URL '.$keyword.' already exists in database or is reserved';
\r
382 // all clear, store !
\r
383 yourls_insert_link_in_db( $url, $keyword, $title );
\r
384 $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
\r
385 $return['status'] = 'success';
\r
386 $return['message'] = yourls_trim_long_string( $strip_url ).' added to database';
\r
387 $return['title'] = $title;
\r
388 $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
\r
389 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
\r
392 // Create random keyword
\r
394 $timestamp = date('Y-m-d H:i:s');
\r
395 $id = yourls_get_next_decimal();
\r
398 $keyword = yourls_int2string( $id );
\r
399 $keyword = yourls_apply_filter( 'random_keyword', $keyword );
\r
400 $free = yourls_keyword_is_free($keyword);
\r
401 $add_url = @yourls_insert_link_in_db( $url, $keyword, $title );
\r
402 $ok = ($free && $add_url);
\r
403 if ( $ok === false && $add_url === 1 ) {
\r
404 // we stored something, but shouldn't have (ie reserved id)
\r
405 $delete = yourls_delete_link_by_keyword( $keyword );
\r
406 $return['extra_info'] .= '(deleted '.$keyword.')';
\r
408 // everything ok, populate needed vars
\r
409 $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
\r
410 $return['status'] = 'success';
\r
411 $return['message'] = yourls_trim_long_string( $strip_url ).' added to database';
\r
412 $return['title'] = $title;
\r
413 $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
\r
414 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
\r
418 @yourls_update_next_decimal($id);
\r
421 // URL was already stored
\r
423 $return['status'] = 'fail';
\r
424 $return['code'] = 'error:url';
\r
425 $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
426 $return['message'] = yourls_trim_long_string( $strip_url ).' already exists in database';
\r
427 $return['title'] = $url_exists->title;
\r
428 $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
\r
431 yourls_do_action( 'post_add_new_link', $url, $keyword );
\r
433 $return['statusCode'] = 200; // regardless of result, this is still a valid request
\r
439 function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
\r
442 $table = YOURLS_DB_TABLE_URL;
\r
443 $url = yourls_escape(yourls_sanitize_url($url));
\r
444 $keyword = yourls_escape(yourls_sanitize_string( $keyword ));
\r
445 $title = yourls_escape(yourls_sanitize_title( $title ));
\r
446 $newkeyword = yourls_escape(yourls_sanitize_string( $newkeyword ));
\r
447 $strip_url = stripslashes($url);
\r
448 $strip_title = stripslashes($title);
\r
449 $old_url = $ydb->get_var("SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';");
\r
450 $old_id = $id = yourls_string2int( $keyword );
\r
451 $new_id = ( $newkeyword == '' ? $old_id : yourls_string2int( $newkeyword ) );
\r
453 // Check if new URL is not here already
\r
454 if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
\r
455 $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';"));
\r
457 $new_url_already_there = false;
\r
460 // Check if the new keyword is not here already
\r
461 if ( $newkeyword != $keyword ) {
\r
462 $keyword_is_ok = yourls_keyword_is_free( $newkeyword );
\r
464 $keyword_is_ok = true;
\r
467 yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
\r
469 // All clear, update
\r
470 if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
\r
471 $update_url = $ydb->query("UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';");
\r
472 if( $update_url ) {
\r
473 $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
474 $return['status'] = 'success';
\r
475 $return['message'] = 'Link updated in database';
\r
477 $return['status'] = 'fail';
\r
478 $return['message'] = 'Error updating '. yourls_trim_long_string( $strip_url ).' (Short URL: '.$keyword.') to database';
\r
483 $return['status'] = 'fail';
\r
484 $return['message'] = 'URL or keyword already exists in database';
\r
487 return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
\r
490 // Update a title link (no checks for duplicates etc..)
\r
491 function yourls_edit_link_title( $keyword, $title ) {
\r
494 $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
\r
495 $title = yourls_escape( yourls_sanitize_title( $title ) );
\r
497 $table = YOURLS_DB_TABLE_URL;
\r
498 $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';");
\r
504 // Check if keyword id is free (ie not already taken, and not reserved). Return bool.
\r
505 function yourls_keyword_is_free( $keyword ) {
\r
507 if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
\r
510 return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
\r
513 // Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
\r
514 function yourls_keyword_is_taken( $keyword ) {
\r
516 $keyword = yourls_sanitize_keyword( $keyword );
\r
518 $table = YOURLS_DB_TABLE_URL;
\r
519 $already_exists = $ydb->get_var("SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';");
\r
520 if ( $already_exists )
\r
523 return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
\r
528 function yourls_page( $page ) {
\r
529 $include = YOURLS_ABSPATH . "/pages/$page.php";
\r
530 if (!file_exists($include)) {
\r
531 yourls_die("Page '$page' not found", 'Not found', 404);
\r
533 yourls_do_action( 'pre_page', $page );
\r
535 yourls_do_action( 'post_page', $page );
\r
540 function yourls_db_connect() {
\r
543 if (!defined('YOURLS_DB_USER')
\r
544 or !defined('YOURLS_DB_PASS')
\r
545 or !defined('YOURLS_DB_NAME')
\r
546 or !defined('YOURLS_DB_HOST')
\r
547 or !class_exists('ezSQL_mysql')
\r
548 ) yourls_die ('DB config missing, or could not find DB class', 'Fatal error', 503);
\r
550 // Are we standalone or in the WordPress environment?
\r
551 if ( class_exists('wpdb') ) {
\r
552 $ydb = new wpdb(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);
\r
554 $ydb = new ezSQL_mysql(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);
\r
556 if ( $ydb->last_error )
\r
557 yourls_die( $ydb->last_error, 'Fatal error', 503 );
\r
559 if ( defined('YOURLS_DEBUG') && YOURLS_DEBUG === true )
\r
560 $ydb->show_errors = true;
\r
565 // Return XML output.
\r
566 function yourls_xml_encode($array) {
\r
567 require_once(YOURLS_INC.'/functions-xml.php');
\r
568 $converter= new yourls_array2xml;
\r
569 return $converter->array2xml($array);
\r
572 // 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
573 function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
\r
575 $keyword = yourls_sanitize_string( $keyword );
\r
577 yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
\r
579 if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) {
\r
580 return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
\r
583 yourls_do_action( 'get_keyword_not_cached', $keyword );
\r
585 $table = YOURLS_DB_TABLE_URL;
\r
586 $infos = $ydb->get_row("SELECT * FROM `$table` WHERE `keyword` = '$keyword'");
\r
589 $infos = (array)$infos;
\r
590 $ydb->infos[$keyword] = $infos;
\r
592 $ydb->infos[$keyword] = false;
\r
595 return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
\r
598 // Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
\r
599 function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
\r
600 $keyword = yourls_sanitize_string( $keyword );
\r
601 $infos = yourls_get_keyword_infos( $keyword );
\r
603 $return = $notfound;
\r
604 if ( isset($infos[$field]) && $infos[$field] !== false )
\r
605 $return = $infos[$field];
\r
607 return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound );
\r
610 // Return title associated with keyword. Optional $notfound = string default message if nothing found
\r
611 function yourls_get_keyword_title( $keyword, $notfound = false ) {
\r
612 return yourls_get_keyword_info( $keyword, 'title', $notfound );
\r
615 // Return long URL associated with keyword. Optional $notfound = string default message if nothing found
\r
616 function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
\r
617 return yourls_get_keyword_info( $keyword, 'url', $notfound );
\r
620 // Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
\r
621 function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
\r
622 return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
\r
625 // Return IP that added a keyword. Optional $notfound = string default message if nothing found
\r
626 function yourls_get_keyword_IP( $keyword, $notfound = false ) {
\r
627 return yourls_get_keyword_info( $keyword, 'ip', $notfound );
\r
630 // Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
\r
631 function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
\r
632 return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
\r
635 // Update click count on a short URL. Return 0/1 for error/success.
\r
636 function yourls_update_clicks( $keyword ) {
\r
638 $keyword = yourls_sanitize_string( $keyword );
\r
639 $table = YOURLS_DB_TABLE_URL;
\r
640 $update = $ydb->query("UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'");
\r
641 yourls_do_action( 'update_clicks', $keyword, $update );
\r
645 // Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
\r
646 function yourls_get_stats( $filter = 'top', $limit = 10 ) {
\r
649 switch( $filter ) {
\r
651 $sort_by = 'clicks';
\r
652 $sort_order = 'asc';
\r
655 $sort_by = 'timestamp';
\r
656 $sort_order = 'desc';
\r
660 $sort_by = 'RAND()';
\r
665 $sort_by = 'clicks';
\r
666 $sort_order = 'desc';
\r
671 $limit = intval( $limit );
\r
672 if ( $limit > 0 ) {
\r
674 $table_url = YOURLS_DB_TABLE_URL;
\r
675 $results = $ydb->get_results("SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT 0, $limit;");
\r
680 foreach ( (array)$results as $res) {
\r
681 $return['links']['link_'.$i++] = array(
\r
682 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
\r
683 'url' => $res->url,
\r
684 'title' => $res->title,
\r
685 'timestamp'=> $res->timestamp,
\r
687 'clicks' => $res->clicks,
\r
692 $return['stats'] = yourls_get_db_stats();
\r
694 $return['statusCode'] = 200;
\r
696 return yourls_apply_filter( 'get_stats', $return, $filter, $limit );
\r
699 // Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
\r
700 function yourls_get_link_stats( $shorturl ) {
\r
703 $table_url = YOURLS_DB_TABLE_URL;
\r
704 $res = $ydb->get_row("SELECT * FROM `$table_url` WHERE keyword = '$shorturl';");
\r
708 // non existent link
\r
710 'statusCode' => 404,
\r
711 'message' => 'Error: short URL not found',
\r
715 'statusCode' => 200,
\r
716 'message' => 'success',
\r
718 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
\r
719 'url' => $res->url,
\r
720 'title' => $res->title,
\r
721 'timestamp'=> $res->timestamp,
\r
723 'clicks' => $res->clicks,
\r
728 return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
\r
731 // Return array for API stat requests
\r
732 function yourls_api_stats( $filter = 'top', $limit = 10 ) {
\r
733 $return = yourls_get_stats( $filter, $limit );
\r
734 $return['simple'] = 'Need either XML or JSON format for stats';
\r
735 $return['message'] = 'success';
\r
736 return yourls_apply_filter( 'api_stats', $return, $filter, $limit );
\r
739 // Return array for API stat requests
\r
740 function yourls_api_url_stats($shorturl) {
\r
741 $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
\r
742 $keyword = yourls_sanitize_string( $keyword );
\r
744 $return = yourls_get_link_stats( $keyword );
\r
745 $return['simple'] = 'Need either XML or JSON format for stats';
\r
746 return yourls_apply_filter( 'api_url_stats', $return, $shorturl );
\r
749 // Expand short url to long url
\r
750 function yourls_api_expand( $shorturl ) {
\r
751 $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
\r
752 $keyword = yourls_sanitize_string( $keyword );
\r
754 $longurl = yourls_get_keyword_longurl( $keyword );
\r
758 'keyword' => $keyword,
\r
759 'shorturl' => YOURLS_SITE . "/$keyword",
\r
760 'longurl' => $longurl,
\r
761 'simple' => $longurl,
\r
762 'message' => 'success',
\r
763 'statusCode' => 200,
\r
767 'keyword' => $keyword,
\r
768 'simple' => 'not found',
\r
769 'message' => 'Error: short URL not found',
\r
770 'errorCode' => 404,
\r
774 return yourls_apply_filter( 'api_expand', $return, $shorturl );
\r
778 // Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
\r
779 function yourls_get_db_stats( $where = '' ) {
\r
781 $table_url = YOURLS_DB_TABLE_URL;
\r
783 $totals = $ydb->get_row("SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where");
\r
784 $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
\r
786 return yourls_apply_filter( 'get_db_stats', $return, $where );
\r
789 // Return API result. Dies after this
\r
790 function yourls_api_output( $mode, $return ) {
\r
791 if( isset( $return['simple'] ) ) {
\r
792 $simple = $return['simple'];
\r
793 unset( $return['simple'] );
\r
796 yourls_do_action( 'pre_api_output', $mode, $return );
\r
800 header('Content-type: application/json');
\r
801 echo json_encode($return);
\r
805 header('Content-type: application/xml');
\r
806 echo yourls_xml_encode($return);
\r
811 if( isset( $simple ) )
\r
816 yourls_do_action( 'api_output', $mode, $return );
\r
821 // Get number of SQL queries performed
\r
822 function yourls_get_num_queries() {
\r
825 return yourls_apply_filter( 'get_num_queries', $ydb->num_queries );
\r
828 // Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
\r
829 function yourls_get_user_agent() {
\r
830 if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
\r
833 $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
\r
834 $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
\r
836 return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
\r
839 // Redirect to another page
\r
840 function yourls_redirect( $location, $code = 301 ) {
\r
841 yourls_do_action( 'pre_redirect', $location, $code );
\r
842 // Redirect, either properly if possible, or via Javascript otherwise
\r
843 if( !headers_sent() ) {
\r
844 yourls_status_header( $code );
\r
845 header("Location: $location");
\r
847 yourls_redirect_javascript( $location );
\r
852 // Set HTTP status header
\r
853 function yourls_status_header( $code = 200 ) {
\r
854 if( headers_sent() )
\r
857 $protocol = $_SERVER["SERVER_PROTOCOL"];
\r
858 if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
\r
859 $protocol = 'HTTP/1.0';
\r
861 $code = intval( $code );
\r
862 $desc = yourls_get_HTTP_status($code);
\r
864 @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
\r
865 yourls_do_action( 'status_header', $code );
\r
868 // 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
869 function yourls_redirect_javascript( $location, $dontwait = true ) {
\r
872 <script type="text/javascript">
\r
873 window.location="$location";
\r
875 <small>(if you are not redirected after 10 seconds, please <a href="$location">click here</a>)</small>
\r
879 <p>Please <a href="$location">click here</a></p>
\r
882 yourls_do_action( 'redirect_javascript', $location );
\r
885 // Return a HTTP status code
\r
886 function yourls_get_HTTP_status( $code ) {
\r
887 $code = intval( $code );
\r
888 $headers_desc = array(
\r
890 101 => 'Switching Protocols',
\r
891 102 => 'Processing',
\r
896 203 => 'Non-Authoritative Information',
\r
897 204 => 'No Content',
\r
898 205 => 'Reset Content',
\r
899 206 => 'Partial Content',
\r
900 207 => 'Multi-Status',
\r
903 300 => 'Multiple Choices',
\r
904 301 => 'Moved Permanently',
\r
906 303 => 'See Other',
\r
907 304 => 'Not Modified',
\r
908 305 => 'Use Proxy',
\r
910 307 => 'Temporary Redirect',
\r
912 400 => 'Bad Request',
\r
913 401 => 'Unauthorized',
\r
914 402 => 'Payment Required',
\r
915 403 => 'Forbidden',
\r
916 404 => 'Not Found',
\r
917 405 => 'Method Not Allowed',
\r
918 406 => 'Not Acceptable',
\r
919 407 => 'Proxy Authentication Required',
\r
920 408 => 'Request Timeout',
\r
923 411 => 'Length Required',
\r
924 412 => 'Precondition Failed',
\r
925 413 => 'Request Entity Too Large',
\r
926 414 => 'Request-URI Too Long',
\r
927 415 => 'Unsupported Media Type',
\r
928 416 => 'Requested Range Not Satisfiable',
\r
929 417 => 'Expectation Failed',
\r
930 422 => 'Unprocessable Entity',
\r
932 424 => 'Failed Dependency',
\r
933 426 => 'Upgrade Required',
\r
935 500 => 'Internal Server Error',
\r
936 501 => 'Not Implemented',
\r
937 502 => 'Bad Gateway',
\r
938 503 => 'Service Unavailable',
\r
939 504 => 'Gateway Timeout',
\r
940 505 => 'HTTP Version Not Supported',
\r
941 506 => 'Variant Also Negotiates',
\r
942 507 => 'Insufficient Storage',
\r
943 510 => 'Not Extended'
\r
946 if ( isset( $headers_desc[$code] ) )
\r
947 return $headers_desc[$code];
\r
953 // Log a redirect (for stats)
\r
954 function yourls_log_redirect( $keyword ) {
\r
955 if ( !yourls_do_log_redirect() )
\r
959 $table = YOURLS_DB_TABLE_LOG;
\r
961 $keyword = yourls_sanitize_string( $keyword );
\r
962 $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' );
\r
963 $ua = yourls_get_user_agent();
\r
964 $ip = yourls_get_IP();
\r
965 $location = yourls_geo_ip_to_countrycode( $ip );
\r
967 return $ydb->query( "INSERT INTO `$table` VALUES ('', NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" );
\r
970 // Check if we want to not log redirects (for stats)
\r
971 function yourls_do_log_redirect() {
\r
972 return ( !defined('YOURLS_NOSTATS') || YOURLS_NOSTATS != true );
\r
975 // Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
\r
976 function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
\r
977 // allow a plugin to shortcircuit the Geo IP API
\r
978 $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
979 if ( false !== $location )
\r
982 if ( !file_exists( YOURLS_INC.'/geo/GeoIP.dat') || !file_exists( YOURLS_INC.'/geo/geoip.inc') )
\r
986 $ip = yourls_get_IP();
\r
988 require_once( YOURLS_INC.'/geo/geoip.inc') ;
\r
989 $gi = geoip_open( YOURLS_INC.'/geo/GeoIP.dat', GEOIP_STANDARD);
\r
990 $location = geoip_country_code_by_addr($gi, $ip);
\r
993 return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
\r
996 // Converts a 2 letter country code to long name (ie AU -> Australia)
\r
997 function yourls_geo_countrycode_to_countryname( $code ) {
\r
998 // Load the Geo class if not already done
\r
999 if( !class_exists('GeoIP') ) {
\r
1000 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');
\r
1003 if( class_exists('GeoIP') ) {
\r
1005 $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[$code];
\r
1006 $long = $geo->GEOIP_COUNTRY_NAMES[$id];
\r
1013 // Return flag URL from 2 letter country code
\r
1014 function yourls_geo_get_flag( $code ) {
\r
1015 // Load the Geo class if not already done
\r
1016 if( !class_exists('GeoIP') ) {
\r
1017 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');
\r
1020 if( class_exists('GeoIP') ) {
\r
1021 return YOURLS_SITE.'/includes/geo/flags/flag_'.(strtolower($code)).'.gif';
\r
1028 // Check if an upgrade is needed
\r
1029 function yourls_upgrade_is_needed() {
\r
1030 // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
\r
1031 list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
\r
1032 if( $currentsql < YOURLS_DB_VERSION )
\r
1038 // Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
\r
1039 function yourls_get_current_version_from_sql() {
\r
1040 $currentver = yourls_get_option( 'version' );
\r
1041 $currentsql = yourls_get_option( 'db_version' );
\r
1043 // Values if version is 1.3
\r
1044 if( !$currentver )
\r
1045 $currentver = '1.3';
\r
1046 if( !$currentsql )
\r
1047 $currentsql = '100';
\r
1049 return array( $currentver, $currentsql);
\r
1052 // Read an option from DB (or from cache if available). Return value or $default if not found
\r
1053 function yourls_get_option( $option_name, $default = false ) {
\r
1056 // Allow plugins to short-circuit options
\r
1057 $pre = yourls_apply_filter( 'pre_option_'.$option_name, false );
\r
1058 if ( false !== $pre )
\r
1061 if ( !isset( $ydb->option[$option_name] ) ) {
\r
1062 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1063 $option_name = yourls_escape( $option_name );
\r
1064 $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
\r
1065 if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
\r
1066 $value = $row->option_value;
\r
1067 } else { // option does not exist, so we must cache its non-existence
\r
1068 $value = $default;
\r
1070 $ydb->option[$option_name] = yourls_maybe_unserialize( $value );
\r
1073 return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
\r
1076 // Read all options from DB at once
\r
1077 function yourls_get_all_options() {
\r
1079 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1081 $allopt = $ydb->get_results("SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1");
\r
1083 foreach( (array)$allopt as $option ) {
\r
1084 $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value );
\r
1088 // Update (add if doesn't exist) an option to DB
\r
1089 function yourls_update_option( $option_name, $newvalue ) {
\r
1091 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1093 $safe_option_name = yourls_escape( $option_name );
\r
1095 $oldvalue = yourls_get_option( $safe_option_name );
\r
1097 // If the new and old values are the same, no need to update.
\r
1098 if ( $newvalue === $oldvalue )
\r
1101 if ( false === $oldvalue ) {
\r
1102 yourls_add_option( $option_name, $newvalue );
\r
1106 $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
\r
1108 yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
\r
1110 $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'");
\r
1112 if ( $ydb->rows_affected == 1 ) {
\r
1113 $ydb->option[$option_name] = $newvalue;
\r
1119 // Add an option to the DB
\r
1120 function yourls_add_option( $name, $value = '' ) {
\r
1122 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1123 $safe_name = yourls_escape( $name );
\r
1125 // Make sure the option doesn't already exist
\r
1126 if ( false !== yourls_get_option( $safe_name ) )
\r
1129 $_value = yourls_escape( yourls_maybe_serialize( $value ) );
\r
1131 yourls_do_action( 'add_option', $safe_name, $_value );
\r
1133 $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
\r
1134 $ydb->option[$name] = $value;
\r
1139 // Delete an option from the DB
\r
1140 function yourls_delete_option( $name ) {
\r
1142 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1143 $name = yourls_escape( $name );
\r
1145 // Get the ID, if no ID then return
\r
1146 $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
\r
1147 if ( is_null($option) || !$option->option_id )
\r
1150 yourls_do_action( 'delete_option', $option_name );
\r
1152 $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
\r
1158 // Serialize data if needed. Stolen from WordPress
\r
1159 function yourls_maybe_serialize( $data ) {
\r
1160 if ( is_array( $data ) || is_object( $data ) )
\r
1161 return serialize( $data );
\r
1163 if ( yourls_is_serialized( $data ) )
\r
1164 return serialize( $data );
\r
1169 // Check value to find if it was serialized. Stolen from WordPress
\r
1170 function yourls_is_serialized( $data ) {
\r
1171 // if it isn't a string, it isn't serialized
\r
1172 if ( !is_string( $data ) )
\r
1174 $data = trim( $data );
\r
1175 if ( 'N;' == $data )
\r
1177 if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
\r
1179 switch ( $badions[1] ) {
\r
1183 if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
\r
1189 if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
\r
1196 // Unserialize value only if it was serialized. Stolen from WP
\r
1197 function yourls_maybe_unserialize( $original ) {
\r
1198 if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
\r
1199 return @unserialize( $original );
\r
1203 // Determine if the current page is private
\r
1204 function yourls_is_private() {
\r
1207 if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
\r
1209 // Allow overruling for particular pages:
\r
1212 if( yourls_is_API() ) {
\r
1213 if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
\r
1217 } elseif( yourls_is_infos() ) {
\r
1218 if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
\r
1228 return yourls_apply_filter( 'is_private', $private );
\r
1231 // Show login form if required
\r
1232 function yourls_maybe_require_auth() {
\r
1233 if( yourls_is_private() )
\r
1234 require_once( YOURLS_INC.'/auth.php' );
\r
1237 // Return word or words if more than one
\r
1238 function yourls_plural( $word, $count=1 ) {
\r
1239 return $word . ($count > 1 ? 's' : '');
\r
1242 // Return trimmed string
\r
1243 function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) {
\r
1244 $newstring = $string;
\r
1245 if( function_exists('mb_substr') ) {
\r
1246 if ( mb_strlen( $newstring ) > $length ) {
\r
1247 $newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append;
\r
1250 if ( strlen( $newstring ) > $length ) {
\r
1251 $newstring = substr( $newstring, 0, $length - strlen( $append ) ) . $append;
\r
1254 return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append );
\r
1257 // Allow several short URLs for the same long URL ?
\r
1258 function yourls_allow_duplicate_longurls() {
\r
1259 // special treatment if API to check for WordPress plugin requests
\r
1260 if( yourls_is_API() ) {
\r
1261 if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' )
\r
1264 return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
\r
1267 // Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords.
\r
1268 function yourls_get_duplicate_keywords( $longurl ) {
\r
1269 if( !yourls_allow_duplicate_longurls() )
\r
1273 $longurl = yourls_escape( yourls_sanitize_url($longurl) );
\r
1274 $table = YOURLS_DB_TABLE_URL;
\r
1276 $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" );
\r
1277 return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl );
\r
1280 // Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
\r
1281 function yourls_check_IP_flood( $ip = '' ) {
\r
1283 yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
\r
1286 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
\r
1287 !defined('YOURLS_FLOOD_DELAY_SECONDS')
\r
1291 $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
\r
1293 // Don't throttle whitelist IPs
\r
1294 if( defined('YOURLS_FLOOD_IP_WHITELIST' && YOURLS_FLOOD_IP_WHITELIST ) ) {
\r
1295 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
\r
1296 foreach( (array)$whitelist_ips as $whitelist_ip ) {
\r
1297 $whitelist_ip = trim( $whitelist_ip );
\r
1298 if ( $whitelist_ip == $ip )
\r
1303 // Don't throttle logged in users
\r
1304 if( yourls_is_private() ) {
\r
1305 if( yourls_is_valid_user() === true )
\r
1309 yourls_do_action( 'check_ip_flood', $ip );
\r
1312 $table = YOURLS_DB_TABLE_URL;
\r
1314 $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
\r
1316 $now = date( 'U' );
\r
1317 $then = date( 'U', strtotime( $lasttime ) );
\r
1318 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
\r
1320 yourls_do_action( 'ip_flood', $ip, $now - $then );
\r
1321 yourls_die( 'Too many URLs added too fast. Slow down please.', 'Forbidden', 403 );
\r
1328 // Check if YOURLS is installed
\r
1329 function yourls_is_installed() {
\r
1330 static $is_installed = false;
\r
1331 if ( $is_installed === false ) {
\r
1332 $check_14 = $check_13 = false;
\r
1334 if( defined('YOURLS_DB_TABLE_NEXTDEC') )
\r
1335 $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC);
\r
1336 $check_14 = yourls_get_option( 'version' );
\r
1337 $is_installed = $check_13 || $check_14;
\r
1339 return yourls_apply_filter( 'is_installed', $is_installed );
\r
1342 // Generate random string of (int)$length length and type $type (see function for details)
\r
1343 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
\r
1345 $length = intval( $length );
\r
1347 // define possible characters
\r
1348 switch ( $type ) {
\r
1350 // custom char list, or comply to charset as defined in config
\r
1352 $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
\r
1355 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
\r
1357 $possible = "23456789bcdfghjkmnpqrstvwxyz";
\r
1360 // Same, with lower + upper
\r
1362 $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
\r
1365 // all letters, lowercase
\r
1367 $possible = "abcdefghijklmnopqrstuvwxyz";
\r
1370 // all letters, lowercase + uppercase
\r
1372 $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
\r
1375 // all digits & letters lowercase
\r
1377 $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
\r
1380 // all digits & letters lowercase + uppercase
\r
1382 $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
\r
1388 while ($i < $length) {
\r
1389 $str .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
\r
1393 return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
\r
1396 // Return salted string
\r
1397 function yourls_salt( $string ) {
\r
1398 $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
\r
1399 return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
\r
1402 // Return a time-dependent string for nonce creation
\r
1403 function yourls_tick() {
\r
1404 return ceil( time() / YOURLS_NONCE_LIFE );
\r
1407 // Create a time limited, action limited and user limited token
\r
1408 function yourls_create_nonce( $action = '-1', $user = false ) {
\r
1409 if( false == $user )
\r
1410 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
\r
1411 $tick = yourls_tick();
\r
1412 return substr( yourls_salt($tick . $action . $user), 0, 10 );
\r
1415 // Check validity of a nonce (ie time span, user and action match)
\r
1416 function yourls_verify_nonce( $nonce, $action = -1, $user = false ) {
\r
1417 if( false == $user )
\r
1418 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
\r
1419 $valid = yourls_create_nonce( $action, $user );
\r
1421 return $nonce == $valid ;
\r
1424 // Sanitize a version number (1.4.1-whatever -> 1.4.1)
\r
1425 function yourls_sanitize_version( $ver ) {
\r
1426 return preg_replace( '/[^0-9.]/', '', $ver );
\r
1429 // Converts keyword into short link
\r
1430 function yourls_link( $keyword = '' ) {
\r
1431 return YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
\r
1434 // Check if we're in API mode. Returns bool
\r
1435 function yourls_is_API() {
\r
1436 if ( defined('YOURLS_API') && YOURLS_API == true )
\r
1441 // Check if we're in Ajax mode. Returns bool
\r
1442 function yourls_is_Ajax() {
\r
1443 if ( defined('YOURLS_AJAX') && YOURLS_AJAX == true )
\r
1448 // Check if we're in GO mode (yourls-go.php). Returns bool
\r
1449 function yourls_is_GO() {
\r
1450 if ( defined('YOURLS_GO') && YOURLS_GO == true )
\r
1455 // Check if we're displaying stats infos (yourls-infos.php). Returns bool
\r
1456 function yourls_is_infos() {
\r
1457 if ( defined('YOURLS_INFOS') && YOURLS_INFOS == true )
\r
1462 // Check if we'll need interface display function (ie not API or redirection)
\r
1463 function yourls_has_interface() {
\r
1464 if( yourls_is_API() or yourls_is_GO() or yourls_is_Ajax() )
\r
1469 // Check if we're in the admin area. Returns bool
\r
1470 function yourls_is_admin() {
\r
1471 if ( defined('YOURLS_ADMIN') && YOURLS_ADMIN == true )
\r
1476 // Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
\r
1477 function yourls_is_windows() {
\r
1478 return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
\r
1481 // Check if SSL is required. Returns bool.
\r
1482 function yourls_needs_ssl() {
\r
1483 if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true )
\r
1488 // Return admin link, with SSL preference if applicable.
\r
1489 function yourls_admin_url( $page = '' ) {
\r
1490 $admin = YOURLS_SITE . '/admin/' . $page;
\r
1491 if( yourls_is_ssl() or yourls_needs_ssl() )
\r
1492 $admin = str_replace('http://', 'https://', $admin);
\r
1493 return yourls_apply_filter( 'admin_url', $admin, $page );
\r
1496 // Check if SSL is used, returns bool. Stolen from WP.
\r
1497 function yourls_is_ssl() {
\r
1499 if ( isset($_SERVER['HTTPS']) ) {
\r
1500 if ( 'on' == strtolower($_SERVER['HTTPS']) )
\r
1502 if ( '1' == $_SERVER['HTTPS'] )
\r
1504 } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
\r
1507 return yourls_apply_filter( 'is_ssl', $is_ssl );
\r
1511 // Get a remote page <title>, return a string (either title or url)
\r
1512 function yourls_get_remote_title( $url ) {
\r
1513 require_once( YOURLS_INC.'/functions-http.php' );
\r
1515 $url = yourls_sanitize_url( $url );
\r
1517 $title = $charset = false;
\r
1519 $content = yourls_get_remote_content( $url );
\r
1521 if( $content !== false ) {
\r
1522 // look for <title>
\r
1523 if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
\r
1524 $title = $found[1];
\r
1528 // look for charset
\r
1529 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
\r
1530 if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) {
\r
1531 $charset = trim($found[1], '"\' ');
\r
1536 // if title not found, guess if returned content was actually an error message
\r
1537 if( $title == false && strpos( $content, 'Error' ) === 0 ) {
\r
1538 $title = $content;
\r
1541 if( $title == false )
\r
1545 if( !yourls_seems_utf8( $title ) )
\r
1546 $title = utf8_encode( $title );
\r
1549 // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars)
\r
1550 if( function_exists( 'mb_convert_encoding' ) ) {
\r
1552 $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
\r
1554 $title = @mb_convert_encoding( $title, 'UTF-8' );
\r
1558 // Remove HTML entities
\r
1559 $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
\r
1561 // Strip out evil things
\r
1562 $title = yourls_sanitize_title( $title );
\r
1564 return yourls_apply_filter( 'get_remote_title', $title, $url );
\r
1567 // Sanitize a filename (no Win32 stuff)
\r
1568 function yourls_sanitize_filename( $file ) {
\r
1569 $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs
\r
1570 $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash
\r
1574 // Check for maintenance mode that will shortcut everything
\r
1575 function yourls_check_maintenance_mode() {
\r
1577 // TODO: all cases that always display the sites (is_admin but not is_ajax?)
\r
1581 // first case: /user/maintenance.php file
\r
1582 if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
\r
1583 include( YOURLS_USERDIR.'/maintenance.php' );
\r
1587 // second case: option in DB
\r
1588 if( yourls_get_option( 'maintenance_mode' ) !== false ) {
\r
1589 require_once( YOURLS_INC.'/functions-html.php' );
\r
1590 $title = 'Service temporarily unavailable';
\r
1591 $message = 'Our service is currently undergoing scheduled maintenance.</p>
\r
1592 <p>Things should not last very long, thank you for your patience and please excuse the inconvenience';
\r
1593 yourls_die( $message, $title , 503 );
\r
1598 // Toggle maintenance mode
\r
1599 function yourls_maintenance_mode( $maintenance = true ) {
\r
1600 yourls_update_option( 'maintenance_mode', (bool)$maintenance );
\r
1603 // Check if a string seems to be UTF-8. Stolen from WP.
\r
1604 function yourls_seems_utf8($str) {
\r
1605 $length = strlen($str);
\r
1606 for ($i=0; $i < $length; $i++) {
\r
1607 $c = ord($str[$i]);
\r
1608 if ($c < 0x80) $n = 0; # 0bbbbbbb
\r
1609 elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
\r
1610 elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
\r
1611 elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
\r
1612 elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
\r
1613 elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
\r
1614 else return false; # Does not match any model
\r
1615 for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
\r
1616 if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
\r