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
232 $save_link = yourls_nonce_url( 'save-link_'.$id,
\r
233 yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit_save', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) )
\r
236 $nonce = yourls_create_nonce( 'edit-save_'.$id );
\r
239 $return = <<<RETURN
\r
240 <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"/><input type="hidden" id="nonce_$id" value="$nonce"/></td></tr>
\r
243 $return = '<tr><td colspan="6">Error, URL not found</td></tr>';
\r
246 $return = yourls_apply_filter( 'table_edit_row', $return, $keyword, $url, $title );
\r
252 function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $timestamp ) {
\r
253 $keyword = yourls_sanitize_string( $keyword );
\r
254 $display_keyword = htmlentities( $keyword );
\r
256 $url = yourls_sanitize_url( $url );
\r
257 $display_url = htmlentities( yourls_trim_long_string( $url ) );
\r
258 $title_url = htmlspecialchars( $url );
\r
260 $title = yourls_sanitize_title( $title ) ;
\r
261 $display_title = yourls_trim_long_string( $title );
\r
262 $title = htmlspecialchars( $title );
\r
264 $id = yourls_string2int( $keyword ); // used as HTML #id
\r
265 $date = date( 'M d, Y H:i', $timestamp+( YOURLS_HOURS_OFFSET * 3600) );
\r
266 $clicks = number_format($clicks, 0, '', '');
\r
268 $shorturl = YOURLS_SITE.'/'.$keyword;
\r
269 $statlink = $shorturl.'+';
\r
270 if( yourls_is_ssl() )
\r
271 $statlink = str_replace( 'http://', 'https://', $statlink );
\r
274 $display_link = "<a href=\"$url\" title=\"$title\">$display_title</a><br/><small><a href=\"$url\" title=\"$title_url\">$display_url</a></small>";
\r
276 $display_link = "<a href=\"$url\" title=\"$title_url\">$display_url</a>";
\r
279 $delete_link = yourls_nonce_url( 'delete-link_'.$id,
\r
280 yourls_add_query_arg( array( 'id' => $id, 'action' => 'delete', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) )
\r
283 $edit_link = yourls_nonce_url( 'edit-link_'.$id,
\r
284 yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) )
\r
289 $actions = <<<ACTION
\r
290 <a href="$statlink" id="statlink-$id" title="Stats" class="button button_stats">Stats</a><a href="" id="share-button-$id" name="share-button" title="Share" class="button button_share" onclick="toggle_share('$id');return false;">Share</a><a href="$edit_link" id="edit-button-$id" name="edit-button" title="Edit" class="button button_edit" onclick="edit('$id');return false;">Edit</a><a href="$delete_link" id="delete-button-$id" name="delete-button" title="Delete" class="button button_delete" onclick="remove('$id');return false;">Delete</a>
\r
292 $actions = yourls_apply_filter( 'action_links', $actions, $keyword, $url, $ip, $clicks, $timestamp );
\r
295 <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
297 $row = yourls_apply_filter( 'table_add_row', $row, $keyword, $url, $title, $ip, $clicks, $timestamp );
\r
302 // Get next id a new link will have if no custom keyword provided
\r
303 function yourls_get_next_decimal() {
\r
304 return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
\r
307 // Update id for next link with no custom keyword
\r
308 function yourls_update_next_decimal( $int = '' ) {
\r
309 $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
\r
310 $update = yourls_update_option( 'next_id', $int );
\r
311 yourls_do_action( 'update_next_decimal', $int, $update );
\r
315 // Delete a link in the DB
\r
316 function yourls_delete_link_by_keyword( $keyword ) {
\r
319 $table = YOURLS_DB_TABLE_URL;
\r
320 $keyword = yourls_sanitize_string( $keyword );
\r
321 $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';");
\r
322 yourls_do_action( 'delete_link', $keyword, $delete );
\r
326 // SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting
\r
327 function yourls_insert_link_in_db( $url, $keyword, $title = '' ) {
\r
330 $url = addslashes( yourls_sanitize_url( $url ) );
\r
331 $keyword = addslashes( yourls_sanitize_keyword( $keyword ) );
\r
332 $title = addslashes( yourls_sanitize_title( $title ) );
\r
334 $table = YOURLS_DB_TABLE_URL;
\r
335 $timestamp = date('Y-m-d H:i:s');
\r
336 $ip = yourls_get_IP();
\r
337 $insert = $ydb->query("INSERT INTO `$table` VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);");
\r
339 yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
\r
341 return (bool)$insert;
\r
344 // Add a new link in the DB, either with custom keyword, or find one
\r
345 function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
\r
348 if ( !$url || $url == 'http://' || $url == 'https://' ) {
\r
349 $return['status'] = 'fail';
\r
350 $return['code'] = 'error:nourl';
\r
351 $return['message'] = 'Missing URL input';
\r
352 $return['errorCode'] = '400';
\r
353 yourls_do_action( 'add_new_link_fail_nourl' );
\r
357 // Prevent DB flood
\r
358 $ip = yourls_get_IP();
\r
359 yourls_check_IP_flood( $ip );
\r
361 // Prevent internal redirection loops: cannot shorten a shortened URL
\r
362 $url = yourls_escape( yourls_sanitize_url($url) );
\r
363 if( preg_match( '!^'.YOURLS_SITE.'/!', $url ) ) {
\r
364 if( yourls_is_shorturl( $url ) ) {
\r
365 $return['status'] = 'fail';
\r
366 $return['code'] = 'error:noloop';
\r
367 $return['message'] = 'URL is a short URL';
\r
368 $return['errorCode'] = '400';
\r
369 yourls_do_action( 'add_new_link_fail_noloop' );
\r
374 yourls_do_action( 'pre_add_new_link', $url, $keyword );
\r
376 $table = YOURLS_DB_TABLE_URL;
\r
377 $strip_url = stripslashes($url);
\r
378 $url_exists = $ydb->get_row("SELECT * FROM `$table` WHERE `url` = '".$strip_url."';");
\r
381 // New URL : store it -- or: URL exists, but duplicates allowed
\r
382 if( !$url_exists || yourls_allow_duplicate_longurls() ) {
\r
384 if( isset( $title ) && !empty( $title ) ) {
\r
385 $title = yourls_sanitize_title( $title );
\r
387 $title = yourls_get_remote_title( $url );
\r
389 $title = yourls_apply_filter( 'add_new_title', $title );
\r
391 // Custom keyword provided
\r
393 $keyword = yourls_escape( yourls_sanitize_string($keyword) );
\r
394 $keyword = yourls_apply_filter( 'custom_keyword', $keyword );
\r
395 if ( !yourls_keyword_is_free($keyword) ) {
\r
396 // This shorturl either reserved or taken already
\r
397 $return['status'] = 'fail';
\r
398 $return['code'] = 'error:keyword';
\r
399 $return['message'] = 'Short URL '.$keyword.' already exists in database or is reserved';
\r
401 // all clear, store !
\r
402 yourls_insert_link_in_db( $url, $keyword, $title );
\r
403 $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
\r
404 $return['status'] = 'success';
\r
405 $return['message'] = yourls_trim_long_string( $strip_url ).' added to database';
\r
406 $return['title'] = $title;
\r
407 $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
\r
408 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
\r
411 // Create random keyword
\r
413 $timestamp = date('Y-m-d H:i:s');
\r
414 $id = yourls_get_next_decimal();
\r
417 $keyword = yourls_int2string( $id );
\r
418 $keyword = yourls_apply_filter( 'random_keyword', $keyword );
\r
419 $free = yourls_keyword_is_free($keyword);
\r
420 $add_url = @yourls_insert_link_in_db( $url, $keyword, $title );
\r
421 $ok = ($free && $add_url);
\r
422 if ( $ok === false && $add_url === 1 ) {
\r
423 // we stored something, but shouldn't have (ie reserved id)
\r
424 $delete = yourls_delete_link_by_keyword( $keyword );
\r
425 $return['extra_info'] .= '(deleted '.$keyword.')';
\r
427 // everything ok, populate needed vars
\r
428 $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
\r
429 $return['status'] = 'success';
\r
430 $return['message'] = yourls_trim_long_string( $strip_url ).' added to database';
\r
431 $return['title'] = $title;
\r
432 $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
\r
433 $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
\r
437 @yourls_update_next_decimal($id);
\r
440 // URL was already stored
\r
442 $return['status'] = 'fail';
\r
443 $return['code'] = 'error:url';
\r
444 $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
445 $return['message'] = yourls_trim_long_string( $strip_url ).' already exists in database';
\r
446 $return['title'] = $url_exists->title;
\r
447 $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
\r
450 yourls_do_action( 'post_add_new_link', $url, $keyword );
\r
452 $return['statusCode'] = 200; // regardless of result, this is still a valid request
\r
458 function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
\r
461 $table = YOURLS_DB_TABLE_URL;
\r
462 $url = yourls_escape(yourls_sanitize_url($url));
\r
463 $keyword = yourls_escape(yourls_sanitize_string( $keyword ));
\r
464 $title = yourls_escape(yourls_sanitize_title( $title ));
\r
465 $newkeyword = yourls_escape(yourls_sanitize_string( $newkeyword ));
\r
466 $strip_url = stripslashes($url);
\r
467 $strip_title = stripslashes($title);
\r
468 $old_url = $ydb->get_var("SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';");
\r
469 $old_id = $id = yourls_string2int( $keyword );
\r
470 $new_id = ( $newkeyword == '' ? $old_id : yourls_string2int( $newkeyword ) );
\r
472 // Check if new URL is not here already
\r
473 if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
\r
474 $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';"));
\r
476 $new_url_already_there = false;
\r
479 // Check if the new keyword is not here already
\r
480 if ( $newkeyword != $keyword ) {
\r
481 $keyword_is_ok = yourls_keyword_is_free( $newkeyword );
\r
483 $keyword_is_ok = true;
\r
486 yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
\r
488 // All clear, update
\r
489 if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
\r
490 $update_url = $ydb->query("UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';");
\r
491 if( $update_url ) {
\r
492 $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
493 $return['status'] = 'success';
\r
494 $return['message'] = 'Link updated in database';
\r
496 $return['status'] = 'fail';
\r
497 $return['message'] = 'Error updating '. yourls_trim_long_string( $strip_url ).' (Short URL: '.$keyword.') to database';
\r
502 $return['status'] = 'fail';
\r
503 $return['message'] = 'URL or keyword already exists in database';
\r
506 return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
\r
509 // Update a title link (no checks for duplicates etc..)
\r
510 function yourls_edit_link_title( $keyword, $title ) {
\r
513 $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
\r
514 $title = yourls_escape( yourls_sanitize_title( $title ) );
\r
516 $table = YOURLS_DB_TABLE_URL;
\r
517 $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';");
\r
523 // Check if keyword id is free (ie not already taken, and not reserved). Return bool.
\r
524 function yourls_keyword_is_free( $keyword ) {
\r
526 if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
\r
529 return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
\r
532 // Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
\r
533 function yourls_keyword_is_taken( $keyword ) {
\r
535 $keyword = yourls_sanitize_keyword( $keyword );
\r
537 $table = YOURLS_DB_TABLE_URL;
\r
538 $already_exists = $ydb->get_var("SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';");
\r
539 if ( $already_exists )
\r
542 return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
\r
547 function yourls_page( $page ) {
\r
548 $include = YOURLS_ABSPATH . "/pages/$page.php";
\r
549 if (!file_exists($include)) {
\r
550 yourls_die("Page '$page' not found", 'Not found', 404);
\r
552 yourls_do_action( 'pre_page', $page );
\r
554 yourls_do_action( 'post_page', $page );
\r
559 function yourls_db_connect() {
\r
562 if (!defined('YOURLS_DB_USER')
\r
563 or !defined('YOURLS_DB_PASS')
\r
564 or !defined('YOURLS_DB_NAME')
\r
565 or !defined('YOURLS_DB_HOST')
\r
566 or !class_exists('ezSQL_mysql')
\r
567 ) yourls_die ('DB config missing, or could not find DB class', 'Fatal error', 503);
\r
569 // Are we standalone or in the WordPress environment?
\r
570 if ( class_exists('wpdb') ) {
\r
571 $ydb = new wpdb(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);
\r
573 $ydb = new ezSQL_mysql(YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST);
\r
575 if ( $ydb->last_error )
\r
576 yourls_die( $ydb->last_error, 'Fatal error', 503 );
\r
578 if ( defined('YOURLS_DEBUG') && YOURLS_DEBUG === true )
\r
579 $ydb->show_errors = true;
\r
584 // Return XML output.
\r
585 function yourls_xml_encode($array) {
\r
586 require_once(YOURLS_INC.'/functions-xml.php');
\r
587 $converter= new yourls_array2xml;
\r
588 return $converter->array2xml($array);
\r
591 // 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
592 function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
\r
594 $keyword = yourls_sanitize_string( $keyword );
\r
596 yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
\r
598 if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) {
\r
599 return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
\r
602 yourls_do_action( 'get_keyword_not_cached', $keyword );
\r
604 $table = YOURLS_DB_TABLE_URL;
\r
605 $infos = $ydb->get_row("SELECT * FROM `$table` WHERE `keyword` = '$keyword'");
\r
608 $infos = (array)$infos;
\r
609 $ydb->infos[$keyword] = $infos;
\r
611 $ydb->infos[$keyword] = false;
\r
614 return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
\r
617 // Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
\r
618 function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
\r
619 $keyword = yourls_sanitize_string( $keyword );
\r
620 $infos = yourls_get_keyword_infos( $keyword );
\r
622 $return = $notfound;
\r
623 if ( isset($infos[$field]) && $infos[$field] !== false )
\r
624 $return = $infos[$field];
\r
626 return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound );
\r
629 // Return title associated with keyword. Optional $notfound = string default message if nothing found
\r
630 function yourls_get_keyword_title( $keyword, $notfound = false ) {
\r
631 return yourls_get_keyword_info( $keyword, 'title', $notfound );
\r
634 // Return long URL associated with keyword. Optional $notfound = string default message if nothing found
\r
635 function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
\r
636 return yourls_get_keyword_info( $keyword, 'url', $notfound );
\r
639 // Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
\r
640 function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
\r
641 return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
\r
644 // Return IP that added a keyword. Optional $notfound = string default message if nothing found
\r
645 function yourls_get_keyword_IP( $keyword, $notfound = false ) {
\r
646 return yourls_get_keyword_info( $keyword, 'ip', $notfound );
\r
649 // Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
\r
650 function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
\r
651 return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
\r
654 // Update click count on a short URL. Return 0/1 for error/success.
\r
655 function yourls_update_clicks( $keyword ) {
\r
657 $keyword = yourls_sanitize_string( $keyword );
\r
658 $table = YOURLS_DB_TABLE_URL;
\r
659 $update = $ydb->query("UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'");
\r
660 yourls_do_action( 'update_clicks', $keyword, $update );
\r
664 // Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
\r
665 function yourls_get_stats( $filter = 'top', $limit = 10 ) {
\r
668 switch( $filter ) {
\r
670 $sort_by = 'clicks';
\r
671 $sort_order = 'asc';
\r
674 $sort_by = 'timestamp';
\r
675 $sort_order = 'desc';
\r
679 $sort_by = 'RAND()';
\r
684 $sort_by = 'clicks';
\r
685 $sort_order = 'desc';
\r
690 $limit = intval( $limit );
\r
691 if ( $limit > 0 ) {
\r
693 $table_url = YOURLS_DB_TABLE_URL;
\r
694 $results = $ydb->get_results("SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT 0, $limit;");
\r
699 foreach ( (array)$results as $res) {
\r
700 $return['links']['link_'.$i++] = array(
\r
701 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
\r
702 'url' => $res->url,
\r
703 'title' => $res->title,
\r
704 'timestamp'=> $res->timestamp,
\r
706 'clicks' => $res->clicks,
\r
711 $return['stats'] = yourls_get_db_stats();
\r
713 $return['statusCode'] = 200;
\r
715 return yourls_apply_filter( 'get_stats', $return, $filter, $limit );
\r
718 // Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
\r
719 function yourls_get_link_stats( $shorturl ) {
\r
722 $table_url = YOURLS_DB_TABLE_URL;
\r
723 $res = $ydb->get_row("SELECT * FROM `$table_url` WHERE keyword = '$shorturl';");
\r
727 // non existent link
\r
729 'statusCode' => 404,
\r
730 'message' => 'Error: short URL not found',
\r
734 'statusCode' => 200,
\r
735 'message' => 'success',
\r
737 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
\r
738 'url' => $res->url,
\r
739 'title' => $res->title,
\r
740 'timestamp'=> $res->timestamp,
\r
742 'clicks' => $res->clicks,
\r
747 return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
\r
750 // Return array for API stat requests
\r
751 function yourls_api_stats( $filter = 'top', $limit = 10 ) {
\r
752 $return = yourls_get_stats( $filter, $limit );
\r
753 $return['simple'] = 'Need either XML or JSON format for stats';
\r
754 $return['message'] = 'success';
\r
755 return yourls_apply_filter( 'api_stats', $return, $filter, $limit );
\r
758 // Return array for API stat requests
\r
759 function yourls_api_url_stats($shorturl) {
\r
760 $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
\r
761 $keyword = yourls_sanitize_string( $keyword );
\r
763 $return = yourls_get_link_stats( $keyword );
\r
764 $return['simple'] = 'Need either XML or JSON format for stats';
\r
765 return yourls_apply_filter( 'api_url_stats', $return, $shorturl );
\r
768 // Expand short url to long url
\r
769 function yourls_api_expand( $shorturl ) {
\r
770 $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
\r
771 $keyword = yourls_sanitize_string( $keyword );
\r
773 $longurl = yourls_get_keyword_longurl( $keyword );
\r
777 'keyword' => $keyword,
\r
778 'shorturl' => YOURLS_SITE . "/$keyword",
\r
779 'longurl' => $longurl,
\r
780 'simple' => $longurl,
\r
781 'message' => 'success',
\r
782 'statusCode' => 200,
\r
786 'keyword' => $keyword,
\r
787 'simple' => 'not found',
\r
788 'message' => 'Error: short URL not found',
\r
789 'errorCode' => 404,
\r
793 return yourls_apply_filter( 'api_expand', $return, $shorturl );
\r
797 // Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
\r
798 function yourls_get_db_stats( $where = '' ) {
\r
800 $table_url = YOURLS_DB_TABLE_URL;
\r
802 $totals = $ydb->get_row("SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where");
\r
803 $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
\r
805 return yourls_apply_filter( 'get_db_stats', $return, $where );
\r
808 // Return API result. Dies after this
\r
809 function yourls_api_output( $mode, $return ) {
\r
810 if( isset( $return['simple'] ) ) {
\r
811 $simple = $return['simple'];
\r
812 unset( $return['simple'] );
\r
815 yourls_do_action( 'pre_api_output', $mode, $return );
\r
819 header('Content-type: application/json');
\r
820 echo json_encode($return);
\r
824 header('Content-type: application/xml');
\r
825 echo yourls_xml_encode($return);
\r
830 if( isset( $simple ) )
\r
835 yourls_do_action( 'api_output', $mode, $return );
\r
840 // Get number of SQL queries performed
\r
841 function yourls_get_num_queries() {
\r
844 return yourls_apply_filter( 'get_num_queries', $ydb->num_queries );
\r
847 // Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
\r
848 function yourls_get_user_agent() {
\r
849 if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
\r
852 $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
\r
853 $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
\r
855 return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
\r
858 // Redirect to another page
\r
859 function yourls_redirect( $location, $code = 301 ) {
\r
860 yourls_do_action( 'pre_redirect', $location, $code );
\r
861 // Redirect, either properly if possible, or via Javascript otherwise
\r
862 if( !headers_sent() ) {
\r
863 yourls_status_header( $code );
\r
864 header("Location: $location");
\r
866 yourls_redirect_javascript( $location );
\r
871 // Set HTTP status header
\r
872 function yourls_status_header( $code = 200 ) {
\r
873 if( headers_sent() )
\r
876 $protocol = $_SERVER["SERVER_PROTOCOL"];
\r
877 if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
\r
878 $protocol = 'HTTP/1.0';
\r
880 $code = intval( $code );
\r
881 $desc = yourls_get_HTTP_status($code);
\r
883 @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
\r
884 yourls_do_action( 'status_header', $code );
\r
887 // 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
888 function yourls_redirect_javascript( $location, $dontwait = true ) {
\r
891 <script type="text/javascript">
\r
892 window.location="$location";
\r
894 <small>(if you are not redirected after 10 seconds, please <a href="$location">click here</a>)</small>
\r
898 <p>Please <a href="$location">click here</a></p>
\r
901 yourls_do_action( 'redirect_javascript', $location );
\r
904 // Return a HTTP status code
\r
905 function yourls_get_HTTP_status( $code ) {
\r
906 $code = intval( $code );
\r
907 $headers_desc = array(
\r
909 101 => 'Switching Protocols',
\r
910 102 => 'Processing',
\r
915 203 => 'Non-Authoritative Information',
\r
916 204 => 'No Content',
\r
917 205 => 'Reset Content',
\r
918 206 => 'Partial Content',
\r
919 207 => 'Multi-Status',
\r
922 300 => 'Multiple Choices',
\r
923 301 => 'Moved Permanently',
\r
925 303 => 'See Other',
\r
926 304 => 'Not Modified',
\r
927 305 => 'Use Proxy',
\r
929 307 => 'Temporary Redirect',
\r
931 400 => 'Bad Request',
\r
932 401 => 'Unauthorized',
\r
933 402 => 'Payment Required',
\r
934 403 => 'Forbidden',
\r
935 404 => 'Not Found',
\r
936 405 => 'Method Not Allowed',
\r
937 406 => 'Not Acceptable',
\r
938 407 => 'Proxy Authentication Required',
\r
939 408 => 'Request Timeout',
\r
942 411 => 'Length Required',
\r
943 412 => 'Precondition Failed',
\r
944 413 => 'Request Entity Too Large',
\r
945 414 => 'Request-URI Too Long',
\r
946 415 => 'Unsupported Media Type',
\r
947 416 => 'Requested Range Not Satisfiable',
\r
948 417 => 'Expectation Failed',
\r
949 422 => 'Unprocessable Entity',
\r
951 424 => 'Failed Dependency',
\r
952 426 => 'Upgrade Required',
\r
954 500 => 'Internal Server Error',
\r
955 501 => 'Not Implemented',
\r
956 502 => 'Bad Gateway',
\r
957 503 => 'Service Unavailable',
\r
958 504 => 'Gateway Timeout',
\r
959 505 => 'HTTP Version Not Supported',
\r
960 506 => 'Variant Also Negotiates',
\r
961 507 => 'Insufficient Storage',
\r
962 510 => 'Not Extended'
\r
965 if ( isset( $headers_desc[$code] ) )
\r
966 return $headers_desc[$code];
\r
972 // Log a redirect (for stats)
\r
973 function yourls_log_redirect( $keyword ) {
\r
974 if ( !yourls_do_log_redirect() )
\r
978 $table = YOURLS_DB_TABLE_LOG;
\r
980 $keyword = yourls_sanitize_string( $keyword );
\r
981 $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' );
\r
982 $ua = yourls_get_user_agent();
\r
983 $ip = yourls_get_IP();
\r
984 $location = yourls_geo_ip_to_countrycode( $ip );
\r
986 return $ydb->query( "INSERT INTO `$table` VALUES ('', NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" );
\r
989 // Check if we want to not log redirects (for stats)
\r
990 function yourls_do_log_redirect() {
\r
991 return ( !defined('YOURLS_NOSTATS') || YOURLS_NOSTATS != true );
\r
994 // Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
\r
995 function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
\r
996 // allow a plugin to shortcircuit the Geo IP API
\r
997 $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
998 if ( false !== $location )
\r
1001 if ( !file_exists( YOURLS_INC.'/geo/GeoIP.dat') || !file_exists( YOURLS_INC.'/geo/geoip.inc') )
\r
1005 $ip = yourls_get_IP();
\r
1007 require_once( YOURLS_INC.'/geo/geoip.inc') ;
\r
1008 $gi = geoip_open( YOURLS_INC.'/geo/GeoIP.dat', GEOIP_STANDARD);
\r
1009 $location = geoip_country_code_by_addr($gi, $ip);
\r
1012 return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
\r
1015 // Converts a 2 letter country code to long name (ie AU -> Australia)
\r
1016 function yourls_geo_countrycode_to_countryname( $code ) {
\r
1017 // Load the Geo class if not already done
\r
1018 if( !class_exists('GeoIP') ) {
\r
1019 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');
\r
1022 if( class_exists('GeoIP') ) {
\r
1024 $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[$code];
\r
1025 $long = $geo->GEOIP_COUNTRY_NAMES[$id];
\r
1032 // Return flag URL from 2 letter country code
\r
1033 function yourls_geo_get_flag( $code ) {
\r
1034 // Load the Geo class if not already done
\r
1035 if( !class_exists('GeoIP') ) {
\r
1036 $temp = yourls_geo_ip_to_countrycode('127.0.0.1');
\r
1039 if( class_exists('GeoIP') ) {
\r
1040 return YOURLS_SITE.'/includes/geo/flags/flag_'.(strtolower($code)).'.gif';
\r
1047 // Check if an upgrade is needed
\r
1048 function yourls_upgrade_is_needed() {
\r
1049 // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
\r
1050 list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
\r
1051 if( $currentsql < YOURLS_DB_VERSION )
\r
1057 // Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
\r
1058 function yourls_get_current_version_from_sql() {
\r
1059 $currentver = yourls_get_option( 'version' );
\r
1060 $currentsql = yourls_get_option( 'db_version' );
\r
1062 // Values if version is 1.3
\r
1063 if( !$currentver )
\r
1064 $currentver = '1.3';
\r
1065 if( !$currentsql )
\r
1066 $currentsql = '100';
\r
1068 return array( $currentver, $currentsql);
\r
1071 // Read an option from DB (or from cache if available). Return value or $default if not found
\r
1072 function yourls_get_option( $option_name, $default = false ) {
\r
1075 // Allow plugins to short-circuit options
\r
1076 $pre = yourls_apply_filter( 'pre_option_'.$option_name, false );
\r
1077 if ( false !== $pre )
\r
1080 if ( !isset( $ydb->option[$option_name] ) ) {
\r
1081 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1082 $option_name = yourls_escape( $option_name );
\r
1083 $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
\r
1084 if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
\r
1085 $value = $row->option_value;
\r
1086 } else { // option does not exist, so we must cache its non-existence
\r
1087 $value = $default;
\r
1089 $ydb->option[$option_name] = yourls_maybe_unserialize( $value );
\r
1092 return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
\r
1095 // Read all options from DB at once
\r
1096 function yourls_get_all_options() {
\r
1098 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1100 $allopt = $ydb->get_results("SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1");
\r
1102 foreach( (array)$allopt as $option ) {
\r
1103 $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value );
\r
1107 // Update (add if doesn't exist) an option to DB
\r
1108 function yourls_update_option( $option_name, $newvalue ) {
\r
1110 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1112 $safe_option_name = yourls_escape( $option_name );
\r
1114 $oldvalue = yourls_get_option( $safe_option_name );
\r
1116 // If the new and old values are the same, no need to update.
\r
1117 if ( $newvalue === $oldvalue )
\r
1120 if ( false === $oldvalue ) {
\r
1121 yourls_add_option( $option_name, $newvalue );
\r
1125 $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
\r
1127 yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
\r
1129 $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'");
\r
1131 if ( $ydb->rows_affected == 1 ) {
\r
1132 $ydb->option[$option_name] = $newvalue;
\r
1138 // Add an option to the DB
\r
1139 function yourls_add_option( $name, $value = '' ) {
\r
1141 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1142 $safe_name = yourls_escape( $name );
\r
1144 // Make sure the option doesn't already exist
\r
1145 if ( false !== yourls_get_option( $safe_name ) )
\r
1148 $_value = yourls_escape( yourls_maybe_serialize( $value ) );
\r
1150 yourls_do_action( 'add_option', $safe_name, $_value );
\r
1152 $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
\r
1153 $ydb->option[$name] = $value;
\r
1158 // Delete an option from the DB
\r
1159 function yourls_delete_option( $name ) {
\r
1161 $table = YOURLS_DB_TABLE_OPTIONS;
\r
1162 $name = yourls_escape( $name );
\r
1164 // Get the ID, if no ID then return
\r
1165 $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
\r
1166 if ( is_null($option) || !$option->option_id )
\r
1169 yourls_do_action( 'delete_option', $option_name );
\r
1171 $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
\r
1177 // Serialize data if needed. Stolen from WordPress
\r
1178 function yourls_maybe_serialize( $data ) {
\r
1179 if ( is_array( $data ) || is_object( $data ) )
\r
1180 return serialize( $data );
\r
1182 if ( yourls_is_serialized( $data ) )
\r
1183 return serialize( $data );
\r
1188 // Check value to find if it was serialized. Stolen from WordPress
\r
1189 function yourls_is_serialized( $data ) {
\r
1190 // if it isn't a string, it isn't serialized
\r
1191 if ( !is_string( $data ) )
\r
1193 $data = trim( $data );
\r
1194 if ( 'N;' == $data )
\r
1196 if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
\r
1198 switch ( $badions[1] ) {
\r
1202 if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
\r
1208 if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
\r
1215 // Unserialize value only if it was serialized. Stolen from WP
\r
1216 function yourls_maybe_unserialize( $original ) {
\r
1217 if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
\r
1218 return @unserialize( $original );
\r
1222 // Determine if the current page is private
\r
1223 function yourls_is_private() {
\r
1226 if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
\r
1228 // Allow overruling for particular pages:
\r
1231 if( yourls_is_API() ) {
\r
1232 if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
\r
1236 } elseif( yourls_is_infos() ) {
\r
1237 if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
\r
1247 return yourls_apply_filter( 'is_private', $private );
\r
1250 // Show login form if required
\r
1251 function yourls_maybe_require_auth() {
\r
1252 if( yourls_is_private() )
\r
1253 require_once( YOURLS_INC.'/auth.php' );
\r
1256 // Return word or words if more than one
\r
1257 function yourls_plural( $word, $count=1 ) {
\r
1258 return $word . ($count > 1 ? 's' : '');
\r
1261 // Return trimmed string
\r
1262 function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) {
\r
1263 $newstring = $string;
\r
1264 if( function_exists('mb_substr') ) {
\r
1265 if ( mb_strlen( $newstring ) > $length ) {
\r
1266 $newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append;
\r
1269 if ( strlen( $newstring ) > $length ) {
\r
1270 $newstring = substr( $newstring, 0, $length - strlen( $append ) ) . $append;
\r
1273 return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append );
\r
1276 // Allow several short URLs for the same long URL ?
\r
1277 function yourls_allow_duplicate_longurls() {
\r
1278 // special treatment if API to check for WordPress plugin requests
\r
1279 if( yourls_is_API() ) {
\r
1280 if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' )
\r
1283 return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
\r
1286 // Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords.
\r
1287 function yourls_get_duplicate_keywords( $longurl ) {
\r
1288 if( !yourls_allow_duplicate_longurls() )
\r
1292 $longurl = yourls_escape( yourls_sanitize_url($longurl) );
\r
1293 $table = YOURLS_DB_TABLE_URL;
\r
1295 $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" );
\r
1296 return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl );
\r
1299 // Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
\r
1300 function yourls_check_IP_flood( $ip = '' ) {
\r
1302 yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
\r
1305 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
\r
1306 !defined('YOURLS_FLOOD_DELAY_SECONDS')
\r
1310 $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
\r
1312 // Don't throttle whitelist IPs
\r
1313 if( defined('YOURLS_FLOOD_IP_WHITELIST' && YOURLS_FLOOD_IP_WHITELIST ) ) {
\r
1314 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
\r
1315 foreach( (array)$whitelist_ips as $whitelist_ip ) {
\r
1316 $whitelist_ip = trim( $whitelist_ip );
\r
1317 if ( $whitelist_ip == $ip )
\r
1322 // Don't throttle logged in users
\r
1323 if( yourls_is_private() ) {
\r
1324 if( yourls_is_valid_user() === true )
\r
1328 yourls_do_action( 'check_ip_flood', $ip );
\r
1331 $table = YOURLS_DB_TABLE_URL;
\r
1333 $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
\r
1335 $now = date( 'U' );
\r
1336 $then = date( 'U', strtotime( $lasttime ) );
\r
1337 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
\r
1339 yourls_do_action( 'ip_flood', $ip, $now - $then );
\r
1340 yourls_die( 'Too many URLs added too fast. Slow down please.', 'Forbidden', 403 );
\r
1347 // Check if YOURLS is installed
\r
1348 function yourls_is_installed() {
\r
1349 static $is_installed = false;
\r
1350 if ( $is_installed === false ) {
\r
1351 $check_14 = $check_13 = false;
\r
1353 if( defined('YOURLS_DB_TABLE_NEXTDEC') )
\r
1354 $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC);
\r
1355 $check_14 = yourls_get_option( 'version' );
\r
1356 $is_installed = $check_13 || $check_14;
\r
1358 return yourls_apply_filter( 'is_installed', $is_installed );
\r
1361 // Generate random string of (int)$length length and type $type (see function for details)
\r
1362 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
\r
1364 $length = intval( $length );
\r
1366 // define possible characters
\r
1367 switch ( $type ) {
\r
1369 // custom char list, or comply to charset as defined in config
\r
1371 $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
\r
1374 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
\r
1376 $possible = "23456789bcdfghjkmnpqrstvwxyz";
\r
1379 // Same, with lower + upper
\r
1381 $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
\r
1384 // all letters, lowercase
\r
1386 $possible = "abcdefghijklmnopqrstuvwxyz";
\r
1389 // all letters, lowercase + uppercase
\r
1391 $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
\r
1394 // all digits & letters lowercase
\r
1396 $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
\r
1399 // all digits & letters lowercase + uppercase
\r
1401 $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
\r
1407 while ($i < $length) {
\r
1408 $str .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
\r
1412 return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
\r
1415 // Return salted string
\r
1416 function yourls_salt( $string ) {
\r
1417 $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
\r
1418 return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
\r
1421 // Add a query var to a URL and return URL. Completely stolen from WP.
\r
1422 // Works with one of these parameter patterns:
\r
1423 // array( 'var' => 'value' )
\r
1424 // array( 'var' => 'value' ), $url
\r
1426 // 'var', 'value', $url
\r
1427 // If $url ommited, uses $_SERVER['REQUEST_URI']
\r
1428 function yourls_add_query_arg() {
\r
1430 if ( is_array( func_get_arg(0) ) ) {
\r
1431 if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
\r
1432 $uri = $_SERVER['REQUEST_URI'];
\r
1434 $uri = @func_get_arg( 1 );
\r
1436 if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
\r
1437 $uri = $_SERVER['REQUEST_URI'];
\r
1439 $uri = @func_get_arg( 2 );
\r
1442 $uri = str_replace( '&', '&', $uri );
\r
1445 if ( $frag = strstr( $uri, '#' ) )
\r
1446 $uri = substr( $uri, 0, -strlen( $frag ) );
\r
1450 if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
\r
1451 $protocol = $matches[0];
\r
1452 $uri = substr( $uri, strlen( $protocol ) );
\r
1457 if ( strpos( $uri, '?' ) !== false ) {
\r
1458 $parts = explode( '?', $uri, 2 );
\r
1459 if ( 1 == count( $parts ) ) {
\r
1461 $query = $parts[0];
\r
1463 $base = $parts[0] . '?';
\r
1464 $query = $parts[1];
\r
1466 } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
\r
1467 $base = $uri . '?';
\r
1474 parse_str( $query, $qs );
\r
1475 $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
\r
1476 if ( is_array( func_get_arg( 0 ) ) ) {
\r
1477 $kayvees = func_get_arg( 0 );
\r
1478 $qs = array_merge( $qs, $kayvees );
\r
1480 $qs[func_get_arg( 0 )] = func_get_arg( 1 );
\r
1483 foreach ( (array) $qs as $k => $v ) {
\r
1484 if ( $v === false )
\r
1488 $ret = http_build_query( $qs );
\r
1489 $ret = trim( $ret, '?' );
\r
1490 $ret = preg_replace( '#=(&|$)#', '$1', $ret );
\r
1491 $ret = $protocol . $base . $ret . $frag;
\r
1492 $ret = rtrim( $ret, '?' );
\r
1496 // Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
\r
1497 function yourls_urlencode_deep($value) {
\r
1498 $value = is_array($value) ? array_map('yourls_urlencode_deep', $value) : urlencode($value);
\r
1502 // Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
\r
1503 function yourls_remove_query_arg( $key, $query = false ) {
\r
1504 if ( is_array( $key ) ) { // removing multiple keys
\r
1505 foreach ( $key as $k )
\r
1506 $query = add_query_arg( $k, false, $query );
\r
1509 return add_query_arg( $key, false, $query );
\r
1512 // Return a time-dependent string for nonce creation
\r
1513 function yourls_tick() {
\r
1514 return ceil( time() / YOURLS_NONCE_LIFE );
\r
1517 // Create a time limited, action limited and user limited token
\r
1518 function yourls_create_nonce( $action, $user = false ) {
\r
1519 if( false == $user )
\r
1520 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
\r
1521 $tick = yourls_tick();
\r
1522 return substr( yourls_salt($tick . $action . $user), 0, 10 );
\r
1525 // Create a nonce field for inclusion into a form
\r
1526 function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
\r
1527 $field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
\r
1533 // Add a nonce to a URL. If URL omitted, adds nonce to current URL
\r
1534 function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
\r
1535 $nonce = yourls_create_nonce( $action, $user );
\r
1536 return yourls_add_query_arg( $name, $nonce, $url );
\r
1539 // Check validity of a nonce (ie time span, user and action match).
\r
1540 // Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
\r
1541 function yourls_verify_nonce( $action, $nonce, $user = false, $return = '' ) {
\r
1543 if( false == $user )
\r
1544 $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
\r
1546 // what nonce should be
\r
1547 $valid = yourls_create_nonce( $action, $user );
\r
1549 if( $nonce == $valid ) {
\r
1554 yourls_die( 'Unauthorized action or expired link', 'Error', 403 );
\r
1558 // Sanitize a version number (1.4.1-whatever -> 1.4.1)
\r
1559 function yourls_sanitize_version( $ver ) {
\r
1560 return preg_replace( '/[^0-9.]/', '', $ver );
\r
1563 // Converts keyword into short link
\r
1564 function yourls_link( $keyword = '' ) {
\r
1565 return YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
\r
1568 // Check if we're in API mode. Returns bool
\r
1569 function yourls_is_API() {
\r
1570 if ( defined('YOURLS_API') && YOURLS_API == true )
\r
1575 // Check if we're in Ajax mode. Returns bool
\r
1576 function yourls_is_Ajax() {
\r
1577 if ( defined('YOURLS_AJAX') && YOURLS_AJAX == true )
\r
1582 // Check if we're in GO mode (yourls-go.php). Returns bool
\r
1583 function yourls_is_GO() {
\r
1584 if ( defined('YOURLS_GO') && YOURLS_GO == true )
\r
1589 // Check if we're displaying stats infos (yourls-infos.php). Returns bool
\r
1590 function yourls_is_infos() {
\r
1591 if ( defined('YOURLS_INFOS') && YOURLS_INFOS == true )
\r
1596 // Check if we'll need interface display function (ie not API or redirection)
\r
1597 function yourls_has_interface() {
\r
1598 if( yourls_is_API() or yourls_is_GO() )
\r
1603 // Check if we're in the admin area. Returns bool
\r
1604 function yourls_is_admin() {
\r
1605 if ( defined('YOURLS_ADMIN') && YOURLS_ADMIN == true )
\r
1610 // Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
\r
1611 function yourls_is_windows() {
\r
1612 return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
\r
1615 // Check if SSL is required. Returns bool.
\r
1616 function yourls_needs_ssl() {
\r
1617 if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true )
\r
1622 // Return admin link, with SSL preference if applicable.
\r
1623 function yourls_admin_url( $page = '' ) {
\r
1624 $admin = YOURLS_SITE . '/admin/' . $page;
\r
1625 if( yourls_is_ssl() or yourls_needs_ssl() )
\r
1626 $admin = str_replace('http://', 'https://', $admin);
\r
1627 return yourls_apply_filter( 'admin_url', $admin, $page );
\r
1630 // Return YOURLS_SITE, with SSL preference
\r
1631 function yourls_site_url( $echo = true ) {
\r
1632 $site = YOURLS_SITE;
\r
1633 // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
\r
1634 if( yourls_is_ssl() )
\r
1635 $site = str_replace( 'http://', 'https://', $site );
\r
1636 $site = yourls_apply_filter( 'site_url', $site );
\r
1642 // Check if SSL is used, returns bool. Stolen from WP.
\r
1643 function yourls_is_ssl() {
\r
1645 if ( isset($_SERVER['HTTPS']) ) {
\r
1646 if ( 'on' == strtolower($_SERVER['HTTPS']) )
\r
1648 if ( '1' == $_SERVER['HTTPS'] )
\r
1650 } elseif ( isset($_SERVER['SERVER_PORT']) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
\r
1653 return yourls_apply_filter( 'is_ssl', $is_ssl );
\r
1657 // Get a remote page <title>, return a string (either title or url)
\r
1658 function yourls_get_remote_title( $url ) {
\r
1659 require_once( YOURLS_INC.'/functions-http.php' );
\r
1661 $url = yourls_sanitize_url( $url );
\r
1663 $title = $charset = false;
\r
1665 $content = yourls_get_remote_content( $url );
\r
1667 // If false, return url as title.
\r
1668 // Todo: improve this with temporary title when shorturl_meta available?
\r
1669 if( false === $content )
\r
1672 if( $content !== false ) {
\r
1673 // look for <title>
\r
1674 if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
\r
1675 $title = $found[1];
\r
1679 // look for charset
\r
1680 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
\r
1681 if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) {
\r
1682 $charset = trim($found[1], '"\' ');
\r
1687 // if title not found, guess if returned content was actually an error message
\r
1688 if( $title == false && strpos( $content, 'Error' ) === 0 ) {
\r
1689 $title = $content;
\r
1692 if( $title == false )
\r
1696 if( !yourls_seems_utf8( $title ) )
\r
1697 $title = utf8_encode( $title );
\r
1700 // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars)
\r
1701 if( function_exists( 'mb_convert_encoding' ) ) {
\r
1703 $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
\r
1705 $title = @mb_convert_encoding( $title, 'UTF-8' );
\r
1709 // Remove HTML entities
\r
1710 $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
\r
1712 // Strip out evil things
\r
1713 $title = yourls_sanitize_title( $title );
\r
1715 return yourls_apply_filter( 'get_remote_title', $title, $url );
\r
1718 // Sanitize a filename (no Win32 stuff)
\r
1719 function yourls_sanitize_filename( $file ) {
\r
1720 $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs
\r
1721 $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash
\r
1725 // Check for maintenance mode that will shortcut everything
\r
1726 function yourls_check_maintenance_mode() {
\r
1728 // TODO: all cases that always display the sites (is_admin but not is_ajax?)
\r
1732 // first case: /user/maintenance.php file
\r
1733 if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
\r
1734 include( YOURLS_USERDIR.'/maintenance.php' );
\r
1738 // second case: option in DB
\r
1739 if( yourls_get_option( 'maintenance_mode' ) !== false ) {
\r
1740 require_once( YOURLS_INC.'/functions-html.php' );
\r
1741 $title = 'Service temporarily unavailable';
\r
1742 $message = 'Our service is currently undergoing scheduled maintenance.</p>
\r
1743 <p>Things should not last very long, thank you for your patience and please excuse the inconvenience';
\r
1744 yourls_die( $message, $title , 503 );
\r
1749 // Toggle maintenance mode
\r
1750 function yourls_maintenance_mode( $maintenance = true ) {
\r
1751 yourls_update_option( 'maintenance_mode', (bool)$maintenance );
\r
1754 // Check if a string seems to be UTF-8. Stolen from WP.
\r
1755 function yourls_seems_utf8($str) {
\r
1756 $length = strlen($str);
\r
1757 for ($i=0; $i < $length; $i++) {
\r
1758 $c = ord($str[$i]);
\r
1759 if ($c < 0x80) $n = 0; # 0bbbbbbb
\r
1760 elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
\r
1761 elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
\r
1762 elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
\r
1763 elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
\r
1764 elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
\r
1765 else return false; # Does not match any model
\r
1766 for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
\r
1767 if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
\r
1774 // Quick UA check for mobile devices. Return boolean.
\r
1775 function yourls_is_mobile_device() {
\r
1776 // Strings searched
\r
1778 'android', 'blackberry', 'blazer',
\r
1779 'compal', 'elaine', 'fennec', 'hiptop',
\r
1780 'iemobile', 'iphone', 'ipod', 'ipad',
\r
1781 'iris', 'kindle', 'opera mobi', 'opera mini',
\r
1782 'palm', 'phone', 'pocket', 'psp', 'symbian',
\r
1783 'treo', 'wap', 'windows ce', 'windows phone'
\r
1786 // Current user-agent
\r
1787 $current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
\r
1790 return str_replace( $mobiles, '', $current ) != $current;
\r
1793 // Get request in YOURLS base (eg in 'http://site.com/yourls/abcd' get 'abdc')
\r
1794 function yourls_get_request() {
\r
1795 // Cover all cases: YOURLS_SITE https or not * current URL https or not
\r
1796 $base = str_replace( array( 'https', 'http' ), '', YOURLS_SITE );
\r
1797 $scheme = yourls_is_ssl() ? 'https' : 'http' ;
\r
1799 // Extract the 'abdc' from the requested URL
\r
1800 $request = str_replace(
\r
1801 $scheme . $base.'/',
\r
1803 $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']
\r
1806 return yourls_apply_filter( 'get_request', $request );
\r
1809 // Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
\r
1810 function yourls_fix_request_uri() {
\r
1812 $default_server_values = array(
\r
1813 'SERVER_SOFTWARE' => '',
\r
1814 'REQUEST_URI' => '',
\r
1816 $_SERVER = array_merge( $default_server_values, $_SERVER );
\r
1818 // Fix for IIS when running with PHP ISAPI
\r
1819 if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
\r
1821 // IIS Mod-Rewrite
\r
1822 if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
\r
1823 $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
\r
1825 // IIS Isapi_Rewrite
\r
1826 else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
\r
1827 $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
\r
1829 // Use ORIG_PATH_INFO if there is no PATH_INFO
\r
1830 if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
\r
1831 $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
\r
1833 // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
\r
1834 if ( isset( $_SERVER['PATH_INFO'] ) ) {
\r
1835 if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
\r
1836 $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
\r
1838 $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
\r
1841 // Append the query string if it exists and isn't null
\r
1842 if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
\r
1843 $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
\r