From 3daf6593d89c608a6660a6c0b872eeb2607548ba Mon Sep 17 00:00:00 2001 From: LeoColomb Date: Sun, 7 Apr 2013 12:52:52 +0200 Subject: [PATCH] Fix end of line --- .gitattributes | 2 + CHANGELOG.md | 190 +- LICENSE.md | 52 +- README.md | 30 +- admin/admin-ajax.php | 102 +- admin/index.php | 588 ++-- admin/install.php | 150 +- admin/plugins.php | 328 +- admin/tools.php | 234 +- admin/upgrade.php | 172 +- css/cal.css | 26 +- css/infos.css | 226 +- css/share.css | 130 +- css/style.css | 660 ++-- css/tablesorter.css | 206 +- includes/class-mysql.php | 1850 +++++----- includes/functions-api.php | 364 +- includes/functions-auth.php | 410 +-- includes/functions-compat.php | 342 +- includes/functions-formatting.php | 978 +++--- includes/functions-html.php | 1702 +++++----- includes/functions-http.php | 334 +- includes/functions-install.php | 500 +-- includes/functions-kses.php | 1558 ++++----- includes/functions-l10n.php | 2266 ++++++------- includes/functions-plugins.php | 1158 +++---- includes/functions-upgrade.php | 592 ++-- includes/functions-xml.php | 162 +- includes/functions.php | 3386 +++++++++---------- includes/load-yourls.php | 354 +- includes/pomo/entry.php | 154 +- includes/pomo/mo.php | 512 +-- includes/pomo/po.php | 768 ++--- includes/pomo/streams.php | 416 +-- includes/pomo/translations.php | 550 +-- includes/version.php | 8 +- js/common.js | 314 +- js/infos.js | 94 +- js/insert.js | 404 +-- js/jquery-1.8.2.min.js | 2 +- js/jquery.cal.js | 636 ++-- js/jquery.tablesorter.min.js | 92 +- js/jquery.zclip.min.js | 22 +- js/share.js | 114 +- pages/examplepage.php | 46 +- readme.html | 1660 ++++----- sample-public-api.txt | 24 +- sample-public-front-page.txt | 232 +- sample-remote-api-call.txt | 92 +- sample-robots.txt | 18 +- user/config-sample.php | 170 +- user/plugins/hyphens-in-urls/README.txt | 6 +- user/plugins/hyphens-in-urls/plugin.php | 38 +- user/plugins/random-bg/README.txt | 6 +- user/plugins/random-bg/plugin.php | 54 +- user/plugins/sample-page/README.txt | 6 +- user/plugins/sample-page/plugin.php | 124 +- user/plugins/sample-plugin/README.txt | 6 +- user/plugins/sample-plugin/plugin.php | 122 +- user/plugins/sample-toolbar/README.txt | 6 +- user/plugins/sample-toolbar/css/toolbar.css | 158 +- user/plugins/sample-toolbar/js/toolbar.js | 44 +- user/plugins/sample-toolbar/plugin.php | 250 +- yourls-api.php | 100 +- yourls-go.php | 90 +- yourls-infos.php | 1098 +++--- yourls-loader.php | 122 +- 67 files changed, 13806 insertions(+), 13804 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..eba1110 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b30a0..ec3e5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,96 +1,96 @@ -YOURLS Changelog -================ - -This file attempts to list the main changes through all versions of YOURLS. For a much more detailed -list, simply refer to the commit messages: http://code.google.com/p/yourls/source/list - -1.0 ---- -- initial release - -1.0.1 ------ -- don't remember. Trivial stuff probably. - -1.1 ---- -- don't remember. Some little bugs I guess. - -1.2 ---- -- don't remember. A few tiny stuff for sure. - -1.3-RC1 -------- -- added bookmarklet and tools page -- improved XSS filter when adding new URL -- code cleanup in admin/index.php to separate code and display -- added favicon -- stricter coding to prevent notices with undefined indexes -- hide PHP notices & SQL errors & warnings, unless YOURLS_DEBUG constant set to true - -1.4 ---- -- added an upgrader from 1.3 to 1.4 -- change in logic: now using a global object $ydb for everything related to DB and other globally needed stuff -- change in logic: include "load-yourls.php" instead of "config.php" to start engine -- change in DB schema: now storing URLs with their keyword as used in shorturl, allowing for any keyword length -- change in DB schema: new table for storing various options including next_id, dropping table of the same name -- change in DB schema: new table for storing hits (for stats) -- improved the installer, with .htaccess file creation -- layout tweak: now prettier, isn't it? -- stats! OMG stats! - -1.4.1 ------ -- fixed base 62 URLs (keywords with MiXeD CaSe) -- new & secure auth method for API calls, with no need to use login & password combo -- allow SSL enforcement for admin pages -- new API method: stats for individual URL. -- prevent internal redirection loops -- filter and search URLs & short URLs by date - -1.4.2 ------ -- fixed bug in auth function -- added sample public API file -- added check in API requests for WordPress plugin when adding a new short URL -- prettier sample public interface - -1.4.3 ------ -- fixed bug no-stats-showing-ffs due to inconsistency in DB schema -- improve error reporting with API method url-stat - -1.5 ---- -- added: plugin architecture! OMG plugins!!1!!1! -- added: directory /user, config.php can be moved there -- added: new "instant bookmarklets" -- added: 1 click copy-to-clipboard a la bitly -- change in logic: now all request are handled by PHP and don't rely on .htaccess -- added: saving URL titles -- added: support for prefix-n-shorten: sho.rt/http://example.com/ -- added: core plugin to allow hyphens in URLs -- added: core sample plugin to wrap redirected URLs in a social toolbar -- added: core sample plugin to show how to create administration page in plugins -- added: core plugin to display a random pretty background -- changed: layout now using a more consistent palette, see http://yourls.org/palette -- added: anti XSS and anti CSRF measures -- added: interactive map if possible in stat traffic by countries -- fixed: lots of bugs - -1.5.1 ------ -- added: full jsonp support -- added: ability to use encrypted passwords in the config file -- fixed: support for http://www.sho.rt/bleh and http://sho.rt/bleh -- added: support for any favicon dropped in the /user directory -- updated: Google Visualization API instead of deprecated Google Charts -- fixed: bugs, bugs, bugs -- added: hooks, hooks, hooks -- improved: things, things, things - -1.6 ---- +YOURLS Changelog +================ + +This file attempts to list the main changes through all versions of YOURLS. For a much more detailed +list, simply refer to the commit messages: http://code.google.com/p/yourls/source/list + +1.0 +--- +- initial release + +1.0.1 +----- +- don't remember. Trivial stuff probably. + +1.1 +--- +- don't remember. Some little bugs I guess. + +1.2 +--- +- don't remember. A few tiny stuff for sure. + +1.3-RC1 +------- +- added bookmarklet and tools page +- improved XSS filter when adding new URL +- code cleanup in admin/index.php to separate code and display +- added favicon +- stricter coding to prevent notices with undefined indexes +- hide PHP notices & SQL errors & warnings, unless YOURLS_DEBUG constant set to true + +1.4 +--- +- added an upgrader from 1.3 to 1.4 +- change in logic: now using a global object $ydb for everything related to DB and other globally needed stuff +- change in logic: include "load-yourls.php" instead of "config.php" to start engine +- change in DB schema: now storing URLs with their keyword as used in shorturl, allowing for any keyword length +- change in DB schema: new table for storing various options including next_id, dropping table of the same name +- change in DB schema: new table for storing hits (for stats) +- improved the installer, with .htaccess file creation +- layout tweak: now prettier, isn't it? +- stats! OMG stats! + +1.4.1 +----- +- fixed base 62 URLs (keywords with MiXeD CaSe) +- new & secure auth method for API calls, with no need to use login & password combo +- allow SSL enforcement for admin pages +- new API method: stats for individual URL. +- prevent internal redirection loops +- filter and search URLs & short URLs by date + +1.4.2 +----- +- fixed bug in auth function +- added sample public API file +- added check in API requests for WordPress plugin when adding a new short URL +- prettier sample public interface + +1.4.3 +----- +- fixed bug no-stats-showing-ffs due to inconsistency in DB schema +- improve error reporting with API method url-stat + +1.5 +--- +- added: plugin architecture! OMG plugins!!1!!1! +- added: directory /user, config.php can be moved there +- added: new "instant bookmarklets" +- added: 1 click copy-to-clipboard a la bitly +- change in logic: now all request are handled by PHP and don't rely on .htaccess +- added: saving URL titles +- added: support for prefix-n-shorten: sho.rt/http://example.com/ +- added: core plugin to allow hyphens in URLs +- added: core sample plugin to wrap redirected URLs in a social toolbar +- added: core sample plugin to show how to create administration page in plugins +- added: core plugin to display a random pretty background +- changed: layout now using a more consistent palette, see http://yourls.org/palette +- added: anti XSS and anti CSRF measures +- added: interactive map if possible in stat traffic by countries +- fixed: lots of bugs + +1.5.1 +----- +- added: full jsonp support +- added: ability to use encrypted passwords in the config file +- fixed: support for http://www.sho.rt/bleh and http://sho.rt/bleh +- added: support for any favicon dropped in the /user directory +- updated: Google Visualization API instead of deprecated Google Charts +- fixed: bugs, bugs, bugs +- added: hooks, hooks, hooks +- improved: things, things, things + +1.6 +--- - added: custom API actions diff --git a/LICENSE.md b/LICENSE.md index 8e51d23..c87ca32 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,26 +1,26 @@ - __ ______ _ _ _____ _ _____ - \ \ / / __ \| | | | __ \| | / ____| - \ \_/ / | | | | | | |__) | | | (___ - \ /| | | | | | | _ /| | \___ \ - | | | |__| | |__| | | \ \| |____ ____) | - |_| \____/ \____/|_| \_\______|_____/ - -YOURLS - Your Own URL Shortener - A URL shortening script - ---------------------------------------------------------------- - -This program is free software. Do whatever the hell you want with it. - -This program is distributed in the hope that it will be useful and/or -fun to use. There is absolutely no guarantee of any kind about anything. - -For more text, have a look at http://www.gnu.org/licenses/gpl.txt - ---------------------------------------------------------------- - -This software incorporates code stolen from WordPress, as shown by -comments in source where applicable. - ---------------------------------------------------------------- - -By Lester CHAN (initial idea), Ozh RICHARD (development) and contributors. + __ ______ _ _ _____ _ _____ + \ \ / / __ \| | | | __ \| | / ____| + \ \_/ / | | | | | | |__) | | | (___ + \ /| | | | | | | _ /| | \___ \ + | | | |__| | |__| | | \ \| |____ ____) | + |_| \____/ \____/|_| \_\______|_____/ + +YOURLS - Your Own URL Shortener - A URL shortening script + +--------------------------------------------------------------- + +This program is free software. Do whatever the hell you want with it. + +This program is distributed in the hope that it will be useful and/or +fun to use. There is absolutely no guarantee of any kind about anything. + +For more text, have a look at http://www.gnu.org/licenses/gpl.txt + +--------------------------------------------------------------- + +This software incorporates code stolen from WordPress, as shown by +comments in source where applicable. + +--------------------------------------------------------------- + +By Lester CHAN (initial idea), Ozh RICHARD (development) and contributors. diff --git a/README.md b/README.md index 5e702ec..40d1f8c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -![yourls](http://yourls.org/images/yourls-logo.png) -[YOURLS](http://yourls.org) -====== -**YOURLS** is a set of PHP script that will allow you to run your own URL shortener. You'll have full control over your data, detailed stats and analytics, plugins, and more. It's free. - -Useful links ------------- -* [Project home & doc](http://yourls.org) -* [Official blog](http://blog.yourls.org) -* [Wiki documentation](https://github.com/YOURLS/YOURLS/wiki/) -* [Issue tracker](https://github.com/YOURLS/YOURLS/issues) -* [Release archive](https://github.com/YOURLS/YOURLS/tags) - -License -------- +![yourls](http://yourls.org/images/yourls-logo.png) +[YOURLS](http://yourls.org) +====== +**YOURLS** is a set of PHP script that will allow you to run your own URL shortener. You'll have full control over your data, detailed stats and analytics, plugins, and more. It's free. + +Useful links +------------ +* [Project home & doc](http://yourls.org) +* [Official blog](http://blog.yourls.org) +* [Wiki documentation](https://github.com/YOURLS/YOURLS/wiki/) +* [Issue tracker](https://github.com/YOURLS/YOURLS/issues) +* [Release archive](https://github.com/YOURLS/YOURLS/tags) + +License +------- Free software. Do whatever the hell you want with it. diff --git a/admin/admin-ajax.php b/admin/admin-ajax.php index e7797c3..c186196 100644 --- a/admin/admin-ajax.php +++ b/admin/admin-ajax.php @@ -1,51 +1,51 @@ - $row) ); - break; - - case 'edit_save': - yourls_verify_nonce( 'edit-save_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' ); - $return = yourls_edit_link( $_REQUEST['url'], $_REQUEST['keyword'], $_REQUEST['newkeyword'], $_REQUEST['title'] ); - echo json_encode($return); - break; - - case 'delete': - yourls_verify_nonce( 'delete-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' ); - $query = yourls_delete_link_by_keyword( $_REQUEST['keyword'] ); - echo json_encode(array('success'=>$query)); - break; - - case 'logout': - // unused for the moment - yourls_logout(); - break; - - default: - yourls_do_action( 'yourls_ajax_'.$action ); - -} - -die(); + $row) ); + break; + + case 'edit_save': + yourls_verify_nonce( 'edit-save_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' ); + $return = yourls_edit_link( $_REQUEST['url'], $_REQUEST['keyword'], $_REQUEST['newkeyword'], $_REQUEST['title'] ); + echo json_encode($return); + break; + + case 'delete': + yourls_verify_nonce( 'delete-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' ); + $query = yourls_delete_link_by_keyword( $_REQUEST['keyword'] ); + echo json_encode(array('success'=>$query)); + break; + + case 'logout': + // unused for the moment + yourls_logout(); + break; + + default: + yourls_do_action( 'yourls_ajax_'.$action ); + +} + +die(); diff --git a/admin/index.php b/admin/index.php index 294b2ba..59a6bcf 100644 --- a/admin/index.php +++ b/admin/index.php @@ -1,295 +1,295 @@ -' : '<' ); - $where = " AND clicks $click_moreless $click_limit"; -} else { - $click_filter = ''; -} - -// Searching -if( !empty( $search ) && !empty( $_GET['search_in'] ) ) { - switch( $_GET['search_in'] ) { - case 'keyword': - $search_in_text = yourls__( 'Short URL' ); - $search_in = 'keyword'; - break; - case 'url': - $search_in_text = yourls__( 'URL' ); - $search_in = 'url'; - break; - case 'title': - $search_in_text = yourls__( 'Title' ); - $search_in = 'title'; - break; - case 'ip': - $search_in_text = yourls__( 'IP Address' ); - $search_in = 'ip'; - break; - } - $search_sentence = yourls_s( 'Searching for %1$s in %2$s.', yourls_esc_html( $search ), yourls_esc_html( $search_in_text ) ); - $search_url = yourls_sanitize_url( "&search=$search&search_in=$search_in" ); - $search_text = $search; - $search = str_replace( '*', '%', '*' . yourls_escape( $search ) . '*' ); - $where .= " AND `$search_in` LIKE ('$search')"; -} - -// Time span -if( !empty( $_GET['date_filter'] ) ) { - switch( $_GET['date_filter'] ) { - case 'before': - $date_filter = 'before'; - if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) { - $date_first = yourls_sanitize_date( $_GET['date_first'] ); - $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); - $where .= " AND `timestamp` < '$date_first_sql'"; - } - break; - case 'after': - $date_filter = 'after'; - if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) { - $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); - $date_first = yourls_sanitize_date( $_GET['date_first'] ); - $where .= " AND `timestamp` > '$date_first_sql'"; - } - break; - case 'between': - $date_filter = 'between'; - if( isset( $_GET['date_first'] ) && isset( $_GET['date_second'] ) && yourls_sanitize_date( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_second'] ) ) { - $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); - $date_second_sql = yourls_sanitize_date_for_sql( $_GET['date_second'] ); - $date_first = yourls_sanitize_date( $_GET['date_first'] ); - $date_second = yourls_sanitize_date( $_GET['date_second'] ); - $where .= " AND `timestamp` BETWEEN '$date_first_sql' AND '$date_second_sql'"; - } - break; - } -} - -// Sorting -if( !empty( $_GET['sort_by'] ) || !empty( $_GET['sort_order'] ) ) { - switch( $_GET['sort_by'] ) { - case 'keyword': - $sort_by_text = yourls__( 'Short URL' ); - $sort_by = 'keyword'; - break; - case 'url': - $sort_by_text = yourls__( 'URL' ); - $sort_by = 'url'; - break; - case 'timestamp': - $sort_by_text = yourls__( 'Date' ); - $sort_by = 'timestamp'; - break; - case 'ip': - $sort_by_text = yourls__( 'IP Address' ); - $sort_by = 'ip'; - break; - case 'clicks': - $sort_by_text = yourls__( 'Clicks' ); - $sort_by = 'clicks'; - break; - } - switch( $_GET['sort_order'] ) { - case 'asc': - $sort_order = 'asc'; - break; - case 'desc': - $sort_order = 'desc'; - break; - } -} - -// Get URLs Count for current filter, total links in DB & total clicks -list( $total_urls, $total_clicks ) = array_values( yourls_get_db_stats() ); -if ( $where ) { - list( $total_items, $total_items_clicks ) = array_values( yourls_get_db_stats( $where ) ); -} else { - $total_items = $total_urls; - $total_items_clicks = false; -} - -// This is a bookmarklet -if ( isset( $_GET['u'] ) ) { - $is_bookmark = true; - yourls_do_action( 'bookmarklet' ); - - // No sanitization needed here: everything happens in yourls_add_new_link() - $url = ( $_GET['u'] ); - $keyword = ( isset( $_GET['k'] ) ? ( $_GET['k'] ) : '' ); - $title = ( isset( $_GET['t'] ) ? ( $_GET['t'] ) : '' ); - $return = yourls_add_new_link( $url, $keyword, $title ); - - // If fails because keyword already exist, retry with no keyword - if ( isset( $return['status'] ) && $return['status'] == 'fail' && isset( $return['code'] ) && $return['code'] == 'error:keyword' ) { - $msg = $return['message']; - $return = yourls_add_new_link( $url, '', $ydb ); - $return['message'] .= ' ('.$msg.')'; - } - - // Stop here if bookmarklet with a JSON callback function - if( isset( $_GET['jsonp'] ) && $_GET['jsonp'] == 'yourls' ) { - $short = $return['shorturl'] ? $return['shorturl'] : ''; - $message = $return['message']; - header( 'Content-type: application/json' ); - echo yourls_apply_filter( 'bookmarklet_jsonp', "yourls_callback({'short_url':'$short','message':'$message'});" ); - - die(); - } - - // Now use the URL that has been sanitized and returned by yourls_add_new_link() - $url = $return['url']['url']; - $where = sprintf( " AND `url` LIKE '%s' ", yourls_escape( $url ) ); - - $page = $total_pages = $perpage = 1; - $offset = 0; - - $text = ( isset( $_GET['s'] ) ? stripslashes( $_GET['s'] ) : '' ); - - -// This is not a bookmarklet -} else { - $is_bookmark = false; - - // Checking $page, $offset, $perpage - if( empty($page) || $page == 0 ) { - $page = 1; - } - if( empty($offset) ) { - $offset = 0; - } - if( empty($perpage) || $perpage == 0) { - $perpage = 50; - } - - // Determine $offset - $offset = ( $page-1 ) * $perpage; - - // Determine Max Number Of Items To Display On Page - if( ( $offset + $perpage ) > $total_items ) { - $max_on_page = $total_items; - } else { - $max_on_page = ( $offset + $perpage ); - } - - // Determine Number Of Items To Display On Page - if ( ( $offset + 1 ) > $total_items ) { - $display_on_page = $total_items; - } else { - $display_on_page = ( $offset + 1 ); - } - - // Determing Total Amount Of Pages - $total_pages = ceil( $total_items / $perpage ); -} - - -// Begin output of the page -$context = ( $is_bookmark ? 'bookmark' : 'index' ); -yourls_html_head( $context ); -yourls_html_logo(); -yourls_html_menu() ; - -yourls_do_action( 'admin_page_before_content' ); - -if ( !$is_bookmark ) { ?> -

-

%1$s to %2$s of %3$s URLs' ), $display_on_page, $max_on_page, $total_items ); - if( $total_items_clicks !== false ) - echo ", " . sprintf( yourls_n( 'counting 1 click', 'counting %s clicks', $total_items_clicks ), yourls_number_format_i18n( $total_items_clicks ) ); - ?>.

- -

%1$s links, %2$s clicks, and counting!' ), yourls_number_format_i18n( $total_urls ), yourls_number_format_i18n( $total_clicks ) ); ?>

- - - - -$(document).ready(function(){ - feedback( "' . $return['message'] . '", "'. $return['status'] .'"); - init_clipboard(); - });'; -} - -yourls_do_action( 'admin_page_before_table' ); - -yourls_table_head(); - -if ( !$is_bookmark ) { - $params = array( - 'search' => $search, - 'search_text' => $search_text, - 'search_in' => $search_in, - 'sort_by' => $sort_by, - 'sort_order' => $sort_order, - 'page' => $page, - 'perpage' => $perpage, - 'click_filter' => $click_filter, - 'click_limit' => $click_limit, - 'total_pages' => $total_pages, - 'date_filter' => $date_filter, - 'date_first' => $date_first, - 'date_second' => $date_second, - ); - yourls_html_tfooter( $params ); -} - -yourls_table_tbody_start(); - -// Main Query -$where = yourls_apply_filter( 'admin_list_where', $where ); -$url_results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 $where ORDER BY `$sort_by` $sort_order LIMIT $offset, $perpage;" ); -$found_rows = false; -if( $url_results ) { - $found_rows = true; - foreach( $url_results as $url_result ) { - $keyword = yourls_sanitize_string( $url_result->keyword ); - $timestamp = strtotime( $url_result->timestamp ); - $url = stripslashes( $url_result->url ); - $ip = $url_result->ip; - $title = $url_result->title ? $url_result->title : ''; - $clicks = $url_result->clicks; - - echo yourls_table_add_row( $keyword, $url, $title, $ip, $clicks, $timestamp ); - } -} - -$display = $found_rows ? 'display:none' : ''; -echo '' . yourls__('No URL') . ''; - -yourls_table_tbody_end(); - -yourls_table_end(); - -yourls_do_action( 'admin_page_after_table' ); - -if ( $is_bookmark ) - yourls_share_box( $url, $return['shorturl'], $title, $text ); -?> - +' : '<' ); + $where = " AND clicks $click_moreless $click_limit"; +} else { + $click_filter = ''; +} + +// Searching +if( !empty( $search ) && !empty( $_GET['search_in'] ) ) { + switch( $_GET['search_in'] ) { + case 'keyword': + $search_in_text = yourls__( 'Short URL' ); + $search_in = 'keyword'; + break; + case 'url': + $search_in_text = yourls__( 'URL' ); + $search_in = 'url'; + break; + case 'title': + $search_in_text = yourls__( 'Title' ); + $search_in = 'title'; + break; + case 'ip': + $search_in_text = yourls__( 'IP Address' ); + $search_in = 'ip'; + break; + } + $search_sentence = yourls_s( 'Searching for %1$s in %2$s.', yourls_esc_html( $search ), yourls_esc_html( $search_in_text ) ); + $search_url = yourls_sanitize_url( "&search=$search&search_in=$search_in" ); + $search_text = $search; + $search = str_replace( '*', '%', '*' . yourls_escape( $search ) . '*' ); + $where .= " AND `$search_in` LIKE ('$search')"; +} + +// Time span +if( !empty( $_GET['date_filter'] ) ) { + switch( $_GET['date_filter'] ) { + case 'before': + $date_filter = 'before'; + if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) { + $date_first = yourls_sanitize_date( $_GET['date_first'] ); + $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); + $where .= " AND `timestamp` < '$date_first_sql'"; + } + break; + case 'after': + $date_filter = 'after'; + if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) { + $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); + $date_first = yourls_sanitize_date( $_GET['date_first'] ); + $where .= " AND `timestamp` > '$date_first_sql'"; + } + break; + case 'between': + $date_filter = 'between'; + if( isset( $_GET['date_first'] ) && isset( $_GET['date_second'] ) && yourls_sanitize_date( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_second'] ) ) { + $date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] ); + $date_second_sql = yourls_sanitize_date_for_sql( $_GET['date_second'] ); + $date_first = yourls_sanitize_date( $_GET['date_first'] ); + $date_second = yourls_sanitize_date( $_GET['date_second'] ); + $where .= " AND `timestamp` BETWEEN '$date_first_sql' AND '$date_second_sql'"; + } + break; + } +} + +// Sorting +if( !empty( $_GET['sort_by'] ) || !empty( $_GET['sort_order'] ) ) { + switch( $_GET['sort_by'] ) { + case 'keyword': + $sort_by_text = yourls__( 'Short URL' ); + $sort_by = 'keyword'; + break; + case 'url': + $sort_by_text = yourls__( 'URL' ); + $sort_by = 'url'; + break; + case 'timestamp': + $sort_by_text = yourls__( 'Date' ); + $sort_by = 'timestamp'; + break; + case 'ip': + $sort_by_text = yourls__( 'IP Address' ); + $sort_by = 'ip'; + break; + case 'clicks': + $sort_by_text = yourls__( 'Clicks' ); + $sort_by = 'clicks'; + break; + } + switch( $_GET['sort_order'] ) { + case 'asc': + $sort_order = 'asc'; + break; + case 'desc': + $sort_order = 'desc'; + break; + } +} + +// Get URLs Count for current filter, total links in DB & total clicks +list( $total_urls, $total_clicks ) = array_values( yourls_get_db_stats() ); +if ( $where ) { + list( $total_items, $total_items_clicks ) = array_values( yourls_get_db_stats( $where ) ); +} else { + $total_items = $total_urls; + $total_items_clicks = false; +} + +// This is a bookmarklet +if ( isset( $_GET['u'] ) ) { + $is_bookmark = true; + yourls_do_action( 'bookmarklet' ); + + // No sanitization needed here: everything happens in yourls_add_new_link() + $url = ( $_GET['u'] ); + $keyword = ( isset( $_GET['k'] ) ? ( $_GET['k'] ) : '' ); + $title = ( isset( $_GET['t'] ) ? ( $_GET['t'] ) : '' ); + $return = yourls_add_new_link( $url, $keyword, $title ); + + // If fails because keyword already exist, retry with no keyword + if ( isset( $return['status'] ) && $return['status'] == 'fail' && isset( $return['code'] ) && $return['code'] == 'error:keyword' ) { + $msg = $return['message']; + $return = yourls_add_new_link( $url, '', $ydb ); + $return['message'] .= ' ('.$msg.')'; + } + + // Stop here if bookmarklet with a JSON callback function + if( isset( $_GET['jsonp'] ) && $_GET['jsonp'] == 'yourls' ) { + $short = $return['shorturl'] ? $return['shorturl'] : ''; + $message = $return['message']; + header( 'Content-type: application/json' ); + echo yourls_apply_filter( 'bookmarklet_jsonp', "yourls_callback({'short_url':'$short','message':'$message'});" ); + + die(); + } + + // Now use the URL that has been sanitized and returned by yourls_add_new_link() + $url = $return['url']['url']; + $where = sprintf( " AND `url` LIKE '%s' ", yourls_escape( $url ) ); + + $page = $total_pages = $perpage = 1; + $offset = 0; + + $text = ( isset( $_GET['s'] ) ? stripslashes( $_GET['s'] ) : '' ); + + +// This is not a bookmarklet +} else { + $is_bookmark = false; + + // Checking $page, $offset, $perpage + if( empty($page) || $page == 0 ) { + $page = 1; + } + if( empty($offset) ) { + $offset = 0; + } + if( empty($perpage) || $perpage == 0) { + $perpage = 50; + } + + // Determine $offset + $offset = ( $page-1 ) * $perpage; + + // Determine Max Number Of Items To Display On Page + if( ( $offset + $perpage ) > $total_items ) { + $max_on_page = $total_items; + } else { + $max_on_page = ( $offset + $perpage ); + } + + // Determine Number Of Items To Display On Page + if ( ( $offset + 1 ) > $total_items ) { + $display_on_page = $total_items; + } else { + $display_on_page = ( $offset + 1 ); + } + + // Determing Total Amount Of Pages + $total_pages = ceil( $total_items / $perpage ); +} + + +// Begin output of the page +$context = ( $is_bookmark ? 'bookmark' : 'index' ); +yourls_html_head( $context ); +yourls_html_logo(); +yourls_html_menu() ; + +yourls_do_action( 'admin_page_before_content' ); + +if ( !$is_bookmark ) { ?> +

+

%1$s to %2$s of %3$s URLs' ), $display_on_page, $max_on_page, $total_items ); + if( $total_items_clicks !== false ) + echo ", " . sprintf( yourls_n( 'counting 1 click', 'counting %s clicks', $total_items_clicks ), yourls_number_format_i18n( $total_items_clicks ) ); + ?>.

+ +

%1$s links, %2$s clicks, and counting!' ), yourls_number_format_i18n( $total_urls ), yourls_number_format_i18n( $total_clicks ) ); ?>

+ + + + +$(document).ready(function(){ + feedback( "' . $return['message'] . '", "'. $return['status'] .'"); + init_clipboard(); + });'; +} + +yourls_do_action( 'admin_page_before_table' ); + +yourls_table_head(); + +if ( !$is_bookmark ) { + $params = array( + 'search' => $search, + 'search_text' => $search_text, + 'search_in' => $search_in, + 'sort_by' => $sort_by, + 'sort_order' => $sort_order, + 'page' => $page, + 'perpage' => $perpage, + 'click_filter' => $click_filter, + 'click_limit' => $click_limit, + 'total_pages' => $total_pages, + 'date_filter' => $date_filter, + 'date_first' => $date_first, + 'date_second' => $date_second, + ); + yourls_html_tfooter( $params ); +} + +yourls_table_tbody_start(); + +// Main Query +$where = yourls_apply_filter( 'admin_list_where', $where ); +$url_results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 $where ORDER BY `$sort_by` $sort_order LIMIT $offset, $perpage;" ); +$found_rows = false; +if( $url_results ) { + $found_rows = true; + foreach( $url_results as $url_result ) { + $keyword = yourls_sanitize_string( $url_result->keyword ); + $timestamp = strtotime( $url_result->timestamp ); + $url = stripslashes( $url_result->url ); + $ip = $url_result->ip; + $title = $url_result->title ? $url_result->title : ''; + $clicks = $url_result->clicks; + + echo yourls_table_add_row( $keyword, $url, $title, $ip, $clicks, $timestamp ); + } +} + +$display = $found_rows ? 'display:none' : ''; +echo '' . yourls__('No URL') . ''; + +yourls_table_tbody_end(); + +yourls_table_end(); + +yourls_do_action( 'admin_page_after_table' ); + +if ( $is_bookmark ) + yourls_share_box( $url, $return['shorturl'], $title, $text ); +?> + \ No newline at end of file diff --git a/admin/install.php b/admin/install.php index dfb83d0..e0a6580 100644 --- a/admin/install.php +++ b/admin/install.php @@ -1,75 +1,75 @@ -.htaccess successfully created/updated.' ); - } else { - $warning[] = yourls__( 'Could not write file .htaccess in YOURLS root directory. You will have to do it manually. See how.' ); - } - - // Create SQL tables - $install = yourls_create_sql_tables(); - if ( isset( $install['error'] ) ) - $error = array_merge( $error, $install['error'] ); - if ( isset( $install['success'] ) ) - $success = array_merge( $success, $install['success'] ); -} - - -// Start output -yourls_html_head( 'install', yourls__( 'Install YOURLS' ) ); -?> -
-
-

- YOURLS -

- 0 ) { - echo "
    "; - foreach( $$info as $msg ) { - echo '
  • '.$msg."
  • \n"; - } - echo '
'; - } - } - - // Display install button or link to admin area if applicable - if( !yourls_is_installed() && !isset($_REQUEST['install']) ) { - echo '

'; - } else { - if( count($error) == 0 ) - echo '

» ' . yourls__( 'YOURLS Administration Page') . '

'; - } - ?> -
-
- +.htaccess successfully created/updated.' ); + } else { + $warning[] = yourls__( 'Could not write file .htaccess in YOURLS root directory. You will have to do it manually. See how.' ); + } + + // Create SQL tables + $install = yourls_create_sql_tables(); + if ( isset( $install['error'] ) ) + $error = array_merge( $error, $install['error'] ); + if ( isset( $install['success'] ) ) + $success = array_merge( $success, $install['success'] ); +} + + +// Start output +yourls_html_head( 'install', yourls__( 'Install YOURLS' ) ); +?> +
+
+

+ YOURLS +

+ 0 ) { + echo "
    "; + foreach( $$info as $msg ) { + echo '
  • '.$msg."
  • \n"; + } + echo '
'; + } + } + + // Display install button or link to admin area if applicable + if( !yourls_is_installed() && !isset($_REQUEST['install']) ) { + echo '

'; + } else { + if( count($error) == 0 ) + echo '

» ' . yourls__( 'YOURLS Administration Page') . '

'; + } + ?> +
+
+ diff --git a/admin/plugins.php b/admin/plugins.php index 8640d32..7183308 100644 --- a/admin/plugins.php +++ b/admin/plugins.php @@ -1,164 +1,164 @@ - - -

- - - -

%1$s installed, and %2$s activated', $plugins_count, $count_active ); ?>

- - - - - - - - - - - - - $plugin ) { - - // default fields to read from the plugin header - $fields = array( - 'name' => 'Plugin Name', - 'uri' => 'Plugin URI', - 'desc' => 'Description', - 'version' => 'Version', - 'author' => 'Author', - 'author_uri' => 'Author URI' - ); - - // Loop through all default fields, get value if any and reset it - foreach( $fields as $field=>$value ) { - if( isset( $plugin[ $value ] ) ) { - $data[ $field ] = $plugin[ $value ]; - } else { - $data[ $field ] = '(no info)'; - } - unset( $plugin[$value] ); - } - - $plugindir = trim( dirname( $file ), '/' ); - - if( yourls_is_active_plugin( $file ) ) { - $class = 'active'; - $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'deactivate', 'plugin' => $plugindir ) ) ); - $action_anchor = yourls__( 'Deactivate' ); - } else { - $class = 'inactive'; - $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'activate', 'plugin' => $plugindir ) ) ); - $action_anchor = yourls__( 'Activate' ); - } - - // Other "Fields: Value" in the header? Get them too - if( $plugin ) { - foreach( $plugin as $extra_field=>$extra_value ) { - $data['desc'] .= "
\n$extra_field: $extra_value"; - unset( $plugin[$extra_value] ); - } - } - - $data['desc'] .= '
' . yourls_s( 'plugin file location: %s', $file) . ''; - - printf( "", - $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor - ); - - } - ?> - -
%s%s%s%s%s
- - - -

plugin.php.' ); ?>

- -

- -

Plugin list.' ); ?>

- - - + + +

+ + + +

%1$s installed, and %2$s activated', $plugins_count, $count_active ); ?>

+ + + + + + + + + + + + + $plugin ) { + + // default fields to read from the plugin header + $fields = array( + 'name' => 'Plugin Name', + 'uri' => 'Plugin URI', + 'desc' => 'Description', + 'version' => 'Version', + 'author' => 'Author', + 'author_uri' => 'Author URI' + ); + + // Loop through all default fields, get value if any and reset it + foreach( $fields as $field=>$value ) { + if( isset( $plugin[ $value ] ) ) { + $data[ $field ] = $plugin[ $value ]; + } else { + $data[ $field ] = '(no info)'; + } + unset( $plugin[$value] ); + } + + $plugindir = trim( dirname( $file ), '/' ); + + if( yourls_is_active_plugin( $file ) ) { + $class = 'active'; + $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'deactivate', 'plugin' => $plugindir ) ) ); + $action_anchor = yourls__( 'Deactivate' ); + } else { + $class = 'inactive'; + $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'activate', 'plugin' => $plugindir ) ) ); + $action_anchor = yourls__( 'Activate' ); + } + + // Other "Fields: Value" in the header? Get them too + if( $plugin ) { + foreach( $plugin as $extra_field=>$extra_value ) { + $data['desc'] .= "
\n$extra_field: $extra_value"; + unset( $plugin[$extra_value] ); + } + } + + $data['desc'] .= '
' . yourls_s( 'plugin file location: %s', $file) . ''; + + printf( "", + $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor + ); + + } + ?> + +
%s%s%s%s%s
+ + + +

plugin.php.' ); ?>

+ +

+ +

Plugin list.' ); ?>

+ + + diff --git a/admin/tools.php b/admin/tools.php index cf89fea..aa82108 100644 --- a/admin/tools.php +++ b/admin/tools.php @@ -1,117 +1,117 @@ - - -
- -

- -

four handy bookmarklets for easier link shortening.' ); ?>

- -

- - - -

Quick Share tool box to make posting to Twitter, Facebook or Friendfeed a snap." ); - echo "\n"; - yourls_e( "If you want to share a description along with the link you're shortening, simply select text on the page you're viewing before clicking on your bookmarklet link" ); - ?>

- -

- -

- - - - - - - - - - - - - - - - - - - - - -
 
- -

- -

%s\" to the beginning of the current URL (right before its 'http://' part) and hit enter.", preg_replace('@https?://@', '', YOURLS_SITE) . '/' ); ?>

- -

.

- - - - -

- -

username and password parameters.' ); - echo "\n"; - yourls_e( "If you're worried about sending your credentials into the wild, you can also make API calls without using your login or your password, using a secret signature token." ); - ?>

- -

%s', yourls_auth_signature() ); - yourls_e( "(It's a secret. Keep it secret) "); - ?>

- -

- - - -

API documentation for more', YOURLS_SITE . '/readme.html#API' ); ?>

- -
- - - - + + +
+ +

+ +

four handy bookmarklets for easier link shortening.' ); ?>

+ +

+ + + +

Quick Share tool box to make posting to Twitter, Facebook or Friendfeed a snap." ); + echo "\n"; + yourls_e( "If you want to share a description along with the link you're shortening, simply select text on the page you're viewing before clicking on your bookmarklet link" ); + ?>

+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + +
 
+ +

+ +

%s\" to the beginning of the current URL (right before its 'http://' part) and hit enter.", preg_replace('@https?://@', '', YOURLS_SITE) . '/' ); ?>

+ +

.

+ + + + +

+ +

username and password parameters.' ); + echo "\n"; + yourls_e( "If you're worried about sending your credentials into the wild, you can also make API calls without using your login or your password, using a secret signature token." ); + ?>

+ +

%s', yourls_auth_signature() ); + yourls_e( "(It's a secret. Keep it secret) "); + ?>

+ +

+ + + +

API documentation for more', YOURLS_SITE . '/readme.html#API' ); ?>

+ +
+ + + + diff --git a/admin/upgrade.php b/admin/upgrade.php index b03acff..fdca3f5 100644 --- a/admin/upgrade.php +++ b/admin/upgrade.php @@ -1,86 +1,86 @@ - -

-' . yourls_s( 'Upgrade not required. Go back to play!', yourls_admin_url('index.php') ) . '

'; - - -} else { - /* - step 1: create new tables and populate them, update old tables structure, - step 2: convert each row of outdated tables if needed - step 3: - if applicable finish updating outdated tables (indexes etc) - - update version & db_version in options, this is all done! - */ - - // From what are we upgrading? - if ( isset( $_GET['oldver'] ) && isset( $_GET['oldsql'] ) ) { - $oldver = yourls_sanitize_version( $_GET['oldver'] ); - $oldsql = yourls_sanitize_version( $_GET['oldsql'] ); - } else { - list( $oldver, $oldsql ) = yourls_get_current_version_from_sql(); - } - - // To what are we upgrading ? - $newver = YOURLS_VERSION; - $newsql = YOURLS_DB_VERSION; - - // Verbose & ugly details - $ydb->show_errors = true; - - // Let's go - $step = ( isset( $_GET['step'] ) ? intval( $_GET['step'] ) : 0 ); - switch( $step ) { - - default: - case 0: - ?> -

-

backup your database
(you should do this regularly anyway)' ); ?>

-

should happen, but this doesn't mean it won't happen, right? ;)" ); ?>

-

something goes wrong, you'll see a message and hopefully a way to fix." ); ?>

-

good for you, let it go :)' ); ?>

-

- - - - - - - - "; - - break; - - case 1: - case 2: - $upgrade = yourls_upgrade( $step, $oldver, $newver, $oldsql, $newsql ); - break; - - case 3: - $upgrade = yourls_upgrade( 3, $oldver, $newver, $oldsql, $newsql ); - echo '

' . yourls__( 'Your installation is now up to date ! ' ) . '

'; - echo '

' . yourls_s( 'Go back to the admin interface', yourls_admin_url('index.php') ) . '

'; - } - -} - - -?> - - + +

+' . yourls_s( 'Upgrade not required. Go back to play!', yourls_admin_url('index.php') ) . '

'; + + +} else { + /* + step 1: create new tables and populate them, update old tables structure, + step 2: convert each row of outdated tables if needed + step 3: - if applicable finish updating outdated tables (indexes etc) + - update version & db_version in options, this is all done! + */ + + // From what are we upgrading? + if ( isset( $_GET['oldver'] ) && isset( $_GET['oldsql'] ) ) { + $oldver = yourls_sanitize_version( $_GET['oldver'] ); + $oldsql = yourls_sanitize_version( $_GET['oldsql'] ); + } else { + list( $oldver, $oldsql ) = yourls_get_current_version_from_sql(); + } + + // To what are we upgrading ? + $newver = YOURLS_VERSION; + $newsql = YOURLS_DB_VERSION; + + // Verbose & ugly details + $ydb->show_errors = true; + + // Let's go + $step = ( isset( $_GET['step'] ) ? intval( $_GET['step'] ) : 0 ); + switch( $step ) { + + default: + case 0: + ?> +

+

backup your database
(you should do this regularly anyway)' ); ?>

+

should happen, but this doesn't mean it won't happen, right? ;)" ); ?>

+

something goes wrong, you'll see a message and hopefully a way to fix." ); ?>

+

good for you, let it go :)' ); ?>

+

+ + + + + + + + "; + + break; + + case 1: + case 2: + $upgrade = yourls_upgrade( $step, $oldver, $newver, $oldsql, $newsql ); + break; + + case 3: + $upgrade = yourls_upgrade( 3, $oldver, $newver, $oldsql, $newsql ); + echo '

' . yourls__( 'Your installation is now up to date ! ' ) . '

'; + echo '

' . yourls_s( 'Go back to the admin interface', yourls_admin_url('index.php') ) . '

'; + } + +} + + +?> + + diff --git a/css/cal.css b/css/cal.css index 0c48b79..c77dfc6 100644 --- a/css/cal.css +++ b/css/cal.css @@ -1,14 +1,14 @@ -/* Calendar */ -.datepicker { border-collapse: collapse; border: 2px solid #999; position: absolute; width: 215px } -.datepicker tr.controls th { height: 22px; font-size: 11px; } -.datepicker select { font-size: 11px; } -.datepicker tr.days th { height: 18px; } -.datepicker tfoot td { height: 18px; text-align: center; text-transform: capitalize; } -.datepicker th, .datepicker tfoot td { background: #eee; font: 10px/18px Verdana, Arial, Helvetica, sans-serif; } -.datepicker th span, .datepicker tfoot td span { font-weight: bold; } -.datepicker tbody td { width: 24px; height: 24px; border: 1px solid #ccc; font: 11px/22px Arial, Helvetica, sans-serif; text-align: center; background: #fff; } -.datepicker tbody td.date { cursor: pointer; } -.datepicker tbody td.date.over { background-color: #99ffff; } -.datepicker tbody td.date.chosen { font-weight: bold; background-color: #ccffcc; } -/* Form defaults */ +/* Calendar */ +.datepicker { border-collapse: collapse; border: 2px solid #999; position: absolute; width: 215px } +.datepicker tr.controls th { height: 22px; font-size: 11px; } +.datepicker select { font-size: 11px; } +.datepicker tr.days th { height: 18px; } +.datepicker tfoot td { height: 18px; text-align: center; text-transform: capitalize; } +.datepicker th, .datepicker tfoot td { background: #eee; font: 10px/18px Verdana, Arial, Helvetica, sans-serif; } +.datepicker th span, .datepicker tfoot td span { font-weight: bold; } +.datepicker tbody td { width: 24px; height: 24px; border: 1px solid #ccc; font: 11px/22px Arial, Helvetica, sans-serif; text-align: center; background: #fff; } +.datepicker tbody td.date { cursor: pointer; } +.datepicker tbody td.date.over { background-color: #99ffff; } +.datepicker tbody td.date.chosen { font-weight: bold; background-color: #ccffcc; } +/* Form defaults */ #date_and, #date_second {display:none} \ No newline at end of file diff --git a/css/infos.css b/css/infos.css index 1ad1cf5..624ba3f 100644 --- a/css/infos.css +++ b/css/infos.css @@ -1,113 +1,113 @@ -h3 span.label { - width:100px; - display:inline-block; -} - -ul.toggle_display { - display:none; - list-style-type:none; - margin-left:0; - margin-right:23px; - padding:12px 5px 3px; - border-bottom:1px solid #C7E7FF; -} -ul.toggle_display li { - padding:0; -} -#tabs ul#headers li, #tabs ul#headers li h2, #stats_lines li{ - display: inline; - margin-right: 10px; -} -#tabs ul#headers { - border-bottom:1px solid #E3F3FF; - padding:12px 5px 3px 5px; - float:left; -} -.wrap_unfloat { - overflow:hidden; -} - -#tabs ul#headers li a { - color:#595441; - border:1px solid #C7E7FF; - -moz-border-radius:10px 10px 0 0; - -webkit-border-radius:10px 10px 0 0; - border-radius:10px 10px 0 0; - padding:10px 5px 5px 15px; - background:#E3F3FF; -} - -#tabs ul#headers li a:hover { - text-decoration:none; - background:#88C0EB; -} - -#tabs ul#headers li a.selected { - border-bottom:2px solid #fff; - background:#fff; -} - -#tabs ul#headers li a.selected:hover { - background:#fff; -} - -#stats_lines li a { - -moz-border-radius:10px 10px 0 0; - -webkit-border-radius:10px 10px 0 0; - border-radius:10px 10px 0 0; - padding:3px 10px; - background:#E3F3FF; - border:1px solid #C7E7FF; -} -#stats_lines li a:hover { - text-decoration:none; - background:#C7E7FF; -} -#stats_lines li a.selected { - background:#fff; - border:1px solid #C7E7FF; - border-bottom:1px solid white; -} -#stats_lines li a.selected:hover { - background:#fff; -} -.tab { - padding:10px; -} -li#sites_various { padding-left:22px; padding-top:4px;} - -li.sites_list img, #longurl img {width:16px; height: 16px; display:inline-block;} - -#referrer_cell { min-width: 300px;} - -#details_clicks li.bestday, #details_clicks li span.best_month, #details_clicks li span.best_year { - font-weight:bold; -} - -ul.no_bullet { - list-style-type: none; - margin-left:0; - padding:0; -} -ul.no_bullet li { - margin-bottom:5px; -} -#historical_clicks { - float:left; - margin:0; -} -#historical_clicks li { - padding:2px 10px; - margin:0; -} -#historical_clicks li:hover { - background:#C7E7FF !important; -} -#historical_clicks span.historical_link { - min-width:130px; - display:inline-block; -} -#historical_clicks span.historical_count { - min-width:100px; - display:inline-block; -} +h3 span.label { + width:100px; + display:inline-block; +} + +ul.toggle_display { + display:none; + list-style-type:none; + margin-left:0; + margin-right:23px; + padding:12px 5px 3px; + border-bottom:1px solid #C7E7FF; +} +ul.toggle_display li { + padding:0; +} +#tabs ul#headers li, #tabs ul#headers li h2, #stats_lines li{ + display: inline; + margin-right: 10px; +} +#tabs ul#headers { + border-bottom:1px solid #E3F3FF; + padding:12px 5px 3px 5px; + float:left; +} +.wrap_unfloat { + overflow:hidden; +} + +#tabs ul#headers li a { + color:#595441; + border:1px solid #C7E7FF; + -moz-border-radius:10px 10px 0 0; + -webkit-border-radius:10px 10px 0 0; + border-radius:10px 10px 0 0; + padding:10px 5px 5px 15px; + background:#E3F3FF; +} + +#tabs ul#headers li a:hover { + text-decoration:none; + background:#88C0EB; +} + +#tabs ul#headers li a.selected { + border-bottom:2px solid #fff; + background:#fff; +} + +#tabs ul#headers li a.selected:hover { + background:#fff; +} + +#stats_lines li a { + -moz-border-radius:10px 10px 0 0; + -webkit-border-radius:10px 10px 0 0; + border-radius:10px 10px 0 0; + padding:3px 10px; + background:#E3F3FF; + border:1px solid #C7E7FF; +} +#stats_lines li a:hover { + text-decoration:none; + background:#C7E7FF; +} +#stats_lines li a.selected { + background:#fff; + border:1px solid #C7E7FF; + border-bottom:1px solid white; +} +#stats_lines li a.selected:hover { + background:#fff; +} +.tab { + padding:10px; +} +li#sites_various { padding-left:22px; padding-top:4px;} + +li.sites_list img, #longurl img {width:16px; height: 16px; display:inline-block;} + +#referrer_cell { min-width: 300px;} + +#details_clicks li.bestday, #details_clicks li span.best_month, #details_clicks li span.best_year { + font-weight:bold; +} + +ul.no_bullet { + list-style-type: none; + margin-left:0; + padding:0; +} +ul.no_bullet li { + margin-bottom:5px; +} +#historical_clicks { + float:left; + margin:0; +} +#historical_clicks li { + padding:2px 10px; + margin:0; +} +#historical_clicks li:hover { + background:#C7E7FF !important; +} +#historical_clicks span.historical_link { + min-width:130px; + display:inline-block; +} +#historical_clicks span.historical_count { + min-width:100px; + display:inline-block; +} diff --git a/css/share.css b/css/share.css index 45d96eb..d162e9f 100644 --- a/css/share.css +++ b/css/share.css @@ -1,65 +1,65 @@ -#shareboxes, #tweet { - overflow:hidden; -} - -#shareboxes{ - margin-top:15px; -} - -div.share { - -moz-border-radius:5px; - -webkit-border-radius:5px; - border-radius:5px; - border:2px solid #88c0eb; - background:#fff; - margin-right:1em; - padding:0 1em; - float:left; - height:140px; -} -#origlink{ - display:inline-block; - white-space:pre; - width:183px; - overflow:hidden; - vertical-align:-2px; -} -#copybox { - width:250px; -} -#sharebox { - width:500px; -} -#tweet_body { - float:left; - width:450px; - height:4em; - font-size:12px; -} -#charcount { - padding-left:5px; - color:#88c0eb; -} -#charcount.negative { - color:red; -} -#share_links a { - padding:0 12px 0 18px; - font-weight:bold -} -#share_links a:hover { - background-position:2px center; -} -#share_tw {background:transparent url(../images/twitter.png) left center no-repeat;} -#share_fb {background:transparent url(../images/facebook.png) left center no-repeat;} -#share_ff {background:transparent url(../images/friendfeed.png) left center no-repeat;} - -#copylink{ - cursor:pointer; - background:transparent url(../images/copy.png) 130% center no-repeat; -} - -#copylink:hover, #copylink.hover { - background-position:100% 50%; -} - +#shareboxes, #tweet { + overflow:hidden; +} + +#shareboxes{ + margin-top:15px; +} + +div.share { + -moz-border-radius:5px; + -webkit-border-radius:5px; + border-radius:5px; + border:2px solid #88c0eb; + background:#fff; + margin-right:1em; + padding:0 1em; + float:left; + height:140px; +} +#origlink{ + display:inline-block; + white-space:pre; + width:183px; + overflow:hidden; + vertical-align:-2px; +} +#copybox { + width:250px; +} +#sharebox { + width:500px; +} +#tweet_body { + float:left; + width:450px; + height:4em; + font-size:12px; +} +#charcount { + padding-left:5px; + color:#88c0eb; +} +#charcount.negative { + color:red; +} +#share_links a { + padding:0 12px 0 18px; + font-weight:bold +} +#share_links a:hover { + background-position:2px center; +} +#share_tw {background:transparent url(../images/twitter.png) left center no-repeat;} +#share_fb {background:transparent url(../images/facebook.png) left center no-repeat;} +#share_ff {background:transparent url(../images/friendfeed.png) left center no-repeat;} + +#copylink{ + cursor:pointer; + background:transparent url(../images/copy.png) 130% center no-repeat; +} + +#copylink:hover, #copylink.hover { + background-position:100% 50%; +} + diff --git a/css/style.css b/css/style.css index 2c9f5cd..eeaae30 100644 --- a/css/style.css +++ b/css/style.css @@ -1,330 +1,330 @@ -body { - font-family: Verdana, Arial; - font-size: 12px; - color: #595441; - background:#e3f3ff; - text-align:center; - margin-top:0px; - padding-top:10px; -} -#wrap { - max-width:950px; - min-height:150px; - margin:0 auto; - background:white; - text-align:left; - padding:5px 20px 10px 20px; - border-left:3px solid #2a85b3; - border-right:3px solid #2a85b3; - border-bottom:3px solid #2a85b3; - border-top:3px solid #2a85b3; - -moz-border-radius:20px; - -webkit-border-radius:20px; - border-radius:20px; -} -.hide-if-no-js {display: none;} -div, p, td { - font-family: Verdana, Arial; - font-size: 12px; -} -a, a:link, a:active, a:visited { - color: #2a85b3; - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -h1 {height:50px;margin:0;float:right;max-width:500px;} -h1 a {text-align:right;font-size:20px;float:right;} -h1 a, h1 a:link, h1 a:active, h1 a:visited {color:#2a85b3} -h1 a:hover{text-decoration:none;} -h1 a:hover span{text-decoration:underline;color:#88c0eb} - -ul#admin_menu { - min-height:100px; - list-style-type:none; - padding:0; - font-size:105%; -} -ul#admin_menu li { - color:#aaa; - padding:1px 0; -} -ul#admin_menu li:hover { - list-style-type:square; - color:#000; -} - -code { - background:#eaeaef; - padding:0 2px; -} -tt { - background:#ffc; - padding:0 2px; -} - -input, textarea { - -moz-border-radius:3px; - -webkit-border-radius:3px; - border-radius:3px; -} -Input.text, select, textarea { - font-family: Verdana, Arial; - font-size: 10px; - color: #595441; - background-color: #FFFFFF; - border: 1px solid #88c0eb; - margin:1px; -} -input.button { - font-family: Verdana, Arial; - font-size: 10px; - color: #595441; - font-weight: bold; - background-color: #FFFFFF; - border: 1px solid #88c0eb; - cursor:pointer; -} -input.primary { - border:2px solid #2A85B3; - background:#fafafe; -} -input.text:focus, textarea:focus { - border:2px solid #2A85B3; - margin:0px; -} -tr.edit-row td { - background:#e3f3ff !important; -} -#new_url { - text-align:center; - padding:1px; - border:1px solid #CDCDCD; - background:#fff; - clear:both; -} -#new_url div { - background:#C7E7FF; - padding:4px; -} -#new_url_form { - padding:4px; -} -#new_url #feedback { - background:#ff8; - color:#88c0eb; - width:50%; - margin:0px 25%; - padding:2px; - border:1px solid #ff8; -} -#new_url #feedback .fail { - color:#f55; -} -#add-url {width:400px} -td.url small a{ - color:#bbc; -} -body.desktop td.actions input,body.desktop td.actions a { - visibility:hidden; -} -td.actions input.disabled, td.actions input.loading { - visibility:visible; -} -tr:hover td.actions input, tr:hover td.actions a { - visibility:visible; -} -td.actions .button { - font-family: Verdana, Arial; - font-size: 10px; - color: #595441; - font-weight: bold; - background-color: #FFFFFF; - border: 1px solid #88c0eb; - -moz-border-radius:3px; - -webkit-border-radius:3px; - border-radius:3px; - cursor:pointer; - height:22px; - width:22px; - margin-top:0px; - margin-right:5px; - display:block; - float:left; - text-indent:-9999px; - outline:0px; -} -td.actions .button:active { - border:1px solid #000; -} -td.actions .button:hover { - text-decoration:none; -} -td.actions .button.disabled, #add-button.disabled { - border:1px solid #333; - background:#ccc; -} -td.actions .button.loading, #add-button.loading { - background:#cc7 url(../images/loading.gif) center center no-repeat; - color:#cc7; -} -td.actions .button_share { - background:transparent url(../images/share.png) 2px center no-repeat; -} -td.actions .button_edit { - background:transparent url(../images/pencil.png) 2px center no-repeat; -} -td.actions .button_delete { - background:transparent url(../images/delete.png) 2px center no-repeat; -} -td.actions .button_stats { - background:transparent url(../images/chart_bar.png) 2px center no-repeat; -} -#main_table tfoot th, #main_table tfoot th div { - font-size:10px; -} -.error { - color: red; - background:#fee; -} -.warning { - color: orange; - background:#ffe9bf; -} -.success { - color: green; - background:#efe; -} -#login { - width: 300px; - margin: 200px auto 0px auto; -} -#login p{ - font-weight: bold; -} -#login .text { - width: 100%; -} -#login ul { - padding-left:0px; - list-style-type:none; - text-indent:0; -} -#login ul li { - padding:0 0 5px 20px; -} -#login ul.error li { - background:transparent url(../images/cancel.png) top left no-repeat; -} -#login ul.warning li { - background:transparent url(../images/error.png) top left no-repeat; -} -#login ul.success li { - background:transparent url(../images/accept.png) top left no-repeat; -} -.sub_wrap { - max-width:580px; - padding-bottom:30px; - text-align:justify; -} -.sub_wrap span { - background:#ffa; - padding:0 2px; -} -a.bookmarklet { - border:2px solid #2a85b3; - -moz-border-radius:3px; - -webkit-border-radius:3px; - border-radius:3px; - padding:5px 5px 5px 20px; - background:#eef url(../images/favicon.gif) 2px center no-repeat; - margin:3px; - display:inline-block; -} -a.bookmarklet:hover { - text-decoration:none; - background-position:3px center; -} -#footer { - text-align:center; - margin-top:20px; -} -#footer p { - padding:10px; - background:white; - margin:0 auto; - max-width:950px; - -moz-border-radius:10px; - -webkit-border-radius:10px; - border-radius:10px; - border:2px solid #2a85b3; - -moz-border-radius-bottomleft:30px; - -moz-border-radius-bottomright:30px; - -webkit-border-bottom-left-radius:25px; - -webkit-border-bottom-right-radius:25px; - border-bottom-left-radius:25px; - border-bottom-right-radius:25px; -} -#footer p a { - background:#fff url(../images/favicon.gif) 2px center no-repeat; - padding-left:20px; -} - -.notice { - border:1px solid #2a85b3; - background: #F3FAFD; - -moz-border-radius:6px; - -webkit-border-radius:6px; - border-radius:6px; - width:70%; - margin-left:15%; - padding-left:10px; - margin-bottom:5px; -} - - -.jquery-notify-bar { - width:100%; - position:fixed; - top:0; - left:0; - z-index:32768; - background-color:#efefef; - font-size:18px; - color:#000; - text-align:center; - font-family: Arial, Verdana, sans-serif; - padding:20px 0px; - border-bottom:1px solid #bbb; - filter:alpha(opacity=95); - -moz-opacity:0.95; - -khtml-opacity:0.95; - opacity:0.95; - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.5); - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); - text-shadow: 0 1px 1px rgba(0,0,0,0.1); -} -.jquery-notify-bar.error ,.jquery-notify-bar.fail { - color:#f00; - background-color:#fdd; -} -.jquery-notify-bar.error span,.jquery-notify-bar.fail span{ - background:transparent url("../images/error.png") no-repeat left center; - padding-left:20px; -}.jquery-notify-bar.success span{ - background:transparent url("../images/accept.png") no-repeat left center; - padding-left:20px; -} -.jquery-notify-bar.success { - color:#060; - background-color:#aea; -} -.notify-bar-close { - position:absolute; - left:95%; - font-size:11px; -} -tr.plugin.active a{ font-weight:bolder;} -body.desktop tr.plugin td.plugin_desc small{ visibility:hidden;} -tr:hover.plugin td.plugin_desc small{ visibility:visible;} +body { + font-family: Verdana, Arial; + font-size: 12px; + color: #595441; + background:#e3f3ff; + text-align:center; + margin-top:0px; + padding-top:10px; +} +#wrap { + max-width:950px; + min-height:150px; + margin:0 auto; + background:white; + text-align:left; + padding:5px 20px 10px 20px; + border-left:3px solid #2a85b3; + border-right:3px solid #2a85b3; + border-bottom:3px solid #2a85b3; + border-top:3px solid #2a85b3; + -moz-border-radius:20px; + -webkit-border-radius:20px; + border-radius:20px; +} +.hide-if-no-js {display: none;} +div, p, td { + font-family: Verdana, Arial; + font-size: 12px; +} +a, a:link, a:active, a:visited { + color: #2a85b3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +h1 {height:50px;margin:0;float:right;max-width:500px;} +h1 a {text-align:right;font-size:20px;float:right;} +h1 a, h1 a:link, h1 a:active, h1 a:visited {color:#2a85b3} +h1 a:hover{text-decoration:none;} +h1 a:hover span{text-decoration:underline;color:#88c0eb} + +ul#admin_menu { + min-height:100px; + list-style-type:none; + padding:0; + font-size:105%; +} +ul#admin_menu li { + color:#aaa; + padding:1px 0; +} +ul#admin_menu li:hover { + list-style-type:square; + color:#000; +} + +code { + background:#eaeaef; + padding:0 2px; +} +tt { + background:#ffc; + padding:0 2px; +} + +input, textarea { + -moz-border-radius:3px; + -webkit-border-radius:3px; + border-radius:3px; +} +Input.text, select, textarea { + font-family: Verdana, Arial; + font-size: 10px; + color: #595441; + background-color: #FFFFFF; + border: 1px solid #88c0eb; + margin:1px; +} +input.button { + font-family: Verdana, Arial; + font-size: 10px; + color: #595441; + font-weight: bold; + background-color: #FFFFFF; + border: 1px solid #88c0eb; + cursor:pointer; +} +input.primary { + border:2px solid #2A85B3; + background:#fafafe; +} +input.text:focus, textarea:focus { + border:2px solid #2A85B3; + margin:0px; +} +tr.edit-row td { + background:#e3f3ff !important; +} +#new_url { + text-align:center; + padding:1px; + border:1px solid #CDCDCD; + background:#fff; + clear:both; +} +#new_url div { + background:#C7E7FF; + padding:4px; +} +#new_url_form { + padding:4px; +} +#new_url #feedback { + background:#ff8; + color:#88c0eb; + width:50%; + margin:0px 25%; + padding:2px; + border:1px solid #ff8; +} +#new_url #feedback .fail { + color:#f55; +} +#add-url {width:400px} +td.url small a{ + color:#bbc; +} +body.desktop td.actions input,body.desktop td.actions a { + visibility:hidden; +} +td.actions input.disabled, td.actions input.loading { + visibility:visible; +} +tr:hover td.actions input, tr:hover td.actions a { + visibility:visible; +} +td.actions .button { + font-family: Verdana, Arial; + font-size: 10px; + color: #595441; + font-weight: bold; + background-color: #FFFFFF; + border: 1px solid #88c0eb; + -moz-border-radius:3px; + -webkit-border-radius:3px; + border-radius:3px; + cursor:pointer; + height:22px; + width:22px; + margin-top:0px; + margin-right:5px; + display:block; + float:left; + text-indent:-9999px; + outline:0px; +} +td.actions .button:active { + border:1px solid #000; +} +td.actions .button:hover { + text-decoration:none; +} +td.actions .button.disabled, #add-button.disabled { + border:1px solid #333; + background:#ccc; +} +td.actions .button.loading, #add-button.loading { + background:#cc7 url(../images/loading.gif) center center no-repeat; + color:#cc7; +} +td.actions .button_share { + background:transparent url(../images/share.png) 2px center no-repeat; +} +td.actions .button_edit { + background:transparent url(../images/pencil.png) 2px center no-repeat; +} +td.actions .button_delete { + background:transparent url(../images/delete.png) 2px center no-repeat; +} +td.actions .button_stats { + background:transparent url(../images/chart_bar.png) 2px center no-repeat; +} +#main_table tfoot th, #main_table tfoot th div { + font-size:10px; +} +.error { + color: red; + background:#fee; +} +.warning { + color: orange; + background:#ffe9bf; +} +.success { + color: green; + background:#efe; +} +#login { + width: 300px; + margin: 200px auto 0px auto; +} +#login p{ + font-weight: bold; +} +#login .text { + width: 100%; +} +#login ul { + padding-left:0px; + list-style-type:none; + text-indent:0; +} +#login ul li { + padding:0 0 5px 20px; +} +#login ul.error li { + background:transparent url(../images/cancel.png) top left no-repeat; +} +#login ul.warning li { + background:transparent url(../images/error.png) top left no-repeat; +} +#login ul.success li { + background:transparent url(../images/accept.png) top left no-repeat; +} +.sub_wrap { + max-width:580px; + padding-bottom:30px; + text-align:justify; +} +.sub_wrap span { + background:#ffa; + padding:0 2px; +} +a.bookmarklet { + border:2px solid #2a85b3; + -moz-border-radius:3px; + -webkit-border-radius:3px; + border-radius:3px; + padding:5px 5px 5px 20px; + background:#eef url(../images/favicon.gif) 2px center no-repeat; + margin:3px; + display:inline-block; +} +a.bookmarklet:hover { + text-decoration:none; + background-position:3px center; +} +#footer { + text-align:center; + margin-top:20px; +} +#footer p { + padding:10px; + background:white; + margin:0 auto; + max-width:950px; + -moz-border-radius:10px; + -webkit-border-radius:10px; + border-radius:10px; + border:2px solid #2a85b3; + -moz-border-radius-bottomleft:30px; + -moz-border-radius-bottomright:30px; + -webkit-border-bottom-left-radius:25px; + -webkit-border-bottom-right-radius:25px; + border-bottom-left-radius:25px; + border-bottom-right-radius:25px; +} +#footer p a { + background:#fff url(../images/favicon.gif) 2px center no-repeat; + padding-left:20px; +} + +.notice { + border:1px solid #2a85b3; + background: #F3FAFD; + -moz-border-radius:6px; + -webkit-border-radius:6px; + border-radius:6px; + width:70%; + margin-left:15%; + padding-left:10px; + margin-bottom:5px; +} + + +.jquery-notify-bar { + width:100%; + position:fixed; + top:0; + left:0; + z-index:32768; + background-color:#efefef; + font-size:18px; + color:#000; + text-align:center; + font-family: Arial, Verdana, sans-serif; + padding:20px 0px; + border-bottom:1px solid #bbb; + filter:alpha(opacity=95); + -moz-opacity:0.95; + -khtml-opacity:0.95; + opacity:0.95; + -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + text-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.jquery-notify-bar.error ,.jquery-notify-bar.fail { + color:#f00; + background-color:#fdd; +} +.jquery-notify-bar.error span,.jquery-notify-bar.fail span{ + background:transparent url("../images/error.png") no-repeat left center; + padding-left:20px; +}.jquery-notify-bar.success span{ + background:transparent url("../images/accept.png") no-repeat left center; + padding-left:20px; +} +.jquery-notify-bar.success { + color:#060; + background-color:#aea; +} +.notify-bar-close { + position:absolute; + left:95%; + font-size:11px; +} +tr.plugin.active a{ font-weight:bolder;} +body.desktop tr.plugin td.plugin_desc small{ visibility:hidden;} +tr:hover.plugin td.plugin_desc small{ visibility:visible;} diff --git a/css/tablesorter.css b/css/tablesorter.css index e56e95c..14e74a2 100644 --- a/css/tablesorter.css +++ b/css/tablesorter.css @@ -1,104 +1,104 @@ -/* jQuery Table Sorter */ -table.tblSorter { - font-family:Verdana, Arial; - background-color: #CDCDCD; - margin:10px 0px 0px; - font-size: 8pt; - width: 100%; - text-align: left; -} -table.tblSorter thead tr th, table.tblSorter tfoot tr th, table.tblSorter th.header { - background-color: #C7E7FF; - border: 1px solid #FFF; - font-size: 8pt; - padding: 4px; -} -table.tblSorter tfoot tr th { - background-color: #E3F3FF; -} -table.tblSorter thead tr .tablesorter-header { - background-image: url('../images/bg.gif'); - background-repeat: no-repeat; - background-position: center right; - cursor: pointer; - padding-right:10px; -} -table.tblSorter thead tr .sorter-false { - background-image: none; - cursor:default; -} -table.tblSorter tbody td { - color: #3D3D3D; - padding: 4px; - background-color: #FFF; - vertical-align: top; -} -table.tblSorter tbody tr.normal-row td { - background: #F1F9FF; -} -table.tblSorter tbody tr.alt-row td { - -} -table.tblSorter tbody tr.normal-row:hover td { - background-color:#F1FFF6; -} -table.tblSorter tbody tr.alt-row:hover td { - background-color:#F1FFF6; -} -table.tblSorter thead tr .tablesorter-headerDesc { - background-image: url('../images/desc.gif'); - background-repeat: no-repeat; - background-position: center right; -} -table.tblSorter thead tr .tablesorter-headerAsc { - background-image: url('../images/asc.gif'); - background-repeat: no-repeat; - background-position: center right; -} -table.tblSorter thead tr .tablesorter-headerAsc, table.tblSorter thead tr .tablesorter-headerDesc { - background-color: #91C7F2; -} -table.tblSorter tfoot tr { - background-color: #BCD9E8; -} -#filter_form{ - float:left; - text-align:left; - max-width:69%; -} -#filter_buttons{ - float:right; -} -#pagination{ - text-align:right; - float:right; - width:30%; -} -.navigation .nav_total{ - display:block; - margin-bottom:10px; -} -.navigation .nav_link a, .navigation .nav_current { - border:1px solid #CDCDCD; - margin:0px 2px; - padding:2px 1px; - background:#fff; - text-align:center; - min-width:15px; - display:inline-block; -} -.navigation .nav_current { - border:0px; - background:none; -} -.navigation .nav_first a, .navigation .nav_last a { - padding:2px 2px; -} -.navigation .nav_prev:before, .navigation .nav_next:after { - content:"..."; -} -.navigation .nav_link a:hover { - border:1px solid #BCD9E8; - background:#BCD9E8; - text-decoration:none; +/* jQuery Table Sorter */ +table.tblSorter { + font-family:Verdana, Arial; + background-color: #CDCDCD; + margin:10px 0px 0px; + font-size: 8pt; + width: 100%; + text-align: left; +} +table.tblSorter thead tr th, table.tblSorter tfoot tr th, table.tblSorter th.header { + background-color: #C7E7FF; + border: 1px solid #FFF; + font-size: 8pt; + padding: 4px; +} +table.tblSorter tfoot tr th { + background-color: #E3F3FF; +} +table.tblSorter thead tr .tablesorter-header { + background-image: url('../images/bg.gif'); + background-repeat: no-repeat; + background-position: center right; + cursor: pointer; + padding-right:10px; +} +table.tblSorter thead tr .sorter-false { + background-image: none; + cursor:default; +} +table.tblSorter tbody td { + color: #3D3D3D; + padding: 4px; + background-color: #FFF; + vertical-align: top; +} +table.tblSorter tbody tr.normal-row td { + background: #F1F9FF; +} +table.tblSorter tbody tr.alt-row td { + +} +table.tblSorter tbody tr.normal-row:hover td { + background-color:#F1FFF6; +} +table.tblSorter tbody tr.alt-row:hover td { + background-color:#F1FFF6; +} +table.tblSorter thead tr .tablesorter-headerDesc { + background-image: url('../images/desc.gif'); + background-repeat: no-repeat; + background-position: center right; +} +table.tblSorter thead tr .tablesorter-headerAsc { + background-image: url('../images/asc.gif'); + background-repeat: no-repeat; + background-position: center right; +} +table.tblSorter thead tr .tablesorter-headerAsc, table.tblSorter thead tr .tablesorter-headerDesc { + background-color: #91C7F2; +} +table.tblSorter tfoot tr { + background-color: #BCD9E8; +} +#filter_form{ + float:left; + text-align:left; + max-width:69%; +} +#filter_buttons{ + float:right; +} +#pagination{ + text-align:right; + float:right; + width:30%; +} +.navigation .nav_total{ + display:block; + margin-bottom:10px; +} +.navigation .nav_link a, .navigation .nav_current { + border:1px solid #CDCDCD; + margin:0px 2px; + padding:2px 1px; + background:#fff; + text-align:center; + min-width:15px; + display:inline-block; +} +.navigation .nav_current { + border:0px; + background:none; +} +.navigation .nav_first a, .navigation .nav_last a { + padding:2px 2px; +} +.navigation .nav_prev:before, .navigation .nav_next:after { + content:"..."; +} +.navigation .nav_link a:hover { + border:1px solid #BCD9E8; + background:#BCD9E8; + text-decoration:none; } \ No newline at end of file diff --git a/includes/class-mysql.php b/includes/class-mysql.php index 081a14b..3f04e7c 100644 --- a/includes/class-mysql.php +++ b/includes/class-mysql.php @@ -1,925 +1,925 @@ -last_error = $err_str; - - // Capture all errors to an error array no matter what happens - $this->captured_errors[] = array - ( - 'error_str' => $err_str, - 'query' => $this->last_query - ); - } - - /********************************************************************** - * Turn error handling on or off.. - */ - - function show_errors() - { - $this->show_errors = true; - } - - function hide_errors() - { - $this->show_errors = false; - } - - /********************************************************************** - * Kill cached query results - */ - - function flush() - { - // Get rid of these - $this->last_result = null; - $this->col_info = null; - $this->last_query = null; - $this->from_disk_cache = false; - } - - /********************************************************************** - * Get one variable from the DB - see docs for more detail - */ - - function get_var($query=null,$x=0,$y=0) - { - - // Log how the function was called - $this->func_call = "\$db->get_var(\"$query\",$x,$y)"; - - // If there is a query then perform it if not then use cached results.. - if ( $query ) - { - $this->query($query); - } - - // Extract var out of cached results based x,y vals - if ( $this->last_result[$y] ) - { - $values = array_values(get_object_vars($this->last_result[$y])); - } - - // If there is a value return it else return null - return (isset($values[$x]) && $values[$x]!=='')?$values[$x]:null; - } - - /********************************************************************** - * Get one row from the DB - see docs for more detail - */ - - function get_row($query=null,$output=OBJECT,$y=0) - { - - // Log how the function was called - $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; - - // If there is a query then perform it if not then use cached results.. - if ( $query ) - { - $this->query($query); - } - - // If the output is an object then return object using the row offset.. - if ( $output == OBJECT ) - { - return $this->last_result[$y]?$this->last_result[$y]:null; - } - // If the output is an associative array then return row as such.. - elseif ( $output == ARRAY_A ) - { - return $this->last_result[$y]?get_object_vars($this->last_result[$y]):null; - } - // If the output is an numerical array then return row as such.. - elseif ( $output == ARRAY_N ) - { - return $this->last_result[$y]?array_values(get_object_vars($this->last_result[$y])):null; - } - // If invalid output type was specified.. - else - { - $this->show_errors ? trigger_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N",E_USER_WARNING) : null; - } - - } - - /********************************************************************** - * Function to get 1 column from the cached result set based in X index - * see docs for usage and info - */ - - function get_col($query=null,$x=0) - { - - $new_array = array(); - - // If there is a query then perform it if not then use cached results.. - if ( $query ) - { - $this->query($query); - } - - // Extract the column values - for ( $i=0; $i < count($this->last_result); $i++ ) - { - $new_array[$i] = $this->get_var(null,$x,$i); - } - - return $new_array; - } - - - /********************************************************************** - * Return the the query as a result set - see docs for more details - */ - - function get_results($query=null, $output = OBJECT) - { - - // Log how the function was called - $this->func_call = "\$db->get_results(\"$query\", $output)"; - - // If there is a query then perform it if not then use cached results.. - if ( $query ) - { - $this->query($query); - } - - // Send back array of objects. Each row is an object - if ( $output == OBJECT ) - { - return $this->last_result; - } - elseif ( $output == ARRAY_A || $output == ARRAY_N ) - { - if ( $this->last_result ) - { - $i=0; - foreach( $this->last_result as $row ) - { - - $new_array[$i] = get_object_vars($row); - - if ( $output == ARRAY_N ) - { - $new_array[$i] = array_values($new_array[$i]); - } - - $i++; - } - - return $new_array; - } - else - { - return null; - } - } - } - - - /********************************************************************** - * Function to get column meta data info pertaining to the last query - * see docs for more info and usage - */ - - function get_col_info($info_type="name",$col_offset=-1) - { - - if ( $this->col_info ) - { - if ( $col_offset == -1 ) - { - $i=0; - foreach($this->col_info as $col ) - { - $new_array[$i] = $col->{$info_type}; - $i++; - } - return $new_array; - } - else - { - return $this->col_info[$col_offset]->{$info_type}; - } - - } - - } - - /********************************************************************** - * store_cache - */ - - function store_cache($query,$is_insert) - { - - // The would be cache file for this query - $cache_file = $this->cache_dir.'/'.md5($query); - - // disk caching of queries - if ( $this->use_disk_cache && ( $this->cache_queries && ! $is_insert ) || ( $this->cache_inserts && $is_insert )) - { - if ( ! is_dir($this->cache_dir) ) - { - $this->register_error("Could not open cache dir: $this->cache_dir"); - $this->show_errors ? trigger_error("Could not open cache dir: $this->cache_dir",E_USER_WARNING) : null; - } - else - { - // Cache all result values - $result_cache = array - ( - 'col_info' => $this->col_info, - 'last_result' => $this->last_result, - 'num_rows' => $this->num_rows, - 'return_value' => $this->num_rows, - ); - file_put_contents($cache_file, serialize($result_cache)); - if( file_exists($cache_file . ".updating") ) - unlink($cache_file . ".updating"); - } - } - - } - - /********************************************************************** - * get_cache - */ - - function get_cache($query) - { - - // The would be cache file for this query - $cache_file = $this->cache_dir.'/'.md5($query); - - // Try to get previously cached version - if ( $this->use_disk_cache && file_exists($cache_file) ) - { - // Only use this cache file if less than 'cache_timeout' (hours) - if ( (time() - filemtime($cache_file)) > ($this->cache_timeout*3600) && - !(file_exists($cache_file . ".updating") && (time() - filemtime($cache_file . ".updating") < 60)) ) - { - touch($cache_file . ".updating"); // Show that we in the process of updating the cache - } - else - { - $result_cache = unserialize(file_get_contents($cache_file)); - - $this->col_info = $result_cache['col_info']; - $this->last_result = $result_cache['last_result']; - $this->num_rows = $result_cache['num_rows']; - - $this->from_disk_cache = true; - - // If debug ALL queries - $this->trace || $this->debug_all ? $this->debug() : null ; - - return $result_cache['return_value']; - } - } - - } - - /********************************************************************** - * Dumps the contents of any input variable to screen in a nicely - * formatted and easy to understand way - any type: Object, Var or Array - */ - - function vardump($mixed='') - { - - // Start outup buffering - ob_start(); - - echo "

"; - echo "
";
-
-		if ( ! $this->vardump_called )
-		{
-			echo "ezSQL (v".EZSQL_VERSION.") Variable Dump..\n\n";
-		}
-
-		$var_type = gettype ($mixed);
-		print_r(($mixed?$mixed:"No Value / False"));
-		echo "\n\nType: " . ucfirst($var_type) . "\n";
-		echo "Last Query [$this->num_queries]: ".($this->last_query?$this->last_query:"NULL")."\n";
-		echo "Last Function Call: " . ($this->func_call?$this->func_call:"None")."\n";
-		echo "Last Rows Returned: ".count($this->last_result)."\n";
-		echo "
".$this->donation(); - echo "\n


"; - - // Stop output buffering and capture debug HTML - $html = ob_get_contents(); - ob_end_clean(); - - // Only echo output if it is turned on - if ( $this->debug_echo_is_on ) - { - echo $html; - } - - $this->vardump_called = true; - - return $html; - - } - - /********************************************************************** - * Alias for the above function - */ - - function dumpvar($mixed) - { - $this->vardump($mixed); - } - - /********************************************************************** - * Displays the last query string that was sent to the database & a - * table listing results (if there were any). - * (abstracted into a seperate file to save server overhead). - */ - - function debug($print_to_screen=true) - { - - // Start outup buffering - ob_start(); - - echo "
"; - - // Only show ezSQL credits once.. - if ( ! $this->debug_called ) - { - echo "ezSQL (v".EZSQL_VERSION.") Debug..

\n"; - } - - if ( $this->last_error ) - { - echo "Last Error -- [$this->last_error]

"; - } - - if ( $this->from_disk_cache ) - { - echo "Results retrieved from disk cache

"; - } - - echo "Query [$this->num_queries] -- "; - echo "[$this->last_query]

"; - - echo "Query Result.."; - echo "

"; - - if ( $this->col_info ) - { - - // ===================================================== - // Results top rows - - echo ""; - echo ""; - - - for ( $i=0; $i < count($this->col_info); $i++ ) - { - echo ""; - } - - echo ""; - - // ====================================================== - // print main results - - if ( $this->last_result ) - { - - $i=0; - foreach ( $this->get_results(null,ARRAY_N) as $one_row ) - { - $i++; - echo ""; - - foreach ( $one_row as $item ) - { - echo ""; - } - - echo ""; - } - - } // if last result - else - { - echo ""; - } - - echo "
(row){$this->col_info[$i]->type} {$this->col_info[$i]->max_length}
{$this->col_info[$i]->name}
$i$item
No Results
"; - - } // if col_info - else - { - echo "No Results"; - } - - echo "
".$this->donation()."
"; - - // Stop output buffering and capture debug HTML - $html = ob_get_contents(); - ob_end_clean(); - - // Only echo output if it is turned on - if ( $this->debug_echo_is_on && $print_to_screen) - { - echo $html; - } - - $this->debug_called = true; - - return $html; - - } - - /********************************************************************** - * Naughty little function to ask for some remuniration! - */ - - function donation() - { - return "If ezSQL has helped make a donation!?   "; - } - - /********************************************************************** - * Timer related functions - */ - - function timer_get_cur() - { - list($usec, $sec) = explode(" ",microtime()); - return ((float)$usec + (float)$sec); - } - - function timer_start($timer_name) - { - $this->timers[$timer_name] = $this->timer_get_cur(); - } - - function timer_elapsed($timer_name) - { - return round($this->timer_get_cur() - $this->timers[$timer_name],2); - } - - function timer_update_global($timer_name) - { - if ( $this->do_profile ) - { - $this->profile_times[] = array - ( - 'query' => $this->last_query, - 'time' => $this->timer_elapsed($timer_name) - ); - } - - $this->total_query_time += $this->timer_elapsed($timer_name); - } - - /********************************************************************** - * Creates a SET nvp sql string from an associative array (and escapes all values) - * - * Usage: - * - * $db_data = array('login'=>'jv','email'=>'jv@vip.ie', 'user_id' => 1, 'created' => 'NOW()'); - * - * $db->query("INSERT INTO users SET ".$db->get_set($db_data)); - * - * ...OR... - * - * $db->query("UPDATE users SET ".$db->get_set($db_data)." WHERE user_id = 1"); - * - * Output: - * - * login = 'jv', email = 'jv@vip.ie', user_id = 1, created = NOW() - */ - - function get_set($parms) - { - $sql = ''; - foreach ( $parms as $field => $val ) - { - if ( $val === 'true' ) $val = 1; - if ( $val === 'false' ) $val = 0; - - if ( $val == 'NOW()' ) - { - $sql .= "$field = ".$this->escape($val).", "; - } - else - { - $sql .= "$field = '".$this->escape($val)."', "; - } - } - - return substr($sql,0,-2); - } - -} - - -/********************************************************************** -* Author: Justin Vincent (jv@jvmultimedia.com) -* Web...: http://twitter.com/justinvincent -* Name..: ezSQL_mysql -* Desc..: mySQL component (part of ezSQL databse abstraction library) -* -*/ - -/********************************************************************** -* ezSQL error strings - mySQL -*/ - -$ezsql_mysql_str = array -( - 1 => 'Require $dbuser and $dbpassword to connect to a database server', - 2 => 'Error establishing mySQL database connection. Correct user/password? Correct hostname? Database server running?', - 3 => 'Require $dbname to select a database', - 4 => 'mySQL database connection is not active', - 5 => 'Unexpected error while trying to select database' -); - -/********************************************************************** -* ezSQL Database specific class - mySQL -*/ - -if ( ! function_exists ('mysql_connect') ) die('Fatal Error: ezSQL_mysql requires mySQL Lib to be compiled and or linked in to the PHP engine'); -if ( ! class_exists ('ezSQLcore') ) die('Fatal Error: ezSQL_mysql requires ezSQLcore (ez_sql_core.php) to be included/loaded before it can be used'); - -class ezSQL_mysql extends ezSQLcore -{ - - var $dbuser = false; - var $dbpassword = false; - var $dbname = false; - var $dbhost = false; - var $encoding = false; - - /********************************************************************** - * Constructor - allow the user to perform a qucik connect at the - * same time as initialising the ezSQL_mysql class - */ - - function ezSQL_mysql($dbuser='', $dbpassword='', $dbname='', $dbhost='localhost', $encoding='') - { - $this->dbuser = $dbuser; - $this->dbpassword = $dbpassword; - $this->dbname = $dbname; - $this->dbhost = $dbhost; - $this->encoding = $encoding; - } - - /********************************************************************** - * Short hand way to connect to mySQL database server - * and select a mySQL database at the same time - */ - - function quick_connect($dbuser='', $dbpassword='', $dbname='', $dbhost='localhost', $encoding='') - { - $return_val = false; - if ( ! $this->connect($dbuser, $dbpassword, $dbhost,true) ) ; - else if ( ! $this->select($dbname,$encoding) ) ; - else $return_val = true; - return $return_val; - } - - /********************************************************************** - * Try to connect to mySQL database server - */ - - function connect($dbuser='', $dbpassword='', $dbhost='localhost') - { - global $ezsql_mysql_str; $return_val = false; - - // Keep track of how long the DB takes to connect - $this->timer_start('db_connect_time'); - - // Must have a user and a password - if ( ! $dbuser ) - { - $this->register_error($ezsql_mysql_str[1].' in '.__FILE__.' on line '.__LINE__); - $this->show_errors ? trigger_error($ezsql_mysql_str[1],E_USER_WARNING) : null; - } - // Try to establish the server database handle - else if ( ! $this->dbh = @mysql_connect($dbhost,$dbuser,$dbpassword,true,131074) ) - { - $this->register_error($ezsql_mysql_str[2].' in '.__FILE__.' on line '.__LINE__); - $this->show_errors ? trigger_error($ezsql_mysql_str[2],E_USER_WARNING) : null; - } - else - { - $this->dbuser = $dbuser; - $this->dbpassword = $dbpassword; - $this->dbhost = $dbhost; - $return_val = true; - } - - return $return_val; - } - - /********************************************************************** - * Try to select a mySQL database - */ - - function select($dbname='', $encoding='') - { - global $ezsql_mysql_str; $return_val = false; - - // Must have a database name - if ( ! $dbname ) - { - $this->register_error($ezsql_mysql_str[3].' in '.__FILE__.' on line '.__LINE__); - $this->show_errors ? trigger_error($ezsql_mysql_str[3],E_USER_WARNING) : null; - } - - // Must have an active database connection - else if ( ! $this->dbh ) - { - $this->register_error($ezsql_mysql_str[4].' in '.__FILE__.' on line '.__LINE__); - $this->show_errors ? trigger_error($ezsql_mysql_str[4],E_USER_WARNING) : null; - } - - // Try to connect to the database - else if ( !@mysql_select_db($dbname,$this->dbh) ) - { - // Try to get error supplied by mysql if not use our own - if ( !$str = @mysql_error($this->dbh)) - $str = $ezsql_mysql_str[5]; - - $this->register_error($str.' in '.__FILE__.' on line '.__LINE__); - $this->show_errors ? trigger_error($str,E_USER_WARNING) : null; - } - else - { - $this->dbname = $dbname; - if($encoding!='') - { - $encoding = strtolower(str_replace("-","",$encoding)); - $charsets = array(); - $result = mysql_query("SHOW CHARACTER SET"); - while($row = mysql_fetch_array($result,MYSQL_ASSOC)) - { - $charsets[] = $row["Charset"]; - } - if(in_array($encoding,$charsets)){ - mysql_query("SET NAMES '".$encoding."'"); - } - } - - $return_val = true; - } - - return $return_val; - } - - /********************************************************************** - * Format a mySQL string correctly for safe mySQL insert - * (no mater if magic quotes are on or not) - */ - - function escape($str) - { - // If there is no existing database connection then try to connect - if ( ! isset($this->dbh) || ! $this->dbh ) - { - $this->connect($this->dbuser, $this->dbpassword, $this->dbhost); - $this->select($this->dbname, $this->encoding); - } - - return mysql_real_escape_string(stripslashes($str)); - } - - /********************************************************************** - * Return mySQL specific system date syntax - * i.e. Oracle: SYSDATE Mysql: NOW() - */ - - function sysdate() - { - return 'NOW()'; - } - - /********************************************************************** - * Perform mySQL query and try to detirmin result value - */ - - function query($query) - { - - // This keeps the connection alive for very long running scripts - if ( $this->num_queries >= 500 ) - { - $this->disconnect(); - $this->quick_connect($this->dbuser,$this->dbpassword,$this->dbname,$this->dbhost,$this->encoding); - } - - // Initialise return - $return_val = 0; - - // Flush cached values.. - $this->flush(); - - // For reg expressions - $query = trim($query); - - // Log how the function was called - $this->func_call = "\$db->query(\"$query\")"; - - // Keep track of the last query for debug.. - $this->last_query = $query; - - // Count how many queries there have been - $this->num_queries++; - - // Keep history of all queries - $this->all_queries .= $query.'
'; - - // Start timer - $this->timer_start($this->num_queries); - - // Use core file cache function - if ( $cache = $this->get_cache($query) ) - { - // Keep tack of how long all queries have taken - $this->timer_update_global($this->num_queries); - - // Trace all queries - if ( $this->use_trace_log ) - { - $this->trace_log[] = $this->debug(false); - } - - return $cache; - } - - // If there is no existing database connection then try to connect - if ( ! isset($this->dbh) || ! $this->dbh ) - { - $this->connect($this->dbuser, $this->dbpassword, $this->dbhost); - $this->select($this->dbname,$this->encoding); - } - - // Perform the query via std mysql_query function.. - $this->result = @mysql_query($query,$this->dbh); - - // If there is an error then take note of it.. - if ( $str = @mysql_error($this->dbh) ) - { - $is_insert = true; - $this->register_error($str); - $this->show_errors ? trigger_error($str,E_USER_WARNING) : null; - return false; - } - - // Query was an insert, delete, update, replace - $is_insert = false; - if ( preg_match("/^(insert|delete|update|replace|truncate|drop|create|alter)\s+/i",$query) ) - { - $this->rows_affected = @mysql_affected_rows($this->dbh); - - // Take note of the insert_id - if ( preg_match("/^(insert|replace)\s+/i",$query) ) - { - $this->insert_id = @mysql_insert_id($this->dbh); - } - - // Return number fo rows affected - $return_val = $this->rows_affected; - } - // Query was a select - else - { - - // Take note of column info - $i=0; - while ($i < @mysql_num_fields($this->result)) - { - $this->col_info[$i] = @mysql_fetch_field($this->result); - $i++; - } - - // Store Query Results - $num_rows=0; - while ( $row = @mysql_fetch_object($this->result) ) - { - // Store relults as an objects within main array - $this->last_result[$num_rows] = $row; - $num_rows++; - } - - @mysql_free_result($this->result); - - // Log number of rows the query returned - $this->num_rows = $num_rows; - - // Return number of rows selected - $return_val = $this->num_rows; - } - - // disk caching of queries - $this->store_cache($query,$is_insert); - - // If debug ALL queries - $this->trace || $this->debug_all ? $this->debug() : null ; - - // Keep tack of how long all queries have taken - $this->timer_update_global($this->num_queries); - - // Trace all queries - if ( $this->use_trace_log ) - { - $this->trace_log[] = $this->debug(false); - } - - return $return_val; - - } - - /********************************************************************** - * Close the active mySQL connection - */ - - function disconnect() - { - @mysql_close($this->dbh); - } - - function mysql_version() { - return mysql_get_server_info( $this->dbh ) ; - } -} +last_error = $err_str; + + // Capture all errors to an error array no matter what happens + $this->captured_errors[] = array + ( + 'error_str' => $err_str, + 'query' => $this->last_query + ); + } + + /********************************************************************** + * Turn error handling on or off.. + */ + + function show_errors() + { + $this->show_errors = true; + } + + function hide_errors() + { + $this->show_errors = false; + } + + /********************************************************************** + * Kill cached query results + */ + + function flush() + { + // Get rid of these + $this->last_result = null; + $this->col_info = null; + $this->last_query = null; + $this->from_disk_cache = false; + } + + /********************************************************************** + * Get one variable from the DB - see docs for more detail + */ + + function get_var($query=null,$x=0,$y=0) + { + + // Log how the function was called + $this->func_call = "\$db->get_var(\"$query\",$x,$y)"; + + // If there is a query then perform it if not then use cached results.. + if ( $query ) + { + $this->query($query); + } + + // Extract var out of cached results based x,y vals + if ( $this->last_result[$y] ) + { + $values = array_values(get_object_vars($this->last_result[$y])); + } + + // If there is a value return it else return null + return (isset($values[$x]) && $values[$x]!=='')?$values[$x]:null; + } + + /********************************************************************** + * Get one row from the DB - see docs for more detail + */ + + function get_row($query=null,$output=OBJECT,$y=0) + { + + // Log how the function was called + $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; + + // If there is a query then perform it if not then use cached results.. + if ( $query ) + { + $this->query($query); + } + + // If the output is an object then return object using the row offset.. + if ( $output == OBJECT ) + { + return $this->last_result[$y]?$this->last_result[$y]:null; + } + // If the output is an associative array then return row as such.. + elseif ( $output == ARRAY_A ) + { + return $this->last_result[$y]?get_object_vars($this->last_result[$y]):null; + } + // If the output is an numerical array then return row as such.. + elseif ( $output == ARRAY_N ) + { + return $this->last_result[$y]?array_values(get_object_vars($this->last_result[$y])):null; + } + // If invalid output type was specified.. + else + { + $this->show_errors ? trigger_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N",E_USER_WARNING) : null; + } + + } + + /********************************************************************** + * Function to get 1 column from the cached result set based in X index + * see docs for usage and info + */ + + function get_col($query=null,$x=0) + { + + $new_array = array(); + + // If there is a query then perform it if not then use cached results.. + if ( $query ) + { + $this->query($query); + } + + // Extract the column values + for ( $i=0; $i < count($this->last_result); $i++ ) + { + $new_array[$i] = $this->get_var(null,$x,$i); + } + + return $new_array; + } + + + /********************************************************************** + * Return the the query as a result set - see docs for more details + */ + + function get_results($query=null, $output = OBJECT) + { + + // Log how the function was called + $this->func_call = "\$db->get_results(\"$query\", $output)"; + + // If there is a query then perform it if not then use cached results.. + if ( $query ) + { + $this->query($query); + } + + // Send back array of objects. Each row is an object + if ( $output == OBJECT ) + { + return $this->last_result; + } + elseif ( $output == ARRAY_A || $output == ARRAY_N ) + { + if ( $this->last_result ) + { + $i=0; + foreach( $this->last_result as $row ) + { + + $new_array[$i] = get_object_vars($row); + + if ( $output == ARRAY_N ) + { + $new_array[$i] = array_values($new_array[$i]); + } + + $i++; + } + + return $new_array; + } + else + { + return null; + } + } + } + + + /********************************************************************** + * Function to get column meta data info pertaining to the last query + * see docs for more info and usage + */ + + function get_col_info($info_type="name",$col_offset=-1) + { + + if ( $this->col_info ) + { + if ( $col_offset == -1 ) + { + $i=0; + foreach($this->col_info as $col ) + { + $new_array[$i] = $col->{$info_type}; + $i++; + } + return $new_array; + } + else + { + return $this->col_info[$col_offset]->{$info_type}; + } + + } + + } + + /********************************************************************** + * store_cache + */ + + function store_cache($query,$is_insert) + { + + // The would be cache file for this query + $cache_file = $this->cache_dir.'/'.md5($query); + + // disk caching of queries + if ( $this->use_disk_cache && ( $this->cache_queries && ! $is_insert ) || ( $this->cache_inserts && $is_insert )) + { + if ( ! is_dir($this->cache_dir) ) + { + $this->register_error("Could not open cache dir: $this->cache_dir"); + $this->show_errors ? trigger_error("Could not open cache dir: $this->cache_dir",E_USER_WARNING) : null; + } + else + { + // Cache all result values + $result_cache = array + ( + 'col_info' => $this->col_info, + 'last_result' => $this->last_result, + 'num_rows' => $this->num_rows, + 'return_value' => $this->num_rows, + ); + file_put_contents($cache_file, serialize($result_cache)); + if( file_exists($cache_file . ".updating") ) + unlink($cache_file . ".updating"); + } + } + + } + + /********************************************************************** + * get_cache + */ + + function get_cache($query) + { + + // The would be cache file for this query + $cache_file = $this->cache_dir.'/'.md5($query); + + // Try to get previously cached version + if ( $this->use_disk_cache && file_exists($cache_file) ) + { + // Only use this cache file if less than 'cache_timeout' (hours) + if ( (time() - filemtime($cache_file)) > ($this->cache_timeout*3600) && + !(file_exists($cache_file . ".updating") && (time() - filemtime($cache_file . ".updating") < 60)) ) + { + touch($cache_file . ".updating"); // Show that we in the process of updating the cache + } + else + { + $result_cache = unserialize(file_get_contents($cache_file)); + + $this->col_info = $result_cache['col_info']; + $this->last_result = $result_cache['last_result']; + $this->num_rows = $result_cache['num_rows']; + + $this->from_disk_cache = true; + + // If debug ALL queries + $this->trace || $this->debug_all ? $this->debug() : null ; + + return $result_cache['return_value']; + } + } + + } + + /********************************************************************** + * Dumps the contents of any input variable to screen in a nicely + * formatted and easy to understand way - any type: Object, Var or Array + */ + + function vardump($mixed='') + { + + // Start outup buffering + ob_start(); + + echo "

"; + echo "
";
+
+		if ( ! $this->vardump_called )
+		{
+			echo "ezSQL (v".EZSQL_VERSION.") Variable Dump..\n\n";
+		}
+
+		$var_type = gettype ($mixed);
+		print_r(($mixed?$mixed:"No Value / False"));
+		echo "\n\nType: " . ucfirst($var_type) . "\n";
+		echo "Last Query [$this->num_queries]: ".($this->last_query?$this->last_query:"NULL")."\n";
+		echo "Last Function Call: " . ($this->func_call?$this->func_call:"None")."\n";
+		echo "Last Rows Returned: ".count($this->last_result)."\n";
+		echo "
".$this->donation(); + echo "\n


"; + + // Stop output buffering and capture debug HTML + $html = ob_get_contents(); + ob_end_clean(); + + // Only echo output if it is turned on + if ( $this->debug_echo_is_on ) + { + echo $html; + } + + $this->vardump_called = true; + + return $html; + + } + + /********************************************************************** + * Alias for the above function + */ + + function dumpvar($mixed) + { + $this->vardump($mixed); + } + + /********************************************************************** + * Displays the last query string that was sent to the database & a + * table listing results (if there were any). + * (abstracted into a seperate file to save server overhead). + */ + + function debug($print_to_screen=true) + { + + // Start outup buffering + ob_start(); + + echo "
"; + + // Only show ezSQL credits once.. + if ( ! $this->debug_called ) + { + echo "ezSQL (v".EZSQL_VERSION.") Debug..

\n"; + } + + if ( $this->last_error ) + { + echo "Last Error -- [$this->last_error]

"; + } + + if ( $this->from_disk_cache ) + { + echo "Results retrieved from disk cache

"; + } + + echo "Query [$this->num_queries] -- "; + echo "[$this->last_query]

"; + + echo "Query Result.."; + echo "

"; + + if ( $this->col_info ) + { + + // ===================================================== + // Results top rows + + echo ""; + echo ""; + + + for ( $i=0; $i < count($this->col_info); $i++ ) + { + echo ""; + } + + echo ""; + + // ====================================================== + // print main results + + if ( $this->last_result ) + { + + $i=0; + foreach ( $this->get_results(null,ARRAY_N) as $one_row ) + { + $i++; + echo ""; + + foreach ( $one_row as $item ) + { + echo ""; + } + + echo ""; + } + + } // if last result + else + { + echo ""; + } + + echo "
(row){$this->col_info[$i]->type} {$this->col_info[$i]->max_length}
{$this->col_info[$i]->name}
$i$item
No Results
"; + + } // if col_info + else + { + echo "No Results"; + } + + echo "
".$this->donation()."
"; + + // Stop output buffering and capture debug HTML + $html = ob_get_contents(); + ob_end_clean(); + + // Only echo output if it is turned on + if ( $this->debug_echo_is_on && $print_to_screen) + { + echo $html; + } + + $this->debug_called = true; + + return $html; + + } + + /********************************************************************** + * Naughty little function to ask for some remuniration! + */ + + function donation() + { + return "If ezSQL has helped make a donation!?   "; + } + + /********************************************************************** + * Timer related functions + */ + + function timer_get_cur() + { + list($usec, $sec) = explode(" ",microtime()); + return ((float)$usec + (float)$sec); + } + + function timer_start($timer_name) + { + $this->timers[$timer_name] = $this->timer_get_cur(); + } + + function timer_elapsed($timer_name) + { + return round($this->timer_get_cur() - $this->timers[$timer_name],2); + } + + function timer_update_global($timer_name) + { + if ( $this->do_profile ) + { + $this->profile_times[] = array + ( + 'query' => $this->last_query, + 'time' => $this->timer_elapsed($timer_name) + ); + } + + $this->total_query_time += $this->timer_elapsed($timer_name); + } + + /********************************************************************** + * Creates a SET nvp sql string from an associative array (and escapes all values) + * + * Usage: + * + * $db_data = array('login'=>'jv','email'=>'jv@vip.ie', 'user_id' => 1, 'created' => 'NOW()'); + * + * $db->query("INSERT INTO users SET ".$db->get_set($db_data)); + * + * ...OR... + * + * $db->query("UPDATE users SET ".$db->get_set($db_data)." WHERE user_id = 1"); + * + * Output: + * + * login = 'jv', email = 'jv@vip.ie', user_id = 1, created = NOW() + */ + + function get_set($parms) + { + $sql = ''; + foreach ( $parms as $field => $val ) + { + if ( $val === 'true' ) $val = 1; + if ( $val === 'false' ) $val = 0; + + if ( $val == 'NOW()' ) + { + $sql .= "$field = ".$this->escape($val).", "; + } + else + { + $sql .= "$field = '".$this->escape($val)."', "; + } + } + + return substr($sql,0,-2); + } + +} + + +/********************************************************************** +* Author: Justin Vincent (jv@jvmultimedia.com) +* Web...: http://twitter.com/justinvincent +* Name..: ezSQL_mysql +* Desc..: mySQL component (part of ezSQL databse abstraction library) +* +*/ + +/********************************************************************** +* ezSQL error strings - mySQL +*/ + +$ezsql_mysql_str = array +( + 1 => 'Require $dbuser and $dbpassword to connect to a database server', + 2 => 'Error establishing mySQL database connection. Correct user/password? Correct hostname? Database server running?', + 3 => 'Require $dbname to select a database', + 4 => 'mySQL database connection is not active', + 5 => 'Unexpected error while trying to select database' +); + +/********************************************************************** +* ezSQL Database specific class - mySQL +*/ + +if ( ! function_exists ('mysql_connect') ) die('Fatal Error: ezSQL_mysql requires mySQL Lib to be compiled and or linked in to the PHP engine'); +if ( ! class_exists ('ezSQLcore') ) die('Fatal Error: ezSQL_mysql requires ezSQLcore (ez_sql_core.php) to be included/loaded before it can be used'); + +class ezSQL_mysql extends ezSQLcore +{ + + var $dbuser = false; + var $dbpassword = false; + var $dbname = false; + var $dbhost = false; + var $encoding = false; + + /********************************************************************** + * Constructor - allow the user to perform a qucik connect at the + * same time as initialising the ezSQL_mysql class + */ + + function ezSQL_mysql($dbuser='', $dbpassword='', $dbname='', $dbhost='localhost', $encoding='') + { + $this->dbuser = $dbuser; + $this->dbpassword = $dbpassword; + $this->dbname = $dbname; + $this->dbhost = $dbhost; + $this->encoding = $encoding; + } + + /********************************************************************** + * Short hand way to connect to mySQL database server + * and select a mySQL database at the same time + */ + + function quick_connect($dbuser='', $dbpassword='', $dbname='', $dbhost='localhost', $encoding='') + { + $return_val = false; + if ( ! $this->connect($dbuser, $dbpassword, $dbhost,true) ) ; + else if ( ! $this->select($dbname,$encoding) ) ; + else $return_val = true; + return $return_val; + } + + /********************************************************************** + * Try to connect to mySQL database server + */ + + function connect($dbuser='', $dbpassword='', $dbhost='localhost') + { + global $ezsql_mysql_str; $return_val = false; + + // Keep track of how long the DB takes to connect + $this->timer_start('db_connect_time'); + + // Must have a user and a password + if ( ! $dbuser ) + { + $this->register_error($ezsql_mysql_str[1].' in '.__FILE__.' on line '.__LINE__); + $this->show_errors ? trigger_error($ezsql_mysql_str[1],E_USER_WARNING) : null; + } + // Try to establish the server database handle + else if ( ! $this->dbh = @mysql_connect($dbhost,$dbuser,$dbpassword,true,131074) ) + { + $this->register_error($ezsql_mysql_str[2].' in '.__FILE__.' on line '.__LINE__); + $this->show_errors ? trigger_error($ezsql_mysql_str[2],E_USER_WARNING) : null; + } + else + { + $this->dbuser = $dbuser; + $this->dbpassword = $dbpassword; + $this->dbhost = $dbhost; + $return_val = true; + } + + return $return_val; + } + + /********************************************************************** + * Try to select a mySQL database + */ + + function select($dbname='', $encoding='') + { + global $ezsql_mysql_str; $return_val = false; + + // Must have a database name + if ( ! $dbname ) + { + $this->register_error($ezsql_mysql_str[3].' in '.__FILE__.' on line '.__LINE__); + $this->show_errors ? trigger_error($ezsql_mysql_str[3],E_USER_WARNING) : null; + } + + // Must have an active database connection + else if ( ! $this->dbh ) + { + $this->register_error($ezsql_mysql_str[4].' in '.__FILE__.' on line '.__LINE__); + $this->show_errors ? trigger_error($ezsql_mysql_str[4],E_USER_WARNING) : null; + } + + // Try to connect to the database + else if ( !@mysql_select_db($dbname,$this->dbh) ) + { + // Try to get error supplied by mysql if not use our own + if ( !$str = @mysql_error($this->dbh)) + $str = $ezsql_mysql_str[5]; + + $this->register_error($str.' in '.__FILE__.' on line '.__LINE__); + $this->show_errors ? trigger_error($str,E_USER_WARNING) : null; + } + else + { + $this->dbname = $dbname; + if($encoding!='') + { + $encoding = strtolower(str_replace("-","",$encoding)); + $charsets = array(); + $result = mysql_query("SHOW CHARACTER SET"); + while($row = mysql_fetch_array($result,MYSQL_ASSOC)) + { + $charsets[] = $row["Charset"]; + } + if(in_array($encoding,$charsets)){ + mysql_query("SET NAMES '".$encoding."'"); + } + } + + $return_val = true; + } + + return $return_val; + } + + /********************************************************************** + * Format a mySQL string correctly for safe mySQL insert + * (no mater if magic quotes are on or not) + */ + + function escape($str) + { + // If there is no existing database connection then try to connect + if ( ! isset($this->dbh) || ! $this->dbh ) + { + $this->connect($this->dbuser, $this->dbpassword, $this->dbhost); + $this->select($this->dbname, $this->encoding); + } + + return mysql_real_escape_string(stripslashes($str)); + } + + /********************************************************************** + * Return mySQL specific system date syntax + * i.e. Oracle: SYSDATE Mysql: NOW() + */ + + function sysdate() + { + return 'NOW()'; + } + + /********************************************************************** + * Perform mySQL query and try to detirmin result value + */ + + function query($query) + { + + // This keeps the connection alive for very long running scripts + if ( $this->num_queries >= 500 ) + { + $this->disconnect(); + $this->quick_connect($this->dbuser,$this->dbpassword,$this->dbname,$this->dbhost,$this->encoding); + } + + // Initialise return + $return_val = 0; + + // Flush cached values.. + $this->flush(); + + // For reg expressions + $query = trim($query); + + // Log how the function was called + $this->func_call = "\$db->query(\"$query\")"; + + // Keep track of the last query for debug.. + $this->last_query = $query; + + // Count how many queries there have been + $this->num_queries++; + + // Keep history of all queries + $this->all_queries .= $query.'
'; + + // Start timer + $this->timer_start($this->num_queries); + + // Use core file cache function + if ( $cache = $this->get_cache($query) ) + { + // Keep tack of how long all queries have taken + $this->timer_update_global($this->num_queries); + + // Trace all queries + if ( $this->use_trace_log ) + { + $this->trace_log[] = $this->debug(false); + } + + return $cache; + } + + // If there is no existing database connection then try to connect + if ( ! isset($this->dbh) || ! $this->dbh ) + { + $this->connect($this->dbuser, $this->dbpassword, $this->dbhost); + $this->select($this->dbname,$this->encoding); + } + + // Perform the query via std mysql_query function.. + $this->result = @mysql_query($query,$this->dbh); + + // If there is an error then take note of it.. + if ( $str = @mysql_error($this->dbh) ) + { + $is_insert = true; + $this->register_error($str); + $this->show_errors ? trigger_error($str,E_USER_WARNING) : null; + return false; + } + + // Query was an insert, delete, update, replace + $is_insert = false; + if ( preg_match("/^(insert|delete|update|replace|truncate|drop|create|alter)\s+/i",$query) ) + { + $this->rows_affected = @mysql_affected_rows($this->dbh); + + // Take note of the insert_id + if ( preg_match("/^(insert|replace)\s+/i",$query) ) + { + $this->insert_id = @mysql_insert_id($this->dbh); + } + + // Return number fo rows affected + $return_val = $this->rows_affected; + } + // Query was a select + else + { + + // Take note of column info + $i=0; + while ($i < @mysql_num_fields($this->result)) + { + $this->col_info[$i] = @mysql_fetch_field($this->result); + $i++; + } + + // Store Query Results + $num_rows=0; + while ( $row = @mysql_fetch_object($this->result) ) + { + // Store relults as an objects within main array + $this->last_result[$num_rows] = $row; + $num_rows++; + } + + @mysql_free_result($this->result); + + // Log number of rows the query returned + $this->num_rows = $num_rows; + + // Return number of rows selected + $return_val = $this->num_rows; + } + + // disk caching of queries + $this->store_cache($query,$is_insert); + + // If debug ALL queries + $this->trace || $this->debug_all ? $this->debug() : null ; + + // Keep tack of how long all queries have taken + $this->timer_update_global($this->num_queries); + + // Trace all queries + if ( $this->use_trace_log ) + { + $this->trace_log[] = $this->debug(false); + } + + return $return_val; + + } + + /********************************************************************** + * Close the active mySQL connection + */ + + function disconnect() + { + @mysql_close($this->dbh); + } + + function mysql_version() { + return mysql_get_server_info( $this->dbh ) ; + } +} diff --git a/includes/functions-api.php b/includes/functions-api.php index 77e7bad..01a23aa 100644 --- a/includes/functions-api.php +++ b/includes/functions-api.php @@ -1,202 +1,202 @@ - yourls_get_db_stats(), - 'statusCode' => 200, - 'simple' => 'Need either XML or JSON format for stats', - 'message' => 'success', - ); - - return yourls_apply_filter( 'api_db_stats', $return ); -} - +function yourls_api_db_stats() { + $return = array( + 'db-stats' => yourls_get_db_stats(), + 'statusCode' => 200, + 'simple' => 'Need either XML or JSON format for stats', + 'message' => 'success', + ); + + return yourls_apply_filter( 'api_db_stats', $return ); +} + /** * Return array for API stat requests * */ -function yourls_api_url_stats( $shorturl ) { - $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' - $keyword = yourls_sanitize_string( $keyword ); - - $return = yourls_get_link_stats( $keyword ); - $return['simple'] = 'Need either XML or JSON format for stats'; - return yourls_apply_filter( 'api_url_stats', $return, $shorturl ); -} - +function yourls_api_url_stats( $shorturl ) { + $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' + $keyword = yourls_sanitize_string( $keyword ); + + $return = yourls_get_link_stats( $keyword ); + $return['simple'] = 'Need either XML or JSON format for stats'; + return yourls_apply_filter( 'api_url_stats', $return, $shorturl ); +} + /** * Expand short url to long url * */ -function yourls_api_expand( $shorturl ) { - $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' - $keyword = yourls_sanitize_string( $keyword ); - - $longurl = yourls_get_keyword_longurl( $keyword ); - - if( $longurl ) { - $return = array( - 'keyword' => $keyword, - 'shorturl' => YOURLS_SITE . "/$keyword", - 'longurl' => $longurl, - 'simple' => $longurl, - 'message' => 'success', - 'statusCode' => 200, - ); - } else { - $return = array( - 'keyword' => $keyword, - 'simple' => 'not found', - 'message' => 'Error: short URL not found', - 'errorCode' => 404, - ); - } - - return yourls_apply_filter( 'api_expand', $return, $shorturl ); -} +function yourls_api_expand( $shorturl ) { + $keyword = str_replace( YOURLS_SITE . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' + $keyword = yourls_sanitize_string( $keyword ); + + $longurl = yourls_get_keyword_longurl( $keyword ); + + if( $longurl ) { + $return = array( + 'keyword' => $keyword, + 'shorturl' => YOURLS_SITE . "/$keyword", + 'longurl' => $longurl, + 'simple' => $longurl, + 'message' => 'success', + 'statusCode' => 200, + ); + } else { + $return = array( + 'keyword' => $keyword, + 'simple' => 'not found', + 'message' => 'Error: short URL not found', + 'errorCode' => 404, + ); + } + + return yourls_apply_filter( 'api_expand', $return, $shorturl ); +} diff --git a/includes/functions-auth.php b/includes/functions-auth.php index 81d728a..1332c11 100644 --- a/includes/functions-auth.php +++ b/includes/functions-auth.php @@ -1,245 +1,245 @@ -pwd. Sets user if applicable, returns bool * */ -function yourls_check_username_password() { - global $yourls_user_passwords; - if( isset( $yourls_user_passwords[ $_REQUEST['username'] ] ) && yourls_check_password_hash( $yourls_user_passwords[ $_REQUEST['username'] ], $_REQUEST['password'] ) ) { - yourls_set_user( $_REQUEST['username'] ); - return true; - } - return false; -} - +function yourls_check_username_password() { + global $yourls_user_passwords; + if( isset( $yourls_user_passwords[ $_REQUEST['username'] ] ) && yourls_check_password_hash( $yourls_user_passwords[ $_REQUEST['username'] ], $_REQUEST['password'] ) ) { + yourls_set_user( $_REQUEST['username'] ); + return true; + } + return false; +} + /** * Check a REQUEST password sent in plain text against stored password which can be a salted hash * */ -function yourls_check_password_hash( $stored, $plaintext ) { - if ( substr( $stored, 0, 4 ) == 'md5:' and strlen( $stored ) == 42 ) { - // Stored password is a salted hash: "md5:<$r = rand(10000,99999)>:" - // And 42. Of course. http://www.google.com/search?q=the+answer+to+life+the+universe+and+everything - list( $temp, $salt, $md5 ) = explode( ':', $stored ); - return( $stored == 'md5:'.$salt.':'.md5( $salt.$plaintext ) ); - } else { - // Password was sent in clear - $message = ''; - $message .= yourls__( 'Notice: your password is stored as clear text in your config.php' ); - $message .= yourls__( 'Did you know you can easily improve the security of your YOURLS install by encrypting your password?' ); - $message .= yourls__( 'See UsernamePassword for details' ); - yourls_add_notice( $message, 'notice' ); - return( $stored == $plaintext ); - } -} - - +function yourls_check_password_hash( $stored, $plaintext ) { + if ( substr( $stored, 0, 4 ) == 'md5:' and strlen( $stored ) == 42 ) { + // Stored password is a salted hash: "md5:<$r = rand(10000,99999)>:" + // And 42. Of course. http://www.google.com/search?q=the+answer+to+life+the+universe+and+everything + list( $temp, $salt, $md5 ) = explode( ':', $stored ); + return( $stored == 'md5:'.$salt.':'.md5( $salt.$plaintext ) ); + } else { + // Password was sent in clear + $message = ''; + $message .= yourls__( 'Notice: your password is stored as clear text in your config.php' ); + $message .= yourls__( 'Did you know you can easily improve the security of your YOURLS install by encrypting your password?' ); + $message .= yourls__( 'See UsernamePassword for details' ); + yourls_add_notice( $message, 'notice' ); + return( $stored == $plaintext ); + } +} + + /** * Check auth against encrypted COOKIE data. Sets user if applicable, returns bool * */ -function yourls_check_auth_cookie() { - global $yourls_user_passwords; - foreach( $yourls_user_passwords as $valid_user => $valid_password ) { - if( - yourls_salt( $valid_user ) == $_COOKIE['yourls_username'] - && yourls_salt( $valid_password ) == $_COOKIE['yourls_password'] - ) { - yourls_set_user( $valid_user ); - return true; - } - } - return false; -} - +function yourls_check_auth_cookie() { + global $yourls_user_passwords; + foreach( $yourls_user_passwords as $valid_user => $valid_password ) { + if( + yourls_salt( $valid_user ) == $_COOKIE['yourls_username'] + && yourls_salt( $valid_password ) == $_COOKIE['yourls_password'] + ) { + yourls_set_user( $valid_user ); + return true; + } + } + return false; +} + /** * Check auth against signature and timestamp. Sets user if applicable, returns bool * */ -function yourls_check_signature_timestamp() { - // Timestamp in PHP : time() - // Timestamp in JS: parseInt(new Date().getTime() / 1000) - global $yourls_user_passwords; - foreach( $yourls_user_passwords as $valid_user => $valid_password ) { - if ( - ( - md5( $_REQUEST['timestamp'].yourls_auth_signature( $valid_user ) ) == $_REQUEST['signature'] - or - md5( yourls_auth_signature( $valid_user ).$_REQUEST['timestamp'] ) == $_REQUEST['signature'] - ) - && - yourls_check_timestamp( $_REQUEST['timestamp'] ) - ) { - yourls_set_user( $valid_user ); - return true; - } - } - return false; -} - +function yourls_check_signature_timestamp() { + // Timestamp in PHP : time() + // Timestamp in JS: parseInt(new Date().getTime() / 1000) + global $yourls_user_passwords; + foreach( $yourls_user_passwords as $valid_user => $valid_password ) { + if ( + ( + md5( $_REQUEST['timestamp'].yourls_auth_signature( $valid_user ) ) == $_REQUEST['signature'] + or + md5( yourls_auth_signature( $valid_user ).$_REQUEST['timestamp'] ) == $_REQUEST['signature'] + ) + && + yourls_check_timestamp( $_REQUEST['timestamp'] ) + ) { + yourls_set_user( $valid_user ); + return true; + } + } + return false; +} + /** * Check auth against signature. Sets user if applicable, returns bool * */ -function yourls_check_signature() { - global $yourls_user_passwords; - foreach( $yourls_user_passwords as $valid_user => $valid_password ) { - if ( yourls_auth_signature( $valid_user ) == $_REQUEST['signature'] ) { - yourls_set_user( $valid_user ); - return true; - } - } - return false; -} - +function yourls_check_signature() { + global $yourls_user_passwords; + foreach( $yourls_user_passwords as $valid_user => $valid_password ) { + if ( yourls_auth_signature( $valid_user ) == $_REQUEST['signature'] ) { + yourls_set_user( $valid_user ); + return true; + } + } + return false; +} + /** * Generate secret signature hash * */ -function yourls_auth_signature( $username = false ) { - if( !$username && defined('YOURLS_USER') ) { - $username = YOURLS_USER; - } - return ( $username ? substr( yourls_salt( $username ), 0, 10 ) : 'Cannot generate auth signature: no username' ); -} - +function yourls_auth_signature( $username = false ) { + if( !$username && defined('YOURLS_USER') ) { + $username = YOURLS_USER; + } + return ( $username ? substr( yourls_salt( $username ), 0, 10 ) : 'Cannot generate auth signature: no username' ); +} + /** * Check if timestamp is not too old * */ -function yourls_check_timestamp( $time ) { - $now = time(); - // Allow timestamp to be a little in the future or the past -- see Issue 766 - return yourls_apply_filter( 'check_timestamp', abs( $now - $time ) < YOURLS_NONCE_LIFE, $time ); -} - +function yourls_check_timestamp( $time ) { + $now = time(); + // Allow timestamp to be a little in the future or the past -- see Issue 766 + return yourls_apply_filter( 'check_timestamp', abs( $now - $time ) < YOURLS_NONCE_LIFE, $time ); +} + /** * Store new cookie. No $user will delete the cookie. * */ -function yourls_store_cookie( $user = null ) { - if( !$user ) { - $pass = null; - $time = time() - 3600; - } else { - global $yourls_user_passwords; - if( isset($yourls_user_passwords[$user]) ) { - $pass = $yourls_user_passwords[$user]; - } else { - die( 'Stealing cookies?' ); // This should never happen - } - $time = time() + YOURLS_COOKIE_LIFE; - } - - $domain = yourls_apply_filter( 'setcookie_domain', parse_url( YOURLS_SITE, 1 ) ); - $secure = yourls_apply_filter( 'setcookie_secure', yourls_is_ssl() ); - $httponly = yourls_apply_filter( 'setcookie_httponly', true ); - - if ( !headers_sent() ) { - // Set httponly if the php version is >= 5.2.0 - if( version_compare( phpversion(), '5.2.0', 'ge' ) ) { - setcookie('yourls_username', yourls_salt( $user ), $time, '/', $domain, $secure, $httponly ); - setcookie('yourls_password', yourls_salt( $pass ), $time, '/', $domain, $secure, $httponly ); - } else { - setcookie('yourls_username', yourls_salt( $user ), $time, '/', $domain, $secure ); - setcookie('yourls_password', yourls_salt( $pass ), $time, '/', $domain, $secure ); - } - } -} - +function yourls_store_cookie( $user = null ) { + if( !$user ) { + $pass = null; + $time = time() - 3600; + } else { + global $yourls_user_passwords; + if( isset($yourls_user_passwords[$user]) ) { + $pass = $yourls_user_passwords[$user]; + } else { + die( 'Stealing cookies?' ); // This should never happen + } + $time = time() + YOURLS_COOKIE_LIFE; + } + + $domain = yourls_apply_filter( 'setcookie_domain', parse_url( YOURLS_SITE, 1 ) ); + $secure = yourls_apply_filter( 'setcookie_secure', yourls_is_ssl() ); + $httponly = yourls_apply_filter( 'setcookie_httponly', true ); + + if ( !headers_sent() ) { + // Set httponly if the php version is >= 5.2.0 + if( version_compare( phpversion(), '5.2.0', 'ge' ) ) { + setcookie('yourls_username', yourls_salt( $user ), $time, '/', $domain, $secure, $httponly ); + setcookie('yourls_password', yourls_salt( $pass ), $time, '/', $domain, $secure, $httponly ); + } else { + setcookie('yourls_username', yourls_salt( $user ), $time, '/', $domain, $secure ); + setcookie('yourls_password', yourls_salt( $pass ), $time, '/', $domain, $secure ); + } + } +} + /** * Set user name * */ -function yourls_set_user( $user ) { - if( !defined( 'YOURLS_USER' ) ) - define( 'YOURLS_USER', $user ); -} +function yourls_set_user( $user ) { + if( !defined( 'YOURLS_USER' ) ) + define( 'YOURLS_USER', $user ); +} diff --git a/includes/functions-compat.php b/includes/functions-compat.php index c77b40a..8bd6391 100644 --- a/includes/functions-compat.php +++ b/includes/functions-compat.php @@ -1,171 +1,171 @@ - $value ){ - - // We first copy each key/value pair into a staging array, - // formatting each key and value properly as we go. - - // Format the key: - if( is_numeric( $key ) ){ - $key = "key_$key"; - } - $key = '"'.addslashes( $key ).'"'; - - // Format the value: - if( is_array( $value )){ - $value = yourls_array_to_json( $value ); - } else if( !is_numeric( $value ) || is_string( $value ) ){ - $value = '"'.addslashes( $value ).'"'; - } - - // Add to staging array: - $construct[] = "$key: $value"; - } - - // Then we collapse the staging array into the JSON form: - $result = "{ " . implode( ", ", $construct ) . " }"; - - } else { // If the array is a vector (not associative): - - $construct = array(); - foreach( $array as $value ){ - - // Format the value: - if( is_array( $value )){ - $value = yourls_array_to_json( $value ); - } else if( !is_numeric( $value ) || is_string( $value ) ){ - $value = '"'.addslashes($value).'"'; - } - - // Add to staging array: - $construct[] = $value; - } - - // Then we collapse the staging array into the JSON form: - $result = "[ " . implode( ", ", $construct ) . " ]"; - } - - return $result; -} - -/** - * Compat http_build_query for PHP4 - * - */ -if ( !function_exists( 'http_build_query' ) ) { - function http_build_query( $data, $prefix=null, $sep=null ) { - return yourls_http_build_query( $data, $prefix, $sep ); - } -} - -/** - * Compat http_build_query for PHP4. Stolen from WP. - * - * from php.net (modified by Mark Jaquith to behave like the native PHP5 function) - * - */ -function yourls_http_build_query( $data, $prefix=null, $sep=null, $key='', $urlencode=true ) { - $ret = array(); - - foreach ( (array) $data as $k => $v ) { - if ( $urlencode) - $k = urlencode( $k ); - if ( is_int($k) && $prefix != null ) - $k = $prefix.$k; - if ( !empty( $key ) ) - $k = $key . '%5B' . $k . '%5D'; - if ( $v === NULL ) - continue; - elseif ( $v === FALSE ) - $v = '0'; - - if ( is_array( $v ) || is_object( $v ) ) - array_push( $ret,yourls_http_build_query( $v, '', $sep, $k, $urlencode ) ); - elseif ( $urlencode ) - array_push( $ret, $k.'='.urlencode( $v ) ); - else - array_push( $ret, $k.'='.$v ); - } - - if ( NULL === $sep ) - $sep = ini_get( 'arg_separator.output' ); - - return implode( $sep, $ret ); -} - -/** - * htmlspecialchars_decode for PHP < 5.1 - * - */ -if ( !function_exists( 'htmlspecialchars_decode' ) ) { - function htmlspecialchars_decode( $text ) { - return strtr( $text, array_flip( get_html_translation_table( HTML_SPECIALCHARS ) ) ); - } -} - -/** - * BC Math functions (assuming if one doesn't exist, none does) - * - */ -if ( !function_exists( 'bcdiv' ) ) { - function bcdiv( $dividend, $divisor ) { - $quotient = floor( $dividend/$divisor ); - return $quotient; - } - function bcmod( $dividend, $modulo ) { - $remainder = $dividend%$modulo; - return $remainder; - } - function bcmul( $left, $right ) { - return $left * $right; - } - function bcadd( $left, $right ) { - return $left + $right; - } - function bcpow( $base, $power ) { - return pow( $base, $power ); - } -} - -/** - * Replacement for property_exists() (5.1+) - * - */ -if ( !function_exists( 'property_exists' ) ) { - function property_exists( $class, $property ) { - if ( is_object( $class ) ) { - $vars = get_object_vars( $class ); - } else { - $vars = get_class_vars( $class ); - } - return array_key_exists( $property, $vars ); - } -} + $value ){ + + // We first copy each key/value pair into a staging array, + // formatting each key and value properly as we go. + + // Format the key: + if( is_numeric( $key ) ){ + $key = "key_$key"; + } + $key = '"'.addslashes( $key ).'"'; + + // Format the value: + if( is_array( $value )){ + $value = yourls_array_to_json( $value ); + } else if( !is_numeric( $value ) || is_string( $value ) ){ + $value = '"'.addslashes( $value ).'"'; + } + + // Add to staging array: + $construct[] = "$key: $value"; + } + + // Then we collapse the staging array into the JSON form: + $result = "{ " . implode( ", ", $construct ) . " }"; + + } else { // If the array is a vector (not associative): + + $construct = array(); + foreach( $array as $value ){ + + // Format the value: + if( is_array( $value )){ + $value = yourls_array_to_json( $value ); + } else if( !is_numeric( $value ) || is_string( $value ) ){ + $value = '"'.addslashes($value).'"'; + } + + // Add to staging array: + $construct[] = $value; + } + + // Then we collapse the staging array into the JSON form: + $result = "[ " . implode( ", ", $construct ) . " ]"; + } + + return $result; +} + +/** + * Compat http_build_query for PHP4 + * + */ +if ( !function_exists( 'http_build_query' ) ) { + function http_build_query( $data, $prefix=null, $sep=null ) { + return yourls_http_build_query( $data, $prefix, $sep ); + } +} + +/** + * Compat http_build_query for PHP4. Stolen from WP. + * + * from php.net (modified by Mark Jaquith to behave like the native PHP5 function) + * + */ +function yourls_http_build_query( $data, $prefix=null, $sep=null, $key='', $urlencode=true ) { + $ret = array(); + + foreach ( (array) $data as $k => $v ) { + if ( $urlencode) + $k = urlencode( $k ); + if ( is_int($k) && $prefix != null ) + $k = $prefix.$k; + if ( !empty( $key ) ) + $k = $key . '%5B' . $k . '%5D'; + if ( $v === NULL ) + continue; + elseif ( $v === FALSE ) + $v = '0'; + + if ( is_array( $v ) || is_object( $v ) ) + array_push( $ret,yourls_http_build_query( $v, '', $sep, $k, $urlencode ) ); + elseif ( $urlencode ) + array_push( $ret, $k.'='.urlencode( $v ) ); + else + array_push( $ret, $k.'='.$v ); + } + + if ( NULL === $sep ) + $sep = ini_get( 'arg_separator.output' ); + + return implode( $sep, $ret ); +} + +/** + * htmlspecialchars_decode for PHP < 5.1 + * + */ +if ( !function_exists( 'htmlspecialchars_decode' ) ) { + function htmlspecialchars_decode( $text ) { + return strtr( $text, array_flip( get_html_translation_table( HTML_SPECIALCHARS ) ) ); + } +} + +/** + * BC Math functions (assuming if one doesn't exist, none does) + * + */ +if ( !function_exists( 'bcdiv' ) ) { + function bcdiv( $dividend, $divisor ) { + $quotient = floor( $dividend/$divisor ); + return $quotient; + } + function bcmod( $dividend, $modulo ) { + $remainder = $dividend%$modulo; + return $remainder; + } + function bcmul( $left, $right ) { + return $left * $right; + } + function bcadd( $left, $right ) { + return $left + $right; + } + function bcpow( $base, $power ) { + return pow( $base, $power ); + } +} + +/** + * Replacement for property_exists() (5.1+) + * + */ +if ( !function_exists( 'property_exists' ) ) { + function property_exists( $class, $property ) { + if ( is_object( $class ) ) { + $vars = get_object_vars( $class ); + } else { + $vars = get_class_vars( $class ); + } + return array_key_exists( $property, $vars ); + } +} diff --git a/includes/functions-formatting.php b/includes/functions-formatting.php index 3b2c489..fb6798b 100644 --- a/includes/functions-formatting.php +++ b/includes/functions-formatting.php @@ -1,565 +1,565 @@ -= $len ) { - $mod = bcmod( $num, $len ); - $num = bcdiv( $num, $len ); - $string = $chars[ $mod ] . $string; - } - $string = $chars[ $num ] . $string; - - return yourls_apply_filter( 'int2string', $string, $num, $chars ); -} - +function yourls_int2string( $num, $chars = null ) { + if( $chars == null ) + $chars = yourls_get_shorturl_charset(); + $string = ''; + $len = strlen( $chars ); + while( $num >= $len ) { + $mod = bcmod( $num, $len ); + $num = bcdiv( $num, $len ); + $string = $chars[ $mod ] . $string; + } + $string = $chars[ $num ] . $string; + + return yourls_apply_filter( 'int2string', $string, $num, $chars ); +} + /** * Convert a string (3jk) to an integer (1337) * */ -function yourls_string2int( $string, $chars = null ) { - if( $chars == null ) - $chars = yourls_get_shorturl_charset(); - $integer = 0; - $string = strrev( $string ); - $baselen = strlen( $chars ); - $inputlen = strlen( $string ); - for ($i = 0; $i < $inputlen; $i++) { - $index = strpos( $chars, $string[$i] ); - $integer = bcadd( $integer, bcmul( $index, bcpow( $baselen, $i ) ) ); - } - - return yourls_apply_filter( 'string2int', $integer, $string, $chars ); -} - +function yourls_string2int( $string, $chars = null ) { + if( $chars == null ) + $chars = yourls_get_shorturl_charset(); + $integer = 0; + $string = strrev( $string ); + $baselen = strlen( $chars ); + $inputlen = strlen( $string ); + for ($i = 0; $i < $inputlen; $i++) { + $index = strpos( $chars, $string[$i] ); + $integer = bcadd( $integer, bcmul( $index, bcpow( $baselen, $i ) ) ); + } + + return yourls_apply_filter( 'string2int', $integer, $string, $chars ); +} + /** * Return a unique(ish) hash for a string to be used as a valid HTML id * */ -function yourls_string2htmlid( $string ) { - return yourls_apply_filter( 'string2htmlid', 'y'.abs( crc32( $string ) ) ); -} - +function yourls_string2htmlid( $string ) { + return yourls_apply_filter( 'string2htmlid', 'y'.abs( crc32( $string ) ) ); +} + /** * Make sure a link keyword (ie "1fv" as in "site.com/1fv") is valid. * */ -function yourls_sanitize_string( $string ) { - // make a regexp pattern with the shorturl charset, and remove everything but this - $pattern = yourls_make_regexp_pattern( yourls_get_shorturl_charset() ); - $valid = substr( preg_replace( '![^'.$pattern.']!', '', $string ), 0, 199 ); - - return yourls_apply_filter( 'sanitize_string', $valid, $string ); -} - +function yourls_sanitize_string( $string ) { + // make a regexp pattern with the shorturl charset, and remove everything but this + $pattern = yourls_make_regexp_pattern( yourls_get_shorturl_charset() ); + $valid = substr( preg_replace( '![^'.$pattern.']!', '', $string ), 0, 199 ); + + return yourls_apply_filter( 'sanitize_string', $valid, $string ); +} + /** * Alias function. I was always getting it wrong. * */ -function yourls_sanitize_keyword( $keyword ) { - return yourls_sanitize_string( $keyword ); -} - +function yourls_sanitize_keyword( $keyword ) { + return yourls_sanitize_string( $keyword ); +} + /** * Sanitize a page title. No HTML per W3C http://www.w3.org/TR/html401/struct/global.html#h-7.4.2 * */ -function yourls_sanitize_title( $unsafe_title ) { - $title = $unsafe_title; - $title = strip_tags( $title ); - $title = preg_replace( "/\s+/", ' ', trim( $title ) ); - return yourls_apply_filter( 'sanitize_title', $title, $unsafe_title ); -} - +function yourls_sanitize_title( $unsafe_title ) { + $title = $unsafe_title; + $title = strip_tags( $title ); + $title = preg_replace( "/\s+/", ' ', trim( $title ) ); + return yourls_apply_filter( 'sanitize_title', $title, $unsafe_title ); +} + /** * A few sanity checks on the URL. Used for redirection or DB. For display purpose, see yourls_esc_url() * */ -function yourls_sanitize_url( $unsafe_url ) { - $url = yourls_esc_url( $unsafe_url, 'redirection' ); - return yourls_apply_filter( 'sanitize_url', $url, $unsafe_url ); -} - +function yourls_sanitize_url( $unsafe_url ) { + $url = yourls_esc_url( $unsafe_url, 'redirection' ); + return yourls_apply_filter( 'sanitize_url', $url, $unsafe_url ); +} + /** - * Perform a replacement while a string is found, eg $subject = '%0%0%0DDD', $search ='%0D' -> $result ='' - * + * Perform a replacement while a string is found, eg $subject = '%0%0%0DDD', $search ='%0D' -> $result ='' + * * Stolen from WP's _deep_replace * */ -function yourls_deep_replace( $search, $subject ){ - $found = true; - while($found) { - $found = false; - foreach( (array) $search as $val ) { - while( strpos( $subject, $val ) !== false ) { - $found = true; - $subject = str_replace( $val, '', $subject ); - } - } - } - - return $subject; -} - +function yourls_deep_replace( $search, $subject ){ + $found = true; + while($found) { + $found = false; + foreach( (array) $search as $val ) { + while( strpos( $subject, $val ) !== false ) { + $found = true; + $subject = str_replace( $val, '', $subject ); + } + } + } + + return $subject; +} + /** * Make sure an integer is a valid integer (PHP's intval() limits to too small numbers) * */ -function yourls_sanitize_int( $in ) { - return ( substr( preg_replace( '/[^0-9]/', '', strval( $in ) ), 0, 20 ) ); -} - +function yourls_sanitize_int( $in ) { + return ( substr( preg_replace( '/[^0-9]/', '', strval( $in ) ), 0, 20 ) ); +} + /** - * Make sure a integer is safe - * - * Note: this is not checking for integers, since integers on 32bits system are way too limited + * Make sure a integer is safe + * + * Note: this is not checking for integers, since integers on 32bits system are way too limited * TODO: find a way to validate as integer * */ -function yourls_intval( $in ) { - return yourls_escape( $in ); -} - +function yourls_intval( $in ) { + return yourls_escape( $in ); +} + /** * Escape a string * */ -function yourls_escape( $in ) { - return mysql_real_escape_string( $in ); -} - +function yourls_escape( $in ) { + return mysql_real_escape_string( $in ); +} + /** * Sanitize an IP address * */ -function yourls_sanitize_ip( $ip ) { - return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip ); -} - +function yourls_sanitize_ip( $ip ) { + return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip ); +} + /** * Make sure a date is m(m)/d(d)/yyyy, return false otherwise * */ -function yourls_sanitize_date( $date ) { - if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) { - return false; - } - return $date; -} - +function yourls_sanitize_date( $date ) { + if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) { + return false; + } + return $date; +} + /** * Sanitize a date for SQL search. Return false if malformed input. * */ -function yourls_sanitize_date_for_sql( $date ) { - if( !yourls_sanitize_date( $date ) ) - return false; - return date( 'Y-m-d', strtotime( $date ) ); -} - +function yourls_sanitize_date_for_sql( $date ) { + if( !yourls_sanitize_date( $date ) ) + return false; + return date( 'Y-m-d', strtotime( $date ) ); +} + /** * Return word or words if more than one * */ -function yourls_plural( $word, $count=1 ) { - yourls_deprecated_function( __FUNCTION__, '1.6', 'yourls_n' ); - return $word . ($count > 1 ? 's' : ''); -} - +function yourls_plural( $word, $count=1 ) { + yourls_deprecated_function( __FUNCTION__, '1.6', 'yourls_n' ); + return $word . ($count > 1 ? 's' : ''); +} + /** * Return trimmed string * */ -function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) { - $newstring = $string; - if( function_exists( 'mb_substr' ) ) { - if ( mb_strlen( $newstring ) > $length ) { - $newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append; - } - } else { - if ( strlen( $newstring ) > $length ) { - $newstring = substr( $newstring, 0, $length - strlen( $append ) ) . $append; - } - } - return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append ); -} - +function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) { + $newstring = $string; + if( function_exists( 'mb_substr' ) ) { + if ( mb_strlen( $newstring ) > $length ) { + $newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append; + } + } else { + if ( strlen( $newstring ) > $length ) { + $newstring = substr( $newstring, 0, $length - strlen( $append ) ) . $append; + } + } + return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append ); +} + /** * Sanitize a version number (1.4.1-whatever -> 1.4.1) * */ -function yourls_sanitize_version( $ver ) { - return preg_replace( '/[^0-9.]/', '', $ver ); -} - +function yourls_sanitize_version( $ver ) { + return preg_replace( '/[^0-9.]/', '', $ver ); +} + /** * Sanitize a filename (no Win32 stuff) * */ -function yourls_sanitize_filename( $file ) { - $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs - $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash - return $file; -} - +function yourls_sanitize_filename( $file ) { + $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs + $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash + return $file; +} + /** * Check if a string seems to be UTF-8. Stolen from WP. * */ -function yourls_seems_utf8( $str ) { - $length = strlen( $str ); - for ( $i=0; $i < $length; $i++ ) { - $c = ord( $str[ $i ] ); - if ( $c < 0x80 ) $n = 0; # 0bbbbbbb - elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb - elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb - elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb - elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb - elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b - else return false; # Does not match any model - for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? - if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) - return false; - } - } - return true; -} - -/** - * Checks for invalid UTF8 in a string. Stolen from WP - * - * @since 1.6 - * - * @param string $string The text which is to be checked. - * @param boolean $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false. - * @return string The checked text. - */ -function yourls_check_invalid_utf8( $string, $strip = false ) { - $string = (string) $string; - - if ( 0 === strlen( $string ) ) { - return ''; - } - - // Check for support for utf8 in the installed PCRE library once and store the result in a static - static $utf8_pcre; - if ( !isset( $utf8_pcre ) ) { - $utf8_pcre = @preg_match( '/^./u', 'a' ); - } - // We can't demand utf8 in the PCRE installation, so just return the string in those cases - if ( !$utf8_pcre ) { - return $string; - } - - // preg_match fails when it encounters invalid UTF8 in $string - if ( 1 === @preg_match( '/^./us', $string ) ) { - return $string; - } - - // Attempt to strip the bad chars if requested (not recommended) - if ( $strip && function_exists( 'iconv' ) ) { - return iconv( 'utf-8', 'utf-8', $string ); - } - - return ''; -} - -/** - * Converts a number of special characters into their HTML entities. Stolen from WP. - * - * Specifically deals with: &, <, >, ", and '. - * - * $quote_style can be set to ENT_COMPAT to encode " to - * ", or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded. - * - * @since 1.6 - * - * @param string $string The text which is to be encoded. - * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. - * @param string $charset Optional. The character encoding of the string. Default is false. - * @param boolean $double_encode Optional. Whether to encode existing html entities. Default is false. - * @return string The encoded text with HTML entities. - */ -function yourls_specialchars( $string, $quote_style = ENT_NOQUOTES, $double_encode = false ) { - $string = (string) $string; - - if ( 0 === strlen( $string ) ) - return ''; - - // Don't bother if there are no specialchars - saves some processing - if ( ! preg_match( '/[&<>"\']/', $string ) ) - return $string; - - // Account for the previous behaviour of the function when the $quote_style is not an accepted value - if ( empty( $quote_style ) ) - $quote_style = ENT_NOQUOTES; - elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) - $quote_style = ENT_QUOTES; - - $charset = 'UTF-8'; - - $_quote_style = $quote_style; - - if ( $quote_style === 'double' ) { - $quote_style = ENT_COMPAT; - $_quote_style = ENT_COMPAT; - } elseif ( $quote_style === 'single' ) { - $quote_style = ENT_NOQUOTES; - } - - // Handle double encoding ourselves - if ( $double_encode ) { - $string = @htmlspecialchars( $string, $quote_style, $charset ); - } else { - // Decode & into & - $string = yourls_specialchars_decode( $string, $_quote_style ); - - // Guarantee every &entity; is valid or re-encode the & - $string = yourls_kses_normalize_entities( $string ); - - // Now re-encode everything except &entity; - $string = preg_split( '/(&#?x?[0-9a-z]+;)/i', $string, -1, PREG_SPLIT_DELIM_CAPTURE ); - - for ( $i = 0; $i < count( $string ); $i += 2 ) - $string[$i] = @htmlspecialchars( $string[$i], $quote_style, $charset ); - - $string = implode( '', $string ); - } - - // Backwards compatibility - if ( 'single' === $_quote_style ) - $string = str_replace( "'", ''', $string ); - - return $string; -} - -/** - * Converts a number of HTML entities into their special characters. Stolen from WP. - * - * Specifically deals with: &, <, >, ", and '. - * - * $quote_style can be set to ENT_COMPAT to decode " entities, - * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded. - * - * @since 1.6 - * - * @param string $string The text which is to be decoded. - * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old _wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. - * @return string The decoded text without HTML entities. - */ -function yourls_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) { - $string = (string) $string; - - if ( 0 === strlen( $string ) ) { - return ''; - } - - // Don't bother if there are no entities - saves a lot of processing - if ( strpos( $string, '&' ) === false ) { - return $string; - } - - // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value - if ( empty( $quote_style ) ) { - $quote_style = ENT_NOQUOTES; - } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) { - $quote_style = ENT_QUOTES; - } - - // More complete than get_html_translation_table( HTML_SPECIALCHARS ) - $single = array( ''' => '\'', ''' => '\'' ); - $single_preg = array( '/�*39;/' => ''', '/�*27;/i' => ''' ); - $double = array( '"' => '"', '"' => '"', '"' => '"' ); - $double_preg = array( '/�*34;/' => '"', '/�*22;/i' => '"' ); - $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' ); - $others_preg = array( '/�*60;/' => '<', '/�*62;/' => '>', '/�*38;/' => '&', '/�*26;/i' => '&' ); - - if ( $quote_style === ENT_QUOTES ) { - $translation = array_merge( $single, $double, $others ); - $translation_preg = array_merge( $single_preg, $double_preg, $others_preg ); - } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) { - $translation = array_merge( $double, $others ); - $translation_preg = array_merge( $double_preg, $others_preg ); - } elseif ( $quote_style === 'single' ) { - $translation = array_merge( $single, $others ); - $translation_preg = array_merge( $single_preg, $others_preg ); - } elseif ( $quote_style === ENT_NOQUOTES ) { - $translation = $others; - $translation_preg = $others_preg; - } - - // Remove zero padding on numeric entities - $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string ); - - // Replace characters according to translation table - return strtr( $string, $translation ); -} - - -/** - * Escaping for HTML blocks. Stolen from WP - * - * @since 1.6 - * - * @param string $text - * @return string - */ -function yourls_esc_html( $text ) { - $safe_text = yourls_check_invalid_utf8( $text ); - $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES ); - return yourls_apply_filters( 'esc_html', $safe_text, $text ); -} - -/** - * Escaping for HTML attributes. Stolen from WP - * - * @since 1.6 - * - * @param string $text - * @return string - */ -function yourls_esc_attr( $text ) { - $safe_text = yourls_check_invalid_utf8( $text ); - $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES ); - return yourls_apply_filters( 'esc_attr', $safe_text, $text ); -} - -/** - * Checks and cleans a URL before printing it. Stolen from WP. - * - * A number of characters are removed from the URL. If the URL is for displaying - * (the default behaviour) ampersands are also replaced. - * - * @since 1.6 - * - * @param string $url The URL to be cleaned. - * @param string $context 'display' or something else. Use yourls_sanitize_url() for database or redirection usage. - * @param array $protocols Optional. Array of allowed protocols, defaults to global $yourls_allowedprotocols - * @return string The cleaned $url - */ -function yourls_esc_url( $url, $context = 'display', $protocols = array() ) { - // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://') - $url = str_replace( - array( 'http://http://', 'http://https://' ), - array( 'http://', 'https://' ), - $url - ); - - if ( '' == $url ) - return $url; - - // make sure there's a protocol, add http:// if not - if ( ! yourls_get_protocol( $url ) ) - $url = 'http://'.$url; - - // force scheme and domain to lowercase - see issue 591 - preg_match( '!^([a-zA-Z]+://([^/]+))(.*)$!', $url, $matches ); - if( isset( $matches[1] ) && isset( $matches[3] ) ) - $url = strtolower( $matches[1] ) . $matches[3]; - - $original_url = $url; - - $url = preg_replace( '|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url ); - // Previous regexp in YOURLS was '|[^a-z0-9-~+_.?\[\]\^#=!&;,/:%@$\|*`\'<>"()\\x80-\\xff\{\}]|i' - // TODO: check if that was it too destructive - $strip = array( '%0d', '%0a', '%0D', '%0A' ); - $url = yourls_deep_replace( $strip, $url ); - $url = str_replace( ';//', '://', $url ); - - // Replace ampersands and single quotes only when displaying. - if ( 'display' == $context ) { - $url = yourls_kses_normalize_entities( $url ); - $url = str_replace( '&', '&', $url ); - $url = str_replace( "'", ''', $url ); - } - - if ( ! is_array( $protocols ) or ! $protocols ) { - global $yourls_allowedprotocols; - $protocols = yourls_apply_filter( 'esc_url_protocols', $yourls_allowedprotocols ); - // Note: $yourls_allowedprotocols is also globally filterable in functions-kses.php/yourls_kses_init() - } - - if ( !yourls_is_allowed_protocol( $url, $protocols ) ) - return ''; - - // I didn't use KSES function kses_bad_protocol() because it doesn't work the way I liked (returns //blah from illegal://blah) - - $url = substr( $url, 0, 1999 ); - - return yourls_apply_filter( 'esc_url', $url, $original_url, $context ); -} - -/** - * Escape single quotes, htmlspecialchar " < > &, and fix line endings. Stolen from WP. - * - * Escapes text strings for echoing in JS. It is intended to be used for inline JS - * (in a tag attribute, for example onclick="..."). Note that the strings have to - * be in single quotes. The filter 'js_escape' is also applied here. - * - * @since 1.6 - * - * @param string $text The text to be escaped. - * @return string Escaped text. - */ -function yourls_esc_js( $text ) { - $safe_text = yourls_check_invalid_utf8( $text ); - $safe_text = yourls_specialchars( $safe_text, ENT_COMPAT ); - $safe_text = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) ); - $safe_text = str_replace( "\r", '', $safe_text ); - $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) ); - return yourls_apply_filters( 'esc_js', $safe_text, $text ); -} - -/** - * Escaping for textarea values. Stolen from WP. - * - * @since 1.6 - * - * @param string $text - * @return string - */ -function yourls_esc_textarea( $text ) { - $safe_text = htmlspecialchars( $text, ENT_QUOTES ); - return yourls_apply_filters( 'esc_textarea', $safe_text, $text ); -} - - -/** -* PHP emulation of JS's encodeURI -* -* @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI -* @param $url -* @return string -*/ -function yourls_encodeURI( $url ) { - return strtr( rawurlencode( $url ), array ( - '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', - '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', - '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', - ) ); -} - -/** - * Adds backslashes before letters and before a number at the start of a string. Stolen from WP. - * - * @since 1.6 - * - * @param string $string Value to which backslashes will be added. - * @return string String with backslashes inserted. - */ -function yourls_backslashit($string) { - $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', $string); - $string = preg_replace('/([a-z])/i', '\\\\\1', $string); - return $string; -} - +function yourls_seems_utf8( $str ) { + $length = strlen( $str ); + for ( $i=0; $i < $length; $i++ ) { + $c = ord( $str[ $i ] ); + if ( $c < 0x80 ) $n = 0; # 0bbbbbbb + elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb + elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb + elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb + elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb + elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b + else return false; # Does not match any model + for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? + if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) + return false; + } + } + return true; +} + +/** + * Checks for invalid UTF8 in a string. Stolen from WP + * + * @since 1.6 + * + * @param string $string The text which is to be checked. + * @param boolean $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false. + * @return string The checked text. + */ +function yourls_check_invalid_utf8( $string, $strip = false ) { + $string = (string) $string; + + if ( 0 === strlen( $string ) ) { + return ''; + } + + // Check for support for utf8 in the installed PCRE library once and store the result in a static + static $utf8_pcre; + if ( !isset( $utf8_pcre ) ) { + $utf8_pcre = @preg_match( '/^./u', 'a' ); + } + // We can't demand utf8 in the PCRE installation, so just return the string in those cases + if ( !$utf8_pcre ) { + return $string; + } + + // preg_match fails when it encounters invalid UTF8 in $string + if ( 1 === @preg_match( '/^./us', $string ) ) { + return $string; + } + + // Attempt to strip the bad chars if requested (not recommended) + if ( $strip && function_exists( 'iconv' ) ) { + return iconv( 'utf-8', 'utf-8', $string ); + } + + return ''; +} + +/** + * Converts a number of special characters into their HTML entities. Stolen from WP. + * + * Specifically deals with: &, <, >, ", and '. + * + * $quote_style can be set to ENT_COMPAT to encode " to + * ", or ENT_QUOTES to do both. Default is ENT_NOQUOTES where no quotes are encoded. + * + * @since 1.6 + * + * @param string $string The text which is to be encoded. + * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. + * @param string $charset Optional. The character encoding of the string. Default is false. + * @param boolean $double_encode Optional. Whether to encode existing html entities. Default is false. + * @return string The encoded text with HTML entities. + */ +function yourls_specialchars( $string, $quote_style = ENT_NOQUOTES, $double_encode = false ) { + $string = (string) $string; + + if ( 0 === strlen( $string ) ) + return ''; + + // Don't bother if there are no specialchars - saves some processing + if ( ! preg_match( '/[&<>"\']/', $string ) ) + return $string; + + // Account for the previous behaviour of the function when the $quote_style is not an accepted value + if ( empty( $quote_style ) ) + $quote_style = ENT_NOQUOTES; + elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) + $quote_style = ENT_QUOTES; + + $charset = 'UTF-8'; + + $_quote_style = $quote_style; + + if ( $quote_style === 'double' ) { + $quote_style = ENT_COMPAT; + $_quote_style = ENT_COMPAT; + } elseif ( $quote_style === 'single' ) { + $quote_style = ENT_NOQUOTES; + } + + // Handle double encoding ourselves + if ( $double_encode ) { + $string = @htmlspecialchars( $string, $quote_style, $charset ); + } else { + // Decode & into & + $string = yourls_specialchars_decode( $string, $_quote_style ); + + // Guarantee every &entity; is valid or re-encode the & + $string = yourls_kses_normalize_entities( $string ); + + // Now re-encode everything except &entity; + $string = preg_split( '/(&#?x?[0-9a-z]+;)/i', $string, -1, PREG_SPLIT_DELIM_CAPTURE ); + + for ( $i = 0; $i < count( $string ); $i += 2 ) + $string[$i] = @htmlspecialchars( $string[$i], $quote_style, $charset ); + + $string = implode( '', $string ); + } + + // Backwards compatibility + if ( 'single' === $_quote_style ) + $string = str_replace( "'", ''', $string ); + + return $string; +} + +/** + * Converts a number of HTML entities into their special characters. Stolen from WP. + * + * Specifically deals with: &, <, >, ", and '. + * + * $quote_style can be set to ENT_COMPAT to decode " entities, + * or ENT_QUOTES to do both " and '. Default is ENT_NOQUOTES where no quotes are decoded. + * + * @since 1.6 + * + * @param string $string The text which is to be decoded. + * @param mixed $quote_style Optional. Converts double quotes if set to ENT_COMPAT, both single and double if set to ENT_QUOTES or none if set to ENT_NOQUOTES. Also compatible with old _wp_specialchars() values; converting single quotes if set to 'single', double if set to 'double' or both if otherwise set. Default is ENT_NOQUOTES. + * @return string The decoded text without HTML entities. + */ +function yourls_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) { + $string = (string) $string; + + if ( 0 === strlen( $string ) ) { + return ''; + } + + // Don't bother if there are no entities - saves a lot of processing + if ( strpos( $string, '&' ) === false ) { + return $string; + } + + // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value + if ( empty( $quote_style ) ) { + $quote_style = ENT_NOQUOTES; + } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) { + $quote_style = ENT_QUOTES; + } + + // More complete than get_html_translation_table( HTML_SPECIALCHARS ) + $single = array( ''' => '\'', ''' => '\'' ); + $single_preg = array( '/�*39;/' => ''', '/�*27;/i' => ''' ); + $double = array( '"' => '"', '"' => '"', '"' => '"' ); + $double_preg = array( '/�*34;/' => '"', '/�*22;/i' => '"' ); + $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' ); + $others_preg = array( '/�*60;/' => '<', '/�*62;/' => '>', '/�*38;/' => '&', '/�*26;/i' => '&' ); + + if ( $quote_style === ENT_QUOTES ) { + $translation = array_merge( $single, $double, $others ); + $translation_preg = array_merge( $single_preg, $double_preg, $others_preg ); + } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) { + $translation = array_merge( $double, $others ); + $translation_preg = array_merge( $double_preg, $others_preg ); + } elseif ( $quote_style === 'single' ) { + $translation = array_merge( $single, $others ); + $translation_preg = array_merge( $single_preg, $others_preg ); + } elseif ( $quote_style === ENT_NOQUOTES ) { + $translation = $others; + $translation_preg = $others_preg; + } + + // Remove zero padding on numeric entities + $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string ); + + // Replace characters according to translation table + return strtr( $string, $translation ); +} + + +/** + * Escaping for HTML blocks. Stolen from WP + * + * @since 1.6 + * + * @param string $text + * @return string + */ +function yourls_esc_html( $text ) { + $safe_text = yourls_check_invalid_utf8( $text ); + $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES ); + return yourls_apply_filters( 'esc_html', $safe_text, $text ); +} + +/** + * Escaping for HTML attributes. Stolen from WP + * + * @since 1.6 + * + * @param string $text + * @return string + */ +function yourls_esc_attr( $text ) { + $safe_text = yourls_check_invalid_utf8( $text ); + $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES ); + return yourls_apply_filters( 'esc_attr', $safe_text, $text ); +} + +/** + * Checks and cleans a URL before printing it. Stolen from WP. + * + * A number of characters are removed from the URL. If the URL is for displaying + * (the default behaviour) ampersands are also replaced. + * + * @since 1.6 + * + * @param string $url The URL to be cleaned. + * @param string $context 'display' or something else. Use yourls_sanitize_url() for database or redirection usage. + * @param array $protocols Optional. Array of allowed protocols, defaults to global $yourls_allowedprotocols + * @return string The cleaned $url + */ +function yourls_esc_url( $url, $context = 'display', $protocols = array() ) { + // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://') + $url = str_replace( + array( 'http://http://', 'http://https://' ), + array( 'http://', 'https://' ), + $url + ); + + if ( '' == $url ) + return $url; + + // make sure there's a protocol, add http:// if not + if ( ! yourls_get_protocol( $url ) ) + $url = 'http://'.$url; + + // force scheme and domain to lowercase - see issue 591 + preg_match( '!^([a-zA-Z]+://([^/]+))(.*)$!', $url, $matches ); + if( isset( $matches[1] ) && isset( $matches[3] ) ) + $url = strtolower( $matches[1] ) . $matches[3]; + + $original_url = $url; + + $url = preg_replace( '|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url ); + // Previous regexp in YOURLS was '|[^a-z0-9-~+_.?\[\]\^#=!&;,/:%@$\|*`\'<>"()\\x80-\\xff\{\}]|i' + // TODO: check if that was it too destructive + $strip = array( '%0d', '%0a', '%0D', '%0A' ); + $url = yourls_deep_replace( $strip, $url ); + $url = str_replace( ';//', '://', $url ); + + // Replace ampersands and single quotes only when displaying. + if ( 'display' == $context ) { + $url = yourls_kses_normalize_entities( $url ); + $url = str_replace( '&', '&', $url ); + $url = str_replace( "'", ''', $url ); + } + + if ( ! is_array( $protocols ) or ! $protocols ) { + global $yourls_allowedprotocols; + $protocols = yourls_apply_filter( 'esc_url_protocols', $yourls_allowedprotocols ); + // Note: $yourls_allowedprotocols is also globally filterable in functions-kses.php/yourls_kses_init() + } + + if ( !yourls_is_allowed_protocol( $url, $protocols ) ) + return ''; + + // I didn't use KSES function kses_bad_protocol() because it doesn't work the way I liked (returns //blah from illegal://blah) + + $url = substr( $url, 0, 1999 ); + + return yourls_apply_filter( 'esc_url', $url, $original_url, $context ); +} + +/** + * Escape single quotes, htmlspecialchar " < > &, and fix line endings. Stolen from WP. + * + * Escapes text strings for echoing in JS. It is intended to be used for inline JS + * (in a tag attribute, for example onclick="..."). Note that the strings have to + * be in single quotes. The filter 'js_escape' is also applied here. + * + * @since 1.6 + * + * @param string $text The text to be escaped. + * @return string Escaped text. + */ +function yourls_esc_js( $text ) { + $safe_text = yourls_check_invalid_utf8( $text ); + $safe_text = yourls_specialchars( $safe_text, ENT_COMPAT ); + $safe_text = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) ); + $safe_text = str_replace( "\r", '', $safe_text ); + $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) ); + return yourls_apply_filters( 'esc_js', $safe_text, $text ); +} + +/** + * Escaping for textarea values. Stolen from WP. + * + * @since 1.6 + * + * @param string $text + * @return string + */ +function yourls_esc_textarea( $text ) { + $safe_text = htmlspecialchars( $text, ENT_QUOTES ); + return yourls_apply_filters( 'esc_textarea', $safe_text, $text ); +} + + +/** +* PHP emulation of JS's encodeURI +* +* @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI +* @param $url +* @return string +*/ +function yourls_encodeURI( $url ) { + return strtr( rawurlencode( $url ), array ( + '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', + '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', + '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', + ) ); +} + +/** + * Adds backslashes before letters and before a number at the start of a string. Stolen from WP. + * + * @since 1.6 + * + * @param string $string Value to which backslashes will be added. + * @return string String with backslashes inserted. + */ +function yourls_backslashit($string) { + $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', $string); + $string = preg_replace('/([a-z])/i', '\\\\\1', $string); + return $string; +} + diff --git a/includes/functions-html.php b/includes/functions-html.php index 5e0e194..b12b2dc 100644 --- a/includes/functions-html.php +++ b/includes/functions-html.php @@ -1,852 +1,852 @@ - header and logo - * - */ -function yourls_html_logo() { - yourls_do_action( 'pre_html_logo' ); - ?> -

- YOURLS: Your Own URL Shortener
- YOURLS
-

- tag - * - * @param string $context Context of the page (stats, index, infos, ...) - * @param string $title HTML title of the page - */ -function yourls_html_head( $context = 'index', $title = '' ) { - - yourls_do_action( 'pre_html_head', $context, $title ); - - // All components to false, except when specified true - $share = $insert = $tablesorter = $tabs = $cal = $charts = false; - - // Load components as needed - switch ( $context ) { - case 'infos': - $share = $tabs = $charts = true; - break; - - case 'bookmark': - $share = $insert = $tablesorter = true; - break; - - case 'index': - $insert = $tablesorter = $cal = $share = true; - break; - - case 'plugins': - case 'tools': - $tablesorter = true; - break; - - case 'install': - case 'login': - case 'new': - case 'upgrade': - break; - } - - // Force no cache for all admin pages - if( yourls_is_admin() && !headers_sent() ) { - header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' ); - header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' ); - header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); - header( 'Pragma: no-cache' ); - yourls_do_action( 'admin_headers', $context, $title ); - } - - // Store page context in global object - global $ydb; - $ydb->context = $context; - - // Body class - $bodyclass = yourls_apply_filter( 'bodyclass', '' ); - $bodyclass .= ( yourls_is_mobile_device() ? 'mobile' : 'desktop' ); - - // Page title - $_title = 'YOURLS — Your Own URL Shortener | ' . yourls_link(); - $title = $title ? $title . " « " . $_title : $_title; - $title = yourls_apply_filter( 'html_title', $title, $context ); - - ?> - -> - - <?php echo $title ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- num_queries ), $ydb->num_queries ); - ?> -
- - '. $ydb->all_queries .'

'; - } ?> - context ); ?> - - - -

-
-
-
: - : : - -
-
- -
- -
- - - - -
-
-
- '; - $_options = array( - 'keyword' => yourls__( 'Short URL' ), - 'url' => yourls__( 'URL' ), - 'title' => yourls__( 'Title' ), - 'ip' => yourls__( 'IP' ), - ); - $_select = yourls_html_select( 'search_in', $_options, $search_in ); - /* //translators: "Search for in ' ); - echo "
\n"; - - // Fourth search control: Show links with more than XX clicks - $_options = array( - 'more' => yourls__( 'more' ), - 'less' => yourls__( 'less' ), - ); - $_select = yourls_html_select( 'click_filter', $_options, $click_filter ); - $_input = ' '; - /* //translators: "Show links with than clicks" */ - yourls_se( 'Show links with %1$s than %2$s clicks', $_select, $_input ); - echo "
\n"; - - // Fifth search control: Show links created before/after/between ... - $_options = array( - 'before' => yourls__('before'), - 'after' => yourls__('after'), - 'between' => yourls__('between'), - ); - $_select = yourls_html_select( 'date_filter', $_options, $date_filter ); - $_input = ''; - $_and = ' & '; - $_input2 = ''; - /* //translators: "Show links created <"and" if applicable> " */ - yourls_se( 'Show links created %1$s %2$s %3$s %4$s', $_select, $_input, $_and, $_input2 ); - ?> - -
- -   - -
- -
-
-
- - - - - - - - - 'Text displayed' - * @param string $selected optional 'value' from the $options array that will be highlighted - * @param boolean $display false (default) to return, true to echo - * @return HTML content of the select element - */ -function yourls_html_select( $name, $options, $selected = '', $display = false ) { - $html = "

-

: - -
: + - - -

- - - - - - - - - - - $title" ); - echo yourls_apply_filter( 'die_message', "

$message

" ); - yourls_do_action( 'yourls_die' ); - if( !yourls_did_action( 'html_head' ) ) { - yourls_html_footer(); - } - die(); -} - -/** - * Return an "Edit" row for the main table - * - * @param string $keyword Keyword to edit - * @return string HTML of the edit row - */ -function yourls_table_edit_row( $keyword ) { - global $ydb; - - $table = YOURLS_DB_TABLE_URL; - $keyword = yourls_sanitize_string( $keyword ); - $id = yourls_string2htmlid( $keyword ); // used as HTML #id - $url = yourls_get_keyword_longurl( $keyword ); - - $title = htmlspecialchars( yourls_get_keyword_title( $keyword ) ); - $safe_url = yourls_esc_attr( $url ); - $safe_title = yourls_esc_attr( $title ); - $www = yourls_link(); - - $save_link = yourls_nonce_url( 'save-link_'.$id, - yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit_save', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) - ); - - $nonce = yourls_create_nonce( 'edit-save_'.$id ); - - if( $url ) { - $return = <<%s:
%s: $www
%s:   -RETURN; - $return = sprintf( urldecode( $return ), yourls__( 'Long URL' ), yourls__( 'Short URL' ), yourls__( 'Title' ), yourls__( 'Save' ), yourls__( 'Save new values' ), yourls__( 'Cancel' ), yourls__( 'Cancel editing' ) ); - } else { - $return = '>' . yourls__( 'Error, URL not found' ) . ''; - } - - $return = yourls_apply_filter( 'table_edit_row', $return, $keyword, $url, $title ); - - return $return; -} - -/** - * Return an "Add" row for the main table - * - * @return string HTML of the edit row - */ -function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $timestamp ) { - $keyword = yourls_sanitize_string( $keyword ); - $id = yourls_string2htmlid( $keyword ); // used as HTML #id - $shorturl = yourls_link( $keyword ); - - $statlink = yourls_statlink( $keyword ); - - $delete_link = yourls_nonce_url( 'delete-link_'.$id, - yourls_add_query_arg( array( 'id' => $id, 'action' => 'delete', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) - ); - - $edit_link = yourls_nonce_url( 'edit-link_'.$id, - yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) - ); - - // Action link buttons: the array - $actions = array( - 'stats' => array( - 'href' => $statlink, - 'id' => "statlink-$id", - 'title' => yourls_esc_attr__( 'Stats' ), - 'anchor' => yourls__( 'Stats' ), - ), - 'share' => array( - 'href' => '', - 'id' => "share-button-$id", - 'title' => yourls_esc_attr__( 'Share' ), - 'anchor' => yourls__( 'Share' ), - 'onclick' => "toggle_share('$id');return false;", - ), - 'edit' => array( - 'href' => $edit_link, - 'id' => "edit-button-$id", - 'title' => yourls_esc_attr__( 'Edit' ), - 'anchor' => yourls__( 'Edit' ), - 'onclick' => "edit_link_display('$id');return false;", - ), - 'delete' => array( - 'href' => $delete_link, - 'id' => "delete-button-$id", - 'title' => yourls_esc_attr__( 'Delete' ), - 'anchor' => yourls__( 'Delete' ), - 'onclick' => "remove_link('$id');return false;", - ) - ); - $actions = yourls_apply_filter( 'table_add_row_action_array', $actions ); - - // Action link buttons: the HTML - $action_links = ''; - foreach( $actions as $key => $action ) { - $onclick = isset( $action['onclick'] ) ? 'onclick="' . $action['onclick'] . '"' : '' ; - $action_links .= sprintf( '%s', - $action['href'], $action['id'], $action['title'], 'button button_'.$key, $onclick, $action['anchor'] - ); - } - $action_links = yourls_apply_filter( 'action_links', $action_links, $keyword, $url, $ip, $clicks, $timestamp ); - - if( ! $title ) - $title = $url; - - $protocol_warning = ''; - if( ! in_array( yourls_get_protocol( $url ) , array( 'http://', 'https://' ) ) ) - $protocol_warning = yourls_apply_filters( 'add_row_protocol_warning', '' ); - - // Row cells: the array - $cells = array( - 'keyword' => array( - 'template' => '%keyword_html%', - 'shorturl' => yourls_esc_url( $shorturl ), - 'keyword_html' => yourls_esc_html( $keyword ), - ), - 'url' => array( - 'template' => '%title_html%
%warning%%long_url_html%', - 'long_url' => yourls_esc_url( $url ), - 'title_attr' => yourls_esc_attr( $title ), - 'title_html' => yourls_esc_html( yourls_trim_long_string( $title ) ), - 'long_url_html' => yourls_esc_html( yourls_trim_long_string( $url ) ), - 'warning' => $protocol_warning, - ), - 'timestamp' => array( - 'template' => '%date%', - 'date' => date( 'M d, Y H:i', $timestamp +( YOURLS_HOURS_OFFSET * 3600 ) ), - ), - 'ip' => array( - 'template' => '%ip%', - 'ip' => $ip, - ), - 'clicks' => array( - 'template' => '%clicks%', - 'clicks' => yourls_number_format_i18n( $clicks, 0, '', '' ), - ), - 'actions' => array( - 'template' => '%actions% ', - 'actions' => $action_links, - 'id' => $id, - 'keyword' => $keyword, - ), - ); - $cells = yourls_apply_filter( 'table_add_row_cell_array', $cells, $keyword, $url, $title, $ip, $clicks, $timestamp ); - - // Row cells: the HTML. Replace every %stuff% in 'template' with 'stuff' value. - $row = ""; - foreach( $cells as $cell_id => $elements ) { - $row .= sprintf( '', $cell_id, $cell_id . '-' . $id ); - $row .= preg_replace( '/%([^%]+)?%/e', '$elements["$1"]', $elements['template'] ); - $row .= ''; - } - $row .= ""; - $row = yourls_apply_filter( 'table_add_row', $row, $keyword, $url, $title, $ip, $clicks, $timestamp ); - - return $row; -} - -/** - * Echo the main table head - * - */ -function yourls_table_head() { - $start = ''."\n"; - echo yourls_apply_filter( 'table_head_start', $start ); - - $cells = yourls_apply_filter( 'table_head_cells', array( - 'shorturl' => yourls__( 'Short URL' ), - 'longurl' => yourls__( 'Original URL' ), - 'date' => yourls__( 'Date' ), - 'ip' => yourls__( 'IP' ), - 'clicks' => yourls__( 'Clicks' ), - 'actions' => yourls__( 'Actions' ) - ) ); - foreach( $cells as $k => $v ) { - echo "\n"; - } - - $end = "\n"; - echo yourls_apply_filter( 'table_head_end', $end ); -} - -/** - * Echo the tbody start tag - * - */ -function yourls_table_tbody_start() { - echo yourls_apply_filter( 'table_tbody_start', '' ); -} - -/** - * Echo the tbody end tag - * - */ -function yourls_table_tbody_end() { - echo yourls_apply_filter( 'table_tbody_end', '' ); -} - -/** - * Echo the table start tag - * - */ -function yourls_table_end() { - echo yourls_apply_filter( 'table_end', '
$v
' ); -} - -/** - * Echo HTML tag for a link - * - */ -function yourls_html_link( $href, $title = '', $element = '' ) { - if( !$title ) - $title = $href; - if( $element ) - $element = sprintf( 'id="%s"', yourls_esc_attr( $element ) ); - $link = sprintf( '%s', yourls_esc_url( $href ), $element, yourls_esc_html( $title ) ); - echo yourls_apply_filter( 'html_link', $link ); -} - -/** - * Display the login screen. Nothing past this point. - * - */ -function yourls_login_screen( $error_msg = '' ) { - yourls_html_head( 'login' ); - - $action = ( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ? '?' : '' ); - - yourls_html_logo(); - ?> -
-
- '.$error_msg.'

'; - } - ?> -

-
- -

-

-
- -

-

- -

-
- -
- %s'), YOURLS_USER ) . ' (' . yourls__( 'Logout' ) . ')' ); - } else { - $logout_link = yourls_apply_filter( 'logout_link', '' ); - } - $help_link = yourls_apply_filter( 'help_link', '' . yourls__( 'Help' ) . '' ); - - $admin_links = array(); - $admin_sublinks = array(); - - $admin_links['admin'] = array( - 'url' => yourls_admin_url( 'index.php' ), - 'title' => yourls__( 'Go to the admin interface' ), - 'anchor' => yourls__( 'Admin interface' ) - ); - - if( yourls_is_admin() ) { - $admin_links['tools'] = array( - 'url' => yourls_admin_url( 'tools.php' ), - 'anchor' => yourls__( 'Tools' ) - ); - $admin_links['plugins'] = array( - 'url' => yourls_admin_url( 'plugins.php' ), - 'anchor' => yourls__( 'Manage Plugins' ) - ); - $admin_sublinks['plugins'] = yourls_list_plugin_admin_pages(); - } - - $admin_links = yourls_apply_filter( 'admin_links', $admin_links ); - $admin_sublinks = yourls_apply_filter( 'admin_sublinks', $admin_sublinks ); - - // Now output menu - echo '
    '."\n"; - if ( yourls_is_private() && !empty( $logout_link ) ) - echo ''; - - foreach( (array)$admin_links as $link => $ar ) { - if( isset( $ar['url'] ) ) { - $anchor = isset( $ar['anchor'] ) ? $ar['anchor'] : $link; - $title = isset( $ar['title'] ) ? 'title="' . $ar['title'] . '"' : ''; - printf( ''; - - yourls_do_action( 'admin_menu' ); - echo "
\n"; - yourls_do_action( 'admin_notices' ); - yourls_do_action( 'admin_notice' ); // because I never remember if it's 'notices' or 'notice' - /* - To display a notice: - $message = "
OMG, dude, I mean!
" ); - yourls_add_action( 'admin_notices', create_function( '', "echo '$message';" ) ); - */ -} - -/** - * Wrapper function to display admin notices - * - */ -function yourls_add_notice( $message, $style = 'notice' ) { - $message = yourls_notice_box( $message, $style ); - yourls_add_action( 'admin_notices', create_function( '', "echo '$message';" ) ); -} - -/** - * Return a formatted notice - * - */ -function yourls_notice_box( $message, $style = 'notice' ) { - return << -

$message

- -HTML; -} - -/** - * Display a page - * - */ -function yourls_page( $page ) { - $include = YOURLS_ABSPATH . "/pages/$page.php"; - if( !file_exists($include) ) { - yourls_die( "Page '$page' not found", 'Not found', 404 ); - } - yourls_do_action( 'pre_page', $page ); - include($include); - yourls_do_action( 'post_page', $page ); - die(); -} - -/** - * Display the language attributes for the HTML tag. - * - * Builds up a set of html attributes containing the text direction and language - * information for the page. Stolen from WP. - * - * @since 1.6 - */ -function yourls_html_language_attributes() { - $attributes = array(); - $output = ''; - - $attributes[] = ( yourls_is_rtl() ? 'dir="rtl"' : 'dir="ltr"' ); - - $doctype = yourls_apply_filters( 'html_language_attributes_doctype', 'html' ); - // Experimental: get HTML lang from locale. Should work. Convert fr_FR -> fr-FR - if ( $lang = str_replace( '_', '-', yourls_get_locale() ) ) { - if( $doctype == 'xhtml' ) { - $attributes[] = "xml:lang=\"$lang\""; - } else { - $attributes[] = "lang=\"$lang\""; - } - } - - $output = implode( ' ', $attributes ); - $output = yourls_apply_filters( 'html_language_attributes', $output ); - echo $output; -} - -/** - * Output translated strings used by the Javascript calendar - * - * @since 1.6 - */ -function yourls_l10n_calendar_strings() { - echo "\n\n"; - - // Dummy returns, to initialize l10n strings used in the calendar - yourls__( 'Today' ); - yourls__( 'Close' ); + header and logo + * + */ +function yourls_html_logo() { + yourls_do_action( 'pre_html_logo' ); + ?> +

+ YOURLS: Your Own URL Shortener
+ YOURLS
+

+ tag + * + * @param string $context Context of the page (stats, index, infos, ...) + * @param string $title HTML title of the page + */ +function yourls_html_head( $context = 'index', $title = '' ) { + + yourls_do_action( 'pre_html_head', $context, $title ); + + // All components to false, except when specified true + $share = $insert = $tablesorter = $tabs = $cal = $charts = false; + + // Load components as needed + switch ( $context ) { + case 'infos': + $share = $tabs = $charts = true; + break; + + case 'bookmark': + $share = $insert = $tablesorter = true; + break; + + case 'index': + $insert = $tablesorter = $cal = $share = true; + break; + + case 'plugins': + case 'tools': + $tablesorter = true; + break; + + case 'install': + case 'login': + case 'new': + case 'upgrade': + break; + } + + // Force no cache for all admin pages + if( yourls_is_admin() && !headers_sent() ) { + header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' ); + header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' ); + header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); + header( 'Pragma: no-cache' ); + yourls_do_action( 'admin_headers', $context, $title ); + } + + // Store page context in global object + global $ydb; + $ydb->context = $context; + + // Body class + $bodyclass = yourls_apply_filter( 'bodyclass', '' ); + $bodyclass .= ( yourls_is_mobile_device() ? 'mobile' : 'desktop' ); + + // Page title + $_title = 'YOURLS — Your Own URL Shortener | ' . yourls_link(); + $title = $title ? $title . " « " . $_title : $_title; + $title = yourls_apply_filter( 'html_title', $title, $context ); + + ?> + +> + + <?php echo $title ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ num_queries ), $ydb->num_queries ); + ?> +
+ + '. $ydb->all_queries .'

'; + } ?> + context ); ?> + + + +

+
+
+
: + : : + +
+
+ +
+ +
+ + + + +
+
+
+ '; + $_options = array( + 'keyword' => yourls__( 'Short URL' ), + 'url' => yourls__( 'URL' ), + 'title' => yourls__( 'Title' ), + 'ip' => yourls__( 'IP' ), + ); + $_select = yourls_html_select( 'search_in', $_options, $search_in ); + /* //translators: "Search for in ' ); + echo "
\n"; + + // Fourth search control: Show links with more than XX clicks + $_options = array( + 'more' => yourls__( 'more' ), + 'less' => yourls__( 'less' ), + ); + $_select = yourls_html_select( 'click_filter', $_options, $click_filter ); + $_input = ' '; + /* //translators: "Show links with than clicks" */ + yourls_se( 'Show links with %1$s than %2$s clicks', $_select, $_input ); + echo "
\n"; + + // Fifth search control: Show links created before/after/between ... + $_options = array( + 'before' => yourls__('before'), + 'after' => yourls__('after'), + 'between' => yourls__('between'), + ); + $_select = yourls_html_select( 'date_filter', $_options, $date_filter ); + $_input = ''; + $_and = ' & '; + $_input2 = ''; + /* //translators: "Show links created <"and" if applicable> " */ + yourls_se( 'Show links created %1$s %2$s %3$s %4$s', $_select, $_input, $_and, $_input2 ); + ?> + +
+ +   + +
+ +
+
+
+ + + + + + + + + 'Text displayed' + * @param string $selected optional 'value' from the $options array that will be highlighted + * @param boolean $display false (default) to return, true to echo + * @return HTML content of the select element + */ +function yourls_html_select( $name, $options, $selected = '', $display = false ) { + $html = "

+

: + +
: + + + +

+ + + + + + + + + + + $title" ); + echo yourls_apply_filter( 'die_message', "

$message

" ); + yourls_do_action( 'yourls_die' ); + if( !yourls_did_action( 'html_head' ) ) { + yourls_html_footer(); + } + die(); +} + +/** + * Return an "Edit" row for the main table + * + * @param string $keyword Keyword to edit + * @return string HTML of the edit row + */ +function yourls_table_edit_row( $keyword ) { + global $ydb; + + $table = YOURLS_DB_TABLE_URL; + $keyword = yourls_sanitize_string( $keyword ); + $id = yourls_string2htmlid( $keyword ); // used as HTML #id + $url = yourls_get_keyword_longurl( $keyword ); + + $title = htmlspecialchars( yourls_get_keyword_title( $keyword ) ); + $safe_url = yourls_esc_attr( $url ); + $safe_title = yourls_esc_attr( $title ); + $www = yourls_link(); + + $save_link = yourls_nonce_url( 'save-link_'.$id, + yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit_save', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) + ); + + $nonce = yourls_create_nonce( 'edit-save_'.$id ); + + if( $url ) { + $return = <<%s:
%s: $www
%s:   +RETURN; + $return = sprintf( urldecode( $return ), yourls__( 'Long URL' ), yourls__( 'Short URL' ), yourls__( 'Title' ), yourls__( 'Save' ), yourls__( 'Save new values' ), yourls__( 'Cancel' ), yourls__( 'Cancel editing' ) ); + } else { + $return = '>' . yourls__( 'Error, URL not found' ) . ''; + } + + $return = yourls_apply_filter( 'table_edit_row', $return, $keyword, $url, $title ); + + return $return; +} + +/** + * Return an "Add" row for the main table + * + * @return string HTML of the edit row + */ +function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $timestamp ) { + $keyword = yourls_sanitize_string( $keyword ); + $id = yourls_string2htmlid( $keyword ); // used as HTML #id + $shorturl = yourls_link( $keyword ); + + $statlink = yourls_statlink( $keyword ); + + $delete_link = yourls_nonce_url( 'delete-link_'.$id, + yourls_add_query_arg( array( 'id' => $id, 'action' => 'delete', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) + ); + + $edit_link = yourls_nonce_url( 'edit-link_'.$id, + yourls_add_query_arg( array( 'id' => $id, 'action' => 'edit', 'keyword' => $keyword ), yourls_admin_url( 'admin-ajax.php' ) ) + ); + + // Action link buttons: the array + $actions = array( + 'stats' => array( + 'href' => $statlink, + 'id' => "statlink-$id", + 'title' => yourls_esc_attr__( 'Stats' ), + 'anchor' => yourls__( 'Stats' ), + ), + 'share' => array( + 'href' => '', + 'id' => "share-button-$id", + 'title' => yourls_esc_attr__( 'Share' ), + 'anchor' => yourls__( 'Share' ), + 'onclick' => "toggle_share('$id');return false;", + ), + 'edit' => array( + 'href' => $edit_link, + 'id' => "edit-button-$id", + 'title' => yourls_esc_attr__( 'Edit' ), + 'anchor' => yourls__( 'Edit' ), + 'onclick' => "edit_link_display('$id');return false;", + ), + 'delete' => array( + 'href' => $delete_link, + 'id' => "delete-button-$id", + 'title' => yourls_esc_attr__( 'Delete' ), + 'anchor' => yourls__( 'Delete' ), + 'onclick' => "remove_link('$id');return false;", + ) + ); + $actions = yourls_apply_filter( 'table_add_row_action_array', $actions ); + + // Action link buttons: the HTML + $action_links = ''; + foreach( $actions as $key => $action ) { + $onclick = isset( $action['onclick'] ) ? 'onclick="' . $action['onclick'] . '"' : '' ; + $action_links .= sprintf( '%s', + $action['href'], $action['id'], $action['title'], 'button button_'.$key, $onclick, $action['anchor'] + ); + } + $action_links = yourls_apply_filter( 'action_links', $action_links, $keyword, $url, $ip, $clicks, $timestamp ); + + if( ! $title ) + $title = $url; + + $protocol_warning = ''; + if( ! in_array( yourls_get_protocol( $url ) , array( 'http://', 'https://' ) ) ) + $protocol_warning = yourls_apply_filters( 'add_row_protocol_warning', '' ); + + // Row cells: the array + $cells = array( + 'keyword' => array( + 'template' => '%keyword_html%', + 'shorturl' => yourls_esc_url( $shorturl ), + 'keyword_html' => yourls_esc_html( $keyword ), + ), + 'url' => array( + 'template' => '%title_html%
%warning%%long_url_html%', + 'long_url' => yourls_esc_url( $url ), + 'title_attr' => yourls_esc_attr( $title ), + 'title_html' => yourls_esc_html( yourls_trim_long_string( $title ) ), + 'long_url_html' => yourls_esc_html( yourls_trim_long_string( $url ) ), + 'warning' => $protocol_warning, + ), + 'timestamp' => array( + 'template' => '%date%', + 'date' => date( 'M d, Y H:i', $timestamp +( YOURLS_HOURS_OFFSET * 3600 ) ), + ), + 'ip' => array( + 'template' => '%ip%', + 'ip' => $ip, + ), + 'clicks' => array( + 'template' => '%clicks%', + 'clicks' => yourls_number_format_i18n( $clicks, 0, '', '' ), + ), + 'actions' => array( + 'template' => '%actions% ', + 'actions' => $action_links, + 'id' => $id, + 'keyword' => $keyword, + ), + ); + $cells = yourls_apply_filter( 'table_add_row_cell_array', $cells, $keyword, $url, $title, $ip, $clicks, $timestamp ); + + // Row cells: the HTML. Replace every %stuff% in 'template' with 'stuff' value. + $row = ""; + foreach( $cells as $cell_id => $elements ) { + $row .= sprintf( '', $cell_id, $cell_id . '-' . $id ); + $row .= preg_replace( '/%([^%]+)?%/e', '$elements["$1"]', $elements['template'] ); + $row .= ''; + } + $row .= ""; + $row = yourls_apply_filter( 'table_add_row', $row, $keyword, $url, $title, $ip, $clicks, $timestamp ); + + return $row; +} + +/** + * Echo the main table head + * + */ +function yourls_table_head() { + $start = ''."\n"; + echo yourls_apply_filter( 'table_head_start', $start ); + + $cells = yourls_apply_filter( 'table_head_cells', array( + 'shorturl' => yourls__( 'Short URL' ), + 'longurl' => yourls__( 'Original URL' ), + 'date' => yourls__( 'Date' ), + 'ip' => yourls__( 'IP' ), + 'clicks' => yourls__( 'Clicks' ), + 'actions' => yourls__( 'Actions' ) + ) ); + foreach( $cells as $k => $v ) { + echo "\n"; + } + + $end = "\n"; + echo yourls_apply_filter( 'table_head_end', $end ); +} + +/** + * Echo the tbody start tag + * + */ +function yourls_table_tbody_start() { + echo yourls_apply_filter( 'table_tbody_start', '' ); +} + +/** + * Echo the tbody end tag + * + */ +function yourls_table_tbody_end() { + echo yourls_apply_filter( 'table_tbody_end', '' ); +} + +/** + * Echo the table start tag + * + */ +function yourls_table_end() { + echo yourls_apply_filter( 'table_end', '
$v
' ); +} + +/** + * Echo HTML tag for a link + * + */ +function yourls_html_link( $href, $title = '', $element = '' ) { + if( !$title ) + $title = $href; + if( $element ) + $element = sprintf( 'id="%s"', yourls_esc_attr( $element ) ); + $link = sprintf( '%s', yourls_esc_url( $href ), $element, yourls_esc_html( $title ) ); + echo yourls_apply_filter( 'html_link', $link ); +} + +/** + * Display the login screen. Nothing past this point. + * + */ +function yourls_login_screen( $error_msg = '' ) { + yourls_html_head( 'login' ); + + $action = ( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ? '?' : '' ); + + yourls_html_logo(); + ?> +
+
+ '.$error_msg.'

'; + } + ?> +

+
+ +

+

+
+ +

+

+ +

+
+ +
+ %s'), YOURLS_USER ) . ' (' . yourls__( 'Logout' ) . ')' ); + } else { + $logout_link = yourls_apply_filter( 'logout_link', '' ); + } + $help_link = yourls_apply_filter( 'help_link', '' . yourls__( 'Help' ) . '' ); + + $admin_links = array(); + $admin_sublinks = array(); + + $admin_links['admin'] = array( + 'url' => yourls_admin_url( 'index.php' ), + 'title' => yourls__( 'Go to the admin interface' ), + 'anchor' => yourls__( 'Admin interface' ) + ); + + if( yourls_is_admin() ) { + $admin_links['tools'] = array( + 'url' => yourls_admin_url( 'tools.php' ), + 'anchor' => yourls__( 'Tools' ) + ); + $admin_links['plugins'] = array( + 'url' => yourls_admin_url( 'plugins.php' ), + 'anchor' => yourls__( 'Manage Plugins' ) + ); + $admin_sublinks['plugins'] = yourls_list_plugin_admin_pages(); + } + + $admin_links = yourls_apply_filter( 'admin_links', $admin_links ); + $admin_sublinks = yourls_apply_filter( 'admin_sublinks', $admin_sublinks ); + + // Now output menu + echo '
    '."\n"; + if ( yourls_is_private() && !empty( $logout_link ) ) + echo ''; + + foreach( (array)$admin_links as $link => $ar ) { + if( isset( $ar['url'] ) ) { + $anchor = isset( $ar['anchor'] ) ? $ar['anchor'] : $link; + $title = isset( $ar['title'] ) ? 'title="' . $ar['title'] . '"' : ''; + printf( ''; + + yourls_do_action( 'admin_menu' ); + echo "
\n"; + yourls_do_action( 'admin_notices' ); + yourls_do_action( 'admin_notice' ); // because I never remember if it's 'notices' or 'notice' + /* + To display a notice: + $message = "
OMG, dude, I mean!
" ); + yourls_add_action( 'admin_notices', create_function( '', "echo '$message';" ) ); + */ +} + +/** + * Wrapper function to display admin notices + * + */ +function yourls_add_notice( $message, $style = 'notice' ) { + $message = yourls_notice_box( $message, $style ); + yourls_add_action( 'admin_notices', create_function( '', "echo '$message';" ) ); +} + +/** + * Return a formatted notice + * + */ +function yourls_notice_box( $message, $style = 'notice' ) { + return << +

$message

+ +HTML; +} + +/** + * Display a page + * + */ +function yourls_page( $page ) { + $include = YOURLS_ABSPATH . "/pages/$page.php"; + if( !file_exists($include) ) { + yourls_die( "Page '$page' not found", 'Not found', 404 ); + } + yourls_do_action( 'pre_page', $page ); + include($include); + yourls_do_action( 'post_page', $page ); + die(); +} + +/** + * Display the language attributes for the HTML tag. + * + * Builds up a set of html attributes containing the text direction and language + * information for the page. Stolen from WP. + * + * @since 1.6 + */ +function yourls_html_language_attributes() { + $attributes = array(); + $output = ''; + + $attributes[] = ( yourls_is_rtl() ? 'dir="rtl"' : 'dir="ltr"' ); + + $doctype = yourls_apply_filters( 'html_language_attributes_doctype', 'html' ); + // Experimental: get HTML lang from locale. Should work. Convert fr_FR -> fr-FR + if ( $lang = str_replace( '_', '-', yourls_get_locale() ) ) { + if( $doctype == 'xhtml' ) { + $attributes[] = "xml:lang=\"$lang\""; + } else { + $attributes[] = "lang=\"$lang\""; + } + } + + $output = implode( ' ', $attributes ); + $output = yourls_apply_filters( 'html_language_attributes', $output ); + echo $output; +} + +/** + * Output translated strings used by the Javascript calendar + * + * @since 1.6 + */ +function yourls_l10n_calendar_strings() { + echo "\n\n"; + + // Dummy returns, to initialize l10n strings used in the calendar + yourls__( 'Today' ); + yourls__( 'Close' ); } \ No newline at end of file diff --git a/includes/functions-http.php b/includes/functions-http.php index 510ac3f..3634f20 100644 --- a/includes/functions-http.php +++ b/includes/functions-http.php @@ -1,190 +1,190 @@ -fopen_error = $string;') ); - - $fp = fopen( $url, 'r'); - if( $fp !== false ) { - $buffer = min( $maxlen, 4096 ); - while ( !feof( $fp ) && !( strlen( $content ) >= $maxlen ) ) { - $content .= fread( $fp, $buffer ); - } - fclose( $fp ); - } - - if( $initial_timeout !== false ) - @ini_set( 'default_socket_timeout', $initial_timeout ); - if( $initial_user_agent !== false ) - @ini_set( 'user_agent', $initial_user_agent ); - - - restore_error_handler(); - - if( !$content ) { - //global $ydb; - //$content = 'Error: '.strip_tags( $ydb->fopen_error ); - return false; - } - - return $content; -} - +function yourls_get_remote_content_fopen( $url, $maxlen = 4096, $timeout = 5 ) { + $content = false; + + $initial_timeout = @ini_set( 'default_socket_timeout', $timeout ); + $initial_user_agent = @ini_set( 'user_agent', yourls_http_user_agent() ); + + // Basic error reporting shortcut + set_error_handler( create_function('$code, $string', 'global $ydb; $ydb->fopen_error = $string;') ); + + $fp = fopen( $url, 'r'); + if( $fp !== false ) { + $buffer = min( $maxlen, 4096 ); + while ( !feof( $fp ) && !( strlen( $content ) >= $maxlen ) ) { + $content .= fread( $fp, $buffer ); + } + fclose( $fp ); + } + + if( $initial_timeout !== false ) + @ini_set( 'default_socket_timeout', $initial_timeout ); + if( $initial_user_agent !== false ) + @ini_set( 'user_agent', $initial_user_agent ); + + + restore_error_handler(); + + if( !$content ) { + //global $ydb; + //$content = 'Error: '.strip_tags( $ydb->fopen_error ); + return false; + } + + return $content; +} + /** * Get remote content using fsockopen. Needs sanitized $url. Returns $content or false * */ -function yourls_get_remote_content_fsockopen( $url, $maxlen = 4096, $timeout = 5 ) { - // get the host name and url path - $parsed_url = parse_url( $url ); - - $host = $parsed_url['host']; - if ( isset( $parsed_url['path'] ) ) { - $path = $parsed_url['path']; - } else { - $path = '/'; // the url is pointing to the host like http://www.mysite.com - } - - if ( isset( $parsed_url['query'] ) ) { - $path .= '?' . $parsed_url['query']; - } - - if ( isset( $parsed_url['port'] ) ) { - $port = $parsed_url['port']; - } else { - $port = '80'; - } - - $response = false; - - // connect to the remote server - $fp = @fsockopen( $host, $port, $errno, $errstr, $timeout ); - var_dump( $errno, $errstr ); - if( $fp !== false ) { - // send some fake headers to mimick a standard browser - fputs($fp, "GET $path HTTP/1.0\r\n" . - "Host: $host\r\n" . - "User-Agent: " . yourls_http_user_agent() . "\r\n" . - "Accept: */*\r\n" . - "Accept-Language: en-us,en;q=0.5\r\n" . - "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" . - "Keep-Alive: 300\r\n" . - "Connection: keep-alive\r\n" . - "Referer: http://$host\r\n\r\n"); - - // retrieve the response from the remote server - $buffer = min( $maxlen, 4096 ); - while ( !feof( $fp ) && !( strlen( $response ) >= $maxlen ) ) { // get more or less $maxlen bytes (between $maxlen and ($maxlen + ($maxlen-1)) actually) - $response .= fread( $fp, $buffer ); - } - - fclose( $fp ); - } else { - //$response = trim( "Error: #$errno. $errstr" ); - return false; - } - - // return the file content - return $response; -} - +function yourls_get_remote_content_fsockopen( $url, $maxlen = 4096, $timeout = 5 ) { + // get the host name and url path + $parsed_url = parse_url( $url ); + + $host = $parsed_url['host']; + if ( isset( $parsed_url['path'] ) ) { + $path = $parsed_url['path']; + } else { + $path = '/'; // the url is pointing to the host like http://www.mysite.com + } + + if ( isset( $parsed_url['query'] ) ) { + $path .= '?' . $parsed_url['query']; + } + + if ( isset( $parsed_url['port'] ) ) { + $port = $parsed_url['port']; + } else { + $port = '80'; + } + + $response = false; + + // connect to the remote server + $fp = @fsockopen( $host, $port, $errno, $errstr, $timeout ); + var_dump( $errno, $errstr ); + if( $fp !== false ) { + // send some fake headers to mimick a standard browser + fputs($fp, "GET $path HTTP/1.0\r\n" . + "Host: $host\r\n" . + "User-Agent: " . yourls_http_user_agent() . "\r\n" . + "Accept: */*\r\n" . + "Accept-Language: en-us,en;q=0.5\r\n" . + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" . + "Keep-Alive: 300\r\n" . + "Connection: keep-alive\r\n" . + "Referer: http://$host\r\n\r\n"); + + // retrieve the response from the remote server + $buffer = min( $maxlen, 4096 ); + while ( !feof( $fp ) && !( strlen( $response ) >= $maxlen ) ) { // get more or less $maxlen bytes (between $maxlen and ($maxlen + ($maxlen-1)) actually) + $response .= fread( $fp, $buffer ); + } + + fclose( $fp ); + } else { + //$response = trim( "Error: #$errno. $errstr" ); + return false; + } + + // return the file content + return $response; +} + /** * Return funky user agent string * */ -function yourls_http_user_agent() { - return yourls_apply_filter( 'http_user_agent', 'YOURLS v'.YOURLS_VERSION.' +http://yourls.org/ (running on '.YOURLS_SITE.')' ); -} +function yourls_http_user_agent() { + return yourls_apply_filter( 'http_user_agent', 'YOURLS v'.YOURLS_VERSION.' +http://yourls.org/ (running on '.YOURLS_SITE.')' ); +} diff --git a/includes/functions-install.php b/includes/functions-install.php index 7a162d3..bca6aba 100644 --- a/includes/functions-install.php +++ b/includes/functions-install.php @@ -1,295 +1,295 @@ -mysql_version() ) <= 0 ); -} - +function yourls_check_database_version() { + global $ydb; + return ( version_compare( '4.1', $ydb->mysql_version() ) <= 0 ); +} + /** * Check if PHP > 4.3 * */ -function yourls_check_php_version() { - return ( version_compare( '4.3', phpversion() ) <= 0 ); -} - +function yourls_check_php_version() { + return ( version_compare( '4.3', phpversion() ) <= 0 ); +} + /** * Check if server is an Apache * */ -function yourls_is_apache() { - return ( - strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) !== false - || strpos( $_SERVER['SERVER_SOFTWARE'], 'LiteSpeed' ) !== false - ); -} - +function yourls_is_apache() { + return ( + strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) !== false + || strpos( $_SERVER['SERVER_SOFTWARE'], 'LiteSpeed' ) !== false + ); +} + /** * Check if server is running IIS * */ -function yourls_is_iis() { - return ( strpos( $_SERVER['SERVER_SOFTWARE'], 'IIS' ) !== false ); -} - +function yourls_is_iis() { + return ( strpos( $_SERVER['SERVER_SOFTWARE'], 'IIS' ) !== false ); +} + /** * Check if module exists in Apache config. Input string eg 'mod_rewrite', return true or $default. Stolen from WordPress * */ -function yourls_apache_mod_loaded( $mod, $default = false ) { - if ( !yourls_is_apache() ) - return false; - - if ( function_exists( 'apache_get_modules' ) ) { - $mods = apache_get_modules(); - if ( in_array( $mod, $mods ) ) - return true; - } elseif ( function_exists( 'phpinfo' ) ) { - ob_start(); - phpinfo( 8 ); - $phpinfo = ob_get_clean(); - if ( false !== strpos( $phpinfo, $mod ) ) - return true; - } - return $default; -} - +function yourls_apache_mod_loaded( $mod, $default = false ) { + if ( !yourls_is_apache() ) + return false; + + if ( function_exists( 'apache_get_modules' ) ) { + $mods = apache_get_modules(); + if ( in_array( $mod, $mods ) ) + return true; + } elseif ( function_exists( 'phpinfo' ) ) { + ob_start(); + phpinfo( 8 ); + $phpinfo = ob_get_clean(); + if ( false !== strpos( $phpinfo, $mod ) ) + return true; + } + return $default; +} + /** * Create .htaccess or web.config. Returns boolean * */ -function yourls_create_htaccess() { - $host = parse_url( YOURLS_SITE ); - $path = ( isset( $host['path'] ) ? $host['path'] : '' ); - - if ( yourls_is_iis() ) { - // Prepare content for a web.config file - $content = array( - '', - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '', - ); - - $filename = YOURLS_ABSPATH.'/web.config'; - $marker = 'none'; - - } else { - // Prepare content for a .htaccess file - $content = array( - '', - 'RewriteEngine On', - 'RewriteBase '.$path.'/', - 'RewriteCond %{REQUEST_FILENAME} !-f', - 'RewriteCond %{REQUEST_FILENAME} !-d', - 'RewriteRule ^.*$ '.$path.'/yourls-loader.php [L]', - '', - ); - - $filename = YOURLS_ABSPATH.'/.htaccess'; - $marker = 'YOURLS'; - - } - - return ( yourls_insert_with_markers( $filename, $marker, $content ) ); -} - +function yourls_create_htaccess() { + $host = parse_url( YOURLS_SITE ); + $path = ( isset( $host['path'] ) ? $host['path'] : '' ); + + if ( yourls_is_iis() ) { + // Prepare content for a web.config file + $content = array( + '', + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '', + ); + + $filename = YOURLS_ABSPATH.'/web.config'; + $marker = 'none'; + + } else { + // Prepare content for a .htaccess file + $content = array( + '', + 'RewriteEngine On', + 'RewriteBase '.$path.'/', + 'RewriteCond %{REQUEST_FILENAME} !-f', + 'RewriteCond %{REQUEST_FILENAME} !-d', + 'RewriteRule ^.*$ '.$path.'/yourls-loader.php [L]', + '', + ); + + $filename = YOURLS_ABSPATH.'/.htaccess'; + $marker = 'YOURLS'; + + } + + return ( yourls_insert_with_markers( $filename, $marker, $content ) ); +} + /** * Inserts $insertion (text in an array of lines) into $filename (.htaccess) between BEGIN/END $marker block. Returns bool. Stolen from WP * */ -function yourls_insert_with_markers( $filename, $marker, $insertion ) { - if ( !file_exists( $filename ) || is_writeable( $filename ) ) { - if ( !file_exists( $filename ) ) { - $markerdata = ''; - } else { - $markerdata = explode( "\n", implode( '', file( $filename ) ) ); - } - - if ( !$f = @fopen( $filename, 'w' ) ) - return false; - - $foundit = false; - if ( $markerdata ) { - $state = true; - foreach ( $markerdata as $n => $markerline ) { - if ( strpos( $markerline, '# BEGIN ' . $marker ) !== false ) - $state = false; - if ( $state ) { - if ( $n + 1 < count( $markerdata ) ) - fwrite( $f, "{$markerline}\n" ); - else - fwrite( $f, "{$markerline}" ); - } - if ( strpos( $markerline, '# END ' . $marker ) !== false ) { - if ( $marker != 'none' ) - fwrite( $f, "# BEGIN {$marker}\n" ); - if ( is_array( $insertion ) ) - foreach ( $insertion as $insertline ) - fwrite( $f, "{$insertline}\n" ); - if ( $marker != 'none' ) - fwrite( $f, "# END {$marker}\n" ); - $state = true; - $foundit = true; - } - } - } - if ( !$foundit ) { - if ( $marker != 'none' ) - fwrite( $f, "\n\n# BEGIN {$marker}\n" ); - foreach ( $insertion as $insertline ) - fwrite( $f, "{$insertline}\n" ); - if ( $marker != 'none' ) - fwrite( $f, "# END {$marker}\n\n" ); - } - fclose( $f ); - return true; - } else { - return false; - } -} - +function yourls_insert_with_markers( $filename, $marker, $insertion ) { + if ( !file_exists( $filename ) || is_writeable( $filename ) ) { + if ( !file_exists( $filename ) ) { + $markerdata = ''; + } else { + $markerdata = explode( "\n", implode( '', file( $filename ) ) ); + } + + if ( !$f = @fopen( $filename, 'w' ) ) + return false; + + $foundit = false; + if ( $markerdata ) { + $state = true; + foreach ( $markerdata as $n => $markerline ) { + if ( strpos( $markerline, '# BEGIN ' . $marker ) !== false ) + $state = false; + if ( $state ) { + if ( $n + 1 < count( $markerdata ) ) + fwrite( $f, "{$markerline}\n" ); + else + fwrite( $f, "{$markerline}" ); + } + if ( strpos( $markerline, '# END ' . $marker ) !== false ) { + if ( $marker != 'none' ) + fwrite( $f, "# BEGIN {$marker}\n" ); + if ( is_array( $insertion ) ) + foreach ( $insertion as $insertline ) + fwrite( $f, "{$insertline}\n" ); + if ( $marker != 'none' ) + fwrite( $f, "# END {$marker}\n" ); + $state = true; + $foundit = true; + } + } + } + if ( !$foundit ) { + if ( $marker != 'none' ) + fwrite( $f, "\n\n# BEGIN {$marker}\n" ); + foreach ( $insertion as $insertline ) + fwrite( $f, "{$insertline}\n" ); + if ( $marker != 'none' ) + fwrite( $f, "# END {$marker}\n\n" ); + } + fclose( $f ); + return true; + } else { + return false; + } +} + /** * Create MySQL tables. Return array( 'success' => array of success strings, 'errors' => array of error strings ) * */ -function yourls_create_sql_tables() { - global $ydb; - - $error_msg = array(); - $success_msg = array(); - - // Create Table Query - $create_tables = array(); - $create_tables[YOURLS_DB_TABLE_URL] = - 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_URL.'` ('. - '`keyword` varchar(200) BINARY NOT NULL,'. - '`url` text BINARY NOT NULL,'. - '`title` text CHARACTER SET utf8,'. - '`timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,'. - '`ip` VARCHAR(41) NOT NULL,'. - '`clicks` INT(10) UNSIGNED NOT NULL,'. - ' PRIMARY KEY (`keyword`),'. - ' KEY `timestamp` (`timestamp`),'. - ' KEY `ip` (`ip`)'. - ');'; - - $create_tables[YOURLS_DB_TABLE_OPTIONS] = - 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_OPTIONS.'` ('. - '`option_id` bigint(20) unsigned NOT NULL auto_increment,'. - '`option_name` varchar(64) NOT NULL default "",'. - '`option_value` longtext NOT NULL,'. - 'PRIMARY KEY (`option_id`,`option_name`),'. - 'KEY `option_name` (`option_name`)'. - ') AUTO_INCREMENT=1 ;'; - - $create_tables[YOURLS_DB_TABLE_LOG] = - 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_LOG.'` ('. - '`click_id` int(11) NOT NULL auto_increment,'. - '`click_time` datetime NOT NULL,'. - '`shorturl` varchar(200) BINARY NOT NULL,'. - '`referrer` varchar(200) NOT NULL,'. - '`user_agent` varchar(255) NOT NULL,'. - '`ip_address` varchar(41) NOT NULL,'. - '`country_code` char(2) NOT NULL,'. - 'PRIMARY KEY (`click_id`),'. - 'KEY `shorturl` (`shorturl`)'. - ') AUTO_INCREMENT=1 ;'; - - - $create_table_count = 0; - - $ydb->show_errors = true; - - // Create tables - foreach ( $create_tables as $table_name => $table_query ) { - $ydb->query( $table_query ); - $create_success = $ydb->query( "SHOW TABLES LIKE '$table_name'" ); - if( $create_success ) { - $create_table_count++; - $success_msg[] = yourls_s( "Table '%s' created.", $table_name ); - } else { - $error_msg[] = yourls_s( "Error creating table '%s'.", $table_name ); - } - } - - // Insert data into tables - yourls_update_option( 'version', YOURLS_VERSION ); - yourls_update_option( 'db_version', YOURLS_DB_VERSION ); - yourls_update_option( 'next_id', 1 ); - - // Insert sample links - yourls_insert_link_in_db( 'http://planetozh.com/blog/', 'ozhblog', 'planetOzh: Ozh\' blog' ); - yourls_insert_link_in_db( 'http://ozh.org/', 'ozh', 'ozh.org' ); - yourls_insert_link_in_db( 'http://yourls.org/', 'yourls', 'YOURLS: Your Own URL Shortener' ); - - // Check results of operations - if ( sizeof( $create_tables ) == $create_table_count ) { - $success_msg[] = yourls__( 'YOURLS tables successfully created.' ); - } else { - $error_msg[] = yourls__( 'Error creating YOURLS tables.' ); - } - - return array( 'success' => $success_msg, 'error' => $error_msg ); -} - - +function yourls_create_sql_tables() { + global $ydb; + + $error_msg = array(); + $success_msg = array(); + + // Create Table Query + $create_tables = array(); + $create_tables[YOURLS_DB_TABLE_URL] = + 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_URL.'` ('. + '`keyword` varchar(200) BINARY NOT NULL,'. + '`url` text BINARY NOT NULL,'. + '`title` text CHARACTER SET utf8,'. + '`timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,'. + '`ip` VARCHAR(41) NOT NULL,'. + '`clicks` INT(10) UNSIGNED NOT NULL,'. + ' PRIMARY KEY (`keyword`),'. + ' KEY `timestamp` (`timestamp`),'. + ' KEY `ip` (`ip`)'. + ');'; + + $create_tables[YOURLS_DB_TABLE_OPTIONS] = + 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_OPTIONS.'` ('. + '`option_id` bigint(20) unsigned NOT NULL auto_increment,'. + '`option_name` varchar(64) NOT NULL default "",'. + '`option_value` longtext NOT NULL,'. + 'PRIMARY KEY (`option_id`,`option_name`),'. + 'KEY `option_name` (`option_name`)'. + ') AUTO_INCREMENT=1 ;'; + + $create_tables[YOURLS_DB_TABLE_LOG] = + 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_LOG.'` ('. + '`click_id` int(11) NOT NULL auto_increment,'. + '`click_time` datetime NOT NULL,'. + '`shorturl` varchar(200) BINARY NOT NULL,'. + '`referrer` varchar(200) NOT NULL,'. + '`user_agent` varchar(255) NOT NULL,'. + '`ip_address` varchar(41) NOT NULL,'. + '`country_code` char(2) NOT NULL,'. + 'PRIMARY KEY (`click_id`),'. + 'KEY `shorturl` (`shorturl`)'. + ') AUTO_INCREMENT=1 ;'; + + + $create_table_count = 0; + + $ydb->show_errors = true; + + // Create tables + foreach ( $create_tables as $table_name => $table_query ) { + $ydb->query( $table_query ); + $create_success = $ydb->query( "SHOW TABLES LIKE '$table_name'" ); + if( $create_success ) { + $create_table_count++; + $success_msg[] = yourls_s( "Table '%s' created.", $table_name ); + } else { + $error_msg[] = yourls_s( "Error creating table '%s'.", $table_name ); + } + } + + // Insert data into tables + yourls_update_option( 'version', YOURLS_VERSION ); + yourls_update_option( 'db_version', YOURLS_DB_VERSION ); + yourls_update_option( 'next_id', 1 ); + + // Insert sample links + yourls_insert_link_in_db( 'http://planetozh.com/blog/', 'ozhblog', 'planetOzh: Ozh\' blog' ); + yourls_insert_link_in_db( 'http://ozh.org/', 'ozh', 'ozh.org' ); + yourls_insert_link_in_db( 'http://yourls.org/', 'yourls', 'YOURLS: Your Own URL Shortener' ); + + // Check results of operations + if ( sizeof( $create_tables ) == $create_table_count ) { + $success_msg[] = yourls__( 'YOURLS tables successfully created.' ); + } else { + $error_msg[] = yourls__( 'Error creating YOURLS tables.' ); + } + + return array( 'success' => $success_msg, 'error' => $error_msg ); +} + + /** * Toggle maintenance mode. Inspired from WP. Returns true for success, false otherwise * */ -function yourls_maintenance_mode( $maintenance = true ) { - - $file = YOURLS_ABSPATH . '/.maintenance' ; - - // Turn maintenance mode on : create .maintenance file - if ( (bool)$maintenance ) { - if ( ! ( $fp = @fopen( $file, 'w' ) ) ) - return false; - - $maintenance_string = ''; - @fwrite( $fp, $maintenance_string ); - @fclose( $fp ); - @chmod( $file, 0644 ); // Read and write for owner, read for everybody else - - // Not sure why the fwrite would fail if the fopen worked... Just in case - return( is_readable( $file ) ); - - // Turn maintenance mode off : delete the .maintenance file - } else { - return @unlink($file); - } +function yourls_maintenance_mode( $maintenance = true ) { + + $file = YOURLS_ABSPATH . '/.maintenance' ; + + // Turn maintenance mode on : create .maintenance file + if ( (bool)$maintenance ) { + if ( ! ( $fp = @fopen( $file, 'w' ) ) ) + return false; + + $maintenance_string = ''; + @fwrite( $fp, $maintenance_string ); + @fclose( $fp ); + @chmod( $file, 0644 ); // Read and write for owner, read for everybody else + + // Not sure why the fwrite would fail if the fopen worked... Just in case + return( is_readable( $file ) ); + + // Turn maintenance mode off : delete the .maintenance file + } else { + return @unlink($file); + } } \ No newline at end of file diff --git a/includes/functions-kses.php b/includes/functions-kses.php index bbb0b4e..f51123e 100644 --- a/includes/functions-kses.php +++ b/includes/functions-kses.php @@ -1,779 +1,779 @@ - - * - * @package External - * @subpackage KSES - * - */ - -/* NOTE ABOUT GLOBALS - * Two globals are defined: $yourls_allowedentitynames and $yourls_allowedprotocols - * - $yourls_allowedentitynames is used internally in KSES functions to sanitize HTML entities - * - $yourls_allowedprotocols is used in various parts of YOURLS, not just in KSES, albeit being defined here - * Two globals are not defined and unused at this moment: $yourls_allowedtags_all and $yourls_allowedtags - * The code for these vars is here and ready for any future use - */ - -// Initialize empty values in globals - populate after plugins have loaded to allow user defined values -$yourls_allowedentitynames = $yourls_allowedprotocols = array(); -yourls_add_action( 'plugins_loaded', 'yourls_kses_init' ); - -/** - * Init KSES globals if not already defined (by a plugin) - * - * @since 1.6 - * - */ -function yourls_kses_init() { - global $yourls_allowedentitynames, $yourls_allowedprotocols; - - if( ! $yourls_allowedentitynames ) { - $yourls_allowedentitynames = yourls_apply_filter( 'kses_allowed_entities', yourls_kses_allowed_entities() ); - } - - if( ! $yourls_allowedprotocols ) { - $yourls_allowedprotocols = yourls_apply_filter( 'kses_allowed_protocols', yourls_kses_allowed_protocols() ); - } - - /** See NOTE ABOUT GLOBALS ** - - if( ! $yourls_allowedtags_all ) { - $yourls_allowedtags_all = yourls_kses_allowed_tags_all(); - $yourls_allowedtags_all = array_map( '_yourls_add_global_attributes', $yourls_allowedtags_all ); - $yourls_allowedtags_all = yourls_apply_filter( 'kses_allowed_tags_all', $yourls_allowedtags_all ); - } else { - // User defined: let's sanitize - $yourls_allowedtags_all = yourls_kses_array_lc( $yourls_allowedtags_all ); - } - - if( ! $yourls_allowedtags ) { - $yourls_allowedtags = yourls_kses_allowed_tags(); - $yourls_allowedtags = array_map( '_yourls_add_global_attributes', $yourls_allowedtags ); - $yourls_allowedtags = yourls_apply_filter( 'kses_allowed_tags', $yourls_allowedtags ); - } else { - // User defined: let's sanitize - $yourls_allowedtags = yourls_kses_array_lc( $yourls_allowedtags ); - } - - /**/ -} - -/** - * Kses global for all allowable HTML tags. - * - * Complete (?) list of HTML tags. Keep this function available for any plugin or - * future feature that will want to display lots of HTML. - * - * @since 1.6 - * - * @return array All tags - */ -function yourls_kses_allowed_tags_all() { - return array( - 'address' => array(), - 'a' => array( - 'href' => true, - 'rel' => true, - 'rev' => true, - 'name' => true, - 'target' => true, - ), - 'abbr' => array(), - 'acronym' => array(), - 'area' => array( - 'alt' => true, - 'coords' => true, - 'href' => true, - 'nohref' => true, - 'shape' => true, - 'target' => true, - ), - 'article' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'aside' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'b' => array(), - 'big' => array(), - 'blockquote' => array( - 'cite' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'br' => array(), - 'button' => array( - 'disabled' => true, - 'name' => true, - 'type' => true, - 'value' => true, - ), - 'caption' => array( - 'align' => true, - ), - 'cite' => array( - 'dir' => true, - 'lang' => true, - ), - 'code' => array(), - 'col' => array( - 'align' => true, - 'char' => true, - 'charoff' => true, - 'span' => true, - 'dir' => true, - 'valign' => true, - 'width' => true, - ), - 'del' => array( - 'datetime' => true, - ), - 'dd' => array(), - 'details' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'open' => true, - 'xml:lang' => true, - ), - 'div' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'dl' => array(), - 'dt' => array(), - 'em' => array(), - 'fieldset' => array(), - 'figure' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'figcaption' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'font' => array( - 'color' => true, - 'face' => true, - 'size' => true, - ), - 'footer' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'form' => array( - 'action' => true, - 'accept' => true, - 'accept-charset' => true, - 'enctype' => true, - 'method' => true, - 'name' => true, - 'target' => true, - ), - 'h1' => array( - 'align' => true, - ), - 'h2' => array( - 'align' => true, - ), - 'h3' => array( - 'align' => true, - ), - 'h4' => array( - 'align' => true, - ), - 'h5' => array( - 'align' => true, - ), - 'h6' => array( - 'align' => true, - ), - 'header' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'hgroup' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'hr' => array( - 'align' => true, - 'noshade' => true, - 'size' => true, - 'width' => true, - ), - 'i' => array(), - 'img' => array( - 'alt' => true, - 'align' => true, - 'border' => true, - 'height' => true, - 'hspace' => true, - 'longdesc' => true, - 'vspace' => true, - 'src' => true, - 'usemap' => true, - 'width' => true, - ), - 'ins' => array( - 'datetime' => true, - 'cite' => true, - ), - 'kbd' => array(), - 'label' => array( - 'for' => true, - ), - 'legend' => array( - 'align' => true, - ), - 'li' => array( - 'align' => true, - ), - 'map' => array( - 'name' => true, - ), - 'menu' => array( - 'type' => true, - ), - 'nav' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'p' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'pre' => array( - 'width' => true, - ), - 'q' => array( - 'cite' => true, - ), - 's' => array(), - 'span' => array( - 'dir' => true, - 'align' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'section' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'small' => array(), - 'strike' => array(), - 'strong' => array(), - 'sub' => array(), - 'summary' => array( - 'align' => true, - 'dir' => true, - 'lang' => true, - 'xml:lang' => true, - ), - 'sup' => array(), - 'table' => array( - 'align' => true, - 'bgcolor' => true, - 'border' => true, - 'cellpadding' => true, - 'cellspacing' => true, - 'dir' => true, - 'rules' => true, - 'summary' => true, - 'width' => true, - ), - 'tbody' => array( - 'align' => true, - 'char' => true, - 'charoff' => true, - 'valign' => true, - ), - 'td' => array( - 'abbr' => true, - 'align' => true, - 'axis' => true, - 'bgcolor' => true, - 'char' => true, - 'charoff' => true, - 'colspan' => true, - 'dir' => true, - 'headers' => true, - 'height' => true, - 'nowrap' => true, - 'rowspan' => true, - 'scope' => true, - 'valign' => true, - 'width' => true, - ), - 'textarea' => array( - 'cols' => true, - 'rows' => true, - 'disabled' => true, - 'name' => true, - 'readonly' => true, - ), - 'tfoot' => array( - 'align' => true, - 'char' => true, - 'charoff' => true, - 'valign' => true, - ), - 'th' => array( - 'abbr' => true, - 'align' => true, - 'axis' => true, - 'bgcolor' => true, - 'char' => true, - 'charoff' => true, - 'colspan' => true, - 'headers' => true, - 'height' => true, - 'nowrap' => true, - 'rowspan' => true, - 'scope' => true, - 'valign' => true, - 'width' => true, - ), - 'thead' => array( - 'align' => true, - 'char' => true, - 'charoff' => true, - 'valign' => true, - ), - 'title' => array(), - 'tr' => array( - 'align' => true, - 'bgcolor' => true, - 'char' => true, - 'charoff' => true, - 'valign' => true, - ), - 'tt' => array(), - 'u' => array(), - 'ul' => array( - 'type' => true, - ), - 'ol' => array( - 'start' => true, - 'type' => true, - ), - 'var' => array(), - ); -} - -/** - * Kses global for default allowable HTML tags. TODO: trim down to necessary only. - * - * Short list of HTML tags used in YOURLS core for display - * - * @since 1.6 - * - * @return array Allowed tags - */ -function yourls_kses_allowed_tags() { - return array( - 'a' => array( - 'href' => true, - 'title' => true, - ), - 'abbr' => array( - 'title' => true, - ), - 'acronym' => array( - 'title' => true, - ), - 'b' => array(), - 'blockquote' => array( - 'cite' => true, - ), - 'cite' => array(), - 'code' => array(), - 'del' => array( - 'datetime' => true, - ), - 'em' => array(), - 'i' => array(), - 'q' => array( - 'cite' => true, - ), - 'strike' => array(), - 'strong' => array(), - ); -} - -/** - * Kses global for allowable HTML entities. - * - * @since 1.6 - * - * @return array Allowed entities - */ -function yourls_kses_allowed_entities() { - return array( - 'nbsp', 'iexcl', 'cent', 'pound', 'curren', 'yen', - 'brvbar', 'sect', 'uml', 'copy', 'ordf', 'laquo', - 'not', 'shy', 'reg', 'macr', 'deg', 'plusmn', - 'acute', 'micro', 'para', 'middot', 'cedil', 'ordm', - 'raquo', 'iquest', 'Agrave', 'Aacute', 'Acirc', 'Atilde', - 'Auml', 'Aring', 'AElig', 'Ccedil', 'Egrave', 'Eacute', - 'Ecirc', 'Euml', 'Igrave', 'Iacute', 'Icirc', 'Iuml', - 'ETH', 'Ntilde', 'Ograve', 'Oacute', 'Ocirc', 'Otilde', - 'Ouml', 'times', 'Oslash', 'Ugrave', 'Uacute', 'Ucirc', - 'Uuml', 'Yacute', 'THORN', 'szlig', 'agrave', 'aacute', - 'acirc', 'atilde', 'auml', 'aring', 'aelig', 'ccedil', - 'egrave', 'eacute', 'ecirc', 'euml', 'igrave', 'iacute', - 'icirc', 'iuml', 'eth', 'ntilde', 'ograve', 'oacute', - 'ocirc', 'otilde', 'ouml', 'divide', 'oslash', 'ugrave', - 'uacute', 'ucirc', 'uuml', 'yacute', 'thorn', 'yuml', - 'quot', 'amp', 'lt', 'gt', 'apos', 'OElig', - 'oelig', 'Scaron', 'scaron', 'Yuml', 'circ', 'tilde', - 'ensp', 'emsp', 'thinsp', 'zwnj', 'zwj', 'lrm', - 'rlm', 'ndash', 'mdash', 'lsquo', 'rsquo', 'sbquo', - 'ldquo', 'rdquo', 'bdquo', 'dagger', 'Dagger', 'permil', - 'lsaquo', 'rsaquo', 'euro', 'fnof', 'Alpha', 'Beta', - 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta', - 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', - 'Omicron', 'Pi', 'Rho', 'Sigma', 'Tau', 'Upsilon', - 'Phi', 'Chi', 'Psi', 'Omega', 'alpha', 'beta', - 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', - 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', - 'omicron', 'pi', 'rho', 'sigmaf', 'sigma', 'tau', - 'upsilon', 'phi', 'chi', 'psi', 'omega', 'thetasym', - 'upsih', 'piv', 'bull', 'hellip', 'prime', 'Prime', - 'oline', 'frasl', 'weierp', 'image', 'real', 'trade', - 'alefsym', 'larr', 'uarr', 'rarr', 'darr', 'harr', - 'crarr', 'lArr', 'uArr', 'rArr', 'dArr', 'hArr', - 'forall', 'part', 'exist', 'empty', 'nabla', 'isin', - 'notin', 'ni', 'prod', 'sum', 'minus', 'lowast', - 'radic', 'prop', 'infin', 'ang', 'and', 'or', - 'cap', 'cup', 'int', 'sim', 'cong', 'asymp', - 'ne', 'equiv', 'le', 'ge', 'sub', 'sup', - 'nsub', 'sube', 'supe', 'oplus', 'otimes', 'perp', - 'sdot', 'lceil', 'rceil', 'lfloor', 'rfloor', 'lang', - 'rang', 'loz', 'spades', 'clubs', 'hearts', 'diams', - ); -} - -/** - * Kses global for allowable protocols. - * - * @since 1.6 - * - * @return array Allowed protocols - */ -function yourls_kses_allowed_protocols() { - // More or less common stuff in links. From http://en.wikipedia.org/wiki/URI_scheme - return array( - // Common - 'http://', 'https://', 'ftp://', - 'file://', 'smb://', - 'sftp://', - 'feed:', 'feed://', - 'mailto:', - 'news:', 'nntp://', - - // Old school bearded geek - 'gopher://', 'telnet://', 'finger://', - 'nntp://', 'worldwind://', - - // Dev - 'ssh://', 'svn://', 'svn+ssh://', 'git://', 'cvs://', - 'apt:', - 'market://', // Google Play - 'data:', - 'view-source:', - - // P2P - 'ed2k://', 'magnet:', 'udp://', - - // Streaming stuff - 'mms://', 'lastfm://', 'spotify:', 'rtsp://', - - // Text & voice - 'aim:', 'facetime://', 'gtalk:', 'xmpp:', - 'irc://', 'ircs://', 'mumble://', - 'callto:', 'skype:', 'sip:', - 'teamspeak://', 'ventrilo://', 'xfire:', - 'ymsgr:', - - // Misc - 'steam:', 'steam://', - 'bitcoin:', - 'ldap://', 'ldaps://', - - // Purposedly removed for security - /* - 'about:', 'chrome://', 'chrome-extension://', - 'javascript:', - */ - ); -} - - -/** - * Converts and fixes HTML entities. - * - * This function normalizes HTML entities. It will convert "AT&T" to the correct - * "AT&T", ":" to ":", "&#XYZZY;" to "&#XYZZY;" and so on. - * - * @since 1.6 - * - * @param string $string Content to normalize entities - * @return string Content with normalized entities - */ -function yourls_kses_normalize_entities($string) { - # Disarm all entities by converting & to & - - $string = str_replace('&', '&', $string); - - # Change back the allowed entities in our entity whitelist - - $string = preg_replace_callback('/&([A-Za-z]{2,8});/', 'yourls_kses_named_entities', $string); - $string = preg_replace_callback('/&#(0*[0-9]{1,7});/', 'yourls_kses_normalize_entities2', $string); - $string = preg_replace_callback('/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'yourls_kses_normalize_entities3', $string); - - return $string; -} - -/** - * Callback for yourls_kses_normalize_entities() regular expression. - * - * This function only accepts valid named entity references, which are finite, - * case-sensitive, and highly scrutinized by HTML and XML validators. - * - * @since 1.6 - * - * @param array $matches preg_replace_callback() matches array - * @return string Correctly encoded entity - */ -function yourls_kses_named_entities($matches) { - global $yourls_allowedentitynames; - - if ( empty($matches[1]) ) - return ''; - - $i = $matches[1]; - return ( ( ! in_array($i, $yourls_allowedentitynames) ) ? "&$i;" : "&$i;" ); -} - -/** - * Callback for yourls_kses_normalize_entities() regular expression. - * - * This function helps yourls_kses_normalize_entities() to only accept 16-bit values - * and nothing more for &#number; entities. - * - * @access private - * @since 1.6 - * - * @param array $matches preg_replace_callback() matches array - * @return string Correctly encoded entity - */ -function yourls_kses_normalize_entities2($matches) { - if ( empty($matches[1]) ) - return ''; - - $i = $matches[1]; - if (yourls_valid_unicode($i)) { - $i = str_pad(ltrim($i,'0'), 3, '0', STR_PAD_LEFT); - $i = "&#$i;"; - } else { - $i = "&#$i;"; - } - - return $i; -} - -/** - * Callback for yourls_kses_normalize_entities() for regular expression. - * - * This function helps yourls_kses_normalize_entities() to only accept valid Unicode - * numeric entities in hex form. - * - * @access private - * @since 1.6 - * - * @param array $matches preg_replace_callback() matches array - * @return string Correctly encoded entity - */ -function yourls_kses_normalize_entities3($matches) { - if ( empty($matches[1]) ) - return ''; - - $hexchars = $matches[1]; - return ( ( ! yourls_valid_unicode(hexdec($hexchars)) ) ? "&#x$hexchars;" : '&#x'.ltrim($hexchars,'0').';' ); -} - -/** - * Helper function to add global attributes to a tag in the allowed html list. - * - * @since 1.6 - * @access private - * - * @param array $value An array of attributes. - * @return array The array of attributes with global attributes added. - */ -function _yourls_add_global_attributes( $value ) { - $global_attributes = array( - 'class' => true, - 'id' => true, - 'style' => true, - 'title' => true, - ); - - if ( true === $value ) - $value = array(); - - if ( is_array( $value ) ) - return array_merge( $value, $global_attributes ); - - return $value; -} - -/** - * Helper function to determine if a Unicode value is valid. - * - * @since 1.6 - * - * @param int $i Unicode value - * @return bool True if the value was a valid Unicode number - */ -function yourls_valid_unicode($i) { - return ( $i == 0x9 || $i == 0xa || $i == 0xd || - ($i >= 0x20 && $i <= 0xd7ff) || - ($i >= 0xe000 && $i <= 0xfffd) || - ($i >= 0x10000 && $i <= 0x10ffff) ); -} - -/** - * Goes through an array and changes the keys to all lower case. - * - * @since 1.6 - * - * @param array $inarray Unfiltered array - * @return array Fixed array with all lowercase keys - */ -function yourls_kses_array_lc($inarray) { - $outarray = array (); - - foreach ( (array) $inarray as $inkey => $inval) { - $outkey = strtolower($inkey); - $outarray[$outkey] = array (); - - foreach ( (array) $inval as $inkey2 => $inval2) { - $outkey2 = strtolower($inkey2); - $outarray[$outkey][$outkey2] = $inval2; - } # foreach $inval - } # foreach $inarray - - return $outarray; -} - -/** - * Convert all entities to their character counterparts. - * - * This function decodes numeric HTML entities (A and A). It doesn't do - * anything with other entities like ä, but we don't need them in the URL - * protocol whitelisting system anyway. - * - * @since 1.6 - * - * @param string $string Content to change entities - * @return string Content after decoded entities - */ -function yourls_kses_decode_entities($string) { - $string = preg_replace_callback('/&#([0-9]+);/', '_yourls_kses_decode_entities_chr', $string); - $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', '_yourls_kses_decode_entities_chr_hexdec', $string); - - return $string; -} - -/** - * Regex callback for yourls_kses_decode_entities() - * - * @since 1.6 - * - * @param array $match preg match - * @return string - */ -function _yourls_kses_decode_entities_chr( $match ) { - return chr( $match[1] ); -} - -/** - * Regex callback for yourls_kses_decode_entities() - * - * @since 1.6 - * - * @param array $match preg match - * @return string - */ -function _yourls_kses_decode_entities_chr_hexdec( $match ) { - return chr( hexdec( $match[1] ) ); -} - -/** - * Removes any null characters in $string. - * - * @since 1.6 - * - * @param string $string - * @return string - */ -function yourls_kses_no_null($string) { - $string = preg_replace( '/\0+/', '', $string ); - $string = preg_replace( '/(\\\\0)+/', '', $string ); - - return $string; -} + + * + * @package External + * @subpackage KSES + * + */ + +/* NOTE ABOUT GLOBALS + * Two globals are defined: $yourls_allowedentitynames and $yourls_allowedprotocols + * - $yourls_allowedentitynames is used internally in KSES functions to sanitize HTML entities + * - $yourls_allowedprotocols is used in various parts of YOURLS, not just in KSES, albeit being defined here + * Two globals are not defined and unused at this moment: $yourls_allowedtags_all and $yourls_allowedtags + * The code for these vars is here and ready for any future use + */ + +// Initialize empty values in globals - populate after plugins have loaded to allow user defined values +$yourls_allowedentitynames = $yourls_allowedprotocols = array(); +yourls_add_action( 'plugins_loaded', 'yourls_kses_init' ); + +/** + * Init KSES globals if not already defined (by a plugin) + * + * @since 1.6 + * + */ +function yourls_kses_init() { + global $yourls_allowedentitynames, $yourls_allowedprotocols; + + if( ! $yourls_allowedentitynames ) { + $yourls_allowedentitynames = yourls_apply_filter( 'kses_allowed_entities', yourls_kses_allowed_entities() ); + } + + if( ! $yourls_allowedprotocols ) { + $yourls_allowedprotocols = yourls_apply_filter( 'kses_allowed_protocols', yourls_kses_allowed_protocols() ); + } + + /** See NOTE ABOUT GLOBALS ** + + if( ! $yourls_allowedtags_all ) { + $yourls_allowedtags_all = yourls_kses_allowed_tags_all(); + $yourls_allowedtags_all = array_map( '_yourls_add_global_attributes', $yourls_allowedtags_all ); + $yourls_allowedtags_all = yourls_apply_filter( 'kses_allowed_tags_all', $yourls_allowedtags_all ); + } else { + // User defined: let's sanitize + $yourls_allowedtags_all = yourls_kses_array_lc( $yourls_allowedtags_all ); + } + + if( ! $yourls_allowedtags ) { + $yourls_allowedtags = yourls_kses_allowed_tags(); + $yourls_allowedtags = array_map( '_yourls_add_global_attributes', $yourls_allowedtags ); + $yourls_allowedtags = yourls_apply_filter( 'kses_allowed_tags', $yourls_allowedtags ); + } else { + // User defined: let's sanitize + $yourls_allowedtags = yourls_kses_array_lc( $yourls_allowedtags ); + } + + /**/ +} + +/** + * Kses global for all allowable HTML tags. + * + * Complete (?) list of HTML tags. Keep this function available for any plugin or + * future feature that will want to display lots of HTML. + * + * @since 1.6 + * + * @return array All tags + */ +function yourls_kses_allowed_tags_all() { + return array( + 'address' => array(), + 'a' => array( + 'href' => true, + 'rel' => true, + 'rev' => true, + 'name' => true, + 'target' => true, + ), + 'abbr' => array(), + 'acronym' => array(), + 'area' => array( + 'alt' => true, + 'coords' => true, + 'href' => true, + 'nohref' => true, + 'shape' => true, + 'target' => true, + ), + 'article' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'aside' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'b' => array(), + 'big' => array(), + 'blockquote' => array( + 'cite' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'br' => array(), + 'button' => array( + 'disabled' => true, + 'name' => true, + 'type' => true, + 'value' => true, + ), + 'caption' => array( + 'align' => true, + ), + 'cite' => array( + 'dir' => true, + 'lang' => true, + ), + 'code' => array(), + 'col' => array( + 'align' => true, + 'char' => true, + 'charoff' => true, + 'span' => true, + 'dir' => true, + 'valign' => true, + 'width' => true, + ), + 'del' => array( + 'datetime' => true, + ), + 'dd' => array(), + 'details' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'open' => true, + 'xml:lang' => true, + ), + 'div' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'dl' => array(), + 'dt' => array(), + 'em' => array(), + 'fieldset' => array(), + 'figure' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'figcaption' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'font' => array( + 'color' => true, + 'face' => true, + 'size' => true, + ), + 'footer' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'form' => array( + 'action' => true, + 'accept' => true, + 'accept-charset' => true, + 'enctype' => true, + 'method' => true, + 'name' => true, + 'target' => true, + ), + 'h1' => array( + 'align' => true, + ), + 'h2' => array( + 'align' => true, + ), + 'h3' => array( + 'align' => true, + ), + 'h4' => array( + 'align' => true, + ), + 'h5' => array( + 'align' => true, + ), + 'h6' => array( + 'align' => true, + ), + 'header' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'hgroup' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'hr' => array( + 'align' => true, + 'noshade' => true, + 'size' => true, + 'width' => true, + ), + 'i' => array(), + 'img' => array( + 'alt' => true, + 'align' => true, + 'border' => true, + 'height' => true, + 'hspace' => true, + 'longdesc' => true, + 'vspace' => true, + 'src' => true, + 'usemap' => true, + 'width' => true, + ), + 'ins' => array( + 'datetime' => true, + 'cite' => true, + ), + 'kbd' => array(), + 'label' => array( + 'for' => true, + ), + 'legend' => array( + 'align' => true, + ), + 'li' => array( + 'align' => true, + ), + 'map' => array( + 'name' => true, + ), + 'menu' => array( + 'type' => true, + ), + 'nav' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'p' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'pre' => array( + 'width' => true, + ), + 'q' => array( + 'cite' => true, + ), + 's' => array(), + 'span' => array( + 'dir' => true, + 'align' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'section' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'small' => array(), + 'strike' => array(), + 'strong' => array(), + 'sub' => array(), + 'summary' => array( + 'align' => true, + 'dir' => true, + 'lang' => true, + 'xml:lang' => true, + ), + 'sup' => array(), + 'table' => array( + 'align' => true, + 'bgcolor' => true, + 'border' => true, + 'cellpadding' => true, + 'cellspacing' => true, + 'dir' => true, + 'rules' => true, + 'summary' => true, + 'width' => true, + ), + 'tbody' => array( + 'align' => true, + 'char' => true, + 'charoff' => true, + 'valign' => true, + ), + 'td' => array( + 'abbr' => true, + 'align' => true, + 'axis' => true, + 'bgcolor' => true, + 'char' => true, + 'charoff' => true, + 'colspan' => true, + 'dir' => true, + 'headers' => true, + 'height' => true, + 'nowrap' => true, + 'rowspan' => true, + 'scope' => true, + 'valign' => true, + 'width' => true, + ), + 'textarea' => array( + 'cols' => true, + 'rows' => true, + 'disabled' => true, + 'name' => true, + 'readonly' => true, + ), + 'tfoot' => array( + 'align' => true, + 'char' => true, + 'charoff' => true, + 'valign' => true, + ), + 'th' => array( + 'abbr' => true, + 'align' => true, + 'axis' => true, + 'bgcolor' => true, + 'char' => true, + 'charoff' => true, + 'colspan' => true, + 'headers' => true, + 'height' => true, + 'nowrap' => true, + 'rowspan' => true, + 'scope' => true, + 'valign' => true, + 'width' => true, + ), + 'thead' => array( + 'align' => true, + 'char' => true, + 'charoff' => true, + 'valign' => true, + ), + 'title' => array(), + 'tr' => array( + 'align' => true, + 'bgcolor' => true, + 'char' => true, + 'charoff' => true, + 'valign' => true, + ), + 'tt' => array(), + 'u' => array(), + 'ul' => array( + 'type' => true, + ), + 'ol' => array( + 'start' => true, + 'type' => true, + ), + 'var' => array(), + ); +} + +/** + * Kses global for default allowable HTML tags. TODO: trim down to necessary only. + * + * Short list of HTML tags used in YOURLS core for display + * + * @since 1.6 + * + * @return array Allowed tags + */ +function yourls_kses_allowed_tags() { + return array( + 'a' => array( + 'href' => true, + 'title' => true, + ), + 'abbr' => array( + 'title' => true, + ), + 'acronym' => array( + 'title' => true, + ), + 'b' => array(), + 'blockquote' => array( + 'cite' => true, + ), + 'cite' => array(), + 'code' => array(), + 'del' => array( + 'datetime' => true, + ), + 'em' => array(), + 'i' => array(), + 'q' => array( + 'cite' => true, + ), + 'strike' => array(), + 'strong' => array(), + ); +} + +/** + * Kses global for allowable HTML entities. + * + * @since 1.6 + * + * @return array Allowed entities + */ +function yourls_kses_allowed_entities() { + return array( + 'nbsp', 'iexcl', 'cent', 'pound', 'curren', 'yen', + 'brvbar', 'sect', 'uml', 'copy', 'ordf', 'laquo', + 'not', 'shy', 'reg', 'macr', 'deg', 'plusmn', + 'acute', 'micro', 'para', 'middot', 'cedil', 'ordm', + 'raquo', 'iquest', 'Agrave', 'Aacute', 'Acirc', 'Atilde', + 'Auml', 'Aring', 'AElig', 'Ccedil', 'Egrave', 'Eacute', + 'Ecirc', 'Euml', 'Igrave', 'Iacute', 'Icirc', 'Iuml', + 'ETH', 'Ntilde', 'Ograve', 'Oacute', 'Ocirc', 'Otilde', + 'Ouml', 'times', 'Oslash', 'Ugrave', 'Uacute', 'Ucirc', + 'Uuml', 'Yacute', 'THORN', 'szlig', 'agrave', 'aacute', + 'acirc', 'atilde', 'auml', 'aring', 'aelig', 'ccedil', + 'egrave', 'eacute', 'ecirc', 'euml', 'igrave', 'iacute', + 'icirc', 'iuml', 'eth', 'ntilde', 'ograve', 'oacute', + 'ocirc', 'otilde', 'ouml', 'divide', 'oslash', 'ugrave', + 'uacute', 'ucirc', 'uuml', 'yacute', 'thorn', 'yuml', + 'quot', 'amp', 'lt', 'gt', 'apos', 'OElig', + 'oelig', 'Scaron', 'scaron', 'Yuml', 'circ', 'tilde', + 'ensp', 'emsp', 'thinsp', 'zwnj', 'zwj', 'lrm', + 'rlm', 'ndash', 'mdash', 'lsquo', 'rsquo', 'sbquo', + 'ldquo', 'rdquo', 'bdquo', 'dagger', 'Dagger', 'permil', + 'lsaquo', 'rsaquo', 'euro', 'fnof', 'Alpha', 'Beta', + 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta', + 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', + 'Omicron', 'Pi', 'Rho', 'Sigma', 'Tau', 'Upsilon', + 'Phi', 'Chi', 'Psi', 'Omega', 'alpha', 'beta', + 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta', + 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', + 'omicron', 'pi', 'rho', 'sigmaf', 'sigma', 'tau', + 'upsilon', 'phi', 'chi', 'psi', 'omega', 'thetasym', + 'upsih', 'piv', 'bull', 'hellip', 'prime', 'Prime', + 'oline', 'frasl', 'weierp', 'image', 'real', 'trade', + 'alefsym', 'larr', 'uarr', 'rarr', 'darr', 'harr', + 'crarr', 'lArr', 'uArr', 'rArr', 'dArr', 'hArr', + 'forall', 'part', 'exist', 'empty', 'nabla', 'isin', + 'notin', 'ni', 'prod', 'sum', 'minus', 'lowast', + 'radic', 'prop', 'infin', 'ang', 'and', 'or', + 'cap', 'cup', 'int', 'sim', 'cong', 'asymp', + 'ne', 'equiv', 'le', 'ge', 'sub', 'sup', + 'nsub', 'sube', 'supe', 'oplus', 'otimes', 'perp', + 'sdot', 'lceil', 'rceil', 'lfloor', 'rfloor', 'lang', + 'rang', 'loz', 'spades', 'clubs', 'hearts', 'diams', + ); +} + +/** + * Kses global for allowable protocols. + * + * @since 1.6 + * + * @return array Allowed protocols + */ +function yourls_kses_allowed_protocols() { + // More or less common stuff in links. From http://en.wikipedia.org/wiki/URI_scheme + return array( + // Common + 'http://', 'https://', 'ftp://', + 'file://', 'smb://', + 'sftp://', + 'feed:', 'feed://', + 'mailto:', + 'news:', 'nntp://', + + // Old school bearded geek + 'gopher://', 'telnet://', 'finger://', + 'nntp://', 'worldwind://', + + // Dev + 'ssh://', 'svn://', 'svn+ssh://', 'git://', 'cvs://', + 'apt:', + 'market://', // Google Play + 'data:', + 'view-source:', + + // P2P + 'ed2k://', 'magnet:', 'udp://', + + // Streaming stuff + 'mms://', 'lastfm://', 'spotify:', 'rtsp://', + + // Text & voice + 'aim:', 'facetime://', 'gtalk:', 'xmpp:', + 'irc://', 'ircs://', 'mumble://', + 'callto:', 'skype:', 'sip:', + 'teamspeak://', 'ventrilo://', 'xfire:', + 'ymsgr:', + + // Misc + 'steam:', 'steam://', + 'bitcoin:', + 'ldap://', 'ldaps://', + + // Purposedly removed for security + /* + 'about:', 'chrome://', 'chrome-extension://', + 'javascript:', + */ + ); +} + + +/** + * Converts and fixes HTML entities. + * + * This function normalizes HTML entities. It will convert "AT&T" to the correct + * "AT&T", ":" to ":", "&#XYZZY;" to "&#XYZZY;" and so on. + * + * @since 1.6 + * + * @param string $string Content to normalize entities + * @return string Content with normalized entities + */ +function yourls_kses_normalize_entities($string) { + # Disarm all entities by converting & to & + + $string = str_replace('&', '&', $string); + + # Change back the allowed entities in our entity whitelist + + $string = preg_replace_callback('/&([A-Za-z]{2,8});/', 'yourls_kses_named_entities', $string); + $string = preg_replace_callback('/&#(0*[0-9]{1,7});/', 'yourls_kses_normalize_entities2', $string); + $string = preg_replace_callback('/&#[Xx](0*[0-9A-Fa-f]{1,6});/', 'yourls_kses_normalize_entities3', $string); + + return $string; +} + +/** + * Callback for yourls_kses_normalize_entities() regular expression. + * + * This function only accepts valid named entity references, which are finite, + * case-sensitive, and highly scrutinized by HTML and XML validators. + * + * @since 1.6 + * + * @param array $matches preg_replace_callback() matches array + * @return string Correctly encoded entity + */ +function yourls_kses_named_entities($matches) { + global $yourls_allowedentitynames; + + if ( empty($matches[1]) ) + return ''; + + $i = $matches[1]; + return ( ( ! in_array($i, $yourls_allowedentitynames) ) ? "&$i;" : "&$i;" ); +} + +/** + * Callback for yourls_kses_normalize_entities() regular expression. + * + * This function helps yourls_kses_normalize_entities() to only accept 16-bit values + * and nothing more for &#number; entities. + * + * @access private + * @since 1.6 + * + * @param array $matches preg_replace_callback() matches array + * @return string Correctly encoded entity + */ +function yourls_kses_normalize_entities2($matches) { + if ( empty($matches[1]) ) + return ''; + + $i = $matches[1]; + if (yourls_valid_unicode($i)) { + $i = str_pad(ltrim($i,'0'), 3, '0', STR_PAD_LEFT); + $i = "&#$i;"; + } else { + $i = "&#$i;"; + } + + return $i; +} + +/** + * Callback for yourls_kses_normalize_entities() for regular expression. + * + * This function helps yourls_kses_normalize_entities() to only accept valid Unicode + * numeric entities in hex form. + * + * @access private + * @since 1.6 + * + * @param array $matches preg_replace_callback() matches array + * @return string Correctly encoded entity + */ +function yourls_kses_normalize_entities3($matches) { + if ( empty($matches[1]) ) + return ''; + + $hexchars = $matches[1]; + return ( ( ! yourls_valid_unicode(hexdec($hexchars)) ) ? "&#x$hexchars;" : '&#x'.ltrim($hexchars,'0').';' ); +} + +/** + * Helper function to add global attributes to a tag in the allowed html list. + * + * @since 1.6 + * @access private + * + * @param array $value An array of attributes. + * @return array The array of attributes with global attributes added. + */ +function _yourls_add_global_attributes( $value ) { + $global_attributes = array( + 'class' => true, + 'id' => true, + 'style' => true, + 'title' => true, + ); + + if ( true === $value ) + $value = array(); + + if ( is_array( $value ) ) + return array_merge( $value, $global_attributes ); + + return $value; +} + +/** + * Helper function to determine if a Unicode value is valid. + * + * @since 1.6 + * + * @param int $i Unicode value + * @return bool True if the value was a valid Unicode number + */ +function yourls_valid_unicode($i) { + return ( $i == 0x9 || $i == 0xa || $i == 0xd || + ($i >= 0x20 && $i <= 0xd7ff) || + ($i >= 0xe000 && $i <= 0xfffd) || + ($i >= 0x10000 && $i <= 0x10ffff) ); +} + +/** + * Goes through an array and changes the keys to all lower case. + * + * @since 1.6 + * + * @param array $inarray Unfiltered array + * @return array Fixed array with all lowercase keys + */ +function yourls_kses_array_lc($inarray) { + $outarray = array (); + + foreach ( (array) $inarray as $inkey => $inval) { + $outkey = strtolower($inkey); + $outarray[$outkey] = array (); + + foreach ( (array) $inval as $inkey2 => $inval2) { + $outkey2 = strtolower($inkey2); + $outarray[$outkey][$outkey2] = $inval2; + } # foreach $inval + } # foreach $inarray + + return $outarray; +} + +/** + * Convert all entities to their character counterparts. + * + * This function decodes numeric HTML entities (A and A). It doesn't do + * anything with other entities like ä, but we don't need them in the URL + * protocol whitelisting system anyway. + * + * @since 1.6 + * + * @param string $string Content to change entities + * @return string Content after decoded entities + */ +function yourls_kses_decode_entities($string) { + $string = preg_replace_callback('/&#([0-9]+);/', '_yourls_kses_decode_entities_chr', $string); + $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', '_yourls_kses_decode_entities_chr_hexdec', $string); + + return $string; +} + +/** + * Regex callback for yourls_kses_decode_entities() + * + * @since 1.6 + * + * @param array $match preg match + * @return string + */ +function _yourls_kses_decode_entities_chr( $match ) { + return chr( $match[1] ); +} + +/** + * Regex callback for yourls_kses_decode_entities() + * + * @since 1.6 + * + * @param array $match preg match + * @return string + */ +function _yourls_kses_decode_entities_chr_hexdec( $match ) { + return chr( hexdec( $match[1] ) ); +} + +/** + * Removes any null characters in $string. + * + * @since 1.6 + * + * @param string $string + * @return string + */ +function yourls_kses_no_null($string) { + $string = preg_replace( '/\0+/', '', $string ); + $string = preg_replace( '/(\\\\0)+/', '', $string ); + + return $string; +} diff --git a/includes/functions-l10n.php b/includes/functions-l10n.php index 361ed44..2b251a2 100644 --- a/includes/functions-l10n.php +++ b/includes/functions-l10n.php @@ -1,1133 +1,1133 @@ -translate( $text ), $text, $domain ); -} - -/** - * Retrieves the translation of $text with a given $context. If there is no translation, or - * the domain isn't loaded, the original text is returned. - * - * Quite a few times, there will be collisions with similar translatable text - * found in more than two places but with different translated context. - * - * By including the context in the pot file translators can translate the two - * strings differently. - * - * @since 1.6 - * @param string $text Text to translate. - * @param string $context Context. - * @param string $domain Domain to retrieve the translated text. - * @return string Translated text - */ -function yourls_translate_with_context( $text, $context, $domain = 'default' ) { - $translations = yourls_get_translations_for_domain( $domain ); - return yourls_apply_filters( 'translate_with_context', $translations->translate( $text, $context ), $text, $context, $domain ); -} - -/** - * Retrieves the translation of $text. If there is no translation, or - * the domain isn't loaded, the original text is returned. - * - * @see yourls_translate() An alias of yourls_translate() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - * @return string Translated text - */ -function yourls__( $text, $domain = 'default' ) { - return yourls_translate( $text, $domain ); -} - -/** - * Return a translated sprintf() string (mix yourls__() and sprintf() in one func) - * - * Instead of doing sprintf( yourls__( 'string %s' ), $arg ) you can simply use: - * yourls_s( 'string %s', $arg ) - * This function accepts an arbitrary number of arguments: - * - first one will be the string to translate, eg "hello %s my name is %s" - * - following ones will be the sprintf arguments, eg "world" and "Ozh" - * - if there are more arguments passed than needed, the last one will be used as the translation domain - * This function will not accept a textdomain argument: do not use in plugins or outside YOURLS core. - * - * @see sprintf() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $arg1, $arg2... Optional: sprintf tokens, and translation domain - * @return string Translated text - */ -function yourls_s( $pattern ) { - // Get pattern and pattern arguments - $args = func_get_args(); - // If yourls_s() called by yourls_se(), all arguments are wrapped in the same array key - if( count( $args ) == 1 && is_array( $args ) ) { - $args = $args[0]; - } - $pattern = $args[0]; - - // get list of sprintf tokens (%s and such) - $num_of_tokens = substr_count( $pattern, '%' ) - 2 * substr_count( $pattern, '%%' ); - - $domain = 'default'; - // More arguments passed than needed for the sprintf? The last one will be the domain - if( $num_of_tokens < ( count( $args ) - 1 ) ) { - $domain = array_pop( $args ); - } - - // Translate text - $args[0] = yourls__( $pattern, $domain ); - - return call_user_func_array( 'sprintf', $args ); -} - -/** - * Echo a translated sprintf() string (mix yourls__() and sprintf() in one func) - * - * Instead of doing printf( yourls__( 'string %s' ), $arg ) you can simply use: - * yourls_se( 'string %s', $arg ) - * This function accepts an arbitrary number of arguments: - * - first one will be the string to translate, eg "hello %s my name is %s" - * - following ones will be the sprintf arguments, eg "world" and "Ozh" - * - if there are more arguments passed than needed, the last one will be used as the translation domain - * - * @see yourls_s() - * @see sprintf() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $arg1, $arg2... Optional: sprintf tokens, and translation domain - * @return string Translated text - */ -function yourls_se( $pattern ) { - echo yourls_s( func_get_args() ); -} - - -/** - * Retrieves the translation of $text and escapes it for safe use in an attribute. - * If there is no translation, or the domain isn't loaded, the original text is returned. - * - * @see yourls_translate() An alias of yourls_translate() - * @see yourls_esc_attr() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - * @return string Translated text - */ -function yourls_esc_attr__( $text, $domain = 'default' ) { - return yourls_esc_attr( yourls_translate( $text, $domain ) ); -} - -/** - * Retrieves the translation of $text and escapes it for safe use in HTML output. - * If there is no translation, or the domain isn't loaded, the original text is returned. - * - * @see yourls_translate() An alias of yourls_translate() - * @see yourls_esc_html() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - * @return string Translated text - */ -function yourls_esc_html__( $text, $domain = 'default' ) { - return yourls_esc_html( yourls_translate( $text, $domain ) ); -} - -/** - * Displays the returned translated text from yourls_translate(). - * - * @see yourls_translate() Echoes returned yourls_translate() string - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - */ -function yourls_e( $text, $domain = 'default' ) { - echo yourls_translate( $text, $domain ); -} - -/** - * Displays translated text that has been escaped for safe use in an attribute. - * - * @see yourls_translate() Echoes returned yourls_translate() string - * @see yourls_esc_attr() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - */ -function yourls_esc_attr_e( $text, $domain = 'default' ) { - echo yourls_esc_attr( yourls_translate( $text, $domain ) ); -} - -/** - * Displays translated text that has been escaped for safe use in HTML output. - * - * @see yourls_translate() Echoes returned yourls_translate() string - * @see yourls_esc_html() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - */ -function yourls_esc_html_e( $text, $domain = 'default' ) { - echo yourls_esc_html( yourls_translate( $text, $domain ) ); -} - -/** - * Retrieve translated string with gettext context - * - * Quite a few times, there will be collisions with similar translatable text - * found in more than two places but with different translated context. - * - * By including the context in the pot file translators can translate the two - * strings differently. - * - * @since 1.6 - * - * @param string $text Text to translate - * @param string $context Context information for the translators - * @param string $domain Optional. Domain to retrieve the translated text - * @return string Translated context string without pipe - */ -function yourls_x( $text, $context, $domain = 'default' ) { - return yourls_translate_with_context( $text, $context, $domain ); -} - -/** - * Displays translated string with gettext context - * - * @see yourls_x() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $context Context information for the translators - * @param string $domain Optional. Domain to retrieve the translated text - * @return string Translated context string without pipe - */ -function yourls_ex( $text, $context, $domain = 'default' ) { - echo yourls_x( $text, $context, $domain ); -} - - -/** - * Return translated text, with context, that has been escaped for safe use in an attribute - * - * @see yourls_translate() Return returned yourls_translate() string - * @see yourls_esc_attr() - * @see yourls_x() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - */ -function yourls_esc_attr_x( $single, $context, $domain = 'default' ) { - return yourls_esc_attr( yourls_translate_with_context( $single, $context, $domain ) ); -} - -/** - * Return translated text, with context, that has been escaped for safe use in HTML output - * - * @see yourls_translate() Return returned yourls_translate() string - * @see yourls_esc_attr() - * @see yourls_x() - * @since 1.6 - * - * @param string $text Text to translate - * @param string $domain Optional. Domain to retrieve the translated text - */ -function yourls_esc_html_x( $single, $context, $domain = 'default' ) { - return yourls_esc_html( yourls_translate_with_context( $single, $context, $domain ) ); -} - -/** - * Retrieve the plural or single form based on the amount. - * - * If the domain is not set in the $yourls_l10n list, then a comparison will be made - * and either $plural or $single parameters returned. - * - * If the domain does exist, then the parameters $single, $plural, and $number - * will first be passed to the domain's ngettext method. Then it will be passed - * to the 'translate_n' filter hook along with the same parameters. The expected - * type will be a string. - * - * @since 1.6 - * @uses $yourls_l10n Gets list of domain translated string (gettext_reader) objects - * @uses yourls_apply_filters() Calls 'translate_n' hook on domains text returned, - * along with $single, $plural, and $number parameters. Expected to return string. - * - * @param string $single The text that will be used if $number is 1 - * @param string $plural The text that will be used if $number is not 1 - * @param int $number The number to compare against to use either $single or $plural - * @param string $domain Optional. The domain identifier the text should be retrieved in - * @return string Either $single or $plural translated text - */ -function yourls_n( $single, $plural, $number, $domain = 'default' ) { - $translations = yourls_get_translations_for_domain( $domain ); - $translation = $translations->translate_plural( $single, $plural, $number ); - return yourls_apply_filters( 'translate_n', $translation, $single, $plural, $number, $domain ); -} - -/** - * A hybrid of yourls_n() and yourls_x(). It supports contexts and plurals. - * - * @since 1.6 - * @see yourls_n() - * @see yourls_x() - * - */ -function yourls_nx($single, $plural, $number, $context, $domain = 'default') { - $translations = yourls_get_translations_for_domain( $domain ); - $translation = $translations->translate_plural( $single, $plural, $number, $context ); - return yourls_apply_filters( 'translate_nx', $translation, $single, $plural, $number, $context, $domain ); -} - -/** - * Register plural strings in POT file, but don't translate them. - * - * Used when you want to keep structures with translatable plural strings and - * use them later. - * - * Example: - * $messages = array( - * 'post' => yourls_n_noop('%s post', '%s posts'), - * 'page' => yourls_n_noop('%s pages', '%s pages') - * ); - * ... - * $message = $messages[$type]; - * $usable_text = sprintf( yourls_translate_nooped_plural( $message, $count ), $count ); - * - * @since 1.6 - * @param string $singular Single form to be i18ned - * @param string $plural Plural form to be i18ned - * @param string $domain Optional. The domain identifier the text will be retrieved in - * @return array array($singular, $plural) - */ -function yourls_n_noop( $singular, $plural, $domain = null ) { - return array( - 0 => $singular, - 1 => $plural, - 'singular' => $singular, - 'plural' => $plural, - 'context' => null, - 'domain' => $domain - ); -} - -/** - * Register plural strings with context in POT file, but don't translate them. - * - * @since 1.6 - * @see yourls_n_noop() - */ -function yourls_nx_noop( $singular, $plural, $context, $domain = null ) { - return array( - 0 => $singular, - 1 => $plural, - 2 => $context, - 'singular' => $singular, - 'plural' => $plural, - 'context' => $context, - 'domain' => $domain - ); -} - -/** - * Translate the result of yourls_n_noop() or yourls_nx_noop() - * - * @since 1.6 - * @param array $nooped_plural Array with singular, plural and context keys, usually the result of yourls_n_noop() or yourls_nx_noop() - * @param int $count Number of objects - * @param string $domain Optional. The domain identifier the text should be retrieved in. If $nooped_plural contains - * a domain passed to yourls_n_noop() or yourls_nx_noop(), it will override this value. - */ -function yourls_translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) { - if ( $nooped_plural['domain'] ) - $domain = $nooped_plural['domain']; - - if ( $nooped_plural['context'] ) - return yourls_nx( $nooped_plural['singular'], $nooped_plural['plural'], $count, $nooped_plural['context'], $domain ); - else - return yourls_n( $nooped_plural['singular'], $nooped_plural['plural'], $count, $domain ); -} - -/** - * Loads a MO file into the domain $domain. - * - * If the domain already exists, the translations will be merged. If both - * sets have the same string, the translation from the original value will be taken. - * - * On success, the .mo file will be placed in the $yourls_l10n global by $domain - * and will be a MO object. - * - * @since 1.6 - * @uses $yourls_l10n Gets list of domain translated string objects - * - * @param string $domain Unique identifier for retrieving translated strings - * @param string $mofile Path to the .mo file - * @return bool True on success, false on failure - */ -function yourls_load_textdomain( $domain, $mofile ) { - global $yourls_l10n; - - $plugin_override = yourls_apply_filters( 'override_load_textdomain', false, $domain, $mofile ); - - if ( true == $plugin_override ) { - return true; - } - - yourls_do_action( 'load_textdomain', $domain, $mofile ); - - $mofile = yourls_apply_filters( 'load_textdomain_mofile', $mofile, $domain ); - - if ( !is_readable( $mofile ) ) return false; - - $mo = new MO(); - if ( !$mo->import_from_file( $mofile ) ) return false; - - if ( isset( $yourls_l10n[$domain] ) ) - $mo->merge_with( $yourls_l10n[$domain] ); - - $yourls_l10n[$domain] = &$mo; - - return true; -} - -/** - * Unloads translations for a domain - * - * @since 1.6 - * @param string $domain Textdomain to be unloaded - * @return bool Whether textdomain was unloaded - */ -function yourls_unload_textdomain( $domain ) { - global $yourls_l10n; - - $plugin_override = yourls_apply_filters( 'override_unload_textdomain', false, $domain ); - - if ( $plugin_override ) - return true; - - yourls_do_action( 'unload_textdomain', $domain ); - - if ( isset( $yourls_l10n[$domain] ) ) { - unset( $yourls_l10n[$domain] ); - return true; - } - - return false; -} - -/** - * Loads default translated strings based on locale. - * - * Loads the .mo file in YOURLS_LANG_DIR constant path from YOURLS root. The - * translated (.mo) file is named based on the locale. - * - * @since 1.6 - */ -function yourls_load_default_textdomain() { - $yourls_locale = yourls_get_locale(); - - yourls_load_textdomain( 'default', YOURLS_LANG_DIR . "/$yourls_locale.mo" ); - -} - -/** - * Returns the Translations instance for a domain. If there isn't one, - * returns empty Translations instance. - * - * @param string $domain - * @return object A Translation instance - */ -function yourls_get_translations_for_domain( $domain ) { - global $yourls_l10n; - if ( !isset( $yourls_l10n[$domain] ) ) { - $yourls_l10n[$domain] = new NOOP_Translations; - } - return $yourls_l10n[$domain]; -} - -/** - * Whether there are translations for the domain - * - * @since 1.6 - * @param string $domain - * @return bool Whether there are translations - */ -function yourls_is_textdomain_loaded( $domain ) { - global $yourls_l10n; - return isset( $yourls_l10n[$domain] ); -} - -/** - * Translates role name. Unused. - * - * Unused function for the moment, we'll see when there are roles. - * From the WP source: Since the role names are in the database and - * not in the source there are dummy gettext calls to get them into the POT - * file and this function properly translates them back. - * - * @since 1.6 - */ -function yourls_translate_user_role( $name ) { - return yourls_translate_with_context( $name, 'User role' ); -} - -/** - * Get all available languages (*.mo files) in a given directory. The default directory is YOURLS_LANG_DIR. - * - * @since 1.6 - * - * @param string $dir A directory in which to search for language files. The default directory is YOURLS_LANG_DIR. - * @return array Array of language codes or an empty array if no languages are present. Language codes are formed by stripping the .mo extension from the language file names. - */ -function yourls_get_available_languages( $dir = null ) { - $languages = array(); - - $dir = is_null( $dir) ? YOURLS_LANG_DIR : $dir; - - foreach( (array) glob( $dir . '/*.mo' ) as $lang_file ) { - $languages[] = basename( $lang_file, '.mo' ); - } - - return yourls_apply_filters( 'get_available_languages', $languages ); -} - -/** - * Return integer number to format based on the locale. - * - * @since 1.6 - * - * @param int $number The number to convert based on locale. - * @param int $decimals Precision of the number of decimal places. - * @return string Converted number in string format. - */ -function yourls_number_format_i18n( $number, $decimals = 0 ) { - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - $formatted = number_format( $number, abs( intval( $decimals ) ), $yourls_locale_formats->number_format['decimal_point'], $yourls_locale_formats->number_format['thousands_sep'] ); - return yourls_apply_filters( 'number_format_i18n', $formatted ); -} - -/** - * Return the date in localized format, based on timestamp. - * - * If the locale specifies the locale month and weekday, then the locale will - * take over the format for the date. If it isn't, then the date format string - * will be used instead. - * - * @since 1.6 - * - * @param string $dateformatstring Format to display the date. - * @param int $unixtimestamp Optional. Unix timestamp. - * @param bool $gmt Optional, default is false. Whether to convert to GMT for time. - * @return string The date, translated if locale specifies it. - */ -function yourls_date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = false ) { - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - $i = $unixtimestamp; - - if ( false === $i ) { - if ( ! $gmt ) - $i = yourls_current_time( 'timestamp' ); - else - $i = time(); - // we should not let date() interfere with our - // specially computed timestamp - $gmt = true; - } - - // store original value for language with untypical grammars - // see http://core.trac.wordpress.org/ticket/9396 - $req_format = $dateformatstring; - - $datefunc = $gmt? 'gmdate' : 'date'; - - if ( ( !empty( $yourls_locale_formats->month ) ) && ( !empty( $yourls_locale_formats->weekday ) ) ) { - $datemonth = $yourls_locale_formats->get_month( $datefunc( 'm', $i ) ); - $datemonth_abbrev = $yourls_locale_formats->get_month_abbrev( $datemonth ); - $dateweekday = $yourls_locale_formats->get_weekday( $datefunc( 'w', $i ) ); - $dateweekday_abbrev = $yourls_locale_formats->get_weekday_abbrev( $dateweekday ); - $datemeridiem = $yourls_locale_formats->get_meridiem( $datefunc( 'a', $i ) ); - $datemeridiem_capital = $yourls_locale_formats->get_meridiem( $datefunc( 'A', $i ) ); - - $dateformatstring = ' '.$dateformatstring; - $dateformatstring = preg_replace( "/([^\\\])D/", "\\1" . yourls_backslashit( $dateweekday_abbrev ), $dateformatstring ); - $dateformatstring = preg_replace( "/([^\\\])F/", "\\1" . yourls_backslashit( $datemonth ), $dateformatstring ); - $dateformatstring = preg_replace( "/([^\\\])l/", "\\1" . yourls_backslashit( $dateweekday ), $dateformatstring ); - $dateformatstring = preg_replace( "/([^\\\])M/", "\\1" . yourls_backslashit( $datemonth_abbrev ), $dateformatstring ); - $dateformatstring = preg_replace( "/([^\\\])a/", "\\1" . yourls_backslashit( $datemeridiem ), $dateformatstring ); - $dateformatstring = preg_replace( "/([^\\\])A/", "\\1" . yourls_backslashit( $datemeridiem_capital ), $dateformatstring ); - - $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 ); - } - $timezone_formats = array( 'P', 'I', 'O', 'T', 'Z', 'e' ); - $timezone_formats_re = implode( '|', $timezone_formats ); - if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) { - - // TODO: implement a timezone option - $timezone_string = yourls_get_option( 'timezone_string' ); - if ( $timezone_string ) { - $timezone_object = timezone_open( $timezone_string ); - $date_object = date_create( null, $timezone_object ); - foreach( $timezone_formats as $timezone_format ) { - if ( false !== strpos( $dateformatstring, $timezone_format ) ) { - $formatted = date_format( $date_object, $timezone_format ); - $dateformatstring = ' '.$dateformatstring; - $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . yourls_backslashit( $formatted ), $dateformatstring ); - $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 ); - } - } - } - } - $j = @$datefunc( $dateformatstring, $i ); - // allow plugins to redo this entirely for languages with untypical grammars - $j = yourls_apply_filters('date_i18n', $j, $req_format, $i, $gmt); - return $j; -} - -/** - * Retrieve the current time based on specified type. Stolen from WP. - * - * The 'mysql' type will return the time in the format for MySQL DATETIME field. - * The 'timestamp' type will return the current timestamp. - * - * If $gmt is set to either '1' or 'true', then both types will use GMT time. - * if $gmt is false, the output is adjusted with the GMT offset in the WordPress option. - * - * @since 1.6 - * - * @param string $type Either 'mysql' or 'timestamp'. - * @param int|bool $gmt Optional. Whether to use GMT timezone. Default is false. - * @return int|string String if $type is 'gmt', int if $type is 'timestamp'. - */ -function yourls_current_time( $type, $gmt = 0 ) { - switch ( $type ) { - case 'mysql': - return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', time() + YOURLS_HOURS_OFFSET * 3600 ); - break; - case 'timestamp': - return ( $gmt ) ? time() : time() + YOURLS_HOURS_OFFSET * 3600; - break; - } -} - - -/** - * Class that loads the calendar locale. - * - * @since 1.6 - */ -class YOURLS_Locale_Formats { - /** - * Stores the translated strings for the full weekday names. - * - * @since 1.6 - * @var array - * @access private - */ - var $weekday; - - /** - * Stores the translated strings for the one character weekday names. - * - * There is a hack to make sure that Tuesday and Thursday, as well - * as Sunday and Saturday, don't conflict. See init() method for more. - * - * @see YOURLS_Locale_Formats::init() for how to handle the hack. - * - * @since 1.6 - * @var array - * @access private - */ - var $weekday_initial; - - /** - * Stores the translated strings for the abbreviated weekday names. - * - * @since 1.6 - * @var array - * @access private - */ - var $weekday_abbrev; - - /** - * Stores the translated strings for the full month names. - * - * @since 1.6 - * @var array - * @access private - */ - var $month; - - /** - * Stores the translated strings for the abbreviated month names. - * - * @since 1.6 - * @var array - * @access private - */ - var $month_abbrev; - - /** - * Stores the translated strings for 'am' and 'pm'. - * - * Also the capitalized versions. - * - * @since 1.6 - * @var array - * @access private - */ - var $meridiem; - - /** - * The text direction of the locale language. - * - * Default is left to right 'ltr'. - * - * @since 1.6 - * @var string - * @access private - */ - var $text_direction = 'ltr'; - - /** - * Sets up the translated strings and object properties. - * - * The method creates the translatable strings for various - * calendar elements. Which allows for specifying locale - * specific calendar names and text direction. - * - * @since 1.6 - * @access private - */ - function init() { - // The Weekdays - $this->weekday[0] = /* //translators: weekday */ yourls__( 'Sunday' ); - $this->weekday[1] = /* //translators: weekday */ yourls__( 'Monday' ); - $this->weekday[2] = /* //translators: weekday */ yourls__( 'Tuesday' ); - $this->weekday[3] = /* //translators: weekday */ yourls__( 'Wednesday' ); - $this->weekday[4] = /* //translators: weekday */ yourls__( 'Thursday' ); - $this->weekday[5] = /* //translators: weekday */ yourls__( 'Friday' ); - $this->weekday[6] = /* //translators: weekday */ yourls__( 'Saturday' ); - - // The first letter of each day. The _%day%_initial suffix is a hack to make - // sure the day initials are unique. - $this->weekday_initial[yourls__( 'Sunday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'S_Sunday_initial' ); - $this->weekday_initial[yourls__( 'Monday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'M_Monday_initial' ); - $this->weekday_initial[yourls__( 'Tuesday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'T_Tuesday_initial' ); - $this->weekday_initial[yourls__( 'Wednesday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'W_Wednesday_initial' ); - $this->weekday_initial[yourls__( 'Thursday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'T_Thursday_initial' ); - $this->weekday_initial[yourls__( 'Friday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'F_Friday_initial' ); - $this->weekday_initial[yourls__( 'Saturday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'S_Saturday_initial' ); - - foreach ($this->weekday_initial as $weekday_ => $weekday_initial_) { - $this->weekday_initial[$weekday_] = preg_replace('/_.+_initial$/', '', $weekday_initial_); - } - - // Abbreviations for each day. - $this->weekday_abbrev[ yourls__( 'Sunday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Sun' ); - $this->weekday_abbrev[ yourls__( 'Monday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Mon' ); - $this->weekday_abbrev[ yourls__( 'Tuesday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Tue' ); - $this->weekday_abbrev[ yourls__( 'Wednesday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Wed' ); - $this->weekday_abbrev[ yourls__( 'Thursday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Thu' ); - $this->weekday_abbrev[ yourls__( 'Friday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Fri' ); - $this->weekday_abbrev[ yourls__( 'Saturday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Sat' ); - - // The Months - $this->month['01'] = /* //translators: month name */ yourls__( 'January' ); - $this->month['02'] = /* //translators: month name */ yourls__( 'February' ); - $this->month['03'] = /* //translators: month name */ yourls__( 'March' ); - $this->month['04'] = /* //translators: month name */ yourls__( 'April' ); - $this->month['05'] = /* //translators: month name */ yourls__( 'May' ); - $this->month['06'] = /* //translators: month name */ yourls__( 'June' ); - $this->month['07'] = /* //translators: month name */ yourls__( 'July' ); - $this->month['08'] = /* //translators: month name */ yourls__( 'August' ); - $this->month['09'] = /* //translators: month name */ yourls__( 'September' ); - $this->month['10'] = /* //translators: month name */ yourls__( 'October' ); - $this->month['11'] = /* //translators: month name */ yourls__( 'November' ); - $this->month['12'] = /* //translators: month name */ yourls__( 'December' ); - - // Abbreviations for each month. Uses the same hack as above to get around the - // 'May' duplication. - $this->month_abbrev[ yourls__( 'January' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jan_January_abbreviation' ); - $this->month_abbrev[ yourls__( 'February' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Feb_February_abbreviation' ); - $this->month_abbrev[ yourls__( 'March' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Mar_March_abbreviation' ); - $this->month_abbrev[ yourls__( 'April' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Apr_April_abbreviation' ); - $this->month_abbrev[ yourls__( 'May' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'May_May_abbreviation' ); - $this->month_abbrev[ yourls__( 'June' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jun_June_abbreviation' ); - $this->month_abbrev[ yourls__( 'July' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jul_July_abbreviation' ); - $this->month_abbrev[ yourls__( 'August' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Aug_August_abbreviation' ); - $this->month_abbrev[ yourls__( 'September' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Sep_September_abbreviation' ); - $this->month_abbrev[ yourls__( 'October' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Oct_October_abbreviation' ); - $this->month_abbrev[ yourls__( 'November' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Nov_November_abbreviation' ); - $this->month_abbrev[ yourls__( 'December' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Dec_December_abbreviation' ); - - foreach ($this->month_abbrev as $month_ => $month_abbrev_) { - $this->month_abbrev[$month_] = preg_replace('/_.+_abbreviation$/', '', $month_abbrev_); - } - - // The Meridiems - $this->meridiem['am'] = yourls__( 'am' ); - $this->meridiem['pm'] = yourls__( 'pm' ); - $this->meridiem['AM'] = yourls__( 'AM' ); - $this->meridiem['PM'] = yourls__( 'PM' ); - - // Numbers formatting - // See http://php.net/number_format - - /* //translators: $thousands_sep argument for http://php.net/number_format, default is , */ - $trans = yourls__( 'number_format_thousands_sep' ); - $this->number_format['thousands_sep'] = ('number_format_thousands_sep' == $trans) ? ',' : $trans; - - /* //translators: $dec_point argument for http://php.net/number_format, default is . */ - $trans = yourls__( 'number_format_decimal_point' ); - $this->number_format['decimal_point'] = ('number_format_decimal_point' == $trans) ? '.' : $trans; - - // Set text direction. - if ( isset( $GLOBALS['text_direction'] ) ) - $this->text_direction = $GLOBALS['text_direction']; - /* //translators: 'rtl' or 'ltr'. This sets the text direction for YOURLS. */ - elseif ( 'rtl' == yourls_x( 'ltr', 'text direction' ) ) - $this->text_direction = 'rtl'; - } - - /** - * Retrieve the full translated weekday word. - * - * Week starts on translated Sunday and can be fetched - * by using 0 (zero). So the week starts with 0 (zero) - * and ends on Saturday with is fetched by using 6 (six). - * - * @since 1.6 - * @access public - * - * @param int $weekday_number 0 for Sunday through 6 Saturday - * @return string Full translated weekday - */ - function get_weekday( $weekday_number ) { - return $this->weekday[ $weekday_number ]; - } - - /** - * Retrieve the translated weekday initial. - * - * The weekday initial is retrieved by the translated - * full weekday word. When translating the weekday initial - * pay attention to make sure that the starting letter does - * not conflict. - * - * @since 1.6 - * @access public - * - * @param string $weekday_name - * @return string - */ - function get_weekday_initial( $weekday_name ) { - return $this->weekday_initial[ $weekday_name ]; - } - - /** - * Retrieve the translated weekday abbreviation. - * - * The weekday abbreviation is retrieved by the translated - * full weekday word. - * - * @since 1.6 - * @access public - * - * @param string $weekday_name Full translated weekday word - * @return string Translated weekday abbreviation - */ - function get_weekday_abbrev( $weekday_name ) { - return $this->weekday_abbrev[ $weekday_name ]; - } - - /** - * Retrieve the full translated month by month number. - * - * The $month_number parameter has to be a string - * because it must have the '0' in front of any number - * that is less than 10. Starts from '01' and ends at - * '12'. - * - * You can use an integer instead and it will add the - * '0' before the numbers less than 10 for you. - * - * @since 1.6 - * @access public - * - * @param string|int $month_number '01' through '12' - * @return string Translated full month name - */ - function get_month( $month_number ) { - return $this->month[ sprintf( '%02s', $month_number ) ]; - } - - /** - * Retrieve translated version of month abbreviation string. - * - * The $month_name parameter is expected to be the translated or - * translatable version of the month. - * - * @since 1.6 - * @access public - * - * @param string $month_name Translated month to get abbreviated version - * @return string Translated abbreviated month - */ - function get_month_abbrev( $month_name ) { - return $this->month_abbrev[ $month_name ]; - } - - /** - * Retrieve translated version of meridiem string. - * - * The $meridiem parameter is expected to not be translated. - * - * @since 1.6 - * @access public - * - * @param string $meridiem Either 'am', 'pm', 'AM', or 'PM'. Not translated version. - * @return string Translated version - */ - function get_meridiem( $meridiem ) { - return $this->meridiem[ $meridiem ]; - } - - /** - * Global variables are deprecated. For backwards compatibility only. - * - * @deprecated For backwards compatibility only. - * @access private - * - * @since 1.6 - */ - function register_globals() { - $GLOBALS['weekday'] = $this->weekday; - $GLOBALS['weekday_initial'] = $this->weekday_initial; - $GLOBALS['weekday_abbrev'] = $this->weekday_abbrev; - $GLOBALS['month'] = $this->month; - $GLOBALS['month_abbrev'] = $this->month_abbrev; - } - - /** - * Constructor which calls helper methods to set up object variables - * - * @uses YOURLS_Locale_Formats::init() - * @uses YOURLS_Locale_Formats::register_globals() - * @since 1.6 - * - * @return YOURLS_Locale_Formats - */ - function __construct() { - $this->init(); - $this->register_globals(); - } - - /** - * Checks if current locale is RTL. - * - * @since 1.6 - * @return bool Whether locale is RTL. - */ - function is_rtl() { - return 'rtl' == $this->text_direction; - } -} - -/** - * Loads a custom translation file (for a plugin, a theme, a public interface...) - * - * The .mo file should be named based on the domain with a dash, and then the locale exactly, - * eg 'myplugin-pt_BR.mo' - * - * @since 1.6 - * - * @param string $domain Unique identifier (the "domain") for retrieving translated strings - * @param string $path Full path to directory containing MO files. - */ -function yourls_load_custom_textdomain( $domain, $path ) { - $locale = yourls_apply_filters( 'load_custom_textdomain', yourls_get_locale(), $domain ); - $mofile = trim( $path, '/' ) . '/'. $domain . '-' . $locale . '.mo'; - - return yourls_load_textdomain( $domain, $mofile ); -} - -/** - * Checks if current locale is RTL. Stolen from WP. - * - * @since 1.6 - * @return bool Whether locale is RTL. - */ -function yourls_is_rtl() { - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - return $yourls_locale_formats->is_rtl(); -} - -/** - * Return translated weekday abbreviation (3 letters, eg 'Fri' for 'Friday') - * - * The $weekday var can be a textual string ('Friday'), a integer (0 to 6) or an empty string - * If $weekday is an empty string, the function returns an array of all translated weekday abbrev - * - * @since 1.6 - * @param mixed $weekday A full textual weekday, eg "Friday", or an integer (0 = Sunday, 1 = Monday, .. 6 = Saturday) - * @return mixed Translated weekday abbreviation, eg "Ven" (abbrev of "Vendredi") for "Friday" or 5, or array of all weekday abbrev - */ -function yourls_l10n_weekday_abbrev( $weekday = '' ){ - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - if( $weekday === '' ) - return $yourls_locale_formats->weekday_abbrev; - - if( is_int( $weekday ) ) { - $day = $yourls_locale_formats->weekday[ $weekday ]; - return $yourls_locale_formats->weekday_abbrev[ $day ]; - } else { - return $yourls_locale_formats->weekday_abbrev[ yourls__( $weekday ) ]; - } -} - -/** - * Return translated weekday initial (1 letter, eg 'F' for 'Friday') - * - * The $weekday var can be a textual string ('Friday'), a integer (0 to 6) or an empty string - * If $weekday is an empty string, the function returns an array of all translated weekday initials - * - * @since 1.6 - * @param mixed $weekday A full textual weekday, eg "Friday", an integer (0 = Sunday, 1 = Monday, .. 6 = Saturday) or empty string - * @return mixed Translated weekday initial, eg "V" (initial of "Vendredi") for "Friday" or 5, or array of all weekday initials - */ -function yourls_l10n_weekday_initial( $weekday = '' ){ - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - if( $weekday === '' ) - return $yourls_locale_formats->weekday_initial; - - if( is_int( $weekday ) ) { - $weekday = $yourls_locale_formats->weekday[ $weekday ]; - return $yourls_locale_formats->weekday_initial[ $weekday ]; - } else { - return $yourls_locale_formats->weekday_initial[ yourls__( $weekday ) ]; - } -} - -/** - * Return translated month abbrevation (3 letters, eg 'Nov' for 'November') - * - * The $month var can be a textual string ('November'), a integer (1 to 12), a two digits strings ('01' to '12), or an empty string - * If $month is an empty string, the function returns an array of all translated abbrev months ('January' => 'Jan', ...) - * - * @since 1.6 - * @param mixed $month Empty string, a full textual weekday, eg "November", or an integer (1 = January, .., 12 = December) - * @return mixed Translated month abbrev (eg "Nov"), or array of all translated abbrev months - */ -function yourls_l10n_month_abbrev( $month = '' ){ - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - if( $month === '' ) - return $yourls_locale_formats->month_abbrev; - - if( intval( $month ) > 0 ) { - $month = $yourls_locale_formats->month[ $month ]; - return $yourls_locale_formats->month_abbrev[ $month ]; - } else { - return $yourls_locale_formats->month_abbrev[ yourls__( $month ) ]; - } -} - -/** - * Return array of all translated months - * - * @since 1.6 - * @return array Array of all translated months - */ -function yourls_l10n_months(){ - global $yourls_locale_formats; - if( !isset( $yourls_locale_formats ) ) - $yourls_locale_formats = new YOURLS_Locale_Formats(); - - return $yourls_locale_formats->month; -} +translate( $text ), $text, $domain ); +} + +/** + * Retrieves the translation of $text with a given $context. If there is no translation, or + * the domain isn't loaded, the original text is returned. + * + * Quite a few times, there will be collisions with similar translatable text + * found in more than two places but with different translated context. + * + * By including the context in the pot file translators can translate the two + * strings differently. + * + * @since 1.6 + * @param string $text Text to translate. + * @param string $context Context. + * @param string $domain Domain to retrieve the translated text. + * @return string Translated text + */ +function yourls_translate_with_context( $text, $context, $domain = 'default' ) { + $translations = yourls_get_translations_for_domain( $domain ); + return yourls_apply_filters( 'translate_with_context', $translations->translate( $text, $context ), $text, $context, $domain ); +} + +/** + * Retrieves the translation of $text. If there is no translation, or + * the domain isn't loaded, the original text is returned. + * + * @see yourls_translate() An alias of yourls_translate() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + * @return string Translated text + */ +function yourls__( $text, $domain = 'default' ) { + return yourls_translate( $text, $domain ); +} + +/** + * Return a translated sprintf() string (mix yourls__() and sprintf() in one func) + * + * Instead of doing sprintf( yourls__( 'string %s' ), $arg ) you can simply use: + * yourls_s( 'string %s', $arg ) + * This function accepts an arbitrary number of arguments: + * - first one will be the string to translate, eg "hello %s my name is %s" + * - following ones will be the sprintf arguments, eg "world" and "Ozh" + * - if there are more arguments passed than needed, the last one will be used as the translation domain + * This function will not accept a textdomain argument: do not use in plugins or outside YOURLS core. + * + * @see sprintf() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $arg1, $arg2... Optional: sprintf tokens, and translation domain + * @return string Translated text + */ +function yourls_s( $pattern ) { + // Get pattern and pattern arguments + $args = func_get_args(); + // If yourls_s() called by yourls_se(), all arguments are wrapped in the same array key + if( count( $args ) == 1 && is_array( $args ) ) { + $args = $args[0]; + } + $pattern = $args[0]; + + // get list of sprintf tokens (%s and such) + $num_of_tokens = substr_count( $pattern, '%' ) - 2 * substr_count( $pattern, '%%' ); + + $domain = 'default'; + // More arguments passed than needed for the sprintf? The last one will be the domain + if( $num_of_tokens < ( count( $args ) - 1 ) ) { + $domain = array_pop( $args ); + } + + // Translate text + $args[0] = yourls__( $pattern, $domain ); + + return call_user_func_array( 'sprintf', $args ); +} + +/** + * Echo a translated sprintf() string (mix yourls__() and sprintf() in one func) + * + * Instead of doing printf( yourls__( 'string %s' ), $arg ) you can simply use: + * yourls_se( 'string %s', $arg ) + * This function accepts an arbitrary number of arguments: + * - first one will be the string to translate, eg "hello %s my name is %s" + * - following ones will be the sprintf arguments, eg "world" and "Ozh" + * - if there are more arguments passed than needed, the last one will be used as the translation domain + * + * @see yourls_s() + * @see sprintf() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $arg1, $arg2... Optional: sprintf tokens, and translation domain + * @return string Translated text + */ +function yourls_se( $pattern ) { + echo yourls_s( func_get_args() ); +} + + +/** + * Retrieves the translation of $text and escapes it for safe use in an attribute. + * If there is no translation, or the domain isn't loaded, the original text is returned. + * + * @see yourls_translate() An alias of yourls_translate() + * @see yourls_esc_attr() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + * @return string Translated text + */ +function yourls_esc_attr__( $text, $domain = 'default' ) { + return yourls_esc_attr( yourls_translate( $text, $domain ) ); +} + +/** + * Retrieves the translation of $text and escapes it for safe use in HTML output. + * If there is no translation, or the domain isn't loaded, the original text is returned. + * + * @see yourls_translate() An alias of yourls_translate() + * @see yourls_esc_html() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + * @return string Translated text + */ +function yourls_esc_html__( $text, $domain = 'default' ) { + return yourls_esc_html( yourls_translate( $text, $domain ) ); +} + +/** + * Displays the returned translated text from yourls_translate(). + * + * @see yourls_translate() Echoes returned yourls_translate() string + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + */ +function yourls_e( $text, $domain = 'default' ) { + echo yourls_translate( $text, $domain ); +} + +/** + * Displays translated text that has been escaped for safe use in an attribute. + * + * @see yourls_translate() Echoes returned yourls_translate() string + * @see yourls_esc_attr() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + */ +function yourls_esc_attr_e( $text, $domain = 'default' ) { + echo yourls_esc_attr( yourls_translate( $text, $domain ) ); +} + +/** + * Displays translated text that has been escaped for safe use in HTML output. + * + * @see yourls_translate() Echoes returned yourls_translate() string + * @see yourls_esc_html() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + */ +function yourls_esc_html_e( $text, $domain = 'default' ) { + echo yourls_esc_html( yourls_translate( $text, $domain ) ); +} + +/** + * Retrieve translated string with gettext context + * + * Quite a few times, there will be collisions with similar translatable text + * found in more than two places but with different translated context. + * + * By including the context in the pot file translators can translate the two + * strings differently. + * + * @since 1.6 + * + * @param string $text Text to translate + * @param string $context Context information for the translators + * @param string $domain Optional. Domain to retrieve the translated text + * @return string Translated context string without pipe + */ +function yourls_x( $text, $context, $domain = 'default' ) { + return yourls_translate_with_context( $text, $context, $domain ); +} + +/** + * Displays translated string with gettext context + * + * @see yourls_x() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $context Context information for the translators + * @param string $domain Optional. Domain to retrieve the translated text + * @return string Translated context string without pipe + */ +function yourls_ex( $text, $context, $domain = 'default' ) { + echo yourls_x( $text, $context, $domain ); +} + + +/** + * Return translated text, with context, that has been escaped for safe use in an attribute + * + * @see yourls_translate() Return returned yourls_translate() string + * @see yourls_esc_attr() + * @see yourls_x() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + */ +function yourls_esc_attr_x( $single, $context, $domain = 'default' ) { + return yourls_esc_attr( yourls_translate_with_context( $single, $context, $domain ) ); +} + +/** + * Return translated text, with context, that has been escaped for safe use in HTML output + * + * @see yourls_translate() Return returned yourls_translate() string + * @see yourls_esc_attr() + * @see yourls_x() + * @since 1.6 + * + * @param string $text Text to translate + * @param string $domain Optional. Domain to retrieve the translated text + */ +function yourls_esc_html_x( $single, $context, $domain = 'default' ) { + return yourls_esc_html( yourls_translate_with_context( $single, $context, $domain ) ); +} + +/** + * Retrieve the plural or single form based on the amount. + * + * If the domain is not set in the $yourls_l10n list, then a comparison will be made + * and either $plural or $single parameters returned. + * + * If the domain does exist, then the parameters $single, $plural, and $number + * will first be passed to the domain's ngettext method. Then it will be passed + * to the 'translate_n' filter hook along with the same parameters. The expected + * type will be a string. + * + * @since 1.6 + * @uses $yourls_l10n Gets list of domain translated string (gettext_reader) objects + * @uses yourls_apply_filters() Calls 'translate_n' hook on domains text returned, + * along with $single, $plural, and $number parameters. Expected to return string. + * + * @param string $single The text that will be used if $number is 1 + * @param string $plural The text that will be used if $number is not 1 + * @param int $number The number to compare against to use either $single or $plural + * @param string $domain Optional. The domain identifier the text should be retrieved in + * @return string Either $single or $plural translated text + */ +function yourls_n( $single, $plural, $number, $domain = 'default' ) { + $translations = yourls_get_translations_for_domain( $domain ); + $translation = $translations->translate_plural( $single, $plural, $number ); + return yourls_apply_filters( 'translate_n', $translation, $single, $plural, $number, $domain ); +} + +/** + * A hybrid of yourls_n() and yourls_x(). It supports contexts and plurals. + * + * @since 1.6 + * @see yourls_n() + * @see yourls_x() + * + */ +function yourls_nx($single, $plural, $number, $context, $domain = 'default') { + $translations = yourls_get_translations_for_domain( $domain ); + $translation = $translations->translate_plural( $single, $plural, $number, $context ); + return yourls_apply_filters( 'translate_nx', $translation, $single, $plural, $number, $context, $domain ); +} + +/** + * Register plural strings in POT file, but don't translate them. + * + * Used when you want to keep structures with translatable plural strings and + * use them later. + * + * Example: + * $messages = array( + * 'post' => yourls_n_noop('%s post', '%s posts'), + * 'page' => yourls_n_noop('%s pages', '%s pages') + * ); + * ... + * $message = $messages[$type]; + * $usable_text = sprintf( yourls_translate_nooped_plural( $message, $count ), $count ); + * + * @since 1.6 + * @param string $singular Single form to be i18ned + * @param string $plural Plural form to be i18ned + * @param string $domain Optional. The domain identifier the text will be retrieved in + * @return array array($singular, $plural) + */ +function yourls_n_noop( $singular, $plural, $domain = null ) { + return array( + 0 => $singular, + 1 => $plural, + 'singular' => $singular, + 'plural' => $plural, + 'context' => null, + 'domain' => $domain + ); +} + +/** + * Register plural strings with context in POT file, but don't translate them. + * + * @since 1.6 + * @see yourls_n_noop() + */ +function yourls_nx_noop( $singular, $plural, $context, $domain = null ) { + return array( + 0 => $singular, + 1 => $plural, + 2 => $context, + 'singular' => $singular, + 'plural' => $plural, + 'context' => $context, + 'domain' => $domain + ); +} + +/** + * Translate the result of yourls_n_noop() or yourls_nx_noop() + * + * @since 1.6 + * @param array $nooped_plural Array with singular, plural and context keys, usually the result of yourls_n_noop() or yourls_nx_noop() + * @param int $count Number of objects + * @param string $domain Optional. The domain identifier the text should be retrieved in. If $nooped_plural contains + * a domain passed to yourls_n_noop() or yourls_nx_noop(), it will override this value. + */ +function yourls_translate_nooped_plural( $nooped_plural, $count, $domain = 'default' ) { + if ( $nooped_plural['domain'] ) + $domain = $nooped_plural['domain']; + + if ( $nooped_plural['context'] ) + return yourls_nx( $nooped_plural['singular'], $nooped_plural['plural'], $count, $nooped_plural['context'], $domain ); + else + return yourls_n( $nooped_plural['singular'], $nooped_plural['plural'], $count, $domain ); +} + +/** + * Loads a MO file into the domain $domain. + * + * If the domain already exists, the translations will be merged. If both + * sets have the same string, the translation from the original value will be taken. + * + * On success, the .mo file will be placed in the $yourls_l10n global by $domain + * and will be a MO object. + * + * @since 1.6 + * @uses $yourls_l10n Gets list of domain translated string objects + * + * @param string $domain Unique identifier for retrieving translated strings + * @param string $mofile Path to the .mo file + * @return bool True on success, false on failure + */ +function yourls_load_textdomain( $domain, $mofile ) { + global $yourls_l10n; + + $plugin_override = yourls_apply_filters( 'override_load_textdomain', false, $domain, $mofile ); + + if ( true == $plugin_override ) { + return true; + } + + yourls_do_action( 'load_textdomain', $domain, $mofile ); + + $mofile = yourls_apply_filters( 'load_textdomain_mofile', $mofile, $domain ); + + if ( !is_readable( $mofile ) ) return false; + + $mo = new MO(); + if ( !$mo->import_from_file( $mofile ) ) return false; + + if ( isset( $yourls_l10n[$domain] ) ) + $mo->merge_with( $yourls_l10n[$domain] ); + + $yourls_l10n[$domain] = &$mo; + + return true; +} + +/** + * Unloads translations for a domain + * + * @since 1.6 + * @param string $domain Textdomain to be unloaded + * @return bool Whether textdomain was unloaded + */ +function yourls_unload_textdomain( $domain ) { + global $yourls_l10n; + + $plugin_override = yourls_apply_filters( 'override_unload_textdomain', false, $domain ); + + if ( $plugin_override ) + return true; + + yourls_do_action( 'unload_textdomain', $domain ); + + if ( isset( $yourls_l10n[$domain] ) ) { + unset( $yourls_l10n[$domain] ); + return true; + } + + return false; +} + +/** + * Loads default translated strings based on locale. + * + * Loads the .mo file in YOURLS_LANG_DIR constant path from YOURLS root. The + * translated (.mo) file is named based on the locale. + * + * @since 1.6 + */ +function yourls_load_default_textdomain() { + $yourls_locale = yourls_get_locale(); + + yourls_load_textdomain( 'default', YOURLS_LANG_DIR . "/$yourls_locale.mo" ); + +} + +/** + * Returns the Translations instance for a domain. If there isn't one, + * returns empty Translations instance. + * + * @param string $domain + * @return object A Translation instance + */ +function yourls_get_translations_for_domain( $domain ) { + global $yourls_l10n; + if ( !isset( $yourls_l10n[$domain] ) ) { + $yourls_l10n[$domain] = new NOOP_Translations; + } + return $yourls_l10n[$domain]; +} + +/** + * Whether there are translations for the domain + * + * @since 1.6 + * @param string $domain + * @return bool Whether there are translations + */ +function yourls_is_textdomain_loaded( $domain ) { + global $yourls_l10n; + return isset( $yourls_l10n[$domain] ); +} + +/** + * Translates role name. Unused. + * + * Unused function for the moment, we'll see when there are roles. + * From the WP source: Since the role names are in the database and + * not in the source there are dummy gettext calls to get them into the POT + * file and this function properly translates them back. + * + * @since 1.6 + */ +function yourls_translate_user_role( $name ) { + return yourls_translate_with_context( $name, 'User role' ); +} + +/** + * Get all available languages (*.mo files) in a given directory. The default directory is YOURLS_LANG_DIR. + * + * @since 1.6 + * + * @param string $dir A directory in which to search for language files. The default directory is YOURLS_LANG_DIR. + * @return array Array of language codes or an empty array if no languages are present. Language codes are formed by stripping the .mo extension from the language file names. + */ +function yourls_get_available_languages( $dir = null ) { + $languages = array(); + + $dir = is_null( $dir) ? YOURLS_LANG_DIR : $dir; + + foreach( (array) glob( $dir . '/*.mo' ) as $lang_file ) { + $languages[] = basename( $lang_file, '.mo' ); + } + + return yourls_apply_filters( 'get_available_languages', $languages ); +} + +/** + * Return integer number to format based on the locale. + * + * @since 1.6 + * + * @param int $number The number to convert based on locale. + * @param int $decimals Precision of the number of decimal places. + * @return string Converted number in string format. + */ +function yourls_number_format_i18n( $number, $decimals = 0 ) { + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + $formatted = number_format( $number, abs( intval( $decimals ) ), $yourls_locale_formats->number_format['decimal_point'], $yourls_locale_formats->number_format['thousands_sep'] ); + return yourls_apply_filters( 'number_format_i18n', $formatted ); +} + +/** + * Return the date in localized format, based on timestamp. + * + * If the locale specifies the locale month and weekday, then the locale will + * take over the format for the date. If it isn't, then the date format string + * will be used instead. + * + * @since 1.6 + * + * @param string $dateformatstring Format to display the date. + * @param int $unixtimestamp Optional. Unix timestamp. + * @param bool $gmt Optional, default is false. Whether to convert to GMT for time. + * @return string The date, translated if locale specifies it. + */ +function yourls_date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = false ) { + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + $i = $unixtimestamp; + + if ( false === $i ) { + if ( ! $gmt ) + $i = yourls_current_time( 'timestamp' ); + else + $i = time(); + // we should not let date() interfere with our + // specially computed timestamp + $gmt = true; + } + + // store original value for language with untypical grammars + // see http://core.trac.wordpress.org/ticket/9396 + $req_format = $dateformatstring; + + $datefunc = $gmt? 'gmdate' : 'date'; + + if ( ( !empty( $yourls_locale_formats->month ) ) && ( !empty( $yourls_locale_formats->weekday ) ) ) { + $datemonth = $yourls_locale_formats->get_month( $datefunc( 'm', $i ) ); + $datemonth_abbrev = $yourls_locale_formats->get_month_abbrev( $datemonth ); + $dateweekday = $yourls_locale_formats->get_weekday( $datefunc( 'w', $i ) ); + $dateweekday_abbrev = $yourls_locale_formats->get_weekday_abbrev( $dateweekday ); + $datemeridiem = $yourls_locale_formats->get_meridiem( $datefunc( 'a', $i ) ); + $datemeridiem_capital = $yourls_locale_formats->get_meridiem( $datefunc( 'A', $i ) ); + + $dateformatstring = ' '.$dateformatstring; + $dateformatstring = preg_replace( "/([^\\\])D/", "\\1" . yourls_backslashit( $dateweekday_abbrev ), $dateformatstring ); + $dateformatstring = preg_replace( "/([^\\\])F/", "\\1" . yourls_backslashit( $datemonth ), $dateformatstring ); + $dateformatstring = preg_replace( "/([^\\\])l/", "\\1" . yourls_backslashit( $dateweekday ), $dateformatstring ); + $dateformatstring = preg_replace( "/([^\\\])M/", "\\1" . yourls_backslashit( $datemonth_abbrev ), $dateformatstring ); + $dateformatstring = preg_replace( "/([^\\\])a/", "\\1" . yourls_backslashit( $datemeridiem ), $dateformatstring ); + $dateformatstring = preg_replace( "/([^\\\])A/", "\\1" . yourls_backslashit( $datemeridiem_capital ), $dateformatstring ); + + $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 ); + } + $timezone_formats = array( 'P', 'I', 'O', 'T', 'Z', 'e' ); + $timezone_formats_re = implode( '|', $timezone_formats ); + if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) { + + // TODO: implement a timezone option + $timezone_string = yourls_get_option( 'timezone_string' ); + if ( $timezone_string ) { + $timezone_object = timezone_open( $timezone_string ); + $date_object = date_create( null, $timezone_object ); + foreach( $timezone_formats as $timezone_format ) { + if ( false !== strpos( $dateformatstring, $timezone_format ) ) { + $formatted = date_format( $date_object, $timezone_format ); + $dateformatstring = ' '.$dateformatstring; + $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . yourls_backslashit( $formatted ), $dateformatstring ); + $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 ); + } + } + } + } + $j = @$datefunc( $dateformatstring, $i ); + // allow plugins to redo this entirely for languages with untypical grammars + $j = yourls_apply_filters('date_i18n', $j, $req_format, $i, $gmt); + return $j; +} + +/** + * Retrieve the current time based on specified type. Stolen from WP. + * + * The 'mysql' type will return the time in the format for MySQL DATETIME field. + * The 'timestamp' type will return the current timestamp. + * + * If $gmt is set to either '1' or 'true', then both types will use GMT time. + * if $gmt is false, the output is adjusted with the GMT offset in the WordPress option. + * + * @since 1.6 + * + * @param string $type Either 'mysql' or 'timestamp'. + * @param int|bool $gmt Optional. Whether to use GMT timezone. Default is false. + * @return int|string String if $type is 'gmt', int if $type is 'timestamp'. + */ +function yourls_current_time( $type, $gmt = 0 ) { + switch ( $type ) { + case 'mysql': + return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', time() + YOURLS_HOURS_OFFSET * 3600 ); + break; + case 'timestamp': + return ( $gmt ) ? time() : time() + YOURLS_HOURS_OFFSET * 3600; + break; + } +} + + +/** + * Class that loads the calendar locale. + * + * @since 1.6 + */ +class YOURLS_Locale_Formats { + /** + * Stores the translated strings for the full weekday names. + * + * @since 1.6 + * @var array + * @access private + */ + var $weekday; + + /** + * Stores the translated strings for the one character weekday names. + * + * There is a hack to make sure that Tuesday and Thursday, as well + * as Sunday and Saturday, don't conflict. See init() method for more. + * + * @see YOURLS_Locale_Formats::init() for how to handle the hack. + * + * @since 1.6 + * @var array + * @access private + */ + var $weekday_initial; + + /** + * Stores the translated strings for the abbreviated weekday names. + * + * @since 1.6 + * @var array + * @access private + */ + var $weekday_abbrev; + + /** + * Stores the translated strings for the full month names. + * + * @since 1.6 + * @var array + * @access private + */ + var $month; + + /** + * Stores the translated strings for the abbreviated month names. + * + * @since 1.6 + * @var array + * @access private + */ + var $month_abbrev; + + /** + * Stores the translated strings for 'am' and 'pm'. + * + * Also the capitalized versions. + * + * @since 1.6 + * @var array + * @access private + */ + var $meridiem; + + /** + * The text direction of the locale language. + * + * Default is left to right 'ltr'. + * + * @since 1.6 + * @var string + * @access private + */ + var $text_direction = 'ltr'; + + /** + * Sets up the translated strings and object properties. + * + * The method creates the translatable strings for various + * calendar elements. Which allows for specifying locale + * specific calendar names and text direction. + * + * @since 1.6 + * @access private + */ + function init() { + // The Weekdays + $this->weekday[0] = /* //translators: weekday */ yourls__( 'Sunday' ); + $this->weekday[1] = /* //translators: weekday */ yourls__( 'Monday' ); + $this->weekday[2] = /* //translators: weekday */ yourls__( 'Tuesday' ); + $this->weekday[3] = /* //translators: weekday */ yourls__( 'Wednesday' ); + $this->weekday[4] = /* //translators: weekday */ yourls__( 'Thursday' ); + $this->weekday[5] = /* //translators: weekday */ yourls__( 'Friday' ); + $this->weekday[6] = /* //translators: weekday */ yourls__( 'Saturday' ); + + // The first letter of each day. The _%day%_initial suffix is a hack to make + // sure the day initials are unique. + $this->weekday_initial[yourls__( 'Sunday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'S_Sunday_initial' ); + $this->weekday_initial[yourls__( 'Monday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'M_Monday_initial' ); + $this->weekday_initial[yourls__( 'Tuesday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'T_Tuesday_initial' ); + $this->weekday_initial[yourls__( 'Wednesday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'W_Wednesday_initial' ); + $this->weekday_initial[yourls__( 'Thursday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'T_Thursday_initial' ); + $this->weekday_initial[yourls__( 'Friday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'F_Friday_initial' ); + $this->weekday_initial[yourls__( 'Saturday' )] = /* //translators: one-letter abbreviation of the weekday */ yourls__( 'S_Saturday_initial' ); + + foreach ($this->weekday_initial as $weekday_ => $weekday_initial_) { + $this->weekday_initial[$weekday_] = preg_replace('/_.+_initial$/', '', $weekday_initial_); + } + + // Abbreviations for each day. + $this->weekday_abbrev[ yourls__( 'Sunday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Sun' ); + $this->weekday_abbrev[ yourls__( 'Monday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Mon' ); + $this->weekday_abbrev[ yourls__( 'Tuesday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Tue' ); + $this->weekday_abbrev[ yourls__( 'Wednesday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Wed' ); + $this->weekday_abbrev[ yourls__( 'Thursday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Thu' ); + $this->weekday_abbrev[ yourls__( 'Friday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Fri' ); + $this->weekday_abbrev[ yourls__( 'Saturday' ) ] = /* //translators: three-letter abbreviation of the weekday */ yourls__( 'Sat' ); + + // The Months + $this->month['01'] = /* //translators: month name */ yourls__( 'January' ); + $this->month['02'] = /* //translators: month name */ yourls__( 'February' ); + $this->month['03'] = /* //translators: month name */ yourls__( 'March' ); + $this->month['04'] = /* //translators: month name */ yourls__( 'April' ); + $this->month['05'] = /* //translators: month name */ yourls__( 'May' ); + $this->month['06'] = /* //translators: month name */ yourls__( 'June' ); + $this->month['07'] = /* //translators: month name */ yourls__( 'July' ); + $this->month['08'] = /* //translators: month name */ yourls__( 'August' ); + $this->month['09'] = /* //translators: month name */ yourls__( 'September' ); + $this->month['10'] = /* //translators: month name */ yourls__( 'October' ); + $this->month['11'] = /* //translators: month name */ yourls__( 'November' ); + $this->month['12'] = /* //translators: month name */ yourls__( 'December' ); + + // Abbreviations for each month. Uses the same hack as above to get around the + // 'May' duplication. + $this->month_abbrev[ yourls__( 'January' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jan_January_abbreviation' ); + $this->month_abbrev[ yourls__( 'February' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Feb_February_abbreviation' ); + $this->month_abbrev[ yourls__( 'March' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Mar_March_abbreviation' ); + $this->month_abbrev[ yourls__( 'April' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Apr_April_abbreviation' ); + $this->month_abbrev[ yourls__( 'May' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'May_May_abbreviation' ); + $this->month_abbrev[ yourls__( 'June' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jun_June_abbreviation' ); + $this->month_abbrev[ yourls__( 'July' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Jul_July_abbreviation' ); + $this->month_abbrev[ yourls__( 'August' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Aug_August_abbreviation' ); + $this->month_abbrev[ yourls__( 'September' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Sep_September_abbreviation' ); + $this->month_abbrev[ yourls__( 'October' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Oct_October_abbreviation' ); + $this->month_abbrev[ yourls__( 'November' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Nov_November_abbreviation' ); + $this->month_abbrev[ yourls__( 'December' ) ] = /* //translators: three-letter abbreviation of the month */ yourls__( 'Dec_December_abbreviation' ); + + foreach ($this->month_abbrev as $month_ => $month_abbrev_) { + $this->month_abbrev[$month_] = preg_replace('/_.+_abbreviation$/', '', $month_abbrev_); + } + + // The Meridiems + $this->meridiem['am'] = yourls__( 'am' ); + $this->meridiem['pm'] = yourls__( 'pm' ); + $this->meridiem['AM'] = yourls__( 'AM' ); + $this->meridiem['PM'] = yourls__( 'PM' ); + + // Numbers formatting + // See http://php.net/number_format + + /* //translators: $thousands_sep argument for http://php.net/number_format, default is , */ + $trans = yourls__( 'number_format_thousands_sep' ); + $this->number_format['thousands_sep'] = ('number_format_thousands_sep' == $trans) ? ',' : $trans; + + /* //translators: $dec_point argument for http://php.net/number_format, default is . */ + $trans = yourls__( 'number_format_decimal_point' ); + $this->number_format['decimal_point'] = ('number_format_decimal_point' == $trans) ? '.' : $trans; + + // Set text direction. + if ( isset( $GLOBALS['text_direction'] ) ) + $this->text_direction = $GLOBALS['text_direction']; + /* //translators: 'rtl' or 'ltr'. This sets the text direction for YOURLS. */ + elseif ( 'rtl' == yourls_x( 'ltr', 'text direction' ) ) + $this->text_direction = 'rtl'; + } + + /** + * Retrieve the full translated weekday word. + * + * Week starts on translated Sunday and can be fetched + * by using 0 (zero). So the week starts with 0 (zero) + * and ends on Saturday with is fetched by using 6 (six). + * + * @since 1.6 + * @access public + * + * @param int $weekday_number 0 for Sunday through 6 Saturday + * @return string Full translated weekday + */ + function get_weekday( $weekday_number ) { + return $this->weekday[ $weekday_number ]; + } + + /** + * Retrieve the translated weekday initial. + * + * The weekday initial is retrieved by the translated + * full weekday word. When translating the weekday initial + * pay attention to make sure that the starting letter does + * not conflict. + * + * @since 1.6 + * @access public + * + * @param string $weekday_name + * @return string + */ + function get_weekday_initial( $weekday_name ) { + return $this->weekday_initial[ $weekday_name ]; + } + + /** + * Retrieve the translated weekday abbreviation. + * + * The weekday abbreviation is retrieved by the translated + * full weekday word. + * + * @since 1.6 + * @access public + * + * @param string $weekday_name Full translated weekday word + * @return string Translated weekday abbreviation + */ + function get_weekday_abbrev( $weekday_name ) { + return $this->weekday_abbrev[ $weekday_name ]; + } + + /** + * Retrieve the full translated month by month number. + * + * The $month_number parameter has to be a string + * because it must have the '0' in front of any number + * that is less than 10. Starts from '01' and ends at + * '12'. + * + * You can use an integer instead and it will add the + * '0' before the numbers less than 10 for you. + * + * @since 1.6 + * @access public + * + * @param string|int $month_number '01' through '12' + * @return string Translated full month name + */ + function get_month( $month_number ) { + return $this->month[ sprintf( '%02s', $month_number ) ]; + } + + /** + * Retrieve translated version of month abbreviation string. + * + * The $month_name parameter is expected to be the translated or + * translatable version of the month. + * + * @since 1.6 + * @access public + * + * @param string $month_name Translated month to get abbreviated version + * @return string Translated abbreviated month + */ + function get_month_abbrev( $month_name ) { + return $this->month_abbrev[ $month_name ]; + } + + /** + * Retrieve translated version of meridiem string. + * + * The $meridiem parameter is expected to not be translated. + * + * @since 1.6 + * @access public + * + * @param string $meridiem Either 'am', 'pm', 'AM', or 'PM'. Not translated version. + * @return string Translated version + */ + function get_meridiem( $meridiem ) { + return $this->meridiem[ $meridiem ]; + } + + /** + * Global variables are deprecated. For backwards compatibility only. + * + * @deprecated For backwards compatibility only. + * @access private + * + * @since 1.6 + */ + function register_globals() { + $GLOBALS['weekday'] = $this->weekday; + $GLOBALS['weekday_initial'] = $this->weekday_initial; + $GLOBALS['weekday_abbrev'] = $this->weekday_abbrev; + $GLOBALS['month'] = $this->month; + $GLOBALS['month_abbrev'] = $this->month_abbrev; + } + + /** + * Constructor which calls helper methods to set up object variables + * + * @uses YOURLS_Locale_Formats::init() + * @uses YOURLS_Locale_Formats::register_globals() + * @since 1.6 + * + * @return YOURLS_Locale_Formats + */ + function __construct() { + $this->init(); + $this->register_globals(); + } + + /** + * Checks if current locale is RTL. + * + * @since 1.6 + * @return bool Whether locale is RTL. + */ + function is_rtl() { + return 'rtl' == $this->text_direction; + } +} + +/** + * Loads a custom translation file (for a plugin, a theme, a public interface...) + * + * The .mo file should be named based on the domain with a dash, and then the locale exactly, + * eg 'myplugin-pt_BR.mo' + * + * @since 1.6 + * + * @param string $domain Unique identifier (the "domain") for retrieving translated strings + * @param string $path Full path to directory containing MO files. + */ +function yourls_load_custom_textdomain( $domain, $path ) { + $locale = yourls_apply_filters( 'load_custom_textdomain', yourls_get_locale(), $domain ); + $mofile = trim( $path, '/' ) . '/'. $domain . '-' . $locale . '.mo'; + + return yourls_load_textdomain( $domain, $mofile ); +} + +/** + * Checks if current locale is RTL. Stolen from WP. + * + * @since 1.6 + * @return bool Whether locale is RTL. + */ +function yourls_is_rtl() { + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + return $yourls_locale_formats->is_rtl(); +} + +/** + * Return translated weekday abbreviation (3 letters, eg 'Fri' for 'Friday') + * + * The $weekday var can be a textual string ('Friday'), a integer (0 to 6) or an empty string + * If $weekday is an empty string, the function returns an array of all translated weekday abbrev + * + * @since 1.6 + * @param mixed $weekday A full textual weekday, eg "Friday", or an integer (0 = Sunday, 1 = Monday, .. 6 = Saturday) + * @return mixed Translated weekday abbreviation, eg "Ven" (abbrev of "Vendredi") for "Friday" or 5, or array of all weekday abbrev + */ +function yourls_l10n_weekday_abbrev( $weekday = '' ){ + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + if( $weekday === '' ) + return $yourls_locale_formats->weekday_abbrev; + + if( is_int( $weekday ) ) { + $day = $yourls_locale_formats->weekday[ $weekday ]; + return $yourls_locale_formats->weekday_abbrev[ $day ]; + } else { + return $yourls_locale_formats->weekday_abbrev[ yourls__( $weekday ) ]; + } +} + +/** + * Return translated weekday initial (1 letter, eg 'F' for 'Friday') + * + * The $weekday var can be a textual string ('Friday'), a integer (0 to 6) or an empty string + * If $weekday is an empty string, the function returns an array of all translated weekday initials + * + * @since 1.6 + * @param mixed $weekday A full textual weekday, eg "Friday", an integer (0 = Sunday, 1 = Monday, .. 6 = Saturday) or empty string + * @return mixed Translated weekday initial, eg "V" (initial of "Vendredi") for "Friday" or 5, or array of all weekday initials + */ +function yourls_l10n_weekday_initial( $weekday = '' ){ + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + if( $weekday === '' ) + return $yourls_locale_formats->weekday_initial; + + if( is_int( $weekday ) ) { + $weekday = $yourls_locale_formats->weekday[ $weekday ]; + return $yourls_locale_formats->weekday_initial[ $weekday ]; + } else { + return $yourls_locale_formats->weekday_initial[ yourls__( $weekday ) ]; + } +} + +/** + * Return translated month abbrevation (3 letters, eg 'Nov' for 'November') + * + * The $month var can be a textual string ('November'), a integer (1 to 12), a two digits strings ('01' to '12), or an empty string + * If $month is an empty string, the function returns an array of all translated abbrev months ('January' => 'Jan', ...) + * + * @since 1.6 + * @param mixed $month Empty string, a full textual weekday, eg "November", or an integer (1 = January, .., 12 = December) + * @return mixed Translated month abbrev (eg "Nov"), or array of all translated abbrev months + */ +function yourls_l10n_month_abbrev( $month = '' ){ + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + if( $month === '' ) + return $yourls_locale_formats->month_abbrev; + + if( intval( $month ) > 0 ) { + $month = $yourls_locale_formats->month[ $month ]; + return $yourls_locale_formats->month_abbrev[ $month ]; + } else { + return $yourls_locale_formats->month_abbrev[ yourls__( $month ) ]; + } +} + +/** + * Return array of all translated months + * + * @since 1.6 + * @return array Array of all translated months + */ +function yourls_l10n_months(){ + global $yourls_locale_formats; + if( !isset( $yourls_locale_formats ) ) + $yourls_locale_formats = new YOURLS_Locale_Formats(); + + return $yourls_locale_formats->month; +} diff --git a/includes/functions-plugins.php b/includes/functions-plugins.php index 7f1ba9a..cd59515 100644 --- a/includes/functions-plugins.php +++ b/includes/functions-plugins.php @@ -1,579 +1,579 @@ - $function_name, - 'accepted_args' => $accepted_args, - 'type' => $type, - ); -} - -/** - * Hooks a function on to a specific action. - * - * Actions are the hooks that YOURLS launches at specific points - * during execution, or when specific events occur. Plugins can specify that - * one or more of its PHP functions are executed at these points, using the - * Action API. - * - * @param string $hook The name of the action to which the $function_to_add is hooked. - * @param callback $function_name The name of the function you wish to be called. - * @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action. - * @param int $accepted_args optional. The number of arguments the function accept (default 1). - */ -function yourls_add_action( $hook, $function_name, $priority = 10, $accepted_args = 1 ) { - return yourls_add_filter( $hook, $function_name, $priority, $accepted_args, 'action' ); -} - - - -/** - * Build Unique ID for storage and retrieval. - * - * Simply using a function name is not enough, as several functions can have the same name when they are enclosed in classes. - * - * @global array $yourls_filters storage for all of the filters - * @param string $hook hook to which the function is attached - * @param string|array $function used for creating unique id - * @param int|bool $priority used in counting how many hooks were applied. If === false and $function is an object reference, we return the unique id only if it already has one, false otherwise. - * @param string $type filter or action - * @return string unique ID for usage as array key - */ -function yourls_filter_unique_id( $hook, $function, $priority ) { - global $yourls_filters; - - // If function then just skip all of the tests and not overwrite the following. - if ( is_string( $function ) ) - return $function; - // Object Class Calling - else if ( is_object( $function[0] ) ) { - $obj_idx = get_class( $function[0] ) . $function[1]; - if ( !isset( $function[0]->_yourls_filters_id ) ) { - if ( false === $priority ) - return false; - $count = isset( $yourls_filters[ $hook ][ $priority ]) ? count( (array)$yourls_filters[ $hook ][ $priority ] ) : 0; - $function[0]->_yourls_filters_id = $count; - $obj_idx .= $count; - unset( $count ); - } else - $obj_idx .= $function[0]->_yourls_filters_id; - return $obj_idx; - } - // Static Calling - else if ( is_string( $function[0] ) ) - return $function[0].$function[1]; - -} - -/** - * Performs a filtering operation on a YOURLS element or event. - * - * Typical use: - * - * 1) Modify a variable if a function is attached to hook 'yourls_hook' - * $yourls_var = "default value"; - * $yourls_var = yourls_apply_filter( 'yourls_hook', $yourls_var ); - * - * 2) Trigger functions is attached to event 'yourls_event' - * yourls_apply_filter( 'yourls_event' ); - * (see yourls_do_action() ) - * - * Returns an element which may have been filtered by a filter. - * - * @global array $yourls_filters storage for all of the filters - * @param string $hook the name of the YOURLS element or action - * @param mixed $value the value of the element before filtering - * @return mixed - */ -function yourls_apply_filter( $hook, $value = '' ) { - global $yourls_filters; - if ( !isset( $yourls_filters[ $hook ] ) ) - return $value; - - $args = func_get_args(); - - // Sort filters by priority - ksort( $yourls_filters[ $hook ] ); - - // Loops through each filter - reset( $yourls_filters[ $hook ] ); - do { - foreach( (array) current( $yourls_filters[ $hook ] ) as $the_ ) { - if ( !is_null( $the_['function'] ) ){ - $args[1] = $value; - $count = $the_['accepted_args']; - if ( is_null( $count ) ) { - $_value = call_user_func_array( $the_['function'], array_slice( $args, 1 ) ); - } else { - $_value = call_user_func_array( $the_['function'], array_slice( $args, 1, (int) $count ) ); - } - } - if( $the_['type'] == 'filter' ) - $value = $_value; - } - - } while ( next( $yourls_filters[ $hook ] ) !== false ); - - if( $the_['type'] == 'filter' ) - return $value; -} - -/** - * Alias for yourls_apply_filter because I never remember if it's _filter or _filters - * - * Plus, semantically, it makes more sense. There can be several filters. I should have named it - * like this from the very start. Duh. - * - * @since 1.6 - * - * @param string $hook the name of the YOURLS element or action - * @param mixed $value the value of the element before filtering - * @return mixed - */ -function yourls_apply_filters( $hook, $value = '' ) { - return yourls_apply_filter( $hook, $value ); -} - - -/** - * Performs an action triggered by a YOURLS event. -* - * @param string $hook the name of the YOURLS action - * @param mixed $arg action arguments - */ -function yourls_do_action( $hook, $arg = '' ) { - global $yourls_actions; - - // Keep track of actions that are "done" - if ( !isset( $yourls_actions ) ) - $yourls_actions = array(); - if ( !isset( $yourls_actions[ $hook ] ) ) - $yourls_actions[ $hook ] = 1; - else - ++$yourls_actions[ $hook ]; - - $args = array(); - if ( is_array( $arg ) && 1 == count( $arg ) && isset( $arg[0] ) && is_object( $arg[0] ) ) // array(&$this) - $args[] =& $arg[0]; - else - $args[] = $arg; - for ( $a = 2; $a < func_num_args(); $a++ ) - $args[] = func_get_arg( $a ); - - yourls_apply_filter( $hook, $args ); -} - -/** -* Retrieve the number times an action is fired. -* -* @param string $hook Name of the action hook. -* @return int The number of times action hook $hook is fired -*/ -function yourls_did_action( $hook ) { - global $yourls_actions; - if ( !isset( $yourls_actions ) || !isset( $yourls_actions[ $hook ] ) ) - return 0; - return $yourls_actions[ $hook ]; -} - -/** - * Removes a function from a specified filter hook. - * - * This function removes a function attached to a specified filter hook. This - * method can be used to remove default functions attached to a specific filter - * hook and possibly replace them with a substitute. - * - * To remove a hook, the $function_to_remove and $priority arguments must match - * when the hook was added. - * - * @global array $yourls_filters storage for all of the filters - * @param string $hook The filter hook to which the function to be removed is hooked. - * @param callback $function_to_remove The name of the function which should be removed. - * @param int $priority optional. The priority of the function (default: 10). - * @param int $accepted_args optional. The number of arguments the function accepts (default: 1). - * @return boolean Whether the function was registered as a filter before it was removed. - */ -function yourls_remove_filter( $hook, $function_to_remove, $priority = 10, $accepted_args = 1 ) { - global $yourls_filters; - - $function_to_remove = yourls_filter_unique_id( $hook, $function_to_remove, $priority ); - - $remove = isset( $yourls_filters[ $hook ][ $priority ][ $function_to_remove ] ); - - if ( $remove === true ) { - unset ( $yourls_filters[$hook][$priority][$function_to_remove] ); - if ( empty( $yourls_filters[$hook][$priority] ) ) - unset( $yourls_filters[$hook] ); - } - return $remove; -} - - -/** - * Check if any filter has been registered for a hook. - * - * @global array $yourls_filters storage for all of the filters - * @param string $hook The name of the filter hook. - * @param callback $function_to_check optional. If specified, return the priority of that function on this hook or false if not attached. - * @return int|boolean Optionally returns the priority on that hook for the specified function. - */ -function yourls_has_filter( $hook, $function_to_check = false ) { - global $yourls_filters; - - $has = !empty( $yourls_filters[ $hook ] ); - if ( false === $function_to_check || false == $has ) { - return $has; - } - if ( !$idx = yourls_filter_unique_id( $hook, $function_to_check, false ) ) { - return false; - } - - foreach ( (array) array_keys( $yourls_filters[ $hook ] ) as $priority ) { - if ( isset( $yourls_filters[ $hook ][ $priority ][ $idx ] ) ) - return $priority; - } - return false; -} - -function yourls_has_action( $hook, $function_to_check = false ) { - return yourls_has_filter( $hook, $function_to_check ); -} - -/** - * Return number of active plugins - * - * @return integer Number of activated plugins - */ -function yourls_has_active_plugins( ) { - global $ydb; - - if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins ) - $ydb->plugins = array(); - - return count( $ydb->plugins ); -} - - -/** - * List plugins in /user/plugins - * - * @global $ydb Storage of mostly everything YOURLS needs to know - * @return array Array of [/plugindir/plugin.php]=>array('Name'=>'Ozh', 'Title'=>'Hello', ) - */ -function yourls_get_plugins( ) { - global $ydb; - - $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php'); - - if( !$plugins ) - return array(); - - foreach( $plugins as $key => $plugin ) { - $_plugin = yourls_plugin_basename( $plugin ); - $plugins[ $_plugin ] = yourls_get_plugin_data( $plugin ); - unset( $plugins[ $key ] ); - } - - return $plugins; -} - -/** - * Check if a plugin is active - * - * @param string $file Physical path to plugin file - * @return bool - */ -function yourls_is_active_plugin( $plugin ) { - if( !yourls_has_active_plugins( ) ) - return false; - - global $ydb; - $plugin = yourls_plugin_basename( $plugin ); - - return in_array( $plugin, $ydb->plugins ); - -} - -/** - * Parse a plugin header - * - * @param string $file Physical path to plugin file - * @return array Array of 'Field'=>'Value' from plugin comment header lines of the form "Field: Value" - */ -function yourls_get_plugin_data( $file ) { - $fp = fopen( $file, 'r' ); // assuming $file is readable, since yourls_load_plugins() filters this - $data = fread( $fp, 8192 ); // get first 8kb - fclose( $fp ); - - // Capture all the header within first comment block - if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) ) - return array(); - - // Capture each line with "Something: some text" - unset( $data ); - $lines = preg_split( "[\n|\r]", $matches[1] ); - unset( $matches ); - - $plugin_data = array(); - foreach( $lines as $line ) { - if( !preg_match( '!(.*?):\s+(.*)!', $line, $matches ) ) - continue; - - list( $null, $field, $value ) = array_map( 'trim', $matches); - $plugin_data[ $field ] = $value; - } - - return $plugin_data; -} - -// Include active plugins -function yourls_load_plugins() { - global $ydb; - $ydb->plugins = array(); - $active_plugins = yourls_get_option( 'active_plugins' ); - - // Don't load plugins when installing or updating - if( !$active_plugins OR yourls_is_installing() OR yourls_upgrade_is_needed() ) - return; - - foreach( (array)$active_plugins as $key=>$plugin ) { - if( yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$plugin ) ) { - include_once( YOURLS_PLUGINDIR.'/'.$plugin ); - $ydb->plugins[] = $plugin; - unset( $active_plugins[$key] ); - } - } - - // $active_plugins should be empty now, if not, a plugin could not be find: remove it - if( count( $active_plugins ) ) { - yourls_update_option( 'active_plugins', $ydb->plugins ); - $message = yourls_n( 'Could not find and deactivated plugin :', 'Could not find and deactivated plugins :', count( $active_plugins ) ); - $missing = ''.join( ', ', $active_plugins ).''; - yourls_add_notice( $message .' '. $missing ); - } -} - -/** - * Check if a file is safe for inclusion (well, "safe", no guarantee) - * - * @param string $file Full pathname to a file - */ -function yourls_validate_plugin_file( $file ) { - if ( - false !== strpos( $file, '..' ) - OR - false !== strpos( $file, './' ) - OR - 'plugin.php' !== substr( $file, -10 ) // a plugin must be named 'plugin.php' - OR - !is_readable( $file ) - ) - return false; - - return true; -} - -/** - * Activate a plugin - * - * @param string $plugin Plugin filename (full or relative to plugins directory) - * @return mixed string if error or true if success - */ -function yourls_activate_plugin( $plugin ) { - // validate file - $plugin = yourls_plugin_basename( $plugin ); - $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR ); - if( !yourls_validate_plugin_file( $plugindir.'/'.$plugin ) ) - return yourls__( 'Not a valid plugin file' ); - - // check not activated already - global $ydb; - if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) ) - return yourls__( 'Plugin already activated' ); - - // attempt activation. TODO: uber cool fail proof sandbox like in WP. - ob_start(); - include( YOURLS_PLUGINDIR.'/'.$plugin ); - if ( ob_get_length() > 0 ) { - // there was some output: error - $output = ob_get_clean(); - return yourls_s( 'Plugin generated unexpected output. Error was:
%s
', $output ); - } - - // so far, so good: update active plugin list - $ydb->plugins[] = $plugin; - yourls_update_option( 'active_plugins', $ydb->plugins ); - yourls_do_action( 'activated_plugin', $plugin ); - yourls_do_action( 'activated_' . $plugin ); - - return true; -} - -/** - * Dectivate a plugin - * - * @param string $plugin Plugin filename (full relative to plugins directory) - * @return mixed string if error or true if success - */ -function yourls_deactivate_plugin( $plugin ) { - $plugin = yourls_plugin_basename( $plugin ); - - // Check plugin is active - if( !yourls_is_active_plugin( $plugin ) ) - return yourls__( 'Plugin not active' ); - - // Deactivate the plugin - global $ydb; - $key = array_search( $plugin, $ydb->plugins ); - if( $key !== false ) { - array_splice( $ydb->plugins, $key, 1 ); - } - - yourls_update_option( 'active_plugins', $ydb->plugins ); - yourls_do_action( 'deactivated_plugin', $plugin ); - yourls_do_action( 'deactivated_' . $plugin ); - - return true; -} - -/** - * Return the path of a plugin file, relative to the plugins directory - */ -function yourls_plugin_basename( $file ) { - $file = yourls_sanitize_filename( $file ); - $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR ); - $file = str_replace( $plugindir, '', $file ); - return trim( $file, '/' ); -} - -/** - * Return the URL of the directory a plugin - */ -function yourls_plugin_url( $file ) { - $url = YOURLS_PLUGINURL . '/' . yourls_plugin_basename( $file ); - if( yourls_is_ssl() or yourls_needs_ssl() ) - $url = str_replace( 'http://', 'https://', $url ); - return yourls_apply_filter( 'plugin_url', $url, $file ); -} - -/** - * Display list of links to plugin admin pages, if any - */ -function yourls_list_plugin_admin_pages() { - global $ydb; - - if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages ) - return; - - $plugin_links = array(); - foreach( (array)$ydb->plugin_pages as $plugin => $page ) { - $plugin_links[ $plugin ] = array( - 'url' => yourls_admin_url( 'plugins.php?page='.$page['slug'] ), - 'anchor' => $page['title'], - ); - } - return $plugin_links; -} - -/** - * Register a plugin administration page - */ -function yourls_register_plugin_page( $slug, $title, $function ) { - global $ydb; - - if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages ) - $ydb->plugin_pages = array(); - - $ydb->plugin_pages[ $slug ] = array( - 'slug' => $slug, - 'title' => $title, - 'function' => $function, - ); -} - -/** - * Handle plugin administration page - * - */ -function yourls_plugin_admin_page( $plugin_page ) { - global $ydb; - - // Check the plugin page is actually registered - if( !isset( $ydb->plugin_pages[$plugin_page] ) ) { - yourls_die( yourls__( 'This page does not exist. Maybe a plugin you thought was activated is inactive?' ), yourls__( 'Invalid link' ) ); - } - - // Draw the page itself - yourls_do_action( 'load-' . $plugin_page); - yourls_html_head( 'plugin_page_' . $plugin_page, $ydb->plugin_pages[$plugin_page]['title'] ); - yourls_html_logo(); - yourls_html_menu(); - - call_user_func( $ydb->plugin_pages[$plugin_page]['function'] ); - - yourls_html_footer(); - - die(); -} - - -/** - * Callback function: Sort plugins - * - * @link http://php.net/uasort - * - * @param array $plugin_a - * @param array $plugin_b - * @return int 0, 1 or -1, see uasort() - */ -function yourls_plugins_sort_callback( $plugin_a, $plugin_b ) { - $orderby = yourls_apply_filters( 'plugins_sort_callback', 'Plugin Name' ); - $order = yourls_apply_filters( 'plugins_sort_callback', 'ASC' ); - - $a = $plugin_a[$orderby]; - $b = $plugin_b[$orderby]; - - if ( $a == $b ) - return 0; - - if ( 'DESC' == $order ) - return ( $a < $b ) ? 1 : -1; - else - return ( $a < $b ) ? -1 : 1; -} + $function_name, + 'accepted_args' => $accepted_args, + 'type' => $type, + ); +} + +/** + * Hooks a function on to a specific action. + * + * Actions are the hooks that YOURLS launches at specific points + * during execution, or when specific events occur. Plugins can specify that + * one or more of its PHP functions are executed at these points, using the + * Action API. + * + * @param string $hook The name of the action to which the $function_to_add is hooked. + * @param callback $function_name The name of the function you wish to be called. + * @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action. + * @param int $accepted_args optional. The number of arguments the function accept (default 1). + */ +function yourls_add_action( $hook, $function_name, $priority = 10, $accepted_args = 1 ) { + return yourls_add_filter( $hook, $function_name, $priority, $accepted_args, 'action' ); +} + + + +/** + * Build Unique ID for storage and retrieval. + * + * Simply using a function name is not enough, as several functions can have the same name when they are enclosed in classes. + * + * @global array $yourls_filters storage for all of the filters + * @param string $hook hook to which the function is attached + * @param string|array $function used for creating unique id + * @param int|bool $priority used in counting how many hooks were applied. If === false and $function is an object reference, we return the unique id only if it already has one, false otherwise. + * @param string $type filter or action + * @return string unique ID for usage as array key + */ +function yourls_filter_unique_id( $hook, $function, $priority ) { + global $yourls_filters; + + // If function then just skip all of the tests and not overwrite the following. + if ( is_string( $function ) ) + return $function; + // Object Class Calling + else if ( is_object( $function[0] ) ) { + $obj_idx = get_class( $function[0] ) . $function[1]; + if ( !isset( $function[0]->_yourls_filters_id ) ) { + if ( false === $priority ) + return false; + $count = isset( $yourls_filters[ $hook ][ $priority ]) ? count( (array)$yourls_filters[ $hook ][ $priority ] ) : 0; + $function[0]->_yourls_filters_id = $count; + $obj_idx .= $count; + unset( $count ); + } else + $obj_idx .= $function[0]->_yourls_filters_id; + return $obj_idx; + } + // Static Calling + else if ( is_string( $function[0] ) ) + return $function[0].$function[1]; + +} + +/** + * Performs a filtering operation on a YOURLS element or event. + * + * Typical use: + * + * 1) Modify a variable if a function is attached to hook 'yourls_hook' + * $yourls_var = "default value"; + * $yourls_var = yourls_apply_filter( 'yourls_hook', $yourls_var ); + * + * 2) Trigger functions is attached to event 'yourls_event' + * yourls_apply_filter( 'yourls_event' ); + * (see yourls_do_action() ) + * + * Returns an element which may have been filtered by a filter. + * + * @global array $yourls_filters storage for all of the filters + * @param string $hook the name of the YOURLS element or action + * @param mixed $value the value of the element before filtering + * @return mixed + */ +function yourls_apply_filter( $hook, $value = '' ) { + global $yourls_filters; + if ( !isset( $yourls_filters[ $hook ] ) ) + return $value; + + $args = func_get_args(); + + // Sort filters by priority + ksort( $yourls_filters[ $hook ] ); + + // Loops through each filter + reset( $yourls_filters[ $hook ] ); + do { + foreach( (array) current( $yourls_filters[ $hook ] ) as $the_ ) { + if ( !is_null( $the_['function'] ) ){ + $args[1] = $value; + $count = $the_['accepted_args']; + if ( is_null( $count ) ) { + $_value = call_user_func_array( $the_['function'], array_slice( $args, 1 ) ); + } else { + $_value = call_user_func_array( $the_['function'], array_slice( $args, 1, (int) $count ) ); + } + } + if( $the_['type'] == 'filter' ) + $value = $_value; + } + + } while ( next( $yourls_filters[ $hook ] ) !== false ); + + if( $the_['type'] == 'filter' ) + return $value; +} + +/** + * Alias for yourls_apply_filter because I never remember if it's _filter or _filters + * + * Plus, semantically, it makes more sense. There can be several filters. I should have named it + * like this from the very start. Duh. + * + * @since 1.6 + * + * @param string $hook the name of the YOURLS element or action + * @param mixed $value the value of the element before filtering + * @return mixed + */ +function yourls_apply_filters( $hook, $value = '' ) { + return yourls_apply_filter( $hook, $value ); +} + + +/** + * Performs an action triggered by a YOURLS event. +* + * @param string $hook the name of the YOURLS action + * @param mixed $arg action arguments + */ +function yourls_do_action( $hook, $arg = '' ) { + global $yourls_actions; + + // Keep track of actions that are "done" + if ( !isset( $yourls_actions ) ) + $yourls_actions = array(); + if ( !isset( $yourls_actions[ $hook ] ) ) + $yourls_actions[ $hook ] = 1; + else + ++$yourls_actions[ $hook ]; + + $args = array(); + if ( is_array( $arg ) && 1 == count( $arg ) && isset( $arg[0] ) && is_object( $arg[0] ) ) // array(&$this) + $args[] =& $arg[0]; + else + $args[] = $arg; + for ( $a = 2; $a < func_num_args(); $a++ ) + $args[] = func_get_arg( $a ); + + yourls_apply_filter( $hook, $args ); +} + +/** +* Retrieve the number times an action is fired. +* +* @param string $hook Name of the action hook. +* @return int The number of times action hook $hook is fired +*/ +function yourls_did_action( $hook ) { + global $yourls_actions; + if ( !isset( $yourls_actions ) || !isset( $yourls_actions[ $hook ] ) ) + return 0; + return $yourls_actions[ $hook ]; +} + +/** + * Removes a function from a specified filter hook. + * + * This function removes a function attached to a specified filter hook. This + * method can be used to remove default functions attached to a specific filter + * hook and possibly replace them with a substitute. + * + * To remove a hook, the $function_to_remove and $priority arguments must match + * when the hook was added. + * + * @global array $yourls_filters storage for all of the filters + * @param string $hook The filter hook to which the function to be removed is hooked. + * @param callback $function_to_remove The name of the function which should be removed. + * @param int $priority optional. The priority of the function (default: 10). + * @param int $accepted_args optional. The number of arguments the function accepts (default: 1). + * @return boolean Whether the function was registered as a filter before it was removed. + */ +function yourls_remove_filter( $hook, $function_to_remove, $priority = 10, $accepted_args = 1 ) { + global $yourls_filters; + + $function_to_remove = yourls_filter_unique_id( $hook, $function_to_remove, $priority ); + + $remove = isset( $yourls_filters[ $hook ][ $priority ][ $function_to_remove ] ); + + if ( $remove === true ) { + unset ( $yourls_filters[$hook][$priority][$function_to_remove] ); + if ( empty( $yourls_filters[$hook][$priority] ) ) + unset( $yourls_filters[$hook] ); + } + return $remove; +} + + +/** + * Check if any filter has been registered for a hook. + * + * @global array $yourls_filters storage for all of the filters + * @param string $hook The name of the filter hook. + * @param callback $function_to_check optional. If specified, return the priority of that function on this hook or false if not attached. + * @return int|boolean Optionally returns the priority on that hook for the specified function. + */ +function yourls_has_filter( $hook, $function_to_check = false ) { + global $yourls_filters; + + $has = !empty( $yourls_filters[ $hook ] ); + if ( false === $function_to_check || false == $has ) { + return $has; + } + if ( !$idx = yourls_filter_unique_id( $hook, $function_to_check, false ) ) { + return false; + } + + foreach ( (array) array_keys( $yourls_filters[ $hook ] ) as $priority ) { + if ( isset( $yourls_filters[ $hook ][ $priority ][ $idx ] ) ) + return $priority; + } + return false; +} + +function yourls_has_action( $hook, $function_to_check = false ) { + return yourls_has_filter( $hook, $function_to_check ); +} + +/** + * Return number of active plugins + * + * @return integer Number of activated plugins + */ +function yourls_has_active_plugins( ) { + global $ydb; + + if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins ) + $ydb->plugins = array(); + + return count( $ydb->plugins ); +} + + +/** + * List plugins in /user/plugins + * + * @global $ydb Storage of mostly everything YOURLS needs to know + * @return array Array of [/plugindir/plugin.php]=>array('Name'=>'Ozh', 'Title'=>'Hello', ) + */ +function yourls_get_plugins( ) { + global $ydb; + + $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php'); + + if( !$plugins ) + return array(); + + foreach( $plugins as $key => $plugin ) { + $_plugin = yourls_plugin_basename( $plugin ); + $plugins[ $_plugin ] = yourls_get_plugin_data( $plugin ); + unset( $plugins[ $key ] ); + } + + return $plugins; +} + +/** + * Check if a plugin is active + * + * @param string $file Physical path to plugin file + * @return bool + */ +function yourls_is_active_plugin( $plugin ) { + if( !yourls_has_active_plugins( ) ) + return false; + + global $ydb; + $plugin = yourls_plugin_basename( $plugin ); + + return in_array( $plugin, $ydb->plugins ); + +} + +/** + * Parse a plugin header + * + * @param string $file Physical path to plugin file + * @return array Array of 'Field'=>'Value' from plugin comment header lines of the form "Field: Value" + */ +function yourls_get_plugin_data( $file ) { + $fp = fopen( $file, 'r' ); // assuming $file is readable, since yourls_load_plugins() filters this + $data = fread( $fp, 8192 ); // get first 8kb + fclose( $fp ); + + // Capture all the header within first comment block + if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) ) + return array(); + + // Capture each line with "Something: some text" + unset( $data ); + $lines = preg_split( "[\n|\r]", $matches[1] ); + unset( $matches ); + + $plugin_data = array(); + foreach( $lines as $line ) { + if( !preg_match( '!(.*?):\s+(.*)!', $line, $matches ) ) + continue; + + list( $null, $field, $value ) = array_map( 'trim', $matches); + $plugin_data[ $field ] = $value; + } + + return $plugin_data; +} + +// Include active plugins +function yourls_load_plugins() { + global $ydb; + $ydb->plugins = array(); + $active_plugins = yourls_get_option( 'active_plugins' ); + + // Don't load plugins when installing or updating + if( !$active_plugins OR yourls_is_installing() OR yourls_upgrade_is_needed() ) + return; + + foreach( (array)$active_plugins as $key=>$plugin ) { + if( yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$plugin ) ) { + include_once( YOURLS_PLUGINDIR.'/'.$plugin ); + $ydb->plugins[] = $plugin; + unset( $active_plugins[$key] ); + } + } + + // $active_plugins should be empty now, if not, a plugin could not be find: remove it + if( count( $active_plugins ) ) { + yourls_update_option( 'active_plugins', $ydb->plugins ); + $message = yourls_n( 'Could not find and deactivated plugin :', 'Could not find and deactivated plugins :', count( $active_plugins ) ); + $missing = ''.join( ', ', $active_plugins ).''; + yourls_add_notice( $message .' '. $missing ); + } +} + +/** + * Check if a file is safe for inclusion (well, "safe", no guarantee) + * + * @param string $file Full pathname to a file + */ +function yourls_validate_plugin_file( $file ) { + if ( + false !== strpos( $file, '..' ) + OR + false !== strpos( $file, './' ) + OR + 'plugin.php' !== substr( $file, -10 ) // a plugin must be named 'plugin.php' + OR + !is_readable( $file ) + ) + return false; + + return true; +} + +/** + * Activate a plugin + * + * @param string $plugin Plugin filename (full or relative to plugins directory) + * @return mixed string if error or true if success + */ +function yourls_activate_plugin( $plugin ) { + // validate file + $plugin = yourls_plugin_basename( $plugin ); + $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR ); + if( !yourls_validate_plugin_file( $plugindir.'/'.$plugin ) ) + return yourls__( 'Not a valid plugin file' ); + + // check not activated already + global $ydb; + if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) ) + return yourls__( 'Plugin already activated' ); + + // attempt activation. TODO: uber cool fail proof sandbox like in WP. + ob_start(); + include( YOURLS_PLUGINDIR.'/'.$plugin ); + if ( ob_get_length() > 0 ) { + // there was some output: error + $output = ob_get_clean(); + return yourls_s( 'Plugin generated unexpected output. Error was:
%s
', $output ); + } + + // so far, so good: update active plugin list + $ydb->plugins[] = $plugin; + yourls_update_option( 'active_plugins', $ydb->plugins ); + yourls_do_action( 'activated_plugin', $plugin ); + yourls_do_action( 'activated_' . $plugin ); + + return true; +} + +/** + * Dectivate a plugin + * + * @param string $plugin Plugin filename (full relative to plugins directory) + * @return mixed string if error or true if success + */ +function yourls_deactivate_plugin( $plugin ) { + $plugin = yourls_plugin_basename( $plugin ); + + // Check plugin is active + if( !yourls_is_active_plugin( $plugin ) ) + return yourls__( 'Plugin not active' ); + + // Deactivate the plugin + global $ydb; + $key = array_search( $plugin, $ydb->plugins ); + if( $key !== false ) { + array_splice( $ydb->plugins, $key, 1 ); + } + + yourls_update_option( 'active_plugins', $ydb->plugins ); + yourls_do_action( 'deactivated_plugin', $plugin ); + yourls_do_action( 'deactivated_' . $plugin ); + + return true; +} + +/** + * Return the path of a plugin file, relative to the plugins directory + */ +function yourls_plugin_basename( $file ) { + $file = yourls_sanitize_filename( $file ); + $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR ); + $file = str_replace( $plugindir, '', $file ); + return trim( $file, '/' ); +} + +/** + * Return the URL of the directory a plugin + */ +function yourls_plugin_url( $file ) { + $url = YOURLS_PLUGINURL . '/' . yourls_plugin_basename( $file ); + if( yourls_is_ssl() or yourls_needs_ssl() ) + $url = str_replace( 'http://', 'https://', $url ); + return yourls_apply_filter( 'plugin_url', $url, $file ); +} + +/** + * Display list of links to plugin admin pages, if any + */ +function yourls_list_plugin_admin_pages() { + global $ydb; + + if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages ) + return; + + $plugin_links = array(); + foreach( (array)$ydb->plugin_pages as $plugin => $page ) { + $plugin_links[ $plugin ] = array( + 'url' => yourls_admin_url( 'plugins.php?page='.$page['slug'] ), + 'anchor' => $page['title'], + ); + } + return $plugin_links; +} + +/** + * Register a plugin administration page + */ +function yourls_register_plugin_page( $slug, $title, $function ) { + global $ydb; + + if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages ) + $ydb->plugin_pages = array(); + + $ydb->plugin_pages[ $slug ] = array( + 'slug' => $slug, + 'title' => $title, + 'function' => $function, + ); +} + +/** + * Handle plugin administration page + * + */ +function yourls_plugin_admin_page( $plugin_page ) { + global $ydb; + + // Check the plugin page is actually registered + if( !isset( $ydb->plugin_pages[$plugin_page] ) ) { + yourls_die( yourls__( 'This page does not exist. Maybe a plugin you thought was activated is inactive?' ), yourls__( 'Invalid link' ) ); + } + + // Draw the page itself + yourls_do_action( 'load-' . $plugin_page); + yourls_html_head( 'plugin_page_' . $plugin_page, $ydb->plugin_pages[$plugin_page]['title'] ); + yourls_html_logo(); + yourls_html_menu(); + + call_user_func( $ydb->plugin_pages[$plugin_page]['function'] ); + + yourls_html_footer(); + + die(); +} + + +/** + * Callback function: Sort plugins + * + * @link http://php.net/uasort + * + * @param array $plugin_a + * @param array $plugin_b + * @return int 0, 1 or -1, see uasort() + */ +function yourls_plugins_sort_callback( $plugin_a, $plugin_b ) { + $orderby = yourls_apply_filters( 'plugins_sort_callback', 'Plugin Name' ); + $order = yourls_apply_filters( 'plugins_sort_callback', 'ASC' ); + + $a = $plugin_a[$orderby]; + $b = $plugin_b[$orderby]; + + if ( $a == $b ) + return 0; + + if ( 'DESC' == $order ) + return ( $a < $b ) ? 1 : -1; + else + return ( $a < $b ) ? -1 : 1; +} diff --git a/includes/functions-upgrade.php b/includes/functions-upgrade.php index 9485ee5..0cdd5f1 100644 --- a/includes/functions-upgrade.php +++ b/includes/functions-upgrade.php @@ -1,348 +1,348 @@ -query( $sql ); - echo "

Updating table structure. Please wait...

"; -} - -/************************** 1.4.3 -> 1.5 **************************/ - +function yourls_upgrade_482() { + // Change URL title charset to UTF8 + global $ydb; + $table_url = YOURLS_DB_TABLE_URL; + $sql = "ALTER TABLE `$table_url` CHANGE `title` `title` TEXT CHARACTER SET utf8;"; + $ydb->query( $sql ); + echo "

Updating table structure. Please wait...

"; +} + +/************************** 1.4.3 -> 1.5 **************************/ + /** * Main func for upgrade from 1.4.3 to 1.5 * */ -function yourls_upgrade_to_15( ) { - // Create empty 'active_plugins' entry in the option if needed - if( yourls_get_option( 'active_plugins' ) === false ) - yourls_add_option( 'active_plugins', array() ); - echo "

Enabling the plugin API. Please wait...

"; - - // Alter URL table to store titles - global $ydb; - $table_url = YOURLS_DB_TABLE_URL; - $sql = "ALTER TABLE `$table_url` ADD `title` TEXT AFTER `url`;"; - $ydb->query( $sql ); - echo "

Updating table structure. Please wait...

"; - - // Update .htaccess - yourls_create_htaccess(); - echo "

Updating .htaccess file. Please wait...

"; -} - -/************************** 1.4.1 -> 1.4.3 **************************/ - +function yourls_upgrade_to_15( ) { + // Create empty 'active_plugins' entry in the option if needed + if( yourls_get_option( 'active_plugins' ) === false ) + yourls_add_option( 'active_plugins', array() ); + echo "

Enabling the plugin API. Please wait...

"; + + // Alter URL table to store titles + global $ydb; + $table_url = YOURLS_DB_TABLE_URL; + $sql = "ALTER TABLE `$table_url` ADD `title` TEXT AFTER `url`;"; + $ydb->query( $sql ); + echo "

Updating table structure. Please wait...

"; + + // Update .htaccess + yourls_create_htaccess(); + echo "

Updating .htaccess file. Please wait...

"; +} + +/************************** 1.4.1 -> 1.4.3 **************************/ + /** * Main func for upgrade from 1.4.1 to 1.4.3 * */ -function yourls_upgrade_to_143( ) { - // Check if we have 'keyword' (borked install) or 'shorturl' (ok install) - global $ydb; - $table_log = YOURLS_DB_TABLE_LOG; - $sql = "SHOW COLUMNS FROM `$table_log`"; - $cols = $ydb->get_results( $sql ); - if ( $cols[2]->Field == 'keyword' ) { - $sql = "ALTER TABLE `$table_log` CHANGE `keyword` `shorturl` VARCHAR( 200 ) BINARY;"; - $ydb->query( $sql ); - } - echo "

Structure of existing tables updated. Please wait...

"; -} - -/************************** 1.4 -> 1.4.1 **************************/ - +function yourls_upgrade_to_143( ) { + // Check if we have 'keyword' (borked install) or 'shorturl' (ok install) + global $ydb; + $table_log = YOURLS_DB_TABLE_LOG; + $sql = "SHOW COLUMNS FROM `$table_log`"; + $cols = $ydb->get_results( $sql ); + if ( $cols[2]->Field == 'keyword' ) { + $sql = "ALTER TABLE `$table_log` CHANGE `keyword` `shorturl` VARCHAR( 200 ) BINARY;"; + $ydb->query( $sql ); + } + echo "

Structure of existing tables updated. Please wait...

"; +} + +/************************** 1.4 -> 1.4.1 **************************/ + /** * Main func for upgrade from 1.4 to 1.4.1 * */ -function yourls_upgrade_to_141( ) { - // Kill old cookies from 1.3 and prior - setcookie('yourls_username', null, time() - 3600 ); - setcookie('yourls_password', null, time() - 3600 ); - // alter table URL - yourls_alter_url_table_to_141(); - // recreate the htaccess file if needed - yourls_create_htaccess(); -} - +function yourls_upgrade_to_141( ) { + // Kill old cookies from 1.3 and prior + setcookie('yourls_username', null, time() - 3600 ); + setcookie('yourls_password', null, time() - 3600 ); + // alter table URL + yourls_alter_url_table_to_141(); + // recreate the htaccess file if needed + yourls_create_htaccess(); +} + /** * Alter table URL to 1.4.1 * */ -function yourls_alter_url_table_to_141() { - global $ydb; - $table_url = YOURLS_DB_TABLE_URL; - $alter = "ALTER TABLE `$table_url` CHANGE `keyword` `keyword` VARCHAR( 200 ) BINARY, CHANGE `url` `url` TEXT BINARY "; - $ydb->query( $alter ); - echo "

Structure of existing tables updated. Please wait...

"; -} - - -/************************** 1.3 -> 1.4 **************************/ - +function yourls_alter_url_table_to_141() { + global $ydb; + $table_url = YOURLS_DB_TABLE_URL; + $alter = "ALTER TABLE `$table_url` CHANGE `keyword` `keyword` VARCHAR( 200 ) BINARY, CHANGE `url` `url` TEXT BINARY "; + $ydb->query( $alter ); + echo "

Structure of existing tables updated. Please wait...

"; +} + + +/************************** 1.3 -> 1.4 **************************/ + /** * Main func for upgrade from 1.3-RC1 to 1.4 * */ -function yourls_upgrade_to_14( $step ) { - - switch( $step ) { - case 1: - // create table log & table options - // update table url structure - // update .htaccess - yourls_create_tables_for_14(); // no value returned, assuming it went OK - yourls_alter_url_table_to_14(); // no value returned, assuming it went OK - $clean = yourls_clean_htaccess_for_14(); // returns bool - $create = yourls_create_htaccess(); // returns bool - if ( !$create ) - echo "

Please create your .htaccess file (I could not do it for you). Please refer to http://yourls.org/htaccess."; - yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=2&oldver=1.3&newver=1.4&oldsql=100&newsql=200" ), $create ); - break; - - case 2: - // convert each link in table url - yourls_update_table_to_14(); - break; - - case 3: - // update table url structure part 2: recreate indexes - yourls_alter_url_table_to_14_part_two(); - // update version & db_version & next_id in the option table - // attempt to drop YOURLS_DB_TABLE_NEXTDEC - yourls_update_options_to_14(); - // Now upgrade to 1.4.1 - yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=1&oldver=1.4&newver=1.4.1&oldsql=200&newsql=210" ) ); - break; - } -} - +function yourls_upgrade_to_14( $step ) { + + switch( $step ) { + case 1: + // create table log & table options + // update table url structure + // update .htaccess + yourls_create_tables_for_14(); // no value returned, assuming it went OK + yourls_alter_url_table_to_14(); // no value returned, assuming it went OK + $clean = yourls_clean_htaccess_for_14(); // returns bool + $create = yourls_create_htaccess(); // returns bool + if ( !$create ) + echo "

Please create your .htaccess file (I could not do it for you). Please refer to http://yourls.org/htaccess."; + yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=2&oldver=1.3&newver=1.4&oldsql=100&newsql=200" ), $create ); + break; + + case 2: + // convert each link in table url + yourls_update_table_to_14(); + break; + + case 3: + // update table url structure part 2: recreate indexes + yourls_alter_url_table_to_14_part_two(); + // update version & db_version & next_id in the option table + // attempt to drop YOURLS_DB_TABLE_NEXTDEC + yourls_update_options_to_14(); + // Now upgrade to 1.4.1 + yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=1&oldver=1.4&newver=1.4.1&oldsql=200&newsql=210" ) ); + break; + } +} + /** * Update options to reflect new version * */ -function yourls_update_options_to_14() { - yourls_update_option( 'version', '1.4' ); - yourls_update_option( 'db_version', '200' ); - - if( defined('YOURLS_DB_TABLE_NEXTDEC') ) { - global $ydb; - $table = YOURLS_DB_TABLE_NEXTDEC; - $next_id = $ydb->get_var("SELECT `next_id` FROM `$table`"); - yourls_update_option( 'next_id', $next_id ); - @$ydb->query( "DROP TABLE `$table`" ); - } else { - yourls_update_option( 'next_id', 1 ); // In case someone mistakenly deleted the next_id constant or table too early - } -} - +function yourls_update_options_to_14() { + yourls_update_option( 'version', '1.4' ); + yourls_update_option( 'db_version', '200' ); + + if( defined('YOURLS_DB_TABLE_NEXTDEC') ) { + global $ydb; + $table = YOURLS_DB_TABLE_NEXTDEC; + $next_id = $ydb->get_var("SELECT `next_id` FROM `$table`"); + yourls_update_option( 'next_id', $next_id ); + @$ydb->query( "DROP TABLE `$table`" ); + } else { + yourls_update_option( 'next_id', 1 ); // In case someone mistakenly deleted the next_id constant or table too early + } +} + /** * Create new tables for YOURLS 1.4: options & log * */ -function yourls_create_tables_for_14() { - global $ydb; - - $queries = array(); - - $queries[YOURLS_DB_TABLE_OPTIONS] = - 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_OPTIONS.'` ('. - '`option_id` int(11) unsigned NOT NULL auto_increment,'. - '`option_name` varchar(64) NOT NULL default "",'. - '`option_value` longtext NOT NULL,'. - 'PRIMARY KEY (`option_id`,`option_name`),'. - 'KEY `option_name` (`option_name`)'. - ');'; - - $queries[YOURLS_DB_TABLE_LOG] = - 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_LOG.'` ('. - '`click_id` int(11) NOT NULL auto_increment,'. - '`click_time` datetime NOT NULL,'. - '`shorturl` varchar(200) NOT NULL,'. - '`referrer` varchar(200) NOT NULL,'. - '`user_agent` varchar(255) NOT NULL,'. - '`ip_address` varchar(41) NOT NULL,'. - '`country_code` char(2) NOT NULL,'. - 'PRIMARY KEY (`click_id`),'. - 'KEY `shorturl` (`shorturl`)'. - ');'; - - foreach( $queries as $query ) { - $ydb->query( $query ); // There's no result to be returned to check if table was created (except making another query to check table existence, which we'll avoid) - } - - echo "

New tables created. Please wait...

"; - -} - +function yourls_create_tables_for_14() { + global $ydb; + + $queries = array(); + + $queries[YOURLS_DB_TABLE_OPTIONS] = + 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_OPTIONS.'` ('. + '`option_id` int(11) unsigned NOT NULL auto_increment,'. + '`option_name` varchar(64) NOT NULL default "",'. + '`option_value` longtext NOT NULL,'. + 'PRIMARY KEY (`option_id`,`option_name`),'. + 'KEY `option_name` (`option_name`)'. + ');'; + + $queries[YOURLS_DB_TABLE_LOG] = + 'CREATE TABLE IF NOT EXISTS `'.YOURLS_DB_TABLE_LOG.'` ('. + '`click_id` int(11) NOT NULL auto_increment,'. + '`click_time` datetime NOT NULL,'. + '`shorturl` varchar(200) NOT NULL,'. + '`referrer` varchar(200) NOT NULL,'. + '`user_agent` varchar(255) NOT NULL,'. + '`ip_address` varchar(41) NOT NULL,'. + '`country_code` char(2) NOT NULL,'. + 'PRIMARY KEY (`click_id`),'. + 'KEY `shorturl` (`shorturl`)'. + ');'; + + foreach( $queries as $query ) { + $ydb->query( $query ); // There's no result to be returned to check if table was created (except making another query to check table existence, which we'll avoid) + } + + echo "

New tables created. Please wait...

"; + +} + /** * Alter table structure, part 1 (change schema, drop index) * */ -function yourls_alter_url_table_to_14() { - global $ydb; - $table = YOURLS_DB_TABLE_URL; - - $alters = array(); - $results = array(); - $alters[] = "ALTER TABLE `$table` CHANGE `id` `keyword` VARCHAR( 200 ) NOT NULL"; - $alters[] = "ALTER TABLE `$table` CHANGE `url` `url` TEXT NOT NULL"; - $alters[] = "ALTER TABLE `$table` DROP PRIMARY KEY"; - - foreach ( $alters as $query ) { - $ydb->query( $query ); - } - - echo "

Structure of existing tables updated. Please wait...

"; -} - +function yourls_alter_url_table_to_14() { + global $ydb; + $table = YOURLS_DB_TABLE_URL; + + $alters = array(); + $results = array(); + $alters[] = "ALTER TABLE `$table` CHANGE `id` `keyword` VARCHAR( 200 ) NOT NULL"; + $alters[] = "ALTER TABLE `$table` CHANGE `url` `url` TEXT NOT NULL"; + $alters[] = "ALTER TABLE `$table` DROP PRIMARY KEY"; + + foreach ( $alters as $query ) { + $ydb->query( $query ); + } + + echo "

Structure of existing tables updated. Please wait...

"; +} + /** * Alter table structure, part 2 (recreate indexes after the table is up to date) * */ -function yourls_alter_url_table_to_14_part_two() { - global $ydb; - $table = YOURLS_DB_TABLE_URL; - - $alters = array(); - $alters[] = "ALTER TABLE `$table` ADD PRIMARY KEY ( `keyword` )"; - $alters[] = "ALTER TABLE `$table` ADD INDEX ( `ip` )"; - $alters[] = "ALTER TABLE `$table` ADD INDEX ( `timestamp` )"; - - foreach ( $alters as $query ) { - $ydb->query( $query ); - } - - echo "

New table index created

"; -} - +function yourls_alter_url_table_to_14_part_two() { + global $ydb; + $table = YOURLS_DB_TABLE_URL; + + $alters = array(); + $alters[] = "ALTER TABLE `$table` ADD PRIMARY KEY ( `keyword` )"; + $alters[] = "ALTER TABLE `$table` ADD INDEX ( `ip` )"; + $alters[] = "ALTER TABLE `$table` ADD INDEX ( `timestamp` )"; + + foreach ( $alters as $query ) { + $ydb->query( $query ); + } + + echo "

New table index created

"; +} + /** * Convert each link from 1.3 (id) to 1.4 (keyword) structure * */ -function yourls_update_table_to_14() { - global $ydb; - $table = YOURLS_DB_TABLE_URL; - - // Modify each link to reflect new structure - $chunk = 45; - $from = isset($_GET['from']) ? intval( $_GET['from'] ) : 0 ; - $total = yourls_get_db_stats(); - $total = $total['total_links']; - - $sql = "SELECT `keyword`,`url` FROM `$table` WHERE 1=1 ORDER BY `url` ASC LIMIT $from, $chunk ;"; - - $rows = $ydb->get_results($sql); - - $count = 0; - $queries = 0; - foreach( $rows as $row ) { - $keyword = $row->keyword; - $url = $row->url; - $newkeyword = yourls_int2string( $keyword ); - $ydb->query("UPDATE `$table` SET `keyword` = '$newkeyword' WHERE `url` = '$url';"); - if( $ydb->result === true ) { - $queries++; - } else { - echo "

Huho... Could not update rown with url='$url', from keyword '$keyword' to keyword '$newkeyword'

"; // Find what went wrong :/ - } - $count++; - } - - // All done for this chunk of queries, did it all go as expected? - $success = true; - if( $count != $queries ) { - $success = false; - $num = $count - $queries; - echo "

$num error(s) occured while updating the URL table :(

"; - } - - if ( $count == $chunk ) { - // there are probably other rows to convert - $from = $from + $chunk; - $remain = $total - $from; - echo "

Converted $chunk database rows ($remain remaining). Continuing... Please do not close this window until it's finished!

"; - yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=2&oldver=1.3&newver=1.4&oldsql=100&newsql=200&from=$from" ), $success ); - } else { - // All done - echo '

All rows converted! Please wait...

'; - yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=3&oldver=1.3&newver=1.4&oldsql=100&newsql=200" ), $success ); - } - -} - +function yourls_update_table_to_14() { + global $ydb; + $table = YOURLS_DB_TABLE_URL; + + // Modify each link to reflect new structure + $chunk = 45; + $from = isset($_GET['from']) ? intval( $_GET['from'] ) : 0 ; + $total = yourls_get_db_stats(); + $total = $total['total_links']; + + $sql = "SELECT `keyword`,`url` FROM `$table` WHERE 1=1 ORDER BY `url` ASC LIMIT $from, $chunk ;"; + + $rows = $ydb->get_results($sql); + + $count = 0; + $queries = 0; + foreach( $rows as $row ) { + $keyword = $row->keyword; + $url = $row->url; + $newkeyword = yourls_int2string( $keyword ); + $ydb->query("UPDATE `$table` SET `keyword` = '$newkeyword' WHERE `url` = '$url';"); + if( $ydb->result === true ) { + $queries++; + } else { + echo "

Huho... Could not update rown with url='$url', from keyword '$keyword' to keyword '$newkeyword'

"; // Find what went wrong :/ + } + $count++; + } + + // All done for this chunk of queries, did it all go as expected? + $success = true; + if( $count != $queries ) { + $success = false; + $num = $count - $queries; + echo "

$num error(s) occured while updating the URL table :(

"; + } + + if ( $count == $chunk ) { + // there are probably other rows to convert + $from = $from + $chunk; + $remain = $total - $from; + echo "

Converted $chunk database rows ($remain remaining). Continuing... Please do not close this window until it's finished!

"; + yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=2&oldver=1.3&newver=1.4&oldsql=100&newsql=200&from=$from" ), $success ); + } else { + // All done + echo '

All rows converted! Please wait...

'; + yourls_redirect_javascript( yourls_admin_url( "upgrade.php?step=3&oldver=1.3&newver=1.4&oldsql=100&newsql=200" ), $success ); + } + +} + /** * Clean .htaccess as it existed before 1.4. Returns boolean * */ -function yourls_clean_htaccess_for_14() { - $filename = YOURLS_ABSPATH.'/.htaccess'; - - $result = false; - if( is_writeable( $filename ) ) { - $contents = implode( '', file( $filename ) ); - // remove "ShortURL" block - $contents = preg_replace( '/# BEGIN ShortURL.*# END ShortURL/s', '', $contents ); - // comment out deprecated RewriteRule - $find = 'RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]'; - $replace = "# You can safely remove this 5 lines block -- it's no longer used in YOURLS\n". - "# $find"; - $contents = str_replace( $find, $replace, $contents ); - - // Write cleaned file - $f = fopen( $filename, 'w' ); - fwrite( $f, $contents ); - fclose( $f ); - - $result = true; - } - - return $result; -} - +function yourls_clean_htaccess_for_14() { + $filename = YOURLS_ABSPATH.'/.htaccess'; + + $result = false; + if( is_writeable( $filename ) ) { + $contents = implode( '', file( $filename ) ); + // remove "ShortURL" block + $contents = preg_replace( '/# BEGIN ShortURL.*# END ShortURL/s', '', $contents ); + // comment out deprecated RewriteRule + $find = 'RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]'; + $replace = "# You can safely remove this 5 lines block -- it's no longer used in YOURLS\n". + "# $find"; + $contents = str_replace( $find, $replace, $contents ); + + // Write cleaned file + $f = fopen( $filename, 'w' ); + fwrite( $f, $contents ); + fclose( $f ); + + $result = true; + } + + return $result; +} + diff --git a/includes/functions-xml.php b/includes/functions-xml.php index a353c13..9b4b69e 100644 --- a/includes/functions-xml.php +++ b/includes/functions-xml.php @@ -1,81 +1,81 @@ -text=""; - $this->text.= $this->array_transform($array); - $this->text .=""; - return $this->text; - } - - function array_transform($array){ - //global $array_text; - foreach($array as $key => $value){ - if(!is_array($value)){ - //BEGIN code mod by Doug Vanderweide, 13 Jan 2011 - //does $value contain html entities? - if(strlen($value) != strlen(htmlentities($value))) { - //if so, encode as CDATA - $value = ""; - } - $this->text .= "<$key>$value"; - //END code mod - } else { - $this->text.="<$key>"; - $this->array_transform($value); - $this->text.=""; - } - } - //return $array_text; - - } - /*Transform an XML string to associative array "XML Parser Functions"*/ - function xml2array($xml){ - $this->depth=-1; - $this->xml_parser = xml_parser_create(); - xml_set_object($this->xml_parser, $this); - xml_parser_set_option ($this->xml_parser,XML_OPTION_CASE_FOLDING,0);//Don't put tags uppercase - xml_set_element_handler($this->xml_parser, "startElement", "endElement"); - xml_set_character_data_handler($this->xml_parser,"characterData"); - xml_parse($this->xml_parser,$xml,true); - xml_parser_free($this->xml_parser); - return $this->arrays[0]; - - } - function startElement($parser, $name, $attrs) - { - $this->keys[]=$name; //We add a key - $this->node_flag=1; - $this->depth++; - } - function characterData($parser,$data) - { - $key=end($this->keys); - $this->arrays[$this->depth][$key]=$data; - $this->node_flag=0; //So that we don't add as an array, but as an element - } - function endElement($parser, $name) - { - $key=array_pop($this->keys); - //If $node_flag==1 we add as an array, if not, as an element - if($this->node_flag==1){ - $this->arrays[$this->depth][$key]=$this->arrays[$this->depth+1]; - unset($this->arrays[$this->depth+1]); - } - $this->node_flag=1; - $this->depth--; - } - -}//End of the class - +text=""; + $this->text.= $this->array_transform($array); + $this->text .=""; + return $this->text; + } + + function array_transform($array){ + //global $array_text; + foreach($array as $key => $value){ + if(!is_array($value)){ + //BEGIN code mod by Doug Vanderweide, 13 Jan 2011 + //does $value contain html entities? + if(strlen($value) != strlen(htmlentities($value))) { + //if so, encode as CDATA + $value = ""; + } + $this->text .= "<$key>$value"; + //END code mod + } else { + $this->text.="<$key>"; + $this->array_transform($value); + $this->text.=""; + } + } + //return $array_text; + + } + /*Transform an XML string to associative array "XML Parser Functions"*/ + function xml2array($xml){ + $this->depth=-1; + $this->xml_parser = xml_parser_create(); + xml_set_object($this->xml_parser, $this); + xml_parser_set_option ($this->xml_parser,XML_OPTION_CASE_FOLDING,0);//Don't put tags uppercase + xml_set_element_handler($this->xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($this->xml_parser,"characterData"); + xml_parse($this->xml_parser,$xml,true); + xml_parser_free($this->xml_parser); + return $this->arrays[0]; + + } + function startElement($parser, $name, $attrs) + { + $this->keys[]=$name; //We add a key + $this->node_flag=1; + $this->depth++; + } + function characterData($parser,$data) + { + $key=end($this->keys); + $this->arrays[$this->depth][$key]=$data; + $this->node_flag=0; //So that we don't add as an array, but as an element + } + function endElement($parser, $name) + { + $key=array_pop($this->keys); + //If $node_flag==1 we add as an array, if not, as an element + if($this->node_flag==1){ + $this->arrays[$this->depth][$key]=$this->arrays[$this->depth+1]; + unset($this->arrays[$this->depth+1]); + } + $this->node_flag=1; + $this->depth--; + } + +}//End of the class + diff --git a/includes/functions.php b/includes/functions.php index e46cb71..10de0db 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1,2035 +1,2035 @@ - HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR - $headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ); - foreach( $headers as $header ) { - if ( !empty( $_SERVER[ $header ] ) ) { - $ip = $_SERVER[ $header ]; - break; - } - } - - // headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one. - if ( strpos( $ip, ',' ) !== false ) - $ip = substr( $ip, 0, strpos( $ip, ',' ) ); - - return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) ); -} - +function yourls_get_IP() { + // Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR + $headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ); + foreach( $headers as $header ) { + if ( !empty( $_SERVER[ $header ] ) ) { + $ip = $_SERVER[ $header ]; + break; + } + } + + // headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one. + if ( strpos( $ip, ',' ) !== false ) + $ip = substr( $ip, 0, strpos( $ip, ',' ) ); + + return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) ); +} + /** * Get next id a new link will have if no custom keyword provided * */ -function yourls_get_next_decimal() { - return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) ); -} - +function yourls_get_next_decimal() { + return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) ); +} + /** * Update id for next link with no custom keyword * */ -function yourls_update_next_decimal( $int = '' ) { - $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ; - $update = yourls_update_option( 'next_id', $int ); - yourls_do_action( 'update_next_decimal', $int, $update ); - return $update; -} - +function yourls_update_next_decimal( $int = '' ) { + $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ; + $update = yourls_update_option( 'next_id', $int ); + yourls_do_action( 'update_next_decimal', $int, $update ); + return $update; +} + /** * Delete a link in the DB * */ -function yourls_delete_link_by_keyword( $keyword ) { - global $ydb; - - $table = YOURLS_DB_TABLE_URL; - $keyword = yourls_sanitize_string( $keyword ); - $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';"); - yourls_do_action( 'delete_link', $keyword, $delete ); - return $delete; -} - +function yourls_delete_link_by_keyword( $keyword ) { + global $ydb; + + $table = YOURLS_DB_TABLE_URL; + $keyword = yourls_sanitize_string( $keyword ); + $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';"); + yourls_do_action( 'delete_link', $keyword, $delete ); + return $delete; +} + /** * SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting * */ -function yourls_insert_link_in_db( $url, $keyword, $title = '' ) { - global $ydb; - - $url = yourls_escape( yourls_sanitize_url( $url ) ); - $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) ); - $title = yourls_escape( yourls_sanitize_title( $title ) ); - - $table = YOURLS_DB_TABLE_URL; - $timestamp = date('Y-m-d H:i:s'); - $ip = yourls_get_IP(); - $insert = $ydb->query("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);"); - - yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip ); - - return (bool)$insert; -} - +function yourls_insert_link_in_db( $url, $keyword, $title = '' ) { + global $ydb; + + $url = yourls_escape( yourls_sanitize_url( $url ) ); + $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) ); + $title = yourls_escape( yourls_sanitize_title( $title ) ); + + $table = YOURLS_DB_TABLE_URL; + $timestamp = date('Y-m-d H:i:s'); + $ip = yourls_get_IP(); + $insert = $ydb->query("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);"); + + yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip ); + + return (bool)$insert; +} + /** * Check if a URL already exists in the DB. Return NULL (doesn't exist) or an object with URL informations. * */ -function yourls_url_exists( $url ) { - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_url_exists', false, $url ); - if ( false !== $pre ) - return $pre; - - global $ydb; - $table = YOURLS_DB_TABLE_URL; - $strip_url = stripslashes($url); - $url_exists = $ydb->get_row("SELECT * FROM `$table` WHERE `url` = '".$strip_url."';"); - - return yourls_apply_filter( 'url_exists', $url_exists, $url ); -} - +function yourls_url_exists( $url ) { + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_url_exists', false, $url ); + if ( false !== $pre ) + return $pre; + + global $ydb; + $table = YOURLS_DB_TABLE_URL; + $strip_url = stripslashes($url); + $url_exists = $ydb->get_row("SELECT * FROM `$table` WHERE `url` = '".$strip_url."';"); + + return yourls_apply_filter( 'url_exists', $url_exists, $url ); +} + /** * Add a new link in the DB, either with custom keyword, or find one * */ -function yourls_add_new_link( $url, $keyword = '', $title = '' ) { - global $ydb; - - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title ); - if ( false !== $pre ) - return $pre; - - $url = yourls_encodeURI( $url ); - $url = yourls_escape( yourls_sanitize_url( $url ) ); - if ( !$url || $url == 'http://' || $url == 'https://' ) { - $return['status'] = 'fail'; - $return['code'] = 'error:nourl'; - $return['message'] = yourls__( 'Missing or malformed URL' ); - $return['errorCode'] = '400'; - return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title ); - } - - // Prevent DB flood - $ip = yourls_get_IP(); - yourls_check_IP_flood( $ip ); - - // Prevent internal redirection loops: cannot shorten a shortened URL - if( yourls_get_relative_url( $url ) ) { - if( yourls_is_shorturl( $url ) ) { - $return['status'] = 'fail'; - $return['code'] = 'error:noloop'; - $return['message'] = yourls__( 'URL is a short URL' ); - $return['errorCode'] = '400'; - return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title ); - } - } - - yourls_do_action( 'pre_add_new_link', $url, $keyword, $title ); - - $strip_url = stripslashes( $url ); - $return = array(); - - // duplicates allowed or new URL => store it - if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) { - - if( isset( $title ) && !empty( $title ) ) { - $title = yourls_sanitize_title( $title ); - } else { - $title = yourls_get_remote_title( $url ); - } - $title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword ); - - // Custom keyword provided - if ( $keyword ) { - - yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title ); - - $keyword = yourls_escape( yourls_sanitize_string( $keyword ) ); - $keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title ); - if ( !yourls_keyword_is_free( $keyword ) ) { - // This shorturl either reserved or taken already - $return['status'] = 'fail'; - $return['code'] = 'error:keyword'; - $return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword ); - } else { - // all clear, store ! - yourls_insert_link_in_db( $url, $keyword, $title ); - $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip ); - $return['status'] = 'success'; - $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) ); - $return['title'] = $title; - $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() ); - $return['shorturl'] = YOURLS_SITE .'/'. $keyword; - } - - // Create random keyword - } else { - - yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title ); - - $timestamp = date( 'Y-m-d H:i:s' ); - $id = yourls_get_next_decimal(); - $ok = false; - do { - $keyword = yourls_int2string( $id ); - $keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title ); - $free = yourls_keyword_is_free($keyword); - $add_url = @yourls_insert_link_in_db( $url, $keyword, $title ); - $ok = ($free && $add_url); - if ( $ok === false && $add_url === 1 ) { - // we stored something, but shouldn't have (ie reserved id) - $delete = yourls_delete_link_by_keyword( $keyword ); - $return['extra_info'] .= '(deleted '.$keyword.')'; - } else { - // everything ok, populate needed vars - $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip ); - $return['status'] = 'success'; - $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) ); - $return['title'] = $title; - $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() ); - $return['shorturl'] = YOURLS_SITE .'/'. $keyword; - } - $id++; - } while ( !$ok ); - @yourls_update_next_decimal( $id ); - } - - // URL was already stored - } else { - - yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title ); - - $return['status'] = 'fail'; - $return['code'] = 'error:url'; - $return['url'] = array( 'keyword' => $url_exists->keyword, 'url' => $strip_url, 'title' => $url_exists->title, 'date' => $url_exists->timestamp, 'ip' => $url_exists->ip, 'clicks' => $url_exists->clicks ); - $return['message'] = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) ); - $return['title'] = $url_exists->title; - $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword; - } - - yourls_do_action( 'post_add_new_link', $url, $keyword, $title ); - - $return['statusCode'] = 200; // regardless of result, this is still a valid request - return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title ); -} - - +function yourls_add_new_link( $url, $keyword = '', $title = '' ) { + global $ydb; + + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title ); + if ( false !== $pre ) + return $pre; + + $url = yourls_encodeURI( $url ); + $url = yourls_escape( yourls_sanitize_url( $url ) ); + if ( !$url || $url == 'http://' || $url == 'https://' ) { + $return['status'] = 'fail'; + $return['code'] = 'error:nourl'; + $return['message'] = yourls__( 'Missing or malformed URL' ); + $return['errorCode'] = '400'; + return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title ); + } + + // Prevent DB flood + $ip = yourls_get_IP(); + yourls_check_IP_flood( $ip ); + + // Prevent internal redirection loops: cannot shorten a shortened URL + if( yourls_get_relative_url( $url ) ) { + if( yourls_is_shorturl( $url ) ) { + $return['status'] = 'fail'; + $return['code'] = 'error:noloop'; + $return['message'] = yourls__( 'URL is a short URL' ); + $return['errorCode'] = '400'; + return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title ); + } + } + + yourls_do_action( 'pre_add_new_link', $url, $keyword, $title ); + + $strip_url = stripslashes( $url ); + $return = array(); + + // duplicates allowed or new URL => store it + if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) { + + if( isset( $title ) && !empty( $title ) ) { + $title = yourls_sanitize_title( $title ); + } else { + $title = yourls_get_remote_title( $url ); + } + $title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword ); + + // Custom keyword provided + if ( $keyword ) { + + yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title ); + + $keyword = yourls_escape( yourls_sanitize_string( $keyword ) ); + $keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title ); + if ( !yourls_keyword_is_free( $keyword ) ) { + // This shorturl either reserved or taken already + $return['status'] = 'fail'; + $return['code'] = 'error:keyword'; + $return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword ); + } else { + // all clear, store ! + yourls_insert_link_in_db( $url, $keyword, $title ); + $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip ); + $return['status'] = 'success'; + $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) ); + $return['title'] = $title; + $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() ); + $return['shorturl'] = YOURLS_SITE .'/'. $keyword; + } + + // Create random keyword + } else { + + yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title ); + + $timestamp = date( 'Y-m-d H:i:s' ); + $id = yourls_get_next_decimal(); + $ok = false; + do { + $keyword = yourls_int2string( $id ); + $keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title ); + $free = yourls_keyword_is_free($keyword); + $add_url = @yourls_insert_link_in_db( $url, $keyword, $title ); + $ok = ($free && $add_url); + if ( $ok === false && $add_url === 1 ) { + // we stored something, but shouldn't have (ie reserved id) + $delete = yourls_delete_link_by_keyword( $keyword ); + $return['extra_info'] .= '(deleted '.$keyword.')'; + } else { + // everything ok, populate needed vars + $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip ); + $return['status'] = 'success'; + $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) ); + $return['title'] = $title; + $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() ); + $return['shorturl'] = YOURLS_SITE .'/'. $keyword; + } + $id++; + } while ( !$ok ); + @yourls_update_next_decimal( $id ); + } + + // URL was already stored + } else { + + yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title ); + + $return['status'] = 'fail'; + $return['code'] = 'error:url'; + $return['url'] = array( 'keyword' => $url_exists->keyword, 'url' => $strip_url, 'title' => $url_exists->title, 'date' => $url_exists->timestamp, 'ip' => $url_exists->ip, 'clicks' => $url_exists->clicks ); + $return['message'] = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) ); + $return['title'] = $url_exists->title; + $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword; + } + + yourls_do_action( 'post_add_new_link', $url, $keyword, $title ); + + $return['statusCode'] = 200; // regardless of result, this is still a valid request + return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title ); +} + + /** * Edit a link * */ -function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) { - global $ydb; - - $table = YOURLS_DB_TABLE_URL; - $url = yourls_escape (yourls_sanitize_url( $url ) ); - $keyword = yourls_escape( yourls_sanitize_string( $keyword ) ); - $title = yourls_escape( yourls_sanitize_title( $title ) ); - $newkeyword = yourls_escape( yourls_sanitize_string( $newkeyword ) ); - $strip_url = stripslashes( $url ); - $strip_title = stripslashes( $title ); - $old_url = $ydb->get_var( "SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';" ); - - // Check if new URL is not here already - if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) { - $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';")); - } else { - $new_url_already_there = false; - } - - // Check if the new keyword is not here already - if ( $newkeyword != $keyword ) { - $keyword_is_ok = yourls_keyword_is_free( $newkeyword ); - } else { - $keyword_is_ok = true; - } - - yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok ); - - // All clear, update - if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) { - $update_url = $ydb->query( "UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';" ); - if( $update_url ) { - $return['url'] = array( 'keyword' => $newkeyword, 'shorturl' => YOURLS_SITE.'/'.$newkeyword, 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) ); - $return['status'] = 'success'; - $return['message'] = yourls__( 'Link updated in database' ); - } else { - $return['status'] = 'fail'; - $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ; - } - - // Nope - } else { - $return['status'] = 'fail'; - $return['message'] = yourls__( 'URL or keyword already exists in database' ); - } - - return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok ); -} - +function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) { + global $ydb; + + $table = YOURLS_DB_TABLE_URL; + $url = yourls_escape (yourls_sanitize_url( $url ) ); + $keyword = yourls_escape( yourls_sanitize_string( $keyword ) ); + $title = yourls_escape( yourls_sanitize_title( $title ) ); + $newkeyword = yourls_escape( yourls_sanitize_string( $newkeyword ) ); + $strip_url = stripslashes( $url ); + $strip_title = stripslashes( $title ); + $old_url = $ydb->get_var( "SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';" ); + + // Check if new URL is not here already + if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) { + $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$strip_url';")); + } else { + $new_url_already_there = false; + } + + // Check if the new keyword is not here already + if ( $newkeyword != $keyword ) { + $keyword_is_ok = yourls_keyword_is_free( $newkeyword ); + } else { + $keyword_is_ok = true; + } + + yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok ); + + // All clear, update + if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) { + $update_url = $ydb->query( "UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';" ); + if( $update_url ) { + $return['url'] = array( 'keyword' => $newkeyword, 'shorturl' => YOURLS_SITE.'/'.$newkeyword, 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) ); + $return['status'] = 'success'; + $return['message'] = yourls__( 'Link updated in database' ); + } else { + $return['status'] = 'fail'; + $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ; + } + + // Nope + } else { + $return['status'] = 'fail'; + $return['message'] = yourls__( 'URL or keyword already exists in database' ); + } + + return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok ); +} + /** * Update a title link (no checks for duplicates etc..) * */ -function yourls_edit_link_title( $keyword, $title ) { - global $ydb; - - $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) ); - $title = yourls_escape( yourls_sanitize_title( $title ) ); - - $table = YOURLS_DB_TABLE_URL; - $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';"); - - return $update; -} - - +function yourls_edit_link_title( $keyword, $title ) { + global $ydb; + + $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) ); + $title = yourls_escape( yourls_sanitize_title( $title ) ); + + $table = YOURLS_DB_TABLE_URL; + $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';"); + + return $update; +} + + /** * Check if keyword id is free (ie not already taken, and not reserved). Return bool. * */ -function yourls_keyword_is_free( $keyword ) { - $free = true; - if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) ) - $free = false; - - return yourls_apply_filter( 'keyword_is_free', $free, $keyword ); -} - +function yourls_keyword_is_free( $keyword ) { + $free = true; + if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) ) + $free = false; + + return yourls_apply_filter( 'keyword_is_free', $free, $keyword ); +} + /** * Check if a keyword is taken (ie there is already a short URL with this id). Return bool. * */ -function yourls_keyword_is_taken( $keyword ) { - global $ydb; - $keyword = yourls_sanitize_keyword( $keyword ); - $taken = false; - $table = YOURLS_DB_TABLE_URL; - $already_exists = $ydb->get_var( "SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';" ); - if ( $already_exists ) - $taken = true; - - return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword ); -} - - +function yourls_keyword_is_taken( $keyword ) { + global $ydb; + $keyword = yourls_sanitize_keyword( $keyword ); + $taken = false; + $table = YOURLS_DB_TABLE_URL; + $already_exists = $ydb->get_var( "SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';" ); + if ( $already_exists ) + $taken = true; + + return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword ); +} + + /** * Connect to DB * */ -function yourls_db_connect() { - global $ydb; - - if ( !defined( 'YOURLS_DB_USER' ) - or !defined( 'YOURLS_DB_PASS' ) - or !defined( 'YOURLS_DB_NAME' ) - or !defined( 'YOURLS_DB_HOST' ) - or !class_exists( 'ezSQL_mysql' ) - ) yourls_die ( yourls__( 'DB config missing, or could not find DB class' ), yourls__( 'Fatal error' ), 503 ); - - // Are we standalone or in the WordPress environment? - if ( class_exists( 'wpdb' ) ) { - $ydb = new wpdb( YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST ); - } else { - $ydb = new ezSQL_mysql( YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST ); - } - if ( $ydb->last_error ) - yourls_die( $ydb->last_error, yourls__( 'Fatal error' ), 503 ); - - if ( defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG === true ) - $ydb->show_errors = true; - - return $ydb; -} - +function yourls_db_connect() { + global $ydb; + + if ( !defined( 'YOURLS_DB_USER' ) + or !defined( 'YOURLS_DB_PASS' ) + or !defined( 'YOURLS_DB_NAME' ) + or !defined( 'YOURLS_DB_HOST' ) + or !class_exists( 'ezSQL_mysql' ) + ) yourls_die ( yourls__( 'DB config missing, or could not find DB class' ), yourls__( 'Fatal error' ), 503 ); + + // Are we standalone or in the WordPress environment? + if ( class_exists( 'wpdb' ) ) { + $ydb = new wpdb( YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST ); + } else { + $ydb = new ezSQL_mysql( YOURLS_DB_USER, YOURLS_DB_PASS, YOURLS_DB_NAME, YOURLS_DB_HOST ); + } + if ( $ydb->last_error ) + yourls_die( $ydb->last_error, yourls__( 'Fatal error' ), 503 ); + + if ( defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG === true ) + $ydb->show_errors = true; + + return $ydb; +} + /** * Return XML output. * */ -function yourls_xml_encode( $array ) { - require_once( YOURLS_INC.'/functions-xml.php' ); - $converter= new yourls_array2xml; - return $converter->array2xml( $array ); -} - +function yourls_xml_encode( $array ) { + require_once( YOURLS_INC.'/functions-xml.php' ); + $converter= new yourls_array2xml; + return $converter->array2xml( $array ); +} + /** * 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 * */ -function yourls_get_keyword_infos( $keyword, $use_cache = true ) { - global $ydb; - $keyword = yourls_sanitize_string( $keyword ); - - yourls_do_action( 'pre_get_keyword', $keyword, $use_cache ); - - if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) { - return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword ); - } - - yourls_do_action( 'get_keyword_not_cached', $keyword ); - - $table = YOURLS_DB_TABLE_URL; - $infos = $ydb->get_row( "SELECT * FROM `$table` WHERE `keyword` = '$keyword'" ); - - if( $infos ) { - $infos = (array)$infos; - $ydb->infos[ $keyword ] = $infos; - } else { - $ydb->infos[ $keyword ] = false; - } - - return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword ); -} - +function yourls_get_keyword_infos( $keyword, $use_cache = true ) { + global $ydb; + $keyword = yourls_sanitize_string( $keyword ); + + yourls_do_action( 'pre_get_keyword', $keyword, $use_cache ); + + if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) { + return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword ); + } + + yourls_do_action( 'get_keyword_not_cached', $keyword ); + + $table = YOURLS_DB_TABLE_URL; + $infos = $ydb->get_row( "SELECT * FROM `$table` WHERE `keyword` = '$keyword'" ); + + if( $infos ) { + $infos = (array)$infos; + $ydb->infos[ $keyword ] = $infos; + } else { + $ydb->infos[ $keyword ] = false; + } + + return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword ); +} + /** * Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_info( $keyword, $field, $notfound = false ) { - - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound ); - if ( false !== $pre ) - return $pre; - - $keyword = yourls_sanitize_string( $keyword ); - $infos = yourls_get_keyword_infos( $keyword ); - - $return = $notfound; - if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false ) - $return = $infos[ $field ]; - - return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound ); -} - +function yourls_get_keyword_info( $keyword, $field, $notfound = false ) { + + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound ); + if ( false !== $pre ) + return $pre; + + $keyword = yourls_sanitize_string( $keyword ); + $infos = yourls_get_keyword_infos( $keyword ); + + $return = $notfound; + if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false ) + $return = $infos[ $field ]; + + return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound ); +} + /** * Return title associated with keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_title( $keyword, $notfound = false ) { - return yourls_get_keyword_info( $keyword, 'title', $notfound ); -} - +function yourls_get_keyword_title( $keyword, $notfound = false ) { + return yourls_get_keyword_info( $keyword, 'title', $notfound ); +} + /** * Return long URL associated with keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_longurl( $keyword, $notfound = false ) { - return yourls_get_keyword_info( $keyword, 'url', $notfound ); -} - +function yourls_get_keyword_longurl( $keyword, $notfound = false ) { + return yourls_get_keyword_info( $keyword, 'url', $notfound ); +} + /** * Return number of clicks on a keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_clicks( $keyword, $notfound = false ) { - return yourls_get_keyword_info( $keyword, 'clicks', $notfound ); -} - +function yourls_get_keyword_clicks( $keyword, $notfound = false ) { + return yourls_get_keyword_info( $keyword, 'clicks', $notfound ); +} + /** * Return IP that added a keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_IP( $keyword, $notfound = false ) { - return yourls_get_keyword_info( $keyword, 'ip', $notfound ); -} - +function yourls_get_keyword_IP( $keyword, $notfound = false ) { + return yourls_get_keyword_info( $keyword, 'ip', $notfound ); +} + /** * Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found * */ -function yourls_get_keyword_timestamp( $keyword, $notfound = false ) { - return yourls_get_keyword_info( $keyword, 'timestamp', $notfound ); -} - +function yourls_get_keyword_timestamp( $keyword, $notfound = false ) { + return yourls_get_keyword_info( $keyword, 'timestamp', $notfound ); +} + /** * Update click count on a short URL. Return 0/1 for error/success. * */ -function yourls_update_clicks( $keyword, $clicks = false ) { - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks ); - if ( false !== $pre ) - return $pre; - - global $ydb; - $keyword = yourls_sanitize_string( $keyword ); - $table = YOURLS_DB_TABLE_URL; - if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 ) - $update = $ydb->query( "UPDATE `$table` SET `clicks` = $clicks WHERE `keyword` = '$keyword'" ); - else - $update = $ydb->query( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'" ); - - yourls_do_action( 'update_clicks', $keyword, $update, $clicks ); - return $update; -} - +function yourls_update_clicks( $keyword, $clicks = false ) { + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks ); + if ( false !== $pre ) + return $pre; + + global $ydb; + $keyword = yourls_sanitize_string( $keyword ); + $table = YOURLS_DB_TABLE_URL; + if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 ) + $update = $ydb->query( "UPDATE `$table` SET `clicks` = $clicks WHERE `keyword` = '$keyword'" ); + else + $update = $ydb->query( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'" ); + + yourls_do_action( 'update_clicks', $keyword, $update, $clicks ); + return $update; +} + /** * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return * */ -function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) { - global $ydb; - - switch( $filter ) { - case 'bottom': - $sort_by = 'clicks'; - $sort_order = 'asc'; - break; - case 'last': - $sort_by = 'timestamp'; - $sort_order = 'desc'; - break; - case 'rand': - case 'random': - $sort_by = 'RAND()'; - $sort_order = ''; - break; - case 'top': - default: - $sort_by = 'clicks'; - $sort_order = 'desc'; - break; - } - - // Fetch links - $limit = intval( $limit ); - $start = intval( $start ); - if ( $limit > 0 ) { - - $table_url = YOURLS_DB_TABLE_URL; - $results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" ); - - $return = array(); - $i = 1; - - foreach ( (array)$results as $res ) { - $return['links']['link_'.$i++] = array( - 'shorturl' => YOURLS_SITE .'/'. $res->keyword, - 'url' => $res->url, - 'title' => $res->title, - 'timestamp'=> $res->timestamp, - 'ip' => $res->ip, - 'clicks' => $res->clicks, - ); - } - } - - $return['stats'] = yourls_get_db_stats(); - - $return['statusCode'] = 200; - - return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start ); -} - +function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) { + global $ydb; + + switch( $filter ) { + case 'bottom': + $sort_by = 'clicks'; + $sort_order = 'asc'; + break; + case 'last': + $sort_by = 'timestamp'; + $sort_order = 'desc'; + break; + case 'rand': + case 'random': + $sort_by = 'RAND()'; + $sort_order = ''; + break; + case 'top': + default: + $sort_by = 'clicks'; + $sort_order = 'desc'; + break; + } + + // Fetch links + $limit = intval( $limit ); + $start = intval( $start ); + if ( $limit > 0 ) { + + $table_url = YOURLS_DB_TABLE_URL; + $results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" ); + + $return = array(); + $i = 1; + + foreach ( (array)$results as $res ) { + $return['links']['link_'.$i++] = array( + 'shorturl' => YOURLS_SITE .'/'. $res->keyword, + 'url' => $res->url, + 'title' => $res->title, + 'timestamp'=> $res->timestamp, + 'ip' => $res->ip, + 'clicks' => $res->clicks, + ); + } + } + + $return['stats'] = yourls_get_db_stats(); + + $return['statusCode'] = 200; + + return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start ); +} + /** * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return * */ -function yourls_get_link_stats( $shorturl ) { - global $ydb; - - $table_url = YOURLS_DB_TABLE_URL; - $res = $ydb->get_row( "SELECT * FROM `$table_url` WHERE keyword = '$shorturl';" ); - $return = array(); - - if( !$res ) { - // non existent link - $return = array( - 'statusCode' => 404, - 'message' => 'Error: short URL not found', - ); - } else { - $return = array( - 'statusCode' => 200, - 'message' => 'success', - 'link' => array( - 'shorturl' => YOURLS_SITE .'/'. $res->keyword, - 'url' => $res->url, - 'title' => $res->title, - 'timestamp'=> $res->timestamp, - 'ip' => $res->ip, - 'clicks' => $res->clicks, - ) - ); - } - - return yourls_apply_filter( 'get_link_stats', $return, $shorturl ); -} - +function yourls_get_link_stats( $shorturl ) { + global $ydb; + + $table_url = YOURLS_DB_TABLE_URL; + $res = $ydb->get_row( "SELECT * FROM `$table_url` WHERE keyword = '$shorturl';" ); + $return = array(); + + if( !$res ) { + // non existent link + $return = array( + 'statusCode' => 404, + 'message' => 'Error: short URL not found', + ); + } else { + $return = array( + 'statusCode' => 200, + 'message' => 'success', + 'link' => array( + 'shorturl' => YOURLS_SITE .'/'. $res->keyword, + 'url' => $res->url, + 'title' => $res->title, + 'timestamp'=> $res->timestamp, + 'ip' => $res->ip, + 'clicks' => $res->clicks, + ) + ); + } + + return yourls_apply_filter( 'get_link_stats', $return, $shorturl ); +} + /** * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array * */ -function yourls_get_db_stats( $where = '' ) { - global $ydb; - $table_url = YOURLS_DB_TABLE_URL; - - $totals = $ydb->get_row( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where" ); - $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum ); - - return yourls_apply_filter( 'get_db_stats', $return, $where ); -} - +function yourls_get_db_stats( $where = '' ) { + global $ydb; + $table_url = YOURLS_DB_TABLE_URL; + + $totals = $ydb->get_row( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where" ); + $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum ); + + return yourls_apply_filter( 'get_db_stats', $return, $where ); +} + /** * Get number of SQL queries performed * */ -function yourls_get_num_queries() { - global $ydb; - - return yourls_apply_filter( 'get_num_queries', $ydb->num_queries ); -} - +function yourls_get_num_queries() { + global $ydb; + + return yourls_apply_filter( 'get_num_queries', $ydb->num_queries ); +} + /** * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK. * */ -function yourls_get_user_agent() { - if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) ) - return '-'; - - $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] )); - $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua ); - - return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) ); -} - +function yourls_get_user_agent() { + if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) ) + return '-'; + + $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] )); + $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua ); + + return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) ); +} + /** * Redirect to another page * */ -function yourls_redirect( $location, $code = 301 ) { - yourls_do_action( 'pre_redirect', $location, $code ); - $location = yourls_apply_filter( 'redirect_location', $location, $code ); - $code = yourls_apply_filter( 'redirect_code', $code, $location ); - // Redirect, either properly if possible, or via Javascript otherwise - if( !headers_sent() ) { - yourls_status_header( $code ); - header( "Location: $location" ); - } else { - yourls_redirect_javascript( $location ); - } - die(); -} - +function yourls_redirect( $location, $code = 301 ) { + yourls_do_action( 'pre_redirect', $location, $code ); + $location = yourls_apply_filter( 'redirect_location', $location, $code ); + $code = yourls_apply_filter( 'redirect_code', $code, $location ); + // Redirect, either properly if possible, or via Javascript otherwise + if( !headers_sent() ) { + yourls_status_header( $code ); + header( "Location: $location" ); + } else { + yourls_redirect_javascript( $location ); + } + die(); +} + /** * Set HTTP status header * */ -function yourls_status_header( $code = 200 ) { - if( headers_sent() ) - return; - - $protocol = $_SERVER["SERVER_PROTOCOL"]; - if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) - $protocol = 'HTTP/1.0'; - - $code = intval( $code ); - $desc = yourls_get_HTTP_status( $code ); - - @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups - yourls_do_action( 'status_header', $code ); -} - +function yourls_status_header( $code = 200 ) { + if( headers_sent() ) + return; + + $protocol = $_SERVER["SERVER_PROTOCOL"]; + if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) + $protocol = 'HTTP/1.0'; + + $code = intval( $code ); + $desc = yourls_get_HTTP_status( $code ); + + @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups + yourls_do_action( 'status_header', $code ); +} + /** * 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) * */ -function yourls_redirect_javascript( $location, $dontwait = true ) { - yourls_do_action( 'pre_redirect_javascript', $location, $dontwait ); - $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait ); - if( $dontwait ) { - $message = yourls_s( 'if you are not redirected after 10 seconds, please click here', $location ); - echo << - window.location="$location"; - - ($message) -REDIR; - } else { - echo '

' . yourls_s( 'Please click here', $location ) . '

'; - } - yourls_do_action( 'post_redirect_javascript', $location ); -} - +function yourls_redirect_javascript( $location, $dontwait = true ) { + yourls_do_action( 'pre_redirect_javascript', $location, $dontwait ); + $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait ); + if( $dontwait ) { + $message = yourls_s( 'if you are not redirected after 10 seconds, please click here', $location ); + echo << + window.location="$location"; + + ($message) +REDIR; + } else { + echo '

' . yourls_s( 'Please click here', $location ) . '

'; + } + yourls_do_action( 'post_redirect_javascript', $location ); +} + /** * Return a HTTP status code * */ -function yourls_get_HTTP_status( $code ) { - $code = intval( $code ); - $headers_desc = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 207 => 'Multi-Status', - 226 => 'IM Used', - - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => 'Reserved', - 307 => 'Temporary Redirect', - - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 422 => 'Unprocessable Entity', - 423 => 'Locked', - 424 => 'Failed Dependency', - 426 => 'Upgrade Required', - - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - 506 => 'Variant Also Negotiates', - 507 => 'Insufficient Storage', - 510 => 'Not Extended' - ); - - if ( isset( $headers_desc[$code] ) ) - return $headers_desc[$code]; - else - return ''; -} - - +function yourls_get_HTTP_status( $code ) { + $code = intval( $code ); + $headers_desc = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 226 => 'IM Used', + + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 426 => 'Upgrade Required', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 510 => 'Not Extended' + ); + + if ( isset( $headers_desc[$code] ) ) + return $headers_desc[$code]; + else + return ''; +} + + /** * Log a redirect (for stats) * */ -function yourls_log_redirect( $keyword ) { - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword ); - if ( false !== $pre ) - return $pre; - - if ( !yourls_do_log_redirect() ) - return true; - - global $ydb; - $table = YOURLS_DB_TABLE_LOG; - - $keyword = yourls_sanitize_string( $keyword ); - $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' ); - $ua = yourls_get_user_agent(); - $ip = yourls_get_IP(); - $location = yourls_geo_ip_to_countrycode( $ip ); - - return $ydb->query( "INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" ); -} - +function yourls_log_redirect( $keyword ) { + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword ); + if ( false !== $pre ) + return $pre; + + if ( !yourls_do_log_redirect() ) + return true; + + global $ydb; + $table = YOURLS_DB_TABLE_LOG; + + $keyword = yourls_sanitize_string( $keyword ); + $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) : 'direct' ); + $ua = yourls_get_user_agent(); + $ip = yourls_get_IP(); + $location = yourls_geo_ip_to_countrycode( $ip ); + + return $ydb->query( "INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" ); +} + /** * Check if we want to not log redirects (for stats) * */ -function yourls_do_log_redirect() { - return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true ); -} - +function yourls_do_log_redirect() { + return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true ); +} + /** * Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/ * */ -function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) { - // Allow plugins to short-circuit the Geo IP API - $location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', false, $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here - if ( false !== $location ) - return $location; - - if ( !file_exists( YOURLS_INC.'/geo/GeoIP.dat') || !file_exists( YOURLS_INC.'/geo/geoip.inc') ) - return $default; - - if ( $ip == '' ) - $ip = yourls_get_IP(); - - require_once( YOURLS_INC.'/geo/geoip.inc') ; - $gi = geoip_open( YOURLS_INC.'/geo/GeoIP.dat', GEOIP_STANDARD); - $location = geoip_country_code_by_addr($gi, $ip); - geoip_close($gi); - - return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default ); -} - +function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) { + // Allow plugins to short-circuit the Geo IP API + $location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', false, $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here + if ( false !== $location ) + return $location; + + if ( !file_exists( YOURLS_INC.'/geo/GeoIP.dat') || !file_exists( YOURLS_INC.'/geo/geoip.inc') ) + return $default; + + if ( $ip == '' ) + $ip = yourls_get_IP(); + + require_once( YOURLS_INC.'/geo/geoip.inc') ; + $gi = geoip_open( YOURLS_INC.'/geo/GeoIP.dat', GEOIP_STANDARD); + $location = geoip_country_code_by_addr($gi, $ip); + geoip_close($gi); + + return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default ); +} + /** * Converts a 2 letter country code to long name (ie AU -> Australia) * */ -function yourls_geo_countrycode_to_countryname( $code ) { - // Allow plugins to short-circuit the Geo IP API - $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code ); - if ( false !== $country ) - return $country; - - // Load the Geo class if not already done - if( !class_exists( 'GeoIP' ) ) { - $temp = yourls_geo_ip_to_countrycode( '127.0.0.1' ); - } - - if( class_exists( 'GeoIP' ) ) { - $geo = new GeoIP; - $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[ $code ]; - $long = $geo->GEOIP_COUNTRY_NAMES[ $id ]; - return $long; - } else { - return false; - } -} - +function yourls_geo_countrycode_to_countryname( $code ) { + // Allow plugins to short-circuit the Geo IP API + $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code ); + if ( false !== $country ) + return $country; + + // Load the Geo class if not already done + if( !class_exists( 'GeoIP' ) ) { + $temp = yourls_geo_ip_to_countrycode( '127.0.0.1' ); + } + + if( class_exists( 'GeoIP' ) ) { + $geo = new GeoIP; + $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[ $code ]; + $long = $geo->GEOIP_COUNTRY_NAMES[ $id ]; + return $long; + } else { + return false; + } +} + /** * Return flag URL from 2 letter country code * */ -function yourls_geo_get_flag( $code ) { - if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) { - $img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' ); - } else { - $img = false; - } - return yourls_apply_filter( 'geo_get_flag', $img, $code ); -} - - +function yourls_geo_get_flag( $code ) { + if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) { + $img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' ); + } else { + $img = false; + } + return yourls_apply_filter( 'geo_get_flag', $img, $code ); +} + + /** * Check if an upgrade is needed * */ -function yourls_upgrade_is_needed() { - // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS - list( $currentver, $currentsql ) = yourls_get_current_version_from_sql(); - if( $currentsql < YOURLS_DB_VERSION ) - return true; - - return false; -} - +function yourls_upgrade_is_needed() { + // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS + list( $currentver, $currentsql ) = yourls_get_current_version_from_sql(); + if( $currentsql < YOURLS_DB_VERSION ) + return true; + + return false; +} + /** * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table. * */ -function yourls_get_current_version_from_sql() { - $currentver = yourls_get_option( 'version' ); - $currentsql = yourls_get_option( 'db_version' ); - - // Values if version is 1.3 - if( !$currentver ) - $currentver = '1.3'; - if( !$currentsql ) - $currentsql = '100'; - - return array( $currentver, $currentsql); -} - +function yourls_get_current_version_from_sql() { + $currentver = yourls_get_option( 'version' ); + $currentsql = yourls_get_option( 'db_version' ); + + // Values if version is 1.3 + if( !$currentver ) + $currentver = '1.3'; + if( !$currentsql ) + $currentsql = '100'; + + return array( $currentver, $currentsql); +} + /** * Read an option from DB (or from cache if available). Return value or $default if not found * */ -function yourls_get_option( $option_name, $default = false ) { - global $ydb; - - // Allow plugins to short-circuit options - $pre = yourls_apply_filter( 'shunt_option_'.$option_name, false ); - if ( false !== $pre ) - return $pre; - - // If option not cached already, get its value from the DB - if ( !isset( $ydb->option[$option_name] ) ) { - $table = YOURLS_DB_TABLE_OPTIONS; - $option_name = yourls_escape( $option_name ); - $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" ); - if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values - $value = $row->option_value; - } else { // option does not exist, so we must cache its non-existence - $value = $default; - } - $ydb->option[ $option_name ] = yourls_maybe_unserialize( $value ); - } - - return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] ); -} - +function yourls_get_option( $option_name, $default = false ) { + global $ydb; + + // Allow plugins to short-circuit options + $pre = yourls_apply_filter( 'shunt_option_'.$option_name, false ); + if ( false !== $pre ) + return $pre; + + // If option not cached already, get its value from the DB + if ( !isset( $ydb->option[$option_name] ) ) { + $table = YOURLS_DB_TABLE_OPTIONS; + $option_name = yourls_escape( $option_name ); + $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" ); + if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values + $value = $row->option_value; + } else { // option does not exist, so we must cache its non-existence + $value = $default; + } + $ydb->option[ $option_name ] = yourls_maybe_unserialize( $value ); + } + + return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] ); +} + /** * Read all options from DB at once * */ -function yourls_get_all_options() { - global $ydb; - - // Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options) - $pre = yourls_apply_filter( 'shunt_all_options', false ); - if ( false !== $pre ) - return $pre; - - $table = YOURLS_DB_TABLE_OPTIONS; - - $allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" ); - - foreach( (array)$allopt as $option ) { - $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value ); - } - - $ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option ); -} - +function yourls_get_all_options() { + global $ydb; + + // Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options) + $pre = yourls_apply_filter( 'shunt_all_options', false ); + if ( false !== $pre ) + return $pre; + + $table = YOURLS_DB_TABLE_OPTIONS; + + $allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" ); + + foreach( (array)$allopt as $option ) { + $ydb->option[$option->option_name] = yourls_maybe_unserialize( $option->option_value ); + } + + $ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option ); +} + /** * Update (add if doesn't exist) an option to DB * */ -function yourls_update_option( $option_name, $newvalue ) { - global $ydb; - $table = YOURLS_DB_TABLE_OPTIONS; - - $safe_option_name = yourls_escape( $option_name ); - - $oldvalue = yourls_get_option( $safe_option_name ); - - // If the new and old values are the same, no need to update. - if ( $newvalue === $oldvalue ) - return false; - - if ( false === $oldvalue ) { - yourls_add_option( $option_name, $newvalue ); - return true; - } - - $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) ); - - yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue ); - - $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" ); - - if ( $ydb->rows_affected == 1 ) { - $ydb->option[ $option_name ] = $newvalue; - return true; - } - return false; -} - +function yourls_update_option( $option_name, $newvalue ) { + global $ydb; + $table = YOURLS_DB_TABLE_OPTIONS; + + $safe_option_name = yourls_escape( $option_name ); + + $oldvalue = yourls_get_option( $safe_option_name ); + + // If the new and old values are the same, no need to update. + if ( $newvalue === $oldvalue ) + return false; + + if ( false === $oldvalue ) { + yourls_add_option( $option_name, $newvalue ); + return true; + } + + $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) ); + + yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue ); + + $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" ); + + if ( $ydb->rows_affected == 1 ) { + $ydb->option[ $option_name ] = $newvalue; + return true; + } + return false; +} + /** * Add an option to the DB * */ -function yourls_add_option( $name, $value = '' ) { - global $ydb; - $table = YOURLS_DB_TABLE_OPTIONS; - $safe_name = yourls_escape( $name ); - - // Make sure the option doesn't already exist - if ( false !== yourls_get_option( $safe_name ) ) - return; - - $_value = yourls_escape( yourls_maybe_serialize( $value ) ); - - yourls_do_action( 'add_option', $safe_name, $_value ); - - $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" ); - $ydb->option[ $name ] = $value; - return; -} - - +function yourls_add_option( $name, $value = '' ) { + global $ydb; + $table = YOURLS_DB_TABLE_OPTIONS; + $safe_name = yourls_escape( $name ); + + // Make sure the option doesn't already exist + if ( false !== yourls_get_option( $safe_name ) ) + return; + + $_value = yourls_escape( yourls_maybe_serialize( $value ) ); + + yourls_do_action( 'add_option', $safe_name, $_value ); + + $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" ); + $ydb->option[ $name ] = $value; + return; +} + + /** * Delete an option from the DB * */ -function yourls_delete_option( $name ) { - global $ydb; - $table = YOURLS_DB_TABLE_OPTIONS; - $name = yourls_escape( $name ); - - // Get the ID, if no ID then return - $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" ); - if ( is_null( $option ) || !$option->option_id ) - return false; - - yourls_do_action( 'delete_option', $option_name ); - - $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" ); - return true; -} - - - +function yourls_delete_option( $name ) { + global $ydb; + $table = YOURLS_DB_TABLE_OPTIONS; + $name = yourls_escape( $name ); + + // Get the ID, if no ID then return + $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" ); + if ( is_null( $option ) || !$option->option_id ) + return false; + + yourls_do_action( 'delete_option', $option_name ); + + $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" ); + return true; +} + + + /** * Serialize data if needed. Stolen from WordPress * */ -function yourls_maybe_serialize( $data ) { - if ( is_array( $data ) || is_object( $data ) ) - return serialize( $data ); - - if ( yourls_is_serialized( $data ) ) - return serialize( $data ); - - return $data; -} - +function yourls_maybe_serialize( $data ) { + if ( is_array( $data ) || is_object( $data ) ) + return serialize( $data ); + + if ( yourls_is_serialized( $data ) ) + return serialize( $data ); + + return $data; +} + /** * Check value to find if it was serialized. Stolen from WordPress * */ -function yourls_is_serialized( $data ) { - // if it isn't a string, it isn't serialized - if ( !is_string( $data ) ) - return false; - $data = trim( $data ); - if ( 'N;' == $data ) - return true; - if ( !preg_match( '/^([adObis]):/', $data, $badions ) ) - return false; - switch ( $badions[1] ) { - case 'a' : - case 'O' : - case 's' : - if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) ) - return true; - break; - case 'b' : - case 'i' : - case 'd' : - if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) ) - return true; - break; - } - return false; -} - +function yourls_is_serialized( $data ) { + // if it isn't a string, it isn't serialized + if ( !is_string( $data ) ) + return false; + $data = trim( $data ); + if ( 'N;' == $data ) + return true; + if ( !preg_match( '/^([adObis]):/', $data, $badions ) ) + return false; + switch ( $badions[1] ) { + case 'a' : + case 'O' : + case 's' : + if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) ) + return true; + break; + case 'b' : + case 'i' : + case 'd' : + if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) ) + return true; + break; + } + return false; +} + /** * Unserialize value only if it was serialized. Stolen from WP * */ -function yourls_maybe_unserialize( $original ) { - if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in - return @unserialize( $original ); - return $original; -} - +function yourls_maybe_unserialize( $original ) { + if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in + return @unserialize( $original ); + return $original; +} + /** * Determine if the current page is private * */ -function yourls_is_private() { - $private = false; - - if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) { - - // Allow overruling for particular pages: - - // API - if( yourls_is_API() ) { - if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false ) - $private = true; - - // Infos - } elseif( yourls_is_infos() ) { - if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false ) - $private = true; - - // Others - } else { - $private = true; - } - - } - - return yourls_apply_filter( 'is_private', $private ); -} - +function yourls_is_private() { + $private = false; + + if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) { + + // Allow overruling for particular pages: + + // API + if( yourls_is_API() ) { + if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false ) + $private = true; + + // Infos + } elseif( yourls_is_infos() ) { + if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false ) + $private = true; + + // Others + } else { + $private = true; + } + + } + + return yourls_apply_filter( 'is_private', $private ); +} + /** * Show login form if required * */ -function yourls_maybe_require_auth() { - if( yourls_is_private() ) - require_once( YOURLS_INC.'/auth.php' ); -} - +function yourls_maybe_require_auth() { + if( yourls_is_private() ) + require_once( YOURLS_INC.'/auth.php' ); +} + /** * Allow several short URLs for the same long URL ? * */ -function yourls_allow_duplicate_longurls() { - // special treatment if API to check for WordPress plugin requests - if( yourls_is_API() ) { - if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' ) - return false; - } - return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false ); -} - +function yourls_allow_duplicate_longurls() { + // special treatment if API to check for WordPress plugin requests + if( yourls_is_API() ) { + if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' ) + return false; + } + return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false ); +} + /** * Return list of all shorturls associated to the same long URL. Returns NULL or array of keywords. * */ -function yourls_get_duplicate_keywords( $longurl ) { - if( !yourls_allow_duplicate_longurls() ) - return NULL; - - global $ydb; - $longurl = yourls_escape( yourls_sanitize_url($longurl) ); - $table = YOURLS_DB_TABLE_URL; - - $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" ); - return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl ); -} - +function yourls_get_duplicate_keywords( $longurl ) { + if( !yourls_allow_duplicate_longurls() ) + return NULL; + + global $ydb; + $longurl = yourls_escape( yourls_sanitize_url($longurl) ); + $table = YOURLS_DB_TABLE_URL; + + $return = $ydb->get_col( "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'" ); + return yourls_apply_filter( 'get_duplicate_keywords', $return, $longurl ); +} + /** * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die. * */ -function yourls_check_IP_flood( $ip = '' ) { - - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip ); - if ( false !== $pre ) - return $pre; - - yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here - - if( - ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) || - !defined('YOURLS_FLOOD_DELAY_SECONDS') - ) - return true; - - $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() ); - - // Don't throttle whitelist IPs - if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) { - $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST ); - foreach( (array)$whitelist_ips as $whitelist_ip ) { - $whitelist_ip = trim( $whitelist_ip ); - if ( $whitelist_ip == $ip ) - return true; - } - } - - // Don't throttle logged in users - if( yourls_is_private() ) { - if( yourls_is_valid_user() === true ) - return true; - } - - yourls_do_action( 'check_ip_flood', $ip ); - - global $ydb; - $table = YOURLS_DB_TABLE_URL; - - $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" ); - if( $lasttime ) { - $now = date( 'U' ); - $then = date( 'U', strtotime( $lasttime ) ); - if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) { - // Flood! - yourls_do_action( 'ip_flood', $ip, $now - $then ); - yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 ); - } - } - - return true; -} - -/** - * Check if YOURLS is installing - * - * @return bool - * @since 1.6 - */ -function yourls_is_installing() { - $installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true; - return yourls_apply_filter( 'is_installing', $installing ); -} - -/** - * Check if YOURLS is upgrading - * - * @return bool - * @since 1.6 - */ -function yourls_is_upgrading() { - $upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true; - return yourls_apply_filter( 'is_upgrading', $upgrading ); -} - - +function yourls_check_IP_flood( $ip = '' ) { + + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip ); + if ( false !== $pre ) + return $pre; + + yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here + + if( + ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) || + !defined('YOURLS_FLOOD_DELAY_SECONDS') + ) + return true; + + $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() ); + + // Don't throttle whitelist IPs + if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) { + $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST ); + foreach( (array)$whitelist_ips as $whitelist_ip ) { + $whitelist_ip = trim( $whitelist_ip ); + if ( $whitelist_ip == $ip ) + return true; + } + } + + // Don't throttle logged in users + if( yourls_is_private() ) { + if( yourls_is_valid_user() === true ) + return true; + } + + yourls_do_action( 'check_ip_flood', $ip ); + + global $ydb; + $table = YOURLS_DB_TABLE_URL; + + $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" ); + if( $lasttime ) { + $now = date( 'U' ); + $then = date( 'U', strtotime( $lasttime ) ); + if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) { + // Flood! + yourls_do_action( 'ip_flood', $ip, $now - $then ); + yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 ); + } + } + + return true; +} + +/** + * Check if YOURLS is installing + * + * @return bool + * @since 1.6 + */ +function yourls_is_installing() { + $installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true; + return yourls_apply_filter( 'is_installing', $installing ); +} + +/** + * Check if YOURLS is upgrading + * + * @return bool + * @since 1.6 + */ +function yourls_is_upgrading() { + $upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true; + return yourls_apply_filter( 'is_upgrading', $upgrading ); +} + + /** * Check if YOURLS is installed * */ -function yourls_is_installed() { - static $is_installed = false; - if ( $is_installed === false ) { - $check_14 = $check_13 = false; - global $ydb; - if( defined('YOURLS_DB_TABLE_NEXTDEC') ) - $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC); - $check_14 = yourls_get_option( 'version' ); - $is_installed = $check_13 || $check_14; - } - return yourls_apply_filter( 'is_installed', $is_installed ); -} - +function yourls_is_installed() { + static $is_installed = false; + if ( $is_installed === false ) { + $check_14 = $check_13 = false; + global $ydb; + if( defined('YOURLS_DB_TABLE_NEXTDEC') ) + $check_13 = $ydb->get_var('SELECT `next_id` FROM '.YOURLS_DB_TABLE_NEXTDEC); + $check_14 = yourls_get_option( 'version' ); + $is_installed = $check_13 || $check_14; + } + return yourls_apply_filter( 'is_installed', $is_installed ); +} + /** * Generate random string of (int)$length length and type $type (see function for details) * */ -function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) { - $str = ''; - $length = intval( $length ); - - // define possible characters - switch ( $type ) { - - // custom char list, or comply to charset as defined in config - case '0': - $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ; - break; - - // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords. - case '1': - $possible = "23456789bcdfghjkmnpqrstvwxyz"; - break; - - // Same, with lower + upper - case '2': - $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ"; - break; - - // all letters, lowercase - case '3': - $possible = "abcdefghijklmnopqrstuvwxyz"; - break; - - // all letters, lowercase + uppercase - case '4': - $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - break; - - // all digits & letters lowercase - case '5': - $possible = "0123456789abcdefghijklmnopqrstuvwxyz"; - break; - - // all digits & letters lowercase + uppercase - case '6': - $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - break; - - } - - $i = 0; - while ($i < $length) { - $str .= substr($possible, mt_rand(0, strlen($possible)-1), 1); - $i++; - } - - return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist ); -} - +function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) { + $str = ''; + $length = intval( $length ); + + // define possible characters + switch ( $type ) { + + // custom char list, or comply to charset as defined in config + case '0': + $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ; + break; + + // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords. + case '1': + $possible = "23456789bcdfghjkmnpqrstvwxyz"; + break; + + // Same, with lower + upper + case '2': + $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ"; + break; + + // all letters, lowercase + case '3': + $possible = "abcdefghijklmnopqrstuvwxyz"; + break; + + // all letters, lowercase + uppercase + case '4': + $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + break; + + // all digits & letters lowercase + case '5': + $possible = "0123456789abcdefghijklmnopqrstuvwxyz"; + break; + + // all digits & letters lowercase + uppercase + case '6': + $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + break; + + } + + $i = 0; + while ($i < $length) { + $str .= substr($possible, mt_rand(0, strlen($possible)-1), 1); + $i++; + } + + return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist ); +} + /** * Return salted string * */ -function yourls_salt( $string ) { - $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ; - return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string ); -} - +function yourls_salt( $string ) { + $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ; + return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string ); +} + /** - * Add a query var to a URL and return URL. Completely stolen from WP. - * - * Works with one of these parameter patterns: - * array( 'var' => 'value' ) - * array( 'var' => 'value' ), $url - * 'var', 'value' - * 'var', 'value', $url + * Add a query var to a URL and return URL. Completely stolen from WP. + * + * Works with one of these parameter patterns: + * array( 'var' => 'value' ) + * array( 'var' => 'value' ), $url + * 'var', 'value' + * 'var', 'value', $url * If $url ommited, uses $_SERVER['REQUEST_URI'] * */ -function yourls_add_query_arg() { - $ret = ''; - if ( is_array( func_get_arg(0) ) ) { - if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) ) - $uri = $_SERVER['REQUEST_URI']; - else - $uri = @func_get_arg( 1 ); - } else { - if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) ) - $uri = $_SERVER['REQUEST_URI']; - else - $uri = @func_get_arg( 2 ); - } - - $uri = str_replace( '&', '&', $uri ); - - - if ( $frag = strstr( $uri, '#' ) ) - $uri = substr( $uri, 0, -strlen( $frag ) ); - else - $frag = ''; - - if ( preg_match( '|^https?://|i', $uri, $matches ) ) { - $protocol = $matches[0]; - $uri = substr( $uri, strlen( $protocol ) ); - } else { - $protocol = ''; - } - - if ( strpos( $uri, '?' ) !== false ) { - $parts = explode( '?', $uri, 2 ); - if ( 1 == count( $parts ) ) { - $base = '?'; - $query = $parts[0]; - } else { - $base = $parts[0] . '?'; - $query = $parts[1]; - } - } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) { - $base = $uri . '?'; - $query = ''; - } else { - $base = ''; - $query = $uri; - } - - parse_str( $query, $qs ); - $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string - if ( is_array( func_get_arg( 0 ) ) ) { - $kayvees = func_get_arg( 0 ); - $qs = array_merge( $qs, $kayvees ); - } else { - $qs[func_get_arg( 0 )] = func_get_arg( 1 ); - } - - foreach ( (array) $qs as $k => $v ) { - if ( $v === false ) - unset( $qs[$k] ); - } - - $ret = http_build_query( $qs ); - $ret = trim( $ret, '?' ); - $ret = preg_replace( '#=(&|$)#', '$1', $ret ); - $ret = $protocol . $base . $ret . $frag; - $ret = rtrim( $ret, '?' ); - return $ret; -} - +function yourls_add_query_arg() { + $ret = ''; + if ( is_array( func_get_arg(0) ) ) { + if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) ) + $uri = $_SERVER['REQUEST_URI']; + else + $uri = @func_get_arg( 1 ); + } else { + if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) ) + $uri = $_SERVER['REQUEST_URI']; + else + $uri = @func_get_arg( 2 ); + } + + $uri = str_replace( '&', '&', $uri ); + + + if ( $frag = strstr( $uri, '#' ) ) + $uri = substr( $uri, 0, -strlen( $frag ) ); + else + $frag = ''; + + if ( preg_match( '|^https?://|i', $uri, $matches ) ) { + $protocol = $matches[0]; + $uri = substr( $uri, strlen( $protocol ) ); + } else { + $protocol = ''; + } + + if ( strpos( $uri, '?' ) !== false ) { + $parts = explode( '?', $uri, 2 ); + if ( 1 == count( $parts ) ) { + $base = '?'; + $query = $parts[0]; + } else { + $base = $parts[0] . '?'; + $query = $parts[1]; + } + } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) { + $base = $uri . '?'; + $query = ''; + } else { + $base = ''; + $query = $uri; + } + + parse_str( $query, $qs ); + $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string + if ( is_array( func_get_arg( 0 ) ) ) { + $kayvees = func_get_arg( 0 ); + $qs = array_merge( $qs, $kayvees ); + } else { + $qs[func_get_arg( 0 )] = func_get_arg( 1 ); + } + + foreach ( (array) $qs as $k => $v ) { + if ( $v === false ) + unset( $qs[$k] ); + } + + $ret = http_build_query( $qs ); + $ret = trim( $ret, '?' ); + $ret = preg_replace( '#=(&|$)#', '$1', $ret ); + $ret = $protocol . $base . $ret . $frag; + $ret = rtrim( $ret, '?' ); + return $ret; +} + /** * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg() * */ -function yourls_urlencode_deep( $value ) { - $value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value ); - return $value; -} - +function yourls_urlencode_deep( $value ) { + $value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value ); + return $value; +} + /** * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP. * */ -function yourls_remove_query_arg( $key, $query = false ) { - if ( is_array( $key ) ) { // removing multiple keys - foreach ( $key as $k ) - $query = yourls_add_query_arg( $k, false, $query ); - return $query; - } - return yourls_add_query_arg( $key, false, $query ); -} - +function yourls_remove_query_arg( $key, $query = false ) { + if ( is_array( $key ) ) { // removing multiple keys + foreach ( $key as $k ) + $query = yourls_add_query_arg( $k, false, $query ); + return $query; + } + return yourls_add_query_arg( $key, false, $query ); +} + /** * Return a time-dependent string for nonce creation * */ -function yourls_tick() { - return ceil( time() / YOURLS_NONCE_LIFE ); -} - +function yourls_tick() { + return ceil( time() / YOURLS_NONCE_LIFE ); +} + /** * Create a time limited, action limited and user limited token * */ -function yourls_create_nonce( $action, $user = false ) { - if( false == $user ) - $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1'; - $tick = yourls_tick(); - return substr( yourls_salt($tick . $action . $user), 0, 10 ); -} - +function yourls_create_nonce( $action, $user = false ) { + if( false == $user ) + $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1'; + $tick = yourls_tick(); + return substr( yourls_salt($tick . $action . $user), 0, 10 ); +} + /** * Create a nonce field for inclusion into a form * */ -function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) { - $field = ''; - if( $echo ) - echo $field."\n"; - return $field; -} - +function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) { + $field = ''; + if( $echo ) + echo $field."\n"; + return $field; +} + /** * Add a nonce to a URL. If URL omitted, adds nonce to current URL * */ -function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) { - $nonce = yourls_create_nonce( $action, $user ); - return yourls_add_query_arg( $name, $nonce, $url ); -} - +function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) { + $nonce = yourls_create_nonce( $action, $user ); + return yourls_add_query_arg( $name, $nonce, $url ); +} + /** - * Check validity of a nonce (ie time span, user and action match). - * - * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined) + * Check validity of a nonce (ie time span, user and action match). + * + * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined) * if $nonce is false or unspecified, it will use $_REQUEST['nonce'] * */ -function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) { - // get user - if( false == $user ) - $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1'; - - // get current nonce value - if( false == $nonce && isset( $_REQUEST['nonce'] ) ) - $nonce = $_REQUEST['nonce']; - - // what nonce should be - $valid = yourls_create_nonce( $action, $user ); - - if( $nonce == $valid ) { - return true; - } else { - if( $return ) - die( $return ); - yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 ); - } -} - +function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) { + // get user + if( false == $user ) + $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1'; + + // get current nonce value + if( false == $nonce && isset( $_REQUEST['nonce'] ) ) + $nonce = $_REQUEST['nonce']; + + // what nonce should be + $valid = yourls_create_nonce( $action, $user ); + + if( $nonce == $valid ) { + return true; + } else { + if( $return ) + die( $return ); + yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 ); + } +} + /** * Converts keyword into short link (prepend with YOURLS base URL) * */ -function yourls_link( $keyword = '' ) { - $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ); - return yourls_apply_filter( 'yourls_link', $link, $keyword ); -} - +function yourls_link( $keyword = '' ) { + $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ); + return yourls_apply_filter( 'yourls_link', $link, $keyword ); +} + /** * Converts keyword into stat link (prepend with YOURLS base URL, append +) * */ -function yourls_statlink( $keyword = '' ) { - $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+'; - if( yourls_is_ssl() ) - $link = str_replace( 'http://', 'https://', $link ); - return yourls_apply_filter( 'yourls_statlink', $link, $keyword ); -} - +function yourls_statlink( $keyword = '' ) { + $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+'; + if( yourls_is_ssl() ) + $link = str_replace( 'http://', 'https://', $link ); + return yourls_apply_filter( 'yourls_statlink', $link, $keyword ); +} + /** * Check if we're in API mode. Returns bool * */ -function yourls_is_API() { - if ( defined( 'YOURLS_API' ) && YOURLS_API == true ) - return true; - return false; -} - +function yourls_is_API() { + if ( defined( 'YOURLS_API' ) && YOURLS_API == true ) + return true; + return false; +} + /** * Check if we're in Ajax mode. Returns bool * */ -function yourls_is_Ajax() { - if ( defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true ) - return true; - return false; -} - +function yourls_is_Ajax() { + if ( defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true ) + return true; + return false; +} + /** * Check if we're in GO mode (yourls-go.php). Returns bool * */ -function yourls_is_GO() { - if ( defined( 'YOURLS_GO' ) && YOURLS_GO == true ) - return true; - return false; -} - +function yourls_is_GO() { + if ( defined( 'YOURLS_GO' ) && YOURLS_GO == true ) + return true; + return false; +} + /** * Check if we're displaying stats infos (yourls-infos.php). Returns bool * */ -function yourls_is_infos() { - if ( defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true ) - return true; - return false; -} - +function yourls_is_infos() { + if ( defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true ) + return true; + return false; +} + /** * Check if we'll need interface display function (ie not API or redirection) * */ -function yourls_has_interface() { - if( yourls_is_API() or yourls_is_GO() ) - return false; - return true; -} - +function yourls_has_interface() { + if( yourls_is_API() or yourls_is_GO() ) + return false; + return true; +} + /** * Check if we're in the admin area. Returns bool * */ -function yourls_is_admin() { - if ( defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true ) - return true; - return false; -} - +function yourls_is_admin() { + if ( defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true ) + return true; + return false; +} + /** * Check if the server seems to be running on Windows. Not exactly sure how reliable this is. * */ -function yourls_is_windows() { - return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\'; -} - +function yourls_is_windows() { + return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\'; +} + /** * Check if SSL is required. Returns bool. * */ -function yourls_needs_ssl() { - if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true ) - return true; - return false; -} - +function yourls_needs_ssl() { + if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true ) + return true; + return false; +} + /** * Return admin link, with SSL preference if applicable. * */ -function yourls_admin_url( $page = '' ) { - $admin = YOURLS_SITE . '/admin/' . $page; - if( yourls_is_ssl() or yourls_needs_ssl() ) - $admin = str_replace('http://', 'https://', $admin); - return yourls_apply_filter( 'admin_url', $admin, $page ); -} - +function yourls_admin_url( $page = '' ) { + $admin = YOURLS_SITE . '/admin/' . $page; + if( yourls_is_ssl() or yourls_needs_ssl() ) + $admin = str_replace('http://', 'https://', $admin); + return yourls_apply_filter( 'admin_url', $admin, $page ); +} + /** * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference * */ -function yourls_site_url( $echo = true, $url = '' ) { - $url = yourls_get_relative_url( $url ); - $url = trim( YOURLS_SITE . '/' . $url, '/' ); - - // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages - if( yourls_is_ssl() ) - $url = str_replace( 'http://', 'https://', $url ); - $url = yourls_apply_filter( 'site_url', $url ); - if( $echo ) - echo $url; - return $url; -} - +function yourls_site_url( $echo = true, $url = '' ) { + $url = yourls_get_relative_url( $url ); + $url = trim( YOURLS_SITE . '/' . $url, '/' ); + + // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages + if( yourls_is_ssl() ) + $url = str_replace( 'http://', 'https://', $url ); + $url = yourls_apply_filter( 'site_url', $url ); + if( $echo ) + echo $url; + return $url; +} + /** * Check if SSL is used, returns bool. Stolen from WP. * */ -function yourls_is_ssl() { - $is_ssl = false; - if ( isset( $_SERVER['HTTPS'] ) ) { - if ( 'on' == strtolower( $_SERVER['HTTPS'] ) ) - $is_ssl = true; - if ( '1' == $_SERVER['HTTPS'] ) - $is_ssl = true; - } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) { - $is_ssl = true; - } - return yourls_apply_filter( 'is_ssl', $is_ssl ); -} - - +function yourls_is_ssl() { + $is_ssl = false; + if ( isset( $_SERVER['HTTPS'] ) ) { + if ( 'on' == strtolower( $_SERVER['HTTPS'] ) ) + $is_ssl = true; + if ( '1' == $_SERVER['HTTPS'] ) + $is_ssl = true; + } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) { + $is_ssl = true; + } + return yourls_apply_filter( 'is_ssl', $is_ssl ); +} + + /** * Get a remote page , return a string (either title or url) * */ -function yourls_get_remote_title( $url ) { - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url ); - if ( false !== $pre ) - return $pre; - - require_once( YOURLS_INC.'/functions-http.php' ); - - $url = yourls_sanitize_url( $url ); - - $title = $charset = false; - - $content = yourls_get_remote_content( $url ); - - // If false, return url as title. - // Todo: improve this with temporary title when shorturl_meta available? - if( false === $content ) - return $url; - - if( $content !== false ) { - // look for <title> - if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) { - $title = $found[1]; - unset( $found ); - } - - // look for charset - // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> - if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) { - $charset = trim($found[1], '"\' '); - unset( $found ); - } - } - - // if title not found, guess if returned content was actually an error message - if( $title == false && strpos( $content, 'Error' ) === 0 ) { - $title = $content; - } - - if( $title == false ) - $title = $url; - - /* - if( !yourls_seems_utf8( $title ) ) - $title = utf8_encode( $title ); - */ - - // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars) - if( function_exists( 'mb_convert_encoding' ) ) { - if( $charset ) { - $title = @mb_convert_encoding( $title, 'UTF-8', $charset ); - } else { - $title = @mb_convert_encoding( $title, 'UTF-8' ); - } - } - - // Remove HTML entities - $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ); - - // Strip out evil things - $title = yourls_sanitize_title( $title ); - - return yourls_apply_filter( 'get_remote_title', $title, $url ); -} - +function yourls_get_remote_title( $url ) { + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url ); + if ( false !== $pre ) + return $pre; + + require_once( YOURLS_INC.'/functions-http.php' ); + + $url = yourls_sanitize_url( $url ); + + $title = $charset = false; + + $content = yourls_get_remote_content( $url ); + + // If false, return url as title. + // Todo: improve this with temporary title when shorturl_meta available? + if( false === $content ) + return $url; + + if( $content !== false ) { + // look for <title> + if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) { + $title = $found[1]; + unset( $found ); + } + + // look for charset + // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + if ( preg_match('/<meta[^>]*?charset=([^>]*?)\/?>/is', $content, $found ) ) { + $charset = trim($found[1], '"\' '); + unset( $found ); + } + } + + // if title not found, guess if returned content was actually an error message + if( $title == false && strpos( $content, 'Error' ) === 0 ) { + $title = $content; + } + + if( $title == false ) + $title = $url; + + /* + if( !yourls_seems_utf8( $title ) ) + $title = utf8_encode( $title ); + */ + + // Charset conversion. We use @ to remove warnings (mb_ functions are easily bitching about illegal chars) + if( function_exists( 'mb_convert_encoding' ) ) { + if( $charset ) { + $title = @mb_convert_encoding( $title, 'UTF-8', $charset ); + } else { + $title = @mb_convert_encoding( $title, 'UTF-8' ); + } + } + + // Remove HTML entities + $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ); + + // Strip out evil things + $title = yourls_sanitize_title( $title ); + + return yourls_apply_filter( 'get_remote_title', $title, $url ); +} + /** * Quick UA check for mobile devices. Return boolean. * */ -function yourls_is_mobile_device() { - // Strings searched - $mobiles = array( - 'android', 'blackberry', 'blazer', - 'compal', 'elaine', 'fennec', 'hiptop', - 'iemobile', 'iphone', 'ipod', 'ipad', - 'iris', 'kindle', 'opera mobi', 'opera mini', - 'palm', 'phone', 'pocket', 'psp', 'symbian', - 'treo', 'wap', 'windows ce', 'windows phone' - ); - - // Current user-agent - $current = strtolower( $_SERVER['HTTP_USER_AGENT'] ); - - // Check and return - $is_mobile = ( str_replace( $mobiles, '', $current ) != $current ); - return yourls_apply_filter( 'is_mobile_device', $is_mobile ); -} - +function yourls_is_mobile_device() { + // Strings searched + $mobiles = array( + 'android', 'blackberry', 'blazer', + 'compal', 'elaine', 'fennec', 'hiptop', + 'iemobile', 'iphone', 'ipod', 'ipad', + 'iris', 'kindle', 'opera mobi', 'opera mini', + 'palm', 'phone', 'pocket', 'psp', 'symbian', + 'treo', 'wap', 'windows ce', 'windows phone' + ); + + // Current user-agent + $current = strtolower( $_SERVER['HTTP_USER_AGENT'] ); + + // Check and return + $is_mobile = ( str_replace( $mobiles, '', $current ) != $current ); + return yourls_apply_filter( 'is_mobile_device', $is_mobile ); +} + /** * Get request in YOURLS base (eg in 'http://site.com/yourls/abcd' get 'abdc') * */ -function yourls_get_request() { - // Allow plugins to short-circuit the whole function - $pre = yourls_apply_filter( 'shunt_get_request', false ); - if ( false !== $pre ) - return $pre; - - static $request = null; - - yourls_do_action( 'pre_get_request', $request ); - - if( $request !== null ) - return $request; - - // Ignore protocol & www. prefix - $root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE ); - // Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah - $request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 ); - - // Unless request looks like a full URL (ie request is a simple keyword) strip query string - if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) { - $request = current( explode( '?', $request ) ); - } - - return yourls_apply_filter( 'get_request', $request ); -} - +function yourls_get_request() { + // Allow plugins to short-circuit the whole function + $pre = yourls_apply_filter( 'shunt_get_request', false ); + if ( false !== $pre ) + return $pre; + + static $request = null; + + yourls_do_action( 'pre_get_request', $request ); + + if( $request !== null ) + return $request; + + // Ignore protocol & www. prefix + $root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE ); + // Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah + $request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 ); + + // Unless request looks like a full URL (ie request is a simple keyword) strip query string + if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) { + $request = current( explode( '?', $request ) ); + } + + return yourls_apply_filter( 'get_request', $request ); +} + /** * Change protocol to match current scheme used (http or https) * */ -function yourls_match_current_protocol( $url, $normal = 'http', $ssl = 'https' ) { - if( yourls_is_ssl() ) - $url = str_replace( $normal, $ssl, $url ); - return yourls_apply_filter( 'match_current_protocol', $url ); -} - +function yourls_match_current_protocol( $url, $normal = 'http', $ssl = 'https' ) { + if( yourls_is_ssl() ) + $url = str_replace( $normal, $ssl, $url ); + return yourls_apply_filter( 'match_current_protocol', $url ); +} + /** * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP. * */ -function yourls_fix_request_uri() { - - $default_server_values = array( - 'SERVER_SOFTWARE' => '', - 'REQUEST_URI' => '', - ); - $_SERVER = array_merge( $default_server_values, $_SERVER ); - - // Fix for IIS when running with PHP ISAPI - if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) { - - // IIS Mod-Rewrite - if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) { - $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL']; - } - // IIS Isapi_Rewrite - else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) { - $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL']; - } else { - // Use ORIG_PATH_INFO if there is no PATH_INFO - if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) ) - $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO']; - - // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice) - if ( isset( $_SERVER['PATH_INFO'] ) ) { - if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] ) - $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO']; - else - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO']; - } - - // Append the query string if it exists and isn't null - if ( ! empty( $_SERVER['QUERY_STRING'] ) ) { - $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING']; - } - } - } -} - +function yourls_fix_request_uri() { + + $default_server_values = array( + 'SERVER_SOFTWARE' => '', + 'REQUEST_URI' => '', + ); + $_SERVER = array_merge( $default_server_values, $_SERVER ); + + // Fix for IIS when running with PHP ISAPI + if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) { + + // IIS Mod-Rewrite + if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) { + $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL']; + } + // IIS Isapi_Rewrite + else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) { + $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL']; + } else { + // Use ORIG_PATH_INFO if there is no PATH_INFO + if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) ) + $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO']; + + // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice) + if ( isset( $_SERVER['PATH_INFO'] ) ) { + if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] ) + $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO']; + else + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO']; + } + + // Append the query string if it exists and isn't null + if ( ! empty( $_SERVER['QUERY_STRING'] ) ) { + $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING']; + } + } + } +} + /** * Shutdown function, runs just before PHP shuts down execution. Stolen from WP * */ -function yourls_shutdown() { - yourls_do_action( 'shutdown' ); -} - +function yourls_shutdown() { + yourls_do_action( 'shutdown' ); +} + /** * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL * */ -function yourls_favicon( $echo = true ) { - static $favicon = null; - if( $favicon !== null ) - return $favicon; - - $custom = null; - // search for favicon.(gif|ico|png|jpg|svg) - foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) { - if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) { - $custom = 'favicon.' . $ext; - break; - } - } - - if( $custom ) { - $favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom ); - } else { - $favicon = yourls_site_url( false ) . '/images/favicon.gif'; - } - if( $echo ) - echo $favicon; - return $favicon; -} - +function yourls_favicon( $echo = true ) { + static $favicon = null; + if( $favicon !== null ) + return $favicon; + + $custom = null; + // search for favicon.(gif|ico|png|jpg|svg) + foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) { + if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) { + $custom = 'favicon.' . $ext; + break; + } + } + + if( $custom ) { + $favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom ); + } else { + $favicon = yourls_site_url( false ) . '/images/favicon.gif'; + } + if( $echo ) + echo $favicon; + return $favicon; +} + /** * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP. * */ -function yourls_check_maintenance_mode() { - - $file = YOURLS_ABSPATH . '/.maintenance' ; - if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() ) - return; - - global $maintenance_start; - - include( $file ); - // If the $maintenance_start timestamp is older than 10 minutes, don't die. - if ( ( time() - $maintenance_start ) >= 600 ) - return; - - // Use any /user/maintenance.php file - if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) { - include( YOURLS_USERDIR.'/maintenance.php' ); - die(); - } - - // https://www.youtube.com/watch?v=Xw-m4jEY-Ns - $title = yourls__( 'Service temporarily unavailable' ); - $message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" . - yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' ); - yourls_die( $message, $title , 503 ); - -} - -/** - * Return current admin page, or null if not an admin page - * - * @return mixed string if admin page, null if not an admin page - * @since 1.6 - */ -function yourls_current_admin_page() { - if( yourls_is_admin() ) { - $current = substr( yourls_get_request(), 6 ); - if( $current === false ) - $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php - - return $current; - } - return null; -} - -/** - * Check if a URL protocol is allowed - * - * Checks a URL against a list of whitelisted protocols. Protocols must be defined with - * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid - * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either - * - * @since 1.6 - * - * @param string $url URL to be check - * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols - * @return boolean true if protocol allowed, false otherwise - */ -function yourls_is_allowed_protocol( $url, $protocols = array() ) { - if( ! $protocols ) { - global $yourls_allowedprotocols; - $protocols = $yourls_allowedprotocols; - } - - $protocol = yourls_get_protocol( $url ); - return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols ); -} - -/** - * Get protocol from a URL (eg mailto:, http:// ...) - * - * @since 1.6 - * - * @param string $url URL to be check - * @return string Protocol, with slash slash if applicable. Empty string if no protocol - */ -function yourls_get_protocol( $url ) { - preg_match( '!^[a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches ); - /* - http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax - The scheme name consists of a sequence of characters beginning with a letter and followed by any - combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are - case-insensitive, the canonical form is lowercase and documents that specify schemes must do so - with lowercase letters. It is followed by a colon (":"). - */ - $protocol = ( isset( $matches[0] ) ? $matches[0] : '' ); - return yourls_apply_filter( 'get_protocol', $protocol, $url ); -} - -/** - * Get relative URL (eg 'abc' from 'http://sho.rt/abc') - * - * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is - * or return empty string if $strict is true - * - * @since 1.6 - * @param string $url URL to relativize - * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string - * @return string URL - */ -function yourls_get_relative_url( $url, $strict = true ) { - $url = yourls_sanitize_url( $url ); - - // Remove protocols to make it easier - $noproto_url = str_replace( 'https:', 'http:', $url ); - $noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE ); - - // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative - $_url = str_replace( $noproto_site . '/', '', $noproto_url ); - if( $_url == $noproto_url ) - $_url = ( $strict ? '' : $url ); - - return yourls_apply_filter( 'get_relative_url', $_url, $url ); -} - -/** - * Marks a function as deprecated and informs when it has been used. Stolen from WP. - * - * There is a hook deprecated_function that will be called that can be used - * to get the backtrace up to what file and function called the deprecated - * function. - * - * The current behavior is to trigger a user error if YOURLS_DEBUG is true. - * - * This function is to be used in every function that is deprecated. - * - * @since 1.6 - * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead, - * and the version the function was deprecated in. - * @uses yourls_apply_filters() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do - * trigger or false to not trigger error. - * - * @param string $function The function that was called - * @param string $version The version of WordPress that deprecated the function - * @param string $replacement Optional. The function that should have been called - */ -function yourls_deprecated_function( $function, $version, $replacement = null ) { - - yourls_do_action( 'deprecated_function', $function, $replacement, $version ); - - // Allow plugin to filter the output error trigger - if ( YOURLS_DEBUG && yourls_apply_filters( 'deprecated_function_trigger_error', true ) ) { - if ( ! is_null( $replacement ) ) - trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) ); - else - trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) ); - } -} - -/** - * Return the value if not an empty string - * - * Used with array_filter(), to remove empty keys but not keys with value 0 or false - * - * @since 1.6 - * @param mixed $val Value to test against '' - * @return bool True if not an empty string - */ -function yourls_return_if_not_empty_string( $val ) { - return( $val !== '' ); -} +function yourls_check_maintenance_mode() { + + $file = YOURLS_ABSPATH . '/.maintenance' ; + if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() ) + return; + + global $maintenance_start; + + include( $file ); + // If the $maintenance_start timestamp is older than 10 minutes, don't die. + if ( ( time() - $maintenance_start ) >= 600 ) + return; + + // Use any /user/maintenance.php file + if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) { + include( YOURLS_USERDIR.'/maintenance.php' ); + die(); + } + + // https://www.youtube.com/watch?v=Xw-m4jEY-Ns + $title = yourls__( 'Service temporarily unavailable' ); + $message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" . + yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' ); + yourls_die( $message, $title , 503 ); + +} + +/** + * Return current admin page, or null if not an admin page + * + * @return mixed string if admin page, null if not an admin page + * @since 1.6 + */ +function yourls_current_admin_page() { + if( yourls_is_admin() ) { + $current = substr( yourls_get_request(), 6 ); + if( $current === false ) + $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php + + return $current; + } + return null; +} + +/** + * Check if a URL protocol is allowed + * + * Checks a URL against a list of whitelisted protocols. Protocols must be defined with + * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid + * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either + * + * @since 1.6 + * + * @param string $url URL to be check + * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols + * @return boolean true if protocol allowed, false otherwise + */ +function yourls_is_allowed_protocol( $url, $protocols = array() ) { + if( ! $protocols ) { + global $yourls_allowedprotocols; + $protocols = $yourls_allowedprotocols; + } + + $protocol = yourls_get_protocol( $url ); + return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols ); +} + +/** + * Get protocol from a URL (eg mailto:, http:// ...) + * + * @since 1.6 + * + * @param string $url URL to be check + * @return string Protocol, with slash slash if applicable. Empty string if no protocol + */ +function yourls_get_protocol( $url ) { + preg_match( '!^[a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches ); + /* + http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax + The scheme name consists of a sequence of characters beginning with a letter and followed by any + combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are + case-insensitive, the canonical form is lowercase and documents that specify schemes must do so + with lowercase letters. It is followed by a colon (":"). + */ + $protocol = ( isset( $matches[0] ) ? $matches[0] : '' ); + return yourls_apply_filter( 'get_protocol', $protocol, $url ); +} + +/** + * Get relative URL (eg 'abc' from 'http://sho.rt/abc') + * + * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is + * or return empty string if $strict is true + * + * @since 1.6 + * @param string $url URL to relativize + * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string + * @return string URL + */ +function yourls_get_relative_url( $url, $strict = true ) { + $url = yourls_sanitize_url( $url ); + + // Remove protocols to make it easier + $noproto_url = str_replace( 'https:', 'http:', $url ); + $noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE ); + + // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative + $_url = str_replace( $noproto_site . '/', '', $noproto_url ); + if( $_url == $noproto_url ) + $_url = ( $strict ? '' : $url ); + + return yourls_apply_filter( 'get_relative_url', $_url, $url ); +} + +/** + * Marks a function as deprecated and informs when it has been used. Stolen from WP. + * + * There is a hook deprecated_function that will be called that can be used + * to get the backtrace up to what file and function called the deprecated + * function. + * + * The current behavior is to trigger a user error if YOURLS_DEBUG is true. + * + * This function is to be used in every function that is deprecated. + * + * @since 1.6 + * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead, + * and the version the function was deprecated in. + * @uses yourls_apply_filters() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do + * trigger or false to not trigger error. + * + * @param string $function The function that was called + * @param string $version The version of WordPress that deprecated the function + * @param string $replacement Optional. The function that should have been called + */ +function yourls_deprecated_function( $function, $version, $replacement = null ) { + + yourls_do_action( 'deprecated_function', $function, $replacement, $version ); + + // Allow plugin to filter the output error trigger + if ( YOURLS_DEBUG && yourls_apply_filters( 'deprecated_function_trigger_error', true ) ) { + if ( ! is_null( $replacement ) ) + trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) ); + else + trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) ); + } +} + +/** + * Return the value if not an empty string + * + * Used with array_filter(), to remove empty keys but not keys with value 0 or false + * + * @since 1.6 + * @param mixed $val Value to test against '' + * @return bool True if not an empty string + */ +function yourls_return_if_not_empty_string( $val ) { + return( $val !== '' ); +} diff --git a/includes/load-yourls.php b/includes/load-yourls.php index 8c09484..e2a5a2a 100644 --- a/includes/load-yourls.php +++ b/includes/load-yourls.php @@ -1,177 +1,177 @@ -<?php -// This file initialize everything needed for YOURLS - -// Include settings -if( file_exists( dirname( dirname( __FILE__ ) ) . '/user/config.php' ) ) { - // config.php in /user/ - require_once( dirname( dirname( __FILE__ ) ) . '/user/config.php' ); -} elseif ( file_exists( dirname( __FILE__ ) . '/config.php' ) ) { - // config.php in /includes/ - require_once( dirname( __FILE__ ) . '/config.php' ); -} else { - // config.php not found :( - die( '<p class="error">Cannot find <tt>config.php</tt>.</p><p>Please read the <tt>readme.html</tt> to learn how to install YOURLS</p>' ); -} - -// Check if config.php was properly updated for 1.4 -if( !defined( 'YOURLS_DB_PREFIX' ) ) - die( '<p class="error">Your <tt>config.php</tt> does not contain all the required constant definitions.</p><p>Please check <tt>config-sample.php</tt> and update your config accordingly, there are new stuffs!</p>' ); - - -// Define core constants that have not been user defined in config.php - -// physical path of YOURLS root -if( !defined( 'YOURLS_ABSPATH' ) ) - define( 'YOURLS_ABSPATH', str_replace( '\\', '/', dirname( dirname( __FILE__ ) ) ) ); - -// physical path of includes directory -if( !defined( 'YOURLS_INC' ) ) - define( 'YOURLS_INC', YOURLS_ABSPATH.'/includes' ); - -// physical path of user directory -if( !defined( 'YOURLS_USERDIR' ) ) - define( 'YOURLS_USERDIR', YOURLS_ABSPATH.'/user' ); - -// URL of user directory -if( !defined( 'YOURLS_USERURL' ) ) - define( 'YOURLS_USERURL', YOURLS_SITE.'/user' ); - -// physical path of translations directory -if( !defined( 'YOURLS_LANG_DIR' ) ) - define( 'YOURLS_LANG_DIR', YOURLS_USERDIR.'/languages' ); - -// physical path of plugins directory -if( !defined( 'YOURLS_PLUGINDIR' ) ) - define( 'YOURLS_PLUGINDIR', YOURLS_USERDIR.'/plugins' ); - -// URL of plugins directory -if( !defined( 'YOURLS_PLUGINURL' ) ) - define( 'YOURLS_PLUGINURL', YOURLS_USERURL.'/plugins' ); - -// physical path of pages directory -if( !defined( 'YOURLS_PAGEDIR' ) ) - define('YOURLS_PAGEDIR', YOURLS_ABSPATH.'/pages' ); - -// table to store URLs -if( !defined( 'YOURLS_DB_TABLE_URL' ) ) - define( 'YOURLS_DB_TABLE_URL', YOURLS_DB_PREFIX.'url' ); - -// table to store options -if( !defined( 'YOURLS_DB_TABLE_OPTIONS' ) ) - define( 'YOURLS_DB_TABLE_OPTIONS', YOURLS_DB_PREFIX.'options' ); - -// table to store hits, for stats -if( !defined( 'YOURLS_DB_TABLE_LOG' ) ) - define( 'YOURLS_DB_TABLE_LOG', YOURLS_DB_PREFIX.'log' ); - -// minimum delay in sec before a same IP can add another URL. Note: logged in users are not throttled down. -if( !defined( 'YOURLS_FLOOD_DELAY_SECONDS' ) ) - define( 'YOURLS_FLOOD_DELAY_SECONDS', 15 ); - -// comma separated list of IPs that can bypass flood check. -if( !defined( 'YOURLS_FLOOD_IP_WHITELIST' ) ) - define( 'YOURLS_FLOOD_IP_WHITELIST', '' ); - -// life span of an auth cookie in seconds (60*60*24*7 = 7 days) -if( !defined( 'YOURLS_COOKIE_LIFE' ) ) - define( 'YOURLS_COOKIE_LIFE', 60*60*24*7 ); - -// life span of a nonce in seconds -if( !defined( 'YOURLS_NONCE_LIFE' ) ) - define( 'YOURLS_NONCE_LIFE', 43200 ); // 3600 * 12 - -// if set to true, disable stat logging (no use for it, too busy servers, ...) -if( !defined( 'YOURLS_NOSTATS' ) ) - define( 'YOURLS_NOSTATS', false ); - -// if set to true, force https:// in the admin area -if( !defined( 'YOURLS_ADMIN_SSL' ) ) - define( 'YOURLS_ADMIN_SSL', false ); - -// if set to true, verbose debug infos. Will break things. Don't enable. -if( !defined( 'YOURLS_DEBUG' ) ) - define( 'YOURLS_DEBUG', false ); - -// Error reporting -if( defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG == true ) { - error_reporting( -1 ); -} else { - error_reporting( E_ERROR | E_PARSE ); -} - -// Include all functions -require_once( YOURLS_INC.'/version.php' ); -require_once( YOURLS_INC.'/functions.php'); -require_once( YOURLS_INC.'/functions-plugins.php' ); -require_once( YOURLS_INC.'/functions-formatting.php' ); -require_once( YOURLS_INC.'/functions-api.php' ); -require_once( YOURLS_INC.'/functions-kses.php' ); -require_once( YOURLS_INC.'/functions-l10n.php' ); -require_once( YOURLS_INC.'/functions-compat.php' ); -require_once( YOURLS_INC.'/functions-html.php' ); -// Allow drop-in replacement for the DB engine -if( file_exists( YOURLS_USERDIR.'/db.php' ) ) { - require_once( YOURLS_USERDIR.'/db.php' ); -} else { - require_once( YOURLS_INC.'/class-mysql.php' ); -} -// Load auth functions if needed -if( yourls_is_private() ) - require_once( YOURLS_INC.'/functions-auth.php' ); - -// Allow early inclusion of a cache layer -if( file_exists( YOURLS_USERDIR.'/cache.php' ) ) - require_once( YOURLS_USERDIR.'/cache.php' ); - -// Load locale -yourls_load_default_textdomain(); - -// Check if we are in maintenance mode - if yes, it will die here. -yourls_check_maintenance_mode(); - -// If request for an admin page is http:// and SSL is required, redirect -if( yourls_is_admin() && yourls_needs_ssl() && !yourls_is_ssl() ) { - if ( 0 === strpos( $_SERVER['REQUEST_URI'], 'http' ) ) { - yourls_redirect( preg_replace( '|^http://|', 'https://', $_SERVER['REQUEST_URI'] ) ); - exit(); - } else { - yourls_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); - exit(); - } -} - -// Fix REQUEST_URI for IIS -yourls_fix_request_uri(); - -// Create the YOURLS object $ydb that will contain everything we globally need -global $ydb; -yourls_db_connect(); - -// Read options right from start -yourls_get_all_options(); - -// Register shutdown function -register_shutdown_function( 'yourls_shutdown' ); - -// Core now loaded -yourls_do_action( 'init' ); // plugins can't see this, not loaded yet - -// Check if need to redirect to install procedure -if( !yourls_is_installed() && !yourls_is_installing() ) { - yourls_redirect( yourls_admin_url( 'install.php' ), 302 ); -} - -// Check if upgrade is needed (bypassed if upgrading or installing) -if ( !yourls_is_upgrading() && !yourls_is_installing() ) { - if ( yourls_upgrade_is_needed() ) { - yourls_redirect( YOURLS_SITE .'/admin/upgrade.php', 302 ); - } -} - -// Init all plugins -yourls_load_plugins(); -yourls_do_action( 'plugins_loaded' ); - -if( yourls_is_admin() ) - yourls_do_action( 'admin_init' ); - +<?php +// This file initialize everything needed for YOURLS + +// Include settings +if( file_exists( dirname( dirname( __FILE__ ) ) . '/user/config.php' ) ) { + // config.php in /user/ + require_once( dirname( dirname( __FILE__ ) ) . '/user/config.php' ); +} elseif ( file_exists( dirname( __FILE__ ) . '/config.php' ) ) { + // config.php in /includes/ + require_once( dirname( __FILE__ ) . '/config.php' ); +} else { + // config.php not found :( + die( '<p class="error">Cannot find <tt>config.php</tt>.</p><p>Please read the <tt>readme.html</tt> to learn how to install YOURLS</p>' ); +} + +// Check if config.php was properly updated for 1.4 +if( !defined( 'YOURLS_DB_PREFIX' ) ) + die( '<p class="error">Your <tt>config.php</tt> does not contain all the required constant definitions.</p><p>Please check <tt>config-sample.php</tt> and update your config accordingly, there are new stuffs!</p>' ); + + +// Define core constants that have not been user defined in config.php + +// physical path of YOURLS root +if( !defined( 'YOURLS_ABSPATH' ) ) + define( 'YOURLS_ABSPATH', str_replace( '\\', '/', dirname( dirname( __FILE__ ) ) ) ); + +// physical path of includes directory +if( !defined( 'YOURLS_INC' ) ) + define( 'YOURLS_INC', YOURLS_ABSPATH.'/includes' ); + +// physical path of user directory +if( !defined( 'YOURLS_USERDIR' ) ) + define( 'YOURLS_USERDIR', YOURLS_ABSPATH.'/user' ); + +// URL of user directory +if( !defined( 'YOURLS_USERURL' ) ) + define( 'YOURLS_USERURL', YOURLS_SITE.'/user' ); + +// physical path of translations directory +if( !defined( 'YOURLS_LANG_DIR' ) ) + define( 'YOURLS_LANG_DIR', YOURLS_USERDIR.'/languages' ); + +// physical path of plugins directory +if( !defined( 'YOURLS_PLUGINDIR' ) ) + define( 'YOURLS_PLUGINDIR', YOURLS_USERDIR.'/plugins' ); + +// URL of plugins directory +if( !defined( 'YOURLS_PLUGINURL' ) ) + define( 'YOURLS_PLUGINURL', YOURLS_USERURL.'/plugins' ); + +// physical path of pages directory +if( !defined( 'YOURLS_PAGEDIR' ) ) + define('YOURLS_PAGEDIR', YOURLS_ABSPATH.'/pages' ); + +// table to store URLs +if( !defined( 'YOURLS_DB_TABLE_URL' ) ) + define( 'YOURLS_DB_TABLE_URL', YOURLS_DB_PREFIX.'url' ); + +// table to store options +if( !defined( 'YOURLS_DB_TABLE_OPTIONS' ) ) + define( 'YOURLS_DB_TABLE_OPTIONS', YOURLS_DB_PREFIX.'options' ); + +// table to store hits, for stats +if( !defined( 'YOURLS_DB_TABLE_LOG' ) ) + define( 'YOURLS_DB_TABLE_LOG', YOURLS_DB_PREFIX.'log' ); + +// minimum delay in sec before a same IP can add another URL. Note: logged in users are not throttled down. +if( !defined( 'YOURLS_FLOOD_DELAY_SECONDS' ) ) + define( 'YOURLS_FLOOD_DELAY_SECONDS', 15 ); + +// comma separated list of IPs that can bypass flood check. +if( !defined( 'YOURLS_FLOOD_IP_WHITELIST' ) ) + define( 'YOURLS_FLOOD_IP_WHITELIST', '' ); + +// life span of an auth cookie in seconds (60*60*24*7 = 7 days) +if( !defined( 'YOURLS_COOKIE_LIFE' ) ) + define( 'YOURLS_COOKIE_LIFE', 60*60*24*7 ); + +// life span of a nonce in seconds +if( !defined( 'YOURLS_NONCE_LIFE' ) ) + define( 'YOURLS_NONCE_LIFE', 43200 ); // 3600 * 12 + +// if set to true, disable stat logging (no use for it, too busy servers, ...) +if( !defined( 'YOURLS_NOSTATS' ) ) + define( 'YOURLS_NOSTATS', false ); + +// if set to true, force https:// in the admin area +if( !defined( 'YOURLS_ADMIN_SSL' ) ) + define( 'YOURLS_ADMIN_SSL', false ); + +// if set to true, verbose debug infos. Will break things. Don't enable. +if( !defined( 'YOURLS_DEBUG' ) ) + define( 'YOURLS_DEBUG', false ); + +// Error reporting +if( defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG == true ) { + error_reporting( -1 ); +} else { + error_reporting( E_ERROR | E_PARSE ); +} + +// Include all functions +require_once( YOURLS_INC.'/version.php' ); +require_once( YOURLS_INC.'/functions.php'); +require_once( YOURLS_INC.'/functions-plugins.php' ); +require_once( YOURLS_INC.'/functions-formatting.php' ); +require_once( YOURLS_INC.'/functions-api.php' ); +require_once( YOURLS_INC.'/functions-kses.php' ); +require_once( YOURLS_INC.'/functions-l10n.php' ); +require_once( YOURLS_INC.'/functions-compat.php' ); +require_once( YOURLS_INC.'/functions-html.php' ); +// Allow drop-in replacement for the DB engine +if( file_exists( YOURLS_USERDIR.'/db.php' ) ) { + require_once( YOURLS_USERDIR.'/db.php' ); +} else { + require_once( YOURLS_INC.'/class-mysql.php' ); +} +// Load auth functions if needed +if( yourls_is_private() ) + require_once( YOURLS_INC.'/functions-auth.php' ); + +// Allow early inclusion of a cache layer +if( file_exists( YOURLS_USERDIR.'/cache.php' ) ) + require_once( YOURLS_USERDIR.'/cache.php' ); + +// Load locale +yourls_load_default_textdomain(); + +// Check if we are in maintenance mode - if yes, it will die here. +yourls_check_maintenance_mode(); + +// If request for an admin page is http:// and SSL is required, redirect +if( yourls_is_admin() && yourls_needs_ssl() && !yourls_is_ssl() ) { + if ( 0 === strpos( $_SERVER['REQUEST_URI'], 'http' ) ) { + yourls_redirect( preg_replace( '|^http://|', 'https://', $_SERVER['REQUEST_URI'] ) ); + exit(); + } else { + yourls_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); + exit(); + } +} + +// Fix REQUEST_URI for IIS +yourls_fix_request_uri(); + +// Create the YOURLS object $ydb that will contain everything we globally need +global $ydb; +yourls_db_connect(); + +// Read options right from start +yourls_get_all_options(); + +// Register shutdown function +register_shutdown_function( 'yourls_shutdown' ); + +// Core now loaded +yourls_do_action( 'init' ); // plugins can't see this, not loaded yet + +// Check if need to redirect to install procedure +if( !yourls_is_installed() && !yourls_is_installing() ) { + yourls_redirect( yourls_admin_url( 'install.php' ), 302 ); +} + +// Check if upgrade is needed (bypassed if upgrading or installing) +if ( !yourls_is_upgrading() && !yourls_is_installing() ) { + if ( yourls_upgrade_is_needed() ) { + yourls_redirect( YOURLS_SITE .'/admin/upgrade.php', 302 ); + } +} + +// Init all plugins +yourls_load_plugins(); +yourls_do_action( 'plugins_loaded' ); + +if( yourls_is_admin() ) + yourls_do_action( 'admin_init' ); + diff --git a/includes/pomo/entry.php b/includes/pomo/entry.php index 75a9e84..097e92c 100644 --- a/includes/pomo/entry.php +++ b/includes/pomo/entry.php @@ -1,78 +1,78 @@ -<?php -/** - * Contains Translation_Entry class - * - * @version $Id: entry.php 718 2012-10-31 00:32:02Z nbachiyski $ - * @package pomo - * @subpackage entry - */ - -if ( !class_exists( 'Translation_Entry' ) ): -/** - * Translation_Entry class encapsulates a translatable string - */ -class Translation_Entry { - - /** - * Whether the entry contains a string and its plural form, default is false - * - * @var boolean - */ - var $is_plural = false; - - var $context = null; - var $singular = null; - var $plural = null; - var $translations = array(); - var $translator_comments = ''; - var $extracted_comments = ''; - var $references = array(); - var $flags = array(); - - /** - * @param array $args associative array, support following keys: - * - singular (string) -- the string to translate, if omitted and empty entry will be created - * - plural (string) -- the plural form of the string, setting this will set {@link $is_plural} to true - * - translations (array) -- translations of the string and possibly -- its plural forms - * - context (string) -- a string differentiating two equal strings used in different contexts - * - translator_comments (string) -- comments left by translators - * - extracted_comments (string) -- comments left by developers - * - references (array) -- places in the code this strings is used, in relative_to_root_path/file.php:linenum form - * - flags (array) -- flags like php-format - */ - function Translation_Entry($args=array()) { - // if no singular -- empty object - if (!isset($args['singular'])) { - return; - } - // get member variable values from args hash - foreach ($args as $varname => $value) { - $this->$varname = $value; - } - if (isset($args['plural'])) $this->is_plural = true; - if (!is_array($this->translations)) $this->translations = array(); - if (!is_array($this->references)) $this->references = array(); - if (!is_array($this->flags)) $this->flags = array(); - } - - /** - * Generates a unique key for this entry - * - * @return string|bool the key or false if the entry is empty - */ - function key() { - if (is_null($this->singular)) return false; - // prepend context and EOT, like in MO files - return is_null($this->context)? $this->singular : $this->context.chr(4).$this->singular; - } - - function merge_with(&$other) { - $this->flags = array_unique( array_merge( $this->flags, $other->flags ) ); - $this->references = array_unique( array_merge( $this->references, $other->references ) ); - if ( $this->extracted_comments != $other->extracted_comments ) { - $this->extracted_comments .= $other->extracted_comments; - } - - } -} +<?php +/** + * Contains Translation_Entry class + * + * @version $Id: entry.php 718 2012-10-31 00:32:02Z nbachiyski $ + * @package pomo + * @subpackage entry + */ + +if ( !class_exists( 'Translation_Entry' ) ): +/** + * Translation_Entry class encapsulates a translatable string + */ +class Translation_Entry { + + /** + * Whether the entry contains a string and its plural form, default is false + * + * @var boolean + */ + var $is_plural = false; + + var $context = null; + var $singular = null; + var $plural = null; + var $translations = array(); + var $translator_comments = ''; + var $extracted_comments = ''; + var $references = array(); + var $flags = array(); + + /** + * @param array $args associative array, support following keys: + * - singular (string) -- the string to translate, if omitted and empty entry will be created + * - plural (string) -- the plural form of the string, setting this will set {@link $is_plural} to true + * - translations (array) -- translations of the string and possibly -- its plural forms + * - context (string) -- a string differentiating two equal strings used in different contexts + * - translator_comments (string) -- comments left by translators + * - extracted_comments (string) -- comments left by developers + * - references (array) -- places in the code this strings is used, in relative_to_root_path/file.php:linenum form + * - flags (array) -- flags like php-format + */ + function Translation_Entry($args=array()) { + // if no singular -- empty object + if (!isset($args['singular'])) { + return; + } + // get member variable values from args hash + foreach ($args as $varname => $value) { + $this->$varname = $value; + } + if (isset($args['plural'])) $this->is_plural = true; + if (!is_array($this->translations)) $this->translations = array(); + if (!is_array($this->references)) $this->references = array(); + if (!is_array($this->flags)) $this->flags = array(); + } + + /** + * Generates a unique key for this entry + * + * @return string|bool the key or false if the entry is empty + */ + function key() { + if (is_null($this->singular)) return false; + // prepend context and EOT, like in MO files + return is_null($this->context)? $this->singular : $this->context.chr(4).$this->singular; + } + + function merge_with(&$other) { + $this->flags = array_unique( array_merge( $this->flags, $other->flags ) ); + $this->references = array_unique( array_merge( $this->references, $other->references ) ); + if ( $this->extracted_comments != $other->extracted_comments ) { + $this->extracted_comments .= $other->extracted_comments; + } + + } +} endif; \ No newline at end of file diff --git a/includes/pomo/mo.php b/includes/pomo/mo.php index f173c00..68c0792 100644 --- a/includes/pomo/mo.php +++ b/includes/pomo/mo.php @@ -1,257 +1,257 @@ -<?php -/** - * Class for working with MO files - * - * @version $Id: mo.php 718 2012-10-31 00:32:02Z nbachiyski $ - * @package pomo - * @subpackage mo - */ - -require_once dirname(__FILE__) . '/translations.php'; -require_once dirname(__FILE__) . '/streams.php'; - -if ( !class_exists( 'MO' ) ): -class MO extends Gettext_Translations { - - var $_nplurals = 2; - - /** - * Fills up with the entries from MO file $filename - * - * @param string $filename MO file to load - */ - function import_from_file($filename) { - $reader = new POMO_FileReader($filename); - if (!$reader->is_resource()) - return false; - return $this->import_from_reader($reader); - } - - function export_to_file($filename) { - $fh = fopen($filename, 'wb'); - if ( !$fh ) return false; - $res = $this->export_to_file_handle( $fh ); - fclose($fh); - return $res; - } - - function export() { - $tmp_fh = fopen("php://temp", 'r+'); - if ( !$tmp_fh ) return false; - $this->export_to_file_handle( $tmp_fh ); - rewind( $tmp_fh ); - return stream_get_contents( $tmp_fh ); - } - - function is_entry_good_for_export( $entry ) { - if ( empty( $entry->translations ) ) { - return false; - } - - if ( !array_filter( $entry->translations ) ) { - return false; - } - - return true; - } - - function export_to_file_handle($fh) { - $entries = array_filter( $this->entries, array( $this, 'is_entry_good_for_export' ) ); - ksort($entries); - $magic = 0x950412de; - $revision = 0; - $total = count($entries) + 1; // all the headers are one entry - $originals_lenghts_addr = 28; - $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total; - $size_of_hash = 0; - $hash_addr = $translations_lenghts_addr + 8 * $total; - $current_addr = $hash_addr; - fwrite($fh, pack('V*', $magic, $revision, $total, $originals_lenghts_addr, - $translations_lenghts_addr, $size_of_hash, $hash_addr)); - fseek($fh, $originals_lenghts_addr); - - // headers' msgid is an empty string - fwrite($fh, pack('VV', 0, $current_addr)); - $current_addr++; - $originals_table = chr(0); - - foreach($entries as $entry) { - $originals_table .= $this->export_original($entry) . chr(0); - $length = strlen($this->export_original($entry)); - fwrite($fh, pack('VV', $length, $current_addr)); - $current_addr += $length + 1; // account for the NULL byte after - } - - $exported_headers = $this->export_headers(); - fwrite($fh, pack('VV', strlen($exported_headers), $current_addr)); - $current_addr += strlen($exported_headers) + 1; - $translations_table = $exported_headers . chr(0); - - foreach($entries as $entry) { - $translations_table .= $this->export_translations($entry) . chr(0); - $length = strlen($this->export_translations($entry)); - fwrite($fh, pack('VV', $length, $current_addr)); - $current_addr += $length + 1; - } - - fwrite($fh, $originals_table); - fwrite($fh, $translations_table); - return true; - } - - function export_original($entry) { - //TODO: warnings for control characters - $exported = $entry->singular; - if ($entry->is_plural) $exported .= chr(0).$entry->plural; - if (!is_null($entry->context)) $exported = $entry->context . chr(4) . $exported; - return $exported; - } - - function export_translations($entry) { - //TODO: warnings for control characters - return implode(chr(0), $entry->translations); - } - - function export_headers() { - $exported = ''; - foreach($this->headers as $header => $value) { - $exported.= "$header: $value\n"; - } - return $exported; - } - - function get_byteorder($magic) { - // The magic is 0x950412de - - // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 - $magic_little = (int) - 1794895138; - $magic_little_64 = (int) 2500072158; - // 0xde120495 - $magic_big = ((int) - 569244523) & 0xFFFFFFFF; - if ($magic_little == $magic || $magic_little_64 == $magic) { - return 'little'; - } else if ($magic_big == $magic) { - return 'big'; - } else { - return false; - } - } - - function import_from_reader($reader) { - $endian_string = MO::get_byteorder($reader->readint32()); - if (false === $endian_string) { - return false; - } - $reader->setEndian($endian_string); - - $endian = ('big' == $endian_string)? 'N' : 'V'; - - $header = $reader->read(24); - if ($reader->strlen($header) != 24) - return false; - - // parse header - $header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header); - if (!is_array($header)) - return false; - - extract( $header ); - - // support revision 0 of MO format specs, only - if ($revision != 0) - return false; - - // seek to data blocks - $reader->seekto($originals_lenghts_addr); - - // read originals' indices - $originals_lengths_length = $translations_lenghts_addr - $originals_lenghts_addr; - if ( $originals_lengths_length != $total * 8 ) - return false; - - $originals = $reader->read($originals_lengths_length); - if ( $reader->strlen( $originals ) != $originals_lengths_length ) - return false; - - // read translations' indices - $translations_lenghts_length = $hash_addr - $translations_lenghts_addr; - if ( $translations_lenghts_length != $total * 8 ) - return false; - - $translations = $reader->read($translations_lenghts_length); - if ( $reader->strlen( $translations ) != $translations_lenghts_length ) - return false; - - // transform raw data into set of indices - $originals = $reader->str_split( $originals, 8 ); - $translations = $reader->str_split( $translations, 8 ); - - // skip hash table - $strings_addr = $hash_addr + $hash_length * 4; - - $reader->seekto($strings_addr); - - $strings = $reader->read_all(); - $reader->close(); - - for ( $i = 0; $i < $total; $i++ ) { - $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] ); - $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] ); - if ( !$o || !$t ) return false; - - // adjust offset due to reading strings to separate space before - $o['pos'] -= $strings_addr; - $t['pos'] -= $strings_addr; - - $original = $reader->substr( $strings, $o['pos'], $o['length'] ); - $translation = $reader->substr( $strings, $t['pos'], $t['length'] ); - - if ('' === $original) { - $this->set_headers($this->make_headers($translation)); - } else { - $entry = &$this->make_entry($original, $translation); - $this->entries[$entry->key()] = &$entry; - } - } - return true; - } - - /** - * Build a Translation_Entry from original string and translation strings, - * found in a MO file - * - * @static - * @param string $original original string to translate from MO file. Might contain - * 0x04 as context separator or 0x00 as singular/plural separator - * @param string $translation translation string from MO file. Might contain - * 0x00 as a plural translations separator - */ - function &make_entry($original, $translation) { - $entry = new Translation_Entry(); - // look for context - $parts = explode(chr(4), $original); - if (isset($parts[1])) { - $original = $parts[1]; - $entry->context = $parts[0]; - } - // look for plural original - $parts = explode(chr(0), $original); - $entry->singular = $parts[0]; - if (isset($parts[1])) { - $entry->is_plural = true; - $entry->plural = $parts[1]; - } - // plural translations are also separated by \0 - $entry->translations = explode(chr(0), $translation); - return $entry; - } - - function select_plural_form($count) { - return $this->gettext_select_plural_form($count); - } - - function get_plural_forms_count() { - return $this->_nplurals; - } -} +<?php +/** + * Class for working with MO files + * + * @version $Id: mo.php 718 2012-10-31 00:32:02Z nbachiyski $ + * @package pomo + * @subpackage mo + */ + +require_once dirname(__FILE__) . '/translations.php'; +require_once dirname(__FILE__) . '/streams.php'; + +if ( !class_exists( 'MO' ) ): +class MO extends Gettext_Translations { + + var $_nplurals = 2; + + /** + * Fills up with the entries from MO file $filename + * + * @param string $filename MO file to load + */ + function import_from_file($filename) { + $reader = new POMO_FileReader($filename); + if (!$reader->is_resource()) + return false; + return $this->import_from_reader($reader); + } + + function export_to_file($filename) { + $fh = fopen($filename, 'wb'); + if ( !$fh ) return false; + $res = $this->export_to_file_handle( $fh ); + fclose($fh); + return $res; + } + + function export() { + $tmp_fh = fopen("php://temp", 'r+'); + if ( !$tmp_fh ) return false; + $this->export_to_file_handle( $tmp_fh ); + rewind( $tmp_fh ); + return stream_get_contents( $tmp_fh ); + } + + function is_entry_good_for_export( $entry ) { + if ( empty( $entry->translations ) ) { + return false; + } + + if ( !array_filter( $entry->translations ) ) { + return false; + } + + return true; + } + + function export_to_file_handle($fh) { + $entries = array_filter( $this->entries, array( $this, 'is_entry_good_for_export' ) ); + ksort($entries); + $magic = 0x950412de; + $revision = 0; + $total = count($entries) + 1; // all the headers are one entry + $originals_lenghts_addr = 28; + $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total; + $size_of_hash = 0; + $hash_addr = $translations_lenghts_addr + 8 * $total; + $current_addr = $hash_addr; + fwrite($fh, pack('V*', $magic, $revision, $total, $originals_lenghts_addr, + $translations_lenghts_addr, $size_of_hash, $hash_addr)); + fseek($fh, $originals_lenghts_addr); + + // headers' msgid is an empty string + fwrite($fh, pack('VV', 0, $current_addr)); + $current_addr++; + $originals_table = chr(0); + + foreach($entries as $entry) { + $originals_table .= $this->export_original($entry) . chr(0); + $length = strlen($this->export_original($entry)); + fwrite($fh, pack('VV', $length, $current_addr)); + $current_addr += $length + 1; // account for the NULL byte after + } + + $exported_headers = $this->export_headers(); + fwrite($fh, pack('VV', strlen($exported_headers), $current_addr)); + $current_addr += strlen($exported_headers) + 1; + $translations_table = $exported_headers . chr(0); + + foreach($entries as $entry) { + $translations_table .= $this->export_translations($entry) . chr(0); + $length = strlen($this->export_translations($entry)); + fwrite($fh, pack('VV', $length, $current_addr)); + $current_addr += $length + 1; + } + + fwrite($fh, $originals_table); + fwrite($fh, $translations_table); + return true; + } + + function export_original($entry) { + //TODO: warnings for control characters + $exported = $entry->singular; + if ($entry->is_plural) $exported .= chr(0).$entry->plural; + if (!is_null($entry->context)) $exported = $entry->context . chr(4) . $exported; + return $exported; + } + + function export_translations($entry) { + //TODO: warnings for control characters + return implode(chr(0), $entry->translations); + } + + function export_headers() { + $exported = ''; + foreach($this->headers as $header => $value) { + $exported.= "$header: $value\n"; + } + return $exported; + } + + function get_byteorder($magic) { + // The magic is 0x950412de + + // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 + $magic_little = (int) - 1794895138; + $magic_little_64 = (int) 2500072158; + // 0xde120495 + $magic_big = ((int) - 569244523) & 0xFFFFFFFF; + if ($magic_little == $magic || $magic_little_64 == $magic) { + return 'little'; + } else if ($magic_big == $magic) { + return 'big'; + } else { + return false; + } + } + + function import_from_reader($reader) { + $endian_string = MO::get_byteorder($reader->readint32()); + if (false === $endian_string) { + return false; + } + $reader->setEndian($endian_string); + + $endian = ('big' == $endian_string)? 'N' : 'V'; + + $header = $reader->read(24); + if ($reader->strlen($header) != 24) + return false; + + // parse header + $header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header); + if (!is_array($header)) + return false; + + extract( $header ); + + // support revision 0 of MO format specs, only + if ($revision != 0) + return false; + + // seek to data blocks + $reader->seekto($originals_lenghts_addr); + + // read originals' indices + $originals_lengths_length = $translations_lenghts_addr - $originals_lenghts_addr; + if ( $originals_lengths_length != $total * 8 ) + return false; + + $originals = $reader->read($originals_lengths_length); + if ( $reader->strlen( $originals ) != $originals_lengths_length ) + return false; + + // read translations' indices + $translations_lenghts_length = $hash_addr - $translations_lenghts_addr; + if ( $translations_lenghts_length != $total * 8 ) + return false; + + $translations = $reader->read($translations_lenghts_length); + if ( $reader->strlen( $translations ) != $translations_lenghts_length ) + return false; + + // transform raw data into set of indices + $originals = $reader->str_split( $originals, 8 ); + $translations = $reader->str_split( $translations, 8 ); + + // skip hash table + $strings_addr = $hash_addr + $hash_length * 4; + + $reader->seekto($strings_addr); + + $strings = $reader->read_all(); + $reader->close(); + + for ( $i = 0; $i < $total; $i++ ) { + $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] ); + $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] ); + if ( !$o || !$t ) return false; + + // adjust offset due to reading strings to separate space before + $o['pos'] -= $strings_addr; + $t['pos'] -= $strings_addr; + + $original = $reader->substr( $strings, $o['pos'], $o['length'] ); + $translation = $reader->substr( $strings, $t['pos'], $t['length'] ); + + if ('' === $original) { + $this->set_headers($this->make_headers($translation)); + } else { + $entry = &$this->make_entry($original, $translation); + $this->entries[$entry->key()] = &$entry; + } + } + return true; + } + + /** + * Build a Translation_Entry from original string and translation strings, + * found in a MO file + * + * @static + * @param string $original original string to translate from MO file. Might contain + * 0x04 as context separator or 0x00 as singular/plural separator + * @param string $translation translation string from MO file. Might contain + * 0x00 as a plural translations separator + */ + function &make_entry($original, $translation) { + $entry = new Translation_Entry(); + // look for context + $parts = explode(chr(4), $original); + if (isset($parts[1])) { + $original = $parts[1]; + $entry->context = $parts[0]; + } + // look for plural original + $parts = explode(chr(0), $original); + $entry->singular = $parts[0]; + if (isset($parts[1])) { + $entry->is_plural = true; + $entry->plural = $parts[1]; + } + // plural translations are also separated by \0 + $entry->translations = explode(chr(0), $translation); + return $entry; + } + + function select_plural_form($count) { + return $this->gettext_select_plural_form($count); + } + + function get_plural_forms_count() { + return $this->_nplurals; + } +} endif; \ No newline at end of file diff --git a/includes/pomo/po.php b/includes/pomo/po.php index c42b811..f76be01 100644 --- a/includes/pomo/po.php +++ b/includes/pomo/po.php @@ -1,384 +1,384 @@ -<?php -/** - * Class for working with PO files - * - * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $ - * @package pomo - * @subpackage po - */ - -require_once dirname(__FILE__) . '/translations.php'; - -define('PO_MAX_LINE_LEN', 79); - -ini_set('auto_detect_line_endings', 1); - -/** - * Routines for working with PO files - */ -if ( !class_exists( 'PO' ) ): -class PO extends Gettext_Translations { - - var $comments_before_headers = ''; - - /** - * Exports headers to a PO entry - * - * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end - */ - function export_headers() { - $header_string = ''; - foreach($this->headers as $header => $value) { - $header_string.= "$header: $value\n"; - } - $poified = PO::poify($header_string); - if ($this->comments_before_headers) - $before_headers = $this->prepend_each_line(rtrim($this->comments_before_headers)."\n", '# '); - else - $before_headers = ''; - return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified"); - } - - /** - * Exports all entries to PO format - * - * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end - */ - function export_entries() { - //TODO sorting - return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries)); - } - - /** - * Exports the whole PO file as a string - * - * @param bool $include_headers whether to include the headers in the export - * @return string ready for inclusion in PO file string for headers and all the enrtries - */ - function export($include_headers = true) { - $res = ''; - if ($include_headers) { - $res .= $this->export_headers(); - $res .= "\n\n"; - } - $res .= $this->export_entries(); - return $res; - } - - /** - * Same as {@link export}, but writes the result to a file - * - * @param string $filename where to write the PO string - * @param bool $include_headers whether to include tje headers in the export - * @return bool true on success, false on error - */ - function export_to_file($filename, $include_headers = true) { - $fh = fopen($filename, 'w'); - if (false === $fh) return false; - $export = $this->export($include_headers); - $res = fwrite($fh, $export); - if (false === $res) return false; - return fclose($fh); - } - - /** - * Text to include as a comment before the start of the PO contents - * - * Doesn't need to include # in the beginning of lines, these are added automatically - */ - function set_comment_before_headers( $text ) { - $this->comments_before_headers = $text; - } - - /** - * Formats a string in PO-style - * - * @static - * @param string $string the string to format - * @return string the poified string - */ - function poify($string) { - $quote = '"'; - $slash = '\\'; - $newline = "\n"; - - $replaces = array( - "$slash" => "$slash$slash", - "$quote" => "$slash$quote", - "\t" => '\t', - ); - - $string = str_replace(array_keys($replaces), array_values($replaces), $string); - - $po = $quote.implode("${slash}n$quote$newline$quote", explode($newline, $string)).$quote; - // add empty string on first line for readbility - if (false !== strpos($string, $newline) && - (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) { - $po = "$quote$quote$newline$po"; - } - // remove empty strings - $po = str_replace("$newline$quote$quote", '', $po); - return $po; - } - - /** - * Gives back the original string from a PO-formatted string - * - * @static - * @param string $string PO-formatted string - * @return string enascaped string - */ - function unpoify($string) { - $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\'); - $lines = array_map('trim', explode("\n", $string)); - $lines = array_map(array('PO', 'trim_quotes'), $lines); - $unpoified = ''; - $previous_is_backslash = false; - foreach($lines as $line) { - preg_match_all('/./u', $line, $chars); - $chars = $chars[0]; - foreach($chars as $char) { - if (!$previous_is_backslash) { - if ('\\' == $char) - $previous_is_backslash = true; - else - $unpoified .= $char; - } else { - $previous_is_backslash = false; - $unpoified .= isset($escapes[$char])? $escapes[$char] : $char; - } - } - } - return $unpoified; - } - - /** - * Inserts $with in the beginning of every new line of $string and - * returns the modified string - * - * @static - * @param string $string prepend lines in this string - * @param string $with prepend lines with this string - */ - function prepend_each_line($string, $with) { - $php_with = var_export($with, true); - $lines = explode("\n", $string); - // do not prepend the string on the last empty line, artefact by explode - if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]); - $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines)); - // give back the empty line, we ignored above - if ("\n" == substr($string, -1)) $res .= "\n"; - return $res; - } - - /** - * Prepare a text as a comment -- wraps the lines and prepends # - * and a special character to each line - * - * @access private - * @param string $text the comment text - * @param string $char character to denote a special PO comment, - * like :, default is a space - */ - function comment_block($text, $char=' ') { - $text = wordwrap($text, PO_MAX_LINE_LEN - 3); - return PO::prepend_each_line($text, "#$char "); - } - - /** - * Builds a string from the entry for inclusion in PO file - * - * @static - * @param object &$entry the entry to convert to po string - * @return string|bool PO-style formatted string for the entry or - * false if the entry is empty - */ - function export_entry(&$entry) { - if (is_null($entry->singular)) return false; - $po = array(); - if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments); - if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.'); - if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); - if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); - if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context); - $po[] = 'msgid '.PO::poify($entry->singular); - if (!$entry->is_plural) { - $translation = empty($entry->translations)? '' : $entry->translations[0]; - $po[] = 'msgstr '.PO::poify($translation); - } else { - $po[] = 'msgid_plural '.PO::poify($entry->plural); - $translations = empty($entry->translations)? array('', '') : $entry->translations; - foreach($translations as $i => $translation) { - $po[] = "msgstr[$i] ".PO::poify($translation); - } - } - return implode("\n", $po); - } - - function import_from_file($filename) { - $f = fopen($filename, 'r'); - if (!$f) return false; - $lineno = 0; - while (true) { - $res = $this->read_entry($f, $lineno); - if (!$res) break; - if ($res['entry']->singular == '') { - $this->set_headers($this->make_headers($res['entry']->translations[0])); - } else { - $this->add_entry($res['entry']); - } - } - PO::read_line($f, 'clear'); - if ( false === $res ) { - return false; - } - if ( ! $this->headers && ! $this->entries ) { - return false; - } - return true; - } - - function read_entry($f, $lineno = 0) { - $entry = new Translation_Entry(); - // where were we in the last step - // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural - $context = ''; - $msgstr_index = 0; - $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";'); - while (true) { - $lineno++; - $line = PO::read_line($f); - if (!$line) { - if (feof($f)) { - if ($is_final($context)) - break; - elseif (!$context) // we haven't read a line and eof came - return null; - else - return false; - } else { - return false; - } - } - if ($line == "\n") continue; - $line = trim($line); - if (preg_match('/^#/', $line, $m)) { - // the comment is the start of a new entry - if ($is_final($context)) { - PO::read_line($f, 'put-back'); - $lineno--; - break; - } - // comments have to be at the beginning - if ($context && $context != 'comment') { - return false; - } - // add comment - $this->add_comment_to_entry($entry, $line);; - } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { - if ($is_final($context)) { - PO::read_line($f, 'put-back'); - $lineno--; - break; - } - if ($context && $context != 'comment') { - return false; - } - $context = 'msgctxt'; - $entry->context .= PO::unpoify($m[1]); - } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { - if ($is_final($context)) { - PO::read_line($f, 'put-back'); - $lineno--; - break; - } - if ($context && $context != 'msgctxt' && $context != 'comment') { - return false; - } - $context = 'msgid'; - $entry->singular .= PO::unpoify($m[1]); - } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) { - if ($context != 'msgid') { - return false; - } - $context = 'msgid_plural'; - $entry->is_plural = true; - $entry->plural .= PO::unpoify($m[1]); - } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) { - if ($context != 'msgid') { - return false; - } - $context = 'msgstr'; - $entry->translations = array(PO::unpoify($m[1])); - } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) { - if ($context != 'msgid_plural' && $context != 'msgstr_plural') { - return false; - } - $context = 'msgstr_plural'; - $msgstr_index = $m[1]; - $entry->translations[$m[1]] = PO::unpoify($m[2]); - } elseif (preg_match('/^".*"$/', $line)) { - $unpoified = PO::unpoify($line); - switch ($context) { - case 'msgid': - $entry->singular .= $unpoified; break; - case 'msgctxt': - $entry->context .= $unpoified; break; - case 'msgid_plural': - $entry->plural .= $unpoified; break; - case 'msgstr': - $entry->translations[0] .= $unpoified; break; - case 'msgstr_plural': - $entry->translations[$msgstr_index] .= $unpoified; break; - default: - return false; - } - } else { - return false; - } - } - if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) { - $entry->translations = array(); - } - return array('entry' => $entry, 'lineno' => $lineno); - } - - function read_line($f, $action = 'read') { - static $last_line = ''; - static $use_last_line = false; - if ('clear' == $action) { - $last_line = ''; - return true; - } - if ('put-back' == $action) { - $use_last_line = true; - return true; - } - $line = $use_last_line? $last_line : fgets($f); - $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line; - $last_line = $line; - $use_last_line = false; - return $line; - } - - function add_comment_to_entry(&$entry, $po_comment_line) { - $first_two = substr($po_comment_line, 0, 2); - $comment = trim(substr($po_comment_line, 2)); - if ('#:' == $first_two) { - $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment)); - } elseif ('#.' == $first_two) { - $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment); - } elseif ('#,' == $first_two) { - $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment)); - } else { - $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment); - } - } - - function trim_quotes($s) { - if ( substr($s, 0, 1) == '"') $s = substr($s, 1); - if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1); - return $s; - } -} -endif; +<?php +/** + * Class for working with PO files + * + * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $ + * @package pomo + * @subpackage po + */ + +require_once dirname(__FILE__) . '/translations.php'; + +define('PO_MAX_LINE_LEN', 79); + +ini_set('auto_detect_line_endings', 1); + +/** + * Routines for working with PO files + */ +if ( !class_exists( 'PO' ) ): +class PO extends Gettext_Translations { + + var $comments_before_headers = ''; + + /** + * Exports headers to a PO entry + * + * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end + */ + function export_headers() { + $header_string = ''; + foreach($this->headers as $header => $value) { + $header_string.= "$header: $value\n"; + } + $poified = PO::poify($header_string); + if ($this->comments_before_headers) + $before_headers = $this->prepend_each_line(rtrim($this->comments_before_headers)."\n", '# '); + else + $before_headers = ''; + return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified"); + } + + /** + * Exports all entries to PO format + * + * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end + */ + function export_entries() { + //TODO sorting + return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries)); + } + + /** + * Exports the whole PO file as a string + * + * @param bool $include_headers whether to include the headers in the export + * @return string ready for inclusion in PO file string for headers and all the enrtries + */ + function export($include_headers = true) { + $res = ''; + if ($include_headers) { + $res .= $this->export_headers(); + $res .= "\n\n"; + } + $res .= $this->export_entries(); + return $res; + } + + /** + * Same as {@link export}, but writes the result to a file + * + * @param string $filename where to write the PO string + * @param bool $include_headers whether to include tje headers in the export + * @return bool true on success, false on error + */ + function export_to_file($filename, $include_headers = true) { + $fh = fopen($filename, 'w'); + if (false === $fh) return false; + $export = $this->export($include_headers); + $res = fwrite($fh, $export); + if (false === $res) return false; + return fclose($fh); + } + + /** + * Text to include as a comment before the start of the PO contents + * + * Doesn't need to include # in the beginning of lines, these are added automatically + */ + function set_comment_before_headers( $text ) { + $this->comments_before_headers = $text; + } + + /** + * Formats a string in PO-style + * + * @static + * @param string $string the string to format + * @return string the poified string + */ + function poify($string) { + $quote = '"'; + $slash = '\\'; + $newline = "\n"; + + $replaces = array( + "$slash" => "$slash$slash", + "$quote" => "$slash$quote", + "\t" => '\t', + ); + + $string = str_replace(array_keys($replaces), array_values($replaces), $string); + + $po = $quote.implode("${slash}n$quote$newline$quote", explode($newline, $string)).$quote; + // add empty string on first line for readbility + if (false !== strpos($string, $newline) && + (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) { + $po = "$quote$quote$newline$po"; + } + // remove empty strings + $po = str_replace("$newline$quote$quote", '', $po); + return $po; + } + + /** + * Gives back the original string from a PO-formatted string + * + * @static + * @param string $string PO-formatted string + * @return string enascaped string + */ + function unpoify($string) { + $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\'); + $lines = array_map('trim', explode("\n", $string)); + $lines = array_map(array('PO', 'trim_quotes'), $lines); + $unpoified = ''; + $previous_is_backslash = false; + foreach($lines as $line) { + preg_match_all('/./u', $line, $chars); + $chars = $chars[0]; + foreach($chars as $char) { + if (!$previous_is_backslash) { + if ('\\' == $char) + $previous_is_backslash = true; + else + $unpoified .= $char; + } else { + $previous_is_backslash = false; + $unpoified .= isset($escapes[$char])? $escapes[$char] : $char; + } + } + } + return $unpoified; + } + + /** + * Inserts $with in the beginning of every new line of $string and + * returns the modified string + * + * @static + * @param string $string prepend lines in this string + * @param string $with prepend lines with this string + */ + function prepend_each_line($string, $with) { + $php_with = var_export($with, true); + $lines = explode("\n", $string); + // do not prepend the string on the last empty line, artefact by explode + if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]); + $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines)); + // give back the empty line, we ignored above + if ("\n" == substr($string, -1)) $res .= "\n"; + return $res; + } + + /** + * Prepare a text as a comment -- wraps the lines and prepends # + * and a special character to each line + * + * @access private + * @param string $text the comment text + * @param string $char character to denote a special PO comment, + * like :, default is a space + */ + function comment_block($text, $char=' ') { + $text = wordwrap($text, PO_MAX_LINE_LEN - 3); + return PO::prepend_each_line($text, "#$char "); + } + + /** + * Builds a string from the entry for inclusion in PO file + * + * @static + * @param object &$entry the entry to convert to po string + * @return string|bool PO-style formatted string for the entry or + * false if the entry is empty + */ + function export_entry(&$entry) { + if (is_null($entry->singular)) return false; + $po = array(); + if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments); + if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.'); + if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); + if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); + if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context); + $po[] = 'msgid '.PO::poify($entry->singular); + if (!$entry->is_plural) { + $translation = empty($entry->translations)? '' : $entry->translations[0]; + $po[] = 'msgstr '.PO::poify($translation); + } else { + $po[] = 'msgid_plural '.PO::poify($entry->plural); + $translations = empty($entry->translations)? array('', '') : $entry->translations; + foreach($translations as $i => $translation) { + $po[] = "msgstr[$i] ".PO::poify($translation); + } + } + return implode("\n", $po); + } + + function import_from_file($filename) { + $f = fopen($filename, 'r'); + if (!$f) return false; + $lineno = 0; + while (true) { + $res = $this->read_entry($f, $lineno); + if (!$res) break; + if ($res['entry']->singular == '') { + $this->set_headers($this->make_headers($res['entry']->translations[0])); + } else { + $this->add_entry($res['entry']); + } + } + PO::read_line($f, 'clear'); + if ( false === $res ) { + return false; + } + if ( ! $this->headers && ! $this->entries ) { + return false; + } + return true; + } + + function read_entry($f, $lineno = 0) { + $entry = new Translation_Entry(); + // where were we in the last step + // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural + $context = ''; + $msgstr_index = 0; + $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";'); + while (true) { + $lineno++; + $line = PO::read_line($f); + if (!$line) { + if (feof($f)) { + if ($is_final($context)) + break; + elseif (!$context) // we haven't read a line and eof came + return null; + else + return false; + } else { + return false; + } + } + if ($line == "\n") continue; + $line = trim($line); + if (preg_match('/^#/', $line, $m)) { + // the comment is the start of a new entry + if ($is_final($context)) { + PO::read_line($f, 'put-back'); + $lineno--; + break; + } + // comments have to be at the beginning + if ($context && $context != 'comment') { + return false; + } + // add comment + $this->add_comment_to_entry($entry, $line);; + } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { + if ($is_final($context)) { + PO::read_line($f, 'put-back'); + $lineno--; + break; + } + if ($context && $context != 'comment') { + return false; + } + $context = 'msgctxt'; + $entry->context .= PO::unpoify($m[1]); + } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { + if ($is_final($context)) { + PO::read_line($f, 'put-back'); + $lineno--; + break; + } + if ($context && $context != 'msgctxt' && $context != 'comment') { + return false; + } + $context = 'msgid'; + $entry->singular .= PO::unpoify($m[1]); + } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) { + if ($context != 'msgid') { + return false; + } + $context = 'msgid_plural'; + $entry->is_plural = true; + $entry->plural .= PO::unpoify($m[1]); + } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) { + if ($context != 'msgid') { + return false; + } + $context = 'msgstr'; + $entry->translations = array(PO::unpoify($m[1])); + } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) { + if ($context != 'msgid_plural' && $context != 'msgstr_plural') { + return false; + } + $context = 'msgstr_plural'; + $msgstr_index = $m[1]; + $entry->translations[$m[1]] = PO::unpoify($m[2]); + } elseif (preg_match('/^".*"$/', $line)) { + $unpoified = PO::unpoify($line); + switch ($context) { + case 'msgid': + $entry->singular .= $unpoified; break; + case 'msgctxt': + $entry->context .= $unpoified; break; + case 'msgid_plural': + $entry->plural .= $unpoified; break; + case 'msgstr': + $entry->translations[0] .= $unpoified; break; + case 'msgstr_plural': + $entry->translations[$msgstr_index] .= $unpoified; break; + default: + return false; + } + } else { + return false; + } + } + if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) { + $entry->translations = array(); + } + return array('entry' => $entry, 'lineno' => $lineno); + } + + function read_line($f, $action = 'read') { + static $last_line = ''; + static $use_last_line = false; + if ('clear' == $action) { + $last_line = ''; + return true; + } + if ('put-back' == $action) { + $use_last_line = true; + return true; + } + $line = $use_last_line? $last_line : fgets($f); + $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line; + $last_line = $line; + $use_last_line = false; + return $line; + } + + function add_comment_to_entry(&$entry, $po_comment_line) { + $first_two = substr($po_comment_line, 0, 2); + $comment = trim(substr($po_comment_line, 2)); + if ('#:' == $first_two) { + $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment)); + } elseif ('#.' == $first_two) { + $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment); + } elseif ('#,' == $first_two) { + $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment)); + } else { + $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment); + } + } + + function trim_quotes($s) { + if ( substr($s, 0, 1) == '"') $s = substr($s, 1); + if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1); + return $s; + } +} +endif; diff --git a/includes/pomo/streams.php b/includes/pomo/streams.php index a69f20f..dbb1de8 100644 --- a/includes/pomo/streams.php +++ b/includes/pomo/streams.php @@ -1,209 +1,209 @@ -<?php -/** - * Classes, which help reading streams of data from files. - * Based on the classes from Danilo Segan <danilo@kvota.net> - * - * @version $Id: streams.php 718 2012-10-31 00:32:02Z nbachiyski $ - * @package pomo - * @subpackage streams - */ - -if ( !class_exists( 'POMO_Reader' ) ): -class POMO_Reader { - - var $endian = 'little'; - var $_post = ''; - - function POMO_Reader() { - $this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr'); - $this->_pos = 0; - } - - /** - * Sets the endianness of the file. - * - * @param $endian string 'big' or 'little' - */ - function setEndian($endian) { - $this->endian = $endian; - } - - /** - * Reads a 32bit Integer from the Stream - * - * @return mixed The integer, corresponding to the next 32 bits from - * the stream of false if there are not enough bytes or on error - */ - function readint32() { - $bytes = $this->read(4); - if (4 != $this->strlen($bytes)) - return false; - $endian_letter = ('big' == $this->endian)? 'N' : 'V'; - $int = unpack($endian_letter, $bytes); - return array_shift($int); - } - - /** - * Reads an array of 32-bit Integers from the Stream - * - * @param integer count How many elements should be read - * @return mixed Array of integers or false if there isn't - * enough data or on error - */ - function readint32array($count) { - $bytes = $this->read(4 * $count); - if (4*$count != $this->strlen($bytes)) - return false; - $endian_letter = ('big' == $this->endian)? 'N' : 'V'; - return unpack($endian_letter.$count, $bytes); - } - - - function substr($string, $start, $length) { - if ($this->is_overloaded) { - return mb_substr($string, $start, $length, 'ascii'); - } else { - return substr($string, $start, $length); - } - } - - function strlen($string) { - if ($this->is_overloaded) { - return mb_strlen($string, 'ascii'); - } else { - return strlen($string); - } - } - - function str_split($string, $chunk_size) { - if (!function_exists('str_split')) { - $length = $this->strlen($string); - $out = array(); - for ($i = 0; $i < $length; $i += $chunk_size) - $out[] = $this->substr($string, $i, $chunk_size); - return $out; - } else { - return str_split( $string, $chunk_size ); - } - } - - - function pos() { - return $this->_pos; - } - - function is_resource() { - return true; - } - - function close() { - return true; - } -} -endif; - -if ( !class_exists( 'POMO_FileReader' ) ): -class POMO_FileReader extends POMO_Reader { - function POMO_FileReader($filename) { - parent::POMO_Reader(); - $this->_f = fopen($filename, 'rb'); - } - - function read($bytes) { - return fread($this->_f, $bytes); - } - - function seekto($pos) { - if ( -1 == fseek($this->_f, $pos, SEEK_SET)) { - return false; - } - $this->_pos = $pos; - return true; - } - - function is_resource() { - return is_resource($this->_f); - } - - function feof() { - return feof($this->_f); - } - - function close() { - return fclose($this->_f); - } - - function read_all() { - $all = ''; - while ( !$this->feof() ) - $all .= $this->read(4096); - return $all; - } -} -endif; - -if ( !class_exists( 'POMO_StringReader' ) ): -/** - * Provides file-like methods for manipulating a string instead - * of a physical file. - */ -class POMO_StringReader extends POMO_Reader { - - var $_str = ''; - - function POMO_StringReader($str = '') { - parent::POMO_Reader(); - $this->_str = $str; - $this->_pos = 0; - } - - - function read($bytes) { - $data = $this->substr($this->_str, $this->_pos, $bytes); - $this->_pos += $bytes; - if ($this->strlen($this->_str) < $this->_pos) $this->_pos = $this->strlen($this->_str); - return $data; - } - - function seekto($pos) { - $this->_pos = $pos; - if ($this->strlen($this->_str) < $this->_pos) $this->_pos = $this->strlen($this->_str); - return $this->_pos; - } - - function length() { - return $this->strlen($this->_str); - } - - function read_all() { - return $this->substr($this->_str, $this->_pos, $this->strlen($this->_str)); - } - -} -endif; - -if ( !class_exists( 'POMO_CachedFileReader' ) ): -/** - * Reads the contents of the file in the beginning. - */ -class POMO_CachedFileReader extends POMO_StringReader { - function POMO_CachedFileReader($filename) { - parent::POMO_StringReader(); - $this->_str = file_get_contents($filename); - if (false === $this->_str) - return false; - $this->_pos = 0; - } -} -endif; - -if ( !class_exists( 'POMO_CachedIntFileReader' ) ): -/** - * Reads the contents of the file in the beginning. - */ -class POMO_CachedIntFileReader extends POMO_CachedFileReader { - function POMO_CachedIntFileReader($filename) { - parent::POMO_CachedFileReader($filename); - } -} +<?php +/** + * Classes, which help reading streams of data from files. + * Based on the classes from Danilo Segan <danilo@kvota.net> + * + * @version $Id: streams.php 718 2012-10-31 00:32:02Z nbachiyski $ + * @package pomo + * @subpackage streams + */ + +if ( !class_exists( 'POMO_Reader' ) ): +class POMO_Reader { + + var $endian = 'little'; + var $_post = ''; + + function POMO_Reader() { + $this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr'); + $this->_pos = 0; + } + + /** + * Sets the endianness of the file. + * + * @param $endian string 'big' or 'little' + */ + function setEndian($endian) { + $this->endian = $endian; + } + + /** + * Reads a 32bit Integer from the Stream + * + * @return mixed The integer, corresponding to the next 32 bits from + * the stream of false if there are not enough bytes or on error + */ + function readint32() { + $bytes = $this->read(4); + if (4 != $this->strlen($bytes)) + return false; + $endian_letter = ('big' == $this->endian)? 'N' : 'V'; + $int = unpack($endian_letter, $bytes); + return array_shift($int); + } + + /** + * Reads an array of 32-bit Integers from the Stream + * + * @param integer count How many elements should be read + * @return mixed Array of integers or false if there isn't + * enough data or on error + */ + function readint32array($count) { + $bytes = $this->read(4 * $count); + if (4*$count != $this->strlen($bytes)) + return false; + $endian_letter = ('big' == $this->endian)? 'N' : 'V'; + return unpack($endian_letter.$count, $bytes); + } + + + function substr($string, $start, $length) { + if ($this->is_overloaded) { + return mb_substr($string, $start, $length, 'ascii'); + } else { + return substr($string, $start, $length); + } + } + + function strlen($string) { + if ($this->is_overloaded) { + return mb_strlen($string, 'ascii'); + } else { + return strlen($string); + } + } + + function str_split($string, $chunk_size) { + if (!function_exists('str_split')) { + $length = $this->strlen($string); + $out = array(); + for ($i = 0; $i < $length; $i += $chunk_size) + $out[] = $this->substr($string, $i, $chunk_size); + return $out; + } else { + return str_split( $string, $chunk_size ); + } + } + + + function pos() { + return $this->_pos; + } + + function is_resource() { + return true; + } + + function close() { + return true; + } +} +endif; + +if ( !class_exists( 'POMO_FileReader' ) ): +class POMO_FileReader extends POMO_Reader { + function POMO_FileReader($filename) { + parent::POMO_Reader(); + $this->_f = fopen($filename, 'rb'); + } + + function read($bytes) { + return fread($this->_f, $bytes); + } + + function seekto($pos) { + if ( -1 == fseek($this->_f, $pos, SEEK_SET)) { + return false; + } + $this->_pos = $pos; + return true; + } + + function is_resource() { + return is_resource($this->_f); + } + + function feof() { + return feof($this->_f); + } + + function close() { + return fclose($this->_f); + } + + function read_all() { + $all = ''; + while ( !$this->feof() ) + $all .= $this->read(4096); + return $all; + } +} +endif; + +if ( !class_exists( 'POMO_StringReader' ) ): +/** + * Provides file-like methods for manipulating a string instead + * of a physical file. + */ +class POMO_StringReader extends POMO_Reader { + + var $_str = ''; + + function POMO_StringReader($str = '') { + parent::POMO_Reader(); + $this->_str = $str; + $this->_pos = 0; + } + + + function read($bytes) { + $data = $this->substr($this->_str, $this->_pos, $bytes); + $this->_pos += $bytes; + if ($this->strlen($this->_str) < $this->_pos) $this->_pos = $this->strlen($this->_str); + return $data; + } + + function seekto($pos) { + $this->_pos = $pos; + if ($this->strlen($this->_str) < $this->_pos) $this->_pos = $this->strlen($this->_str); + return $this->_pos; + } + + function length() { + return $this->strlen($this->_str); + } + + function read_all() { + return $this->substr($this->_str, $this->_pos, $this->strlen($this->_str)); + } + +} +endif; + +if ( !class_exists( 'POMO_CachedFileReader' ) ): +/** + * Reads the contents of the file in the beginning. + */ +class POMO_CachedFileReader extends POMO_StringReader { + function POMO_CachedFileReader($filename) { + parent::POMO_StringReader(); + $this->_str = file_get_contents($filename); + if (false === $this->_str) + return false; + $this->_pos = 0; + } +} +endif; + +if ( !class_exists( 'POMO_CachedIntFileReader' ) ): +/** + * Reads the contents of the file in the beginning. + */ +class POMO_CachedIntFileReader extends POMO_CachedFileReader { + function POMO_CachedIntFileReader($filename) { + parent::POMO_CachedFileReader($filename); + } +} endif; \ No newline at end of file diff --git a/includes/pomo/translations.php b/includes/pomo/translations.php index 6240e99..106b6da 100644 --- a/includes/pomo/translations.php +++ b/includes/pomo/translations.php @@ -1,275 +1,275 @@ -<?php -/** - * Class for a set of entries for translation and their associated headers - * - * @version $Id: translations.php 718 2012-10-31 00:32:02Z nbachiyski $ - * @package pomo - * @subpackage translations - */ - -require_once dirname(__FILE__) . '/entry.php'; - -if ( !class_exists( 'Translations' ) ): -class Translations { - var $entries = array(); - var $headers = array(); - - /** - * Add entry to the PO structure - * - * @param object &$entry - * @return bool true on success, false if the entry doesn't have a key - */ - function add_entry($entry) { - if (is_array($entry)) { - $entry = new Translation_Entry($entry); - } - $key = $entry->key(); - if (false === $key) return false; - $this->entries[$key] = &$entry; - return true; - } - - function add_entry_or_merge($entry) { - if (is_array($entry)) { - $entry = new Translation_Entry($entry); - } - $key = $entry->key(); - if (false === $key) return false; - if (isset($this->entries[$key])) - $this->entries[$key]->merge_with($entry); - else - $this->entries[$key] = &$entry; - return true; - } - - /** - * Sets $header PO header to $value - * - * If the header already exists, it will be overwritten - * - * TODO: this should be out of this class, it is gettext specific - * - * @param string $header header name, without trailing : - * @param string $value header value, without trailing \n - */ - function set_header($header, $value) { - $this->headers[$header] = $value; - } - - function set_headers($headers) { - foreach($headers as $header => $value) { - $this->set_header($header, $value); - } - } - - function get_header($header) { - return isset($this->headers[$header])? $this->headers[$header] : false; - } - - function translate_entry(&$entry) { - $key = $entry->key(); - return isset($this->entries[$key])? $this->entries[$key] : false; - } - - function translate($singular, $context=null) { - $entry = new Translation_Entry(array('singular' => $singular, 'context' => $context)); - $translated = $this->translate_entry($entry); - return ($translated && !empty($translated->translations))? $translated->translations[0] : $singular; - } - - /** - * Given the number of items, returns the 0-based index of the plural form to use - * - * Here, in the base Translations class, the common logic for English is implemented: - * 0 if there is one element, 1 otherwise - * - * This function should be overrided by the sub-classes. For example MO/PO can derive the logic - * from their headers. - * - * @param integer $count number of items - */ - function select_plural_form($count) { - return 1 == $count? 0 : 1; - } - - function get_plural_forms_count() { - return 2; - } - - function translate_plural($singular, $plural, $count, $context = null) { - $entry = new Translation_Entry(array('singular' => $singular, 'plural' => $plural, 'context' => $context)); - $translated = $this->translate_entry($entry); - $index = $this->select_plural_form($count); - $total_plural_forms = $this->get_plural_forms_count(); - if ($translated && 0 <= $index && $index < $total_plural_forms && - is_array($translated->translations) && - isset($translated->translations[$index])) - return $translated->translations[$index]; - else - return 1 == $count? $singular : $plural; - } - - /** - * Merge $other in the current object. - * - * @param Object &$other Another Translation object, whose translations will be merged in this one - * @return void - **/ - function merge_with(&$other) { - foreach( $other->entries as $entry ) { - $this->entries[$entry->key()] = $entry; - } - } - - function merge_originals_with(&$other) { - foreach( $other->entries as $entry ) { - if ( !isset( $this->entries[$entry->key()] ) ) - $this->entries[$entry->key()] = $entry; - else - $this->entries[$entry->key()]->merge_with($entry); - } - } -} - -class Gettext_Translations extends Translations { - /** - * The gettext implementation of select_plural_form. - * - * It lives in this class, because there are more than one descendand, which will use it and - * they can't share it effectively. - * - */ - function gettext_select_plural_form($count) { - if (!isset($this->_gettext_select_plural_form) || is_null($this->_gettext_select_plural_form)) { - list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms')); - $this->_nplurals = $nplurals; - $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression); - } - return call_user_func($this->_gettext_select_plural_form, $count); - } - - function nplurals_and_expression_from_header($header) { - if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches)) { - $nplurals = (int)$matches[1]; - $expression = trim($this->parenthesize_plural_exression($matches[2])); - return array($nplurals, $expression); - } else { - return array(2, 'n != 1'); - } - } - - /** - * Makes a function, which will return the right translation index, according to the - * plural forms header - */ - function make_plural_form_function($nplurals, $expression) { - $expression = str_replace('n', '$n', $expression); - $func_body = " - \$index = (int)($expression); - return (\$index < $nplurals)? \$index : $nplurals - 1;"; - return create_function('$n', $func_body); - } - - /** - * Adds parantheses to the inner parts of ternary operators in - * plural expressions, because PHP evaluates ternary oerators from left to right - * - * @param string $expression the expression without parentheses - * @return string the expression with parentheses added - */ - function parenthesize_plural_exression($expression) { - $expression .= ';'; - $res = ''; - $depth = 0; - for ($i = 0; $i < strlen($expression); ++$i) { - $char = $expression[$i]; - switch ($char) { - case '?': - $res .= ' ? ('; - $depth++; - break; - case ':': - $res .= ') : ('; - break; - case ';': - $res .= str_repeat(')', $depth) . ';'; - $depth= 0; - break; - default: - $res .= $char; - } - } - return rtrim($res, ';'); - } - - function make_headers($translation) { - $headers = array(); - // sometimes \ns are used instead of real new lines - $translation = str_replace('\n', "\n", $translation); - $lines = explode("\n", $translation); - foreach($lines as $line) { - $parts = explode(':', $line, 2); - if (!isset($parts[1])) continue; - $headers[trim($parts[0])] = trim($parts[1]); - } - return $headers; - } - - function set_header($header, $value) { - parent::set_header($header, $value); - if ('Plural-Forms' == $header) { - list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms')); - $this->_nplurals = $nplurals; - $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression); - } - } -} -endif; - -if ( !class_exists( 'NOOP_Translations' ) ): -/** - * Provides the same interface as Translations, but doesn't do anything - */ -class NOOP_Translations { - var $entries = array(); - var $headers = array(); - - function add_entry($entry) { - return true; - } - - function set_header($header, $value) { - } - - function set_headers($headers) { - } - - function get_header($header) { - return false; - } - - function translate_entry(&$entry) { - return false; - } - - function translate($singular, $context=null) { - return $singular; - } - - function select_plural_form($count) { - return 1 == $count? 0 : 1; - } - - function get_plural_forms_count() { - return 2; - } - - function translate_plural($singular, $plural, $count, $context = null) { - return 1 == $count? $singular : $plural; - } - - function merge_with(&$other) { - } -} -endif; +<?php +/** + * Class for a set of entries for translation and their associated headers + * + * @version $Id: translations.php 718 2012-10-31 00:32:02Z nbachiyski $ + * @package pomo + * @subpackage translations + */ + +require_once dirname(__FILE__) . '/entry.php'; + +if ( !class_exists( 'Translations' ) ): +class Translations { + var $entries = array(); + var $headers = array(); + + /** + * Add entry to the PO structure + * + * @param object &$entry + * @return bool true on success, false if the entry doesn't have a key + */ + function add_entry($entry) { + if (is_array($entry)) { + $entry = new Translation_Entry($entry); + } + $key = $entry->key(); + if (false === $key) return false; + $this->entries[$key] = &$entry; + return true; + } + + function add_entry_or_merge($entry) { + if (is_array($entry)) { + $entry = new Translation_Entry($entry); + } + $key = $entry->key(); + if (false === $key) return false; + if (isset($this->entries[$key])) + $this->entries[$key]->merge_with($entry); + else + $this->entries[$key] = &$entry; + return true; + } + + /** + * Sets $header PO header to $value + * + * If the header already exists, it will be overwritten + * + * TODO: this should be out of this class, it is gettext specific + * + * @param string $header header name, without trailing : + * @param string $value header value, without trailing \n + */ + function set_header($header, $value) { + $this->headers[$header] = $value; + } + + function set_headers($headers) { + foreach($headers as $header => $value) { + $this->set_header($header, $value); + } + } + + function get_header($header) { + return isset($this->headers[$header])? $this->headers[$header] : false; + } + + function translate_entry(&$entry) { + $key = $entry->key(); + return isset($this->entries[$key])? $this->entries[$key] : false; + } + + function translate($singular, $context=null) { + $entry = new Translation_Entry(array('singular' => $singular, 'context' => $context)); + $translated = $this->translate_entry($entry); + return ($translated && !empty($translated->translations))? $translated->translations[0] : $singular; + } + + /** + * Given the number of items, returns the 0-based index of the plural form to use + * + * Here, in the base Translations class, the common logic for English is implemented: + * 0 if there is one element, 1 otherwise + * + * This function should be overrided by the sub-classes. For example MO/PO can derive the logic + * from their headers. + * + * @param integer $count number of items + */ + function select_plural_form($count) { + return 1 == $count? 0 : 1; + } + + function get_plural_forms_count() { + return 2; + } + + function translate_plural($singular, $plural, $count, $context = null) { + $entry = new Translation_Entry(array('singular' => $singular, 'plural' => $plural, 'context' => $context)); + $translated = $this->translate_entry($entry); + $index = $this->select_plural_form($count); + $total_plural_forms = $this->get_plural_forms_count(); + if ($translated && 0 <= $index && $index < $total_plural_forms && + is_array($translated->translations) && + isset($translated->translations[$index])) + return $translated->translations[$index]; + else + return 1 == $count? $singular : $plural; + } + + /** + * Merge $other in the current object. + * + * @param Object &$other Another Translation object, whose translations will be merged in this one + * @return void + **/ + function merge_with(&$other) { + foreach( $other->entries as $entry ) { + $this->entries[$entry->key()] = $entry; + } + } + + function merge_originals_with(&$other) { + foreach( $other->entries as $entry ) { + if ( !isset( $this->entries[$entry->key()] ) ) + $this->entries[$entry->key()] = $entry; + else + $this->entries[$entry->key()]->merge_with($entry); + } + } +} + +class Gettext_Translations extends Translations { + /** + * The gettext implementation of select_plural_form. + * + * It lives in this class, because there are more than one descendand, which will use it and + * they can't share it effectively. + * + */ + function gettext_select_plural_form($count) { + if (!isset($this->_gettext_select_plural_form) || is_null($this->_gettext_select_plural_form)) { + list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms')); + $this->_nplurals = $nplurals; + $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression); + } + return call_user_func($this->_gettext_select_plural_form, $count); + } + + function nplurals_and_expression_from_header($header) { + if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches)) { + $nplurals = (int)$matches[1]; + $expression = trim($this->parenthesize_plural_exression($matches[2])); + return array($nplurals, $expression); + } else { + return array(2, 'n != 1'); + } + } + + /** + * Makes a function, which will return the right translation index, according to the + * plural forms header + */ + function make_plural_form_function($nplurals, $expression) { + $expression = str_replace('n', '$n', $expression); + $func_body = " + \$index = (int)($expression); + return (\$index < $nplurals)? \$index : $nplurals - 1;"; + return create_function('$n', $func_body); + } + + /** + * Adds parantheses to the inner parts of ternary operators in + * plural expressions, because PHP evaluates ternary oerators from left to right + * + * @param string $expression the expression without parentheses + * @return string the expression with parentheses added + */ + function parenthesize_plural_exression($expression) { + $expression .= ';'; + $res = ''; + $depth = 0; + for ($i = 0; $i < strlen($expression); ++$i) { + $char = $expression[$i]; + switch ($char) { + case '?': + $res .= ' ? ('; + $depth++; + break; + case ':': + $res .= ') : ('; + break; + case ';': + $res .= str_repeat(')', $depth) . ';'; + $depth= 0; + break; + default: + $res .= $char; + } + } + return rtrim($res, ';'); + } + + function make_headers($translation) { + $headers = array(); + // sometimes \ns are used instead of real new lines + $translation = str_replace('\n', "\n", $translation); + $lines = explode("\n", $translation); + foreach($lines as $line) { + $parts = explode(':', $line, 2); + if (!isset($parts[1])) continue; + $headers[trim($parts[0])] = trim($parts[1]); + } + return $headers; + } + + function set_header($header, $value) { + parent::set_header($header, $value); + if ('Plural-Forms' == $header) { + list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms')); + $this->_nplurals = $nplurals; + $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression); + } + } +} +endif; + +if ( !class_exists( 'NOOP_Translations' ) ): +/** + * Provides the same interface as Translations, but doesn't do anything + */ +class NOOP_Translations { + var $entries = array(); + var $headers = array(); + + function add_entry($entry) { + return true; + } + + function set_header($header, $value) { + } + + function set_headers($headers) { + } + + function get_header($header) { + return false; + } + + function translate_entry(&$entry) { + return false; + } + + function translate($singular, $context=null) { + return $singular; + } + + function select_plural_form($count) { + return 1 == $count? 0 : 1; + } + + function get_plural_forms_count() { + return 2; + } + + function translate_plural($singular, $plural, $count, $context = null) { + return 1 == $count? $singular : $plural; + } + + function merge_with(&$other) { + } +} +endif; diff --git a/includes/version.php b/includes/version.php index 9dc134c..c793fdc 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,4 +1,4 @@ -<?php -// Bump this when updating the zip package -define( 'YOURLS_VERSION', '1.6-polyglot' ); -define( 'YOURLS_DB_VERSION', '482' ); +<?php +// Bump this when updating the zip package +define( 'YOURLS_VERSION', '1.6-polyglot' ); +define( 'YOURLS_DB_VERSION', '482' ); diff --git a/js/common.js b/js/common.js index 4c8cade..190b28c 100644 --- a/js/common.js +++ b/js/common.js @@ -1,157 +1,157 @@ -// Handle .hide-if-no-js and .hide-if-js styles -$(document).ready(function(){ - $('.hide-if-no-js').removeClass('hide-if-no-js'); - $('.hide-if-js').hide(); -}); - -// Change an element text an revert in a smooth pulse. el is an element id like '#copybox h2' -function html_pulse( el, newtext ){ - var oldtext = $(el).html(); - // Fast pulse to "Copied" and revert - $(el).fadeTo( - "normal", - 0.01, - function(){ - $(el) - .html( newtext ) - .css('opacity', 1) - .fadeTo( - "slow", 1, // this fades from 1 to 1: just a 'sleep(1)' actually - function(){ - $(el).fadeTo("normal", 0.01, function(){$(el).html( oldtext ).css('opacity', 1)}); - } - ); - } - ); - - -} - -// Update feedback message -function feedback(msg, type, delay) { - closeme = ( type == 'fail' || type == 'error' ) ? true : false; - delay = delay || ( closeme == true ? 10000 : 3500 ); - $.notifyBar({ - html: '<span>'+msg+'</span>', - delay: delay, - animationSpeed: "normal", - close: closeme, - cls: type - }); - return true; -} - -// Unused for now -function logout() { - $.ajax({ - type: "POST", - url: ajaxurl, - data: {action:'logout'}, - success: function() { - window.parent.location.href = window.parent.location.href; - } - }); -} - -// Begin the spinning animation & disable a button -function add_loading(el) { - $(el).attr("disabled", "disabled").addClass('disabled').addClass('loading'); -} - -// End spinning animation -function end_loading(el) { - $(el).removeClass('loading'); -} - -// Un-disable an element -function end_disable(el) { - $(el).removeAttr("disabled").removeClass('disabled'); -} - -// Trim long string -function trim_long_string( string, length) { - var newstring = string; - length = length || 60; - if ( newstring.length > length ) { - newstring = newstring.substr(0, (length - 5) ) + '[...]'; - } - return newstring; -} - -// Get the var=xxx from a query string -function get_var_from_query( url, varname, default_val ) { - if( varname == undefined ) { - varname = 'nonce'; - } - if( default_val == undefined ) { - default_val = ''; - } - - // Split the url on '?' and get only the params (which is element 1) - url = url.split('?')[1]; - // Now split those params on '&' so we can get each one individually (Ex. param_var=param_value) - url = url.split('&'); - // Now we have to find the varname in that array using methods that IE likes (Curse you IE!!!) - var i=0; - for( i=0; i<url.length; i++ ){ - // So split the first param elemment on '=' and check the param_var to see if it matches varname (element 0) - if( url[i].split('=')[0] == varname ){ - // If it matches we want to return the param_value - return url[i].split('=')[1]; - } - } - - // If we didn't find anything then we just return the default_val - return default_val; -} - -/** - * Jquery Cookie plugin - * Copyright (c) 2006 Klaus Hartl (stilbuero.de) - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * Available at http://plugins.jquery.com/files/jquery.cookie.js.txt - */ -jQuery.cookie = function(name, value, options) { - if (typeof value != 'undefined') { // name and value given, set cookie - options = options || {}; - if (value === null) { - value = ''; - options.expires = -1; - } - var expires = ''; - if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { - var date; - if (typeof options.expires == 'number') { - date = new Date(); - date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); - } else { - date = options.expires; - } - expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE - } - // CAUTION: Needed to parenthesize options.path and options.domain - // in the following expressions, otherwise they evaluate to undefined - // in the packed version for some reason... - var path = options.path ? '; path=' + (options.path) : ''; - var domain = options.domain ? '; domain=' + (options.domain) : ''; - var secure = options.secure ? '; secure' : ''; - document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); - } else { // only name given, get cookie - var cookieValue = null; - if (document.cookie && document.cookie != '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = jQuery.trim(cookies[i]); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) == (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; - } -}; - +// Handle .hide-if-no-js and .hide-if-js styles +$(document).ready(function(){ + $('.hide-if-no-js').removeClass('hide-if-no-js'); + $('.hide-if-js').hide(); +}); + +// Change an element text an revert in a smooth pulse. el is an element id like '#copybox h2' +function html_pulse( el, newtext ){ + var oldtext = $(el).html(); + // Fast pulse to "Copied" and revert + $(el).fadeTo( + "normal", + 0.01, + function(){ + $(el) + .html( newtext ) + .css('opacity', 1) + .fadeTo( + "slow", 1, // this fades from 1 to 1: just a 'sleep(1)' actually + function(){ + $(el).fadeTo("normal", 0.01, function(){$(el).html( oldtext ).css('opacity', 1)}); + } + ); + } + ); + + +} + +// Update feedback message +function feedback(msg, type, delay) { + closeme = ( type == 'fail' || type == 'error' ) ? true : false; + delay = delay || ( closeme == true ? 10000 : 3500 ); + $.notifyBar({ + html: '<span>'+msg+'</span>', + delay: delay, + animationSpeed: "normal", + close: closeme, + cls: type + }); + return true; +} + +// Unused for now +function logout() { + $.ajax({ + type: "POST", + url: ajaxurl, + data: {action:'logout'}, + success: function() { + window.parent.location.href = window.parent.location.href; + } + }); +} + +// Begin the spinning animation & disable a button +function add_loading(el) { + $(el).attr("disabled", "disabled").addClass('disabled').addClass('loading'); +} + +// End spinning animation +function end_loading(el) { + $(el).removeClass('loading'); +} + +// Un-disable an element +function end_disable(el) { + $(el).removeAttr("disabled").removeClass('disabled'); +} + +// Trim long string +function trim_long_string( string, length) { + var newstring = string; + length = length || 60; + if ( newstring.length > length ) { + newstring = newstring.substr(0, (length - 5) ) + '[...]'; + } + return newstring; +} + +// Get the var=xxx from a query string +function get_var_from_query( url, varname, default_val ) { + if( varname == undefined ) { + varname = 'nonce'; + } + if( default_val == undefined ) { + default_val = ''; + } + + // Split the url on '?' and get only the params (which is element 1) + url = url.split('?')[1]; + // Now split those params on '&' so we can get each one individually (Ex. param_var=param_value) + url = url.split('&'); + // Now we have to find the varname in that array using methods that IE likes (Curse you IE!!!) + var i=0; + for( i=0; i<url.length; i++ ){ + // So split the first param elemment on '=' and check the param_var to see if it matches varname (element 0) + if( url[i].split('=')[0] == varname ){ + // If it matches we want to return the param_value + return url[i].split('=')[1]; + } + } + + // If we didn't find anything then we just return the default_val + return default_val; +} + +/** + * Jquery Cookie plugin + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * Available at http://plugins.jquery.com/files/jquery.cookie.js.txt + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; + diff --git a/js/infos.js b/js/infos.js index d8ca104..a897f22 100644 --- a/js/infos.js +++ b/js/infos.js @@ -1,48 +1,48 @@ - -$(document).ready(function(){ - $('ul.toggle_display').css('display', 'block'); - $('.tab h2').css('display','none'); - - // Toggle tabs - $('ul.toggle_display li a').click(function(){ - var target = $(this).attr('href').replace('#', ''); // 'stat_tab_location' - var divs = target.split('_')[1]; // 'tab' - $('div.'+divs).css('display', 'none'); - $('div#'+target).css('display', 'block'); - $('ul.stat_'+divs+' li a').removeClass('selected'); - $('ul.stat_'+divs+' li a[href="#'+target+'"]').addClass('selected').css('outline', 'none').blur(); - return false; - }); - - // Activate main tab - if (location.hash) { - $('#tabs ul#headers li a[href="'+location.hash+'"]').click(); - } else { - $('#tabs ul#headers li a:first').click(); - } - - // Activate first line graph - $('#stats_lines li a:first').click(); - - // Prettify list - $('#historical_clicks li:odd').css('background', '#E3F3FF'); - - // Toggle detail lists - $('a.details').click(function(){ - var target = $(this).attr('id').replace('more_', 'details_'); - $('#'+target).toggle(); - return false; - }); - - // If an image src is erroneous (404 or anything) replace it with a transparent gif - $('.fix_images').each(function(i,img) { - $(img).error(function(){ - $(img).attr('src', 'images/blank.gif'); - }); - }); - - // If we have the zeroclipboard thing, init it when Share Tab is displayed - $('#tabs ul#headers li a[href="#stat_tab_share"]').click(function(){ - init_clipboard(); - }); + +$(document).ready(function(){ + $('ul.toggle_display').css('display', 'block'); + $('.tab h2').css('display','none'); + + // Toggle tabs + $('ul.toggle_display li a').click(function(){ + var target = $(this).attr('href').replace('#', ''); // 'stat_tab_location' + var divs = target.split('_')[1]; // 'tab' + $('div.'+divs).css('display', 'none'); + $('div#'+target).css('display', 'block'); + $('ul.stat_'+divs+' li a').removeClass('selected'); + $('ul.stat_'+divs+' li a[href="#'+target+'"]').addClass('selected').css('outline', 'none').blur(); + return false; + }); + + // Activate main tab + if (location.hash) { + $('#tabs ul#headers li a[href="'+location.hash+'"]').click(); + } else { + $('#tabs ul#headers li a:first').click(); + } + + // Activate first line graph + $('#stats_lines li a:first').click(); + + // Prettify list + $('#historical_clicks li:odd').css('background', '#E3F3FF'); + + // Toggle detail lists + $('a.details').click(function(){ + var target = $(this).attr('id').replace('more_', 'details_'); + $('#'+target).toggle(); + return false; + }); + + // If an image src is erroneous (404 or anything) replace it with a transparent gif + $('.fix_images').each(function(i,img) { + $(img).error(function(){ + $(img).attr('src', 'images/blank.gif'); + }); + }); + + // If we have the zeroclipboard thing, init it when Share Tab is displayed + $('#tabs ul#headers li a[href="#stat_tab_share"]').click(function(){ + init_clipboard(); + }); }); \ No newline at end of file diff --git a/js/insert.js b/js/insert.js index e505582..e57e636 100644 --- a/js/insert.js +++ b/js/insert.js @@ -1,202 +1,202 @@ -// Init some stuff -$(document).ready(function(){ - $('#add-url, #add-keyword').keypress(function(e){ - if (e.which == 13) {add_link();} - }); - add_link_reset(); - $('#new_url_form').attr('action', 'javascript:add_link();'); - - $('input.text').focus(function(){ - $(this).select(); - }); - - // this one actually has little impact, the .hasClass('disabled') in each edit_link_display(), remove() etc... fires faster - $(document).on( 'click', 'a.button', function() { - if( $(this).hasClass('disabled') ) { - return false; - } - }); -}); - -// Create new link and add to table -function add_link() { - if( $('#add-button').hasClass('disabled') ) { - return false; - } - var newurl = $("#add-url").val(); - var nonce = $("#nonce-add").val(); - if ( !newurl || newurl == 'http://' || newurl == 'https://' ) { - return; - } - var keyword = $("#add-keyword").val(); - add_loading("#add-button"); - $.getJSON( - ajaxurl, - {action:'add', url: newurl, keyword: keyword, nonce: nonce}, - function(data){ - if(data.status == 'success') { - $('#main_table tbody').prepend( data.html ).trigger("update"); - $('#nourl_found').css('display', 'none'); - zebra_table(); - increment_counter(); - toggle_share_fill_boxes( data.url.url, data.shorturl, data.url.title ); - } - - add_link_reset(); - end_loading("#add-button"); - end_disable("#add-button"); - - feedback(data.message, data.status); - } - ); -} - -function toggle_share_fill_boxes( url, shorturl, title ) { - $('#copylink').val( shorturl ); - $('#titlelink').val( title ); - $('#origlink').attr( 'href', url ).html( url ); - $('#statlink').attr( 'href', shorturl+'+' ).html( shorturl+'+' ); - var tweet = ( title ? title + ' ' + shorturl : shorturl ); - $('#tweet_body').val( tweet ).keypress(); - $('#shareboxes').slideDown( '300', function(){ init_clipboard(); } ); // clipboard re-initialized after slidedown to make sure the invisible Flash element is correctly positionned - $('#tweet_body').keypress(); -} - -// Display the edition interface -function edit_link_display(id) { - if( $('#edit-button-'+id).hasClass('disabled') ) { - return false; - } - add_loading('#actions-'+id+' .button'); - var keyword = $('#keyword_'+id).val(); - var nonce = get_var_from_query( $('#edit-button-'+id).attr('href'), 'nonce' ); - $.getJSON( - ajaxurl, - { action: "edit_display", keyword: keyword, nonce: nonce, id: id }, - function(data){ - $("#id-" + id).after( data.html ); - $("#edit-url-"+ id).focus(); - end_loading('#actions-'+id+' .button'); - } - ); -} - -// Delete a link -function remove_link(id) { - if( $('#delete-button-'+id).hasClass('disabled') ) { - return false; - } - if (!confirm('Really delete?')) { - return; - } - var keyword = $('#keyword_'+id).val(); - var nonce = get_var_from_query( $('#delete-button-'+id).attr('href'), 'nonce' ); - $.getJSON( - ajaxurl, - { action: "delete", keyword: keyword, nonce: nonce, id: id }, - function(data){ - if (data.success == 1) { - $("#id-" + id).fadeOut(function(){ - $(this).remove(); - if( $('#main_table tbody tr').length == 1 ) { - $('#nourl_found').css('display', ''); - } - - zebra_table(); - }); - decrement_counter(); - } else { - alert('something wrong happened while deleting :/'); - } - } - ); -} - -// Redirect to stat page -function go_stats(link) { - window.location=link; -} - -// Cancel edition of a link -function edit_link_hide(id) { - $("#edit-" + id).fadeOut(200, function(){ - end_disable('#actions-'+id+' .button'); - }); -} - -// Save edition of a link -function edit_link_save(id) { - add_loading("#edit-close-" + id); - var newurl = encodeURI( $("#edit-url-" + id).val() ); - var newkeyword = $("#edit-keyword-" + id).val(); - var title = $("#edit-title-" + id).val(); - var keyword = $('#old_keyword_'+id).val(); - var nonce = $('#nonce_'+id).val(); - var www = $('#yourls-site').val(); - $.getJSON( - ajaxurl, - {action:'edit_save', url: newurl, id: id, keyword: keyword, newkeyword: newkeyword, title: title, nonce: nonce }, - function(data){ - if(data.status == 'success') { - - if( data.url.title != '' ) { - var display_link = '<a href="' + data.url.url + '" title="' + data.url.url + '">' + data.url.display_title + '</a><br/><small><a href="' + data.url.url + '">' + data.url.display_url + '</a></small>'; - } else { - var display_link = '<a href="' + data.url.url + '" title="' + data.url.url + '">' + data.url.display_url + '</a>'; - } - - $("#url-" + id).html(display_link); - $("#keyword-" + id).html('<a href="' + data.url.shorturl + '" title="' + data.url.shorturl + '">' + data.url.keyword + '</a>'); - $("#timestamp-" + id).html(data.url.date); - $("#edit-" + id).fadeOut(200, function(){ - $('#main_table tbody').trigger("update"); - }); - $('#keyword_'+id).val( newkeyword ); - $('#statlink-'+id).attr( 'href', data.url.shorturl+'+' ); - } - feedback(data.message, data.status); - end_loading("#edit-close-" + id); - end_disable("#actions-" + id + ' .button'); - } - ); -} - -// Prettify table with odd & even rows -function zebra_table() { - $("#main_table tbody tr:even").removeClass('odd').addClass('even'); - $("#main_table tbody tr:odd").removeClass('even').addClass('odd'); - $('#main_table tbody').trigger("update"); -} - -// Ready to add another URL -function add_link_reset() { - $('#add-url').val('http://').focus(); - $('#add-keyword').val(''); -} - -// Increment URL counters -function increment_counter() { - $('.increment').each(function(){ - $(this).html( parseInt($(this).html()) + 1); - }); -} - -// Decrement URL counters -function decrement_counter() { - $('.increment').each(function(){ - $(this).html( parseInt($(this).html()) - 1 ); - }); -} - -// Toggle Share box -function toggle_share(id) { - if( $('#share-button-'+id).hasClass('disabled') ) { - return false; - } - var link = $('#url-'+id+' a:first'); - var longurl = link.attr('href'); - var title = link.attr('title'); - var shorturl = $('#keyword-'+id+' a:first').attr('href'); - - toggle_share_fill_boxes( longurl, shorturl, title ); -} +// Init some stuff +$(document).ready(function(){ + $('#add-url, #add-keyword').keypress(function(e){ + if (e.which == 13) {add_link();} + }); + add_link_reset(); + $('#new_url_form').attr('action', 'javascript:add_link();'); + + $('input.text').focus(function(){ + $(this).select(); + }); + + // this one actually has little impact, the .hasClass('disabled') in each edit_link_display(), remove() etc... fires faster + $(document).on( 'click', 'a.button', function() { + if( $(this).hasClass('disabled') ) { + return false; + } + }); +}); + +// Create new link and add to table +function add_link() { + if( $('#add-button').hasClass('disabled') ) { + return false; + } + var newurl = $("#add-url").val(); + var nonce = $("#nonce-add").val(); + if ( !newurl || newurl == 'http://' || newurl == 'https://' ) { + return; + } + var keyword = $("#add-keyword").val(); + add_loading("#add-button"); + $.getJSON( + ajaxurl, + {action:'add', url: newurl, keyword: keyword, nonce: nonce}, + function(data){ + if(data.status == 'success') { + $('#main_table tbody').prepend( data.html ).trigger("update"); + $('#nourl_found').css('display', 'none'); + zebra_table(); + increment_counter(); + toggle_share_fill_boxes( data.url.url, data.shorturl, data.url.title ); + } + + add_link_reset(); + end_loading("#add-button"); + end_disable("#add-button"); + + feedback(data.message, data.status); + } + ); +} + +function toggle_share_fill_boxes( url, shorturl, title ) { + $('#copylink').val( shorturl ); + $('#titlelink').val( title ); + $('#origlink').attr( 'href', url ).html( url ); + $('#statlink').attr( 'href', shorturl+'+' ).html( shorturl+'+' ); + var tweet = ( title ? title + ' ' + shorturl : shorturl ); + $('#tweet_body').val( tweet ).keypress(); + $('#shareboxes').slideDown( '300', function(){ init_clipboard(); } ); // clipboard re-initialized after slidedown to make sure the invisible Flash element is correctly positionned + $('#tweet_body').keypress(); +} + +// Display the edition interface +function edit_link_display(id) { + if( $('#edit-button-'+id).hasClass('disabled') ) { + return false; + } + add_loading('#actions-'+id+' .button'); + var keyword = $('#keyword_'+id).val(); + var nonce = get_var_from_query( $('#edit-button-'+id).attr('href'), 'nonce' ); + $.getJSON( + ajaxurl, + { action: "edit_display", keyword: keyword, nonce: nonce, id: id }, + function(data){ + $("#id-" + id).after( data.html ); + $("#edit-url-"+ id).focus(); + end_loading('#actions-'+id+' .button'); + } + ); +} + +// Delete a link +function remove_link(id) { + if( $('#delete-button-'+id).hasClass('disabled') ) { + return false; + } + if (!confirm('Really delete?')) { + return; + } + var keyword = $('#keyword_'+id).val(); + var nonce = get_var_from_query( $('#delete-button-'+id).attr('href'), 'nonce' ); + $.getJSON( + ajaxurl, + { action: "delete", keyword: keyword, nonce: nonce, id: id }, + function(data){ + if (data.success == 1) { + $("#id-" + id).fadeOut(function(){ + $(this).remove(); + if( $('#main_table tbody tr').length == 1 ) { + $('#nourl_found').css('display', ''); + } + + zebra_table(); + }); + decrement_counter(); + } else { + alert('something wrong happened while deleting :/'); + } + } + ); +} + +// Redirect to stat page +function go_stats(link) { + window.location=link; +} + +// Cancel edition of a link +function edit_link_hide(id) { + $("#edit-" + id).fadeOut(200, function(){ + end_disable('#actions-'+id+' .button'); + }); +} + +// Save edition of a link +function edit_link_save(id) { + add_loading("#edit-close-" + id); + var newurl = encodeURI( $("#edit-url-" + id).val() ); + var newkeyword = $("#edit-keyword-" + id).val(); + var title = $("#edit-title-" + id).val(); + var keyword = $('#old_keyword_'+id).val(); + var nonce = $('#nonce_'+id).val(); + var www = $('#yourls-site').val(); + $.getJSON( + ajaxurl, + {action:'edit_save', url: newurl, id: id, keyword: keyword, newkeyword: newkeyword, title: title, nonce: nonce }, + function(data){ + if(data.status == 'success') { + + if( data.url.title != '' ) { + var display_link = '<a href="' + data.url.url + '" title="' + data.url.url + '">' + data.url.display_title + '</a><br/><small><a href="' + data.url.url + '">' + data.url.display_url + '</a></small>'; + } else { + var display_link = '<a href="' + data.url.url + '" title="' + data.url.url + '">' + data.url.display_url + '</a>'; + } + + $("#url-" + id).html(display_link); + $("#keyword-" + id).html('<a href="' + data.url.shorturl + '" title="' + data.url.shorturl + '">' + data.url.keyword + '</a>'); + $("#timestamp-" + id).html(data.url.date); + $("#edit-" + id).fadeOut(200, function(){ + $('#main_table tbody').trigger("update"); + }); + $('#keyword_'+id).val( newkeyword ); + $('#statlink-'+id).attr( 'href', data.url.shorturl+'+' ); + } + feedback(data.message, data.status); + end_loading("#edit-close-" + id); + end_disable("#actions-" + id + ' .button'); + } + ); +} + +// Prettify table with odd & even rows +function zebra_table() { + $("#main_table tbody tr:even").removeClass('odd').addClass('even'); + $("#main_table tbody tr:odd").removeClass('even').addClass('odd'); + $('#main_table tbody').trigger("update"); +} + +// Ready to add another URL +function add_link_reset() { + $('#add-url').val('http://').focus(); + $('#add-keyword').val(''); +} + +// Increment URL counters +function increment_counter() { + $('.increment').each(function(){ + $(this).html( parseInt($(this).html()) + 1); + }); +} + +// Decrement URL counters +function decrement_counter() { + $('.increment').each(function(){ + $(this).html( parseInt($(this).html()) - 1 ); + }); +} + +// Toggle Share box +function toggle_share(id) { + if( $('#share-button-'+id).hasClass('disabled') ) { + return false; + } + var link = $('#url-'+id+' a:first'); + var longurl = link.attr('href'); + var title = link.attr('title'); + var shorturl = $('#keyword-'+id+' a:first').attr('href'); + + toggle_share_fill_boxes( longurl, shorturl, title ); +} diff --git a/js/jquery-1.8.2.min.js b/js/jquery-1.8.2.min.js index f65cf1d..bc3fbc8 100644 --- a/js/jquery-1.8.2.min.js +++ b/js/jquery-1.8.2.min.js @@ -1,2 +1,2 @@ -/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ (function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){return d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}return bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.getBoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/js/jquery.cal.js b/js/jquery.cal.js index 6f54133..d86e721 100644 --- a/js/jquery.cal.js +++ b/js/jquery.cal.js @@ -1,319 +1,319 @@ -/** - the script only works on "input [type=text]" - http://teddevito.com/demos/calendar.php -**/ - -// don't declare anything out here in the global namespace - -(function($) { // create private scope (inside you can use $ instead of jQuery) - - // functions and vars declared here are effectively 'singletons'. there will be only a single - // instance of them. so this is a good place to declare any immutable items or stateless - // functions. for example: - - var today = new Date(); // used in defaults - var months = 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','); - var months = l10n_cal_month; - var monthlengths = '31,28,31,30,31,30,31,31,30,31,30,31'.split(','); - var dateRegEx = /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/; - var yearRegEx = /^\d{4,4}$/; - - // next, declare the plugin function - $.fn.simpleDatepicker = function(options) { - - // functions and vars declared here are created each time your plugn function is invoked - - // you could probably refactor your 'build', 'load_month', etc, functions to be passed - // the DOM element from below - - var opts = jQuery.extend({}, jQuery.fn.simpleDatepicker.defaults, options); - - // replaces a date string with a date object in opts.startdate and opts.enddate, if one exists - // populates two new properties with a ready-to-use year: opts.startyear and opts.endyear - - setupYearRange(); - /** extracts and setup a valid year range from the opts object **/ - function setupYearRange () { - - var startyear, endyear; - if (opts.startdate.constructor == Date) { - startyear = opts.startdate.getFullYear(); - } else if (opts.startdate) { - if (yearRegEx.test(opts.startdate)) { - startyear = opts.startdate; - } else if (dateRegEx.test(opts.startdate)) { - opts.startdate = new Date(opts.startdate); - startyear = opts.startdate.getFullYear(); - } else { - startyear = today.getFullYear(); - } - } else { - startyear = today.getFullYear(); - } - opts.startyear = startyear; - - if (opts.enddate.constructor == Date) { - endyear = opts.enddate.getFullYear(); - } else if (opts.enddate) { - if (yearRegEx.test(opts.enddate)) { - endyear = opts.enddate; - } else if (dateRegEx.test(opts.enddate)) { - opts.enddate = new Date(opts.enddate); - endyear = opts.enddate.getFullYear(); - } else { - endyear = today.getFullYear(); - } - } else { - endyear = today.getFullYear(); - } - opts.endyear = endyear; - } - - /** HTML factory for the actual datepicker table element **/ - // has to read the year range so it can setup the correct years in our HTML <select> - function newDatepickerHTML () { - - var years = []; - - // process year range into an array - for (var i = 0; i <= opts.endyear - opts.startyear; i ++) years[i] = opts.startyear + i; - - // build the table structure - var table = jQuery('<table class="datepicker" cellpadding="0" cellspacing="0"></table>'); - table.append('<thead></thead>'); - table.append('<tfoot></tfoot>'); - table.append('<tbody></tbody>'); - - // month select field - var monthselect = '<select name="month">'; - for (var i in l10n_cal_month) monthselect += '<option value="'+i+'">'+l10n_cal_month[i]+'</option>'; - monthselect += '</select>'; - - // year select field - var yearselect = '<select name="year">'; - for (var i in years) yearselect += '<option>'+years[i]+'</option>'; - yearselect += '</select>'; - - jQuery("thead",table).append('<tr class="controls"><th colspan="7"><span class="prevMonth">«</span> '+monthselect+yearselect+' <span class="nextMonth">»</span></th></tr>'); - jQuery("thead",table).append('<tr class="days"><th>'+l10n_cal_days[0]+'</th><th>'+l10n_cal_days[1]+'</th><th>'+l10n_cal_days[2]+'</th><th>'+l10n_cal_days[3]+'</th><th>'+l10n_cal_days[4]+'</th><th>'+l10n_cal_days[5]+'</th><th>'+l10n_cal_days[6]+'</th></tr>'); - jQuery("tfoot",table).append('<tr><td colspan="2"><span class="today">'+l10n_cal_today+'</span></td><td colspan="3"> </td><td colspan="2"><span class="close">'+l10n_cal_close+'</span></td></tr>'); - for (var i = 0; i < 6; i++) jQuery("tbody",table).append('<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'); - return table; - } - - /** get the real position of the input (well, anything really) **/ - //http://www.quirksmode.org/js/findpos.html - function findPosition (obj) { - var curleft = curtop = 0; - if (obj.offsetParent) { - do { - curleft += obj.offsetLeft; - curtop += obj.offsetTop; - } while (obj = obj.offsetParent); - return [curleft,curtop]; - } else { - return false; - } - } - - /** load the initial date and handle all date-navigation **/ - // initial calendar load (e is null) - // prevMonth & nextMonth buttons - // onchange for the select fields - function loadMonth (e, el, datepicker, chosendate) { - - // reference our years for the nextMonth and prevMonth buttons - var mo = jQuery("select[name=month]", datepicker).get(0).selectedIndex; - var yr = jQuery("select[name=year]", datepicker).get(0).selectedIndex; - var yrs = jQuery("select[name=year] option", datepicker).get().length; - - // first try to process buttons that may change the month we're on - if (e && jQuery(e.target).hasClass('prevMonth')) { - if (0 == mo && yr) { - yr -= 1; mo = 11; - jQuery("select[name=month]", datepicker).get(0).selectedIndex = 11; - jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; - } else { - mo -= 1; - jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; - } - } else if (e && jQuery(e.target).hasClass('nextMonth')) { - if (11 == mo && yr + 1 < yrs) { - yr += 1; mo = 0; - jQuery("select[name=month]", datepicker).get(0).selectedIndex = 0; - jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; - } else { - mo += 1; - jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; - } - } - - // maybe hide buttons - if (0 == mo && !yr) jQuery("span.prevMonth", datepicker).hide(); - else jQuery("span.prevMonth", datepicker).show(); - if (yr + 1 == yrs && 11 == mo) jQuery("span.nextMonth", datepicker).hide(); - else jQuery("span.nextMonth", datepicker).show(); - - // clear the old cells - var cells = jQuery("tbody td", datepicker).unbind().empty().removeClass('date'); - - // figure out what month and year to load - var m = jQuery("select[name=month]", datepicker).val(); - var y = jQuery("select[name=year]", datepicker).val(); - var d = new Date(y, m, 1); - var startindex = d.getDay(); - var numdays = monthlengths[m]; - - // http://en.wikipedia.org/wiki/Leap_year - if (1 == m && ((y%4 == 0 && y%100 != 0) || y%400 == 0)) numdays = 29; - - // test for end dates (instead of just a year range) - if (opts.startdate.constructor == Date) { - var startMonth = opts.startdate.getMonth(); - var startDate = opts.startdate.getDate(); - } - if (opts.enddate.constructor == Date) { - var endMonth = opts.enddate.getMonth(); - var endDate = opts.enddate.getDate(); - } - - // walk through the index and populate each cell, binding events too - for (var i = 0; i < numdays; i++) { - - var cell = jQuery(cells.get(i+startindex)).removeClass('chosen'); - - // test that the date falls within a range, if we have a range - if ( - (yr || ((!startDate && !startMonth) || ((i+1 >= startDate && mo == startMonth) || mo > startMonth))) && - (yr + 1 < yrs || ((!endDate && !endMonth) || ((i+1 <= endDate && mo == endMonth) || mo < endMonth)))) { - - cell - .text(i+1) - .addClass('date') - .hover( - function () { jQuery(this).addClass('over'); }, - function () { jQuery(this).removeClass('over'); }) - .click(function () { - var chosenDateObj = new Date(jQuery("select[name=year]", datepicker).val(), jQuery("select[name=month]", datepicker).val(), jQuery(this).text()); - closeIt(el, datepicker, chosenDateObj); - }); - - // highlight the previous chosen date - if (i+1 == chosendate.getDate() && m == chosendate.getMonth() && y == chosendate.getFullYear()) cell.addClass('chosen'); - } - } - } - - /** closes the datepicker **/ - // sets the currently matched input element's value to the date, if one is available - // remove the table element from the DOM - // indicate that there is no datepicker for the currently matched input element - function closeIt (el, datepicker, dateObj) { - if (dateObj && dateObj.constructor == Date) - el.val(jQuery.fn.simpleDatepicker.formatOutput(dateObj)); - datepicker.remove(); - datepicker = null; - jQuery.data(el.get(0), "simpleDatepicker", { hasDatepicker : false }); - } - - // iterate the matched nodeset - return this.each(function() { - - // functions and vars declared here are created for each matched element. so if - // your functions need to manage or access per-node state you can defined them - // here and use $this to get at the DOM element - - if ( jQuery(this).is('input') && 'text' == jQuery(this).attr('type')) { - - var datepicker; - jQuery.data(jQuery(this).get(0), "simpleDatepicker", { hasDatepicker : false }); - - // open a datepicker on the click event - jQuery(this).click(function (ev) { - - var $this = jQuery(ev.target); - - if (false == jQuery.data($this.get(0), "simpleDatepicker").hasDatepicker) { - - // store data telling us there is already a datepicker - jQuery.data($this.get(0), "simpleDatepicker", { hasDatepicker : true }); - - // validate the form's initial content for a date - var initialDate = $this.val(); - - if (initialDate && dateRegEx.test(initialDate)) { - var chosendate = new Date(initialDate); - } else if (opts.chosendate.constructor == Date) { - var chosendate = opts.chosendate; - } else if (opts.chosendate) { - var chosendate = new Date(opts.chosendate); - } else { - var chosendate = today; - } - - // insert the datepicker in the DOM - datepicker = newDatepickerHTML(); - jQuery("body").prepend(datepicker); - - // position the datepicker - var elPos = findPosition($this.get(0)); - var x = (parseInt(opts.x) ? parseInt(opts.x) : 0) + elPos[0]; - var y = (parseInt(opts.y) ? parseInt(opts.y) : 0) + elPos[1]; - jQuery(datepicker).css({ position: 'absolute', left: x, top: y }); - - // bind events to the table controls - jQuery("span", datepicker).css("cursor","pointer"); - jQuery("select", datepicker).bind('change', function () { loadMonth (null, $this, datepicker, chosendate); }); - jQuery("span.prevMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); - jQuery("span.nextMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); - jQuery("span.today", datepicker).click(function () { closeIt($this, datepicker, new Date()); }); - jQuery("span.close", datepicker).click(function () { closeIt($this, datepicker); }); - - // set the initial values for the month and year select fields - // and load the first month - jQuery("select[name=month]", datepicker).get(0).selectedIndex = chosendate.getMonth(); - jQuery("select[name=year]", datepicker).get(0).selectedIndex = Math.max(0, chosendate.getFullYear() - opts.startyear); - loadMonth(null, $this, datepicker, chosendate); - } - - }); - } - - }); - - }; - - // finally, I like to expose default plugin options as public so they can be manipulated. one - // way to do this is to add a property to the already-public plugin fn - - jQuery.fn.simpleDatepicker.formatOutput = function (dateObj) { - return (dateObj.getMonth() + 1) + "/" + dateObj.getDate() + "/" + dateObj.getFullYear(); - }; - - jQuery.fn.simpleDatepicker.defaults = { - // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ - chosendate : today, - - // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ - // or four digit year - startdate : today.getFullYear(), - enddate : today.getFullYear() + 1, - - // offset from the top left corner of the input element - x : 1, // must be in px - y : 18 // must be in px - }; - -})(jQuery); - -// Init the form -$(document).ready(function(){ - $('#date_first').simpleDatepicker({startdate: 2005, enddate: 2100}); - $('#date_second').simpleDatepicker({startdate: 2005, enddate: 2100}); - - $('#date_filter').change(function(){ - var show = $(this).val() == 'between' ? 'inline' : 'none'; - $('#date_second').css('display', show); - $('#date_and').css('display', show); - }); +/** + the script only works on "input [type=text]" + http://teddevito.com/demos/calendar.php +**/ + +// don't declare anything out here in the global namespace + +(function($) { // create private scope (inside you can use $ instead of jQuery) + + // functions and vars declared here are effectively 'singletons'. there will be only a single + // instance of them. so this is a good place to declare any immutable items or stateless + // functions. for example: + + var today = new Date(); // used in defaults + var months = 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','); + var months = l10n_cal_month; + var monthlengths = '31,28,31,30,31,30,31,31,30,31,30,31'.split(','); + var dateRegEx = /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/; + var yearRegEx = /^\d{4,4}$/; + + // next, declare the plugin function + $.fn.simpleDatepicker = function(options) { + + // functions and vars declared here are created each time your plugn function is invoked + + // you could probably refactor your 'build', 'load_month', etc, functions to be passed + // the DOM element from below + + var opts = jQuery.extend({}, jQuery.fn.simpleDatepicker.defaults, options); + + // replaces a date string with a date object in opts.startdate and opts.enddate, if one exists + // populates two new properties with a ready-to-use year: opts.startyear and opts.endyear + + setupYearRange(); + /** extracts and setup a valid year range from the opts object **/ + function setupYearRange () { + + var startyear, endyear; + if (opts.startdate.constructor == Date) { + startyear = opts.startdate.getFullYear(); + } else if (opts.startdate) { + if (yearRegEx.test(opts.startdate)) { + startyear = opts.startdate; + } else if (dateRegEx.test(opts.startdate)) { + opts.startdate = new Date(opts.startdate); + startyear = opts.startdate.getFullYear(); + } else { + startyear = today.getFullYear(); + } + } else { + startyear = today.getFullYear(); + } + opts.startyear = startyear; + + if (opts.enddate.constructor == Date) { + endyear = opts.enddate.getFullYear(); + } else if (opts.enddate) { + if (yearRegEx.test(opts.enddate)) { + endyear = opts.enddate; + } else if (dateRegEx.test(opts.enddate)) { + opts.enddate = new Date(opts.enddate); + endyear = opts.enddate.getFullYear(); + } else { + endyear = today.getFullYear(); + } + } else { + endyear = today.getFullYear(); + } + opts.endyear = endyear; + } + + /** HTML factory for the actual datepicker table element **/ + // has to read the year range so it can setup the correct years in our HTML <select> + function newDatepickerHTML () { + + var years = []; + + // process year range into an array + for (var i = 0; i <= opts.endyear - opts.startyear; i ++) years[i] = opts.startyear + i; + + // build the table structure + var table = jQuery('<table class="datepicker" cellpadding="0" cellspacing="0"></table>'); + table.append('<thead></thead>'); + table.append('<tfoot></tfoot>'); + table.append('<tbody></tbody>'); + + // month select field + var monthselect = '<select name="month">'; + for (var i in l10n_cal_month) monthselect += '<option value="'+i+'">'+l10n_cal_month[i]+'</option>'; + monthselect += '</select>'; + + // year select field + var yearselect = '<select name="year">'; + for (var i in years) yearselect += '<option>'+years[i]+'</option>'; + yearselect += '</select>'; + + jQuery("thead",table).append('<tr class="controls"><th colspan="7"><span class="prevMonth">«</span> '+monthselect+yearselect+' <span class="nextMonth">»</span></th></tr>'); + jQuery("thead",table).append('<tr class="days"><th>'+l10n_cal_days[0]+'</th><th>'+l10n_cal_days[1]+'</th><th>'+l10n_cal_days[2]+'</th><th>'+l10n_cal_days[3]+'</th><th>'+l10n_cal_days[4]+'</th><th>'+l10n_cal_days[5]+'</th><th>'+l10n_cal_days[6]+'</th></tr>'); + jQuery("tfoot",table).append('<tr><td colspan="2"><span class="today">'+l10n_cal_today+'</span></td><td colspan="3"> </td><td colspan="2"><span class="close">'+l10n_cal_close+'</span></td></tr>'); + for (var i = 0; i < 6; i++) jQuery("tbody",table).append('<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'); + return table; + } + + /** get the real position of the input (well, anything really) **/ + //http://www.quirksmode.org/js/findpos.html + function findPosition (obj) { + var curleft = curtop = 0; + if (obj.offsetParent) { + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while (obj = obj.offsetParent); + return [curleft,curtop]; + } else { + return false; + } + } + + /** load the initial date and handle all date-navigation **/ + // initial calendar load (e is null) + // prevMonth & nextMonth buttons + // onchange for the select fields + function loadMonth (e, el, datepicker, chosendate) { + + // reference our years for the nextMonth and prevMonth buttons + var mo = jQuery("select[name=month]", datepicker).get(0).selectedIndex; + var yr = jQuery("select[name=year]", datepicker).get(0).selectedIndex; + var yrs = jQuery("select[name=year] option", datepicker).get().length; + + // first try to process buttons that may change the month we're on + if (e && jQuery(e.target).hasClass('prevMonth')) { + if (0 == mo && yr) { + yr -= 1; mo = 11; + jQuery("select[name=month]", datepicker).get(0).selectedIndex = 11; + jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; + } else { + mo -= 1; + jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; + } + } else if (e && jQuery(e.target).hasClass('nextMonth')) { + if (11 == mo && yr + 1 < yrs) { + yr += 1; mo = 0; + jQuery("select[name=month]", datepicker).get(0).selectedIndex = 0; + jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; + } else { + mo += 1; + jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; + } + } + + // maybe hide buttons + if (0 == mo && !yr) jQuery("span.prevMonth", datepicker).hide(); + else jQuery("span.prevMonth", datepicker).show(); + if (yr + 1 == yrs && 11 == mo) jQuery("span.nextMonth", datepicker).hide(); + else jQuery("span.nextMonth", datepicker).show(); + + // clear the old cells + var cells = jQuery("tbody td", datepicker).unbind().empty().removeClass('date'); + + // figure out what month and year to load + var m = jQuery("select[name=month]", datepicker).val(); + var y = jQuery("select[name=year]", datepicker).val(); + var d = new Date(y, m, 1); + var startindex = d.getDay(); + var numdays = monthlengths[m]; + + // http://en.wikipedia.org/wiki/Leap_year + if (1 == m && ((y%4 == 0 && y%100 != 0) || y%400 == 0)) numdays = 29; + + // test for end dates (instead of just a year range) + if (opts.startdate.constructor == Date) { + var startMonth = opts.startdate.getMonth(); + var startDate = opts.startdate.getDate(); + } + if (opts.enddate.constructor == Date) { + var endMonth = opts.enddate.getMonth(); + var endDate = opts.enddate.getDate(); + } + + // walk through the index and populate each cell, binding events too + for (var i = 0; i < numdays; i++) { + + var cell = jQuery(cells.get(i+startindex)).removeClass('chosen'); + + // test that the date falls within a range, if we have a range + if ( + (yr || ((!startDate && !startMonth) || ((i+1 >= startDate && mo == startMonth) || mo > startMonth))) && + (yr + 1 < yrs || ((!endDate && !endMonth) || ((i+1 <= endDate && mo == endMonth) || mo < endMonth)))) { + + cell + .text(i+1) + .addClass('date') + .hover( + function () { jQuery(this).addClass('over'); }, + function () { jQuery(this).removeClass('over'); }) + .click(function () { + var chosenDateObj = new Date(jQuery("select[name=year]", datepicker).val(), jQuery("select[name=month]", datepicker).val(), jQuery(this).text()); + closeIt(el, datepicker, chosenDateObj); + }); + + // highlight the previous chosen date + if (i+1 == chosendate.getDate() && m == chosendate.getMonth() && y == chosendate.getFullYear()) cell.addClass('chosen'); + } + } + } + + /** closes the datepicker **/ + // sets the currently matched input element's value to the date, if one is available + // remove the table element from the DOM + // indicate that there is no datepicker for the currently matched input element + function closeIt (el, datepicker, dateObj) { + if (dateObj && dateObj.constructor == Date) + el.val(jQuery.fn.simpleDatepicker.formatOutput(dateObj)); + datepicker.remove(); + datepicker = null; + jQuery.data(el.get(0), "simpleDatepicker", { hasDatepicker : false }); + } + + // iterate the matched nodeset + return this.each(function() { + + // functions and vars declared here are created for each matched element. so if + // your functions need to manage or access per-node state you can defined them + // here and use $this to get at the DOM element + + if ( jQuery(this).is('input') && 'text' == jQuery(this).attr('type')) { + + var datepicker; + jQuery.data(jQuery(this).get(0), "simpleDatepicker", { hasDatepicker : false }); + + // open a datepicker on the click event + jQuery(this).click(function (ev) { + + var $this = jQuery(ev.target); + + if (false == jQuery.data($this.get(0), "simpleDatepicker").hasDatepicker) { + + // store data telling us there is already a datepicker + jQuery.data($this.get(0), "simpleDatepicker", { hasDatepicker : true }); + + // validate the form's initial content for a date + var initialDate = $this.val(); + + if (initialDate && dateRegEx.test(initialDate)) { + var chosendate = new Date(initialDate); + } else if (opts.chosendate.constructor == Date) { + var chosendate = opts.chosendate; + } else if (opts.chosendate) { + var chosendate = new Date(opts.chosendate); + } else { + var chosendate = today; + } + + // insert the datepicker in the DOM + datepicker = newDatepickerHTML(); + jQuery("body").prepend(datepicker); + + // position the datepicker + var elPos = findPosition($this.get(0)); + var x = (parseInt(opts.x) ? parseInt(opts.x) : 0) + elPos[0]; + var y = (parseInt(opts.y) ? parseInt(opts.y) : 0) + elPos[1]; + jQuery(datepicker).css({ position: 'absolute', left: x, top: y }); + + // bind events to the table controls + jQuery("span", datepicker).css("cursor","pointer"); + jQuery("select", datepicker).bind('change', function () { loadMonth (null, $this, datepicker, chosendate); }); + jQuery("span.prevMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); + jQuery("span.nextMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); + jQuery("span.today", datepicker).click(function () { closeIt($this, datepicker, new Date()); }); + jQuery("span.close", datepicker).click(function () { closeIt($this, datepicker); }); + + // set the initial values for the month and year select fields + // and load the first month + jQuery("select[name=month]", datepicker).get(0).selectedIndex = chosendate.getMonth(); + jQuery("select[name=year]", datepicker).get(0).selectedIndex = Math.max(0, chosendate.getFullYear() - opts.startyear); + loadMonth(null, $this, datepicker, chosendate); + } + + }); + } + + }); + + }; + + // finally, I like to expose default plugin options as public so they can be manipulated. one + // way to do this is to add a property to the already-public plugin fn + + jQuery.fn.simpleDatepicker.formatOutput = function (dateObj) { + return (dateObj.getMonth() + 1) + "/" + dateObj.getDate() + "/" + dateObj.getFullYear(); + }; + + jQuery.fn.simpleDatepicker.defaults = { + // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ + chosendate : today, + + // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ + // or four digit year + startdate : today.getFullYear(), + enddate : today.getFullYear() + 1, + + // offset from the top left corner of the input element + x : 1, // must be in px + y : 18 // must be in px + }; + +})(jQuery); + +// Init the form +$(document).ready(function(){ + $('#date_first').simpleDatepicker({startdate: 2005, enddate: 2100}); + $('#date_second').simpleDatepicker({startdate: 2005, enddate: 2100}); + + $('#date_filter').change(function(){ + var show = $(this).val() == 'between' ? 'inline' : 'none'; + $('#date_second').css('display', show); + $('#date_and').css('display', show); + }); }); \ No newline at end of file diff --git a/js/jquery.tablesorter.min.js b/js/jquery.tablesorter.min.js index bbeedfe..8d647de 100644 --- a/js/jquery.tablesorter.min.js +++ b/js/jquery.tablesorter.min.js @@ -1,47 +1,47 @@ -/*! -* TableSorter 2.7.6 min - Client-side table sorting with ease! -* Copyright (c) 2007 Christian Bach -* -* https://github.com/Mottie/tablesorter/ -* Dual licensed under the MIT and GPL licenses. -*/ -!function(j){j.extend({tablesorter:new function(){function e(d){"undefined"!==typeof console&&"undefined"!==typeof console.log?console.log(d):alert(d)}function v(d,c){e(d+" ("+((new Date).getTime()-c.getTime())+"ms)")}function p(d,c,a){if(!c)return"";var b=d.config,g=b.textExtraction,f="",f="simple"===g?b.supportsTextContent?c.textContent:j(c).text():"function"===typeof g?g(c,d,a):"object"===typeof g&&g.hasOwnProperty(a)?g[a](c,d,a):b.supportsTextContent?c.textContent:j(c).text();return j.trim(f)} function h(d){var c=d.config,a=c.$tbodies=c.$table.children("tbody:not(."+c.cssInfoBlock+")"),b,q,f,l,j,n,k="";if(0===a.length)return c.debug?e("*Empty table!* Not building a parser cache"):"";a=a[0].rows;if(a[0]){b=[];q=a[0].cells.length;for(f=0;f<q;f++){l=c.$headers.filter(":not([colspan])");l=l.add(c.$headers.filter('[colspan="1"]')).filter('[data-column="'+f+'"]:last');j=c.headers[f];n=g.getParserById(g.getData(l,j,"sorter"));c.empties[f]=g.getData(l,j,"empty")||c.emptyTo||(c.emptyToBottom?"bottom": "top");c.strings[f]=g.getData(l,j,"string")||c.stringTo||"max";if(!n)a:{l=d;j=a;n=-1;for(var v=f,x=void 0,t=g.parsers.length,y=!1,m="",x=!0;""===m&&x;)n++,j[n]?(y=j[n].cells[v],m=p(l,y,v),l.config.debug&&e("Checking if value was empty on row "+n+", column: "+v+": "+m)):x=!1;for(x=1;x<t;x++)if(g.parsers[x].is&&g.parsers[x].is(m,l,y)){n=g.parsers[x];break a}n=g.parsers[0]}c.debug&&(k+="column:"+f+"; parser:"+n.id+"; string:"+c.strings[f]+"; empty: "+c.empties[f]+"\n");b.push(n)}}c.debug&&e(k);return b} function s(d){var c=d.tBodies,a=d.config,b,q,f=a.parsers,l,u,n,k,h,x,t,m=[];a.cache={};if(!f)return a.debug?e("*Empty table!* Not building a cache"):"";a.debug&&(t=new Date);a.showProcessing&&g.isProcessing(d,!0);for(k=0;k<c.length;k++)if(a.cache[k]={row:[],normalized:[]},!j(c[k]).hasClass(a.cssInfoBlock)){b=c[k]&&c[k].rows.length||0;q=c[k].rows[0]&&c[k].rows[0].cells.length||0;for(u=0;u<b;++u)if(h=j(c[k].rows[u]),x=[],h.hasClass(a.cssChildRow))a.cache[k].row[a.cache[k].row.length-1]=a.cache[k].row[a.cache[k].row.length- 1].add(h);else{a.cache[k].row.push(h);for(n=0;n<q;++n)if(l=p(d,h[0].cells[n],n),l=f[n].format(l,d,h[0].cells[n],n),x.push(l),"numeric"===(f[n].type||"").toLowerCase())m[n]=Math.max(Math.abs(l),m[n]||0);x.push(a.cache[k].normalized.length);a.cache[k].normalized.push(x)}a.cache[k].colMax=m}a.showProcessing&&g.isProcessing(d);a.debug&&v("Building cache for "+b+" rows",t)}function m(d,c){var a=d.config,b=d.tBodies,q=[],f=a.cache,e,u,n,k,h,p,m,y,s,r,E;if(f[0]){a.debug&&(E=new Date);for(y=0;y<b.length;y++)if(e= j(b[y]),!e.hasClass(a.cssInfoBlock)){h=g.processTbody(d,e,!0);e=f[y].row;u=f[y].normalized;k=(n=u.length)?u[0].length-1:0;for(p=0;p<n;p++)if(r=u[p][k],q.push(e[r]),!a.appender||!a.removeRows){s=e[r].length;for(m=0;m<s;m++)h.append(e[r][m])}g.processTbody(d,h,!1)}a.appender&&a.appender(d,q);a.debug&&v("Rebuilt table",E);c||g.applyWidget(d);j(d).trigger("sortEnd",d)}}function F(d){var c,a,b,g=d.config,f=g.sortList,e=[g.cssAsc,g.cssDesc],h=j(d).find("tfoot tr").children().removeClass(e.join(" "));g.$headers.removeClass(e.join(" ")); b=f.length;for(c=0;c<b;c++)if(2!==f[c][1]&&(d=g.$headers.not(".sorter-false").filter('[data-column="'+f[c][0]+'"]'+(1===b?":last":"")),d.length))for(a=0;a<d.length;a++)d[a].sortDisabled||(d.eq(a).addClass(e[f[c][1]]),h.length&&h.filter('[data-column="'+f[c][0]+'"]').eq(a).addClass(e[f[c][1]]))}function G(d){var c=0,a=d.config,b=a.sortList,g=b.length,f=d.tBodies.length,e,h,n,k,p,m,t,r,s;if(!a.serverSideSorting&&a.cache[0]){a.debug&&(e=new Date);for(n=0;n<f;n++)p=a.cache[n].colMax,s=(m=a.cache[n].normalized)&& m[0]?m[0].length-1:0,m.sort(function(f,e){for(h=0;h<g;h++){k=b[h][0];r=b[h][1];t=/n/i.test(a.parsers&&a.parsers[k]?a.parsers[k].type||"":"")?"Numeric":"Text";t+=0===r?"":"Desc";/Numeric/.test(t)&&a.strings[k]&&(c="boolean"===typeof a.string[a.strings[k]]?(0===r?1:-1)*(a.string[a.strings[k]]?-1:1):a.strings[k]?a.string[a.strings[k]]||0:0);var l=j.tablesorter["sort"+t](d,f[k],e[k],k,p[k],c);if(l)return l}return f[s]-e[s]});a.debug&&v("Sorting on "+b.toString()+" and dir "+r+" time",e)}}function C(d, c){d.trigger("updateComplete");"function"===typeof c&&c(d[0])}function I(d,c,a){!1!==c?d.trigger("sorton",[d[0].config.sortList,function(){C(d,a)}]):C(d,a)}var g=this;g.version="2.7.6";g.parsers=[];g.widgets=[];g.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,headers:{},ignoreCase:!0, sortForce:null,sortList:[],sortAppend:null,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"simple",textSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,initialized:null,tableClass:"tablesorter",cssAsc:"tablesorter-headerAsc",cssChildRow:"tablesorter-childRow",cssDesc:"tablesorter-headerDesc",cssHeader:"tablesorter-header",cssHeaderRow:"tablesorter-headerRow",cssIcon:"tablesorter-icon",cssInfoBlock:"tablesorter-infoOnly", cssProcessing:"tablesorter-processing",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]};g.benchmark=v;g.construct=function(d){return this.each(function(){if(!this.tHead||0===this.tBodies.length||!0===this.hasInitialized)return this.config&&this.config.debug?e("stopping initialization! No thead, tbody or tablesorter has already been initialized"):"";var c=j(this),a=this,b,q,f,l="",u,n,k,C,x=j.metadata; a.hasInitialized=!1;a.config={};b=j.extend(!0,a.config,g.defaults,d);j.data(a,"tablesorter",b);b.debug&&j.data(a,"startoveralltimer",new Date);b.supportsTextContent="x"===j("<span>x</span>")[0].textContent;b.supportsDataObject=1.4<=parseFloat(j.fn.jquery);b.string={max:1,min:-1,"max+":1,"max-":-1,zero:0,none:0,"null":0,top:!0,bottom:!1};/tablesorter\-/.test(c.attr("class"))||(l=""!==b.theme?" tablesorter-"+b.theme:"");b.$table=c.addClass(b.tableClass+l);b.$tbodies=c.children("tbody:not(."+b.cssInfoBlock+ ")");var t=[],y={},Q=0,V=j(a).find("thead:eq(0), tfoot").children("tr"),E,L,z,A,R,D,M,W,X,H;for(E=0;E<V.length;E++){R=V[E].cells;for(L=0;L<R.length;L++){A=R[L];D=A.parentNode.rowIndex;M=D+"-"+A.cellIndex;W=A.rowSpan||1;X=A.colSpan||1;"undefined"===typeof t[D]&&(t[D]=[]);for(z=0;z<t[D].length+1;z++)if("undefined"===typeof t[D][z]){H=z;break}y[M]=H;Q=Math.max(H,Q);j(A).attr({"data-column":H});for(z=D;z<D+W;z++){"undefined"===typeof t[z]&&(t[z]=[]);M=t[z];for(A=H;A<H+X;A++)M[A]="x"}}}a.config.columns= Q;var N,B,S,Y,O,J,K,w=a.config;w.headerList=[];w.headerContent=[];w.debug&&(K=new Date);Y=w.cssIcon?'<i class="'+w.cssIcon+'"></i>':"";t=j(a).find(w.selectorHeaders).each(function(a){B=j(this);N=w.headers[a];w.headerContent[a]=this.innerHTML;O=w.headerTemplate.replace(/\{content\}/g,this.innerHTML).replace(/\{icon\}/g,Y);w.onRenderTemplate&&(S=w.onRenderTemplate.apply(B,[a,O]))&&"string"===typeof S&&(O=S);this.innerHTML='<div class="tablesorter-header-inner">'+O+"</div>";w.onRenderHeader&&w.onRenderHeader.apply(B, [a]);this.column=y[this.parentNode.rowIndex+"-"+this.cellIndex];var b=g.getData(B,N,"sortInitialOrder")||w.sortInitialOrder;this.order=/^d/i.test(b)||1===b?[1,0,2]:[0,1,2];this.count=-1;"false"===g.getData(B,N,"sorter")?(this.sortDisabled=!0,B.addClass("sorter-false")):B.removeClass("sorter-false");this.lockedOrder=!1;J=g.getData(B,N,"lockedOrder")||!1;"undefined"!==typeof J&&!1!==J&&(this.order=this.lockedOrder=/^d/i.test(J)||1===J?[1,1,1]:[0,0,0]);B.addClass((this.sortDisabled?"sorter-false ":" ")+ w.cssHeader);w.headerList[a]=this;B.parent().addClass(w.cssHeaderRow)});a.config.debug&&(v("Built headers:",K),e(t));b.$headers=t;var T,P=a.config,U=j("<colgroup>");K=P.$table.find("colgroup");var Z=P.$table.width();j("tr:first td",a.tBodies[0]).each(function(){T=j("<col>");P.widthFixed&&T.css("width",parseInt(1E3*(j(this).width()/Z),10)/10+"%");U.append(T)});K.length?K.html(U.html()):P.$table.prepend(U);b.parsers=h(a);b.delayInit||s(a);b.$headers.find("*").andSelf().filter(b.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter").bind("mousedown.tablesorter mouseup.tablesorter", function(d,e){var h=(this.tagName.match("TH|TD")?j(this):j(this).parents("th, td").filter(":last"))[0];if(1!==(d.which||d.button))return!1;if("mousedown"===d.type)return C=(new Date).getTime(),"INPUT"===d.target.tagName?"":!b.cancelSelection;if(!0!==e&&250<(new Date).getTime()-C)return!1;b.delayInit&&!b.cache&&s(a);if(!h.sortDisabled){c.trigger("sortStart",a);l=!d[b.sortMultiSortKey];h.count=d[b.sortResetKey]?2:(h.count+1)%(b.sortReset?3:2);b.sortRestart&&(q=h,b.$headers.each(function(){if(this!== q&&(l||!j(this).is("."+b.cssDesc+",."+b.cssAsc)))this.count=-1}));q=h.column;if(l){b.sortList=[];if(null!==b.sortForce){u=b.sortForce;for(f=0;f<u.length;f++)u[f][0]!==q&&b.sortList.push(u[f])}k=h.order[h.count];if(2>k&&(b.sortList.push([q,k]),1<h.colSpan))for(f=1;f<h.colSpan;f++)b.sortList.push([q+f,k])}else if(b.sortAppend&&1<b.sortList.length&&g.isValueInArray(b.sortAppend[0][0],b.sortList)&&b.sortList.pop(),g.isValueInArray(q,b.sortList))for(f=0;f<b.sortList.length;f++)n=b.sortList[f],k=b.headerList[n[0]], n[0]===q&&(n[1]=k.order[k.count],2===n[1]&&(b.sortList.splice(f,1),k.count=-1));else if(k=h.order[h.count],2>k&&(b.sortList.push([q,k]),1<h.colSpan))for(f=1;f<h.colSpan;f++)b.sortList.push([q+f,k]);if(null!==b.sortAppend){u=b.sortAppend;for(f=0;f<u.length;f++)u[f][0]!==q&&b.sortList.push(u[f])}c.trigger("sortBegin",a);setTimeout(function(){F(a);G(a);m(a)},1)}});b.cancelSelection&&b.$headers.each(function(){this.onselectstart=function(){return!1}});c.unbind("sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave").bind("sortReset", function(){b.sortList=[];F(a);G(a);m(a)}).bind("update updateRows",function(d,f,g){j(b.selectorRemove,a).remove();b.parsers=h(a);s(a);I(c,f,g)}).bind("updateCell",function(d,f,g,e){var q,h,l;q=c.find("tbody");d=q.index(j(f).parents("tbody").filter(":last"));var k=j(f).parents("tr").filter(":last");f=j(f)[0];q.length&&0<=d&&(h=q.eq(d).find("tr").index(k),l=f.cellIndex,q=a.config.cache[d].normalized[h].length-1,a.config.cache[d].row[a.config.cache[d].normalized[h][q]]=k,a.config.cache[d].normalized[h][l]= b.parsers[l].format(p(a,f,l),a,f,l),I(c,g,e))}).bind("addRows",function(d,g,e,q){var l=g.filter("tr").length,j=[],k=g[0].cells.length,n=c.find("tbody").index(g.closest("tbody"));b.parsers||(b.parsers=h(a));for(d=0;d<l;d++){for(f=0;f<k;f++)j[f]=b.parsers[f].format(p(a,g[d].cells[f],f),a,g[d].cells[f],f);j.push(b.cache[n].row.length);b.cache[n].row.push([g[d]]);b.cache[n].normalized.push(j);j=[]}I(c,e,q)}).bind("sorton",function(b,d,f,g){c.trigger("sortStart",this);var e,q,h,l=a.config;b=d||l.sortList; l.sortList=[];j.each(b,function(a,b){e=[parseInt(b[0],10),parseInt(b[1],10)];if(h=l.headerList[e[0]])l.sortList.push(e),q=j.inArray(e[1],h.order),h.count=0<=q?q:e[1]%(l.sortReset?3:2)});F(a);G(a);m(a,g);"function"===typeof f&&f(a)}).bind("appendCache",function(b,c,d){m(a,d);"function"===typeof c&&c(a)}).bind("applyWidgetId",function(c,d){g.getWidgetById(d).format(a,b,b.widgetOptions)}).bind("applyWidgets",function(b,c){g.applyWidget(a,c)}).bind("refreshWidgets",function(b,c,d){g.refreshWidgets(a, c,d)}).bind("destroy",function(b,c,d){g.destroy(a,c,d)});b.supportsDataObject&&"undefined"!==typeof c.data().sortlist?b.sortList=c.data().sortlist:x&&(c.metadata()&&c.metadata().sortlist)&&(b.sortList=c.metadata().sortlist);g.applyWidget(a,!0);0<b.sortList.length?c.trigger("sorton",[b.sortList,{},!b.initWidgets]):b.initWidgets&&g.applyWidget(a);b.showProcessing&&c.unbind("sortBegin sortEnd").bind("sortBegin sortEnd",function(b){g.isProcessing(a,"sortBegin"===b.type)});a.hasInitialized=!0;b.debug&& g.benchmark("Overall initialization time",j.data(a,"startoveralltimer"));c.trigger("tablesorter-initialized",a);"function"===typeof b.initialized&&b.initialized(a)})};g.isProcessing=function(d,c,a){var b=d.config;d=a||j(d).find("."+b.cssHeader);c?(0<b.sortList.length&&(d=d.filter(function(){return this.sortDisabled?!1:g.isValueInArray(parseFloat(j(this).attr("data-column")),b.sortList)})),d.addClass(b.cssProcessing)):d.removeClass(b.cssProcessing)};g.processTbody=function(d,c,a){if(a)return c.before('<span class="tablesorter-savemyplace"/>'), d=j.fn.detach?c.detach():c.remove();d=j(d).find("span.tablesorter-savemyplace");c.insertAfter(d);d.remove()};g.clearTableBody=function(d){d.config.$tbodies.empty()};g.destroy=function(d,c,a){if(d.hasInitialized){g.refreshWidgets(d,!0,!0);var b=j(d),e=d.config,f=b.find("thead:first"),l=f.find("tr."+e.cssHeaderRow).removeClass(e.cssHeaderRow),h=b.find("tfoot:first > tr").children("th, td");f.find("tr").not(l).remove();b.removeData("tablesorter").unbind("sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave"); e.$headers.add(h).removeClass(e.cssHeader+" "+e.cssAsc+" "+e.cssDesc).removeAttr("data-column");l.find(e.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter");l.children().each(function(a){j(this).html(e.headerContent[a])});!1!==c&&b.removeClass(e.tableClass+" tablesorter-"+e.theme);d.hasInitialized=!1;"function"===typeof a&&a(d)}};g.regex=[/(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, /^0x[0-9a-f]+$/i];g.sortText=function(d,c,a,b){if(c===a)return 0;var e=d.config,f=e.string[e.empties[b]||e.emptyTo],h=g.regex;if(""===c&&0!==f)return"boolean"===typeof f?f?-1:1:-f||-1;if(""===a&&0!==f)return"boolean"===typeof f?f?1:-1:f||1;if("function"===typeof e.textSorter)return e.textSorter(c,a,d,b);d=c.replace(h[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");b=a.replace(h[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");c=parseInt(c.match(h[2]),16)||1!==d.length&& c.match(h[1])&&Date.parse(c);if(a=parseInt(a.match(h[2]),16)||c&&a.match(h[1])&&Date.parse(a)||null){if(c<a)return-1;if(c>a)return 1}e=Math.max(d.length,b.length);for(c=0;c<e;c++){a=isNaN(d[c])?d[c]||0:parseFloat(d[c])||0;h=isNaN(b[c])?b[c]||0:parseFloat(b[c])||0;if(isNaN(a)!==isNaN(h))return isNaN(a)?1:-1;typeof a!==typeof h&&(a+="",h+="");if(a<h)return-1;if(a>h)return 1}return 0};g.sortTextDesc=function(d,c,a,b){if(c===a)return 0;var e=d.config,f=e.string[e.empties[b]||e.emptyTo];return""===c&& 0!==f?"boolean"===typeof f?f?-1:1:f||1:""===a&&0!==f?"boolean"===typeof f?f?1:-1:-f||-1:"function"===typeof e.textSorter?e.textSorter(a,c,d,b):g.sortText(d,a,c)};g.getTextValue=function(d,c,a){if(c){var b=d.length,e=c+a;for(c=0;c<b;c++)e+=d.charCodeAt(c);return a*e}return 0};g.sortNumeric=function(d,c,a,b,e,f){if(c===a)return 0;d=d.config;b=d.string[d.empties[b]||d.emptyTo];if(""===c&&0!==b)return"boolean"===typeof b?b?-1:1:-b||-1;if(""===a&&0!==b)return"boolean"===typeof b?b?1:-1:b||1;isNaN(c)&& (c=g.getTextValue(c,e,f));isNaN(a)&&(a=g.getTextValue(a,e,f));return c-a};g.sortNumericDesc=function(d,c,a,b,e,f){if(c===a)return 0;d=d.config;b=d.string[d.empties[b]||d.emptyTo];if(""===c&&0!==b)return"boolean"===typeof b?b?-1:1:b||1;if(""===a&&0!==b)return"boolean"===typeof b?b?1:-1:-b||-1;isNaN(c)&&(c=g.getTextValue(c,e,f));isNaN(a)&&(a=g.getTextValue(a,e,f));return a-c};g.characterEquivalents={a:"\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5",A:"\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5",c:"\u00e7\u0107\u010d", C:"\u00c7\u0106\u010c",e:"\u00e9\u00e8\u00ea\u00eb\u011b\u0119",E:"\u00c9\u00c8\u00ca\u00cb\u011a\u0118",i:"\u00ed\u00ec\u0130\u00ee\u00ef\u0131",I:"\u00cd\u00cc\u0130\u00ce\u00cf",o:"\u00f3\u00f2\u00f4\u00f5\u00f6",O:"\u00d3\u00d2\u00d4\u00d5\u00d6",ss:"\u00df",SS:"\u1e9e",u:"\u00fa\u00f9\u00fb\u00fc\u016f",U:"\u00da\u00d9\u00db\u00dc\u016e"};g.replaceAccents=function(d){var c,a="[",b=g.characterEquivalents;if(!g.characterRegex){g.characterRegexArray={};for(c in b)"string"===typeof c&&(a+=b[c],g.characterRegexArray[c]= RegExp("["+b[c]+"]","g"));g.characterRegex=RegExp(a+"]")}if(g.characterRegex.test(d))for(c in b)"string"===typeof c&&(d=d.replace(g.characterRegexArray[c],c));return d};g.isValueInArray=function(d,c){var a,b=c.length;for(a=0;a<b;a++)if(c[a][0]===d)return!0;return!1};g.addParser=function(d){var c,a=g.parsers.length,b=!0;for(c=0;c<a;c++)g.parsers[c].id.toLowerCase()===d.id.toLowerCase()&&(b=!1);b&&g.parsers.push(d)};g.getParserById=function(d){var c,a=g.parsers.length;for(c=0;c<a;c++)if(g.parsers[c].id.toLowerCase()=== d.toString().toLowerCase())return g.parsers[c];return!1};g.addWidget=function(d){g.widgets.push(d)};g.getWidgetById=function(d){var c,a,b=g.widgets.length;for(c=0;c<b;c++)if((a=g.widgets[c])&&a.hasOwnProperty("id")&&a.id.toLowerCase()===d.toLowerCase())return a};g.applyWidget=function(d,c){var a=d.config,b=a.widgetOptions,e=a.widgets.sort().reverse(),f,h,m,n=e.length;h=j.inArray("zebra",a.widgets);0<=h&&(a.widgets.splice(h,1),a.widgets.push("zebra"));a.debug&&(f=new Date);for(h=0;h<n;h++)(m=g.getWidgetById(e[h]))&& (!0===c&&m.hasOwnProperty("init")?m.init(d,m,a,b):!c&&m.hasOwnProperty("format")&&m.format(d,a,b));a.debug&&v("Completed "+(!0===c?"initializing":"applying")+" widgets",f)};g.refreshWidgets=function(d,c,a){var b,h=d.config,f=h.widgets,l=g.widgets,m=l.length;for(b=0;b<m;b++)if(l[b]&&l[b].id&&(c||0>j.inArray(l[b].id,f)))h.debug&&e("Refeshing widgets: Removing "+l[b].id),l[b].hasOwnProperty("remove")&&l[b].remove(d,h,h.widgetOptions);!0!==a&&g.applyWidget(d,c)};g.getData=function(d,c,a){var b="";d=j(d); var e,f;if(!d.length)return"";e=j.metadata?d.metadata():!1;f=" "+(d.attr("class")||"");"undefined"!==typeof d.data(a)||"undefined"!==typeof d.data(a.toLowerCase())?b+=d.data(a)||d.data(a.toLowerCase()):e&&"undefined"!==typeof e[a]?b+=e[a]:c&&"undefined"!==typeof c[a]?b+=c[a]:" "!==f&&f.match(" "+a+"-")&&(b=f.match(RegExp(" "+a+"-(\\w+)"))[1]||"");return j.trim(b)};g.formatFloat=function(d,c){if("string"!==typeof d||""===d)return d;var a;d=(c&&c.config?!1!==c.config.usNumberFormat:"undefined"!==typeof c? c:1)?d.replace(/,/g,""):d.replace(/[\s|\.]/g,"").replace(/,/g,".");/^\s*\([.\d]+\)/.test(d)&&(d=d.replace(/^\s*\(/,"-").replace(/\)/,""));a=parseFloat(d);return isNaN(a)?j.trim(d):a};g.isDigit=function(d){return isNaN(d)?/^[\-+(]?\d+[)]?$/.test(d.toString().replace(/[,.'"\s]/g,"")):!0}}});var h=j.tablesorter;j.fn.extend({tablesorter:h.construct});h.addParser({id:"text",is:function(){return!0},format:function(e,v){var p=v.config;e=j.trim(p.ignoreCase?e.toLocaleLowerCase():e);return p.sortLocaleCompare? h.replaceAccents(e):e},type:"text"});h.addParser({id:"currency",is:function(e){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test(e)},format:function(e,j){return h.formatFloat(e.replace(/[^\w,. \-()]/g,""),j)},type:"numeric"});h.addParser({id:"ipAddress",is:function(e){return/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(e)},format:function(e,j){var p,r=e.split("."),s="",m=r.length;for(p=0;p<m;p++)s+=("00"+r[p]).slice(-3);return h.formatFloat(s,j)}, type:"numeric"});h.addParser({id:"url",is:function(e){return/^(https?|ftp|file):\/\//.test(e)},format:function(e){return j.trim(e.replace(/(https?|ftp|file):\/\//,""))},type:"text"});h.addParser({id:"isoDate",is:function(e){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(e)},format:function(e,j){return h.formatFloat(""!==e?(new Date(e.replace(/-/g,"/"))).getTime()||"":"",j)},type:"numeric"});h.addParser({id:"percent",is:function(e){return/(\d\s?%|%\s?\d)/.test(e)},format:function(e,j){return h.formatFloat(e.replace(/%/g, ""),j)},type:"numeric"});h.addParser({id:"usLongDate",is:function(e){return/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i.test(e)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(e)},format:function(e,j){return h.formatFloat((new Date(e.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",j)},type:"numeric"});h.addParser({id:"shortDate",is:function(e){return/^(\d{1,2}|\d{4})[\/\-\,\.\s+]\d{1,2}[\/\-\.\,\s+](\d{1,2}|\d{4})$/.test(e)},format:function(e,j,p,r){p=j.config;var s=p.headerList[r], m=s.shortDateFormat;"undefined"===typeof m&&(m=s.shortDateFormat=h.getData(s,p.headers[r],"dateFormat")||p.dateFormat);e=e.replace(/\s+/g," ").replace(/[\-|\.|\,]/g,"/");"mmddyyyy"===m?e=e.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===m?e=e.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===m&&(e=e.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3"));return h.formatFloat((new Date(e)).getTime()||"",j)},type:"numeric"});h.addParser({id:"time", is:function(e){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(e)},format:function(e,j){return h.formatFloat((new Date("2000/01/01 "+e.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",j)},type:"numeric"});h.addParser({id:"digit",is:function(e){return h.isDigit(e)},format:function(e,j){return h.formatFloat(e.replace(/[^\w,. \-()]/g,""),j)},type:"numeric"});h.addParser({id:"metadata",is:function(){return!1},format:function(e,h,p){e=h.config;e=!e.parserMetadataName?"sortValue":e.parserMetadataName; return j(p).metadata()[e]},type:"numeric"});h.addWidget({id:"zebra",format:function(e,v,p){var r,s,m,F,G,C,I=RegExp(v.cssChildRow,"i"),g=v.$tbodies;v.debug&&(G=new Date);for(e=0;e<g.length;e++)r=g.eq(e),C=r.children("tr").length,1<C&&(m=0,r=r.children("tr:visible"),r.each(function(){s=j(this);I.test(this.className)||m++;F=0===m%2;s.removeClass(p.zebra[F?1:0]).addClass(p.zebra[F?0:1])}));v.debug&&h.benchmark("Applying Zebra widget",G)},remove:function(e,h){var p,r,s=h.$tbodies,m=(h.widgetOptions.zebra|| ["even","odd"]).join(" ");for(p=0;p<s.length;p++)r=j.tablesorter.processTbody(e,s.eq(p),!0),r.children().removeClass(m),j.tablesorter.processTbody(e,r,!1)}})}(jQuery); - -var yourls_defaultsort = 2; // default column to sort on (overwrite this inline in page) -var yourls_defaultorder = 1; // default order ('asc':0, 'desc':1) to sort on (overwrite this inline in page) - -// Initialise the table to sort -$(document).ready(function(){ - if ($("#main_table").tablesorter && $("#main_table tr#nourl_found").css('display') == 'none') { - var order = {'keyword':0, 'url':1, 'timestamp':2, 'ip':3, 'clicks':4}; - var order_by = {'asc':0, 'desc':1}; - var sort_by = order[query_string('sort_by')]; - var sort_order = order_by[query_string('sort_order')]; - if( sort_by == undefined ) { - sort_by = yourls_defaultsort; - sort_order = yourls_defaultorder; - } - - $("#main_table").tablesorter({ - textExtraction: { - 1: function(node, table, cellIndex){return $(node).find("small a").text();} // Sort column "URL" by URL, not by whole cell content - }, - sortList:[[ sort_by, sort_order ]], - headers: { 5: {sorter: false} }, // no sorter on column "Actions" - widgets: ['zebra'], // prettify, see tr.normal-row and tr.alt-row in tablesorter.css - widgetOptions : { zebra : [ "normal-row", "alt-row" ] } - }); - } -}); - -// Get query string -function query_string( key ) { - default_=""; - key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); - var regex = new RegExp("[\\?&]"+key+"=([^&#]*)"); - var qs = regex.exec(window.location.href); - if(qs == null) - return yourls_defaultsort; - else - return qs[1]; +/*! +* TableSorter 2.7.6 min - Client-side table sorting with ease! +* Copyright (c) 2007 Christian Bach +* +* https://github.com/Mottie/tablesorter/ +* Dual licensed under the MIT and GPL licenses. +*/ +!function(j){j.extend({tablesorter:new function(){function e(d){"undefined"!==typeof console&&"undefined"!==typeof console.log?console.log(d):alert(d)}function v(d,c){e(d+" ("+((new Date).getTime()-c.getTime())+"ms)")}function p(d,c,a){if(!c)return"";var b=d.config,g=b.textExtraction,f="",f="simple"===g?b.supportsTextContent?c.textContent:j(c).text():"function"===typeof g?g(c,d,a):"object"===typeof g&&g.hasOwnProperty(a)?g[a](c,d,a):b.supportsTextContent?c.textContent:j(c).text();return j.trim(f)} function h(d){var c=d.config,a=c.$tbodies=c.$table.children("tbody:not(."+c.cssInfoBlock+")"),b,q,f,l,j,n,k="";if(0===a.length)return c.debug?e("*Empty table!* Not building a parser cache"):"";a=a[0].rows;if(a[0]){b=[];q=a[0].cells.length;for(f=0;f<q;f++){l=c.$headers.filter(":not([colspan])");l=l.add(c.$headers.filter('[colspan="1"]')).filter('[data-column="'+f+'"]:last');j=c.headers[f];n=g.getParserById(g.getData(l,j,"sorter"));c.empties[f]=g.getData(l,j,"empty")||c.emptyTo||(c.emptyToBottom?"bottom": "top");c.strings[f]=g.getData(l,j,"string")||c.stringTo||"max";if(!n)a:{l=d;j=a;n=-1;for(var v=f,x=void 0,t=g.parsers.length,y=!1,m="",x=!0;""===m&&x;)n++,j[n]?(y=j[n].cells[v],m=p(l,y,v),l.config.debug&&e("Checking if value was empty on row "+n+", column: "+v+": "+m)):x=!1;for(x=1;x<t;x++)if(g.parsers[x].is&&g.parsers[x].is(m,l,y)){n=g.parsers[x];break a}n=g.parsers[0]}c.debug&&(k+="column:"+f+"; parser:"+n.id+"; string:"+c.strings[f]+"; empty: "+c.empties[f]+"\n");b.push(n)}}c.debug&&e(k);return b} function s(d){var c=d.tBodies,a=d.config,b,q,f=a.parsers,l,u,n,k,h,x,t,m=[];a.cache={};if(!f)return a.debug?e("*Empty table!* Not building a cache"):"";a.debug&&(t=new Date);a.showProcessing&&g.isProcessing(d,!0);for(k=0;k<c.length;k++)if(a.cache[k]={row:[],normalized:[]},!j(c[k]).hasClass(a.cssInfoBlock)){b=c[k]&&c[k].rows.length||0;q=c[k].rows[0]&&c[k].rows[0].cells.length||0;for(u=0;u<b;++u)if(h=j(c[k].rows[u]),x=[],h.hasClass(a.cssChildRow))a.cache[k].row[a.cache[k].row.length-1]=a.cache[k].row[a.cache[k].row.length- 1].add(h);else{a.cache[k].row.push(h);for(n=0;n<q;++n)if(l=p(d,h[0].cells[n],n),l=f[n].format(l,d,h[0].cells[n],n),x.push(l),"numeric"===(f[n].type||"").toLowerCase())m[n]=Math.max(Math.abs(l),m[n]||0);x.push(a.cache[k].normalized.length);a.cache[k].normalized.push(x)}a.cache[k].colMax=m}a.showProcessing&&g.isProcessing(d);a.debug&&v("Building cache for "+b+" rows",t)}function m(d,c){var a=d.config,b=d.tBodies,q=[],f=a.cache,e,u,n,k,h,p,m,y,s,r,E;if(f[0]){a.debug&&(E=new Date);for(y=0;y<b.length;y++)if(e= j(b[y]),!e.hasClass(a.cssInfoBlock)){h=g.processTbody(d,e,!0);e=f[y].row;u=f[y].normalized;k=(n=u.length)?u[0].length-1:0;for(p=0;p<n;p++)if(r=u[p][k],q.push(e[r]),!a.appender||!a.removeRows){s=e[r].length;for(m=0;m<s;m++)h.append(e[r][m])}g.processTbody(d,h,!1)}a.appender&&a.appender(d,q);a.debug&&v("Rebuilt table",E);c||g.applyWidget(d);j(d).trigger("sortEnd",d)}}function F(d){var c,a,b,g=d.config,f=g.sortList,e=[g.cssAsc,g.cssDesc],h=j(d).find("tfoot tr").children().removeClass(e.join(" "));g.$headers.removeClass(e.join(" ")); b=f.length;for(c=0;c<b;c++)if(2!==f[c][1]&&(d=g.$headers.not(".sorter-false").filter('[data-column="'+f[c][0]+'"]'+(1===b?":last":"")),d.length))for(a=0;a<d.length;a++)d[a].sortDisabled||(d.eq(a).addClass(e[f[c][1]]),h.length&&h.filter('[data-column="'+f[c][0]+'"]').eq(a).addClass(e[f[c][1]]))}function G(d){var c=0,a=d.config,b=a.sortList,g=b.length,f=d.tBodies.length,e,h,n,k,p,m,t,r,s;if(!a.serverSideSorting&&a.cache[0]){a.debug&&(e=new Date);for(n=0;n<f;n++)p=a.cache[n].colMax,s=(m=a.cache[n].normalized)&& m[0]?m[0].length-1:0,m.sort(function(f,e){for(h=0;h<g;h++){k=b[h][0];r=b[h][1];t=/n/i.test(a.parsers&&a.parsers[k]?a.parsers[k].type||"":"")?"Numeric":"Text";t+=0===r?"":"Desc";/Numeric/.test(t)&&a.strings[k]&&(c="boolean"===typeof a.string[a.strings[k]]?(0===r?1:-1)*(a.string[a.strings[k]]?-1:1):a.strings[k]?a.string[a.strings[k]]||0:0);var l=j.tablesorter["sort"+t](d,f[k],e[k],k,p[k],c);if(l)return l}return f[s]-e[s]});a.debug&&v("Sorting on "+b.toString()+" and dir "+r+" time",e)}}function C(d, c){d.trigger("updateComplete");"function"===typeof c&&c(d[0])}function I(d,c,a){!1!==c?d.trigger("sorton",[d[0].config.sortList,function(){C(d,a)}]):C(d,a)}var g=this;g.version="2.7.6";g.parsers=[];g.widgets=[];g.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,headers:{},ignoreCase:!0, sortForce:null,sortList:[],sortAppend:null,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"simple",textSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,initialized:null,tableClass:"tablesorter",cssAsc:"tablesorter-headerAsc",cssChildRow:"tablesorter-childRow",cssDesc:"tablesorter-headerDesc",cssHeader:"tablesorter-header",cssHeaderRow:"tablesorter-headerRow",cssIcon:"tablesorter-icon",cssInfoBlock:"tablesorter-infoOnly", cssProcessing:"tablesorter-processing",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]};g.benchmark=v;g.construct=function(d){return this.each(function(){if(!this.tHead||0===this.tBodies.length||!0===this.hasInitialized)return this.config&&this.config.debug?e("stopping initialization! No thead, tbody or tablesorter has already been initialized"):"";var c=j(this),a=this,b,q,f,l="",u,n,k,C,x=j.metadata; a.hasInitialized=!1;a.config={};b=j.extend(!0,a.config,g.defaults,d);j.data(a,"tablesorter",b);b.debug&&j.data(a,"startoveralltimer",new Date);b.supportsTextContent="x"===j("<span>x</span>")[0].textContent;b.supportsDataObject=1.4<=parseFloat(j.fn.jquery);b.string={max:1,min:-1,"max+":1,"max-":-1,zero:0,none:0,"null":0,top:!0,bottom:!1};/tablesorter\-/.test(c.attr("class"))||(l=""!==b.theme?" tablesorter-"+b.theme:"");b.$table=c.addClass(b.tableClass+l);b.$tbodies=c.children("tbody:not(."+b.cssInfoBlock+ ")");var t=[],y={},Q=0,V=j(a).find("thead:eq(0), tfoot").children("tr"),E,L,z,A,R,D,M,W,X,H;for(E=0;E<V.length;E++){R=V[E].cells;for(L=0;L<R.length;L++){A=R[L];D=A.parentNode.rowIndex;M=D+"-"+A.cellIndex;W=A.rowSpan||1;X=A.colSpan||1;"undefined"===typeof t[D]&&(t[D]=[]);for(z=0;z<t[D].length+1;z++)if("undefined"===typeof t[D][z]){H=z;break}y[M]=H;Q=Math.max(H,Q);j(A).attr({"data-column":H});for(z=D;z<D+W;z++){"undefined"===typeof t[z]&&(t[z]=[]);M=t[z];for(A=H;A<H+X;A++)M[A]="x"}}}a.config.columns= Q;var N,B,S,Y,O,J,K,w=a.config;w.headerList=[];w.headerContent=[];w.debug&&(K=new Date);Y=w.cssIcon?'<i class="'+w.cssIcon+'"></i>':"";t=j(a).find(w.selectorHeaders).each(function(a){B=j(this);N=w.headers[a];w.headerContent[a]=this.innerHTML;O=w.headerTemplate.replace(/\{content\}/g,this.innerHTML).replace(/\{icon\}/g,Y);w.onRenderTemplate&&(S=w.onRenderTemplate.apply(B,[a,O]))&&"string"===typeof S&&(O=S);this.innerHTML='<div class="tablesorter-header-inner">'+O+"</div>";w.onRenderHeader&&w.onRenderHeader.apply(B, [a]);this.column=y[this.parentNode.rowIndex+"-"+this.cellIndex];var b=g.getData(B,N,"sortInitialOrder")||w.sortInitialOrder;this.order=/^d/i.test(b)||1===b?[1,0,2]:[0,1,2];this.count=-1;"false"===g.getData(B,N,"sorter")?(this.sortDisabled=!0,B.addClass("sorter-false")):B.removeClass("sorter-false");this.lockedOrder=!1;J=g.getData(B,N,"lockedOrder")||!1;"undefined"!==typeof J&&!1!==J&&(this.order=this.lockedOrder=/^d/i.test(J)||1===J?[1,1,1]:[0,0,0]);B.addClass((this.sortDisabled?"sorter-false ":" ")+ w.cssHeader);w.headerList[a]=this;B.parent().addClass(w.cssHeaderRow)});a.config.debug&&(v("Built headers:",K),e(t));b.$headers=t;var T,P=a.config,U=j("<colgroup>");K=P.$table.find("colgroup");var Z=P.$table.width();j("tr:first td",a.tBodies[0]).each(function(){T=j("<col>");P.widthFixed&&T.css("width",parseInt(1E3*(j(this).width()/Z),10)/10+"%");U.append(T)});K.length?K.html(U.html()):P.$table.prepend(U);b.parsers=h(a);b.delayInit||s(a);b.$headers.find("*").andSelf().filter(b.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter").bind("mousedown.tablesorter mouseup.tablesorter", function(d,e){var h=(this.tagName.match("TH|TD")?j(this):j(this).parents("th, td").filter(":last"))[0];if(1!==(d.which||d.button))return!1;if("mousedown"===d.type)return C=(new Date).getTime(),"INPUT"===d.target.tagName?"":!b.cancelSelection;if(!0!==e&&250<(new Date).getTime()-C)return!1;b.delayInit&&!b.cache&&s(a);if(!h.sortDisabled){c.trigger("sortStart",a);l=!d[b.sortMultiSortKey];h.count=d[b.sortResetKey]?2:(h.count+1)%(b.sortReset?3:2);b.sortRestart&&(q=h,b.$headers.each(function(){if(this!== q&&(l||!j(this).is("."+b.cssDesc+",."+b.cssAsc)))this.count=-1}));q=h.column;if(l){b.sortList=[];if(null!==b.sortForce){u=b.sortForce;for(f=0;f<u.length;f++)u[f][0]!==q&&b.sortList.push(u[f])}k=h.order[h.count];if(2>k&&(b.sortList.push([q,k]),1<h.colSpan))for(f=1;f<h.colSpan;f++)b.sortList.push([q+f,k])}else if(b.sortAppend&&1<b.sortList.length&&g.isValueInArray(b.sortAppend[0][0],b.sortList)&&b.sortList.pop(),g.isValueInArray(q,b.sortList))for(f=0;f<b.sortList.length;f++)n=b.sortList[f],k=b.headerList[n[0]], n[0]===q&&(n[1]=k.order[k.count],2===n[1]&&(b.sortList.splice(f,1),k.count=-1));else if(k=h.order[h.count],2>k&&(b.sortList.push([q,k]),1<h.colSpan))for(f=1;f<h.colSpan;f++)b.sortList.push([q+f,k]);if(null!==b.sortAppend){u=b.sortAppend;for(f=0;f<u.length;f++)u[f][0]!==q&&b.sortList.push(u[f])}c.trigger("sortBegin",a);setTimeout(function(){F(a);G(a);m(a)},1)}});b.cancelSelection&&b.$headers.each(function(){this.onselectstart=function(){return!1}});c.unbind("sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave").bind("sortReset", function(){b.sortList=[];F(a);G(a);m(a)}).bind("update updateRows",function(d,f,g){j(b.selectorRemove,a).remove();b.parsers=h(a);s(a);I(c,f,g)}).bind("updateCell",function(d,f,g,e){var q,h,l;q=c.find("tbody");d=q.index(j(f).parents("tbody").filter(":last"));var k=j(f).parents("tr").filter(":last");f=j(f)[0];q.length&&0<=d&&(h=q.eq(d).find("tr").index(k),l=f.cellIndex,q=a.config.cache[d].normalized[h].length-1,a.config.cache[d].row[a.config.cache[d].normalized[h][q]]=k,a.config.cache[d].normalized[h][l]= b.parsers[l].format(p(a,f,l),a,f,l),I(c,g,e))}).bind("addRows",function(d,g,e,q){var l=g.filter("tr").length,j=[],k=g[0].cells.length,n=c.find("tbody").index(g.closest("tbody"));b.parsers||(b.parsers=h(a));for(d=0;d<l;d++){for(f=0;f<k;f++)j[f]=b.parsers[f].format(p(a,g[d].cells[f],f),a,g[d].cells[f],f);j.push(b.cache[n].row.length);b.cache[n].row.push([g[d]]);b.cache[n].normalized.push(j);j=[]}I(c,e,q)}).bind("sorton",function(b,d,f,g){c.trigger("sortStart",this);var e,q,h,l=a.config;b=d||l.sortList; l.sortList=[];j.each(b,function(a,b){e=[parseInt(b[0],10),parseInt(b[1],10)];if(h=l.headerList[e[0]])l.sortList.push(e),q=j.inArray(e[1],h.order),h.count=0<=q?q:e[1]%(l.sortReset?3:2)});F(a);G(a);m(a,g);"function"===typeof f&&f(a)}).bind("appendCache",function(b,c,d){m(a,d);"function"===typeof c&&c(a)}).bind("applyWidgetId",function(c,d){g.getWidgetById(d).format(a,b,b.widgetOptions)}).bind("applyWidgets",function(b,c){g.applyWidget(a,c)}).bind("refreshWidgets",function(b,c,d){g.refreshWidgets(a, c,d)}).bind("destroy",function(b,c,d){g.destroy(a,c,d)});b.supportsDataObject&&"undefined"!==typeof c.data().sortlist?b.sortList=c.data().sortlist:x&&(c.metadata()&&c.metadata().sortlist)&&(b.sortList=c.metadata().sortlist);g.applyWidget(a,!0);0<b.sortList.length?c.trigger("sorton",[b.sortList,{},!b.initWidgets]):b.initWidgets&&g.applyWidget(a);b.showProcessing&&c.unbind("sortBegin sortEnd").bind("sortBegin sortEnd",function(b){g.isProcessing(a,"sortBegin"===b.type)});a.hasInitialized=!0;b.debug&& g.benchmark("Overall initialization time",j.data(a,"startoveralltimer"));c.trigger("tablesorter-initialized",a);"function"===typeof b.initialized&&b.initialized(a)})};g.isProcessing=function(d,c,a){var b=d.config;d=a||j(d).find("."+b.cssHeader);c?(0<b.sortList.length&&(d=d.filter(function(){return this.sortDisabled?!1:g.isValueInArray(parseFloat(j(this).attr("data-column")),b.sortList)})),d.addClass(b.cssProcessing)):d.removeClass(b.cssProcessing)};g.processTbody=function(d,c,a){if(a)return c.before('<span class="tablesorter-savemyplace"/>'), d=j.fn.detach?c.detach():c.remove();d=j(d).find("span.tablesorter-savemyplace");c.insertAfter(d);d.remove()};g.clearTableBody=function(d){d.config.$tbodies.empty()};g.destroy=function(d,c,a){if(d.hasInitialized){g.refreshWidgets(d,!0,!0);var b=j(d),e=d.config,f=b.find("thead:first"),l=f.find("tr."+e.cssHeaderRow).removeClass(e.cssHeaderRow),h=b.find("tfoot:first > tr").children("th, td");f.find("tr").not(l).remove();b.removeData("tablesorter").unbind("sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave"); e.$headers.add(h).removeClass(e.cssHeader+" "+e.cssAsc+" "+e.cssDesc).removeAttr("data-column");l.find(e.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter");l.children().each(function(a){j(this).html(e.headerContent[a])});!1!==c&&b.removeClass(e.tableClass+" tablesorter-"+e.theme);d.hasInitialized=!1;"function"===typeof a&&a(d)}};g.regex=[/(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, /^0x[0-9a-f]+$/i];g.sortText=function(d,c,a,b){if(c===a)return 0;var e=d.config,f=e.string[e.empties[b]||e.emptyTo],h=g.regex;if(""===c&&0!==f)return"boolean"===typeof f?f?-1:1:-f||-1;if(""===a&&0!==f)return"boolean"===typeof f?f?1:-1:f||1;if("function"===typeof e.textSorter)return e.textSorter(c,a,d,b);d=c.replace(h[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");b=a.replace(h[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");c=parseInt(c.match(h[2]),16)||1!==d.length&& c.match(h[1])&&Date.parse(c);if(a=parseInt(a.match(h[2]),16)||c&&a.match(h[1])&&Date.parse(a)||null){if(c<a)return-1;if(c>a)return 1}e=Math.max(d.length,b.length);for(c=0;c<e;c++){a=isNaN(d[c])?d[c]||0:parseFloat(d[c])||0;h=isNaN(b[c])?b[c]||0:parseFloat(b[c])||0;if(isNaN(a)!==isNaN(h))return isNaN(a)?1:-1;typeof a!==typeof h&&(a+="",h+="");if(a<h)return-1;if(a>h)return 1}return 0};g.sortTextDesc=function(d,c,a,b){if(c===a)return 0;var e=d.config,f=e.string[e.empties[b]||e.emptyTo];return""===c&& 0!==f?"boolean"===typeof f?f?-1:1:f||1:""===a&&0!==f?"boolean"===typeof f?f?1:-1:-f||-1:"function"===typeof e.textSorter?e.textSorter(a,c,d,b):g.sortText(d,a,c)};g.getTextValue=function(d,c,a){if(c){var b=d.length,e=c+a;for(c=0;c<b;c++)e+=d.charCodeAt(c);return a*e}return 0};g.sortNumeric=function(d,c,a,b,e,f){if(c===a)return 0;d=d.config;b=d.string[d.empties[b]||d.emptyTo];if(""===c&&0!==b)return"boolean"===typeof b?b?-1:1:-b||-1;if(""===a&&0!==b)return"boolean"===typeof b?b?1:-1:b||1;isNaN(c)&& (c=g.getTextValue(c,e,f));isNaN(a)&&(a=g.getTextValue(a,e,f));return c-a};g.sortNumericDesc=function(d,c,a,b,e,f){if(c===a)return 0;d=d.config;b=d.string[d.empties[b]||d.emptyTo];if(""===c&&0!==b)return"boolean"===typeof b?b?-1:1:b||1;if(""===a&&0!==b)return"boolean"===typeof b?b?1:-1:-b||-1;isNaN(c)&&(c=g.getTextValue(c,e,f));isNaN(a)&&(a=g.getTextValue(a,e,f));return a-c};g.characterEquivalents={a:"\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5",A:"\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5",c:"\u00e7\u0107\u010d", C:"\u00c7\u0106\u010c",e:"\u00e9\u00e8\u00ea\u00eb\u011b\u0119",E:"\u00c9\u00c8\u00ca\u00cb\u011a\u0118",i:"\u00ed\u00ec\u0130\u00ee\u00ef\u0131",I:"\u00cd\u00cc\u0130\u00ce\u00cf",o:"\u00f3\u00f2\u00f4\u00f5\u00f6",O:"\u00d3\u00d2\u00d4\u00d5\u00d6",ss:"\u00df",SS:"\u1e9e",u:"\u00fa\u00f9\u00fb\u00fc\u016f",U:"\u00da\u00d9\u00db\u00dc\u016e"};g.replaceAccents=function(d){var c,a="[",b=g.characterEquivalents;if(!g.characterRegex){g.characterRegexArray={};for(c in b)"string"===typeof c&&(a+=b[c],g.characterRegexArray[c]= RegExp("["+b[c]+"]","g"));g.characterRegex=RegExp(a+"]")}if(g.characterRegex.test(d))for(c in b)"string"===typeof c&&(d=d.replace(g.characterRegexArray[c],c));return d};g.isValueInArray=function(d,c){var a,b=c.length;for(a=0;a<b;a++)if(c[a][0]===d)return!0;return!1};g.addParser=function(d){var c,a=g.parsers.length,b=!0;for(c=0;c<a;c++)g.parsers[c].id.toLowerCase()===d.id.toLowerCase()&&(b=!1);b&&g.parsers.push(d)};g.getParserById=function(d){var c,a=g.parsers.length;for(c=0;c<a;c++)if(g.parsers[c].id.toLowerCase()=== d.toString().toLowerCase())return g.parsers[c];return!1};g.addWidget=function(d){g.widgets.push(d)};g.getWidgetById=function(d){var c,a,b=g.widgets.length;for(c=0;c<b;c++)if((a=g.widgets[c])&&a.hasOwnProperty("id")&&a.id.toLowerCase()===d.toLowerCase())return a};g.applyWidget=function(d,c){var a=d.config,b=a.widgetOptions,e=a.widgets.sort().reverse(),f,h,m,n=e.length;h=j.inArray("zebra",a.widgets);0<=h&&(a.widgets.splice(h,1),a.widgets.push("zebra"));a.debug&&(f=new Date);for(h=0;h<n;h++)(m=g.getWidgetById(e[h]))&& (!0===c&&m.hasOwnProperty("init")?m.init(d,m,a,b):!c&&m.hasOwnProperty("format")&&m.format(d,a,b));a.debug&&v("Completed "+(!0===c?"initializing":"applying")+" widgets",f)};g.refreshWidgets=function(d,c,a){var b,h=d.config,f=h.widgets,l=g.widgets,m=l.length;for(b=0;b<m;b++)if(l[b]&&l[b].id&&(c||0>j.inArray(l[b].id,f)))h.debug&&e("Refeshing widgets: Removing "+l[b].id),l[b].hasOwnProperty("remove")&&l[b].remove(d,h,h.widgetOptions);!0!==a&&g.applyWidget(d,c)};g.getData=function(d,c,a){var b="";d=j(d); var e,f;if(!d.length)return"";e=j.metadata?d.metadata():!1;f=" "+(d.attr("class")||"");"undefined"!==typeof d.data(a)||"undefined"!==typeof d.data(a.toLowerCase())?b+=d.data(a)||d.data(a.toLowerCase()):e&&"undefined"!==typeof e[a]?b+=e[a]:c&&"undefined"!==typeof c[a]?b+=c[a]:" "!==f&&f.match(" "+a+"-")&&(b=f.match(RegExp(" "+a+"-(\\w+)"))[1]||"");return j.trim(b)};g.formatFloat=function(d,c){if("string"!==typeof d||""===d)return d;var a;d=(c&&c.config?!1!==c.config.usNumberFormat:"undefined"!==typeof c? c:1)?d.replace(/,/g,""):d.replace(/[\s|\.]/g,"").replace(/,/g,".");/^\s*\([.\d]+\)/.test(d)&&(d=d.replace(/^\s*\(/,"-").replace(/\)/,""));a=parseFloat(d);return isNaN(a)?j.trim(d):a};g.isDigit=function(d){return isNaN(d)?/^[\-+(]?\d+[)]?$/.test(d.toString().replace(/[,.'"\s]/g,"")):!0}}});var h=j.tablesorter;j.fn.extend({tablesorter:h.construct});h.addParser({id:"text",is:function(){return!0},format:function(e,v){var p=v.config;e=j.trim(p.ignoreCase?e.toLocaleLowerCase():e);return p.sortLocaleCompare? h.replaceAccents(e):e},type:"text"});h.addParser({id:"currency",is:function(e){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test(e)},format:function(e,j){return h.formatFloat(e.replace(/[^\w,. \-()]/g,""),j)},type:"numeric"});h.addParser({id:"ipAddress",is:function(e){return/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(e)},format:function(e,j){var p,r=e.split("."),s="",m=r.length;for(p=0;p<m;p++)s+=("00"+r[p]).slice(-3);return h.formatFloat(s,j)}, type:"numeric"});h.addParser({id:"url",is:function(e){return/^(https?|ftp|file):\/\//.test(e)},format:function(e){return j.trim(e.replace(/(https?|ftp|file):\/\//,""))},type:"text"});h.addParser({id:"isoDate",is:function(e){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(e)},format:function(e,j){return h.formatFloat(""!==e?(new Date(e.replace(/-/g,"/"))).getTime()||"":"",j)},type:"numeric"});h.addParser({id:"percent",is:function(e){return/(\d\s?%|%\s?\d)/.test(e)},format:function(e,j){return h.formatFloat(e.replace(/%/g, ""),j)},type:"numeric"});h.addParser({id:"usLongDate",is:function(e){return/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i.test(e)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(e)},format:function(e,j){return h.formatFloat((new Date(e.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",j)},type:"numeric"});h.addParser({id:"shortDate",is:function(e){return/^(\d{1,2}|\d{4})[\/\-\,\.\s+]\d{1,2}[\/\-\.\,\s+](\d{1,2}|\d{4})$/.test(e)},format:function(e,j,p,r){p=j.config;var s=p.headerList[r], m=s.shortDateFormat;"undefined"===typeof m&&(m=s.shortDateFormat=h.getData(s,p.headers[r],"dateFormat")||p.dateFormat);e=e.replace(/\s+/g," ").replace(/[\-|\.|\,]/g,"/");"mmddyyyy"===m?e=e.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===m?e=e.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===m&&(e=e.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3"));return h.formatFloat((new Date(e)).getTime()||"",j)},type:"numeric"});h.addParser({id:"time", is:function(e){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(e)},format:function(e,j){return h.formatFloat((new Date("2000/01/01 "+e.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",j)},type:"numeric"});h.addParser({id:"digit",is:function(e){return h.isDigit(e)},format:function(e,j){return h.formatFloat(e.replace(/[^\w,. \-()]/g,""),j)},type:"numeric"});h.addParser({id:"metadata",is:function(){return!1},format:function(e,h,p){e=h.config;e=!e.parserMetadataName?"sortValue":e.parserMetadataName; return j(p).metadata()[e]},type:"numeric"});h.addWidget({id:"zebra",format:function(e,v,p){var r,s,m,F,G,C,I=RegExp(v.cssChildRow,"i"),g=v.$tbodies;v.debug&&(G=new Date);for(e=0;e<g.length;e++)r=g.eq(e),C=r.children("tr").length,1<C&&(m=0,r=r.children("tr:visible"),r.each(function(){s=j(this);I.test(this.className)||m++;F=0===m%2;s.removeClass(p.zebra[F?1:0]).addClass(p.zebra[F?0:1])}));v.debug&&h.benchmark("Applying Zebra widget",G)},remove:function(e,h){var p,r,s=h.$tbodies,m=(h.widgetOptions.zebra|| ["even","odd"]).join(" ");for(p=0;p<s.length;p++)r=j.tablesorter.processTbody(e,s.eq(p),!0),r.children().removeClass(m),j.tablesorter.processTbody(e,r,!1)}})}(jQuery); + +var yourls_defaultsort = 2; // default column to sort on (overwrite this inline in page) +var yourls_defaultorder = 1; // default order ('asc':0, 'desc':1) to sort on (overwrite this inline in page) + +// Initialise the table to sort +$(document).ready(function(){ + if ($("#main_table").tablesorter && $("#main_table tr#nourl_found").css('display') == 'none') { + var order = {'keyword':0, 'url':1, 'timestamp':2, 'ip':3, 'clicks':4}; + var order_by = {'asc':0, 'desc':1}; + var sort_by = order[query_string('sort_by')]; + var sort_order = order_by[query_string('sort_order')]; + if( sort_by == undefined ) { + sort_by = yourls_defaultsort; + sort_order = yourls_defaultorder; + } + + $("#main_table").tablesorter({ + textExtraction: { + 1: function(node, table, cellIndex){return $(node).find("small a").text();} // Sort column "URL" by URL, not by whole cell content + }, + sortList:[[ sort_by, sort_order ]], + headers: { 5: {sorter: false} }, // no sorter on column "Actions" + widgets: ['zebra'], // prettify, see tr.normal-row and tr.alt-row in tablesorter.css + widgetOptions : { zebra : [ "normal-row", "alt-row" ] } + }); + } +}); + +// Get query string +function query_string( key ) { + default_=""; + key = key.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); + var regex = new RegExp("[\\?&]"+key+"=([^&#]*)"); + var qs = regex.exec(window.location.href); + if(qs == null) + return yourls_defaultsort; + else + return qs[1]; } \ No newline at end of file diff --git a/js/jquery.zclip.min.js b/js/jquery.zclip.min.js index 3548cc2..51471a1 100644 --- a/js/jquery.zclip.min.js +++ b/js/jquery.zclip.min.js @@ -1,12 +1,12 @@ -/* - * zClip :: jQuery ZeroClipboard v1.1.1 - * http://steamdev.com/zclip - * - * Copyright 2011, SteamDev - * Released under the MIT license. - * http://www.opensource.org/licenses/mit-license.php - * - * Date: Wed Jun 01, 2011 - */ - +/* + * zClip :: jQuery ZeroClipboard v1.1.1 + * http://steamdev.com/zclip + * + * Copyright 2011, SteamDev + * Released under the MIT license. + * http://www.opensource.org/licenses/mit-license.php + * + * Date: Wed Jun 01, 2011 + */ + (function(a){a.fn.zclip=function(c){if(typeof c=="object"&&!c.length){var b=a.extend({path:"ZeroClipboard.swf",copy:null,beforeCopy:null,afterCopy:null,clickAfter:true,setHandCursor:true,setCSSEffects:true},c);return this.each(function(){var e=a(this);if(e.is(":visible")&&(typeof b.copy=="string"||a.isFunction(b.copy))){ZeroClipboard.setMoviePath(b.path);var d=new ZeroClipboard.Client();if(a.isFunction(b.copy)){e.bind("zClip_copy",b.copy)}if(a.isFunction(b.beforeCopy)){e.bind("zClip_beforeCopy",b.beforeCopy)}if(a.isFunction(b.afterCopy)){e.bind("zClip_afterCopy",b.afterCopy)}d.setHandCursor(b.setHandCursor);d.setCSSEffects(b.setCSSEffects);d.addEventListener("mouseOver",function(f){e.trigger("mouseenter")});d.addEventListener("mouseOut",function(f){e.trigger("mouseleave")});d.addEventListener("mouseDown",function(f){e.trigger("mousedown");if(!a.isFunction(b.copy)){d.setText(b.copy)}else{d.setText(e.triggerHandler("zClip_copy"))}if(a.isFunction(b.beforeCopy)){e.trigger("zClip_beforeCopy")}});d.addEventListener("complete",function(f,g){if(a.isFunction(b.afterCopy)){e.trigger("zClip_afterCopy")}else{if(g.length>500){g=g.substr(0,500)+"...\n\n("+(g.length-500)+" characters not shown)"}e.removeClass("hover");alert("Copied text to clipboard:\n\n "+g)}if(b.clickAfter){e.trigger("click")}});d.glue(e[0],e.parent()[0]);a(window).bind("load resize",function(){d.reposition()})}})}else{if(typeof c=="string"){return this.each(function(){var f=a(this);c=c.toLowerCase();var e=f.data("zclipId");var d=a("#"+e+".zclip");if(c=="remove"){d.remove();f.removeClass("active hover")}else{if(c=="hide"){d.hide();f.removeClass("active hover")}else{if(c=="show"){d.show()}}}})}}}})(jQuery);var ZeroClipboard={version:"1.0.7",clients:{},moviePath:"ZeroClipboard.swf",nextId:1,$:function(a){if(typeof(a)=="string"){a=document.getElementById(a)}if(!a.addClass){a.hide=function(){this.style.display="none"};a.show=function(){this.style.display=""};a.addClass=function(b){this.removeClass(b);this.className+=" "+b};a.removeClass=function(d){var e=this.className.split(/\s+/);var b=-1;for(var c=0;c<e.length;c++){if(e[c]==d){b=c;c=e.length}}if(b>-1){e.splice(b,1);this.className=e.join(" ")}return this};a.hasClass=function(b){return !!this.className.match(new RegExp("\\s*"+b+"\\s*"))}}return a},setMoviePath:function(a){this.moviePath=a},dispatch:function(d,b,c){var a=this.clients[d];if(a){a.receiveEvent(b,c)}},register:function(b,a){this.clients[b]=a},getDOMObjectPosition:function(c,a){var b={left:0,top:0,width:c.width?c.width:c.offsetWidth,height:c.height?c.height:c.offsetHeight};if(c&&(c!=a)){b.left+=c.offsetLeft;b.top+=c.offsetTop}return b},Client:function(a){this.handlers={};this.id=ZeroClipboard.nextId++;this.movieId="ZeroClipboardMovie_"+this.id;ZeroClipboard.register(this.id,this);if(a){this.glue(a)}}};ZeroClipboard.Client.prototype={id:0,ready:false,movie:null,clipText:"",handCursorEnabled:true,cssEffects:true,handlers:null,glue:function(d,b,e){this.domElement=ZeroClipboard.$(d);var f=99;if(this.domElement.style.zIndex){f=parseInt(this.domElement.style.zIndex,10)+1}if(typeof(b)=="string"){b=ZeroClipboard.$(b)}else{if(typeof(b)=="undefined"){b=document.getElementsByTagName("body")[0]}}var c=ZeroClipboard.getDOMObjectPosition(this.domElement,b);this.div=document.createElement("div");this.div.className="zclip";this.div.id="zclip-"+this.movieId;$(this.domElement).data("zclipId","zclip-"+this.movieId);var a=this.div.style;a.position="absolute";a.left=""+c.left+"px";a.top=""+c.top+"px";a.width=""+c.width+"px";a.height=""+c.height+"px";a.zIndex=f;if(typeof(e)=="object"){for(addedStyle in e){a[addedStyle]=e[addedStyle]}}b.appendChild(this.div);this.div.innerHTML=this.getHTML(c.width,c.height)},getHTML:function(d,a){var c="";var b="id="+this.id+"&width="+d+"&height="+a;if(navigator.userAgent.match(/MSIE/)){var e=location.href.match(/^https/i)?"https://":"http://";c+='<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+e+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+d+'" height="'+a+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+b+'"/><param name="wmode" value="transparent"/></object>'}else{c+='<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+d+'" height="'+a+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+b+'" wmode="transparent" />'}return c},hide:function(){if(this.div){this.div.style.left="-2000px"}},show:function(){this.reposition()},destroy:function(){if(this.domElement&&this.div){this.hide();this.div.innerHTML="";var a=document.getElementsByTagName("body")[0];try{a.removeChild(this.div)}catch(b){}this.domElement=null;this.div=null}},reposition:function(c){if(c){this.domElement=ZeroClipboard.$(c);if(!this.domElement){this.hide()}}if(this.domElement&&this.div){var b=ZeroClipboard.getDOMObjectPosition(this.domElement);var a=this.div.style;a.left=""+b.left+"px";a.top=""+b.top+"px"}},setText:function(a){this.clipText=a;if(this.ready){this.movie.setText(a)}},addEventListener:function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");if(!this.handlers[a]){this.handlers[a]=[]}this.handlers[a].push(b)},setHandCursor:function(a){this.handCursorEnabled=a;if(this.ready){this.movie.setHandCursor(a)}},setCSSEffects:function(a){this.cssEffects=!!a},receiveEvent:function(d,f){d=d.toString().toLowerCase().replace(/^on/,"");switch(d){case"load":this.movie=document.getElementById(this.movieId);if(!this.movie){var c=this;setTimeout(function(){c.receiveEvent("load",null)},1);return}if(!this.ready&&navigator.userAgent.match(/Firefox/)&&navigator.userAgent.match(/Windows/)){var c=this;setTimeout(function(){c.receiveEvent("load",null)},100);this.ready=true;return}this.ready=true;try{this.movie.setText(this.clipText)}catch(h){}try{this.movie.setHandCursor(this.handCursorEnabled)}catch(h){}break;case"mouseover":if(this.domElement&&this.cssEffects){this.domElement.addClass("hover");if(this.recoverActive){this.domElement.addClass("active")}}break;case"mouseout":if(this.domElement&&this.cssEffects){this.recoverActive=false;if(this.domElement.hasClass("active")){this.domElement.removeClass("active");this.recoverActive=true}this.domElement.removeClass("hover")}break;case"mousedown":if(this.domElement&&this.cssEffects){this.domElement.addClass("active")}break;case"mouseup":if(this.domElement&&this.cssEffects){this.domElement.removeClass("active");this.recoverActive=false}break}if(this.handlers[d]){for(var b=0,a=this.handlers[d].length;b<a;b++){var g=this.handlers[d][b];if(typeof(g)=="function"){g(this,f)}else{if((typeof(g)=="object")&&(g.length==2)){g[0][g[1]](this,f)}else{if(typeof(g)=="string"){window[g](this,f)}}}}}}}; \ No newline at end of file diff --git a/js/share.js b/js/share.js index 772d6b4..bdb59d9 100644 --- a/js/share.js +++ b/js/share.js @@ -1,57 +1,57 @@ -$(document).ready(function(){ - $('#tweet_body').focus(); - - $('#tweet_body').keypress(function(){ - setTimeout( function(){update_share()}, 50 ); // we're delaying, otherwise keypress() always triggers too fast before current key press actually inserts a letter?!! Go figure. - }); -}) - -function update_share() { - var text = encodeURIComponent( $('#tweet_body').val() ); - var url = encodeURIComponent( $('#copylink').val() ); - var tw = 'http://twitter.com/intent/tweet?status='+text; - var ff = 'http://friendfeed.com/share/bookmarklet/frame#title='+text ; - var fb = 'http://www.facebook.com/share.php?u='+url ; - $('#share_tw').attr('href', tw); - $('#share_ff').attr('href', ff); - $('#share_fb').attr('href', fb); - - var charcount = parseInt(140 - $('#tweet_body').val().length); - $('#charcount') - .toggleClass("negative", charcount < 0) - .text( charcount ); -} - -function share(dest) { - var url = $('#share_'+dest).attr('href'); - switch (dest) { - case 'ff': - //$('body').append('<script type="text/javascript" src="http://friendfeed.com/share/bookmarklet/javascript"></script>'); - window.open(url, 'ff','toolbar=no,width=500,height=350'); - break; - case 'fb': - //var url = $('#share_fb').attr('href'); - window.open( url, 'fb','toolbar=no,width=1000,height=550'); - break; - case 'tw': - //var url = $('#share_tw').attr('href'); - window.open(url, 'tw','toolbar=no,width=800,height=550'); - break; - } - return false; -} - -function init_clipboard() { - $('#copylink').click(function(){ - $(this).select(); - }) - - $('#copylink').zclip({ - path: zclipurl, - copy: $('#copylink').val(), - afterCopy:function(){ - html_pulse( '#copybox h2, #copybox h3', 'Copied!' ); - } - }); -}; - +$(document).ready(function(){ + $('#tweet_body').focus(); + + $('#tweet_body').keypress(function(){ + setTimeout( function(){update_share()}, 50 ); // we're delaying, otherwise keypress() always triggers too fast before current key press actually inserts a letter?!! Go figure. + }); +}) + +function update_share() { + var text = encodeURIComponent( $('#tweet_body').val() ); + var url = encodeURIComponent( $('#copylink').val() ); + var tw = 'http://twitter.com/intent/tweet?status='+text; + var ff = 'http://friendfeed.com/share/bookmarklet/frame#title='+text ; + var fb = 'http://www.facebook.com/share.php?u='+url ; + $('#share_tw').attr('href', tw); + $('#share_ff').attr('href', ff); + $('#share_fb').attr('href', fb); + + var charcount = parseInt(140 - $('#tweet_body').val().length); + $('#charcount') + .toggleClass("negative", charcount < 0) + .text( charcount ); +} + +function share(dest) { + var url = $('#share_'+dest).attr('href'); + switch (dest) { + case 'ff': + //$('body').append('<script type="text/javascript" src="http://friendfeed.com/share/bookmarklet/javascript"></script>'); + window.open(url, 'ff','toolbar=no,width=500,height=350'); + break; + case 'fb': + //var url = $('#share_fb').attr('href'); + window.open( url, 'fb','toolbar=no,width=1000,height=550'); + break; + case 'tw': + //var url = $('#share_tw').attr('href'); + window.open(url, 'tw','toolbar=no,width=800,height=550'); + break; + } + return false; +} + +function init_clipboard() { + $('#copylink').click(function(){ + $(this).select(); + }) + + $('#copylink').zclip({ + path: zclipurl, + copy: $('#copylink').val(), + afterCopy:function(){ + html_pulse( '#copybox h2, #copybox h3', 'Copied!' ); + } + }); +}; + diff --git a/pages/examplepage.php b/pages/examplepage.php index dc11062..057c10f 100644 --- a/pages/examplepage.php +++ b/pages/examplepage.php @@ -1,23 +1,23 @@ -<?php - -// Make sure we're in YOURLS context -if( !defined( 'YOURLS_ABSPATH' ) ) { - // Attempt to guess URL via YOURLS - $url = 'http://' . $_SERVER['HTTP_HOST'] . str_replace( array( '/pages/', '.php' ) , array ( '/', '' ), $_SERVER['REQUEST_URI'] ); - echo "Try this instead: <a href='$url'>$url</a>"; - die(); -} - -// Display page content. Any PHP, HTML and YOURLS function can go here. -$url = YOURLS_SITE . '/examplepage'; - -yourls_html_head( 'examplepage', 'Example page' ); - -?> - -<p>This is an example page. Its URL is simply <?php echo $url; ?></p> - -<?php - -yourls_html_footer(); - +<?php + +// Make sure we're in YOURLS context +if( !defined( 'YOURLS_ABSPATH' ) ) { + // Attempt to guess URL via YOURLS + $url = 'http://' . $_SERVER['HTTP_HOST'] . str_replace( array( '/pages/', '.php' ) , array ( '/', '' ), $_SERVER['REQUEST_URI'] ); + echo "Try this instead: <a href='$url'>$url</a>"; + die(); +} + +// Display page content. Any PHP, HTML and YOURLS function can go here. +$url = YOURLS_SITE . '/examplepage'; + +yourls_html_head( 'examplepage', 'Example page' ); + +?> + +<p>This is an example page. Its URL is simply <?php echo $url; ?></p> + +<?php + +yourls_html_footer(); + diff --git a/readme.html b/readme.html index 968840c..967c06a 100644 --- a/readme.html +++ b/readme.html @@ -1,831 +1,831 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <meta http-equiv="X-UA-Compatible" content="IE-9"/> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>YOURLS: Your Own URL Shortener - - - - - -
-
- - -

YOURLS: Your Own URL Shortener

- - - - - -
- -
-
-
- - -
-
-
- -
-
- - -
-
- - -
-

About YOURLS

- -

What is YOURLS

-

YOURLS stands for Your Own URL Shortener. It is a small set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or bitly).

- -

Running your own URL shortener is fun, geeky and useful: you own your data and don't depend on third party services. It's also a great way to add branding to your short URLs, instead of using the same public URL shortener everyone uses.

- -

YOURLS Features

-
    -
  • Free and Open Source software.
  • -
  • Private (your links only) or Public (everybody can create short links, fine for an intranet)
  • -
  • Sequential or custom URL keyword
  • -
  • Handy bookmarklets to easily shorten and share links
  • -
  • Awesome stats: historical click reports, referrers tracking, visitors geo-location
  • -
  • Neat Ajaxed interface
  • -
  • Terrific Plugin architecture to easily implement new features
  • -
  • Cool developer API
  • -
  • Full jsonp support
  • -
  • Friendly installer
  • -
  • Sample files to create your own public interface and more
  • -
- -

Screenshots

- -
-

Main admin dashboard

- - -

Stats for each short URL

- - -

See a live example of YOURLS stats on http://yourls.org/cookie+

-
- -

Download

- -

Download YOURLS from Google Code

-

You can follow YOURLS' development on the revision list and get current snapshot using SVN

- -

Credits

- -

YOURLS is made by:

- -

Keep up to date: follow Ozh and read the official YOURLS Blog

-
- - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - + + + + + + YOURLS: Your Own URL Shortener + + + + + +
+
+ + +

YOURLS: Your Own URL Shortener

+ + + + + +
+ +
+
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+

About YOURLS

+ +

What is YOURLS

+

YOURLS stands for Your Own URL Shortener. It is a small set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or bitly).

+ +

Running your own URL shortener is fun, geeky and useful: you own your data and don't depend on third party services. It's also a great way to add branding to your short URLs, instead of using the same public URL shortener everyone uses.

+ +

YOURLS Features

+
    +
  • Free and Open Source software.
  • +
  • Private (your links only) or Public (everybody can create short links, fine for an intranet)
  • +
  • Sequential or custom URL keyword
  • +
  • Handy bookmarklets to easily shorten and share links
  • +
  • Awesome stats: historical click reports, referrers tracking, visitors geo-location
  • +
  • Neat Ajaxed interface
  • +
  • Terrific Plugin architecture to easily implement new features
  • +
  • Cool developer API
  • +
  • Full jsonp support
  • +
  • Friendly installer
  • +
  • Sample files to create your own public interface and more
  • +
+ +

Screenshots

+ +
+

Main admin dashboard

+ + +

Stats for each short URL

+ + +

See a live example of YOURLS stats on http://yourls.org/cookie+

+
+ +

Download

+ +

Download YOURLS from Google Code

+

You can follow YOURLS' development on the revision list and get current snapshot using SVN

+ +

Credits

+ +

YOURLS is made by:

+ +

Keep up to date: follow Ozh and read the official YOURLS Blog

+
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + \ No newline at end of file diff --git a/sample-public-api.txt b/sample-public-api.txt index d7dd61f..c3e1974 100644 --- a/sample-public-api.txt +++ b/sample-public-api.txt @@ -1,13 +1,13 @@ - markup and all CSS & JS files -yourls_html_head(); - -// Display title -echo "

YOURLS - Your Own URL Shortener

\n"; - -// Display left hand menu -yourls_html_menu() ; - -// Part to be executed if FORM has been submitted -if ( isset( $_REQUEST['url'] ) && $_REQUEST['url'] != 'http://' ) { - - // Display result message of short link creation - if( isset( $message ) ) { - echo "

$message

"; - } - - // Include the Copy box and the Quick Share box - yourls_share_box( $url, $shorturl, $title, $text ); - - // Initialize clipboard -- requires js/share.js and js/jquery.zclip.min.js to be properly loaded in the - echo << - init_clipboard(); - -JS; - -// Part to be executed when no form has been submitted -} else { - - $site = YOURLS_SITE; - - // Display the form - echo <<Enter a new URL to shorten -
-

-

-

-

-
-HTML; - -} - -?> - -

Bookmarklets

- -

Bookmark these links:

- -

- -Simple Shorten - -Instant Shorten - -Custom Shorten - -Instant Custom Shorten - -

- -

Please note

- -

Be aware that a public interface will attract spammers. You are strongly advised to install anti spam plugins and any appropriate counter measure to deal with this issue.

- - markup and all CSS & JS files +yourls_html_head(); + +// Display title +echo "

YOURLS - Your Own URL Shortener

\n"; + +// Display left hand menu +yourls_html_menu() ; + +// Part to be executed if FORM has been submitted +if ( isset( $_REQUEST['url'] ) && $_REQUEST['url'] != 'http://' ) { + + // Display result message of short link creation + if( isset( $message ) ) { + echo "

$message

"; + } + + // Include the Copy box and the Quick Share box + yourls_share_box( $url, $shorturl, $title, $text ); + + // Initialize clipboard -- requires js/share.js and js/jquery.zclip.min.js to be properly loaded in the + echo << + init_clipboard(); + +JS; + +// Part to be executed when no form has been submitted +} else { + + $site = YOURLS_SITE; + + // Display the form + echo <<Enter a new URL to shorten +
+

+

+

+

+
+HTML; + +} + +?> + +

Bookmarklets

+ +

Bookmark these links:

+ +

+ +Simple Shorten + +Instant Shorten + +Custom Shorten + +Instant Custom Shorten + +

+ +

Please note

+ +

Be aware that a public interface will attract spammers. You are strongly advised to install anti spam plugins and any appropriate counter measure to deal with this issue.

+ + $url, - 'keyword' => $keyword, - 'format' => $format, - 'action' => 'shorturl', - 'username' => $username, - 'password' => $password - )); - -// Fetch and return content -$data = curl_exec($ch); -curl_close($ch); - -// Do something with the result. Here, we just echo it. -echo $data; - + $url, + 'keyword' => $keyword, + 'format' => $format, + 'action' => 'shorturl', + 'username' => $username, + 'password' => $password + )); + +// Fetch and return content +$data = curl_exec($ch); +curl_close($ch); + +// Do something with the result. Here, we just echo it. +echo $data; + ?> \ No newline at end of file diff --git a/sample-robots.txt b/sample-robots.txt index f374d20..be16b0c 100644 --- a/sample-robots.txt +++ b/sample-robots.txt @@ -1,9 +1,9 @@ -User-agent: * -Disallow: /admin -Disallow: /css -Disallow: /images -Disallow: /includes -Disallow: /js -Disallow: /pages -Disallow: /user - +User-agent: * +Disallow: /admin +Disallow: /css +Disallow: /images +Disallow: /includes +Disallow: /js +Disallow: /pages +Disallow: /user + diff --git a/user/config-sample.php b/user/config-sample.php index dd1d02a..ab53f38 100644 --- a/user/config-sample.php +++ b/user/config-sample.php @@ -1,85 +1,85 @@ - 'password', - 'username2' => 'password2' // You can have one or more 'login'=>'password' lines - ); - -/* - ** URL Shortening settings - */ - -/** URL shortening method: 36 or 62 */ -define( 'YOURLS_URL_CONVERT', 36 ); -/* - * 36: generates all lowercase keywords (ie: 13jkm) - * 62: generates mixed case keywords (ie: 13jKm or 13JKm) - * Stick to one setting. It's best not to change after you've started creating links. - */ - -/** -* Reserved keywords (so that generated URLs won't match them) -* Define here negative, unwanted or potentially misleading keywords. -*/ -$yourls_reserved_URL = array( - 'porn', 'faggot', 'sex', 'nigger', 'fuck', 'cunt', 'dick', 'gay', -); - -/* - ** Personal settings would go after here. - */ - + 'password', + 'username2' => 'password2' // You can have one or more 'login'=>'password' lines + ); + +/* + ** URL Shortening settings + */ + +/** URL shortening method: 36 or 62 */ +define( 'YOURLS_URL_CONVERT', 36 ); +/* + * 36: generates all lowercase keywords (ie: 13jkm) + * 62: generates mixed case keywords (ie: 13jKm or 13JKm) + * Stick to one setting. It's best not to change after you've started creating links. + */ + +/** +* Reserved keywords (so that generated URLs won't match them) +* Define here negative, unwanted or potentially misleading keywords. +*/ +$yourls_reserved_URL = array( + 'porn', 'faggot', 'sex', 'nigger', 'fuck', 'cunt', 'dick', 'gay', +); + +/* + ** Personal settings would go after here. + */ + diff --git a/user/plugins/hyphens-in-urls/README.txt b/user/plugins/hyphens-in-urls/README.txt index 54f1425..c96d514 100644 --- a/user/plugins/hyphens-in-urls/README.txt +++ b/user/plugins/hyphens-in-urls/README.txt @@ -1,4 +1,4 @@ -This is a core plugin, bundled with YOURLS. -Don't modify this plugin. Instead, copy its folder -and modify your own copy. This way, your code won't +This is a core plugin, bundled with YOURLS. +Don't modify this plugin. Instead, copy its folder +and modify your own copy. This way, your code won't be overwritten when you upgrade YOURLS. \ No newline at end of file diff --git a/user/plugins/hyphens-in-urls/plugin.php b/user/plugins/hyphens-in-urls/plugin.php index aa89117..3803412 100644 --- a/user/plugins/hyphens-in-urls/plugin.php +++ b/user/plugins/hyphens-in-urls/plugin.php @@ -1,19 +1,19 @@ -http://sho.rt/hello-world) -Version: 1.0 -Author: Ozh -Author URI: http://ozh.org/ -*/ - -// No direct call -if( !defined( 'YOURLS_ABSPATH' ) ) die(); - -yourls_add_filter( 'get_shorturl_charset', 'ozh_hyphen_in_charset' ); -function ozh_hyphen_in_charset( $in ) { - return $in.'-'; -} - - +http://sho.rt/hello-world) +Version: 1.0 +Author: Ozh +Author URI: http://ozh.org/ +*/ + +// No direct call +if( !defined( 'YOURLS_ABSPATH' ) ) die(); + +yourls_add_filter( 'get_shorturl_charset', 'ozh_hyphen_in_charset' ); +function ozh_hyphen_in_charset( $in ) { + return $in.'-'; +} + + diff --git a/user/plugins/random-bg/README.txt b/user/plugins/random-bg/README.txt index 1504eb5..f038585 100644 --- a/user/plugins/random-bg/README.txt +++ b/user/plugins/random-bg/README.txt @@ -1,4 +1,4 @@ -This is a sample plugin, for illustration purpose. -Don't modify this plugin. Instead, copy its folder -and modify your own copy. This way, your code won't +This is a sample plugin, for illustration purpose. +Don't modify this plugin. Instead, copy its folder +and modify your own copy. This way, your code won't be overwritten when you upgrade YOURLS. \ No newline at end of file diff --git a/user/plugins/random-bg/plugin.php b/user/plugins/random-bg/plugin.php index ed27eed..e0c1ef1 100644 --- a/user/plugins/random-bg/plugin.php +++ b/user/plugins/random-bg/plugin.php @@ -1,27 +1,27 @@ - - body {background:#e3f3ff url($rnd) repeat;} - - -CSS; -} - + + body {background:#e3f3ff url($rnd) repeat;} + + +CSS; +} + diff --git a/user/plugins/sample-page/README.txt b/user/plugins/sample-page/README.txt index 1504eb5..f038585 100644 --- a/user/plugins/sample-page/README.txt +++ b/user/plugins/sample-page/README.txt @@ -1,4 +1,4 @@ -This is a sample plugin, for illustration purpose. -Don't modify this plugin. Instead, copy its folder -and modify your own copy. This way, your code won't +This is a sample plugin, for illustration purpose. +Don't modify this plugin. Instead, copy its folder +and modify your own copy. This way, your code won't be overwritten when you upgrade YOURLS. \ No newline at end of file diff --git a/user/plugins/sample-page/plugin.php b/user/plugins/sample-page/plugin.php index bfb920c..717961d 100644 --- a/user/plugins/sample-page/plugin.php +++ b/user/plugins/sample-page/plugin.php @@ -1,63 +1,63 @@ -Sample Plugin Administration Page -

This plugin stores an integer in the option database

-
- -

-

-
- -HTML; -} - -// Update option in database -function ozh_yourls_samplepage_update_option() { - $in = $_POST['test_option']; - - if( $in ) { - // Validate test_option. ALWAYS validate and sanitize user input. - // Here, we want an integer - $in = intval( $in); - - // Update value in database - yourls_update_option( 'test_option', $in ); - } +Sample Plugin Administration Page +

This plugin stores an integer in the option database

+
+ +

+

+
+ +HTML; +} + +// Update option in database +function ozh_yourls_samplepage_update_option() { + $in = $_POST['test_option']; + + if( $in ) { + // Validate test_option. ALWAYS validate and sanitize user input. + // Here, we want an integer + $in = intval( $in); + + // Update value in database + yourls_update_option( 'test_option', $in ); + } } \ No newline at end of file diff --git a/user/plugins/sample-plugin/README.txt b/user/plugins/sample-plugin/README.txt index 1504eb5..f038585 100644 --- a/user/plugins/sample-plugin/README.txt +++ b/user/plugins/sample-plugin/README.txt @@ -1,4 +1,4 @@ -This is a sample plugin, for illustration purpose. -Don't modify this plugin. Instead, copy its folder -and modify your own copy. This way, your code won't +This is a sample plugin, for illustration purpose. +Don't modify this plugin. Instead, copy its folder +and modify your own copy. This way, your code won't be overwritten when you upgrade YOURLS. \ No newline at end of file diff --git a/user/plugins/sample-plugin/plugin.php b/user/plugins/sample-plugin/plugin.php index 1ec2a47..c4769c3 100644 --- a/user/plugins/sample-plugin/plugin.php +++ b/user/plugins/sample-plugin/plugin.php @@ -1,61 +1,61 @@ -Plugin API documentation for more details. -Version: 0.1 -Author: Ozh -Author URI: http://ozh.org/ -*/ - -// No direct call -if( !defined( 'YOURLS_ABSPATH' ) ) die(); - -/* Example of an action - * - * We're going to add an entry to the menu. - * - * The menu is drawn by function yourls_html_menu() in file includes/functions-html.php. - * Right before the function outputs the closing , notice the following function call: - * yourls_do_action( 'admin_menu' ); - * This function says: "hey, for your information, I've just done something called 'admin menu', thought I'd let you know..." - * - * We're going to hook into this action and add our menu entry - */ - -yourls_add_action( 'admin_menu', 'ozh_sample_add_menu' ); -/* This says: when YOURLS does action 'admin_menu', call function 'ozh_sample_add_menu' - */ - -function ozh_sample_add_menu() { - echo '
  • Ozh
  • '; -} -/* And that's it. Activate the plugin and notice the new menu entry. - */ - - - -/* Example of a filter - * - * We're going to modify the of pages in the admin area - * - * The <title> tag is generated by function yourls_html_head() in includes/functions-html.php - * Notice the following function call: - * $title = yourls_apply_filter( 'html_title', 'YOURLS: Your Own URL Shortener' ); - * This function means: give $title the value "YOURLS: Your Own URL Shortener", unless a - * filter modifies this value. - * - * We're going to hook into this filter and modify this value. - */ - -yourls_add_filter( 'html_title', 'ozh_sample_change_title' ); -/* This says: when filter 'html_title' is triggered, send its value to function 'ozh_sample_change_title' - * and use what this function will return. - */ - -function ozh_sample_change_title( $value ) { - $value = $value . ' (we have hacked this title)'; - return $value; // a filter *always* has to return a value -} -/* And that's it. Activate the plugin and notice how the page title changes */ - +<?php +/* +Plugin Name: Sample Plugin +Plugin URI: http://yourls.org/ +Description: Sample plugin to illustrate how actions and filters work. Read its source. Refer to the <a href="http://yourls.org/pluginapi">Plugin API documentation</a> for more details. +Version: 0.1 +Author: Ozh +Author URI: http://ozh.org/ +*/ + +// No direct call +if( !defined( 'YOURLS_ABSPATH' ) ) die(); + +/* Example of an action + * + * We're going to add an entry to the menu. + * + * The menu is drawn by function yourls_html_menu() in file includes/functions-html.php. + * Right before the function outputs the closing </ul>, notice the following function call: + * yourls_do_action( 'admin_menu' ); + * This function says: "hey, for your information, I've just done something called 'admin menu', thought I'd let you know..." + * + * We're going to hook into this action and add our menu entry + */ + +yourls_add_action( 'admin_menu', 'ozh_sample_add_menu' ); +/* This says: when YOURLS does action 'admin_menu', call function 'ozh_sample_add_menu' + */ + +function ozh_sample_add_menu() { + echo '<li><a href="http://ozh.org/">Ozh</a></li>'; +} +/* And that's it. Activate the plugin and notice the new menu entry. + */ + + + +/* Example of a filter + * + * We're going to modify the <title> of pages in the admin area + * + * The <title> tag is generated by function yourls_html_head() in includes/functions-html.php + * Notice the following function call: + * $title = yourls_apply_filter( 'html_title', 'YOURLS: Your Own URL Shortener' ); + * This function means: give $title the value "YOURLS: Your Own URL Shortener", unless a + * filter modifies this value. + * + * We're going to hook into this filter and modify this value. + */ + +yourls_add_filter( 'html_title', 'ozh_sample_change_title' ); +/* This says: when filter 'html_title' is triggered, send its value to function 'ozh_sample_change_title' + * and use what this function will return. + */ + +function ozh_sample_change_title( $value ) { + $value = $value . ' (we have hacked this title)'; + return $value; // a filter *always* has to return a value +} +/* And that's it. Activate the plugin and notice how the page title changes */ + diff --git a/user/plugins/sample-toolbar/README.txt b/user/plugins/sample-toolbar/README.txt index 1504eb5..f038585 100644 --- a/user/plugins/sample-toolbar/README.txt +++ b/user/plugins/sample-toolbar/README.txt @@ -1,4 +1,4 @@ -This is a sample plugin, for illustration purpose. -Don't modify this plugin. Instead, copy its folder -and modify your own copy. This way, your code won't +This is a sample plugin, for illustration purpose. +Don't modify this plugin. Instead, copy its folder +and modify your own copy. This way, your code won't be overwritten when you upgrade YOURLS. \ No newline at end of file diff --git a/user/plugins/sample-toolbar/css/toolbar.css b/user/plugins/sample-toolbar/css/toolbar.css index 358c143..59c7607 100644 --- a/user/plugins/sample-toolbar/css/toolbar.css +++ b/user/plugins/sample-toolbar/css/toolbar.css @@ -1,79 +1,79 @@ -body { - margin:0; - overflow:hidden; - background-color:#fff; - font-size:12px; - font-family: Verdana, Arial; - padding:35px 0 0; -} - -#yourls-frame { - width: 100%; - height:100%; - z-index: 1; -} - -#yourls-bar { - font-family: Verdana, Arial; - font-size: 12px; - position:absolute; - top:0; - height:35px; - width:100%; - background:#e3f3ff url(../img/toolbar_bg.png) repeat-x bottom center; - color:#2A85B3; - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.5); - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); - z-index: 900000; -} - -#yourls-bar a { - text-decoration:none; - color:#2A85B3; -} - -#yourls-bar a:hover { - text-decoration:underline; -} - -#yourls-about, #yourls-topsy, #yourls-delicious, #yourls-selfclose { - margin-left:10px; - float:left; - display:block; - top:5px; - position:relative; -} - -#yourls-about a { - background:transparent url(../img/favicon.gif) center left no-repeat; - padding-left:20px; - color:inherit; - font-weight:bold; -} - -#yourls-topsy { - width:300px; -} - -#yourls-selfclose { - float:right; - margin-right:10px; -} - -#yourls-once { - display:block; - text-indent:-9999px; - background:transparent url(../img/close_button.gif) center center no-repeat; - width:20px; - height:20px; - float:left; -} - -#yourls-always { - display:none; - text-indent:-9999px; - background:transparent url(../img/close_button_red.gif) center center no-repeat; - width:20px; - height:20px; - float:left; -} +body { + margin:0; + overflow:hidden; + background-color:#fff; + font-size:12px; + font-family: Verdana, Arial; + padding:35px 0 0; +} + +#yourls-frame { + width: 100%; + height:100%; + z-index: 1; +} + +#yourls-bar { + font-family: Verdana, Arial; + font-size: 12px; + position:absolute; + top:0; + height:35px; + width:100%; + background:#e3f3ff url(../img/toolbar_bg.png) repeat-x bottom center; + color:#2A85B3; + -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + z-index: 900000; +} + +#yourls-bar a { + text-decoration:none; + color:#2A85B3; +} + +#yourls-bar a:hover { + text-decoration:underline; +} + +#yourls-about, #yourls-topsy, #yourls-delicious, #yourls-selfclose { + margin-left:10px; + float:left; + display:block; + top:5px; + position:relative; +} + +#yourls-about a { + background:transparent url(../img/favicon.gif) center left no-repeat; + padding-left:20px; + color:inherit; + font-weight:bold; +} + +#yourls-topsy { + width:300px; +} + +#yourls-selfclose { + float:right; + margin-right:10px; +} + +#yourls-once { + display:block; + text-indent:-9999px; + background:transparent url(../img/close_button.gif) center center no-repeat; + width:20px; + height:20px; + float:left; +} + +#yourls-always { + display:none; + text-indent:-9999px; + background:transparent url(../img/close_button_red.gif) center center no-repeat; + width:20px; + height:20px; + float:left; +} diff --git a/user/plugins/sample-toolbar/js/toolbar.js b/user/plugins/sample-toolbar/js/toolbar.js index 7a65622..bfa249a 100644 --- a/user/plugins/sample-toolbar/js/toolbar.js +++ b/user/plugins/sample-toolbar/js/toolbar.js @@ -1,22 +1,22 @@ - -// If javascript is enabled, display the button -document.getElementById('yourls-always').style.display = 'block'; - -// When button clicked, store a cookie that says the user doesn't want a toolbar -document.getElementById('yourls-always').onclick = yourls_cookie_no_toolbar_please; -function yourls_cookie_no_toolbar_please() { - var exdate=new Date(); - exdate.setDate( exdate.getDate()+365 ); // store 365 days - document.cookie = "yourls_no_toolbar=1;expires="+exdate.toUTCString() ; -} - -// Get the number of delicious bookmarks -function yourls_get_books(json) { - if( json.length ) { - var books = json[0].total_posts.toString(); - if( books ) { - document.getElementById('yourls-delicious-link').innerHTML = ' <b>'+books+'</b> bookmarks'; - } - } -} - + +// If javascript is enabled, display the button +document.getElementById('yourls-always').style.display = 'block'; + +// When button clicked, store a cookie that says the user doesn't want a toolbar +document.getElementById('yourls-always').onclick = yourls_cookie_no_toolbar_please; +function yourls_cookie_no_toolbar_please() { + var exdate=new Date(); + exdate.setDate( exdate.getDate()+365 ); // store 365 days + document.cookie = "yourls_no_toolbar=1;expires="+exdate.toUTCString() ; +} + +// Get the number of delicious bookmarks +function yourls_get_books(json) { + if( json.length ) { + var books = json[0].total_posts.toString(); + if( books ) { + document.getElementById('yourls-delicious-link').innerHTML = ' <b>'+books+'</b> bookmarks'; + } + } +} + diff --git a/user/plugins/sample-toolbar/plugin.php b/user/plugins/sample-toolbar/plugin.php index fc500aa..3a2b1fa 100644 --- a/user/plugins/sample-toolbar/plugin.php +++ b/user/plugins/sample-toolbar/plugin.php @@ -1,126 +1,126 @@ -<?php -/* -Plugin Name: YOURLS Toolbar -Plugin URI: http://yourls.org/ -Description: Add a social toolbar to your redirected short URLs. Fork this plugin if you want to make your own toolbar. -Version: 1.0 -Author: Ozh -Author URI: http://ozh.org/ -Disclaimer: Toolbars ruin the user experience. Be warned. -*/ - -// No direct call -if( !defined( 'YOURLS_ABSPATH' ) ) die(); - -global $ozh_toolbar; -$ozh_toolbar['do'] = false; -$ozh_toolbar['keyword'] = ''; - -// When a redirection to a shorturl is about to happen, register variables -yourls_add_action( 'redirect_shorturl', 'ozh_toolbar_add' ); -function ozh_toolbar_add( $args ) { - global $ozh_toolbar; - $ozh_toolbar['do'] = true; - $ozh_toolbar['keyword'] = $args[1]; -} - -// On redirection, check if this is a toolbar and draw it if needed -yourls_add_action( 'pre_redirect', 'ozh_toolbar_do' ); -function ozh_toolbar_do( $args ) { - global $ozh_toolbar; - - // Does this redirection need a toolbar? - if( !$ozh_toolbar['do'] ) - return; - - // Do we have a cookie stating the user doesn't want a toolbar? - if( isset( $_COOKIE['yourls_no_toolbar'] ) && $_COOKIE['yourls_no_toolbar'] == 1 ) - return; - - // Get URL and page title - $url = $args[0]; - $pagetitle = yourls_get_keyword_title( $ozh_toolbar['keyword'] ); - - // Update title if it hasn't been stored yet - if( $pagetitle == '' ) { - $pagetitle = yourls_get_remote_title( $url ); - yourls_edit_link_title( $ozh_toolbar['keyword'], $pagetitle ); - } - $_pagetitle = htmlentities( yourls_get_remote_title( $url ) ); - - $www = YOURLS_SITE; - $ver = YOURLS_VERSION; - $md5 = md5( $url ); - $sql = yourls_get_num_queries(); - - // When was the link created (in days) - $diff = abs( time() - strtotime( yourls_get_keyword_timestamp( $ozh_toolbar['keyword'] ) ) ); - $days = floor( $diff / (60*60*24) ); - if( $days == 0 ) { - $created = 'today'; - } else { - $created = $days.' '.yourls_plural( 'day', $days).' ago'; - } - - // How many hits on the page - $hits = 1 + yourls_get_keyword_clicks( $ozh_toolbar['keyword'] ); - $hits = $hits.' '.yourls_plural( 'view', $hits); - - // Plugin URL (no URL is hardcoded) - $pluginurl = YOURLS_PLUGINURL . '/'.yourls_plugin_basename( dirname(__FILE__) ); - - // All set. Draw the toolbar itself. - echo <<<PAGE -<html> -<head> - <title>$pagetitle — YOURLS - - - - - - - - -
    -
    - Short link powered by YOURLS and created $created. $hits. - -
    - - - - -
    - -
    - -
    - close - close - -
    -
    - - - - - - - -PAGE; - - // Don't forget to die, to interrupt the flow of normal events (ie redirecting to long URL) - die(); + + + $pagetitle — YOURLS + + + + + + + + +
    +
    + Short link powered by YOURLS and created $created. $hits. + +
    + + + + +
    + +
    + +
    + close + close + +
    +
    + + + + + + + +PAGE; + + // Don't forget to die, to interrupt the flow of normal events (ie redirecting to long URL) + die(); } \ No newline at end of file diff --git a/yourls-api.php b/yourls-api.php index d61ac74..fda4af9 100644 --- a/yourls-api.php +++ b/yourls-api.php @@ -1,51 +1,51 @@ - 'yourls_api_action_shorturl', - 'stats' => 'yourls_api_action_stats', - 'db-stats' => 'yourls_api_action_db_stats', - 'url-stats' => 'yourls_api_action_url_stats', - 'expand' => 'yourls_api_action_expand', - 'version' => 'yourls_api_action_version', -); -$api_actions = yourls_apply_filters( 'api_actions', $api_actions ); - -// Register API actions -foreach( (array) $api_actions as $_action => $_callback ) { - yourls_add_filter( 'api_action_' . $_action, $_callback, 99 ); -} - -// Try requested API method. Properly registered actions should return an array. -$return = yourls_apply_filter( 'api_action_' . $action, false ); -if ( false === $return ) { - $return = array( - 'errorCode' => 400, - 'message' => 'Unknown or missing "action" parameter', - 'simple' => 'Unknown or missing "action" parameter', - ); -} - -if( isset( $_REQUEST['callback'] ) ) - $return['callback'] = $_REQUEST['callback']; - -$format = ( isset( $_REQUEST['format'] ) ? $_REQUEST['format'] : 'xml' ); - -yourls_api_output( $format, $return ); - + 'yourls_api_action_shorturl', + 'stats' => 'yourls_api_action_stats', + 'db-stats' => 'yourls_api_action_db_stats', + 'url-stats' => 'yourls_api_action_url_stats', + 'expand' => 'yourls_api_action_expand', + 'version' => 'yourls_api_action_version', +); +$api_actions = yourls_apply_filters( 'api_actions', $api_actions ); + +// Register API actions +foreach( (array) $api_actions as $_action => $_callback ) { + yourls_add_filter( 'api_action_' . $_action, $_callback, 99 ); +} + +// Try requested API method. Properly registered actions should return an array. +$return = yourls_apply_filter( 'api_action_' . $action, false ); +if ( false === $return ) { + $return = array( + 'errorCode' => 400, + 'message' => 'Unknown or missing "action" parameter', + 'simple' => 'Unknown or missing "action" parameter', + ); +} + +if( isset( $_REQUEST['callback'] ) ) + $return['callback'] = $_REQUEST['callback']; + +$format = ( isset( $_REQUEST['format'] ) ? $_REQUEST['format'] : 'xml' ); + +yourls_api_output( $format, $return ); + die(); \ No newline at end of file diff --git a/yourls-go.php b/yourls-go.php index 3e6341b..1f7530d 100644 --- a/yourls-go.php +++ b/yourls-go.php @@ -1,45 +1,45 @@ -get_results( yourls_apply_filter( 'stat_query_referrer', $query ) ); - - // Loop through all results and build list of referrers, countries and hits per day - foreach( (array)$rows as $row ) { - if ( $row->referrer == 'direct' ) { - $direct = $row->count; - continue; - } - - $host = yourls_get_domain( $row->referrer ); - if( !array_key_exists( $host, $referrers ) ) - $referrers[$host] = array( ); - if( !array_key_exists( $row->referrer, $referrers[$host] ) ) { - $referrers[$host][$row->referrer] = $row->count; - $notdirect += $row->count; - } else { - $referrers[$host][$row->referrer] += $row->count; - $notdirect += $row->count; - } - } - - // Sort referrers. $referrer_sort is a array of most frequent domains - arsort( $referrers ); - $referrer_sort = array(); - $number_of_sites = count( array_keys( $referrers ) ); - foreach( $referrers as $site => $urls ) { - if( count($urls) > 1 || $number_of_sites == 1 ) - $referrer_sort[$site] = array_sum( $urls ); - } - arsort($referrer_sort); - - - // *** Countries *** - $query = "SELECT `country_code`, COUNT(*) AS `count` FROM `$table` WHERE `shorturl` $keyword_range GROUP BY `country_code`;"; - $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_country', $query ) ); - - // Loop through all results and build list of countries and hits - foreach( (array)$rows as $row ) { - if ("$row->country_code") - $countries["$row->country_code"] = $row->count; - } - - // Sort countries, most frequent first - if ( $countries ) - arsort( $countries ); - - - // *** Dates : array of $dates[$year][$month][$day] = number of clicks *** - $query = "SELECT - DATE_FORMAT(`click_time`, '%Y') AS `year`, - DATE_FORMAT(`click_time`, '%m') AS `month`, - DATE_FORMAT(`click_time`, '%d') AS `day`, - COUNT(*) AS `count` - FROM `$table` - WHERE `shorturl` $keyword_range - GROUP BY `year`, `month`, `day`;"; - $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_dates', $query ) ); - - // Loop through all results and fill blanks - foreach( (array)$rows as $row ) { - if( !array_key_exists($row->year, $dates ) ) - $dates[$row->year] = array(); - if( !array_key_exists( $row->month, $dates[$row->year] ) ) - $dates[$row->year][$row->month] = array(); - if( !array_key_exists( $row->day, $dates[$row->year][$row->month] ) ) - $dates[$row->year][$row->month][$row->day] = $row->count; - else - $dates[$row->year][$row->month][$row->day] += $row->count; - } - - // Sort dates, chronologically from [2007][12][24] to [2009][02][19] - ksort( $dates ); - foreach( $dates as $year=>$months ) { - ksort( $dates[$year] ); - foreach( $months as $month=>$day ) { - ksort( $dates[$year][$month] ); - } - } - - // Get $list_of_days, $list_of_months, $list_of_years - reset( $dates ); - if( $dates ) { - extract( yourls_build_list_of_days( $dates ) ); - } - - - // *** Last 24 hours : array of $last_24h[ $hour ] = number of click *** - $query = "SELECT - DATE_FORMAT(`click_time`, '%H %p') AS `time`, - COUNT(*) AS `count` - FROM `$table` - WHERE `shorturl` $keyword_range AND `click_time` > (CURRENT_TIMESTAMP - INTERVAL 1 DAY) - GROUP BY `time`;"; - $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_last24h', $query ) ); - - $_last_24h = array(); - foreach( (array)$rows as $row ) { - if ( $row->time ) - $_last_24h[ "$row->time" ] = $row->count; - } - - $now = intval( date('U') ); - for ($i = 23; $i >= 0; $i--) { - $h = date('H A', $now - ($i * 60 * 60) ); - // If the $last_24h doesn't have all the hours, insert missing hours with value 0 - $last_24h[ $h ] = array_key_exists( $h, $_last_24h ) ? $_last_24h[ $h ] : 0 ; - } - unset( $_last_24h ); - - // *** Queries all done, phew *** - - // Filter all this junk if applicable. Be warned, some are possibly huge datasets. - $referrers = yourls_apply_filter( 'pre_yourls_info_referrers', $referrers ); - $referrer_sort = yourls_apply_filter( 'pre_yourls_info_referrer_sort', $referrer_sort ); - $direct = yourls_apply_filter( 'pre_yourls_info_direct', $direct ); - $notdirect = yourls_apply_filter( 'pre_yourls_info_notdirect', $notdirect ); - $dates = yourls_apply_filter( 'pre_yourls_info_dates', $dates ); - $list_of_days = yourls_apply_filter( 'pre_yourls_info_list_of_days', $list_of_days ); - $list_of_months = yourls_apply_filter( 'pre_yourls_info_list_of_months', $list_of_months ); - $list_of_years = yourls_apply_filter( 'pre_yourls_info_list_of_years', $list_of_years ); - $last_24h = yourls_apply_filter( 'pre_yourls_info_last_24h', $last_24h ); - $countries = yourls_apply_filter( 'pre_yourls_info_countries', $countries ); - - // I can haz debug data - /** - echo "
    ";
    -	echo "referrers: "; print_r( $referrers );
    -	echo "referrer sort: "; print_r( $referrer_sort );
    -	echo "direct: $direct\n";
    -	echo "notdirect: $notdirect\n";
    -	echo "dates: "; print_r( $dates );
    -	echo "list of days: "; print_r( $list_of_days );
    -	echo "list_of_months: "; print_r( $list_of_months );
    -	echo "list_of_years: "; print_r( $list_of_years );
    -	echo "last_24h: "; print_r( $last_24h );
    -	echo "countries: "; print_r( $countries );
    -	die();
    -	/**/
    -
    -}
    -
    -yourls_html_head( 'infos', yourls_s( 'Statistics for %s', YOURLS_SITE.'/'.$keyword ) );
    -yourls_html_logo();
    -yourls_html_menu();
    -?>
    -
    -

    - -

    : - 1 ) - echo ' '; -} ?>

    -

    :

    - -
    -
    -
      - -
    • -
    • -
    • - -
    • -
    -
    - - - -
    -

    - - - - - - yourls__( 'Last 24 hours' ), - '7' => yourls__( 'Last 7 days' ), - '30' => yourls__( 'Last 30 days' ), - 'all'=> yourls__( 'All time' ), - ); - - // Which graph to generate ? - $do_all = $do_30 = $do_7 = $do_24 = false; - $hits_all = array_sum( $list_of_days ); - $hits_30 = array_sum( array_slice( $list_of_days, -30 ) ); - $hits_7 = array_sum( array_slice( $list_of_days, -7 ) ); - $hits_24 = array_sum( $last_24h ); - if( $hits_all > 0 ) - $do_all = true; // graph for all days range - if( $hits_30 > 0 && count( array_slice( $list_of_days, -30 ) ) == 30 ) - $do_30 = true; // graph for last 30 days - if( $hits_7 > 0 && count( array_slice( $list_of_days, -7 ) ) == 7 ) - $do_7 = true; // graph for last 7 days - if( $hits_24 > 0 ) - $do_24 = true; // graph for last 24 hours - - // Which graph to display ? - $display_all = $display_30 = $display_7 = $display_24 = false; - if( $do_24 ) { - $display_24 = true; - } elseif ( $do_7 ) { - $display_7 = true; - } elseif ( $do_30 ) { - $display_30 = true; - } elseif ( $do_all ) { - $display_all = true; - } - ?> - - - - - - - -
    - - $graphtitle ) { - if( ${'do_'.$graph} == true ) { - $display = ( ${'display_'.$graph} === true ? 'display:block' : 'display:none' ); - echo "
    "; - echo '

    ' . yourls_s( 'Number of hits : %s' , $graphtitle ) . '

    '; - switch( $graph ) { - case '24': - yourls_stats_line( $last_24h, "stat_line_$graph" ); - break; - - case '7': - case '30': - $slice = array_slice( $list_of_days, intval( $graph ) * -1 ); - yourls_stats_line( $slice, "stat_line_$graph" ); - unset( $slice ); - break; - - case 'all': - yourls_stats_line( $list_of_days, "stat_line_$graph" ); - break; - } - echo "
    \n"; - } - } ?> - -
    -

    - -

    -
    -
      - $graphtitle ) { - if ( ${'do_'.$graph} ) { - $link = "$graphtitle"; - } else { - $link = $graphtitle; - } - $stat = ''; - if( ${'do_'.$graph} ) { - switch( $graph ) { - case '7': - case '30': - $stat = yourls_s( '%s per day', round( ( ${'hits_'.$graph} / intval( $graph ) ) * 100 ) / 100 ); - break; - case '24': - $stat = yourls_s( '%s per hour', round( ( ${'hits_'.$graph} / 24 ) * 100 ) / 100 ); - break; - case 'all': - if( $ago > 0 ) - $stat = yourls_s( '%s per day', round( ( ${'hits_'.$graph} / $ago ) * 100 ) / 100 ); - } - } - $hits = sprintf( yourls_n( '%s hit', '%s hits', ${'hits_'.$graph} ), ${'hits_'.$graph} ); - echo "
    • $link ${'hits_'.$graph} $hits $stat
    • \n"; - } - ?> -
    -
    - -

    - -

    %1$s hit on %2$s', '%1$s hits on %2$s', $best['max'] ), $best['max'], yourls_date_i18n( "F j, Y", strtotime( $best['day'] ) ) ); ?>. -

    - - -
    - - - - ' . yourls__( 'No traffic yet. Get some clicks first!' ) . '

    '; - } ?> -
    - - -
    -

    - - - - - - - - - - -
    -

    - -

    - - -
    -

    - -
    - - - - ' . yourls__( 'No country data.' ) . '

    '; - } ?> -
    - - -
    -

    - - - - - - - - - - - -
    -

    - 1 ) - $referrer_sort[ yourls__( 'Others' ) ] = count( $referrers ); - yourls_stats_pie( $referrer_sort, 5, '440x220', 'stat_tab_source_ref' ); - unset( $referrer_sort['Others'] ); - ?> -

    -
      - $count ) { - $i++; - $favicon = yourls_get_favicon_url( $site ); - echo "
    • $site: $count " . yourls__( '(details)' ) . "
    • \n"; - echo "\n"; - unset( $referrers[$site] ); - } - // Any referrer left? Group in "various" - if ( $referrers ) { - echo "
    • " . yourls__( 'Various:' ) . " ". count( $referrers ). " " . yourls__( '(details)' ) . "
    • \n"; - echo "\n"; - } - ?> - -
    - -
    -

    - $direct, yourls__( 'Referrers' ) => $notdirect ), 5, '440x220', 'stat_tab_source_direct' ); - ?> -

    %s hit', '%s hits', $direct ), $direct ); ?>

    -

    %s hit', '%s hits', $notdirect ), $direct ); ?>

    - -
    - - - - ' . yourls__( 'No referrer data.' ) . '

    '; - } ?> - -
    - - - - -
    -

    - - ' . yourls__( 'Short link' ) . '', '

    ' . yourls__( 'Quick Share' ) . '

    '); ?> - -
    - -
    - - - +get_results( yourls_apply_filter( 'stat_query_referrer', $query ) ); + + // Loop through all results and build list of referrers, countries and hits per day + foreach( (array)$rows as $row ) { + if ( $row->referrer == 'direct' ) { + $direct = $row->count; + continue; + } + + $host = yourls_get_domain( $row->referrer ); + if( !array_key_exists( $host, $referrers ) ) + $referrers[$host] = array( ); + if( !array_key_exists( $row->referrer, $referrers[$host] ) ) { + $referrers[$host][$row->referrer] = $row->count; + $notdirect += $row->count; + } else { + $referrers[$host][$row->referrer] += $row->count; + $notdirect += $row->count; + } + } + + // Sort referrers. $referrer_sort is a array of most frequent domains + arsort( $referrers ); + $referrer_sort = array(); + $number_of_sites = count( array_keys( $referrers ) ); + foreach( $referrers as $site => $urls ) { + if( count($urls) > 1 || $number_of_sites == 1 ) + $referrer_sort[$site] = array_sum( $urls ); + } + arsort($referrer_sort); + + + // *** Countries *** + $query = "SELECT `country_code`, COUNT(*) AS `count` FROM `$table` WHERE `shorturl` $keyword_range GROUP BY `country_code`;"; + $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_country', $query ) ); + + // Loop through all results and build list of countries and hits + foreach( (array)$rows as $row ) { + if ("$row->country_code") + $countries["$row->country_code"] = $row->count; + } + + // Sort countries, most frequent first + if ( $countries ) + arsort( $countries ); + + + // *** Dates : array of $dates[$year][$month][$day] = number of clicks *** + $query = "SELECT + DATE_FORMAT(`click_time`, '%Y') AS `year`, + DATE_FORMAT(`click_time`, '%m') AS `month`, + DATE_FORMAT(`click_time`, '%d') AS `day`, + COUNT(*) AS `count` + FROM `$table` + WHERE `shorturl` $keyword_range + GROUP BY `year`, `month`, `day`;"; + $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_dates', $query ) ); + + // Loop through all results and fill blanks + foreach( (array)$rows as $row ) { + if( !array_key_exists($row->year, $dates ) ) + $dates[$row->year] = array(); + if( !array_key_exists( $row->month, $dates[$row->year] ) ) + $dates[$row->year][$row->month] = array(); + if( !array_key_exists( $row->day, $dates[$row->year][$row->month] ) ) + $dates[$row->year][$row->month][$row->day] = $row->count; + else + $dates[$row->year][$row->month][$row->day] += $row->count; + } + + // Sort dates, chronologically from [2007][12][24] to [2009][02][19] + ksort( $dates ); + foreach( $dates as $year=>$months ) { + ksort( $dates[$year] ); + foreach( $months as $month=>$day ) { + ksort( $dates[$year][$month] ); + } + } + + // Get $list_of_days, $list_of_months, $list_of_years + reset( $dates ); + if( $dates ) { + extract( yourls_build_list_of_days( $dates ) ); + } + + + // *** Last 24 hours : array of $last_24h[ $hour ] = number of click *** + $query = "SELECT + DATE_FORMAT(`click_time`, '%H %p') AS `time`, + COUNT(*) AS `count` + FROM `$table` + WHERE `shorturl` $keyword_range AND `click_time` > (CURRENT_TIMESTAMP - INTERVAL 1 DAY) + GROUP BY `time`;"; + $rows = $ydb->get_results( yourls_apply_filter( 'stat_query_last24h', $query ) ); + + $_last_24h = array(); + foreach( (array)$rows as $row ) { + if ( $row->time ) + $_last_24h[ "$row->time" ] = $row->count; + } + + $now = intval( date('U') ); + for ($i = 23; $i >= 0; $i--) { + $h = date('H A', $now - ($i * 60 * 60) ); + // If the $last_24h doesn't have all the hours, insert missing hours with value 0 + $last_24h[ $h ] = array_key_exists( $h, $_last_24h ) ? $_last_24h[ $h ] : 0 ; + } + unset( $_last_24h ); + + // *** Queries all done, phew *** + + // Filter all this junk if applicable. Be warned, some are possibly huge datasets. + $referrers = yourls_apply_filter( 'pre_yourls_info_referrers', $referrers ); + $referrer_sort = yourls_apply_filter( 'pre_yourls_info_referrer_sort', $referrer_sort ); + $direct = yourls_apply_filter( 'pre_yourls_info_direct', $direct ); + $notdirect = yourls_apply_filter( 'pre_yourls_info_notdirect', $notdirect ); + $dates = yourls_apply_filter( 'pre_yourls_info_dates', $dates ); + $list_of_days = yourls_apply_filter( 'pre_yourls_info_list_of_days', $list_of_days ); + $list_of_months = yourls_apply_filter( 'pre_yourls_info_list_of_months', $list_of_months ); + $list_of_years = yourls_apply_filter( 'pre_yourls_info_list_of_years', $list_of_years ); + $last_24h = yourls_apply_filter( 'pre_yourls_info_last_24h', $last_24h ); + $countries = yourls_apply_filter( 'pre_yourls_info_countries', $countries ); + + // I can haz debug data + /** + echo "
    ";
    +	echo "referrers: "; print_r( $referrers );
    +	echo "referrer sort: "; print_r( $referrer_sort );
    +	echo "direct: $direct\n";
    +	echo "notdirect: $notdirect\n";
    +	echo "dates: "; print_r( $dates );
    +	echo "list of days: "; print_r( $list_of_days );
    +	echo "list_of_months: "; print_r( $list_of_months );
    +	echo "list_of_years: "; print_r( $list_of_years );
    +	echo "last_24h: "; print_r( $last_24h );
    +	echo "countries: "; print_r( $countries );
    +	die();
    +	/**/
    +
    +}
    +
    +yourls_html_head( 'infos', yourls_s( 'Statistics for %s', YOURLS_SITE.'/'.$keyword ) );
    +yourls_html_logo();
    +yourls_html_menu();
    +?>
    +
    +

    + +

    : + 1 ) + echo ' '; +} ?>

    +

    :

    + +
    +
    +
      + +
    • +
    • +
    • + +
    • +
    +
    + + + +
    +

    + + + + + + yourls__( 'Last 24 hours' ), + '7' => yourls__( 'Last 7 days' ), + '30' => yourls__( 'Last 30 days' ), + 'all'=> yourls__( 'All time' ), + ); + + // Which graph to generate ? + $do_all = $do_30 = $do_7 = $do_24 = false; + $hits_all = array_sum( $list_of_days ); + $hits_30 = array_sum( array_slice( $list_of_days, -30 ) ); + $hits_7 = array_sum( array_slice( $list_of_days, -7 ) ); + $hits_24 = array_sum( $last_24h ); + if( $hits_all > 0 ) + $do_all = true; // graph for all days range + if( $hits_30 > 0 && count( array_slice( $list_of_days, -30 ) ) == 30 ) + $do_30 = true; // graph for last 30 days + if( $hits_7 > 0 && count( array_slice( $list_of_days, -7 ) ) == 7 ) + $do_7 = true; // graph for last 7 days + if( $hits_24 > 0 ) + $do_24 = true; // graph for last 24 hours + + // Which graph to display ? + $display_all = $display_30 = $display_7 = $display_24 = false; + if( $do_24 ) { + $display_24 = true; + } elseif ( $do_7 ) { + $display_7 = true; + } elseif ( $do_30 ) { + $display_30 = true; + } elseif ( $do_all ) { + $display_all = true; + } + ?> + + + + + + + +
    + + $graphtitle ) { + if( ${'do_'.$graph} == true ) { + $display = ( ${'display_'.$graph} === true ? 'display:block' : 'display:none' ); + echo "
    "; + echo '

    ' . yourls_s( 'Number of hits : %s' , $graphtitle ) . '

    '; + switch( $graph ) { + case '24': + yourls_stats_line( $last_24h, "stat_line_$graph" ); + break; + + case '7': + case '30': + $slice = array_slice( $list_of_days, intval( $graph ) * -1 ); + yourls_stats_line( $slice, "stat_line_$graph" ); + unset( $slice ); + break; + + case 'all': + yourls_stats_line( $list_of_days, "stat_line_$graph" ); + break; + } + echo "
    \n"; + } + } ?> + +
    +

    + +

    +
    +
      + $graphtitle ) { + if ( ${'do_'.$graph} ) { + $link = "$graphtitle"; + } else { + $link = $graphtitle; + } + $stat = ''; + if( ${'do_'.$graph} ) { + switch( $graph ) { + case '7': + case '30': + $stat = yourls_s( '%s per day', round( ( ${'hits_'.$graph} / intval( $graph ) ) * 100 ) / 100 ); + break; + case '24': + $stat = yourls_s( '%s per hour', round( ( ${'hits_'.$graph} / 24 ) * 100 ) / 100 ); + break; + case 'all': + if( $ago > 0 ) + $stat = yourls_s( '%s per day', round( ( ${'hits_'.$graph} / $ago ) * 100 ) / 100 ); + } + } + $hits = sprintf( yourls_n( '%s hit', '%s hits', ${'hits_'.$graph} ), ${'hits_'.$graph} ); + echo "
    • $link ${'hits_'.$graph} $hits $stat
    • \n"; + } + ?> +
    +
    + +

    + +

    %1$s hit on %2$s', '%1$s hits on %2$s', $best['max'] ), $best['max'], yourls_date_i18n( "F j, Y", strtotime( $best['day'] ) ) ); ?>. +

    + + +
    + + + + ' . yourls__( 'No traffic yet. Get some clicks first!' ) . '

    '; + } ?> +
    + + +
    +

    + + + + + + + + + + +
    +

    + +

    + + +
    +

    + +
    + + + + ' . yourls__( 'No country data.' ) . '

    '; + } ?> +
    + + +
    +

    + + + + + + + + + + + +
    +

    + 1 ) + $referrer_sort[ yourls__( 'Others' ) ] = count( $referrers ); + yourls_stats_pie( $referrer_sort, 5, '440x220', 'stat_tab_source_ref' ); + unset( $referrer_sort['Others'] ); + ?> +

    +
      + $count ) { + $i++; + $favicon = yourls_get_favicon_url( $site ); + echo "
    • $site: $count " . yourls__( '(details)' ) . "
    • \n"; + echo "\n"; + unset( $referrers[$site] ); + } + // Any referrer left? Group in "various" + if ( $referrers ) { + echo "
    • " . yourls__( 'Various:' ) . " ". count( $referrers ). " " . yourls__( '(details)' ) . "
    • \n"; + echo "\n"; + } + ?> + +
    + +
    +

    + $direct, yourls__( 'Referrers' ) => $notdirect ), 5, '440x220', 'stat_tab_source_direct' ); + ?> +

    %s hit', '%s hits', $direct ), $direct ); ?>

    +

    %s hit', '%s hits', $notdirect ), $direct ); ?>

    + +
    + + + + ' . yourls__( 'No referrer data.' ) . '

    '; + } ?> + +
    + + + + +
    +

    + + ' . yourls__( 'Short link' ) . '', '

    ' . yourls__( 'Quick Share' ) . '

    '); ?> + +
    + +
    + + + diff --git a/yourls-loader.php b/yourls-loader.php index 8b8cd72..b500fae 100644 --- a/yourls-loader.php +++ b/yourls-loader.php @@ -1,62 +1,62 @@ -