Version Description
- [Update] We should have known better: for every naysayer there is always a yaysayer that will ask for just the opposite feature, right? Fair enough: that's what the settings panels are for, after all. In order to make everybody happy, we introduced a new option (under Slimstat > Settings > Reports) that will allow you to decide what the default time span should be: current month or past 30 days, it's totally up to you.
- [Update] We implemented Chris' idea to use a transient when recording the visit_id, to improve performance (thank you, Chris).
- [Fix] If your website is using a caching plugin, the tracking code might still be appended to the page, even if the user turned off tracking. Slimstat now checks if Tracking is enabled in the settings, before processing any request it receives from the browser (thank you, Chris).
- [Fix] A false positive alert of attempted XSS injection was being returned when the 'android-app' scheme was being used to access the website (thank you, Sasa).
Download this release
Release Info
Developer | coolmann |
Plugin | Slimstat Analytics |
Version | 4.6.8 |
Comparing to | |
See all releases |
Code changes from version 4.6.7 to 4.6.8
- admin/config/index.php +3 -2
- admin/view/index.php +1 -1
- admin/view/wp-slimstat-db.php +18 -1
- admin/wp-slimstat-admin.php +2 -2
- maxmind.php +730 -0
- readme.txt +7 -1
- wp-slimstat.php +25 -7
admin/config/index.php
CHANGED
@@ -154,8 +154,8 @@ $settings = array(
|
|
154 |
4 => array(
|
155 |
'title' => __( 'Reports', 'wp-slimstat' ),
|
156 |
'rows' => array(
|
157 |
-
'reports_basic_header' => array('description' => __('Formats and
|
158 |
-
'use_european_separators' => array('description' => __('Number Format','wp-slimstat'), 'type' => 'toggle', 'long_description' => __('Choose the number format you want to use for your reports.','wp-slimstat'), 'custom_label_on' => '1.234,5', 'custom_label_off' => '1,234.5'),
|
159 |
'date_format' => array('description' => __('Date Format','wp-slimstat'), 'type' => 'text', 'long_description' => __("<a href='http://php.net/manual/en/function.date.php' target='_blank'>PHP Format</a> to use when displaying a pageview's date.", 'wp-slimstat')),
|
160 |
'time_format' => array('description' => __('Time Format','wp-slimstat'), 'type' => 'text', 'long_description' => __("<a href='http://php.net/manual/en/function.date.php' target='_blank'>PHP Format</a> to use when displaying a pageview's time.", 'wp-slimstat')),
|
161 |
'show_display_name' => array('description' => __('Use Display Name','wp-slimstat'), 'type' => 'toggle', 'long_description' => __('By default, users are listed by their usernames. Use this option to visualize their display names instead.','wp-slimstat')),
|
@@ -164,6 +164,7 @@ $settings = array(
|
|
164 |
|
165 |
'reports_functionality_header' => array( 'description' => __( 'Functionality', 'wp-slimstat' ), 'type' => 'section_header' ),
|
166 |
'async_load' => array( 'description' => __( 'Async Mode', 'wp-slimstat' ), 'type' => 'toggle', 'long_description' => __( 'Activate this feature if your reports take a while to load. It breaks down the load on your server into multiple requests, thus avoiding memory issues and performance problems.', 'wp-slimstat' ) ),
|
|
|
167 |
'expand_details' => array('description' => __('Expand Details','wp-slimstat'), 'type' => 'toggle', 'long_description' => __("Expand each row's details by default, insted of on mousehover.",'wp-slimstat')),
|
168 |
'rows_to_show' => array('description' => __('Rows to Display','wp-slimstat'), 'type' => 'integer', 'long_description' => __('Specify the number of items in each report.','wp-slimstat')),
|
169 |
'limit_results' => array( 'description' => __( 'Max Results','wp-slimstat' ), 'type' => 'integer', 'long_description' => __( 'Decide how many records should be retrieved from the database in total. Depending on your server configuration, you may want to fine tune this value to avoid exceeding your PHP memory limit.', 'wp-slimstat' ) ),
|
154 |
4 => array(
|
155 |
'title' => __( 'Reports', 'wp-slimstat' ),
|
156 |
'rows' => array(
|
157 |
+
'reports_basic_header' => array( 'description' => __( 'Data Formats and Conversion', 'wp-slimstat' ), 'type' => 'section_header' ),
|
158 |
+
'use_european_separators' => array( 'description' => __( 'Number Format', 'wp-slimstat' ), 'type' => 'toggle', 'long_description' => __( 'Choose the number format you want to use for your reports.','wp-slimstat' ), 'custom_label_on' => '1.234,5', 'custom_label_off' => '1,234.5' ),
|
159 |
'date_format' => array('description' => __('Date Format','wp-slimstat'), 'type' => 'text', 'long_description' => __("<a href='http://php.net/manual/en/function.date.php' target='_blank'>PHP Format</a> to use when displaying a pageview's date.", 'wp-slimstat')),
|
160 |
'time_format' => array('description' => __('Time Format','wp-slimstat'), 'type' => 'text', 'long_description' => __("<a href='http://php.net/manual/en/function.date.php' target='_blank'>PHP Format</a> to use when displaying a pageview's time.", 'wp-slimstat')),
|
161 |
'show_display_name' => array('description' => __('Use Display Name','wp-slimstat'), 'type' => 'toggle', 'long_description' => __('By default, users are listed by their usernames. Use this option to visualize their display names instead.','wp-slimstat')),
|
164 |
|
165 |
'reports_functionality_header' => array( 'description' => __( 'Functionality', 'wp-slimstat' ), 'type' => 'section_header' ),
|
166 |
'async_load' => array( 'description' => __( 'Async Mode', 'wp-slimstat' ), 'type' => 'toggle', 'long_description' => __( 'Activate this feature if your reports take a while to load. It breaks down the load on your server into multiple requests, thus avoiding memory issues and performance problems.', 'wp-slimstat' ) ),
|
167 |
+
'use_current_month_timespan' => array( 'description' => __( 'Default Time Span', 'wp-slimstat' ), 'type' => 'toggle', 'long_description' => __( 'Determine what is the default time period for calculating all the data in each report: current month or past 30 days. You can always use the time filter dropdown to customize this value even further.', 'wp-slimstat' ), 'custom_label_on' => 'Month', 'custom_label_off' => '30 Days' ),
|
168 |
'expand_details' => array('description' => __('Expand Details','wp-slimstat'), 'type' => 'toggle', 'long_description' => __("Expand each row's details by default, insted of on mousehover.",'wp-slimstat')),
|
169 |
'rows_to_show' => array('description' => __('Rows to Display','wp-slimstat'), 'type' => 'integer', 'long_description' => __('Specify the number of items in each report.','wp-slimstat')),
|
170 |
'limit_results' => array( 'description' => __( 'Max Results','wp-slimstat' ), 'type' => 'integer', 'long_description' => __( 'Decide how many records should be retrieved from the database in total. Depending on your server configuration, you may want to fine tune this value to avoid exceeding your PHP memory limit.', 'wp-slimstat' ) ),
|
admin/view/index.php
CHANGED
@@ -56,7 +56,7 @@
|
|
56 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals '.date_i18n('d', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&month equals '.date_i18n('m', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&year equals '.date_i18n('Y', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&interval equals 0') ?>"><?php _e('Yesterday','wp-slimstat') ?></a>
|
57 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals '.date_i18n('d').'&&&month equals '.date_i18n('m').'&&&year equals '.date_i18n('Y').'&&&interval equals 7&&&interval_direction equals 0') ?>"><?php _e('Last 7 Days','wp-slimstat') ?></a>
|
58 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url( 'hour equals 0&&&day equals ' . date_i18n( 'd' ) . '&&&month equals ' . date_i18n( 'm' ) . '&&&year equals ' . date_i18n( 'Y' ) . '&&&interval equals 60&&&interval_direction equals 0' ) ?>"><?php _e( 'Last 60 Days', 'wp-slimstat' ) ?></a>
|
59 |
-
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url(
|
60 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals 0&&&month equals 0&&&year equals '.date_i18n('Y').'&&&interval equals 0') ?>"><?php _e('This Year So Far','wp-slimstat') ?></a>
|
61 |
<strong><?php _e('Date Range','wp-slimstat') ?></strong>
|
62 |
|
56 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals '.date_i18n('d', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&month equals '.date_i18n('m', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&year equals '.date_i18n('Y', mktime(0, 0, 0, date_i18n('m'), date_i18n('d')-1, date_i18n('Y'))).'&&&interval equals 0') ?>"><?php _e('Yesterday','wp-slimstat') ?></a>
|
57 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals '.date_i18n('d').'&&&month equals '.date_i18n('m').'&&&year equals '.date_i18n('Y').'&&&interval equals 7&&&interval_direction equals 0') ?>"><?php _e('Last 7 Days','wp-slimstat') ?></a>
|
58 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url( 'hour equals 0&&&day equals ' . date_i18n( 'd' ) . '&&&month equals ' . date_i18n( 'm' ) . '&&&year equals ' . date_i18n( 'Y' ) . '&&&interval equals 60&&&interval_direction equals 0' ) ?>"><?php _e( 'Last 60 Days', 'wp-slimstat' ) ?></a>
|
59 |
+
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals '.date_i18n('d').'&&&month equals '.date_i18n('m').'&&&year equals '.date_i18n('Y').'&&&interval equals 90&&&interval_direction equals minus') ?>"><?php _e('Last 90 Days','wp-slimstat') ?></a>
|
60 |
<a class="slimstat-filter-link slimstat-date-choice noslimstat" href="<?php echo wp_slimstat_reports::fs_url('hour equals 0&&&day equals 0&&&month equals 0&&&year equals '.date_i18n('Y').'&&&interval equals 0') ?>"><?php _e('This Year So Far','wp-slimstat') ?></a>
|
61 |
<strong><?php _e('Date Range','wp-slimstat') ?></strong>
|
62 |
|
admin/view/wp-slimstat-db.php
CHANGED
@@ -541,7 +541,7 @@ class wp_slimstat_db {
|
|
541 |
) - 1;
|
542 |
$filters_normalized[ 'utime' ][ 'type' ] = 'm';
|
543 |
}
|
544 |
-
else {
|
545 |
$filters_normalized[ 'utime' ][ 'end' ] = mktime(
|
546 |
date_i18n( 'H' ),
|
547 |
date_i18n( 'i' ),
|
@@ -556,6 +556,23 @@ class wp_slimstat_db {
|
|
556 |
$filters_normalized[ 'date' ][ 'interval' ] = isset( $filters_normalized[ 'date' ][ 'interval' ] ) ? $filters_normalized[ 'date' ][ 'interval' ] : 30;
|
557 |
$filters_normalized[ 'date' ][ 'interval_direction' ] = 0;
|
558 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
559 |
}
|
560 |
else { // An interval was specified
|
561 |
$filters_normalized[ 'utime' ][ 'type' ] = 'interval';
|
541 |
) - 1;
|
542 |
$filters_normalized[ 'utime' ][ 'type' ] = 'm';
|
543 |
}
|
544 |
+
else if ( !empty( wp_slimstat::$settings[ 'use_current_month_timespan' ] ) && wp_slimstat::$settings[ 'use_current_month_timespan' ] == 'no' ) {
|
545 |
$filters_normalized[ 'utime' ][ 'end' ] = mktime(
|
546 |
date_i18n( 'H' ),
|
547 |
date_i18n( 'i' ),
|
556 |
$filters_normalized[ 'date' ][ 'interval' ] = isset( $filters_normalized[ 'date' ][ 'interval' ] ) ? $filters_normalized[ 'date' ][ 'interval' ] : 30;
|
557 |
$filters_normalized[ 'date' ][ 'interval_direction' ] = 0;
|
558 |
}
|
559 |
+
else {
|
560 |
+
$filters_normalized[ 'utime' ][ 'start' ] = mktime(
|
561 |
+
0,
|
562 |
+
0,
|
563 |
+
0,
|
564 |
+
!empty( $filters_normalized[ 'date' ][ 'month' ] )?$filters_normalized[ 'date' ][ 'month' ]:date_i18n( 'n' ),
|
565 |
+
1,
|
566 |
+
!empty( $filters_normalized[ 'date' ][ 'year' ] )?$filters_normalized[ 'date' ][ 'year' ]:date_i18n( 'Y' )
|
567 |
+
);
|
568 |
+
|
569 |
+
$filters_normalized[ 'utime' ][ 'end' ] = strtotime(
|
570 |
+
( !empty( $filters_normalized[ 'date' ][ 'year' ] )?$filters_normalized[ 'date' ][ 'year' ]:date_i18n( 'Y' ) ).'-'.
|
571 |
+
( !empty( $filters_normalized[ 'date' ][ 'month' ] )?$filters_normalized[ 'date' ][ 'month' ]:date_i18n( 'n' ) ).
|
572 |
+
'-01 00:00 +1 month UTC'
|
573 |
+
)-1;
|
574 |
+
$filters_normalized[ 'utime' ][ 'type' ] = 'm';
|
575 |
+
}
|
576 |
}
|
577 |
else { // An interval was specified
|
578 |
$filters_normalized[ 'utime' ][ 'type' ] = 'interval';
|
admin/wp-slimstat-admin.php
CHANGED
@@ -11,9 +11,9 @@ class wp_slimstat_admin {
|
|
11 |
* Init -- Sets things up.
|
12 |
*/
|
13 |
public static function init() {
|
14 |
-
self::$admin_notice = "
|
15 |
|
16 |
-
// "
|
17 |
|
18 |
self::$admin_notice .= '<br/><br/><a id="slimstat-hide-admin-notice" href="#" class="button-secondary">Got it, thanks</a>';
|
19 |
|
11 |
* Init -- Sets things up.
|
12 |
*/
|
13 |
public static function init() {
|
14 |
+
self::$admin_notice = "This has been an incredible year so far for our team. To celebrate all the great achievements, we've decided to launch our Summer Madness Discounts season. From June to September we are <strong>discounting by 50%</strong> all our <a href='http://www.wp-slimstat.com/addons/' target='_blank'>premium add-ons</a>. Including the bundles! No special codes are required: all discounts are automatically applied during checkout. Extend Slimstat with features like Excel exports, email reports, heatmaps and more, for just a fraction of the price you would normally pay. Not only: if you purchased a premium add-on in April or May, you are eligible for getting back FIFTY PERCENT of what you paid in discounts on future purchases. <strong>ON TOP</strong> of the Summer Madness Discount. Stack both to get basically free add-ons for your website! Please <a href='http://support.wp-slimstat.com' target='_blank'>contact us</a> to get your personal RollBack coupon today or for any questions you might have regarding this promo!";
|
15 |
|
16 |
+
// "As those who have been using Slimstat for a while know, we never stop doing our good share of research and development to imrpove this plugin. One request that has been sitting on our wishlist for a while is to make our geolocation functionality more accurate, and track not just a user's Country of origin, but possibly his State (where applicable) and city. In order to geolocate visitors, our code has been leveraging a third-party data file provided by <a href='https://www.maxmind.com/en/home' target='_blank'>MaxMind.com</a>. A while ago, they launched a new data format, which improves performance and offers a way to determine the city of origin. However, the new library required a higher version of PHP, and up until now we had preferred allowing more people to use our plugin, over the chance of offering this feature. Now, we found a way to get the best of both worlds: by customizing their PHP library, we were able to make it work with PHP 5.3! Which means that soon Slimstat will be able to tell you your visitors' city of origin right out of the box. Please contact us if you would like to test this feature in advance."
|
17 |
|
18 |
self::$admin_notice .= '<br/><br/><a id="slimstat-hide-admin-notice" href="#" class="button-secondary">Got it, thanks</a>';
|
19 |
|
maxmind.php
ADDED
@@ -0,0 +1,730 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class maxmind_geolite2 {
|
4 |
+
public static $upload_dir = '';
|
5 |
+
public static $maxmind_path = '';
|
6 |
+
|
7 |
+
public function get_geolocation_info( $_ip = '' ) {
|
8 |
+
|
9 |
+
self::$upload_dir = wp_upload_dir();
|
10 |
+
self::$upload_dir = self::$upload_dir[ 'basedir' ];
|
11 |
+
|
12 |
+
if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
|
13 |
+
self::$upload_dir = str_replace( '/sites/' . get_current_blog_id(), '', self::$upload_dir );
|
14 |
+
}
|
15 |
+
|
16 |
+
self::$upload_dir .= '/wp-slimstat';
|
17 |
+
self::$upload_dir = apply_filters( 'slimstat_maxmind_path', self::$upload_dir );
|
18 |
+
|
19 |
+
self::$maxmind_path = self::$upload_dir . '/GeoLite2-City.mmdb';
|
20 |
+
|
21 |
+
$reader = new Reader( self::$maxmind_path );
|
22 |
+
$record = $reader->get( $_ip );
|
23 |
+
|
24 |
+
if ( !empty( $record[ 'city' ][ 'names' ][ 'en' ] ) ) {
|
25 |
+
// Work in progress
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
public function download_maxmind_db() {
|
30 |
+
// Work in progress
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Instances of this class provide a reader for the MaxMind DB format. IP
|
36 |
+
* addresses can be looked up using the <code>get</code> method.
|
37 |
+
*/
|
38 |
+
class Reader
|
39 |
+
{
|
40 |
+
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
|
41 |
+
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
|
42 |
+
private static $METADATA_START_MARKER_LENGTH = 14;
|
43 |
+
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KB
|
44 |
+
|
45 |
+
private $decoder;
|
46 |
+
private $fileHandle;
|
47 |
+
private $fileSize;
|
48 |
+
private $ipV4Start;
|
49 |
+
private $metadata;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Constructs a Reader for the MaxMind DB format. The file passed to it must
|
53 |
+
* be a valid MaxMind DB file such as a GeoIp2 database file.
|
54 |
+
*
|
55 |
+
* @param string $database
|
56 |
+
* the MaxMind DB file to use.
|
57 |
+
* @throws \InvalidArgumentException for invalid database path or unknown arguments
|
58 |
+
* @throws \MaxMind\Db\Reader\InvalidDatabaseException
|
59 |
+
* if the database is invalid or there is an error reading
|
60 |
+
* from it.
|
61 |
+
*/
|
62 |
+
public function __construct($database)
|
63 |
+
{
|
64 |
+
if (func_num_args() != 1) {
|
65 |
+
throw new \InvalidArgumentException(
|
66 |
+
'The constructor takes exactly one argument.'
|
67 |
+
);
|
68 |
+
}
|
69 |
+
|
70 |
+
if (!is_readable($database)) {
|
71 |
+
throw new \InvalidArgumentException(
|
72 |
+
"The file \"$database\" does not exist or is not readable."
|
73 |
+
);
|
74 |
+
}
|
75 |
+
$this->fileHandle = @fopen($database, 'rb');
|
76 |
+
if ($this->fileHandle === false) {
|
77 |
+
throw new \InvalidArgumentException(
|
78 |
+
"Error opening \"$database\"."
|
79 |
+
);
|
80 |
+
}
|
81 |
+
$this->fileSize = @filesize($database);
|
82 |
+
if ($this->fileSize === false) {
|
83 |
+
throw new \UnexpectedValueException(
|
84 |
+
"Error determining the size of \"$database\"."
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
$start = $this->findMetadataStart($database);
|
89 |
+
$metadataDecoder = new Decoder($this->fileHandle, $start);
|
90 |
+
list($metadataArray) = $metadataDecoder->decode($start);
|
91 |
+
$this->metadata = new Metadata($metadataArray);
|
92 |
+
$this->decoder = new Decoder(
|
93 |
+
$this->fileHandle,
|
94 |
+
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
|
95 |
+
);
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Looks up the <code>address</code> in the MaxMind DB.
|
100 |
+
*
|
101 |
+
* @param string $ipAddress
|
102 |
+
* the IP address to look up.
|
103 |
+
* @return array the record for the IP address.
|
104 |
+
* @throws \BadMethodCallException if this method is called on a closed database.
|
105 |
+
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method.
|
106 |
+
* @throws InvalidDatabaseException
|
107 |
+
* if the database is invalid or there is an error reading
|
108 |
+
* from it.
|
109 |
+
*/
|
110 |
+
public function get($ipAddress)
|
111 |
+
{
|
112 |
+
if (func_num_args() != 1) {
|
113 |
+
throw new \InvalidArgumentException(
|
114 |
+
'Method takes exactly one argument.'
|
115 |
+
);
|
116 |
+
}
|
117 |
+
|
118 |
+
if (!is_resource($this->fileHandle)) {
|
119 |
+
throw new \BadMethodCallException(
|
120 |
+
'Attempt to read from a closed MaxMind DB.'
|
121 |
+
);
|
122 |
+
}
|
123 |
+
|
124 |
+
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
|
125 |
+
throw new \InvalidArgumentException(
|
126 |
+
"The value \"$ipAddress\" is not a valid IP address."
|
127 |
+
);
|
128 |
+
}
|
129 |
+
|
130 |
+
if ($this->metadata->ipVersion == 4 && strrpos($ipAddress, ':')) {
|
131 |
+
throw new \InvalidArgumentException(
|
132 |
+
"Error looking up $ipAddress. You attempted to look up an"
|
133 |
+
. " IPv6 address in an IPv4-only database."
|
134 |
+
);
|
135 |
+
}
|
136 |
+
$pointer = $this->findAddressInTree($ipAddress);
|
137 |
+
if ($pointer == 0) {
|
138 |
+
return null;
|
139 |
+
}
|
140 |
+
return $this->resolveDataPointer($pointer);
|
141 |
+
}
|
142 |
+
|
143 |
+
private function findAddressInTree($ipAddress)
|
144 |
+
{
|
145 |
+
// XXX - could simplify. Done as a byte array to ease porting
|
146 |
+
$rawAddress = array_merge(unpack('C*', inet_pton($ipAddress)));
|
147 |
+
|
148 |
+
$bitCount = count($rawAddress) * 8;
|
149 |
+
|
150 |
+
// The first node of the tree is always node 0, at the beginning of the
|
151 |
+
// value
|
152 |
+
$node = $this->startNode($bitCount);
|
153 |
+
|
154 |
+
for ($i = 0; $i < $bitCount; $i++) {
|
155 |
+
if ($node >= $this->metadata->nodeCount) {
|
156 |
+
break;
|
157 |
+
}
|
158 |
+
$tempBit = 0xFF & $rawAddress[$i >> 3];
|
159 |
+
$bit = 1 & ($tempBit >> 7 - ($i % 8));
|
160 |
+
|
161 |
+
$node = $this->readNode($node, $bit);
|
162 |
+
}
|
163 |
+
if ($node == $this->metadata->nodeCount) {
|
164 |
+
// Record is empty
|
165 |
+
return 0;
|
166 |
+
} elseif ($node > $this->metadata->nodeCount) {
|
167 |
+
// Record is a data pointer
|
168 |
+
return $node;
|
169 |
+
}
|
170 |
+
throw new InvalidDatabaseException("Something bad happened");
|
171 |
+
}
|
172 |
+
|
173 |
+
|
174 |
+
private function startNode($length)
|
175 |
+
{
|
176 |
+
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
|
177 |
+
// is the case, we can skip over the first 96 nodes.
|
178 |
+
if ($this->metadata->ipVersion == 6 && $length == 32) {
|
179 |
+
return $this->ipV4StartNode();
|
180 |
+
}
|
181 |
+
// The first node of the tree is always node 0, at the beginning of the
|
182 |
+
// value
|
183 |
+
return 0;
|
184 |
+
}
|
185 |
+
|
186 |
+
private function ipV4StartNode()
|
187 |
+
{
|
188 |
+
// This is a defensive check. There is no reason to call this when you
|
189 |
+
// have an IPv4 tree.
|
190 |
+
if ($this->metadata->ipVersion == 4) {
|
191 |
+
return 0;
|
192 |
+
}
|
193 |
+
|
194 |
+
if ($this->ipV4Start != 0) {
|
195 |
+
return $this->ipV4Start;
|
196 |
+
}
|
197 |
+
$node = 0;
|
198 |
+
|
199 |
+
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; $i++) {
|
200 |
+
$node = $this->readNode($node, 0);
|
201 |
+
}
|
202 |
+
$this->ipV4Start = $node;
|
203 |
+
return $node;
|
204 |
+
}
|
205 |
+
|
206 |
+
private function readNode($nodeNumber, $index)
|
207 |
+
{
|
208 |
+
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
|
209 |
+
|
210 |
+
// XXX - probably could condense this.
|
211 |
+
switch ($this->metadata->recordSize) {
|
212 |
+
case 24:
|
213 |
+
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
|
214 |
+
list(, $node) = unpack('N', "\x00" . $bytes);
|
215 |
+
return $node;
|
216 |
+
case 28:
|
217 |
+
$middleByte = Util::read($this->fileHandle, $baseOffset + 3, 1);
|
218 |
+
list(, $middle) = unpack('C', $middleByte);
|
219 |
+
if ($index == 0) {
|
220 |
+
$middle = (0xF0 & $middle) >> 4;
|
221 |
+
} else {
|
222 |
+
$middle = 0x0F & $middle;
|
223 |
+
}
|
224 |
+
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 3);
|
225 |
+
list(, $node) = unpack('N', chr($middle) . $bytes);
|
226 |
+
return $node;
|
227 |
+
case 32:
|
228 |
+
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
|
229 |
+
list(, $node) = unpack('N', $bytes);
|
230 |
+
return $node;
|
231 |
+
default:
|
232 |
+
throw new InvalidDatabaseException(
|
233 |
+
'Unknown record size: '
|
234 |
+
. $this->metadata->recordSize
|
235 |
+
);
|
236 |
+
}
|
237 |
+
}
|
238 |
+
|
239 |
+
private function resolveDataPointer($pointer)
|
240 |
+
{
|
241 |
+
$resolved = $pointer - $this->metadata->nodeCount
|
242 |
+
+ $this->metadata->searchTreeSize;
|
243 |
+
if ($resolved > $this->fileSize) {
|
244 |
+
throw new InvalidDatabaseException(
|
245 |
+
"The MaxMind DB file's search tree is corrupt"
|
246 |
+
);
|
247 |
+
}
|
248 |
+
|
249 |
+
list($data) = $this->decoder->decode($resolved);
|
250 |
+
return $data;
|
251 |
+
}
|
252 |
+
|
253 |
+
/*
|
254 |
+
* This is an extremely naive but reasonably readable implementation. There
|
255 |
+
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
|
256 |
+
* an issue, but I suspect it won't be.
|
257 |
+
*/
|
258 |
+
private function findMetadataStart($filename)
|
259 |
+
{
|
260 |
+
$handle = $this->fileHandle;
|
261 |
+
$fstat = fstat($handle);
|
262 |
+
$fileSize = $fstat['size'];
|
263 |
+
$marker = self::$METADATA_START_MARKER;
|
264 |
+
$markerLength = self::$METADATA_START_MARKER_LENGTH;
|
265 |
+
$metadataMaxLengthExcludingMarker
|
266 |
+
= min(self::$METADATA_MAX_SIZE, $fileSize) - $markerLength;
|
267 |
+
|
268 |
+
for ($i = 0; $i <= $metadataMaxLengthExcludingMarker; $i++) {
|
269 |
+
for ($j = 0; $j < $markerLength; $j++) {
|
270 |
+
fseek($handle, $fileSize - $i - $j - 1);
|
271 |
+
$matchBit = fgetc($handle);
|
272 |
+
if ($matchBit != $marker[$markerLength - $j - 1]) {
|
273 |
+
continue 2;
|
274 |
+
}
|
275 |
+
}
|
276 |
+
return $fileSize - $i;
|
277 |
+
}
|
278 |
+
throw new InvalidDatabaseException(
|
279 |
+
"Error opening database file ($filename). " .
|
280 |
+
'Is this a valid MaxMind DB file?'
|
281 |
+
);
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* @throws \InvalidArgumentException if arguments are passed to the method.
|
286 |
+
* @throws \BadMethodCallException if the database has been closed.
|
287 |
+
* @return Metadata object for the database.
|
288 |
+
*/
|
289 |
+
public function metadata()
|
290 |
+
{
|
291 |
+
if (func_num_args()) {
|
292 |
+
throw new \InvalidArgumentException(
|
293 |
+
'Method takes no arguments.'
|
294 |
+
);
|
295 |
+
}
|
296 |
+
|
297 |
+
// Not technically required, but this makes it consistent with
|
298 |
+
// C extension and it allows us to change our implementation later.
|
299 |
+
if (!is_resource($this->fileHandle)) {
|
300 |
+
throw new \BadMethodCallException(
|
301 |
+
'Attempt to read from a closed MaxMind DB.'
|
302 |
+
);
|
303 |
+
}
|
304 |
+
|
305 |
+
return $this->metadata;
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Closes the MaxMind DB and returns resources to the system.
|
310 |
+
*
|
311 |
+
* @throws \Exception
|
312 |
+
* if an I/O error occurs.
|
313 |
+
*/
|
314 |
+
public function close()
|
315 |
+
{
|
316 |
+
if (!is_resource($this->fileHandle)) {
|
317 |
+
throw new \BadMethodCallException(
|
318 |
+
'Attempt to close a closed MaxMind DB.'
|
319 |
+
);
|
320 |
+
}
|
321 |
+
fclose($this->fileHandle);
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
+
class Decoder
|
326 |
+
{
|
327 |
+
|
328 |
+
private $fileStream;
|
329 |
+
private $pointerBase;
|
330 |
+
// This is only used for unit testing
|
331 |
+
private $pointerTestHack;
|
332 |
+
private $switchByteOrder;
|
333 |
+
|
334 |
+
private $types = array(
|
335 |
+
0 => 'extended',
|
336 |
+
1 => 'pointer',
|
337 |
+
2 => 'utf8_string',
|
338 |
+
3 => 'double',
|
339 |
+
4 => 'bytes',
|
340 |
+
5 => 'uint16',
|
341 |
+
6 => 'uint32',
|
342 |
+
7 => 'map',
|
343 |
+
8 => 'int32',
|
344 |
+
9 => 'uint64',
|
345 |
+
10 => 'uint128',
|
346 |
+
11 => 'array',
|
347 |
+
12 => 'container',
|
348 |
+
13 => 'end_marker',
|
349 |
+
14 => 'boolean',
|
350 |
+
15 => 'float',
|
351 |
+
);
|
352 |
+
|
353 |
+
public function __construct(
|
354 |
+
$fileStream,
|
355 |
+
$pointerBase = 0,
|
356 |
+
$pointerTestHack = false
|
357 |
+
) {
|
358 |
+
$this->fileStream = $fileStream;
|
359 |
+
$this->pointerBase = $pointerBase;
|
360 |
+
$this->pointerTestHack = $pointerTestHack;
|
361 |
+
|
362 |
+
$this->switchByteOrder = $this->isPlatformLittleEndian();
|
363 |
+
}
|
364 |
+
|
365 |
+
|
366 |
+
public function decode($offset)
|
367 |
+
{
|
368 |
+
list(, $ctrlByte) = unpack(
|
369 |
+
'C',
|
370 |
+
Util::read($this->fileStream, $offset, 1)
|
371 |
+
);
|
372 |
+
$offset++;
|
373 |
+
|
374 |
+
$type = $this->types[$ctrlByte >> 5];
|
375 |
+
|
376 |
+
// Pointers are a special case, we don't read the next $size bytes, we
|
377 |
+
// use the size to determine the length of the pointer and then follow
|
378 |
+
// it.
|
379 |
+
if ($type == 'pointer') {
|
380 |
+
list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
|
381 |
+
|
382 |
+
// for unit testing
|
383 |
+
if ($this->pointerTestHack) {
|
384 |
+
return array($pointer);
|
385 |
+
}
|
386 |
+
|
387 |
+
list($result) = $this->decode($pointer);
|
388 |
+
|
389 |
+
return array($result, $offset);
|
390 |
+
}
|
391 |
+
|
392 |
+
if ($type == 'extended') {
|
393 |
+
list(, $nextByte) = unpack(
|
394 |
+
'C',
|
395 |
+
Util::read($this->fileStream, $offset, 1)
|
396 |
+
);
|
397 |
+
|
398 |
+
$typeNum = $nextByte + 7;
|
399 |
+
|
400 |
+
if ($typeNum < 8) {
|
401 |
+
throw new InvalidDatabaseException(
|
402 |
+
"Something went horribly wrong in the decoder. An extended type "
|
403 |
+
. "resolved to a type number < 8 ("
|
404 |
+
. $this->types[$typeNum]
|
405 |
+
. ")"
|
406 |
+
);
|
407 |
+
}
|
408 |
+
|
409 |
+
$type = $this->types[$typeNum];
|
410 |
+
$offset++;
|
411 |
+
}
|
412 |
+
|
413 |
+
list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
|
414 |
+
|
415 |
+
return $this->decodeByType($type, $offset, $size);
|
416 |
+
}
|
417 |
+
|
418 |
+
private function decodeByType($type, $offset, $size)
|
419 |
+
{
|
420 |
+
switch ($type) {
|
421 |
+
case 'map':
|
422 |
+
return $this->decodeMap($size, $offset);
|
423 |
+
case 'array':
|
424 |
+
return $this->decodeArray($size, $offset);
|
425 |
+
case 'boolean':
|
426 |
+
return array($this->decodeBoolean($size), $offset);
|
427 |
+
}
|
428 |
+
|
429 |
+
$newOffset = $offset + $size;
|
430 |
+
$bytes = Util::read($this->fileStream, $offset, $size);
|
431 |
+
switch ($type) {
|
432 |
+
case 'utf8_string':
|
433 |
+
return array($this->decodeString($bytes), $newOffset);
|
434 |
+
case 'double':
|
435 |
+
$this->verifySize(8, $size);
|
436 |
+
return array($this->decodeDouble($bytes), $newOffset);
|
437 |
+
case 'float':
|
438 |
+
$this->verifySize(4, $size);
|
439 |
+
return array($this->decodeFloat($bytes), $newOffset);
|
440 |
+
case 'bytes':
|
441 |
+
return array($bytes, $newOffset);
|
442 |
+
case 'uint16':
|
443 |
+
case 'uint32':
|
444 |
+
return array($this->decodeUint($bytes), $newOffset);
|
445 |
+
case 'int32':
|
446 |
+
return array($this->decodeInt32($bytes), $newOffset);
|
447 |
+
case 'uint64':
|
448 |
+
case 'uint128':
|
449 |
+
return array($this->decodeBigUint($bytes, $size), $newOffset);
|
450 |
+
default:
|
451 |
+
throw new InvalidDatabaseException(
|
452 |
+
"Unknown or unexpected type: " . $type
|
453 |
+
);
|
454 |
+
}
|
455 |
+
}
|
456 |
+
|
457 |
+
private function verifySize($expected, $actual)
|
458 |
+
{
|
459 |
+
if ($expected != $actual) {
|
460 |
+
throw new InvalidDatabaseException(
|
461 |
+
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
|
462 |
+
);
|
463 |
+
}
|
464 |
+
}
|
465 |
+
|
466 |
+
private function decodeArray($size, $offset)
|
467 |
+
{
|
468 |
+
$array = array();
|
469 |
+
|
470 |
+
for ($i = 0; $i < $size; $i++) {
|
471 |
+
list($value, $offset) = $this->decode($offset);
|
472 |
+
array_push($array, $value);
|
473 |
+
}
|
474 |
+
|
475 |
+
return array($array, $offset);
|
476 |
+
}
|
477 |
+
|
478 |
+
private function decodeBoolean($size)
|
479 |
+
{
|
480 |
+
return $size == 0 ? false : true;
|
481 |
+
}
|
482 |
+
|
483 |
+
private function decodeDouble($bits)
|
484 |
+
{
|
485 |
+
// XXX - Assumes IEEE 754 double on platform
|
486 |
+
list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits));
|
487 |
+
return $double;
|
488 |
+
}
|
489 |
+
|
490 |
+
private function decodeFloat($bits)
|
491 |
+
{
|
492 |
+
// XXX - Assumes IEEE 754 floats on platform
|
493 |
+
list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits));
|
494 |
+
return $float;
|
495 |
+
}
|
496 |
+
|
497 |
+
private function decodeInt32($bytes)
|
498 |
+
{
|
499 |
+
$bytes = $this->zeroPadLeft($bytes, 4);
|
500 |
+
list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
|
501 |
+
return $int;
|
502 |
+
}
|
503 |
+
|
504 |
+
private function decodeMap($size, $offset)
|
505 |
+
{
|
506 |
+
|
507 |
+
$map = array();
|
508 |
+
|
509 |
+
for ($i = 0; $i < $size; $i++) {
|
510 |
+
list($key, $offset) = $this->decode($offset);
|
511 |
+
list($value, $offset) = $this->decode($offset);
|
512 |
+
$map[$key] = $value;
|
513 |
+
}
|
514 |
+
|
515 |
+
return array($map, $offset);
|
516 |
+
}
|
517 |
+
|
518 |
+
private $pointerValueOffset = array(
|
519 |
+
1 => 0,
|
520 |
+
2 => 2048,
|
521 |
+
3 => 526336,
|
522 |
+
4 => 0,
|
523 |
+
);
|
524 |
+
|
525 |
+
private function decodePointer($ctrlByte, $offset)
|
526 |
+
{
|
527 |
+
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
|
528 |
+
|
529 |
+
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
|
530 |
+
$offset = $offset + $pointerSize;
|
531 |
+
|
532 |
+
$packed = $pointerSize == 4
|
533 |
+
? $buffer
|
534 |
+
: (pack('C', $ctrlByte & 0x7)) . $buffer;
|
535 |
+
|
536 |
+
$unpacked = $this->decodeUint($packed);
|
537 |
+
$pointer = $unpacked + $this->pointerBase
|
538 |
+
+ $this->pointerValueOffset[$pointerSize];
|
539 |
+
|
540 |
+
return array($pointer, $offset);
|
541 |
+
}
|
542 |
+
|
543 |
+
private function decodeUint($bytes)
|
544 |
+
{
|
545 |
+
list(, $int) = unpack('N', $this->zeroPadLeft($bytes, 4));
|
546 |
+
return $int;
|
547 |
+
}
|
548 |
+
|
549 |
+
private function decodeBigUint($bytes, $byteLength)
|
550 |
+
{
|
551 |
+
$maxUintBytes = log(PHP_INT_MAX, 2) / 8;
|
552 |
+
|
553 |
+
if ($byteLength == 0) {
|
554 |
+
return 0;
|
555 |
+
}
|
556 |
+
|
557 |
+
$numberOfLongs = ceil($byteLength / 4);
|
558 |
+
$paddedLength = $numberOfLongs * 4;
|
559 |
+
$paddedBytes = $this->zeroPadLeft($bytes, $paddedLength);
|
560 |
+
$unpacked = array_merge(unpack("N$numberOfLongs", $paddedBytes));
|
561 |
+
|
562 |
+
$integer = 0;
|
563 |
+
|
564 |
+
// 2^32
|
565 |
+
$twoTo32 = '4294967296';
|
566 |
+
|
567 |
+
foreach ($unpacked as $part) {
|
568 |
+
// We only use gmp or bcmath if the final value is too big
|
569 |
+
if ($byteLength <= $maxUintBytes) {
|
570 |
+
$integer = ($integer << 32) + $part;
|
571 |
+
} elseif (extension_loaded('gmp')) {
|
572 |
+
$integer = gmp_strval(gmp_add(gmp_mul($integer, $twoTo32), $part));
|
573 |
+
} elseif (extension_loaded('bcmath')) {
|
574 |
+
$integer = bcadd(bcmul($integer, $twoTo32), $part);
|
575 |
+
} else {
|
576 |
+
throw new \RuntimeException(
|
577 |
+
'The gmp or bcmath extension must be installed to read this database.'
|
578 |
+
);
|
579 |
+
}
|
580 |
+
}
|
581 |
+
return $integer;
|
582 |
+
}
|
583 |
+
|
584 |
+
private function decodeString($bytes)
|
585 |
+
{
|
586 |
+
// XXX - NOOP. As far as I know, the end user has to explicitly set the
|
587 |
+
// encoding in PHP. Strings are just bytes.
|
588 |
+
return $bytes;
|
589 |
+
}
|
590 |
+
|
591 |
+
private function sizeFromCtrlByte($ctrlByte, $offset)
|
592 |
+
{
|
593 |
+
$size = $ctrlByte & 0x1f;
|
594 |
+
$bytesToRead = $size < 29 ? 0 : $size - 28;
|
595 |
+
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
|
596 |
+
$decoded = $this->decodeUint($bytes);
|
597 |
+
|
598 |
+
if ($size == 29) {
|
599 |
+
$size = 29 + $decoded;
|
600 |
+
} elseif ($size == 30) {
|
601 |
+
$size = 285 + $decoded;
|
602 |
+
} elseif ($size > 30) {
|
603 |
+
$size = ($decoded & (0x0FFFFFFF >> (32 - (8 * $bytesToRead))))
|
604 |
+
+ 65821;
|
605 |
+
}
|
606 |
+
|
607 |
+
return array($size, $offset + $bytesToRead);
|
608 |
+
}
|
609 |
+
|
610 |
+
private function zeroPadLeft($content, $desiredLength)
|
611 |
+
{
|
612 |
+
return str_pad($content, $desiredLength, "\x00", STR_PAD_LEFT);
|
613 |
+
}
|
614 |
+
|
615 |
+
private function maybeSwitchByteOrder($bytes)
|
616 |
+
{
|
617 |
+
return $this->switchByteOrder ? strrev($bytes) : $bytes;
|
618 |
+
}
|
619 |
+
|
620 |
+
private function isPlatformLittleEndian()
|
621 |
+
{
|
622 |
+
$testint = 0x00FF;
|
623 |
+
$packed = pack('S', $testint);
|
624 |
+
return $testint === current(unpack('v', $packed));
|
625 |
+
}
|
626 |
+
}
|
627 |
+
|
628 |
+
/**
|
629 |
+
* This class should be thrown when unexpected data is found in the database.
|
630 |
+
*/
|
631 |
+
class InvalidDatabaseException extends \Exception
|
632 |
+
{
|
633 |
+
}
|
634 |
+
|
635 |
+
/**
|
636 |
+
* This class provides the metadata for the MaxMind DB file.
|
637 |
+
*
|
638 |
+
* @property integer nodeCount This is an unsigned 32-bit integer indicating
|
639 |
+
* the number of nodes in the search tree.
|
640 |
+
*
|
641 |
+
* @property integer recordSize This is an unsigned 16-bit integer. It
|
642 |
+
* indicates the number of bits in a record in the search tree. Note that each
|
643 |
+
* node consists of two records.
|
644 |
+
*
|
645 |
+
* @property integer ipVersion This is an unsigned 16-bit integer which is
|
646 |
+
* always 4 or 6. It indicates whether the database contains IPv4 or IPv6
|
647 |
+
* address data.
|
648 |
+
*
|
649 |
+
* @property string databaseType This is a string that indicates the structure
|
650 |
+
* of each data record associated with an IP address. The actual definition of
|
651 |
+
* these structures is left up to the database creator.
|
652 |
+
*
|
653 |
+
* @property array languages An array of strings, each of which is a language
|
654 |
+
* code. A given record may contain data items that have been localized to
|
655 |
+
* some or all of these languages. This may be undefined.
|
656 |
+
*
|
657 |
+
* @property integer binaryFormatMajorVersion This is an unsigned 16-bit
|
658 |
+
* integer indicating the major version number for the database's binary
|
659 |
+
* format.
|
660 |
+
*
|
661 |
+
* @property integer binaryFormatMinorVersion This is an unsigned 16-bit
|
662 |
+
* integer indicating the minor version number for the database's binary format.
|
663 |
+
*
|
664 |
+
* @property integer buildEpoch This is an unsigned 64-bit integer that
|
665 |
+
* contains the database build timestamp as a Unix epoch value.
|
666 |
+
*
|
667 |
+
* @property array description This key will always point to a map
|
668 |
+
* (associative array). The keys of that map will be language codes, and the
|
669 |
+
* values will be a description in that language as a UTF-8 string. May be
|
670 |
+
* undefined for some databases.
|
671 |
+
*/
|
672 |
+
class Metadata
|
673 |
+
{
|
674 |
+
private $binaryFormatMajorVersion;
|
675 |
+
private $binaryFormatMinorVersion;
|
676 |
+
private $buildEpoch;
|
677 |
+
private $databaseType;
|
678 |
+
private $description;
|
679 |
+
private $ipVersion;
|
680 |
+
private $languages;
|
681 |
+
private $nodeByteSize;
|
682 |
+
private $nodeCount;
|
683 |
+
private $recordSize;
|
684 |
+
private $searchTreeSize;
|
685 |
+
|
686 |
+
public function __construct($metadata)
|
687 |
+
{
|
688 |
+
$this->binaryFormatMajorVersion =
|
689 |
+
$metadata['binary_format_major_version'];
|
690 |
+
$this->binaryFormatMinorVersion =
|
691 |
+
$metadata['binary_format_minor_version'];
|
692 |
+
$this->buildEpoch = $metadata['build_epoch'];
|
693 |
+
$this->databaseType = $metadata['database_type'];
|
694 |
+
$this->languages = $metadata['languages'];
|
695 |
+
$this->description = $metadata['description'];
|
696 |
+
$this->ipVersion = $metadata['ip_version'];
|
697 |
+
$this->nodeCount = $metadata['node_count'];
|
698 |
+
$this->recordSize = $metadata['record_size'];
|
699 |
+
$this->nodeByteSize = $this->recordSize / 4;
|
700 |
+
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
|
701 |
+
}
|
702 |
+
|
703 |
+
public function __get($var)
|
704 |
+
{
|
705 |
+
return $this->$var;
|
706 |
+
}
|
707 |
+
}
|
708 |
+
|
709 |
+
class Util
|
710 |
+
{
|
711 |
+
public static function read($stream, $offset, $numberOfBytes)
|
712 |
+
{
|
713 |
+
if ($numberOfBytes == 0) {
|
714 |
+
return '';
|
715 |
+
}
|
716 |
+
if (fseek($stream, $offset) == 0) {
|
717 |
+
$value = fread($stream, $numberOfBytes);
|
718 |
+
|
719 |
+
// We check that the number of bytes read is equal to the number
|
720 |
+
// asked for. We use ftell as getting the length of $value is
|
721 |
+
// much slower.
|
722 |
+
if (ftell($stream) - $offset === $numberOfBytes) {
|
723 |
+
return $value;
|
724 |
+
}
|
725 |
+
}
|
726 |
+
throw new InvalidDatabaseException(
|
727 |
+
"The MaxMind DB file contains bad data"
|
728 |
+
);
|
729 |
+
}
|
730 |
+
}
|
readme.txt
CHANGED
@@ -5,7 +5,7 @@ Tags: analytics, statistics, counter, tracking, reports, wassup, geolocation, on
|
|
5 |
Text Domain: wp-slimstat
|
6 |
Requires at least: 3.8
|
7 |
Tested up to: 4.8
|
8 |
-
Stable tag: 4.6.
|
9 |
|
10 |
== Description ==
|
11 |
The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
|
@@ -71,6 +71,12 @@ Our knowledge base is available on our [support center](http://docs.wp-slimstat.
|
|
71 |
5. **Responsive layout** - Keep an eye on your reports on the go
|
72 |
|
73 |
== Changelog ==
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
= 4.6.7 =
|
75 |
* [Update] Same-domain referrers are now tracked by default, when Slimstat is activated for the first time. Pre-existing installations are not affected. This change will allow us to introduce new features as suggested [by one of our users](https://wordpress.org/support/topic/can-we-see-top-visitor-path/). Stay tuned.
|
76 |
* [Update] Every first of the month we get at least one or two support requests from alarmed users who think their data has disappeared. Some of them end up leaving [negative reviews](https://wordpress.org/support/topic/limited-service/) because of this. The fundamental misunderstanding was related to the fact that Slimstat uses the CURRENT MONTH time span as the default range for calculating its reports and metrics. So every first of the month all the reports can look "empty" because there is not much data to be crunched. In order to address this problem, we've changed the default time range from the current month to the last 20 days. Hopefully this will avoid further negative reviews in the future!
|
5 |
Text Domain: wp-slimstat
|
6 |
Requires at least: 3.8
|
7 |
Tested up to: 4.8
|
8 |
+
Stable tag: 4.6.8
|
9 |
|
10 |
== Description ==
|
11 |
The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
|
71 |
5. **Responsive layout** - Keep an eye on your reports on the go
|
72 |
|
73 |
== Changelog ==
|
74 |
+
= 4.6.8 =
|
75 |
+
* [Update] We should have known better: for every naysayer there is always a yaysayer that will [ask for just the opposite feature](https://wordpress.org/support/topic/recent-month-change/), right? Fair enough: that's what the settings panels are for, after all. In order to make everybody happy, we introduced a new option (under Slimstat > Settings > Reports) that will allow you to decide what the default time span should be: current month or past 30 days, it's totally up to you.
|
76 |
+
* [Update] We implemented Chris' idea to use a transient when recording the visit_id, to improve performance (thank you, [Chris](https://wordpress.org/support/topic/recommend-switching-slimstat_visit_id-to-use-a-transient/)).
|
77 |
+
* [Fix] If your website is using a caching plugin, the tracking code might still be appended to the page, even if the user turned off tracking. Slimstat now checks if Tracking is enabled in the settings, before processing any request it receives from the browser (thank you, Chris).
|
78 |
+
* [Fix] A false positive alert of attempted XSS injection was being returned when the 'android-app' scheme was being used to access the website (thank you, [Sasa](https://wordpress.org/support/topic/false-positive-tracker-error-attempted-xss-injection/)).
|
79 |
+
|
80 |
= 4.6.7 =
|
81 |
* [Update] Same-domain referrers are now tracked by default, when Slimstat is activated for the first time. Pre-existing installations are not affected. This change will allow us to introduce new features as suggested [by one of our users](https://wordpress.org/support/topic/can-we-see-top-visitor-path/). Stay tuned.
|
82 |
* [Update] Every first of the month we get at least one or two support requests from alarmed users who think their data has disappeared. Some of them end up leaving [negative reviews](https://wordpress.org/support/topic/limited-service/) because of this. The fundamental misunderstanding was related to the fact that Slimstat uses the CURRENT MONTH time span as the default range for calculating its reports and metrics. So every first of the month all the reports can look "empty" because there is not much data to be crunched. In order to address this problem, we've changed the default time range from the current month to the last 20 days. Hopefully this will avoid further negative reviews in the future!
|
wp-slimstat.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: Slimstat Analytics
|
4 |
Plugin URI: http://wordpress.org/plugins/wp-slimstat/
|
5 |
Description: The leading web analytics plugin for WordPress
|
6 |
-
Version: 4.6.
|
7 |
Author: Jason Crouse
|
8 |
Author URI: http://www.wp-slimstat.com/
|
9 |
Text Domain: wp-slimstat
|
@@ -15,7 +15,7 @@ if ( !empty( wp_slimstat::$settings ) ) {
|
|
15 |
}
|
16 |
|
17 |
class wp_slimstat {
|
18 |
-
public static $version = '4.6.
|
19 |
public static $settings = array();
|
20 |
public static $options = array(); // To be removed, here just for backward compatibility
|
21 |
|
@@ -133,6 +133,14 @@ class wp_slimstat {
|
|
133 |
* Ajax Tracking
|
134 |
*/
|
135 |
public static function slimtrack_ajax() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
// This function also initializes self::$data_js and removes the checksum from self::$data_js['id']
|
137 |
self::_check_data_integrity( self::$raw_post_array );
|
138 |
|
@@ -244,6 +252,13 @@ class wp_slimstat {
|
|
244 |
* Core tracking functionality
|
245 |
*/
|
246 |
public static function slimtrack( $_argument = '' ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
self::toggle_date_i18n_filters( false );
|
248 |
self::$stat[ 'dt' ] = date_i18n( 'U' );
|
249 |
self::$stat[ 'notes' ] = array();
|
@@ -286,7 +301,7 @@ class wp_slimstat {
|
|
286 |
unset( self::$stat[ 'referer' ] );
|
287 |
}
|
288 |
|
289 |
-
if ( !empty( $referer[ 'scheme' ] ) && !in_array( strtolower( $referer[ 'scheme' ] ), array( 'http', 'https' ) ) ) {
|
290 |
self::_set_error_array( sprintf( __( 'Attempted XSS Injection: %s (IP: %s)', 'wp-slimstat' ), self::$stat[ 'referer' ], self::$stat[ 'ip' ] ), false, 203 );
|
291 |
self::$stat[ 'notes' ][] = sprintf( __( 'Attempted XSS Injection: %s', 'wp-slimstat' ), self::$stat[ 'referer' ] );
|
292 |
unset( self::$stat[ 'referer' ] );
|
@@ -1007,14 +1022,16 @@ class wp_slimstat {
|
|
1007 |
|
1008 |
// User doesn't have an active session
|
1009 |
if ( $is_new_session && ( $_force_assign || self::$settings[ 'javascript_mode' ] == 'yes' ) ) {
|
1010 |
-
if ( empty( self::$settings[ 'session_duration' ] ) )
|
|
|
|
|
1011 |
|
1012 |
-
self::$stat[ 'visit_id' ] =
|
1013 |
-
if ( self::$stat[ 'visit_id' ]
|
1014 |
self::$stat[ 'visit_id' ] = intval( self::$wpdb->get_var( "SELECT MAX( visit_id ) FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats" ) );
|
1015 |
}
|
1016 |
self::$stat[ 'visit_id' ]++;
|
1017 |
-
|
1018 |
|
1019 |
$is_set_cookie = apply_filters( 'slimstat_set_visit_cookie', true );
|
1020 |
if ( $is_set_cookie ) {
|
@@ -1473,6 +1490,7 @@ class wp_slimstat {
|
|
1473 |
'convert_resource_urls_to_titles' => 'yes',
|
1474 |
'convert_ip_addresses' => 'no',
|
1475 |
'async_load' => 'no',
|
|
|
1476 |
'expand_details' => 'no',
|
1477 |
'rows_to_show' => '20',
|
1478 |
'limit_results' => '1000',
|
3 |
Plugin Name: Slimstat Analytics
|
4 |
Plugin URI: http://wordpress.org/plugins/wp-slimstat/
|
5 |
Description: The leading web analytics plugin for WordPress
|
6 |
+
Version: 4.6.8
|
7 |
Author: Jason Crouse
|
8 |
Author URI: http://www.wp-slimstat.com/
|
9 |
Text Domain: wp-slimstat
|
15 |
}
|
16 |
|
17 |
class wp_slimstat {
|
18 |
+
public static $version = '4.6.8';
|
19 |
public static $settings = array();
|
20 |
public static $options = array(); // To be removed, here just for backward compatibility
|
21 |
|
133 |
* Ajax Tracking
|
134 |
*/
|
135 |
public static function slimtrack_ajax() {
|
136 |
+
// If the website is using a caching plugin, the tracking code might still be there, even if the user turned off tracking
|
137 |
+
if ( self::$settings[ 'is_tracking' ] != 'yes' ) {
|
138 |
+
self::$stat[ 'id' ] = -204;
|
139 |
+
self::_set_error_array( __( 'Tracker is turned off, but client-side tracking code is still running.', 'wp-slimstat' ), true );
|
140 |
+
self::slimstat_save_options();
|
141 |
+
exit( self::_get_id_with_checksum( self::$stat[ 'id' ] ) );
|
142 |
+
}
|
143 |
+
|
144 |
// This function also initializes self::$data_js and removes the checksum from self::$data_js['id']
|
145 |
self::_check_data_integrity( self::$raw_post_array );
|
146 |
|
252 |
* Core tracking functionality
|
253 |
*/
|
254 |
public static function slimtrack( $_argument = '' ) {
|
255 |
+
// If the website is using a caching plugin, the tracking code might still be there, even if the user turned off tracking
|
256 |
+
if ( self::$settings['is_tracking'] != 'yes' ) {
|
257 |
+
self::$stat[ 'id' ] = -204;
|
258 |
+
self::_set_error_array( __( 'Tracker is turned off, but client-side tracking code is still running.', 'wp-slimstat' ), true );
|
259 |
+
return $_argument;
|
260 |
+
}
|
261 |
+
|
262 |
self::toggle_date_i18n_filters( false );
|
263 |
self::$stat[ 'dt' ] = date_i18n( 'U' );
|
264 |
self::$stat[ 'notes' ] = array();
|
301 |
unset( self::$stat[ 'referer' ] );
|
302 |
}
|
303 |
|
304 |
+
if ( !empty( $referer[ 'scheme' ] ) && !in_array( strtolower( $referer[ 'scheme' ] ), array( 'http', 'https', 'android-app' ) ) ) {
|
305 |
self::_set_error_array( sprintf( __( 'Attempted XSS Injection: %s (IP: %s)', 'wp-slimstat' ), self::$stat[ 'referer' ], self::$stat[ 'ip' ] ), false, 203 );
|
306 |
self::$stat[ 'notes' ][] = sprintf( __( 'Attempted XSS Injection: %s', 'wp-slimstat' ), self::$stat[ 'referer' ] );
|
307 |
unset( self::$stat[ 'referer' ] );
|
1022 |
|
1023 |
// User doesn't have an active session
|
1024 |
if ( $is_new_session && ( $_force_assign || self::$settings[ 'javascript_mode' ] == 'yes' ) ) {
|
1025 |
+
if ( empty( self::$settings[ 'session_duration' ] ) ) {
|
1026 |
+
self::$settings[ 'session_duration' ] = 1800;
|
1027 |
+
}
|
1028 |
|
1029 |
+
self::$stat[ 'visit_id' ] = get_transient( 'slimstat_visit_id' );
|
1030 |
+
if ( self::$stat[ 'visit_id' ] === false ) {
|
1031 |
self::$stat[ 'visit_id' ] = intval( self::$wpdb->get_var( "SELECT MAX( visit_id ) FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats" ) );
|
1032 |
}
|
1033 |
self::$stat[ 'visit_id' ]++;
|
1034 |
+
set_transient( 'slimstat_visit_id', self::$stat[ 'visit_id' ] );
|
1035 |
|
1036 |
$is_set_cookie = apply_filters( 'slimstat_set_visit_cookie', true );
|
1037 |
if ( $is_set_cookie ) {
|
1490 |
'convert_resource_urls_to_titles' => 'yes',
|
1491 |
'convert_ip_addresses' => 'no',
|
1492 |
'async_load' => 'no',
|
1493 |
+
'use_current_month_timespan' => 'no',
|
1494 |
'expand_details' => 'no',
|
1495 |
'rows_to_show' => '20',
|
1496 |
'limit_results' => '1000',
|