Version Description
Download this release
Release Info
Developer | matomoteam |
Plugin | Matomo Analytics – Ethical Stats. Powerful Insights. |
Version | 4.4.2 |
Comparing to | |
See all releases |
Code changes from version 4.4.1 to 4.4.2
- LEGALNOTICE +6 -1
- assets/chart.js +52 -0
- assets/js/blocks/matomo_opt_out.js +19 -0
- classes/WpMatomo.php +48 -47
- classes/WpMatomo/API.php +34 -28
- classes/WpMatomo/Access.php +4 -5
- classes/WpMatomo/Admin/AccessSettings.php +12 -12
- classes/WpMatomo/Admin/Admin.php +3 -4
- classes/WpMatomo/Admin/AdminSettings.php +34 -36
- classes/WpMatomo/Admin/AdvancedSettings.php +38 -39
- classes/WpMatomo/Admin/Chart.php +20 -0
- classes/WpMatomo/Admin/CookieConsent.php +10 -10
- classes/WpMatomo/Admin/Dashboard.php +73 -61
- classes/WpMatomo/Admin/ExclusionSettings.php +28 -22
- classes/WpMatomo/Admin/GeolocationSettings.php +17 -17
- classes/WpMatomo/Admin/GetStarted.php +12 -14
- classes/WpMatomo/Admin/Info.php +23 -21
- classes/WpMatomo/Admin/Marketplace.php +0 -1
- classes/WpMatomo/Admin/Menu.php +63 -63
- classes/WpMatomo/Admin/PrivacySettings.php +8 -8
- classes/WpMatomo/Admin/SafeModeMenu.php +6 -6
- classes/WpMatomo/Admin/Summary.php +57 -24
- classes/WpMatomo/Admin/SystemReport.php +602 -525
- classes/WpMatomo/Admin/TrackingSettings.php +84 -62
- classes/WpMatomo/Admin/TrackingSettings/Forms.php +16 -12
- classes/WpMatomo/Admin/views/access.php +33 -25
- classes/WpMatomo/Admin/views/advanced_settings.php +52 -39
- classes/WpMatomo/Admin/views/exclusion_settings.php +119 -107
- classes/WpMatomo/Admin/views/geolocation_settings.php +34 -32
- classes/WpMatomo/Admin/views/get_started.php +20 -11
- classes/WpMatomo/Admin/views/info.php +25 -22
- classes/WpMatomo/Admin/views/info_bug_report.php +14 -14
- classes/WpMatomo/Admin/views/info_help.php +10 -6
- classes/WpMatomo/Admin/views/info_high_traffic.php +4 -4
- classes/WpMatomo/Admin/views/info_multisite.php +22 -19
- classes/WpMatomo/Admin/views/info_newsletter.php +15 -15
- classes/WpMatomo/Admin/views/info_shared.php +2 -2
- classes/WpMatomo/Admin/views/marketplace.php +207 -176
- classes/WpMatomo/Admin/views/privacy_gdpr.php +50 -42
- classes/WpMatomo/Admin/views/settings.php +20 -17
- classes/WpMatomo/Admin/views/settings_errors.php +7 -3
- classes/WpMatomo/Admin/views/summary.php +87 -54
- classes/WpMatomo/Admin/views/systemreport.php +89 -68
- classes/WpMatomo/Admin/views/tracking.php +53 -51
- classes/WpMatomo/Annotations.php +8 -6
- classes/WpMatomo/Bootstrap.php +10 -4
- classes/WpMatomo/Capabilities.php +9 -9
- classes/WpMatomo/Commands/MatomoCommands.php +12 -9
- classes/WpMatomo/Compatibility.php +8 -9
- classes/WpMatomo/Db/Settings.php +30 -15
- classes/WpMatomo/Db/WordPress.php +6 -6
- classes/WpMatomo/Db/WordPressDbStatement.php +14 -3
- classes/WpMatomo/Db/WordPressTracker.php +4 -4
- classes/WpMatomo/Ecommerce/Base.php +23 -13
- classes/WpMatomo/Ecommerce/EasyDigitalDownloads.php +23 -23
- classes/WpMatomo/Ecommerce/MatomoTestEcommerce.php +37 -0
- classes/WpMatomo/Ecommerce/MemberPress.php +24 -23
- classes/WpMatomo/Ecommerce/Woocommerce.php +57 -46
- classes/WpMatomo/Email.php +97 -94
- classes/WpMatomo/Installer.php +71 -62
- classes/WpMatomo/Logger.php +15 -14
- classes/WpMatomo/OptOut.php +45 -25
- classes/WpMatomo/Paths.php +12 -4
- classes/WpMatomo/PrivacyBadge.php +3 -5
- classes/WpMatomo/RedirectOnActivation.php +8 -6
- classes/WpMatomo/Referral.php +10 -9
- classes/WpMatomo/Report/Data.php +3 -4
- classes/WpMatomo/Report/Dates.php +3 -5
- classes/WpMatomo/Report/Metadata.php +34 -31
- classes/WpMatomo/Report/Renderer.php +30 -29
- classes/WpMatomo/Report/views/table.php +1 -1
- classes/WpMatomo/Report/views/table_map_no_dimension.php +11 -5
- classes/WpMatomo/Report/views/table_no_dimension.php +1 -1
- classes/WpMatomo/Roles.php +13 -13
- classes/WpMatomo/ScheduledTasks.php +89 -75
- classes/WpMatomo/Settings.php +39 -39
- classes/WpMatomo/Site.php +0 -1
- classes/WpMatomo/Site/Sync.php +89 -84
- classes/WpMatomo/Site/Sync/SyncConfig.php +106 -109
- classes/WpMatomo/TrackingCode.php +22 -16
- classes/WpMatomo/TrackingCode/TrackingCodeGenerator.php +33 -32
- classes/WpMatomo/Uninstaller.php +11 -2
- classes/WpMatomo/Updater.php +42 -35
- classes/WpMatomo/Updater/UpdateInProgressException.php +7 -3
- classes/WpMatomo/User.php +0 -3
- classes/WpMatomo/User/Sync.php +90 -87
- classes/WpMatomo/views/referral.php +8 -7
- config/config.php +1 -0
- matomo.php +56 -52
- node_modules/chart.js/LICENSE.md +9 -0
- node_modules/chart.js/README.md +36 -0
- node_modules/chart.js/dist/chart.min.js +13 -0
- package-lock.json +11 -0
- plugins/WordPress/Menu.php +0 -1
- plugins/WordPress/WordPress.php +7 -2
- plugins/WordPress/stylesheets/user.css +3 -0
- readme.txt +1 -1
LEGALNOTICE
CHANGED
@@ -42,4 +42,9 @@ See a list of all components/libraries and its licenses in Matomo in `app/LEGALN
|
|
42 |
|
43 |
THIRD-PARTY CONTENT
|
44 |
|
45 |
-
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
THIRD-PARTY CONTENT
|
44 |
|
45 |
+
Name: chart.js
|
46 |
+
Link: https://www.chartjs.org/
|
47 |
+
License: The MIT License (MIT) see node_modules/chart.js/LICENSE.md
|
48 |
+
|
49 |
+
For more third party content see the list in `app/LEGALNOTICE`.
|
50 |
+
|
assets/chart.js
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery(document).ready(function(){
|
2 |
+
jQuery('.matomo-table[data-chart]').each(function() {
|
3 |
+
let $this = jQuery(this);
|
4 |
+
let $postbox = $this.parents('div.postbox');
|
5 |
+
let $table = $postbox.find('table');
|
6 |
+
$table.hide();
|
7 |
+
let $canvas = jQuery('<canvas/>',{'id':$this.attr('data-chart')});
|
8 |
+
$canvas.insertAfter($table);
|
9 |
+
let data = [];
|
10 |
+
let labels = [];
|
11 |
+
let title = $postbox.find('h2').text();
|
12 |
+
let $row;
|
13 |
+
let value;
|
14 |
+
$table.find('tr').each(function() {
|
15 |
+
$row = jQuery(this);
|
16 |
+
value = $row.find('td:nth-child(2)').text();
|
17 |
+
if ( '-' === value ) {
|
18 |
+
value = 0;
|
19 |
+
}
|
20 |
+
data.push(value);
|
21 |
+
labels.push($row.find('td:nth-child(1)').text());
|
22 |
+
});
|
23 |
+
|
24 |
+
var myChart = new Chart($canvas, {
|
25 |
+
type: 'line',
|
26 |
+
data: {
|
27 |
+
labels: labels.reverse(),
|
28 |
+
datasets: [{
|
29 |
+
label: title,
|
30 |
+
data: data.reverse(),
|
31 |
+
borderColor: "#55bae7",
|
32 |
+
pointBackgroundColor: "#55bae7",
|
33 |
+
pointBorderColor: "#55bae7",
|
34 |
+
pointHoverBackgroundColor: "#55bae7",
|
35 |
+
pointHoverBorderColor: "#55bae7",
|
36 |
+
}]
|
37 |
+
},
|
38 |
+
options: {
|
39 |
+
plugins: {
|
40 |
+
legend: {
|
41 |
+
display: false
|
42 |
+
}
|
43 |
+
},
|
44 |
+
scales: {
|
45 |
+
y: {
|
46 |
+
beginAtZero: true
|
47 |
+
}
|
48 |
+
}
|
49 |
+
}
|
50 |
+
});
|
51 |
+
});
|
52 |
+
});
|
assets/js/blocks/matomo_opt_out.js
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function (blocks, i18n, element) {
|
2 |
+
var el = element.createElement;
|
3 |
+
var __ = i18n.__;
|
4 |
+
|
5 |
+
const matomo_icon = el('img', {src: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgd2lkdGg9IjI0LjM0NTg3NW1tIgogICBoZWlnaHQ9IjEzLjg0NzIwOG1tIgogICB2aWV3Qm94PSIwIDAgMjQuMzQ1ODc1IDEzLjg0NzIwOCIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnMzk3MiIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi41ICgyMDYwZWMxZjlmLCAyMDIwLTA0LTA4KSIKICAgc29kaXBvZGk6ZG9jbmFtZT0ibG9nby5zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMzOTY2Ij4KICAgIDxjbGlwUGF0aAogICAgICAgaWQ9IlNWR0lEXzJfIj4KICAgICAgPHVzZQogICAgICAgICBpZD0idXNlMzgzMyIKICAgICAgICAgc3R5bGU9Im92ZXJmbG93OnZpc2libGUiCiAgICAgICAgIHhsaW5rOmhyZWY9IiNTVkdJRF8xXyIKICAgICAgICAgeD0iMCIKICAgICAgICAgeT0iMCIKICAgICAgICAgd2lkdGg9IjEwMCUiCiAgICAgICAgIGhlaWdodD0iMTAwJSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8cGF0aAogICAgICAgZD0ibSAxNDEuNzEsMTE1LjUyIDAuMDEsLTAuMDEgLTAuMjQsLTAuMzYgYyAtMC4wNCwtMC4wNiAtMC4wNywtMC4xMSAtMC4xMSwtMC4xNyBsIC0xNi4yNCwtMjQuNzQgLTAuMDEsMC4wMSBjIC0yLjMyLC0zLjc2IC02LjQ1LC02LjI3IC0xMS4xOSwtNi4yNyAtNy4yNiwwIC0xMy4xNSw1Ljg5IC0xMy4xNSwxMy4xNSAwLDMuMzcgMS4yOCw2LjQzIDMuMzYsOC43NSBsIC0wLjAxLDAuMDEgMTUuMzYsMjMuNjQgYyAwLjA3LDAuMSAwLjEzLDAuMiAwLjIsMC4zIGwgMC4wOCwwLjEzIDAuMDEsLTAuMDEgYyAyLjM4LDMuMzggNi4zLDUuNTkgMTAuNzUsNS41OSA3LjI2LDAgMTMuMTUsLTUuODkgMTMuMTUsLTEzLjE1IC0wLjAyLC0yLjUxIC0wLjc0LC00Ljg2IC0xLjk3LC02Ljg3IHoiCiAgICAgICBpZD0iU1ZHSURfMV8iCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogIDwvZGVmcz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6em9vbT0iOS4yNjcxNTc5IgogICAgIGlua3NjYXBlOmN4PSI0Ni4wMDc5NDYiCiAgICAgaW5rc2NhcGU6Y3k9IjI2LjE2Nzk0NSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0ibW0iCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEyMDgiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iNjU2IgogICAgIGlua3NjYXBlOndpbmRvdy14PSI3MiIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMjciCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBmaXQtbWFyZ2luLXRvcD0iMC4xIgogICAgIGZpdC1tYXJnaW4tbGVmdD0iMC4xIgogICAgIGZpdC1tYXJnaW4tcmlnaHQ9IjAuMSIKICAgICBmaXQtbWFyZ2luLWJvdHRvbT0iMC4xIiAvPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTM5NjkiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGRjOnRpdGxlPjwvZGM6dGl0bGU+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjguNDM0MiwtMTcwLjYzNTkyKSI+CiAgICA8ZwogICAgICAgaWQ9ImczODg4IgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC4yNjQ1ODMzMywwLDAsMC4yNjQ1ODMzMywxMTQuNjY3MzksMTQ4LjUxNjIxKSI+CiAgICAgIDxnCiAgICAgICAgIGlkPSJnMzg2MCI+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDM4MjYiCiAgICAgICAgICAgZD0ibSAxNDEuNzEsMTE1LjUyIDAuMDEsLTAuMDEgLTAuMjQsLTAuMzYgYyAtMC4wNCwtMC4wNiAtMC4wNywtMC4xMSAtMC4xMSwtMC4xNyBsIC0xNi4yNCwtMjQuNzQgLTIxLjAxLDE1LjY1IDE1LjM2LDIzLjY0IGMgMC4wNywwLjEgMC4xMywwLjIgMC4yLDAuMyBsIDAuMDgsMC4xMyAwLjAxLC0wLjAxIGMgMi4zOCwzLjM4IDYuMyw1LjU5IDEwLjc1LDUuNTkgNy4yNiwwIDEzLjE1LC01Ljg5IDEzLjE1LC0xMy4xNSAtMC4wMSwtMi41MSAtMC43MywtNC44NiAtMS45NiwtNi44NyB6IgogICAgICAgICAgIGNsYXNzPSJzdDEiCiAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojOTVjNzQ4IiAvPgogICAgICAgIDxjaXJjbGUKICAgICAgICAgICBpZD0iY2lyY2xlMzgyOCIKICAgICAgICAgICByPSIxMy4xNSIKICAgICAgICAgICBjeT0iMTIyLjQiCiAgICAgICAgICAgY3g9IjY1LjU1OTk5OCIKICAgICAgICAgICBjbGFzcz0ic3QyIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiMzNWJmYzAiIC8+CiAgICAgICAgPGcKICAgICAgICAgICBpZD0iZzM4NDgiPgogICAgICAgICAgPGRlZnMKICAgICAgICAgICAgIGlkPSJkZWZzMzgzMSI+CiAgICAgICAgICAgIDxwYXRoCiAgICAgICAgICAgICAgIGQ9Im0gMTQxLjcxLDExNS41MiAwLjAxLC0wLjAxIC0wLjI0LC0wLjM2IGMgLTAuMDQsLTAuMDYgLTAuMDcsLTAuMTEgLTAuMTEsLTAuMTcgbCAtMTYuMjQsLTI0Ljc0IC0wLjAxLDAuMDEgYyAtMi4zMiwtMy43NiAtNi40NSwtNi4yNyAtMTEuMTksLTYuMjcgLTcuMjYsMCAtMTMuMTUsNS44OSAtMTMuMTUsMTMuMTUgMCwzLjM3IDEuMjgsNi40MyAzLjM2LDguNzUgbCAtMC4wMSwwLjAxIDE1LjM2LDIzLjY0IGMgMC4wNywwLjEgMC4xMywwLjIgMC4yLDAuMyBsIDAuMDgsMC4xMyAwLjAxLC0wLjAxIGMgMi4zOCwzLjM4IDYuMyw1LjU5IDEwLjc1LDUuNTkgNy4yNiwwIDEzLjE1LC01Ljg5IDEzLjE1LC0xMy4xNSAtMC4wMiwtMi41MSAtMC43NCwtNC44NiAtMS45NywtNi44NyB6IgogICAgICAgICAgICAgICBpZD0icGF0aDQwMDUiCiAgICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICAgICAgICA8L2RlZnM+CiAgICAgICAgICA8Y2xpcFBhdGgKICAgICAgICAgICAgIGlkPSJjbGlwUGF0aDM5NDgiPgogICAgICAgICAgICA8dXNlCiAgICAgICAgICAgICAgIGlkPSJ1c2UzOTQ2IgogICAgICAgICAgICAgICBzdHlsZT0ib3ZlcmZsb3c6dmlzaWJsZSIKICAgICAgICAgICAgICAgeGxpbms6aHJlZj0iI1NWR0lEXzFfIgogICAgICAgICAgICAgICB4PSIwIgogICAgICAgICAgICAgICB5PSIwIgogICAgICAgICAgICAgICB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgaGVpZ2h0PSIxMDAlIiAvPgogICAgICAgICAgPC9jbGlwUGF0aD4KICAgICAgICAgIDxnCiAgICAgICAgICAgICBpZD0iZzM4NDYiCiAgICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjU1ZHSURfMl8pIgogICAgICAgICAgICAgY2xhc3M9InN0MyI+CiAgICAgICAgICAgIDxpbWFnZQogICAgICAgICAgICAgICBpZD0iaW1hZ2UzODM2IgogICAgICAgICAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0OC43ODE1LDc4LjIzNTcpIgogICAgICAgICAgICAgICB4bGluazpocmVmPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUZjQUFBQkNDQVlBQUFBaTAwcEVBQUFBQ1hCSVdYTUFBQXNTQUFBTEVnSFMzWDc4QUFBQSBHWFJGV0hSVGIyWjBkMkZ5WlFCQlpHOWlaU0JKYldGblpWSmxZV1I1Y2NsbFBBQUFFYzVKUkVGVWVOcnNYRmw3MnppeVJRRWt0ZHVTIDdlenA2ZW4welBOOXVQLy9KOXp2NjNucmg4azJTWng0azdXTEcxRDNGRURac3VNa1hpUmJtUm5tWTBSVEZBRWNGazZkS2dCVTZyL2IgMmpiYWtQS1g2OEhmT1A3cHlxY0hBcFIyZDNmcDFhdFhOQndPS2M5emNzN0plWTdqV0RVYURSZEZFZi94eHgrODFFRCsyY3FuKzdUUSBuWjBkL2VUSkU1Mk5SanBoTnE1ZU4xU1dwb2dpSTQzRHBneVJNMlZwMlpoU3oyWTJhelpkclZaemYvNzVwN3REUXgray9Qc0FsN3JkIHJrYkREQ3pFb0tLeHNpcWh3aVdzYlkzSUpFd2NzZE5hRWJNbXR1eW9VR3d6cmVMTTFWVEJ6SG1hcGlVc3lyNSsvWHJSU043MDh0Y0ogTGoyVDd0ZHE2ZUhPRG5wYm5MQzFkYTFVazBrM3RlTW1ybWt4S2ZsTWlNbWd0bWdkRitSVWlvcE44ZDBNcloyUnRUT3I5UnlXbFpiVCBhZkg0OE5EK24xTHVCMWIwME9XdkRWemEyOXVqNTl2YmtadWtjUmJYR3NyWURtcXpoZSsyRmV1ZS95VDVtN2RRdlFiRGhGQVpHQkhuIE9KN2kreUhPRDNITkVPMFk0THNoQUJoUm9hZE5ubVZwdDFzdWRWWGVzUExYQnE1djJMTnVOeXJtOHhyNHJJMysxbFdhOXBqVkkzYnEgTVVwOUJCdnA0Yk9MNnp2WTYwSjNZam40bCtQdldkVzRnU0k2d2ZFaGFYV0VoaC9DWHZwT3VWSEQybm5aNlJSWE5QQ2h5MThmdUdpWSBsb1k1YVpqV0hVdTBSOW84UmRFdlVmTVhMTDJWK0JHeGIxZ2JleE9WU0ZBekxSWEVjWW5qRE1jVDFHNkU0ejdPSHBEaWZYVExqN2prIEU3cjNZVTJwZ1hadWRxbUJhZ0hzUFpkdnJ3STNXaVd3ditGaDFiUE1ETk0waVV6Y1FRTWVvU0V2NEJCK3hWZS9vZlJmaVBneHFyRlQgTmF5T3ZZYnpFWjgvYUtsb2dUM0ZkVE9jM01NOWRwZ0ozUmw4Q2JWRVFDdGpyWnl4YkFjRC9oOWMvdy84NERIMjNRY28vMzhCYk1YQiBGd0EycTZRRGFyZE4xTzRsRWNWdGJkUWV1dHBMVk80VnZ2b2JxdjQ3S2ZvTHJudUt2M2NDMzZtVzc1S0thdDZwK0oxaWFiQnZPRW5qIHFZSGpScmlHSTgxZU1jRWdWWW51bnBPTnk3SVJ1Ny9QWnFyV2JPcFpweE5IcUltT0RDeFd2VmhiK1Jyc3kzQisxcGJqWnRPT3B0T3YgTEhkbDRQWjZQYlBWNjhYb1hFMkRKdzJvbjZNTHdwalYzN0QvanBML2dncUtjVWwzYkMwYVZQVWVxWWRlMnVWY0ZCcktjYmlPcE92RyBhREF3MEdKb0Jhd3daMlVoc0xSTjIyMHU4SEFwaWhwRzZ4NnM2em02czFqcjM5ZFF2a1FjQlk0eXJYWE90VnFKOXNPSUI3eHlXdmdGIHBYWFF4Q0xMYWdodE9zcVlQWngrZ2YydjFTNFdERWZDSFhRYzZZcm9ocXkvd2ZsbklXbDFqZmFOSjVHZy9qdDBQeTV4SC9BaXB4VHAgUENaakVSUUkyZ1FlaEx6U08zQll6OURTWC9HVFgvRWpzV0NoaUswN2xpK25MVDdFNmMyVnFBcWlXV1J0TmltS1VsMlNaNnV3WE5KQyBCNzFlb3VLNGpjSWVrZFl2NFpsL2w2Nkk3OEYzNm9tWFBvSGpvcXJDUDNLbXRMVHJKZXZ5djBQdkxORTl4ZkdrT0puRHlUaDh4bzRCIElHbnArcUhYVUNpZlF2bU51NVl2WFFiRzZ3QjhqbTltYU9zTXNkMGNuem1jcVQwOVBlV1ZXUzY2Zzk3YTJrS0U0eHB3SE5zd25rY3MgbnBub0Y5UUtubG50UXNKMCtHYkFMbSs2K3BRdUxJSWZHaFZjcTNndWVwVEZnb1Q3akpHR2l6TVM3UXBhVUZLUFBXaFRrVnpvTWJ5SyA4bHVlRGhTTG94dmduc2V5bzMwRGFMblpaRDdQMVpKamkxWklCMjA4UGFHRDUrRXJmb0ZuRE0vTTIzd3ppL2xSQTlFN1ZLQUZWbk4wIFZVUlRsTU5zZ1RkbktCTmRuNlJNY0NzSjBDMWNWOE0xMzZPQ2E1VmZjVEFVQTVTRDRqMzhMUTlRbk9PaDZHSkVnWE44bGxVWmJGWkogQjhyVEFiM0M4ZThlWUdoTTdCM3NTZFd0N3FxcnRmUktXZ2FKbERpWEVnMjFPRVlaMUNGNXFFRVpQTUhsUFpLUVY1elIrVU82NnliVyBtWHVwcGhHNUVZa1ZUMkxtZEd0M3QxZzR0bHRicm1TWXVoRHJFcThyejNNQVY1d1kwVXN2ZDRqRXFiUXJhMXNGc05YdldTS3BXc1hoIEM4Y2kxQ0RXM0ZlU2hHR3hicXJEVWhOWWRzU3JBZFZ6YjlVRGFpRXZRVUkzV3pqWnhIRmlvOGhvK05SS0s5OGFYQ3FLUWtNRXhWRFUgTFNkQUtucUtoa0FWOEhNUHRBQit6ck9yaWdRWHprVWVtRmhzRjQzS3dQV3A2TjZnV1pWNGM0UzBMTWZSSmNkMDUvS0ZXbkFqUEdCUSBCSGdjSVYwZDVTZVFaTkRBckdkbHVhZ24zd3BjU2VIdGRqb1JmbHgzV20vNUtFYnhNM0NkT0REUHN3QTQ4Q3pmbXVkK3lMOHNRUUN6IFJGdkN2NkFGbG1nckZja0Zla0pZQzQwYUxHbWx1Wk9xSnhqUWdVZzBBMnNPa3MwNXhCU1dicTBXdkJNckNsTTRsOWc0N3VDcDdhSUIgRXJ2RFl0VVQ5dDRjWGhWUGMwVjA4QzBMUnNOOElOQUd3SHZDdlNMMHE2U0xKR0p3M2tkZGhsZGJCNllnQjlCMHJrU3Q1SUZKWWplRyBCZDg2aUtCeEc1Rmx0eHRyWTVxUVgxMG9CSWw2bmxWaEpUd29peVdqMFpJZjViV2xOTDJsc085NVFqMWJ2cVVBVzNLd3dyTWtrc3p6IDdwbEtXVkhCTUJzOFNCUW5JWGpCa2tYVFZPSUVoTFlXaFhBN25YdEIwenJuTlMwYUNPbEZMMEtnd0JKYU5vSm5YamtkZkk5LzJZc0kgUEZRQW0xV1cybHdLR2xaVkQvWTlSSnluQkM0S3dRUHgzT2VBY2M0NFo1TjZuVytzYzcvU3RDSEVGV0Jmc253UzdWYnFvTFpHT3JpYSBIa0taRWtRSURSUjByb2xqYjkwQitGWFVSenFJS0lHTVBQM3dCTGNkU2FRR1k4dHpJbnUwdjM5ank3MUFCeUR1SHA3YUUzU0o1NmczIHJKWWZoOHorMnB6WWowWlNUUFdIcVVEMTU4VFJvQXV2cWk0c1doci9GV2hmaXJaUGZKU20xQWltUEFXNDJSemViREtaM014eXZ3cHggdFY1b1dvbkUxcUZwYnd1d1dSNlI0SEM0S3F1VmZJSU5DU09mU0JkZ1Q0bWRCQkV6T1BmY0JIMTdmY3U5Um9pN0xrMTdVNERYV2E1RCBPMjJRZko0S0FLemtGZFN4Z0F5RGsrUk5mbnA2Nm00QzdrVTZZTzVCYnp4bXAxNDhNQjNjNThZZVhPYWlpZ2FIT0Q0Q1JSeWdieHdwIEdlNEJ1RG1pcXRsc2RuMXdyd3B4dVFweE9hVHhlampmckJMTHhGZVBndjdzWUxzcW5FMkZYMzBtVEtuUE1xWUdtampDMzJPcmRWcXYgMSsxU1B2ZUg0SjZGdUlqdG1qRDdIdkI3QXFuOC9BellFRzVLS0NoOFZGNEtOU1ZMUlpjODljOEd0RFJBa3ZPUVhXcUs2dmR4L0FVdCArb2kyZlFZZG5DaXRKMWxaNW5Cazl0b0RsQkxpZGhEaXNnaHhyVHVBYjRjbHhhWjRsOExnbmpnUDhaNlNOQkZwUW53NXVVd1VYUnBHICtkbXMyUnNOVjFZTGF6a0dCcDhGWVB4OUpCUmgwblRlR28zS2o1Y280WnZnbm9XNFJaRndrclJnZkYxSGFnZFBxa3NpekFPRUVnbVYga0NSNmFkQmU3QlNSazc5djdXeWdMK2pQK0ZLMFJCdFBCOEdKNVFnU3h2ZzhRYnRndGJ5UDR3TTA0aFJjTzdYRzVPUFo3TnBENjk2SiB4YTFXYkxSdUVPazJITmkySko1eFl4a0J4Wk9rQVlBZEI3WERZSXd6cnFIcW5vbk1ZdkVqQUFvU2pVa1MzTTBGMEJJYVZ4SGNwb0s4IDdNUm1LdVJyRHpsdzdSZllzNHcraktaNW5nMm1VenY1eG95YjZDb25KaU81enRvRWlEVk5pTnNCRWtuR0NRS2FqbUdlNkJJUTBvcEYgOHhVK1VSMFNHS1JDVWxyVVF3ZXc3YWlRcWQ5bG1mV2lXQjVTQ3hXczhUbGRiS29USzgvVkFTaEEwVDZNNjVOWUxZQWV5Tnl4cU5FbyBKZ2NIVGwwVFhPL0U4anhQWW1NYUVoZ0F0WFpJeENnWkVPeFhDV3FoaEFrZUxmaVdFUFZ4NkJaQ0VheGlnQ2ZqL1RKTlNQSzhqME5TIGg1L2k5NC94b1BZazMwbytCeUhPYnVQazI3bW1sV0NCejNqMm95YTE3eHlmSVBRWEo1YU54Mk9ydmpNUkw3cENlaGtBNEJQQjJCdncgaGpFQWN6SUpUUXBrNG9sRGw4QkZZeFNXc3FhQ2JDbVRXMzJ0Y0wwaDUrUmh5RmhUbDJXZWxmQ1ZIOUNUZUp4S0g2TDcwWm9Md05JRyAwc0dwOHZYblQ2Q0JULzdZMnFISjgyODZzVytCNnhPOXNGeURHOFd4MWdsdUhsZVpvSmtmU21ZYUs0UjdPRDlCMkRkMUdzV1VoVFVWIFZrNmtGMEo2U0xjSU42czVCNjRpZllyZnlXekJDUWRKNHkrcTBvVm1pYWMza0E1OGtQQVI0SDVBM2IrdzQ3NkQ5RUtGdituRXZndXUgZVBNb2lqVExzZFl5RndBeWhHWk9yRTdMeERROTBscmpXT2UxUnFNOC9QQ0JqVEVCWE9ka3lqdHQ3ZTdxMldnMHQyVTVUMHcwaFpTVCB5dWJPa3pKSDFUQjdqY0xETTFWdTlxSHA0U282Z0RMZ0Q2alVQZ3ZReG94Z2ZHbC9QQzVIMTVqOGZNRmlndDZIYlRKYmhMc2lRYVlzIEV4N1lsUUJvU3BxbVJWN01BR0ErR0F6S0Zrb0U3L0RsZXd6SFl4cUJqeUt0eSsxMlc4YjVyZWF6OGhvVXBtMzZlVnFLZVFGdy9JRDAgOEQwNjJKZWhjMURkRU5wMEhvTU9QdjJBRHE0Q0Y1STJjUUN1Vk5ibUxpdkFqNUVWZTdKa0M4Y3VSUkFvWVY3UjcvZnR5Y21KT3pvNiArcW9BZ0s2cW9XV1d0UVFtanJuWmFDaG5aV0NQWWppL3BrelpZMUVOck9TNGhnWXNSbWlqcXRMMEFIU3dIT0lLSFh3Q3VCL1IyVDdEIDJrNXVRZ2RxT1E5NmxxTGprTkJ2eExIU1JlRktFNVZhNmRTVVVRcDFtclZhclFLZ0NyRFhXaE1BbWxGbENhTUhiVVFBV1F1OHdnemEgUjI0MUN2UlFQMHRzbjArRXUwOXdmWjZXQWgyZ3Q1Tk04UGdYYXZBYW4yOVpISmt4L2R6YTJlbDRYUFN6N05xTFhTN29UQUNoc2l4VCBNWUJvZFRxMkpDNlRXbElZYllyandiRlBUSHorL1BrbWl6MDh3SWoybEl4a3NnNXpPZ2lLUWpxS1RPZXR3RjFNNFl4VUdFbWxld1JZIHJMYmdNTERaOS95cTZDMXM3QTMrL29ENkhrVlpOcWtOaC9sK3lCL2NEbHhwRUp3U2w5WWlER05PMDlSbWVlWnkvRHM4UEhURDRmQlcgYThFU0FFeTFtdEtOQmp1MW1EWURnRm1GdWJCVWhjcmV3ZkY5aHNqQmlSRkpia1RXUFh4Qm5kNlRXQzJwOTZqRWx3Z0JBK3FjanZyOSBjbnJESlZyUkZjU3U1dk01WTZmbGMzZlpKSlUweXpLM2xXVkZNMjZFN0ZJRUhSM3l3R0dTTWZzOXZrZDV0cElROXlhV3U3Wk5LR2U3IEtMZ0pqNVpGRWN2YUdRcnl3aXNGOWhUQmRaSzVDSkpOQytmWFNROExUUnZVQVN2SXJqTTZlTytCMW5wTWNaeWgxOXJiR05sOWluZEcgV0szbUxXZmpScEl4cEEwYUlITzVZTDNVOWt1V0Z1c1UxaS9QVmhiaWJvVGxLaFU4UmdHbkpoSk5acVk0Q2VuWTV4ZkVVaE94M3BEUyBKRkVTOGFVOE1LMlVEa0tPWklLeWpvUHNVbTgwc3lpRWovREN4OUMwazJRd3lEL2QwSWs5R0xpeUljSlJZc0VBbDZNb1pycC9lWGFKIERoZ1dxOTZnRHEraGFkL0RqeDg0aEw2eVd2STJUdXhCd1Yzdzd3UEpzNFU2U0xGTHNQQUZ2ZVU5enY4VE4zNTNGMDI3TWVBK2tEejcgSGgyOFVSVG9JQUlkM0ViVGJoUzRNcjg5azZVYlVhUmluYkRHUDZYOWdLWk13SThxY092VnpNWG9qdnk3QUxha0MrcEF2VjJpZ3krciBvb01IQi9lYThxeEdZUmIzQW1COUM0RDVRaXBSMXZSS3NBQzVoUzllQTh5M1VDNzdxNlNEalFCWHRwclFRNk91NHEyMjUwUUtTUTVkIE9iTmFOUW9TbkJ0ZFdJeDNIWURQZ1EzTHFtU2cwZWRveFlscHhXOFc2bUNWZExBeDRDN0xNMk5BdWRhR3hDNDhuSkozSUpCazB2ekkgY1V4K1lQTnNaYzFsQjNjNW9xem1ISGlMbFp5MExKSStramtIUG5lZzZEVUpIVGgzYUZkTUJ4c0Q3ckk4azVuWk1jVk95Mmk5RHF2cCBLdEFNaFpVNmxXb2dQd2Y1SEZWYXpuQnhsVDRVYTgwbDBWOWx1NDdJanlxb3QwRjZxZmZRMlB0c3pXbXV5cFhTd1VhQit4WC94bDcvIEl1N0g3a0VNODAxOG9qY000MysxeXdJL0NxRG1hckhLUjFTQmpOMlJPb1MxZmdyQWVpcDRwMWp2VytKK21lZVQ3bmlZZjVoTW5GcnggMjZBMlplenFMRHpPMm13YmlVbFZYZ3pCRTlvSnRsckwxRTFaTlRsakdTZ2xQK3RIMXZHMktrMGMwL243RW16MXZvUjVHTGRUb2d3TyAvZnNTb0dseHdiK1VkWitOY3NlUkxTZmNhUlFuaC9zckIzYlR3RlhIa2oxTFUvZTAzUzVhY1NRekI1V05JZ2NtTG1TNENkWTNKSm0yIHlUSmN6ekpYYlpHUFNBSnR5TEJ5OWE0RVA2SWdhVVQvcG84RGRJZ3ZBRjZpc1VONHpYNXN5ekUwZHZaNU1DaVAxL1Qrc2syYzdVTE4gWnBPZTd1M3BwakZ4a2RzNjY2ak4yc3BzZHV4cUYrRHVLbGwrR2w2aklrdFFHMkVXejlrN2FpYXFlbzBLR3RqSHlST0FmNnBsRWFEViBJNlB5YVZTdjV4N1k0Mk5XLzBIZ25nSDh2RjdYVGFYTnBOV3VBVHBaVE5lRzViYjl4RURtRHF5eXpZcXJUSnFzQ2ZNcm1EemZ3bG1OIFpZNEZUb3lKM1pobERFeVdrK1kyM1o0TWl2ZTFtbDBuc0pzTXJ0LzJKRHhMRXJLOW5tbTMyM0ZaRkhGa2pDekRxbE1wczNwY0E1NnYgRGc2T1ZmVmVNQ1d6ZjVneUFEb0gzaWtDNXhRNk5pMWgwWEVjRi8yREE5dUZsbjI3L2xjWi9oUlRPYjBWQTF3b1g2MjN0N2NqbTJiRyBsQzR1RGNYbzZwR0x0SEV1T0RRakM2MnRLMlZoU0dTNUtPcXh4Yy9LbzZNaksxcjY0T0JncmRhNmtWTHNCenFZcDlPcG4zd2lHblkyIGxzbmNxbWozdWptenl4MENBQWRMaFh5YlEyRE1ramhLbzFxU25wNGNGZk95RkFkblpReHdlc1Y3YVA3VExmZktlbmU3WFpuYlJtbWEga294WWh4bFZZWGxvbzlId24rL2V2YnMzSy8xM0F2ZDc3V0QxMyszZmYvdC9BUVlBNjZnMU1DdTRUUjhBQUFBQVNVVk9SSzVDWUlJPSIKICAgICAgICAgICAgICAgaGVpZ2h0PSI2NSIKICAgICAgICAgICAgICAgd2lkdGg9Ijg2IgogICAgICAgICAgICAgICBzdHlsZT0ib3ZlcmZsb3c6dmlzaWJsZTtvcGFjaXR5OjAuNSIgLz4KICAgICAgICAgICAgPGcKICAgICAgICAgICAgICAgaWQ9ImczODQ0Ij4KICAgICAgICAgICAgICA8ZwogICAgICAgICAgICAgICAgIGlkPSJnMzg0MiI+CiAgICAgICAgICAgICAgICA8cGF0aAogICAgICAgICAgICAgICAgICAgaWQ9InBhdGgzODM4IgogICAgICAgICAgICAgICAgICAgZD0ibSAxMjcuMTQsOTcuMTUgYyAwLC03LjI2IC01Ljg5LC0xMy4xNSAtMTMuMTUsLTEzLjE1IC03LjI2LDAgLTEzLjE1LDUuODkgLTEzLjE1LDEzLjE1IDAsMi42NyAwLjgsNS4xNSAyLjE3LDcuMjIgMCwwIDAsMCAwLDAgSCAxMDMgTCA5My4wMiw5MC4yNiBIIDkzIEMgOTAuNjgsODYuNTEgODYuNTUsODQgODEuODEsODQgYyAtNC43NCwwIC04Ljg3LDIuNTEgLTExLjE4LDYuMjYgaCAtMC4wMSBsIC0xNS45LDI0Ljc4IGMgMi4zNiwtMy40OSA2LjM2LC01Ljc5IDEwLjksLTUuNzkgNC43NywwIDguOTQsMi41NSAxMS4yNCw2LjM2IGggMC4wMiBsIDEwLjM5LDE0LjU5IGggMC4wMiBjIDIuMzksMy4yNCA2LjIzLDUuMzYgMTAuNTcsNS4zNiA0LjM0LDAgOC4xOCwtMi4xMSAxMC41NywtNS4zNiBoIDAuMDIgbCAwLjExLC0wLjE3IGMgMC4yNywtMC4zOCAwLjUyLC0wLjc3IDAuNzYsLTEuMTggbCAxNS41NywtMjQuMzMgYyAwLDAgMCwwIDAsMCAxLjQxLC0yLjEgMi4yNSwtNC42NCAyLjI1LC03LjM3IHogTSAxMDQsMTA1LjY5IGMgLTAuMDEsLTAuMDIgLTAuMDMsLTAuMDMgLTAuMDUsLTAuMDUgMC4wMiwwLjAxIDAuMDMsMC4wMyAwLjA1LDAuMDUgeiBtIDAuOTcsMS4wMSBjIDAuMDgsMC4wNyAwLjE2LDAuMTQgMC4yMywwLjIxIC0wLjA3LC0wLjA2IC0wLjE1LC0wLjEzIC0wLjIzLC0wLjIxIHogbSAxLjE3LDAuOTggYyAwLjA5LDAuMDYgMC4xNywwLjEzIDAuMjYsMC4xOSAtMC4wOSwtMC4wNiAtMC4xNywtMC4xMiAtMC4yNiwtMC4xOSB6IG0gMS40OSwwLjk4IGMgMC4wMSwwIDAuMDIsMC4wMSAwLjAzLDAuMDIgLTAuMDEsLTAuMDEgLTAuMDIsLTAuMDIgLTAuMDMsLTAuMDIgeiBtIDEuNCwwLjY2IGMgMC4xMSwwLjA0IDAuMjIsMC4wOCAwLjM0LDAuMTIgLTAuMTIsLTAuMDQgLTAuMjMsLTAuMDcgLTAuMzQsLTAuMTIgeiBtIDEuNDEsMC40OCBjIDAuMTYsMC4wNCAwLjMxLDAuMDggMC40NywwLjEyIC0wLjE1LC0wLjA0IC0wLjMxLC0wLjA4IC0wLjQ3LC0wLjEyIHogbSAxLjU0LDAuMzMgYyAwLjE1LDAuMDIgMC4zLDAuMDUgMC40NiwwLjA3IC0wLjE2LC0wLjAyIC0wLjMxLC0wLjA1IC0wLjQ2LC0wLjA3IHogbSAxMC42NSwtMy4wOSBjIDAuMTIsLTAuMSAwLjIzLC0wLjIxIDAuMzUsLTAuMzEgLTAuMTEsMC4xIC0wLjIzLDAuMiAtMC4zNSwwLjMxIHogbSAtNy4xMSwzLjE2IGMgMC4xNiwtMC4wMiAwLjMxLC0wLjA1IDAuNDYsLTAuMDcgLTAuMTUsMC4wMiAtMC4zLDAuMDUgLTAuNDYsMC4wNyB6IG0gMS40OSwtMC4yNyBjIDAuMTcsLTAuMDQgMC4zMywtMC4wOCAwLjUsLTAuMTMgLTAuMTYsMC4wNSAtMC4zMywwLjA5IC0wLjUsMC4xMyB6IG0gMS40OSwtMC40NSBjIDAuMTQsLTAuMDUgMC4yOCwtMC4xIDAuNDIsLTAuMTUgLTAuMTQsMC4wNiAtMC4yOCwwLjEgLTAuNDIsMC4xNSB6IG0gMS43NCwtMC43NiBjIDAsMCAwLDAgMCwwIDAsMCAwLDAgMCwwIHogbSAxLjI0LC0wLjc4IGMgMC4xMSwtMC4wOCAwLjIyLC0wLjE2IDAuMzMsLTAuMjQgLTAuMTEsMC4wOCAtMC4yMSwwLjE2IC0wLjMzLDAuMjQgeiBtIDIuMjMsLTEuOTcgYyAwLjA5LC0wLjEgMC4xOSwtMC4yIDAuMjgsLTAuMzEgLTAuMDksMC4xMSAtMC4xOCwwLjIxIC0wLjI4LDAuMzEgeiIKICAgICAgICAgICAgICAgICAgIGNsYXNzPSJzdDQiCiAgICAgICAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzMyNTJhMCIgLz4KICAgICAgICAgICAgICAgIDxwYXRoCiAgICAgICAgICAgICAgICAgICBpZD0icGF0aDM4NDAiCiAgICAgICAgICAgICAgICAgICBkPSJtIDU0LjQsMTE1LjU2IGMgMC4xLC0wLjE2IDAuMTksLTAuMzIgMC4yOSwtMC40OCBsIC0wLjMxLDAuNDggeiIKICAgICAgICAgICAgICAgICAgIGNsYXNzPSJzdDQiCiAgICAgICAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzMyNTJhMCIgLz4KICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICAgICAgPGNpcmNsZQogICAgICAgICAgIGlkPSJjaXJjbGUzODUwIgogICAgICAgICAgIHI9IjEzLjE4IgogICAgICAgICAgIGN5PSI5Ny4xNjk5OTgiCiAgICAgICAgICAgY3g9IjExMy45NiIKICAgICAgICAgICBjbGFzcz0ic3Q1IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmMzgzMzQiIC8+CiAgICAgICAgPGcKICAgICAgICAgICBpZD0iZzM4NTgiPgogICAgICAgICAgPHBhdGgKICAgICAgICAgICAgIGlkPSJwYXRoMzg1MiIKICAgICAgICAgICAgIGQ9Im0gMTEzLjkzLDExMC4zIGMgLTQuNTksMCAtOC42MywtMi4zNiAtMTAuOTgsLTUuOTMgaCAtMC4wMSBMIDkyLjk2LDkwLjI2IEggOTIuOTUgQyA5MC42Myw4Ni41MSA4Ni40OSw4NCA4MS43Niw4NCBjIC00LjczLDAgLTguODcsMi41MSAtMTEuMTgsNi4yNiBoIC0wLjAxIGwgLTE1LjksMjQuNzggYyAyLjM2LC0zLjQ5IDYuMzYsLTUuNzkgMTAuOSwtNS43OSA0Ljc3LDAgOC45NCwyLjU1IDExLjI0LDYuMzYgaCAwLjAyIGwgMTAuMzksMTQuNTkgaCAwLjAyIGMgMi4zOSwzLjI0IDYuMjMsNS4zNiAxMC41Nyw1LjM2IDQuMzQsMCA4LjE4LC0yLjExIDEwLjU3LC01LjM2IGggMC4wMiBsIDAuMTEsLTAuMTcgYyAwLjI3LC0wLjM4IDAuNTIsLTAuNzcgMC43NiwtMS4xOCBsIDE1LjYyLC0yNC4zMyBjIC0yLjM4LDMuNDkgLTYuNDMsNS43OCAtMTAuOTYsNS43OCB6IgogICAgICAgICAgICAgY2xhc3M9InN0NiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojMzE1MmEwIiAvPgogICAgICAgICAgPHBhdGgKICAgICAgICAgICAgIGlkPSJwYXRoMzg1NCIKICAgICAgICAgICAgIGQ9Im0gNTQuMzMsMTE1LjU2IGggMC4wMiBjIDAuMSwtMC4xNiAwLjE5LC0wLjMyIDAuMjksLTAuNDggeiIKICAgICAgICAgICAgIGNsYXNzPSJzdDYiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzMxNTJhMCIgLz4KICAgICAgICAgIDxwYXRoCiAgICAgICAgICAgICBpZD0icGF0aDM4NTYiCiAgICAgICAgICAgICBkPSJNIDEyNC44OCwxMDQuNDMiCiAgICAgICAgICAgICBjbGFzcz0ic3Q2IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiMzMTUyYTAiIC8+CiAgICAgICAgPC9nPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'});
|
6 |
+
|
7 |
+
blocks.registerBlockType('matomo/matomo-opt-out', {
|
8 |
+
title: __('Matomo opt out', 'matomo'),
|
9 |
+
icon: matomo_icon,
|
10 |
+
category: 'text',
|
11 |
+
example: {},
|
12 |
+
edit: function () {
|
13 |
+
return __('Matomo opt out', 'matomo');
|
14 |
+
},
|
15 |
+
save: function () {
|
16 |
+
return '[matomo_opt_out]';
|
17 |
+
},
|
18 |
+
});
|
19 |
+
})(window.wp.blocks, window.wp.i18n, window.wp.element);
|
classes/WpMatomo.php
CHANGED
@@ -11,32 +11,32 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
11 |
exit; // if accessed directly
|
12 |
}
|
13 |
|
14 |
-
use WpMatomo\Admin\
|
|
|
15 |
use WpMatomo\Admin\Dashboard;
|
|
|
|
|
|
|
|
|
|
|
16 |
use WpMatomo\Commands\MatomoCommands;
|
17 |
use WpMatomo\Ecommerce\EasyDigitalDownloads;
|
18 |
use WpMatomo\Ecommerce\MemberPress;
|
|
|
|
|
19 |
use WpMatomo\OptOut;
|
20 |
use WpMatomo\Paths;
|
21 |
-
use WpMatomo\ScheduledTasks;
|
22 |
-
use \WpMatomo\Site\Sync as SiteSync;
|
23 |
-
use WpMatomo\AjaxTracker;
|
24 |
-
use \WpMatomo\User\Sync as UserSync;
|
25 |
-
use \WpMatomo\Installer;
|
26 |
-
use \WpMatomo\Updater;
|
27 |
-
use \WpMatomo\Roles;
|
28 |
-
use \WpMatomo\Annotations;
|
29 |
-
use \WpMatomo\TrackingCode;
|
30 |
-
use \WpMatomo\Settings;
|
31 |
-
use \WpMatomo\Capabilities;
|
32 |
-
use \WpMatomo\Ecommerce\Woocommerce;
|
33 |
-
use \WpMatomo\Report\Renderer;
|
34 |
-
use WpMatomo\API;
|
35 |
-
use \WpMatomo\Admin\Admin;
|
36 |
use WpMatomo\RedirectOnActivation;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
class WpMatomo {
|
39 |
-
|
40 |
/**
|
41 |
* @var Settings
|
42 |
*/
|
@@ -58,7 +58,7 @@ class WpMatomo {
|
|
58 |
return;
|
59 |
}
|
60 |
|
61 |
-
add_action( 'init',
|
62 |
|
63 |
$capabilities = new Capabilities( self::$settings );
|
64 |
$capabilities->register_hooks();
|
@@ -97,10 +97,13 @@ class WpMatomo {
|
|
97 |
$referral->register_hooks();
|
98 |
}
|
99 |
|
|
|
|
|
|
|
100 |
/*
|
101 |
* @see https://github.com/matomo-org/matomo-for-wordpress/issues/434
|
102 |
*/
|
103 |
-
$redirect = new RedirectOnActivation($this);
|
104 |
$redirect->register_hooks();
|
105 |
}
|
106 |
|
@@ -115,10 +118,10 @@ class WpMatomo {
|
|
115 |
|
116 |
add_filter(
|
117 |
'plugin_action_links_' . plugin_basename( MATOMO_ANALYTICS_FILE ),
|
118 |
-
|
119 |
$this,
|
120 |
'add_settings_link',
|
121 |
-
|
122 |
);
|
123 |
}
|
124 |
|
@@ -134,7 +137,7 @@ class WpMatomo {
|
|
134 |
$upload_path = $paths->get_upload_base_dir();
|
135 |
|
136 |
if ( $upload_path
|
137 |
-
|
138 |
add_action(
|
139 |
'init',
|
140 |
function () use ( $upload_path ) {
|
@@ -142,7 +145,7 @@ class WpMatomo {
|
|
142 |
add_action(
|
143 |
'admin_notices',
|
144 |
function () use ( $upload_path ) {
|
145 |
-
echo '<div class="error"><p>' . sprintf(
|
146 |
}
|
147 |
);
|
148 |
}
|
@@ -157,24 +160,13 @@ class WpMatomo {
|
|
157 |
|
158 |
public static function is_admin_user() {
|
159 |
if ( ! function_exists( 'is_multisite' )
|
160 |
-
|
161 |
return current_user_can( 'administrator' );
|
162 |
}
|
163 |
|
164 |
return is_super_admin();
|
165 |
}
|
166 |
|
167 |
-
private static function get_active_plugins() {
|
168 |
-
$plugins = [];
|
169 |
-
if ( function_exists( 'is_multisite' ) && is_multisite() ) {
|
170 |
-
$muplugins = get_site_option( 'active_sitewide_plugins' );
|
171 |
-
$plugins = array_keys( $muplugins );
|
172 |
-
}
|
173 |
-
$plugins = array_merge( (array) get_option( 'active_plugins', array() ), $plugins );
|
174 |
-
|
175 |
-
return $plugins;
|
176 |
-
}
|
177 |
-
|
178 |
public static function is_safe_mode() {
|
179 |
if ( defined( 'MATOMO_SAFE_MODE' ) ) {
|
180 |
return MATOMO_SAFE_MODE;
|
@@ -183,14 +175,29 @@ class WpMatomo {
|
|
183 |
// we are not using is_plugin_active() for performance reasons
|
184 |
$active_plugins = self::get_active_plugins();
|
185 |
|
186 |
-
if ( in_array( 'wp-rss-aggregator/wp-rss-aggregator.php', $active_plugins )
|
187 |
-
|
188 |
return true;
|
189 |
}
|
190 |
|
191 |
return false;
|
192 |
}
|
193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
public function add_settings_link( $links ) {
|
195 |
$get_started = new \WpMatomo\Admin\GetStarted( self::$settings );
|
196 |
|
@@ -204,13 +211,11 @@ class WpMatomo {
|
|
204 |
}
|
205 |
|
206 |
public function init_plugin() {
|
207 |
-
if ( ( is_admin() || matomo_is_app_request() )
|
208 |
-
&& ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
|
209 |
$installer = new Installer( self::$settings );
|
210 |
$installer->register_hooks();
|
211 |
if ( $installer->looks_like_it_is_installed() ) {
|
212 |
-
if ( is_admin()
|
213 |
-
&& ( ! defined( 'MATOMO_ENABLE_AUTO_UPGRADE' ) || MATOMO_ENABLE_AUTO_UPGRADE ) ) {
|
214 |
$updater = new Updater( self::$settings );
|
215 |
$updater->update_if_needed();
|
216 |
}
|
@@ -228,8 +233,8 @@ class WpMatomo {
|
|
228 |
}
|
229 |
$tracking_code = new TrackingCode( self::$settings );
|
230 |
if ( self::$settings->is_tracking_enabled()
|
231 |
-
|
232 |
-
|
233 |
$tracker = new AjaxTracker( self::$settings );
|
234 |
|
235 |
$woocommerce = new Woocommerce( $tracker );
|
@@ -244,8 +249,4 @@ class WpMatomo {
|
|
244 |
do_action( 'matomo_ecommerce_init', $tracker );
|
245 |
}
|
246 |
}
|
247 |
-
|
248 |
-
public static function should_disable_addhandler() {
|
249 |
-
return defined( 'MATOMO_DISABLE_ADDHANDLER' ) && MATOMO_DISABLE_ADDHANDLER;
|
250 |
-
}
|
251 |
}
|
11 |
exit; // if accessed directly
|
12 |
}
|
13 |
|
14 |
+
use WpMatomo\Admin\Admin;
|
15 |
+
use WpMatomo\Admin\Chart;
|
16 |
use WpMatomo\Admin\Dashboard;
|
17 |
+
use WpMatomo\Admin\Menu;
|
18 |
+
use WpMatomo\AjaxTracker;
|
19 |
+
use WpMatomo\Annotations;
|
20 |
+
use WpMatomo\API;
|
21 |
+
use WpMatomo\Capabilities;
|
22 |
use WpMatomo\Commands\MatomoCommands;
|
23 |
use WpMatomo\Ecommerce\EasyDigitalDownloads;
|
24 |
use WpMatomo\Ecommerce\MemberPress;
|
25 |
+
use WpMatomo\Ecommerce\Woocommerce;
|
26 |
+
use WpMatomo\Installer;
|
27 |
use WpMatomo\OptOut;
|
28 |
use WpMatomo\Paths;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
use WpMatomo\RedirectOnActivation;
|
30 |
+
use WpMatomo\Report\Renderer;
|
31 |
+
use WpMatomo\Roles;
|
32 |
+
use WpMatomo\ScheduledTasks;
|
33 |
+
use WpMatomo\Settings;
|
34 |
+
use WpMatomo\Site\Sync as SiteSync;
|
35 |
+
use WpMatomo\TrackingCode;
|
36 |
+
use WpMatomo\Updater;
|
37 |
+
use WpMatomo\User\Sync as UserSync;
|
38 |
|
39 |
class WpMatomo {
|
|
|
40 |
/**
|
41 |
* @var Settings
|
42 |
*/
|
58 |
return;
|
59 |
}
|
60 |
|
61 |
+
add_action( 'init', [ $this, 'init_plugin' ] );
|
62 |
|
63 |
$capabilities = new Capabilities( self::$settings );
|
64 |
$capabilities->register_hooks();
|
97 |
$referral->register_hooks();
|
98 |
}
|
99 |
|
100 |
+
$chart = new Chart();
|
101 |
+
$chart->register_hooks();
|
102 |
+
|
103 |
/*
|
104 |
* @see https://github.com/matomo-org/matomo-for-wordpress/issues/434
|
105 |
*/
|
106 |
+
$redirect = new RedirectOnActivation( $this );
|
107 |
$redirect->register_hooks();
|
108 |
}
|
109 |
|
118 |
|
119 |
add_filter(
|
120 |
'plugin_action_links_' . plugin_basename( MATOMO_ANALYTICS_FILE ),
|
121 |
+
[
|
122 |
$this,
|
123 |
'add_settings_link',
|
124 |
+
]
|
125 |
);
|
126 |
}
|
127 |
|
137 |
$upload_path = $paths->get_upload_base_dir();
|
138 |
|
139 |
if ( $upload_path
|
140 |
+
&& ! is_writable( dirname( $upload_path ) ) ) {
|
141 |
add_action(
|
142 |
'init',
|
143 |
function () use ( $upload_path ) {
|
145 |
add_action(
|
146 |
'admin_notices',
|
147 |
function () use ( $upload_path ) {
|
148 |
+
echo '<div class="error"><p>' . sprintf( esc_html__( 'Matomo Analytics requires the uploads directory %s to be writable. Please make the directory writable for it to work.', 'matomo' ), '(' . esc_html( dirname( $upload_path ) ) . ')' ) . '</p></div>';
|
149 |
}
|
150 |
);
|
151 |
}
|
160 |
|
161 |
public static function is_admin_user() {
|
162 |
if ( ! function_exists( 'is_multisite' )
|
163 |
+
|| ! is_multisite() ) {
|
164 |
return current_user_can( 'administrator' );
|
165 |
}
|
166 |
|
167 |
return is_super_admin();
|
168 |
}
|
169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
public static function is_safe_mode() {
|
171 |
if ( defined( 'MATOMO_SAFE_MODE' ) ) {
|
172 |
return MATOMO_SAFE_MODE;
|
175 |
// we are not using is_plugin_active() for performance reasons
|
176 |
$active_plugins = self::get_active_plugins();
|
177 |
|
178 |
+
if ( in_array( 'wp-rss-aggregator/wp-rss-aggregator.php', $active_plugins, true )
|
179 |
+
|| in_array( 'wp-defender/wp-defender.php', $active_plugins, true ) ) {
|
180 |
return true;
|
181 |
}
|
182 |
|
183 |
return false;
|
184 |
}
|
185 |
|
186 |
+
private static function get_active_plugins() {
|
187 |
+
$plugins = [];
|
188 |
+
if ( function_exists( 'is_multisite' ) && is_multisite() ) {
|
189 |
+
$muplugins = get_site_option( 'active_sitewide_plugins' );
|
190 |
+
$plugins = array_keys( $muplugins );
|
191 |
+
}
|
192 |
+
$plugins = array_merge( (array) get_option( 'active_plugins', [] ), $plugins );
|
193 |
+
|
194 |
+
return $plugins;
|
195 |
+
}
|
196 |
+
|
197 |
+
public static function should_disable_addhandler() {
|
198 |
+
return defined( 'MATOMO_DISABLE_ADDHANDLER' ) && MATOMO_DISABLE_ADDHANDLER;
|
199 |
+
}
|
200 |
+
|
201 |
public function add_settings_link( $links ) {
|
202 |
$get_started = new \WpMatomo\Admin\GetStarted( self::$settings );
|
203 |
|
211 |
}
|
212 |
|
213 |
public function init_plugin() {
|
214 |
+
if ( ( is_admin() || matomo_is_app_request() ) && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
|
|
|
215 |
$installer = new Installer( self::$settings );
|
216 |
$installer->register_hooks();
|
217 |
if ( $installer->looks_like_it_is_installed() ) {
|
218 |
+
if ( is_admin() && ( ! defined( 'MATOMO_ENABLE_AUTO_UPGRADE' ) || MATOMO_ENABLE_AUTO_UPGRADE ) ) {
|
|
|
219 |
$updater = new Updater( self::$settings );
|
220 |
$updater->update_if_needed();
|
221 |
}
|
233 |
}
|
234 |
$tracking_code = new TrackingCode( self::$settings );
|
235 |
if ( self::$settings->is_tracking_enabled()
|
236 |
+
&& self::$settings->get_global_option( 'track_ecommerce' )
|
237 |
+
&& ! $tracking_code->is_hidden_user() ) {
|
238 |
$tracker = new AjaxTracker( self::$settings );
|
239 |
|
240 |
$woocommerce = new Woocommerce( $tracker );
|
249 |
do_action( 'matomo_ecommerce_init', $tracker );
|
250 |
}
|
251 |
}
|
|
|
|
|
|
|
|
|
252 |
}
|
classes/WpMatomo/API.php
CHANGED
@@ -9,31 +9,36 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
12 |
use Piwik\API\Request;
|
13 |
use Piwik\Common;
|
|
|
|
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
-
|
|
|
|
|
19 |
class API {
|
20 |
const VERSION = 'matomo/v1';
|
21 |
|
22 |
const ROUTE_HIT = 'hit';
|
23 |
|
24 |
public function register_hooks() {
|
25 |
-
add_action( 'rest_api_init',
|
26 |
}
|
27 |
|
28 |
public function register_routes() {
|
29 |
register_rest_route(
|
30 |
self::VERSION,
|
31 |
'/' . self::ROUTE_HIT . '/',
|
32 |
-
|
33 |
-
'methods'
|
34 |
'permission_callback' => '__return_true',
|
35 |
-
'callback'
|
36 |
-
|
37 |
);
|
38 |
$this->register_route( 'API', 'getProcessedReport' );
|
39 |
$this->register_route( 'API', 'getReportMetadata' );
|
@@ -88,18 +93,20 @@ class API {
|
|
88 |
if ( empty( $_GET ) && empty( $_POST ) && empty( $_POST['idsite'] ) && empty( $_GET['idsite'] ) ) {
|
89 |
// todo if uploads dir is not writable, we may want to generate the matomo.js here and save it as an
|
90 |
// option... then we could also save it compressed
|
91 |
-
$paths
|
92 |
-
$path
|
|
|
93 |
header( 'Content-Type: application/javascript' );
|
94 |
header( 'Content-Length: ' . ( filesize( $path ) ) );
|
95 |
-
|
|
|
96 |
exit;
|
97 |
}
|
98 |
include_once plugin_dir_path( MATOMO_ANALYTICS_FILE ) . 'app/piwik.php';
|
99 |
exit;
|
100 |
}
|
101 |
|
102 |
-
public function execute_api_method(
|
103 |
$attributes = $request->get_attributes();
|
104 |
$method = $attributes['matomoModule'] . '.' . $attributes['matomoMethod'];
|
105 |
|
@@ -135,7 +142,7 @@ class API {
|
|
135 |
* @api
|
136 |
*/
|
137 |
public function register_route( $api_module, $api_method ) {
|
138 |
-
$methods =
|
139 |
'get' => 'GET',
|
140 |
'edit' => 'PUT',
|
141 |
'update' => 'PUT',
|
@@ -147,8 +154,8 @@ class API {
|
|
147 |
'send' => 'POST',
|
148 |
'delete' => 'DELETE',
|
149 |
'remove' => 'DELETE',
|
150 |
-
|
151 |
-
$starts_with_keep_prefix =
|
152 |
|
153 |
$method = 'GET';
|
154 |
$wp_api_module = $this->to_snake_case( $api_module );
|
@@ -170,13 +177,13 @@ class API {
|
|
170 |
register_rest_route(
|
171 |
self::VERSION,
|
172 |
'/' . $wp_api_module . '/' . $wp_api_action . '/',
|
173 |
-
|
174 |
-
'methods'
|
175 |
-
'callback'
|
176 |
'permission_callback' => '__return_true', // permissions are checked in the method itself
|
177 |
-
'matomoModule'
|
178 |
-
'matomoMethod'
|
179 |
-
|
180 |
);
|
181 |
}
|
182 |
|
@@ -186,7 +193,7 @@ class API {
|
|
186 |
$idsite = $site->get_current_matomo_site_id();
|
187 |
|
188 |
if ( ! $idsite ) {
|
189 |
-
return new
|
190 |
}
|
191 |
|
192 |
$params['idSite'] = $idsite;
|
@@ -203,19 +210,18 @@ class API {
|
|
203 |
|
204 |
// refs https://github.com/matomo-org/wp-matomo/issues/370 ensuring segment will be used from default request when
|
205 |
// creating new request object and not the encoded segment
|
206 |
-
if (isset($params['segment'])) {
|
207 |
-
if (isset($_GET['segment']) || isset($_POST['segment'])) {
|
208 |
-
unset($params['segment']); // matomo will read the segment from default request
|
209 |
-
} elseif (!empty($params['segment']) && is_string($params['segment'])) {
|
210 |
// manually unsanitize this value
|
211 |
-
$params['segment'] = Common::unsanitizeInputValue($params['segment']);
|
212 |
}
|
213 |
}
|
214 |
|
215 |
-
|
216 |
try {
|
217 |
$result = Request::processRequest( $api_method, $params );
|
218 |
-
} catch (
|
219 |
$code = 'matomo_error';
|
220 |
if ( $e->getCode() ) {
|
221 |
$code .= '_' . $code;
|
@@ -224,7 +230,7 @@ class API {
|
|
224 |
$code = str_replace( 'piwik', 'matomo', $this->to_snake_case( get_class( $e ) ) );
|
225 |
}
|
226 |
|
227 |
-
return new
|
228 |
}
|
229 |
|
230 |
return $result;
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Exception;
|
13 |
use Piwik\API\Request;
|
14 |
use Piwik\Common;
|
15 |
+
use WP_Error;
|
16 |
+
use WP_REST_Request;
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit; // if accessed directly
|
20 |
}
|
21 |
+
/**
|
22 |
+
* phpcs:disable WordPress.Security.NonceVerification.Missing
|
23 |
+
*/
|
24 |
class API {
|
25 |
const VERSION = 'matomo/v1';
|
26 |
|
27 |
const ROUTE_HIT = 'hit';
|
28 |
|
29 |
public function register_hooks() {
|
30 |
+
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
|
31 |
}
|
32 |
|
33 |
public function register_routes() {
|
34 |
register_rest_route(
|
35 |
self::VERSION,
|
36 |
'/' . self::ROUTE_HIT . '/',
|
37 |
+
[
|
38 |
+
'methods' => [ 'GET', 'POST' ],
|
39 |
'permission_callback' => '__return_true',
|
40 |
+
'callback' => [ $this, 'hit' ],
|
41 |
+
]
|
42 |
);
|
43 |
$this->register_route( 'API', 'getProcessedReport' );
|
44 |
$this->register_route( 'API', 'getReportMetadata' );
|
93 |
if ( empty( $_GET ) && empty( $_POST ) && empty( $_POST['idsite'] ) && empty( $_GET['idsite'] ) ) {
|
94 |
// todo if uploads dir is not writable, we may want to generate the matomo.js here and save it as an
|
95 |
// option... then we could also save it compressed
|
96 |
+
$paths = new Paths();
|
97 |
+
$path = $paths->get_matomo_js_upload_path();
|
98 |
+
$wp_filesystem = $paths->get_file_system();
|
99 |
header( 'Content-Type: application/javascript' );
|
100 |
header( 'Content-Length: ' . ( filesize( $path ) ) );
|
101 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
102 |
+
echo $wp_filesystem->get_contents( $paths->get_upload_base_dir() . '/matomo.js' ); // Reading the file into the output buffer
|
103 |
exit;
|
104 |
}
|
105 |
include_once plugin_dir_path( MATOMO_ANALYTICS_FILE ) . 'app/piwik.php';
|
106 |
exit;
|
107 |
}
|
108 |
|
109 |
+
public function execute_api_method( WP_REST_Request $request ) {
|
110 |
$attributes = $request->get_attributes();
|
111 |
$method = $attributes['matomoModule'] . '.' . $attributes['matomoMethod'];
|
112 |
|
142 |
* @api
|
143 |
*/
|
144 |
public function register_route( $api_module, $api_method ) {
|
145 |
+
$methods = [
|
146 |
'get' => 'GET',
|
147 |
'edit' => 'PUT',
|
148 |
'update' => 'PUT',
|
154 |
'send' => 'POST',
|
155 |
'delete' => 'DELETE',
|
156 |
'remove' => 'DELETE',
|
157 |
+
];
|
158 |
+
$starts_with_keep_prefix = [ 'anonymize', 'invalidate', 'run', 'send' ];
|
159 |
|
160 |
$method = 'GET';
|
161 |
$wp_api_module = $this->to_snake_case( $api_module );
|
177 |
register_rest_route(
|
178 |
self::VERSION,
|
179 |
'/' . $wp_api_module . '/' . $wp_api_action . '/',
|
180 |
+
[
|
181 |
+
'methods' => $method,
|
182 |
+
'callback' => [ $this, 'execute_api_method' ],
|
183 |
'permission_callback' => '__return_true', // permissions are checked in the method itself
|
184 |
+
'matomoModule' => $api_module,
|
185 |
+
'matomoMethod' => $api_method,
|
186 |
+
]
|
187 |
);
|
188 |
}
|
189 |
|
193 |
$idsite = $site->get_current_matomo_site_id();
|
194 |
|
195 |
if ( ! $idsite ) {
|
196 |
+
return new WP_Error( 'Site not found. Make sure it is synced' );
|
197 |
}
|
198 |
|
199 |
$params['idSite'] = $idsite;
|
210 |
|
211 |
// refs https://github.com/matomo-org/wp-matomo/issues/370 ensuring segment will be used from default request when
|
212 |
// creating new request object and not the encoded segment
|
213 |
+
if ( isset( $params['segment'] ) ) {
|
214 |
+
if ( isset( $_GET['segment'] ) || isset( $_POST['segment'] ) ) {
|
215 |
+
unset( $params['segment'] ); // matomo will read the segment from default request
|
216 |
+
} elseif ( ! empty( $params['segment'] ) && is_string( $params['segment'] ) ) {
|
217 |
// manually unsanitize this value
|
218 |
+
$params['segment'] = Common::unsanitizeInputValue( $params['segment'] );
|
219 |
}
|
220 |
}
|
221 |
|
|
|
222 |
try {
|
223 |
$result = Request::processRequest( $api_method, $params );
|
224 |
+
} catch ( Exception $e ) {
|
225 |
$code = 'matomo_error';
|
226 |
if ( $e->getCode() ) {
|
227 |
$code .= '_' . $code;
|
230 |
$code = str_replace( 'piwik', 'matomo', $this->to_snake_case( get_class( $e ) ) );
|
231 |
}
|
232 |
|
233 |
+
return new WP_Error( $code, $e->getMessage() );
|
234 |
}
|
235 |
|
236 |
return $result;
|
classes/WpMatomo/Access.php
CHANGED
@@ -16,12 +16,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
16 |
}
|
17 |
|
18 |
class Access {
|
19 |
-
public static $matomo_permissions =
|
20 |
Capabilities::KEY_NONE => 'None',
|
21 |
Capabilities::KEY_VIEW => 'View',
|
22 |
Capabilities::KEY_WRITE => 'Write',
|
23 |
Capabilities::KEY_ADMIN => 'Admin',
|
24 |
-
|
25 |
|
26 |
/**
|
27 |
* @var Settings
|
@@ -47,7 +47,7 @@ class Access {
|
|
47 |
$roles = new Roles( $this->settings );
|
48 |
$available_roles = $roles->get_available_roles_for_configuration();
|
49 |
|
50 |
-
$caps_to_store =
|
51 |
foreach ( $values as $role => $matomo_permission ) {
|
52 |
if ( isset( $available_roles[ $role ] ) &&
|
53 |
$wp_roles->is_role( $role )
|
@@ -58,7 +58,7 @@ class Access {
|
|
58 |
|
59 |
// we can't add the capabilities to the role directly using say $wp_roles->add_role cause it would not be
|
60 |
// synced across sites when the plugin is network activated
|
61 |
-
$this->settings->apply_changes(
|
62 |
|
63 |
$sync = new Sync();
|
64 |
$sync->sync_current_users();
|
@@ -70,5 +70,4 @@ class Access {
|
|
70 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_SYNC );
|
71 |
}
|
72 |
}
|
73 |
-
|
74 |
}
|
16 |
}
|
17 |
|
18 |
class Access {
|
19 |
+
public static $matomo_permissions = [
|
20 |
Capabilities::KEY_NONE => 'None',
|
21 |
Capabilities::KEY_VIEW => 'View',
|
22 |
Capabilities::KEY_WRITE => 'Write',
|
23 |
Capabilities::KEY_ADMIN => 'Admin',
|
24 |
+
];
|
25 |
|
26 |
/**
|
27 |
* @var Settings
|
47 |
$roles = new Roles( $this->settings );
|
48 |
$available_roles = $roles->get_available_roles_for_configuration();
|
49 |
|
50 |
+
$caps_to_store = [];
|
51 |
foreach ( $values as $role => $matomo_permission ) {
|
52 |
if ( isset( $available_roles[ $role ] ) &&
|
53 |
$wp_roles->is_role( $role )
|
58 |
|
59 |
// we can't add the capabilities to the role directly using say $wp_roles->add_role cause it would not be
|
60 |
// synced across sites when the plugin is network activated
|
61 |
+
$this->settings->apply_changes( [ Settings::OPTION_KEY_CAPS_ACCESS => $caps_to_store ] );
|
62 |
|
63 |
$sync = new Sync();
|
64 |
$sync->sync_current_users();
|
70 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_SYNC );
|
71 |
}
|
72 |
}
|
|
|
73 |
}
|
classes/WpMatomo/Admin/AccessSettings.php
CHANGED
@@ -11,8 +11,8 @@ namespace WpMatomo\Admin;
|
|
11 |
|
12 |
use WpMatomo\Access;
|
13 |
use WpMatomo\Capabilities;
|
14 |
-
use WpMatomo\Settings;
|
15 |
use WpMatomo\Roles;
|
|
|
16 |
|
17 |
if ( ! defined( 'ABSPATH' ) ) {
|
18 |
exit; // if accessed directly
|
@@ -41,27 +41,27 @@ class AccessSettings implements AdminSettingsInterface {
|
|
41 |
return esc_html__( 'Access', 'matomo' );
|
42 |
}
|
43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
private function update_if_submitted() {
|
45 |
if ( isset( $_POST )
|
46 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
47 |
&& is_admin()
|
48 |
&& check_admin_referer( self::NONCE_NAME )
|
49 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
50 |
-
|
|
|
51 |
|
52 |
return true;
|
53 |
}
|
54 |
|
55 |
return false;
|
56 |
}
|
57 |
-
|
58 |
-
public function show_settings() {
|
59 |
-
$this->update_if_submitted();
|
60 |
-
|
61 |
-
$access = $this->access;
|
62 |
-
$roles = new Roles( $this->settings );
|
63 |
-
$capabilites = new Capabilities( $this->settings );
|
64 |
-
include dirname( __FILE__ ) . '/views/access.php';
|
65 |
-
}
|
66 |
-
|
67 |
}
|
11 |
|
12 |
use WpMatomo\Access;
|
13 |
use WpMatomo\Capabilities;
|
|
|
14 |
use WpMatomo\Roles;
|
15 |
+
use WpMatomo\Settings;
|
16 |
|
17 |
if ( ! defined( 'ABSPATH' ) ) {
|
18 |
exit; // if accessed directly
|
41 |
return esc_html__( 'Access', 'matomo' );
|
42 |
}
|
43 |
|
44 |
+
public function show_settings() {
|
45 |
+
$this->update_if_submitted();
|
46 |
+
|
47 |
+
$access = $this->access;
|
48 |
+
$roles = new Roles( $this->settings );
|
49 |
+
$capabilites = new Capabilities( $this->settings );
|
50 |
+
include dirname( __FILE__ ) . '/views/access.php';
|
51 |
+
}
|
52 |
+
|
53 |
private function update_if_submitted() {
|
54 |
if ( isset( $_POST )
|
55 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
56 |
&& is_admin()
|
57 |
&& check_admin_referer( self::NONCE_NAME )
|
58 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
59 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
60 |
+
$this->access->save( wp_unslash( $_POST[ self::FORM_NAME ] ) );
|
61 |
|
62 |
return true;
|
63 |
}
|
64 |
|
65 |
return false;
|
66 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
}
|
classes/WpMatomo/Admin/Admin.php
CHANGED
@@ -20,16 +20,15 @@ class Admin {
|
|
20 |
* @param Settings $settings
|
21 |
*/
|
22 |
public function __construct( $settings, $init_menu = true ) {
|
23 |
-
if ($init_menu) {
|
24 |
new Menu( $settings );
|
25 |
}
|
26 |
|
27 |
-
add_action( 'admin_enqueue_scripts',
|
28 |
}
|
29 |
|
30 |
public function load_scripts() {
|
31 |
wp_enqueue_style( 'matomo_admin_css', plugins_url( 'assets/css/admin-style.css', MATOMO_ANALYTICS_FILE ), false, '1.0.0' );
|
32 |
-
wp_enqueue_script( 'matomo_admin_js', plugins_url( 'assets/js/admin.js', MATOMO_ANALYTICS_FILE ),
|
33 |
}
|
34 |
-
|
35 |
}
|
20 |
* @param Settings $settings
|
21 |
*/
|
22 |
public function __construct( $settings, $init_menu = true ) {
|
23 |
+
if ( $init_menu ) {
|
24 |
new Menu( $settings );
|
25 |
}
|
26 |
|
27 |
+
add_action( 'admin_enqueue_scripts', [ $this, 'load_scripts' ] );
|
28 |
}
|
29 |
|
30 |
public function load_scripts() {
|
31 |
wp_enqueue_style( 'matomo_admin_css', plugins_url( 'assets/css/admin-style.css', MATOMO_ANALYTICS_FILE ), false, '1.0.0' );
|
32 |
+
wp_enqueue_script( 'matomo_admin_js', plugins_url( 'assets/js/admin.js', MATOMO_ANALYTICS_FILE ), [ 'jquery' ], '1.0', true );
|
33 |
}
|
|
|
34 |
}
|
classes/WpMatomo/Admin/AdminSettings.php
CHANGED
@@ -9,9 +9,6 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
-
use Piwik\Cache;
|
13 |
-
use Piwik\Option;
|
14 |
-
use Piwik\Plugins\SitesManager\API;
|
15 |
use WpMatomo\Access;
|
16 |
use WpMatomo\Settings;
|
17 |
|
@@ -20,12 +17,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
20 |
}
|
21 |
|
22 |
class AdminSettings {
|
23 |
-
const TAB_TRACKING
|
24 |
-
const TAB_ACCESS
|
25 |
const TAB_EXCLUSIONS = 'exlusions';
|
26 |
-
const TAB_PRIVACY
|
27 |
const TAB_GEOLOCATION = 'geolocation';
|
28 |
-
const TAB_ADVANCED
|
29 |
|
30 |
/**
|
31 |
* @var Settings
|
@@ -40,10 +37,10 @@ class AdminSettings {
|
|
40 |
global $_parent_pages;
|
41 |
$menu_slug = Menu::SLUG_SETTINGS;
|
42 |
|
43 |
-
if (is_multisite() && is_network_admin()) {
|
44 |
-
if ( isset( $_parent_pages[$menu_slug] ) ) {
|
45 |
-
$parent_slug = $_parent_pages[$menu_slug];
|
46 |
-
if ( $parent_slug && ! isset( $_parent_pages[$parent_slug] ) ) {
|
47 |
$url = network_admin_url( add_query_arg( 'page', $menu_slug, $parent_slug ) );
|
48 |
} else {
|
49 |
$url = network_admin_url( 'admin.php?page=' . $menu_slug );
|
@@ -51,51 +48,52 @@ class AdminSettings {
|
|
51 |
} else {
|
52 |
$url = '';
|
53 |
}
|
54 |
-
|
55 |
-
$url = esc_url( $url );
|
56 |
} else {
|
57 |
$url = menu_page_url( $menu_slug, false );
|
58 |
}
|
59 |
-
|
|
|
60 |
}
|
61 |
|
62 |
public function show() {
|
63 |
-
$access
|
64 |
$access_settings = new AccessSettings( $access, $this->settings );
|
65 |
-
$tracking
|
66 |
-
$exclusions
|
67 |
-
$geolocation
|
68 |
-
$privacy
|
69 |
-
$advanced
|
70 |
-
$setting_tabs
|
71 |
-
self::TAB_TRACKING
|
72 |
-
self::TAB_ACCESS
|
73 |
-
self::TAB_PRIVACY
|
74 |
-
self::TAB_EXCLUSIONS
|
75 |
self::TAB_GEOLOCATION => $geolocation,
|
76 |
-
self::TAB_ADVANCED
|
77 |
-
|
78 |
|
79 |
$active_tab = self::TAB_TRACKING;
|
80 |
|
81 |
-
if ($this->settings->is_network_enabled() && !is_network_admin()){
|
82 |
-
$active_tab
|
83 |
-
$setting_tabs =
|
84 |
self::TAB_EXCLUSIONS => $exclusions,
|
85 |
-
self::TAB_PRIVACY
|
86 |
-
|
87 |
}
|
88 |
|
89 |
$setting_tabs = apply_filters( 'matomo_setting_tabs', $setting_tabs, $this->settings );
|
90 |
|
91 |
-
if ( ! empty( $_GET['tab'] )
|
92 |
-
$
|
|
|
|
|
|
|
93 |
}
|
94 |
|
95 |
-
$content_tab
|
96 |
$matomo_settings = $this->settings;
|
97 |
|
98 |
include dirname( __FILE__ ) . '/views/settings.php';
|
99 |
}
|
100 |
-
|
101 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
|
|
|
|
12 |
use WpMatomo\Access;
|
13 |
use WpMatomo\Settings;
|
14 |
|
17 |
}
|
18 |
|
19 |
class AdminSettings {
|
20 |
+
const TAB_TRACKING = 'tracking';
|
21 |
+
const TAB_ACCESS = 'access';
|
22 |
const TAB_EXCLUSIONS = 'exlusions';
|
23 |
+
const TAB_PRIVACY = 'privacy';
|
24 |
const TAB_GEOLOCATION = 'geolocation';
|
25 |
+
const TAB_ADVANCED = 'advanced';
|
26 |
|
27 |
/**
|
28 |
* @var Settings
|
37 |
global $_parent_pages;
|
38 |
$menu_slug = Menu::SLUG_SETTINGS;
|
39 |
|
40 |
+
if ( is_multisite() && is_network_admin() ) {
|
41 |
+
if ( isset( $_parent_pages[ $menu_slug ] ) ) {
|
42 |
+
$parent_slug = $_parent_pages[ $menu_slug ];
|
43 |
+
if ( $parent_slug && ! isset( $_parent_pages[ $parent_slug ] ) ) {
|
44 |
$url = network_admin_url( add_query_arg( 'page', $menu_slug, $parent_slug ) );
|
45 |
} else {
|
46 |
$url = network_admin_url( 'admin.php?page=' . $menu_slug );
|
48 |
} else {
|
49 |
$url = '';
|
50 |
}
|
|
|
|
|
51 |
} else {
|
52 |
$url = menu_page_url( $menu_slug, false );
|
53 |
}
|
54 |
+
|
55 |
+
return add_query_arg( [ 'tab' => $tab ], $url );
|
56 |
}
|
57 |
|
58 |
public function show() {
|
59 |
+
$access = new Access( $this->settings );
|
60 |
$access_settings = new AccessSettings( $access, $this->settings );
|
61 |
+
$tracking = new TrackingSettings( $this->settings );
|
62 |
+
$exclusions = new ExclusionSettings( $this->settings );
|
63 |
+
$geolocation = new GeolocationSettings( $this->settings );
|
64 |
+
$privacy = new PrivacySettings( $this->settings );
|
65 |
+
$advanced = new AdvancedSettings( $this->settings );
|
66 |
+
$setting_tabs = [
|
67 |
+
self::TAB_TRACKING => $tracking,
|
68 |
+
self::TAB_ACCESS => $access_settings,
|
69 |
+
self::TAB_PRIVACY => $privacy,
|
70 |
+
self::TAB_EXCLUSIONS => $exclusions,
|
71 |
self::TAB_GEOLOCATION => $geolocation,
|
72 |
+
self::TAB_ADVANCED => $advanced,
|
73 |
+
];
|
74 |
|
75 |
$active_tab = self::TAB_TRACKING;
|
76 |
|
77 |
+
if ( $this->settings->is_network_enabled() && ! is_network_admin() ) {
|
78 |
+
$active_tab = self::TAB_EXCLUSIONS;
|
79 |
+
$setting_tabs = [
|
80 |
self::TAB_EXCLUSIONS => $exclusions,
|
81 |
+
self::TAB_PRIVACY => $privacy,
|
82 |
+
];
|
83 |
}
|
84 |
|
85 |
$setting_tabs = apply_filters( 'matomo_setting_tabs', $setting_tabs, $this->settings );
|
86 |
|
87 |
+
if ( ! empty( $_GET['tab'] ) ) {
|
88 |
+
$tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
|
89 |
+
if ( isset( $setting_tabs[ $tab ] ) ) {
|
90 |
+
$active_tab = $tab;
|
91 |
+
}
|
92 |
}
|
93 |
|
94 |
+
$content_tab = $setting_tabs[ $active_tab ];
|
95 |
$matomo_settings = $this->settings;
|
96 |
|
97 |
include dirname( __FILE__ ) . '/views/settings.php';
|
98 |
}
|
|
|
99 |
}
|
classes/WpMatomo/Admin/AdvancedSettings.php
CHANGED
@@ -9,7 +9,6 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
-
use Piwik\Config;
|
13 |
use Piwik\IP;
|
14 |
use WpMatomo\Bootstrap;
|
15 |
use WpMatomo\Capabilities;
|
@@ -19,12 +18,14 @@ use WpMatomo\Site\Sync\SyncConfig as SiteConfigSync;
|
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
21 |
}
|
22 |
-
|
|
|
|
|
23 |
class AdvancedSettings implements AdminSettingsInterface {
|
24 |
-
const FORM_NAME
|
25 |
-
const NONCE_NAME
|
26 |
|
27 |
-
public static $valid_host_headers =
|
28 |
'HTTP_CLIENT_IP',
|
29 |
'HTTP_X_REAL_IP',
|
30 |
'HTTP_X_FORWARDED_FOR',
|
@@ -34,7 +35,7 @@ class AdvancedSettings implements AdminSettingsInterface {
|
|
34 |
'HTTP_CF_CONNECTING_IP',
|
35 |
'HTTP_TRUE_CLIENT_IP',
|
36 |
'HTTP_X_CLUSTER_CLIENT_IP',
|
37 |
-
|
38 |
|
39 |
/**
|
40 |
* @var Settings
|
@@ -50,7 +51,7 @@ class AdvancedSettings implements AdminSettingsInterface {
|
|
50 |
* @param Settings $settings
|
51 |
*/
|
52 |
public function __construct( $settings ) {
|
53 |
-
$this->settings
|
54 |
$this->site_config_sync = new SiteConfigSync( $settings );
|
55 |
}
|
56 |
|
@@ -58,13 +59,27 @@ class AdvancedSettings implements AdminSettingsInterface {
|
|
58 |
return esc_html__( 'Advanced', 'matomo' );
|
59 |
}
|
60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
private function update_if_submitted() {
|
62 |
if ( isset( $_POST )
|
63 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
64 |
&& is_admin()
|
65 |
&& check_admin_referer( self::NONCE_NAME )
|
66 |
&& $this->can_user_manage() ) {
|
67 |
-
|
68 |
$this->apply_settings();
|
69 |
|
70 |
return true;
|
@@ -78,40 +93,24 @@ class AdvancedSettings implements AdminSettingsInterface {
|
|
78 |
}
|
79 |
|
80 |
private function apply_settings() {
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
if (!empty($_POST[ self::FORM_NAME ]['proxy_client_header'])) {
|
89 |
-
$client_header = $_POST[ self::FORM_NAME ]['proxy_client_header'];
|
90 |
-
if (in_array($client_header, self::$valid_host_headers, true)) {
|
91 |
-
$client_headers[] = $client_header;
|
92 |
-
}
|
93 |
-
}
|
94 |
-
|
95 |
-
$this->site_config_sync->set_config_value('General', 'proxy_client_headers', $client_headers);
|
96 |
-
|
97 |
-
return true;
|
98 |
-
}
|
99 |
-
|
100 |
-
public function show_settings() {
|
101 |
-
$was_updated = $this->update_if_submitted();
|
102 |
|
103 |
-
$
|
104 |
-
if (empty($
|
105 |
-
$
|
|
|
|
|
|
|
106 |
}
|
107 |
|
108 |
-
|
109 |
-
$matomo_detected_ip = IP::getIpFromHeader();
|
110 |
-
$matomo_delete_all_data = $this->settings->should_delete_all_data_on_uninstall();
|
111 |
|
112 |
-
|
113 |
}
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
12 |
use Piwik\IP;
|
13 |
use WpMatomo\Bootstrap;
|
14 |
use WpMatomo\Capabilities;
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit; // if accessed directly
|
20 |
}
|
21 |
+
/**
|
22 |
+
* phpcs:disable WordPress.Security.NonceVerification.Missing
|
23 |
+
*/
|
24 |
class AdvancedSettings implements AdminSettingsInterface {
|
25 |
+
const FORM_NAME = 'matomo';
|
26 |
+
const NONCE_NAME = 'matomo_advanced';
|
27 |
|
28 |
+
public static $valid_host_headers = [
|
29 |
'HTTP_CLIENT_IP',
|
30 |
'HTTP_X_REAL_IP',
|
31 |
'HTTP_X_FORWARDED_FOR',
|
35 |
'HTTP_CF_CONNECTING_IP',
|
36 |
'HTTP_TRUE_CLIENT_IP',
|
37 |
'HTTP_X_CLUSTER_CLIENT_IP',
|
38 |
+
];
|
39 |
|
40 |
/**
|
41 |
* @var Settings
|
51 |
* @param Settings $settings
|
52 |
*/
|
53 |
public function __construct( $settings ) {
|
54 |
+
$this->settings = $settings;
|
55 |
$this->site_config_sync = new SiteConfigSync( $settings );
|
56 |
}
|
57 |
|
59 |
return esc_html__( 'Advanced', 'matomo' );
|
60 |
}
|
61 |
|
62 |
+
public function show_settings() {
|
63 |
+
$was_updated = $this->update_if_submitted();
|
64 |
+
|
65 |
+
$matomo_client_headers = $this->site_config_sync->get_config_value( 'General', 'proxy_client_headers' );
|
66 |
+
if ( empty( $matomo_client_headers ) ) {
|
67 |
+
$matomo_client_headers = [];
|
68 |
+
}
|
69 |
+
|
70 |
+
Bootstrap::do_bootstrap();
|
71 |
+
$matomo_detected_ip = IP::getIpFromHeader();
|
72 |
+
$matomo_delete_all_data = $this->settings->should_delete_all_data_on_uninstall();
|
73 |
+
|
74 |
+
include dirname( __FILE__ ) . '/views/advanced_settings.php';
|
75 |
+
}
|
76 |
+
|
77 |
private function update_if_submitted() {
|
78 |
if ( isset( $_POST )
|
79 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
80 |
&& is_admin()
|
81 |
&& check_admin_referer( self::NONCE_NAME )
|
82 |
&& $this->can_user_manage() ) {
|
|
|
83 |
$this->apply_settings();
|
84 |
|
85 |
return true;
|
93 |
}
|
94 |
|
95 |
private function apply_settings() {
|
96 |
+
if ( ! defined( 'MATOMO_REMOVE_ALL_DATA' ) ) {
|
97 |
+
$this->settings->apply_changes(
|
98 |
+
[
|
99 |
+
Settings::DELETE_ALL_DATA_ON_UNINSTALL => ! empty( $_POST['matomo']['delete_all_data'] ),
|
100 |
+
]
|
101 |
+
);
|
102 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
|
104 |
+
$client_headers = [];
|
105 |
+
if ( ! empty( $_POST[ self::FORM_NAME ]['proxy_client_header'] ) ) {
|
106 |
+
$client_header = sanitize_text_field( wp_unslash( $_POST[ self::FORM_NAME ]['proxy_client_header'] ) );
|
107 |
+
if ( in_array( $client_header, self::$valid_host_headers, true ) ) {
|
108 |
+
$client_headers[] = $client_header;
|
109 |
+
}
|
110 |
}
|
111 |
|
112 |
+
$this->site_config_sync->set_config_value( 'General', 'proxy_client_headers', $client_headers );
|
|
|
|
|
113 |
|
114 |
+
return true;
|
115 |
}
|
|
|
|
|
|
|
116 |
}
|
classes/WpMatomo/Admin/Chart.php
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @package matomo
|
4 |
+
*/
|
5 |
+
namespace WpMatomo\Admin;
|
6 |
+
|
7 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
8 |
+
exit; // if accessed directly
|
9 |
+
}
|
10 |
+
|
11 |
+
class Chart {
|
12 |
+
public function register_hooks() {
|
13 |
+
add_action( 'matomo_load_chartjs', [ $this, 'load_chartjs' ] );
|
14 |
+
}
|
15 |
+
|
16 |
+
public function load_chartjs() {
|
17 |
+
wp_enqueue_script( 'chart.js', plugins_url( 'node_modules/chart.js/dist/chart.min.js', MATOMO_ANALYTICS_FILE ), [], '1.0.0', true );
|
18 |
+
wp_enqueue_script( 'matomo_chart.js', plugins_url( 'assets/chart.js', MATOMO_ANALYTICS_FILE ), [], '1.0.0', true );
|
19 |
+
}
|
20 |
+
}
|
classes/WpMatomo/Admin/CookieConsent.php
CHANGED
@@ -7,7 +7,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
7 |
}
|
8 |
|
9 |
class CookieConsent {
|
10 |
-
|
11 |
const REQUIRE_COOKIE_CONSENT = 'cookie';
|
12 |
|
13 |
const REQUIRE_TRACKING_CONSENT = 'tracking';
|
@@ -19,26 +18,26 @@ class CookieConsent {
|
|
19 |
*/
|
20 |
public static function get_available_options() {
|
21 |
return [
|
22 |
-
self::REQUIRE_NONE
|
23 |
-
self::REQUIRE_COOKIE_CONSENT
|
24 |
-
self::REQUIRE_TRACKING_CONSENT => __('Require tracking consent', 'matomo')
|
25 |
];
|
26 |
-
|
27 |
}
|
|
|
28 |
/**
|
29 |
* @param string $tracking_mode
|
30 |
-
*
|
|
|
31 |
* @see CookieConsent::REQUIRE_NONE
|
32 |
* @see CookieConsent::REQUIRE_TRACKING_CONSENT
|
33 |
-
* @
|
34 |
*/
|
35 |
public function get_tracking_consent_option( $tracking_mode ) {
|
36 |
-
switch( $tracking_mode ) {
|
37 |
case self::REQUIRE_TRACKING_CONSENT:
|
38 |
$tracking_code = <<<JAVASCRIPT
|
39 |
_paq.push(['requireConsent']);
|
40 |
JAVASCRIPT;
|
41 |
-
;
|
42 |
break;
|
43 |
case self::REQUIRE_COOKIE_CONSENT:
|
44 |
$tracking_code = <<<JAVASCRIPT
|
@@ -47,8 +46,9 @@ JAVASCRIPT;
|
|
47 |
break;
|
48 |
case self::REQUIRE_NONE:
|
49 |
default:
|
50 |
-
|
51 |
}
|
|
|
52 |
return $tracking_code;
|
53 |
}
|
54 |
}
|
7 |
}
|
8 |
|
9 |
class CookieConsent {
|
|
|
10 |
const REQUIRE_COOKIE_CONSENT = 'cookie';
|
11 |
|
12 |
const REQUIRE_TRACKING_CONSENT = 'tracking';
|
18 |
*/
|
19 |
public static function get_available_options() {
|
20 |
return [
|
21 |
+
self::REQUIRE_NONE => __( 'None', 'matomo' ),
|
22 |
+
self::REQUIRE_COOKIE_CONSENT => __( 'Require cookie consent', 'matomo' ),
|
23 |
+
self::REQUIRE_TRACKING_CONSENT => __( 'Require tracking consent', 'matomo' ),
|
24 |
];
|
|
|
25 |
}
|
26 |
+
|
27 |
/**
|
28 |
* @param string $tracking_mode
|
29 |
+
*
|
30 |
+
* @return string
|
31 |
* @see CookieConsent::REQUIRE_NONE
|
32 |
* @see CookieConsent::REQUIRE_TRACKING_CONSENT
|
33 |
+
* @see CookieConsent::REQUIRE_COOKIE_CONSENT
|
34 |
*/
|
35 |
public function get_tracking_consent_option( $tracking_mode ) {
|
36 |
+
switch ( $tracking_mode ) {
|
37 |
case self::REQUIRE_TRACKING_CONSENT:
|
38 |
$tracking_code = <<<JAVASCRIPT
|
39 |
_paq.push(['requireConsent']);
|
40 |
JAVASCRIPT;
|
|
|
41 |
break;
|
42 |
case self::REQUIRE_COOKIE_CONSENT:
|
43 |
$tracking_code = <<<JAVASCRIPT
|
46 |
break;
|
47 |
case self::REQUIRE_NONE:
|
48 |
default:
|
49 |
+
$tracking_code = '';
|
50 |
}
|
51 |
+
|
52 |
return $tracking_code;
|
53 |
}
|
54 |
}
|
classes/WpMatomo/Admin/Dashboard.php
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
12 |
use WpMatomo\Capabilities;
|
13 |
use WpMatomo\Logger;
|
14 |
use WpMatomo\Report\Dates;
|
@@ -21,109 +22,120 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
21 |
}
|
22 |
|
23 |
class Dashboard {
|
24 |
-
|
25 |
const DASHBOARD_USER_OPTION = 'matomo_dashboard_widgets';
|
26 |
|
27 |
public function register_hooks() {
|
28 |
-
add_action( 'wp_dashboard_setup',
|
29 |
}
|
30 |
|
31 |
-
public function add_dashboard_widgets()
|
32 |
-
{
|
33 |
$widgets = $this->get_widgets();
|
34 |
-
if (!empty($widgets) && is_array($widgets) && current_user_can(Capabilities::KEY_VIEW)) {
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
}
|
60 |
}
|
61 |
}
|
62 |
|
63 |
-
public function
|
64 |
-
|
65 |
-
if (empty($
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
return false;
|
67 |
}
|
68 |
|
69 |
$metadata = new Metadata();
|
70 |
-
$report
|
71 |
|
72 |
-
if (empty($report)) {
|
73 |
return false;
|
74 |
}
|
75 |
|
76 |
$report_dates_obj = new Dates();
|
77 |
$report_dates = $report_dates_obj->get_supported_dates();
|
78 |
|
79 |
-
if (empty($report_dates[$date])) {
|
80 |
return false;
|
81 |
}
|
82 |
|
83 |
-
return
|
|
|
|
|
|
|
84 |
}
|
85 |
|
86 |
-
public function has_widget($report_unique_id, $report_date)
|
87 |
-
{
|
88 |
$widgets = $this->get_widgets();
|
89 |
-
foreach ($widgets as $index => $widget) {
|
90 |
-
if ($widget['unique_id'] === $report_unique_id && $widget['date'] === $report_date) {
|
91 |
return true;
|
92 |
}
|
93 |
}
|
|
|
94 |
return false;
|
95 |
}
|
96 |
|
97 |
-
public function toggle_widget($report_unique_id, $report_date)
|
98 |
-
{
|
99 |
$widgets = $this->get_widgets();
|
100 |
-
foreach ($widgets as $index => $widget) {
|
101 |
-
if ($widget['unique_id'] === $report_unique_id && $widget['date'] === $report_date) {
|
102 |
-
unset($widgets[$index]);
|
103 |
-
$this->set_widgets(array_values($widgets));
|
|
|
104 |
return;
|
105 |
}
|
106 |
}
|
107 |
-
$widgets[] =
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
|
112 |
-
|
113 |
-
{
|
114 |
-
$meta = get_user_meta(get_current_user_id(), self::DASHBOARD_USER_OPTION, true);
|
115 |
-
if (empty($meta)) {
|
116 |
-
$meta = array();
|
117 |
-
}
|
118 |
-
return $meta;
|
119 |
}
|
120 |
|
121 |
-
private function set_widgets($widgets)
|
122 |
-
|
123 |
-
update_user_meta(get_current_user_id(),self::DASHBOARD_USER_OPTION, $widgets);
|
124 |
}
|
125 |
|
126 |
public function uninstall() {
|
127 |
-
|
128 |
}
|
129 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
+
use Exception;
|
13 |
use WpMatomo\Capabilities;
|
14 |
use WpMatomo\Logger;
|
15 |
use WpMatomo\Report\Dates;
|
22 |
}
|
23 |
|
24 |
class Dashboard {
|
|
|
25 |
const DASHBOARD_USER_OPTION = 'matomo_dashboard_widgets';
|
26 |
|
27 |
public function register_hooks() {
|
28 |
+
add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widgets' ] );
|
29 |
}
|
30 |
|
31 |
+
public function add_dashboard_widgets() {
|
|
|
32 |
$widgets = $this->get_widgets();
|
33 |
+
if ( ! empty( $widgets ) && is_array( $widgets ) && current_user_can( Capabilities::KEY_VIEW ) ) {
|
34 |
+
do_action( 'matomo_load_chartjs' );
|
35 |
+
foreach ( $widgets as $widget ) {
|
36 |
+
try {
|
37 |
+
$widget_meta = $this->is_valid_widget( $widget['unique_id'], $widget['date'] );
|
38 |
+
if ( ! empty( $widget_meta['report']['name'] ) ) {
|
39 |
+
$id = 'matomo_dashboard_widget_' . $widget['unique_id'] . '_' . $widget['date'];
|
40 |
+
|
41 |
+
$title = $widget_meta['report']['name'] . ' - ' . $widget_meta['date'] . ' - Matomo';
|
42 |
+
|
43 |
+
wp_add_dashboard_widget(
|
44 |
+
$id,
|
45 |
+
esc_html( $title ),
|
46 |
+
function () use ( $widget ) {
|
47 |
+
$renderer = new Renderer();
|
48 |
+
// do not escape the content, we want the HTML
|
49 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
50 |
+
echo $renderer->show_report(
|
51 |
+
[
|
52 |
+
'unique_id' => $widget['unique_id'],
|
53 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
54 |
+
'report_date' => $widget['date'],
|
55 |
+
'limit' => 10,
|
56 |
+
]
|
57 |
+
);
|
58 |
+
}
|
59 |
+
);
|
60 |
+
}
|
61 |
+
} catch ( Exception $e ) {
|
62 |
+
// dont want to break dashboard if there is any issue with matomo ... eg in case bootstrap fails
|
63 |
+
// or is reinstalled but matomo not yet fully installed etc
|
64 |
+
$logger = new Logger();
|
65 |
+
$logger->log( sprintf( 'Failed to add Matomo widget %s to dashboard: %s', wp_json_encode( $widget ), $e->getMessage() ) );
|
66 |
+
}
|
67 |
}
|
68 |
}
|
69 |
}
|
70 |
|
71 |
+
public function get_widgets() {
|
72 |
+
$meta = get_user_meta( get_current_user_id(), self::DASHBOARD_USER_OPTION, true );
|
73 |
+
if ( empty( $meta ) ) {
|
74 |
+
$meta = [];
|
75 |
+
}
|
76 |
+
|
77 |
+
return $meta;
|
78 |
+
}
|
79 |
+
|
80 |
+
public function is_valid_widget( $unique_id, $date ) {
|
81 |
+
if ( empty( $unique_id ) || empty( $date ) ) {
|
82 |
return false;
|
83 |
}
|
84 |
|
85 |
$metadata = new Metadata();
|
86 |
+
$report = $metadata->find_report_by_unique_id( $unique_id );
|
87 |
|
88 |
+
if ( empty( $report ) ) {
|
89 |
return false;
|
90 |
}
|
91 |
|
92 |
$report_dates_obj = new Dates();
|
93 |
$report_dates = $report_dates_obj->get_supported_dates();
|
94 |
|
95 |
+
if ( empty( $report_dates[ $date ] ) ) {
|
96 |
return false;
|
97 |
}
|
98 |
|
99 |
+
return [
|
100 |
+
'report' => $report,
|
101 |
+
'date' => $report_dates[ $date ],
|
102 |
+
];
|
103 |
}
|
104 |
|
105 |
+
public function has_widget( $report_unique_id, $report_date ) {
|
|
|
106 |
$widgets = $this->get_widgets();
|
107 |
+
foreach ( $widgets as $index => $widget ) {
|
108 |
+
if ( $widget['unique_id'] === $report_unique_id && $widget['date'] === $report_date ) {
|
109 |
return true;
|
110 |
}
|
111 |
}
|
112 |
+
|
113 |
return false;
|
114 |
}
|
115 |
|
116 |
+
public function toggle_widget( $report_unique_id, $report_date ) {
|
|
|
117 |
$widgets = $this->get_widgets();
|
118 |
+
foreach ( $widgets as $index => $widget ) {
|
119 |
+
if ( $widget['unique_id'] === $report_unique_id && $widget['date'] === $report_date ) {
|
120 |
+
unset( $widgets[ $index ] );
|
121 |
+
$this->set_widgets( array_values( $widgets ) );
|
122 |
+
|
123 |
return;
|
124 |
}
|
125 |
}
|
126 |
+
$widgets[] = [
|
127 |
+
'unique_id' => $report_unique_id,
|
128 |
+
'date' => $report_date,
|
129 |
+
];
|
130 |
|
131 |
+
$this->set_widgets( $widgets );
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
}
|
133 |
|
134 |
+
private function set_widgets( $widgets ) {
|
135 |
+
update_user_meta( get_current_user_id(), self::DASHBOARD_USER_OPTION, $widgets );
|
|
|
136 |
}
|
137 |
|
138 |
public function uninstall() {
|
139 |
+
Uninstaller::uninstall_user_meta( self::DASHBOARD_USER_OPTION );
|
140 |
}
|
141 |
}
|
classes/WpMatomo/Admin/ExclusionSettings.php
CHANGED
@@ -36,6 +36,24 @@ class ExclusionSettings implements AdminSettingsInterface {
|
|
36 |
return esc_html__( 'Exclusions', 'matomo' );
|
37 |
}
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
private function update_if_submitted() {
|
40 |
if ( isset( $_POST )
|
41 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
@@ -43,8 +61,8 @@ class ExclusionSettings implements AdminSettingsInterface {
|
|
43 |
&& check_admin_referer( self::NONCE_NAME )
|
44 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
45 |
Bootstrap::do_bootstrap();
|
46 |
-
|
47 |
-
$post = $_POST[ self::FORM_NAME ];
|
48 |
|
49 |
$api = API::getInstance();
|
50 |
if ( isset( $post['excluded_ips'] ) ) {
|
@@ -69,11 +87,12 @@ class ExclusionSettings implements AdminSettingsInterface {
|
|
69 |
}
|
70 |
|
71 |
$keep_fragments = ! empty( $post['keep_url_fragments'] );
|
|
|
72 |
if ( $keep_fragments != $api->getKeepURLFragmentsGlobal() ) {
|
73 |
$api->setKeepURLFragmentsGlobal( $keep_fragments );
|
74 |
}
|
75 |
|
76 |
-
$setting_values =
|
77 |
if ( ! empty( $post[ Settings::OPTION_KEY_STEALTH ] ) ) {
|
78 |
$setting_values[ Settings::OPTION_KEY_STEALTH ] = $post[ Settings::OPTION_KEY_STEALTH ];
|
79 |
}
|
@@ -104,24 +123,12 @@ class ExclusionSettings implements AdminSettingsInterface {
|
|
104 |
return implode( "\n", array_filter( explode( ',', $value ) ) );
|
105 |
}
|
106 |
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
$api = API::getInstance();
|
115 |
-
$excluded_ips = $this->from_comma_list( $api->getExcludedIpsGlobal() );
|
116 |
-
$excluded_query_params = $this->from_comma_list( $api->getExcludedQueryParametersGlobal() );
|
117 |
-
$excluded_user_agents = $this->from_comma_list( $api->getExcludedUserAgentsGlobal() );
|
118 |
-
$keep_url_fragments = $api->getKeepURLFragmentsGlobal();
|
119 |
-
$current_ip = $this->get_current_ip();
|
120 |
-
$settings = $this->settings;
|
121 |
-
|
122 |
-
include dirname( __FILE__ ) . '/views/exclusion_settings.php';
|
123 |
-
}
|
124 |
-
|
125 |
private function get_current_ip() {
|
126 |
if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
|
127 |
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
@@ -133,5 +140,4 @@ class ExclusionSettings implements AdminSettingsInterface {
|
|
133 |
|
134 |
return $ip;
|
135 |
}
|
136 |
-
|
137 |
}
|
36 |
return esc_html__( 'Exclusions', 'matomo' );
|
37 |
}
|
38 |
|
39 |
+
public function show_settings() {
|
40 |
+
global $wp_roles;
|
41 |
+
|
42 |
+
$was_updated = $this->update_if_submitted();
|
43 |
+
|
44 |
+
Bootstrap::do_bootstrap();
|
45 |
+
|
46 |
+
$api = API::getInstance();
|
47 |
+
$excluded_ips = $this->from_comma_list( $api->getExcludedIpsGlobal() );
|
48 |
+
$excluded_query_params = $this->from_comma_list( $api->getExcludedQueryParametersGlobal() );
|
49 |
+
$excluded_user_agents = $this->from_comma_list( $api->getExcludedUserAgentsGlobal() );
|
50 |
+
$keep_url_fragments = $api->getKeepURLFragmentsGlobal();
|
51 |
+
$current_ip = $this->get_current_ip();
|
52 |
+
$settings = $this->settings;
|
53 |
+
|
54 |
+
include dirname( __FILE__ ) . '/views/exclusion_settings.php';
|
55 |
+
}
|
56 |
+
|
57 |
private function update_if_submitted() {
|
58 |
if ( isset( $_POST )
|
59 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
61 |
&& check_admin_referer( self::NONCE_NAME )
|
62 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
63 |
Bootstrap::do_bootstrap();
|
64 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
65 |
+
$post = wp_unslash( $_POST[ self::FORM_NAME ] );
|
66 |
|
67 |
$api = API::getInstance();
|
68 |
if ( isset( $post['excluded_ips'] ) ) {
|
87 |
}
|
88 |
|
89 |
$keep_fragments = ! empty( $post['keep_url_fragments'] );
|
90 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
91 |
if ( $keep_fragments != $api->getKeepURLFragmentsGlobal() ) {
|
92 |
$api->setKeepURLFragmentsGlobal( $keep_fragments );
|
93 |
}
|
94 |
|
95 |
+
$setting_values = [ Settings::OPTION_KEY_STEALTH => [] ];
|
96 |
if ( ! empty( $post[ Settings::OPTION_KEY_STEALTH ] ) ) {
|
97 |
$setting_values[ Settings::OPTION_KEY_STEALTH ] = $post[ Settings::OPTION_KEY_STEALTH ];
|
98 |
}
|
123 |
return implode( "\n", array_filter( explode( ',', $value ) ) );
|
124 |
}
|
125 |
|
126 |
+
/**
|
127 |
+
* do not sanitize $_SERVER variables
|
128 |
+
* phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
129 |
+
*
|
130 |
+
* @return mixed|string
|
131 |
+
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
private function get_current_ip() {
|
133 |
if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
|
134 |
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
140 |
|
141 |
return $ip;
|
142 |
}
|
|
|
143 |
}
|
classes/WpMatomo/Admin/GeolocationSettings.php
CHANGED
@@ -19,7 +19,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
19 |
|
20 |
class GeolocationSettings implements AdminSettingsInterface {
|
21 |
const NONCE_NAME = 'matomo_geolocation';
|
22 |
-
const FORM_NAME
|
23 |
|
24 |
/**
|
25 |
* @var Settings
|
@@ -34,24 +34,33 @@ class GeolocationSettings implements AdminSettingsInterface {
|
|
34 |
return esc_html__( 'Geolocation', 'matomo' );
|
35 |
}
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
private function update_if_submitted() {
|
38 |
if ( isset( $_POST )
|
39 |
&& isset( $_POST[ self::FORM_NAME ] )
|
40 |
&& is_admin()
|
41 |
&& check_admin_referer( self::NONCE_NAME )
|
42 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
|
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
if (empty($maxmind_license)) {
|
47 |
$maxmind_license = '';
|
48 |
-
} elseif (strlen($maxmind_license) > 20 || strlen($maxmind_license) < 7 || !ctype_graph($maxmind_license)) {
|
49 |
return false;
|
50 |
}
|
51 |
|
52 |
-
$this->settings->apply_changes(
|
53 |
-
|
54 |
-
|
|
|
|
|
55 |
|
56 |
// update geoip in the backgronud
|
57 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_GEOIP );
|
@@ -59,13 +68,4 @@ class GeolocationSettings implements AdminSettingsInterface {
|
|
59 |
return true;
|
60 |
}
|
61 |
}
|
62 |
-
|
63 |
-
public function show_settings() {
|
64 |
-
$invalid_format = $this->update_if_submitted() === false;
|
65 |
-
|
66 |
-
$current_maxmind_license = $this->settings->get_global_option('maxmind_license_key');
|
67 |
-
|
68 |
-
include dirname( __FILE__ ) . '/views/geolocation_settings.php';
|
69 |
-
}
|
70 |
-
|
71 |
}
|
19 |
|
20 |
class GeolocationSettings implements AdminSettingsInterface {
|
21 |
const NONCE_NAME = 'matomo_geolocation';
|
22 |
+
const FORM_NAME = 'matomo_maxmind_license';
|
23 |
|
24 |
/**
|
25 |
* @var Settings
|
34 |
return esc_html__( 'Geolocation', 'matomo' );
|
35 |
}
|
36 |
|
37 |
+
public function show_settings() {
|
38 |
+
$invalid_format = $this->update_if_submitted() === false;
|
39 |
+
|
40 |
+
$current_maxmind_license = $this->settings->get_global_option( 'maxmind_license_key' );
|
41 |
+
|
42 |
+
include dirname( __FILE__ ) . '/views/geolocation_settings.php';
|
43 |
+
}
|
44 |
+
|
45 |
private function update_if_submitted() {
|
46 |
if ( isset( $_POST )
|
47 |
&& isset( $_POST[ self::FORM_NAME ] )
|
48 |
&& is_admin()
|
49 |
&& check_admin_referer( self::NONCE_NAME )
|
50 |
&& current_user_can( Capabilities::KEY_SUPERUSER ) ) {
|
51 |
+
$maxmind_license = trim( stripslashes( sanitize_text_field( wp_unslash( $_POST[ self::FORM_NAME ] ) ) ) );
|
52 |
|
53 |
+
if ( empty( $maxmind_license ) ) {
|
|
|
|
|
54 |
$maxmind_license = '';
|
55 |
+
} elseif ( strlen( $maxmind_license ) > 20 || strlen( $maxmind_license ) < 7 || ! ctype_graph( $maxmind_license ) ) {
|
56 |
return false;
|
57 |
}
|
58 |
|
59 |
+
$this->settings->apply_changes(
|
60 |
+
[
|
61 |
+
'maxmind_license_key' => $maxmind_license,
|
62 |
+
]
|
63 |
+
);
|
64 |
|
65 |
// update geoip in the backgronud
|
66 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_GEOIP );
|
68 |
return true;
|
69 |
}
|
70 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
}
|
classes/WpMatomo/Admin/GetStarted.php
CHANGED
@@ -31,6 +31,15 @@ class GetStarted {
|
|
31 |
$this->settings = $settings;
|
32 |
}
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
private function update_if_submitted() {
|
35 |
if ( isset( $_POST )
|
36 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
@@ -40,16 +49,16 @@ class GetStarted {
|
|
40 |
if ( ! empty( $_POST[ self::FORM_NAME ][ Settings::SHOW_GET_STARTED_PAGE ] )
|
41 |
&& 'no' === $_POST[ self::FORM_NAME ][ Settings::SHOW_GET_STARTED_PAGE ] ) {
|
42 |
$this->settings->apply_changes(
|
43 |
-
|
44 |
Settings::SHOW_GET_STARTED_PAGE => 0,
|
45 |
-
|
46 |
);
|
47 |
|
48 |
return true;
|
49 |
}
|
50 |
if ( ! empty( $_POST[ self::FORM_NAME ]['track_mode'] )
|
51 |
&& TrackingSettings::TRACK_MODE_DEFAULT === $_POST[ self::FORM_NAME ]['track_mode'] ) {
|
52 |
-
$this->settings->apply_tracking_related_changes(
|
53 |
|
54 |
return true;
|
55 |
}
|
@@ -63,15 +72,4 @@ class GetStarted {
|
|
63 |
|
64 |
return $tracking_settings->can_user_manage();
|
65 |
}
|
66 |
-
|
67 |
-
public function show() {
|
68 |
-
$was_updated = $this->update_if_submitted();
|
69 |
-
$settings = $this->settings;
|
70 |
-
$can_user_edit = $this->can_user_manage();
|
71 |
-
$show_this_page = $this->settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE );
|
72 |
-
|
73 |
-
include dirname( __FILE__ ) . '/views/get_started.php';
|
74 |
-
}
|
75 |
-
|
76 |
-
|
77 |
}
|
31 |
$this->settings = $settings;
|
32 |
}
|
33 |
|
34 |
+
public function show() {
|
35 |
+
$was_updated = $this->update_if_submitted();
|
36 |
+
$settings = $this->settings;
|
37 |
+
$can_user_edit = $this->can_user_manage();
|
38 |
+
$show_this_page = $this->settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE );
|
39 |
+
|
40 |
+
include dirname( __FILE__ ) . '/views/get_started.php';
|
41 |
+
}
|
42 |
+
|
43 |
private function update_if_submitted() {
|
44 |
if ( isset( $_POST )
|
45 |
&& ! empty( $_POST[ self::FORM_NAME ] )
|
49 |
if ( ! empty( $_POST[ self::FORM_NAME ][ Settings::SHOW_GET_STARTED_PAGE ] )
|
50 |
&& 'no' === $_POST[ self::FORM_NAME ][ Settings::SHOW_GET_STARTED_PAGE ] ) {
|
51 |
$this->settings->apply_changes(
|
52 |
+
[
|
53 |
Settings::SHOW_GET_STARTED_PAGE => 0,
|
54 |
+
]
|
55 |
);
|
56 |
|
57 |
return true;
|
58 |
}
|
59 |
if ( ! empty( $_POST[ self::FORM_NAME ]['track_mode'] )
|
60 |
&& TrackingSettings::TRACK_MODE_DEFAULT === $_POST[ self::FORM_NAME ]['track_mode'] ) {
|
61 |
+
$this->settings->apply_tracking_related_changes( [ 'track_mode' => TrackingSettings::TRACK_MODE_DEFAULT ] );
|
62 |
|
63 |
return true;
|
64 |
}
|
72 |
|
73 |
return $tracking_settings->can_user_manage();
|
74 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
}
|
classes/WpMatomo/Admin/Info.php
CHANGED
@@ -21,48 +21,50 @@ class Info {
|
|
21 |
|
22 |
private function update_if_submitted() {
|
23 |
if ( isset( $_POST )
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
$
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
38 |
|
39 |
return true;
|
40 |
}
|
41 |
}
|
42 |
|
43 |
private function show_newsletter_signup() {
|
44 |
-
if (!is_user_logged_in()) {
|
45 |
return false;
|
46 |
}
|
47 |
|
48 |
$user = wp_get_current_user();
|
49 |
-
|
|
|
50 |
}
|
51 |
|
52 |
public function show() {
|
53 |
-
$this->render('info');
|
54 |
}
|
55 |
|
56 |
public function show_multisite() {
|
57 |
-
$this->render('info_multisite');
|
58 |
}
|
59 |
|
60 |
-
private function render($template) {
|
61 |
$signedup_newsletter = $this->update_if_submitted();
|
62 |
$show_newsletter = $this->show_newsletter_signup();
|
63 |
|
64 |
include dirname( __FILE__ ) . '/views/' . $template . '.php';
|
65 |
}
|
66 |
-
|
67 |
-
|
68 |
}
|
21 |
|
22 |
private function update_if_submitted() {
|
23 |
if ( isset( $_POST )
|
24 |
+
&& ! empty( $_POST[ self::FORM_NAME ] )
|
25 |
+
&& is_admin()
|
26 |
+
&& check_admin_referer( self::NONCE_NAME )
|
27 |
+
&& $this->show_newsletter_signup()
|
28 |
+
&& current_user_can( Capabilities::KEY_VIEW ) ) {
|
29 |
+
$user = wp_get_current_user();
|
30 |
+
$locale = explode( '_', get_user_locale( $user->ID ) );
|
31 |
+
wp_remote_get(
|
32 |
+
'https://api.matomo.org/1.0/subscribeNewsletter/?' . http_build_query(
|
33 |
+
[
|
34 |
+
'email' => $user->user_email,
|
35 |
+
'wordpress' => 1,
|
36 |
+
'language' => $locale[0],
|
37 |
+
]
|
38 |
+
)
|
39 |
+
);
|
40 |
+
update_user_meta( $user->ID, self::FORM_NAME, '1' );
|
41 |
|
42 |
return true;
|
43 |
}
|
44 |
}
|
45 |
|
46 |
private function show_newsletter_signup() {
|
47 |
+
if ( ! is_user_logged_in() ) {
|
48 |
return false;
|
49 |
}
|
50 |
|
51 |
$user = wp_get_current_user();
|
52 |
+
|
53 |
+
return ! get_user_meta( $user->ID, self::FORM_NAME, true );
|
54 |
}
|
55 |
|
56 |
public function show() {
|
57 |
+
$this->render( 'info' );
|
58 |
}
|
59 |
|
60 |
public function show_multisite() {
|
61 |
+
$this->render( 'info_multisite' );
|
62 |
}
|
63 |
|
64 |
+
private function render( $template ) {
|
65 |
$signedup_newsletter = $this->update_if_submitted();
|
66 |
$show_newsletter = $this->show_newsletter_signup();
|
67 |
|
68 |
include dirname( __FILE__ ) . '/views/' . $template . '.php';
|
69 |
}
|
|
|
|
|
70 |
}
|
classes/WpMatomo/Admin/Marketplace.php
CHANGED
@@ -31,5 +31,4 @@ class Marketplace {
|
|
31 |
|
32 |
include dirname( __FILE__ ) . '/views/marketplace.php';
|
33 |
}
|
34 |
-
|
35 |
}
|
31 |
|
32 |
include dirname( __FILE__ ) . '/views/marketplace.php';
|
33 |
}
|
|
|
34 |
}
|
classes/WpMatomo/Admin/Menu.php
CHANGED
@@ -52,15 +52,15 @@ class Menu {
|
|
52 |
public function __construct( $settings ) {
|
53 |
$this->settings = $settings;
|
54 |
// Hook for adding admin menus
|
55 |
-
add_action( 'admin_menu',
|
56 |
-
add_action( 'network_admin_menu',
|
57 |
-
add_action( 'admin_head',
|
58 |
|
59 |
// as we are redirecting we need to perform the redirect as soon as possible before WP has eg echoed the header
|
60 |
-
add_action( 'load-matomo-analytics_page_' . self::SLUG_REPORTING,
|
61 |
-
add_action( 'load-' . self::$parent_slug . '_page_' . self::SLUG_REPORTING,
|
62 |
-
add_action( 'load-matomo-analytics_page_' . self::SLUG_TAGMANAGER,
|
63 |
-
add_action( 'load-' . self::$parent_slug . '_page_' . self::SLUG_TAGMANAGER,
|
64 |
}
|
65 |
|
66 |
public function add_menu() {
|
@@ -75,19 +75,19 @@ class Menu {
|
|
75 |
add_menu_page( 'Matomo Analytics', 'Matomo Analytics', self::CAP_NOT_EXISTS, 'matomo', null, 'dashicons-analytics' );
|
76 |
|
77 |
if ( $this->settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE ) && $get_started->can_user_manage() ) {
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
}
|
92 |
|
93 |
if ( is_network_admin() ) {
|
@@ -97,10 +97,10 @@ class Menu {
|
|
97 |
__( 'Multi Site', 'matomo' ),
|
98 |
Capabilities::KEY_SUPERUSER,
|
99 |
'matomo-multisite',
|
100 |
-
|
101 |
$info,
|
102 |
'show_multisite',
|
103 |
-
|
104 |
);
|
105 |
} else {
|
106 |
add_submenu_page(
|
@@ -109,10 +109,10 @@ class Menu {
|
|
109 |
__( 'Summary', 'matomo' ),
|
110 |
Capabilities::KEY_VIEW,
|
111 |
self::SLUG_REPORT_SUMMARY,
|
112 |
-
|
113 |
$summary,
|
114 |
'show',
|
115 |
-
|
116 |
);
|
117 |
|
118 |
// the network itself is not a blog
|
@@ -122,30 +122,29 @@ class Menu {
|
|
122 |
__( 'Reporting', 'matomo' ),
|
123 |
Capabilities::KEY_VIEW,
|
124 |
self::SLUG_REPORTING,
|
125 |
-
|
126 |
$this,
|
127 |
'reporting',
|
128 |
-
|
129 |
);
|
130 |
// the network itself is not a blog
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
}
|
146 |
|
147 |
-
|
148 |
-
|
149 |
|
150 |
if ( $can_matomo_be_managed ) {
|
151 |
add_submenu_page(
|
@@ -154,10 +153,10 @@ class Menu {
|
|
154 |
__( 'Settings', 'matomo' ),
|
155 |
Capabilities::KEY_SUPERUSER,
|
156 |
self::SLUG_SETTINGS,
|
157 |
-
|
158 |
$admin_settings,
|
159 |
'show',
|
160 |
-
|
161 |
);
|
162 |
}
|
163 |
|
@@ -168,10 +167,10 @@ class Menu {
|
|
168 |
__( 'Marketplace', 'matomo' ),
|
169 |
Capabilities::KEY_VIEW,
|
170 |
self::SLUG_MARKETPLACE,
|
171 |
-
|
172 |
$marketplace,
|
173 |
'show',
|
174 |
-
|
175 |
);
|
176 |
}
|
177 |
|
@@ -182,10 +181,10 @@ class Menu {
|
|
182 |
__( 'Diagnostics', 'matomo' ),
|
183 |
Capabilities::KEY_SUPERUSER,
|
184 |
self::SLUG_SYSTEM_REPORT,
|
185 |
-
|
186 |
$system_report,
|
187 |
'show',
|
188 |
-
|
189 |
);
|
190 |
}
|
191 |
|
@@ -195,10 +194,10 @@ class Menu {
|
|
195 |
__( 'About', 'matomo' ),
|
196 |
Capabilities::KEY_VIEW,
|
197 |
self::SLUG_ABOUT,
|
198 |
-
|
199 |
$info,
|
200 |
'show',
|
201 |
-
|
202 |
);
|
203 |
}
|
204 |
|
@@ -210,6 +209,8 @@ class Menu {
|
|
210 |
$tagmanager = __( 'Tag Manager', 'matomo' );
|
211 |
foreach ( $submenu[ self::$parent_slug ] as $key => $menu_item ) {
|
212 |
if ( 0 === strpos( $menu_item[0], $reporting ) || 0 === strpos( $menu_item[0], $tagmanager ) ) {
|
|
|
|
|
213 |
$submenu[ self::$parent_slug ][ $key ][0] .= ' <span class="dashicons-before dashicons-external"></span>';
|
214 |
}
|
215 |
}
|
@@ -217,7 +218,7 @@ class Menu {
|
|
217 |
}
|
218 |
|
219 |
public static function get_matomo_goto_url( $goto ) {
|
220 |
-
return add_query_arg(
|
221 |
}
|
222 |
|
223 |
public static function get_reporting_url() {
|
@@ -233,7 +234,7 @@ class Menu {
|
|
233 |
|
234 |
public function reporting() {
|
235 |
if ( ! empty( $_GET['goto'] ) ) {
|
236 |
-
switch ( $_GET['goto'] ) {
|
237 |
case self::REPORTING_GOTO_ADMIN:
|
238 |
$this->go_to_matomo_page( 'CoreAdminHome', 'home', Capabilities::KEY_SUPERUSER );
|
239 |
break;
|
@@ -264,26 +265,26 @@ class Menu {
|
|
264 |
$idsite = $site->get_current_matomo_site_id();
|
265 |
|
266 |
if ( $idsite ) {
|
267 |
-
$url = add_query_arg(
|
268 |
}
|
269 |
|
270 |
if ( ! empty( $_GET['report_date'] ) ) {
|
271 |
-
$
|
272 |
-
|
|
|
273 |
'module' => 'CoreHome',
|
274 |
'action' => 'index',
|
275 |
-
|
276 |
$url
|
277 |
);
|
278 |
|
279 |
-
|
280 |
$date = new Dates();
|
281 |
-
list( $period, $date ) = $date->detect_period_and_date( $
|
282 |
$url = add_query_arg(
|
283 |
-
|
284 |
'period' => $period,
|
285 |
'date' => $date,
|
286 |
-
|
287 |
$url
|
288 |
);
|
289 |
}
|
@@ -295,7 +296,7 @@ class Menu {
|
|
295 |
/**
|
296 |
* @api
|
297 |
*/
|
298 |
-
public static function get_matomo_reporting_url( $category, $subcategory, $params =
|
299 |
$site = new Site();
|
300 |
$idsite = $site->get_current_matomo_site_id();
|
301 |
|
@@ -330,7 +331,7 @@ class Menu {
|
|
330 |
/**
|
331 |
* @api
|
332 |
*/
|
333 |
-
public static function get_matomo_action_url( $module, $action, $params =
|
334 |
$site = new Site();
|
335 |
$idsite = $site->get_current_matomo_site_id();
|
336 |
|
@@ -372,5 +373,4 @@ class Menu {
|
|
372 |
wp_safe_redirect( $url );
|
373 |
exit;
|
374 |
}
|
375 |
-
|
376 |
}
|
52 |
public function __construct( $settings ) {
|
53 |
$this->settings = $settings;
|
54 |
// Hook for adding admin menus
|
55 |
+
add_action( 'admin_menu', [ $this, 'add_menu' ] );
|
56 |
+
add_action( 'network_admin_menu', [ $this, 'add_menu' ] );
|
57 |
+
add_action( 'admin_head', [ $this, 'menu_external_icons' ] );
|
58 |
|
59 |
// as we are redirecting we need to perform the redirect as soon as possible before WP has eg echoed the header
|
60 |
+
add_action( 'load-matomo-analytics_page_' . self::SLUG_REPORTING, [ $this, 'reporting' ] );
|
61 |
+
add_action( 'load-' . self::$parent_slug . '_page_' . self::SLUG_REPORTING, [ $this, 'reporting' ] );
|
62 |
+
add_action( 'load-matomo-analytics_page_' . self::SLUG_TAGMANAGER, [ $this, 'tagmanager' ] );
|
63 |
+
add_action( 'load-' . self::$parent_slug . '_page_' . self::SLUG_TAGMANAGER, [ $this, 'tagmanager' ] );
|
64 |
}
|
65 |
|
66 |
public function add_menu() {
|
75 |
add_menu_page( 'Matomo Analytics', 'Matomo Analytics', self::CAP_NOT_EXISTS, 'matomo', null, 'dashicons-analytics' );
|
76 |
|
77 |
if ( $this->settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE ) && $get_started->can_user_manage() ) {
|
78 |
+
if ( ! is_multisite() || ! is_network_admin() ) {
|
79 |
+
add_submenu_page(
|
80 |
+
self::$parent_slug,
|
81 |
+
__( 'Get Started', 'matomo' ),
|
82 |
+
__( 'Get Started', 'matomo' ),
|
83 |
+
Capabilities::KEY_SUPERUSER,
|
84 |
+
self::SLUG_GET_STARTED,
|
85 |
+
[
|
86 |
+
$get_started,
|
87 |
+
'show',
|
88 |
+
]
|
89 |
+
);
|
90 |
+
}
|
91 |
}
|
92 |
|
93 |
if ( is_network_admin() ) {
|
97 |
__( 'Multi Site', 'matomo' ),
|
98 |
Capabilities::KEY_SUPERUSER,
|
99 |
'matomo-multisite',
|
100 |
+
[
|
101 |
$info,
|
102 |
'show_multisite',
|
103 |
+
]
|
104 |
);
|
105 |
} else {
|
106 |
add_submenu_page(
|
109 |
__( 'Summary', 'matomo' ),
|
110 |
Capabilities::KEY_VIEW,
|
111 |
self::SLUG_REPORT_SUMMARY,
|
112 |
+
[
|
113 |
$summary,
|
114 |
'show',
|
115 |
+
]
|
116 |
);
|
117 |
|
118 |
// the network itself is not a blog
|
122 |
__( 'Reporting', 'matomo' ),
|
123 |
Capabilities::KEY_VIEW,
|
124 |
self::SLUG_REPORTING,
|
125 |
+
[
|
126 |
$this,
|
127 |
'reporting',
|
128 |
+
]
|
129 |
);
|
130 |
// the network itself is not a blog
|
131 |
+
if ( matomo_has_tag_manager() ) {
|
132 |
+
add_submenu_page(
|
133 |
+
self::$parent_slug,
|
134 |
+
__( 'Tag Manager', 'matomo' ),
|
135 |
+
__( 'Tag Manager', 'matomo' ),
|
136 |
+
Capabilities::KEY_WRITE,
|
137 |
+
self::SLUG_TAGMANAGER,
|
138 |
+
[
|
139 |
+
$this,
|
140 |
+
'tagmanager',
|
141 |
+
]
|
142 |
+
);
|
143 |
+
}
|
|
|
144 |
}
|
145 |
|
146 |
+
// we always show settings except when multi site is used, plugin is not network enabled, and we are in network admin
|
147 |
+
$can_matomo_be_managed = ( ! is_multisite() || $this->settings->is_network_enabled() || ! is_network_admin() );
|
148 |
|
149 |
if ( $can_matomo_be_managed ) {
|
150 |
add_submenu_page(
|
153 |
__( 'Settings', 'matomo' ),
|
154 |
Capabilities::KEY_SUPERUSER,
|
155 |
self::SLUG_SETTINGS,
|
156 |
+
[
|
157 |
$admin_settings,
|
158 |
'show',
|
159 |
+
]
|
160 |
);
|
161 |
}
|
162 |
|
167 |
__( 'Marketplace', 'matomo' ),
|
168 |
Capabilities::KEY_VIEW,
|
169 |
self::SLUG_MARKETPLACE,
|
170 |
+
[
|
171 |
$marketplace,
|
172 |
'show',
|
173 |
+
]
|
174 |
);
|
175 |
}
|
176 |
|
181 |
__( 'Diagnostics', 'matomo' ),
|
182 |
Capabilities::KEY_SUPERUSER,
|
183 |
self::SLUG_SYSTEM_REPORT,
|
184 |
+
[
|
185 |
$system_report,
|
186 |
'show',
|
187 |
+
]
|
188 |
);
|
189 |
}
|
190 |
|
194 |
__( 'About', 'matomo' ),
|
195 |
Capabilities::KEY_VIEW,
|
196 |
self::SLUG_ABOUT,
|
197 |
+
[
|
198 |
$info,
|
199 |
'show',
|
200 |
+
]
|
201 |
);
|
202 |
}
|
203 |
|
209 |
$tagmanager = __( 'Tag Manager', 'matomo' );
|
210 |
foreach ( $submenu[ self::$parent_slug ] as $key => $menu_item ) {
|
211 |
if ( 0 === strpos( $menu_item[0], $reporting ) || 0 === strpos( $menu_item[0], $tagmanager ) ) {
|
212 |
+
// No other choice
|
213 |
+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
214 |
$submenu[ self::$parent_slug ][ $key ][0] .= ' <span class="dashicons-before dashicons-external"></span>';
|
215 |
}
|
216 |
}
|
218 |
}
|
219 |
|
220 |
public static function get_matomo_goto_url( $goto ) {
|
221 |
+
return add_query_arg( [ 'goto' => $goto ], menu_page_url( self::SLUG_REPORTING, false ) );
|
222 |
}
|
223 |
|
224 |
public static function get_reporting_url() {
|
234 |
|
235 |
public function reporting() {
|
236 |
if ( ! empty( $_GET['goto'] ) ) {
|
237 |
+
switch ( sanitize_text_field( wp_unslash( $_GET['goto'] ) ) ) {
|
238 |
case self::REPORTING_GOTO_ADMIN:
|
239 |
$this->go_to_matomo_page( 'CoreAdminHome', 'home', Capabilities::KEY_SUPERUSER );
|
240 |
break;
|
265 |
$idsite = $site->get_current_matomo_site_id();
|
266 |
|
267 |
if ( $idsite ) {
|
268 |
+
$url = add_query_arg( [ 'idSite' => (int) $idsite ], $url );
|
269 |
}
|
270 |
|
271 |
if ( ! empty( $_GET['report_date'] ) ) {
|
272 |
+
$report_date = sanitize_text_field( wp_unslash( $_GET['report_date'] ) );
|
273 |
+
$url = add_query_arg(
|
274 |
+
[
|
275 |
'module' => 'CoreHome',
|
276 |
'action' => 'index',
|
277 |
+
],
|
278 |
$url
|
279 |
);
|
280 |
|
|
|
281 |
$date = new Dates();
|
282 |
+
list( $period, $date ) = $date->detect_period_and_date( $report_date );
|
283 |
$url = add_query_arg(
|
284 |
+
[
|
285 |
'period' => $period,
|
286 |
'date' => $date,
|
287 |
+
],
|
288 |
$url
|
289 |
);
|
290 |
}
|
296 |
/**
|
297 |
* @api
|
298 |
*/
|
299 |
+
public static function get_matomo_reporting_url( $category, $subcategory, $params = [] ) {
|
300 |
$site = new Site();
|
301 |
$idsite = $site->get_current_matomo_site_id();
|
302 |
|
331 |
/**
|
332 |
* @api
|
333 |
*/
|
334 |
+
public static function get_matomo_action_url( $module, $action, $params = [] ) {
|
335 |
$site = new Site();
|
336 |
$idsite = $site->get_current_matomo_site_id();
|
337 |
|
373 |
wp_safe_redirect( $url );
|
374 |
exit;
|
375 |
}
|
|
|
376 |
}
|
classes/WpMatomo/Admin/PrivacySettings.php
CHANGED
@@ -19,21 +19,21 @@ class PrivacySettings implements AdminSettingsInterface {
|
|
19 |
const EXAMPLE_MINIMAL = '[matomo_opt_out]';
|
20 |
const EXAMPLE_FULL = '[matomo_opt_out language=de]';
|
21 |
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
|
31 |
public function get_title() {
|
32 |
return esc_html__( 'Privacy & GDPR', 'matomo' );
|
33 |
}
|
34 |
|
35 |
public function show_settings() {
|
36 |
-
|
37 |
|
38 |
include dirname( __FILE__ ) . '/views/privacy_gdpr.php';
|
39 |
}
|
19 |
const EXAMPLE_MINIMAL = '[matomo_opt_out]';
|
20 |
const EXAMPLE_FULL = '[matomo_opt_out language=de]';
|
21 |
|
22 |
+
/**
|
23 |
+
* @var Settings
|
24 |
+
*/
|
25 |
+
private $settings;
|
26 |
|
27 |
+
public function __construct( Settings $settings ) {
|
28 |
+
$this->settings = $settings;
|
29 |
+
}
|
30 |
|
31 |
public function get_title() {
|
32 |
return esc_html__( 'Privacy & GDPR', 'matomo' );
|
33 |
}
|
34 |
|
35 |
public function show_settings() {
|
36 |
+
$matomo_settings = $this->settings;
|
37 |
|
38 |
include dirname( __FILE__ ) . '/views/privacy_gdpr.php';
|
39 |
}
|
classes/WpMatomo/Admin/SafeModeMenu.php
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
12 |
use WpMatomo\Settings;
|
13 |
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -28,12 +29,12 @@ class SafeModeMenu {
|
|
28 |
*/
|
29 |
public function __construct( $settings ) {
|
30 |
$this->settings = $settings;
|
31 |
-
add_action( 'admin_menu',
|
32 |
-
add_action( 'network_admin_menu',
|
33 |
}
|
34 |
|
35 |
public function add_menu() {
|
36 |
-
if ( !
|
37 |
return;
|
38 |
}
|
39 |
|
@@ -47,11 +48,10 @@ class SafeModeMenu {
|
|
47 |
__( 'System Report', 'matomo' ),
|
48 |
'administrator',
|
49 |
Menu::SLUG_SYSTEM_REPORT,
|
50 |
-
|
51 |
$system_report,
|
52 |
'show',
|
53 |
-
|
54 |
);
|
55 |
}
|
56 |
-
|
57 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
+
use WpMatomo;
|
13 |
use WpMatomo\Settings;
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
29 |
*/
|
30 |
public function __construct( $settings ) {
|
31 |
$this->settings = $settings;
|
32 |
+
add_action( 'admin_menu', [ $this, 'add_menu' ] );
|
33 |
+
add_action( 'network_admin_menu', [ $this, 'add_menu' ] );
|
34 |
}
|
35 |
|
36 |
public function add_menu() {
|
37 |
+
if ( ! WpMatomo::is_admin_user() ) {
|
38 |
return;
|
39 |
}
|
40 |
|
48 |
__( 'System Report', 'matomo' ),
|
49 |
'administrator',
|
50 |
Menu::SLUG_SYSTEM_REPORT,
|
51 |
+
[
|
52 |
$system_report,
|
53 |
'show',
|
54 |
+
]
|
55 |
);
|
56 |
}
|
|
|
57 |
}
|
classes/WpMatomo/Admin/Summary.php
CHANGED
@@ -14,13 +14,13 @@ use WpMatomo\Report\Dates;
|
|
14 |
use WpMatomo\Report\Metadata;
|
15 |
use WpMatomo\Report\Renderer;
|
16 |
use WpMatomo\Settings;
|
|
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit; // if accessed directly
|
20 |
}
|
21 |
|
22 |
class Summary {
|
23 |
-
|
24 |
const NONCE_DASHBOARD = 'matomo_pin_dashboard';
|
25 |
|
26 |
/**
|
@@ -36,20 +36,19 @@ class Summary {
|
|
36 |
}
|
37 |
|
38 |
private function pin_if_submitted() {
|
39 |
-
if ( ! empty( $_GET[
|
40 |
-
&& ! empty( $_GET[
|
41 |
-
&& ! empty( $_GET[
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
$unique_id = $_GET[
|
47 |
-
$date = $_GET[
|
48 |
-
|
49 |
$dashobard = new Dashboard();
|
50 |
-
if ($dashobard->is_valid_widget($unique_id, $date)) {
|
51 |
$dashobard->toggle_widget( $unique_id, $date );
|
52 |
-
|
53 |
}
|
54 |
}
|
55 |
|
@@ -57,6 +56,8 @@ class Summary {
|
|
57 |
}
|
58 |
|
59 |
public function show() {
|
|
|
|
|
60 |
$matomo_pinned = $this->pin_if_submitted();
|
61 |
|
62 |
$settings = $this->settings;
|
@@ -67,9 +68,42 @@ class Summary {
|
|
67 |
$report_dates_obj = new Dates();
|
68 |
$report_dates = $report_dates_obj->get_supported_dates();
|
69 |
|
70 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
if ( isset( $_GET['report_date'] ) && isset( $report_dates[ $_GET['report_date'] ] ) ) {
|
72 |
-
$report_date = $_GET['report_date'];
|
73 |
}
|
74 |
|
75 |
list( $report_period_selected, $report_date_selected ) = $report_dates_obj->detect_period_and_date( $report_date );
|
@@ -78,22 +112,24 @@ class Summary {
|
|
78 |
|
79 |
$matomo_dashboard = new Dashboard();
|
80 |
|
81 |
-
$wp_version
|
82 |
-
$matomo_is_version_pre55 = empty($wp_version) || version_compare($wp_version, '5.5.0') === -1;
|
83 |
|
84 |
include dirname( __FILE__ ) . '/views/summary.php';
|
85 |
}
|
86 |
|
87 |
private function get_reports_to_show() {
|
88 |
-
$reports_to_show =
|
|
|
89 |
'VisitsSummary_get',
|
90 |
'UserCountry_getCountry',
|
|
|
91 |
'DevicesDetection_getType',
|
|
|
92 |
'Resolution_getResolution',
|
93 |
'DevicesDetection_getOsFamilies',
|
94 |
'DevicesDetection_getBrowsers',
|
95 |
'VisitTime_getVisitInformationPerServerTime',
|
96 |
-
'Actions_get',
|
97 |
'Actions_getPageTitles',
|
98 |
'Actions_getEntryPageTitles',
|
99 |
'Actions_getExitPageTitles',
|
@@ -102,18 +138,16 @@ class Summary {
|
|
102 |
'Referrers_getAll',
|
103 |
'Referrers_getSocials',
|
104 |
'Referrers_getCampaigns',
|
105 |
-
|
106 |
-
);
|
107 |
|
108 |
if ( $this->settings->get_global_option( 'track_ecommerce' ) ) {
|
109 |
$reports_to_show[] = 'Goals_get_idGoal--ecommerceOrder';
|
110 |
$reports_to_show[] = 'Goals_getItemsName';
|
111 |
}
|
112 |
|
113 |
-
$reports_to_show[] = Renderer::CUSTOM_UNIQUE_ID_VISITS_OVER_TIME;
|
114 |
$reports_to_show = apply_filters( 'matomo_report_summary_report_ids', $reports_to_show );
|
115 |
|
116 |
-
$report_metadata =
|
117 |
$metadata = new Metadata();
|
118 |
foreach ( $reports_to_show as $report_unique_id ) {
|
119 |
$report = $metadata->find_report_by_unique_id( $report_unique_id );
|
@@ -128,5 +162,4 @@ class Summary {
|
|
128 |
|
129 |
return $report_metadata;
|
130 |
}
|
131 |
-
|
132 |
}
|
14 |
use WpMatomo\Report\Metadata;
|
15 |
use WpMatomo\Report\Renderer;
|
16 |
use WpMatomo\Settings;
|
17 |
+
use Piwik\Plugins\UsersManager\UserPreferences;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
21 |
}
|
22 |
|
23 |
class Summary {
|
|
|
24 |
const NONCE_DASHBOARD = 'matomo_pin_dashboard';
|
25 |
|
26 |
/**
|
36 |
}
|
37 |
|
38 |
private function pin_if_submitted() {
|
39 |
+
if ( ! empty( $_GET['pin'] )
|
40 |
+
&& ! empty( $_GET['report_uniqueid'] )
|
41 |
+
&& ! empty( $_GET['report_date'] )
|
42 |
+
&& is_admin()
|
43 |
+
&& check_admin_referer( self::NONCE_DASHBOARD )
|
44 |
+
&& is_user_logged_in()
|
45 |
+
&& current_user_can( Capabilities::KEY_VIEW ) ) {
|
46 |
+
$unique_id = sanitize_text_field( wp_unslash( $_GET['report_uniqueid'] ) );
|
47 |
+
$date = sanitize_text_field( wp_unslash( $_GET['report_date'] ) );
|
|
|
48 |
$dashobard = new Dashboard();
|
49 |
+
if ( $dashobard->is_valid_widget( $unique_id, $date ) ) {
|
50 |
$dashobard->toggle_widget( $unique_id, $date );
|
51 |
+
return true;
|
52 |
}
|
53 |
}
|
54 |
|
56 |
}
|
57 |
|
58 |
public function show() {
|
59 |
+
do_action( 'matomo_load_chartjs' );
|
60 |
+
|
61 |
$matomo_pinned = $this->pin_if_submitted();
|
62 |
|
63 |
$settings = $this->settings;
|
68 |
$report_dates_obj = new Dates();
|
69 |
$report_dates = $report_dates_obj->get_supported_dates();
|
70 |
|
71 |
+
$user_preference = new UserPreferences();
|
72 |
+
$default_date = $user_preference->getDefaultDate();
|
73 |
+
$report_period = $user_preference->getDefaultPeriod();
|
74 |
+
switch ( $report_period ) {
|
75 |
+
case 'day':
|
76 |
+
$report_date = $default_date;
|
77 |
+
break;
|
78 |
+
case 'year':
|
79 |
+
case 'month':
|
80 |
+
case 'week':
|
81 |
+
switch ( $default_date ) {
|
82 |
+
case 'yesterday':
|
83 |
+
$report_date = 'last' . $report_period;
|
84 |
+
break;
|
85 |
+
case 'today':
|
86 |
+
$report_date = 'this' . $report_period;
|
87 |
+
break;
|
88 |
+
}
|
89 |
+
break;
|
90 |
+
case 'range':
|
91 |
+
switch ( $default_date ) {
|
92 |
+
case 'previous30':
|
93 |
+
$report_date = 'lastmonth';
|
94 |
+
break;
|
95 |
+
case 'previous7':
|
96 |
+
$report_date = 'lastweek';
|
97 |
+
break;
|
98 |
+
case 'last30':
|
99 |
+
$report_date = 'thismonth';
|
100 |
+
break;
|
101 |
+
case 'last7':
|
102 |
+
$report_date = 'thisweek';
|
103 |
+
}
|
104 |
+
}
|
105 |
if ( isset( $_GET['report_date'] ) && isset( $report_dates[ $_GET['report_date'] ] ) ) {
|
106 |
+
$report_date = sanitize_text_field( wp_unslash( $_GET['report_date'] ) );
|
107 |
}
|
108 |
|
109 |
list( $report_period_selected, $report_date_selected ) = $report_dates_obj->detect_period_and_date( $report_date );
|
112 |
|
113 |
$matomo_dashboard = new Dashboard();
|
114 |
|
115 |
+
$wp_version = get_bloginfo( 'version' );
|
116 |
+
$matomo_is_version_pre55 = empty( $wp_version ) || version_compare( $wp_version, '5.5.0' ) === - 1;
|
117 |
|
118 |
include dirname( __FILE__ ) . '/views/summary.php';
|
119 |
}
|
120 |
|
121 |
private function get_reports_to_show() {
|
122 |
+
$reports_to_show = [
|
123 |
+
Renderer::CUSTOM_UNIQUE_ID_VISITS_OVER_TIME,
|
124 |
'VisitsSummary_get',
|
125 |
'UserCountry_getCountry',
|
126 |
+
'Actions_get',
|
127 |
'DevicesDetection_getType',
|
128 |
+
'Goals_get',
|
129 |
'Resolution_getResolution',
|
130 |
'DevicesDetection_getOsFamilies',
|
131 |
'DevicesDetection_getBrowsers',
|
132 |
'VisitTime_getVisitInformationPerServerTime',
|
|
|
133 |
'Actions_getPageTitles',
|
134 |
'Actions_getEntryPageTitles',
|
135 |
'Actions_getExitPageTitles',
|
138 |
'Referrers_getAll',
|
139 |
'Referrers_getSocials',
|
140 |
'Referrers_getCampaigns',
|
141 |
+
];
|
|
|
142 |
|
143 |
if ( $this->settings->get_global_option( 'track_ecommerce' ) ) {
|
144 |
$reports_to_show[] = 'Goals_get_idGoal--ecommerceOrder';
|
145 |
$reports_to_show[] = 'Goals_getItemsName';
|
146 |
}
|
147 |
|
|
|
148 |
$reports_to_show = apply_filters( 'matomo_report_summary_report_ids', $reports_to_show );
|
149 |
|
150 |
+
$report_metadata = [];
|
151 |
$metadata = new Metadata();
|
152 |
foreach ( $reports_to_show as $report_unique_id ) {
|
153 |
$report = $metadata->find_report_by_unique_id( $report_unique_id );
|
162 |
|
163 |
return $report_metadata;
|
164 |
}
|
|
|
165 |
}
|
classes/WpMatomo/Admin/SystemReport.php
CHANGED
@@ -9,6 +9,8 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
|
|
12 |
use Piwik\CliMulti;
|
13 |
use Piwik\Common;
|
14 |
use Piwik\Config;
|
@@ -23,6 +25,7 @@ use Piwik\Plugins\UserCountry\LocationProvider;
|
|
23 |
use Piwik\SettingsPiwik;
|
24 |
use Piwik\Tracker\Failures;
|
25 |
use Piwik\Version;
|
|
|
26 |
use WpMatomo\Bootstrap;
|
27 |
use WpMatomo\Capabilities;
|
28 |
use WpMatomo\Installer;
|
@@ -39,6 +42,20 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
39 |
exit; // if accessed directly
|
40 |
}
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
class SystemReport {
|
43 |
const NONCE_NAME = 'matomo_troubleshooting';
|
44 |
const TROUBLESHOOT_SYNC_USERS = 'matomo_troubleshooting_action_site_users';
|
@@ -51,21 +68,32 @@ class SystemReport {
|
|
51 |
const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
|
52 |
const TROUBLESHOOT_RUN_UPDATER = 'matomo_troubleshooting_action_run_updater';
|
53 |
|
54 |
-
private $not_compatible_plugins =
|
55 |
-
'background-manager',
|
56 |
-
|
57 |
-
'
|
58 |
-
|
59 |
-
'
|
60 |
-
|
61 |
-
'
|
62 |
-
|
63 |
-
'
|
64 |
-
|
65 |
-
'
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
/**
|
71 |
* @var Settings
|
@@ -81,12 +109,12 @@ class SystemReport {
|
|
81 |
/**
|
82 |
* @var \WpMatomo\Db\Settings
|
83 |
*/
|
84 |
-
public $
|
85 |
|
86 |
public function __construct( Settings $settings ) {
|
87 |
-
$this->settings
|
88 |
-
$this->logger
|
89 |
-
$this->
|
90 |
}
|
91 |
|
92 |
public function get_not_compatible_plugins() {
|
@@ -102,36 +130,38 @@ class SystemReport {
|
|
102 |
Bootstrap::do_bootstrap();
|
103 |
$scheduled_tasks = new ScheduledTasks( $this->settings );
|
104 |
|
105 |
-
if (!defined('PIWIK_ARCHIVE_NO_TRUNCATE')) {
|
106 |
-
|
|
|
107 |
}
|
108 |
|
109 |
try {
|
110 |
// force invalidation of archive to ensure it actually will rearchive the data
|
111 |
-
$site
|
112 |
$idsite = $site->get_current_matomo_site_id();
|
113 |
-
if ($idsite) {
|
114 |
-
$timezone
|
115 |
-
$now_string = \Piwik\Date::factory('now', $timezone)->toString();
|
116 |
-
foreach (
|
117 |
-
API::getInstance()->invalidateArchivedReports($idsite, $now_string, $period, false, false);
|
118 |
}
|
119 |
}
|
120 |
-
} catch (
|
121 |
-
$this->logger->log_exception('archive_invalidate', $e);
|
122 |
}
|
123 |
|
124 |
try {
|
125 |
-
$errors = $scheduled_tasks->archive(
|
126 |
-
} catch (
|
127 |
-
echo '<div class="error"><p>' . esc_html__('Matomo Archive Error', 'matomo') . ': '. esc_html(matomo_anonymize_value($e->getMessage() . ' =>' . $this->logger->get_readable_trace($e))) . '</p></div>';
|
128 |
throw $e;
|
129 |
}
|
130 |
|
131 |
if ( ! empty( $errors ) ) {
|
132 |
echo '<div class="notice notice-warning"><p>Matomo Archive Warnings: ';
|
133 |
-
foreach ($errors as $error) {
|
134 |
-
|
|
|
135 |
echo '<br/>';
|
136 |
}
|
137 |
echo '</p></div>';
|
@@ -191,47 +221,52 @@ class SystemReport {
|
|
191 |
$settings = $this->settings;
|
192 |
|
193 |
$matomo_active_tab = '';
|
194 |
-
|
195 |
-
|
|
|
|
|
|
|
|
|
196 |
}
|
197 |
|
198 |
-
$matomo_tables =
|
199 |
if ( empty( $matomo_active_tab ) ) {
|
|
|
200 |
$this->initial_error_reporting = @error_reporting();
|
201 |
-
$matomo_tables
|
202 |
-
|
203 |
'title' => 'Matomo',
|
204 |
'rows' => $this->get_matomo_info(),
|
205 |
'has_comments' => true,
|
206 |
-
|
207 |
-
|
208 |
-
'title'
|
209 |
-
'rows'
|
210 |
'has_comments' => true,
|
211 |
-
|
212 |
-
|
213 |
'title' => 'WordPress Plugins',
|
214 |
'rows' => $this->get_plugins_info(),
|
215 |
'has_comments' => true,
|
216 |
-
|
217 |
-
|
218 |
'title' => 'Server',
|
219 |
'rows' => $this->get_server_info(),
|
220 |
'has_comments' => true,
|
221 |
-
|
222 |
-
|
223 |
'title' => 'Database',
|
224 |
'rows' => $this->get_db_info(),
|
225 |
'has_comments' => true,
|
226 |
-
|
227 |
-
|
228 |
'title' => 'Browser',
|
229 |
'rows' => $this->get_browser_info(),
|
230 |
'has_comments' => true,
|
231 |
-
|
232 |
-
|
233 |
}
|
234 |
-
$matomo_tables = apply_filters('matomo_systemreport_tables', $matomo_tables);
|
235 |
$matomo_tables = $this->add_errors_first( $matomo_tables );
|
236 |
$matomo_has_warning_and_no_errors = $this->has_only_warnings_no_error( $matomo_tables );
|
237 |
|
@@ -258,11 +293,11 @@ class SystemReport {
|
|
258 |
}
|
259 |
|
260 |
private function add_errors_first( $report_tables ) {
|
261 |
-
$errors =
|
262 |
'title' => 'Errors',
|
263 |
-
'rows' =>
|
264 |
'has_comments' => true,
|
265 |
-
|
266 |
foreach ( $report_tables as $report_table ) {
|
267 |
foreach ( $report_table['rows'] as $row ) {
|
268 |
if ( ! empty( $row['is_error'] ) ) {
|
@@ -293,28 +328,28 @@ class SystemReport {
|
|
293 |
$comment .= sprintf( esc_html__( '%s is not writable. ', 'matomo' ), $title );
|
294 |
}
|
295 |
|
296 |
-
$rows[] =
|
297 |
'name' => sprintf( esc_html__( '%s exists and is writable.', 'matomo' ), $title ),
|
298 |
'value' => $file_exists && $file_readable && $file_writable ? esc_html__( 'Yes', 'matomo' ) : esc_html__( 'No', 'matomo' ),
|
299 |
'comment' => $comment,
|
300 |
'is_error' => $required && ( ! $file_exists || ! $file_readable ),
|
301 |
'is_warning' => ! $required && ( ! $file_exists || ! $file_readable ),
|
302 |
-
|
303 |
|
304 |
return $rows;
|
305 |
}
|
306 |
|
307 |
private function get_matomo_info() {
|
308 |
-
$rows =
|
309 |
|
310 |
$plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
|
311 |
-
$install_time = get_option(Installer::OPTION_NAME_INSTALL_DATE);
|
312 |
|
313 |
-
$rows[] =
|
314 |
'name' => esc_html__( 'Matomo Plugin Version', 'matomo' ),
|
315 |
'value' => $plugin_data['Version'],
|
316 |
'comment' => '',
|
317 |
-
|
318 |
|
319 |
$paths = new Paths();
|
320 |
$path_config_file = $paths->get_config_ini_path();
|
@@ -323,21 +358,23 @@ class SystemReport {
|
|
323 |
$path_tracker_file = $paths->get_matomo_js_upload_path();
|
324 |
$rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );
|
325 |
|
326 |
-
$rows[] =
|
327 |
'name' => esc_html__( 'Plugin directories', 'matomo' ),
|
328 |
'value' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? 'Yes' : 'No',
|
329 |
'comment' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? wp_json_encode( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) : '',
|
330 |
-
|
331 |
|
332 |
$tmp_dir = $paths->get_tmp_dir();
|
333 |
|
334 |
-
$rows[] =
|
335 |
'name' => esc_html__( 'Tmp directory writable', 'matomo' ),
|
336 |
'value' => is_writable( $tmp_dir ),
|
337 |
'comment' => $tmp_dir,
|
338 |
-
|
339 |
|
340 |
if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] ) ) {
|
|
|
|
|
341 |
$custom_path = rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php';
|
342 |
$path_exists = file_exists( $custom_path );
|
343 |
$comment = '';
|
@@ -345,147 +382,146 @@ class SystemReport {
|
|
345 |
$comment = 'It seems the path does not point to the WP root directory.';
|
346 |
}
|
347 |
|
348 |
-
$rows[] =
|
349 |
'name' => 'Custom MATOMO_WP_ROOT_PATH',
|
350 |
'value' => $path_exists,
|
351 |
'is_error' => ! $path_exists,
|
352 |
'comment' => $comment,
|
353 |
-
|
354 |
}
|
355 |
|
356 |
$report = null;
|
357 |
|
358 |
-
if ( !
|
359 |
try {
|
360 |
Bootstrap::do_bootstrap();
|
361 |
/** @var DiagnosticService $service */
|
362 |
$service = StaticContainer::get( DiagnosticService::class );
|
363 |
$report = $service->runDiagnostics();
|
364 |
|
365 |
-
$rows[] =
|
366 |
'name' => esc_html__( 'Matomo Version', 'matomo' ),
|
367 |
'value' => \Piwik\Version::VERSION,
|
368 |
'comment' => '',
|
369 |
-
|
370 |
-
} catch (
|
371 |
-
$rows[] =
|
372 |
'name' => esc_html__( 'Matomo System Check', 'matomo' ),
|
373 |
'value' => 'Failed to run Matomo system check.',
|
374 |
'comment' => $e->getMessage(),
|
375 |
-
|
376 |
}
|
377 |
}
|
378 |
|
379 |
$site = new Site();
|
380 |
$idsite = $site->get_current_matomo_site_id();
|
381 |
|
382 |
-
$rows[] =
|
383 |
'name' => esc_html__( 'Matomo Blog idSite', 'matomo' ),
|
384 |
'value' => $idsite,
|
385 |
'comment' => '',
|
386 |
-
|
387 |
|
388 |
$install_date = '';
|
389 |
-
if (!empty($install_time)) {
|
390 |
-
$install_date = 'Install date: '.
|
391 |
}
|
392 |
|
393 |
-
$rows[] =
|
394 |
'name' => esc_html__( 'Matomo Install Version', 'matomo' ),
|
395 |
-
'value' => get_option(Installer::OPTION_NAME_INSTALL_VERSION),
|
396 |
'comment' => $install_date,
|
397 |
-
|
398 |
-
|
399 |
-
$wpmatomo_updater = new \WpMatomo\Updater($this->settings);
|
400 |
-
if (!\WpMatomo::is_safe_mode()) {
|
401 |
|
|
|
|
|
402 |
$outstanding_updates = $wpmatomo_updater->get_plugins_requiring_update();
|
403 |
$upgrade_in_progress = $wpmatomo_updater->is_upgrade_in_progress();
|
404 |
-
$rows[]
|
405 |
-
'name'
|
406 |
-
'value'
|
407 |
-
'comment'
|
408 |
-
|
409 |
-
$rows[]
|
410 |
-
'name'
|
411 |
-
'value'
|
412 |
-
'comment'
|
413 |
-
|
414 |
}
|
415 |
|
416 |
-
if (
|
417 |
// this should actually never happen...
|
418 |
-
$rows[] =
|
419 |
-
'name'
|
420 |
-
'is_warning'
|
421 |
-
'value'
|
422 |
-
'comment'
|
423 |
-
|
424 |
}
|
425 |
|
426 |
-
$rows[] =
|
427 |
'section' => 'Endpoints',
|
428 |
-
|
429 |
|
430 |
-
$rows[] =
|
431 |
'name' => 'Matomo JavaScript Tracker URL',
|
432 |
'value' => '',
|
433 |
'comment' => $paths->get_js_tracker_url_in_matomo_dir(),
|
434 |
-
|
435 |
|
436 |
-
$rows[] =
|
437 |
'name' => 'Matomo JavaScript Tracker - WP Rest API',
|
438 |
'value' => '',
|
439 |
'comment' => $paths->get_js_tracker_rest_api_endpoint(),
|
440 |
-
|
441 |
|
442 |
-
$rows[] =
|
443 |
'name' => 'Matomo HTTP Tracking API',
|
444 |
'value' => '',
|
445 |
'comment' => $paths->get_tracker_api_url_in_matomo_dir(),
|
446 |
-
|
447 |
|
448 |
-
$rows[] =
|
449 |
'name' => 'Matomo HTTP Tracking API - WP Rest API',
|
450 |
'value' => '',
|
451 |
'comment' => $paths->get_tracker_api_rest_api_endpoint(),
|
452 |
-
|
453 |
|
454 |
-
$matomo_plugin_dir_name = basename(dirname(MATOMO_ANALYTICS_FILE));
|
455 |
-
if (
|
456 |
-
$rows[] =
|
457 |
-
'name'
|
458 |
-
'value'
|
459 |
'is_error' => true,
|
460 |
-
'comment'
|
461 |
-
|
462 |
-
} elseif (!is_plugin_active('matomo/matomo.php')) {
|
463 |
-
$rows[] =
|
464 |
-
'name'
|
465 |
-
'value'
|
466 |
'is_error' => true,
|
467 |
-
'comment'
|
468 |
-
|
469 |
}
|
470 |
|
471 |
-
$rows[] =
|
472 |
'section' => 'Crons',
|
473 |
-
|
474 |
|
475 |
$scheduled_tasks = new ScheduledTasks( $this->settings );
|
476 |
$all_events = $scheduled_tasks->get_all_events();
|
477 |
|
478 |
-
$rows[] =
|
479 |
'name' => esc_html__( 'Server time', 'matomo' ),
|
480 |
'value' => $this->convert_time_to_date( time(), false ),
|
481 |
'comment' => '',
|
482 |
-
|
483 |
|
484 |
-
$rows[] =
|
485 |
'name' => esc_html__( 'Blog time', 'matomo' ),
|
486 |
'value' => $this->convert_time_to_date( time(), true ),
|
487 |
'comment' => esc_html__( 'Below dates are shown in blog timezone', 'matomo' ),
|
488 |
-
|
489 |
|
490 |
foreach ( $all_events as $event_name => $event_config ) {
|
491 |
$last_run_before = $scheduled_tasks->get_last_time_before_cron( $event_name );
|
@@ -497,133 +533,131 @@ class SystemReport {
|
|
497 |
$comment .= ' Last ended: ' . $this->convert_time_to_date( $last_run_after, true, true ) . '.';
|
498 |
$comment .= ' Interval: ' . $event_config['interval'];
|
499 |
|
500 |
-
$rows[] =
|
501 |
'name' => $event_config['name'],
|
502 |
'value' => 'Next run: ' . $this->convert_time_to_date( $next_scheduled, true, true ),
|
503 |
'comment' => $comment,
|
504 |
-
|
505 |
}
|
506 |
|
507 |
$suports_async = false;
|
508 |
-
if ( !
|
509 |
-
$rows[] =
|
510 |
'section' => esc_html__( 'Mandatory checks', 'matomo' ),
|
511 |
-
|
512 |
|
513 |
$rows = $this->add_diagnostic_results( $rows, $report->getMandatoryDiagnosticResults() );
|
514 |
|
515 |
-
$rows[] =
|
516 |
'section' => esc_html__( 'Optional checks', 'matomo' ),
|
517 |
-
|
518 |
$rows = $this->add_diagnostic_results( $rows, $report->getOptionalDiagnosticResults() );
|
519 |
|
520 |
-
$cli_multi
|
521 |
$suports_async = $cli_multi->supportsAsync();
|
522 |
|
523 |
-
$rows[] =
|
524 |
'name' => 'Supports Async Archiving',
|
525 |
'value' => $suports_async,
|
526 |
'comment' => '',
|
527 |
-
|
528 |
|
529 |
$location_provider = LocationProvider::getCurrentProvider();
|
530 |
-
if ($location_provider) {
|
531 |
-
$rows[] =
|
532 |
'name' => 'Location provider ID',
|
533 |
'value' => $location_provider->getId(),
|
534 |
'comment' => '',
|
535 |
-
|
536 |
-
$rows[] =
|
537 |
'name' => 'Location provider available',
|
538 |
'value' => $location_provider->isAvailable(),
|
539 |
'comment' => '',
|
540 |
-
|
541 |
-
$rows[] =
|
542 |
'name' => 'Location provider working',
|
543 |
'value' => $location_provider->isWorking(),
|
544 |
'comment' => '',
|
545 |
-
|
546 |
}
|
547 |
|
548 |
-
if ( !
|
549 |
Bootstrap::do_bootstrap();
|
550 |
$general = Config::getInstance()->General;
|
551 |
-
|
552 |
-
if (empty($general['proxy_client_headers'])) {
|
553 |
-
foreach (AdvancedSettings::$valid_host_headers as $header) {
|
554 |
-
if (!empty($_SERVER[$header])) {
|
555 |
-
$rows[] =
|
556 |
-
'name'
|
557 |
-
'value'
|
558 |
'is_warning' => true,
|
559 |
-
'comment'
|
560 |
-
|
561 |
}
|
562 |
}
|
563 |
}
|
564 |
-
|
565 |
-
if (!empty($incompatible_plugins)) {
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
}
|
579 |
}
|
580 |
|
581 |
$num_days_check_visits = 5;
|
582 |
-
$had_visits
|
583 |
-
if (
|
584 |
// do not show info if we could not detect it (had_visits === null)
|
585 |
$comment = '';
|
586 |
-
if (
|
587 |
$comment = 'It looks like there were no visits in the last ' . $num_days_check_visits . ' days. This may be expected if tracking is disabled, you have not added the tracking code, or your website does not have many visitors in general and you exclude your own visits.';
|
588 |
}
|
589 |
|
590 |
-
$rows[] =
|
591 |
-
'name'
|
592 |
-
'value'
|
593 |
-
'is_warning' =>
|
594 |
-
'comment'
|
595 |
-
|
596 |
}
|
597 |
|
598 |
-
if ( !
|
599 |
Bootstrap::do_bootstrap();
|
600 |
$matomo_url = SettingsPiwik::getPiwikUrl();
|
601 |
-
$rows[] =
|
602 |
'name' => 'Matomo URL',
|
603 |
'comment' => $matomo_url,
|
604 |
'value' => ! empty( $matomo_url ),
|
605 |
-
|
606 |
}
|
607 |
-
|
608 |
}
|
609 |
|
610 |
-
$rows[] =
|
611 |
'section' => 'Matomo Settings',
|
612 |
-
|
613 |
|
614 |
// always show these settings
|
615 |
-
$global_settings_always_show =
|
616 |
'track_mode',
|
617 |
'track_codeposition',
|
618 |
'track_api_endpoint',
|
619 |
'track_js_endpoint',
|
620 |
-
|
621 |
foreach ( $global_settings_always_show as $key ) {
|
622 |
-
$rows[] =
|
623 |
'name' => ucfirst( str_replace( '_', ' ', $key ) ),
|
624 |
'value' => $this->settings->get_global_option( $key ),
|
625 |
'comment' => '',
|
626 |
-
|
627 |
}
|
628 |
|
629 |
// otherwise show only few customised settings
|
@@ -635,132 +669,130 @@ class SystemReport {
|
|
635 |
$val = implode( ', ', $val );
|
636 |
}
|
637 |
|
638 |
-
$rows[] =
|
639 |
'name' => ucfirst( str_replace( '_', ' ', $key ) ),
|
640 |
'value' => $val,
|
641 |
'comment' => '',
|
642 |
-
|
643 |
}
|
644 |
}
|
645 |
|
646 |
-
$rows[] =
|
647 |
'section' => 'Logs',
|
648 |
-
|
649 |
|
650 |
$error_log_entries = $this->logger->get_last_logged_entries();
|
651 |
-
|
652 |
-
if ( ! empty( $error_log_entries ) ) {
|
653 |
|
|
|
654 |
foreach ( $error_log_entries as $error ) {
|
655 |
-
if (!empty($install_time)
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
// the first sync might right after the installation
|
663 |
continue;
|
664 |
}
|
665 |
|
666 |
// we only consider plugin_updates as errors only if there are still outstanding updates
|
667 |
-
$is_plugin_update_error = !empty($error['name']) && $error['name']
|
668 |
-
|
669 |
|
670 |
-
$skip_plugin_update = !empty($error['name']) && $error['name']
|
671 |
-
|
672 |
|
673 |
-
if (empty($error['comment']) && $error['comment']
|
674 |
$error['comment'] = '';
|
675 |
}
|
676 |
|
677 |
-
$error['value']
|
678 |
-
$error['is_warning'] = !empty($error['name']) && stripos($error['name'], 'archiv') !== false && $error['name']
|
679 |
-
$error['is_error']
|
680 |
-
if ($is_plugin_update_error) {
|
681 |
$error['comment'] = 'Please reach out to us and include the copied system report (see https://matomo.org/faq/wordpress/how-do-i-troubleshoot-a-failed-database-upgrade-in-matomo-for-wordpress/ for more info)<br><br>You can also retry the update manually by clicking in the top on the "Troubleshooting" tab and then clicking on the "Run updater" button.' . $error['comment'];
|
682 |
-
} elseif ($skip_plugin_update) {
|
683 |
$error['comment'] = 'As there are no outstanding plugin updates it looks like this log can be ignored.<br><br>' . $error['comment'];
|
684 |
}
|
685 |
-
$error['comment'] = matomo_anonymize_value($error['comment']);
|
686 |
-
$rows[]
|
687 |
}
|
688 |
|
689 |
foreach ( $error_log_entries as $error ) {
|
690 |
-
if ($suports_async
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
'
|
696 |
-
'
|
697 |
-
'
|
698 |
-
|
699 |
-
);
|
700 |
}
|
701 |
}
|
702 |
} else {
|
703 |
-
$rows[] =
|
704 |
-
'name' => __('None', 'matomo'),
|
705 |
'value' => '',
|
706 |
'comment' => '',
|
707 |
-
|
708 |
}
|
709 |
|
710 |
-
|
711 |
-
if ( ! \WpMatomo::is_safe_mode() ) {
|
712 |
Bootstrap::do_bootstrap();
|
713 |
$trackfailures = [];
|
714 |
try {
|
715 |
$tracking_failures = new Failures();
|
716 |
-
$trackfailures
|
717 |
-
|
|
|
718 |
// ignored in case not set up yet etc.
|
719 |
}
|
720 |
-
if (!empty($trackfailures)) {
|
721 |
-
$rows[] =
|
722 |
'section' => 'Tracking failures',
|
723 |
-
|
724 |
-
foreach ($trackfailures as $failure) {
|
725 |
-
$comment = sprintf(
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
'
|
731 |
-
'value' => '',
|
732 |
-
'comment' => $comment,
|
733 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
734 |
}
|
735 |
-
|
736 |
}
|
737 |
-
|
738 |
}
|
739 |
|
740 |
-
|
741 |
return $rows;
|
742 |
}
|
743 |
|
744 |
-
private function had_visits_in_last_days($
|
745 |
-
{
|
746 |
global $wpdb;
|
747 |
|
748 |
-
if (
|
749 |
return null;
|
750 |
}
|
751 |
|
752 |
-
$days_in_seconds = $
|
753 |
|
754 |
-
$prefix_table = $this->
|
755 |
|
756 |
$suppress_errors = $wpdb->suppress_errors;
|
757 |
$wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
|
758 |
|
759 |
try {
|
760 |
$time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
|
761 |
-
$sql
|
762 |
-
$row
|
763 |
-
} catch (
|
764 |
$row = null;
|
765 |
}
|
766 |
|
@@ -769,8 +801,8 @@ class SystemReport {
|
|
769 |
// 0 === had no visit
|
770 |
// 1 === had visit
|
771 |
// null === sum error... eg table was not correctly installed
|
772 |
-
if ($row
|
773 |
-
$row = !empty($row);
|
774 |
}
|
775 |
|
776 |
return $row;
|
@@ -781,7 +813,7 @@ class SystemReport {
|
|
781 |
return esc_html__( 'Unknown', 'matomo' );
|
782 |
}
|
783 |
|
784 |
-
$date = gmdate( 'Y-m-d H:i:s', (int)$time );
|
785 |
|
786 |
if ( $in_blog_timezone ) {
|
787 |
$date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
|
@@ -789,7 +821,7 @@ class SystemReport {
|
|
789 |
|
790 |
if ( $print_diff && class_exists( '\Piwik\Metrics\Formatter' ) ) {
|
791 |
$formatter = new \Piwik\Metrics\Formatter();
|
792 |
-
$date
|
793 |
}
|
794 |
|
795 |
return $date;
|
@@ -818,13 +850,13 @@ class SystemReport {
|
|
818 |
}
|
819 |
}
|
820 |
|
821 |
-
$rows[] =
|
822 |
'name' => $result->getLabel(),
|
823 |
'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
|
824 |
'comment' => $comment,
|
825 |
'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
|
826 |
'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
|
827 |
-
|
828 |
}
|
829 |
|
830 |
return $rows;
|
@@ -842,245 +874,272 @@ class SystemReport {
|
|
842 |
$is_network_enabled = $settings->is_network_enabled();
|
843 |
}
|
844 |
|
845 |
-
$rows =
|
846 |
-
$rows[] =
|
847 |
'name' => 'Home URL',
|
848 |
'value' => home_url(),
|
849 |
-
|
850 |
-
$rows[] =
|
851 |
'name' => 'Site URL',
|
852 |
'value' => site_url(),
|
853 |
-
|
854 |
-
$rows[] =
|
855 |
'name' => 'WordPress Version',
|
856 |
'value' => get_bloginfo( 'version' ),
|
857 |
-
|
858 |
-
$rows[] =
|
859 |
'name' => 'Number of blogs',
|
860 |
'value' => $num_blogs,
|
861 |
-
|
862 |
-
$rows[] =
|
863 |
'name' => 'Multisite Enabled',
|
864 |
'value' => $is_multi_site,
|
865 |
-
|
866 |
-
$rows[] =
|
867 |
'name' => 'Network Enabled',
|
868 |
'value' => $is_network_enabled,
|
869 |
-
|
870 |
-
$consts =
|
871 |
-
|
872 |
-
|
873 |
-
|
874 |
-
'
|
875 |
-
'
|
876 |
-
|
877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
878 |
'name' => $const,
|
879 |
-
'value' => defined( $const ) ? constant( $const) : '-',
|
880 |
-
|
881 |
}
|
882 |
|
883 |
-
$rows[] =
|
884 |
'name' => 'Permalink Structure',
|
885 |
'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
|
886 |
-
|
887 |
|
888 |
-
$rows[] =
|
889 |
'name' => 'Possibly uses symlink',
|
890 |
'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
|
891 |
-
|
892 |
|
893 |
$upload_dir = wp_upload_dir();
|
894 |
-
$rows[]
|
895 |
'name' => 'Upload base url',
|
896 |
'value' => $upload_dir['baseurl'],
|
897 |
-
|
898 |
|
899 |
-
$rows[] =
|
900 |
'name' => 'Upload base dir',
|
901 |
'value' => $upload_dir['basedir'],
|
902 |
-
|
903 |
|
904 |
-
$rows[] =
|
905 |
'name' => 'Upload url',
|
906 |
'value' => $upload_dir['url'],
|
907 |
-
|
908 |
|
909 |
-
foreach (['upload_path', 'upload_url_path'] as $option_read) {
|
910 |
-
$rows[] =
|
911 |
'name' => 'Custom ' . $option_read,
|
912 |
'value' => get_option( $option_read ),
|
913 |
-
|
914 |
}
|
915 |
|
916 |
-
if (is_plugin_active('wp-piwik/wp-piwik.php')) {
|
917 |
-
$rows[] =
|
918 |
-
'name'
|
919 |
-
'value'
|
920 |
'is_warning' => true,
|
921 |
-
'comment'
|
922 |
-
|
923 |
|
924 |
-
$mode = get_option
|
925 |
-
if (function_exists('get_site_option') && is_plugin_active_for_network
|
926 |
-
$mode = get_site_option
|
927 |
}
|
928 |
-
if (!empty($mode)) {
|
929 |
-
$rows[] =
|
930 |
-
'name'
|
931 |
-
'value'
|
932 |
-
'is_warning' =>
|
933 |
-
'comment'
|
934 |
-
|
935 |
}
|
936 |
}
|
937 |
|
938 |
$compatible_content_dir = matomo_has_compatible_content_dir();
|
939 |
-
if ($compatible_content_dir
|
940 |
-
$rows[] =
|
941 |
'name' => 'Compatible content directory',
|
942 |
'value' => true,
|
943 |
-
|
944 |
} else {
|
945 |
-
$rows[] =
|
946 |
-
'name'
|
947 |
-
'value'
|
948 |
'is_warning' => true,
|
949 |
-
'comment'
|
950 |
-
|
951 |
}
|
952 |
|
953 |
return $rows;
|
954 |
}
|
955 |
|
956 |
private function get_server_info() {
|
957 |
-
$rows =
|
958 |
|
959 |
if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
960 |
-
$rows[] =
|
961 |
'name' => 'Server Info',
|
962 |
-
|
963 |
-
|
|
|
964 |
}
|
965 |
if ( PHP_OS ) {
|
966 |
-
$rows[] =
|
967 |
'name' => 'PHP OS',
|
968 |
'value' => PHP_OS,
|
969 |
-
|
970 |
}
|
971 |
-
$rows[] =
|
972 |
'name' => 'PHP Version',
|
973 |
'value' => phpversion(),
|
974 |
-
|
975 |
-
$rows[] =
|
976 |
'name' => 'PHP SAPI',
|
977 |
'value' => php_sapi_name(),
|
978 |
-
|
979 |
-
if (defined('PHP_BINARY') && PHP_BINARY) {
|
980 |
-
$rows[] =
|
981 |
'name' => 'PHP Binary Name',
|
982 |
-
'value' => @basename(PHP_BINARY),
|
983 |
-
|
984 |
}
|
985 |
// we report error reporting before matomo bootstraped and after to see if Matomo changed it successfully etc
|
986 |
-
$rows[] =
|
987 |
'name' => 'PHP Error Reporting',
|
988 |
-
|
989 |
-
|
990 |
-
|
|
|
991 |
Bootstrap::do_bootstrap();
|
992 |
-
$
|
993 |
-
$binary
|
994 |
-
if (!empty($binary)) {
|
995 |
-
$binary = basename($binary);
|
996 |
-
$rows[] =
|
997 |
'name' => 'PHP Found Binary',
|
998 |
'value' => $binary,
|
999 |
-
|
1000 |
}
|
1001 |
}
|
1002 |
-
$rows[] =
|
1003 |
'name' => 'Timezone',
|
1004 |
'value' => date_default_timezone_get(),
|
1005 |
-
|
1006 |
-
if (function_exists('wp_timezone_string')) {
|
1007 |
-
$rows[] =
|
1008 |
'name' => 'WP timezone',
|
1009 |
'value' => wp_timezone_string(),
|
1010 |
-
|
1011 |
}
|
1012 |
-
$rows[] =
|
1013 |
'name' => 'Locale',
|
1014 |
'value' => get_locale(),
|
1015 |
-
|
1016 |
-
if (function_exists('get_user_locale')) {
|
1017 |
-
$rows[] =
|
1018 |
'name' => 'User Locale',
|
1019 |
'value' => get_user_locale(),
|
1020 |
-
|
1021 |
}
|
1022 |
|
1023 |
-
$rows[] =
|
1024 |
'name' => 'Memory Limit',
|
1025 |
'value' => @ini_get( 'memory_limit' ),
|
1026 |
'comment' => 'At least 128MB recommended. Depending on your traffic 256MB or more may be needed.',
|
1027 |
-
|
1028 |
|
1029 |
-
$rows[] =
|
1030 |
'name' => 'WP Memory Limit',
|
1031 |
'value' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '',
|
1032 |
'comment' => '',
|
1033 |
-
|
1034 |
|
1035 |
-
$rows[] =
|
1036 |
'name' => 'WP Max Memory Limit',
|
1037 |
'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
|
1038 |
'comment' => '',
|
1039 |
-
|
1040 |
-
|
1041 |
-
if (function_exists('timezone_version_get')) {
|
1042 |
-
$rows[] =
|
1043 |
'name' => 'Timezone version',
|
1044 |
'value' => timezone_version_get(),
|
1045 |
-
|
1046 |
}
|
1047 |
-
|
1048 |
-
$rows[] =
|
1049 |
'name' => 'Time',
|
1050 |
'value' => time(),
|
1051 |
-
|
1052 |
|
1053 |
-
$rows[] =
|
1054 |
'name' => 'Max Execution Time',
|
1055 |
'value' => ini_get( 'max_execution_time' ),
|
1056 |
-
|
1057 |
-
$rows[] =
|
1058 |
'name' => 'Max Post Size',
|
1059 |
'value' => ini_get( 'post_max_size' ),
|
1060 |
-
|
1061 |
-
$rows[] =
|
1062 |
'name' => 'Max Upload Size',
|
1063 |
'value' => wp_max_upload_size(),
|
1064 |
-
|
1065 |
-
$rows[] =
|
1066 |
'name' => 'Max Input Vars',
|
1067 |
'value' => ini_get( 'max_input_vars' ),
|
1068 |
-
|
1069 |
|
1070 |
-
$disabled_functions = ini_get('disable_functions');
|
1071 |
-
$rows[]
|
1072 |
-
'name'
|
1073 |
-
'value'
|
1074 |
-
'comment' => !empty($disabled_functions) ? $disabled_functions : ''
|
1075 |
-
|
1076 |
|
1077 |
$zlib_compression = ini_get( 'zlib.output_compression' );
|
1078 |
-
$row =
|
1079 |
'name' => 'zlib.output_compression is off',
|
1080 |
-
'value' =>
|
1081 |
-
|
1082 |
|
1083 |
-
if (
|
1084 |
$row['is_error'] = true;
|
1085 |
$row['comment'] = 'You need to set "zlib.output_compression" in your php.ini to "Off".';
|
1086 |
}
|
@@ -1089,108 +1148,108 @@ class SystemReport {
|
|
1089 |
if ( function_exists( 'curl_version' ) ) {
|
1090 |
$curl_version = curl_version();
|
1091 |
$curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
|
1092 |
-
$rows[] =
|
1093 |
'name' => 'Curl Version',
|
1094 |
'value' => $curl_version,
|
1095 |
-
|
1096 |
}
|
1097 |
|
1098 |
$suhosin_installed = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
|
1099 |
-
$rows[]
|
1100 |
-
'name'
|
1101 |
-
'value'
|
1102 |
-
'comment' => ''
|
1103 |
-
|
1104 |
|
1105 |
return $rows;
|
1106 |
}
|
1107 |
|
1108 |
private function get_browser_info() {
|
1109 |
-
$rows =
|
1110 |
|
1111 |
-
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
|
1112 |
-
$rows[] =
|
1113 |
'name' => 'Browser',
|
1114 |
'value' => '',
|
1115 |
-
|
1116 |
-
|
|
|
1117 |
}
|
1118 |
-
if (
|
1119 |
Bootstrap::do_bootstrap();
|
1120 |
try {
|
1121 |
-
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
|
1122 |
-
|
1123 |
-
$
|
1124 |
-
|
1125 |
-
|
1126 |
-
|
|
|
1127 |
'is_warning' => true,
|
1128 |
-
'value'
|
1129 |
-
'comment'
|
1130 |
-
|
1131 |
}
|
1132 |
}
|
1133 |
-
|
1134 |
-
|
1135 |
-
|
1136 |
}
|
1137 |
|
1138 |
-
$rows[] =
|
1139 |
'name' => 'Language',
|
1140 |
'value' => Common::getBrowserLanguage(),
|
1141 |
-
'comment' => ''
|
1142 |
-
|
1143 |
}
|
1144 |
|
1145 |
-
|
1146 |
return $rows;
|
1147 |
}
|
1148 |
|
1149 |
private function get_db_info() {
|
1150 |
global $wpdb;
|
1151 |
-
$rows =
|
1152 |
|
1153 |
-
$rows[] =
|
1154 |
'name' => 'MySQL Version',
|
1155 |
'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
|
1156 |
'comment' => '',
|
1157 |
-
|
1158 |
|
1159 |
-
$rows[] =
|
1160 |
'name' => 'Mysqli Connect',
|
1161 |
'value' => function_exists( 'mysqli_connect' ),
|
1162 |
'comment' => '',
|
1163 |
-
|
1164 |
-
$rows[] =
|
1165 |
'name' => 'Force MySQL over Mysqli',
|
1166 |
'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
|
1167 |
'comment' => '',
|
1168 |
-
|
1169 |
|
1170 |
-
$rows[] =
|
1171 |
'name' => 'DB Prefix',
|
1172 |
'value' => $wpdb->prefix,
|
1173 |
-
|
1174 |
|
1175 |
-
$rows[] =
|
1176 |
'name' => 'DB CHARSET',
|
1177 |
-
'value' => defined('DB_CHARSET') ? DB_CHARSET : '',
|
1178 |
-
|
1179 |
|
1180 |
-
$rows[] =
|
1181 |
'name' => 'DB COLLATE',
|
1182 |
-
'value' => defined('DB_COLLATE') ? DB_COLLATE : '',
|
1183 |
-
|
1184 |
|
1185 |
-
$rows[] =
|
1186 |
'name' => 'SHOW ERRORS',
|
1187 |
-
'value' => !empty($wpdb->show_errors),
|
1188 |
-
|
1189 |
|
1190 |
-
$rows[] =
|
1191 |
'name' => 'SUPPRESS ERRORS',
|
1192 |
-
'value' => !empty($wpdb->suppress_errors),
|
1193 |
-
|
1194 |
|
1195 |
if ( method_exists( $wpdb, 'parse_db_host' ) ) {
|
1196 |
$host_data = $wpdb->parse_db_host( DB_HOST );
|
@@ -1198,41 +1257,52 @@ class SystemReport {
|
|
1198 |
list( $host, $port, $socket, $is_ipv6 ) = $host_data;
|
1199 |
}
|
1200 |
|
1201 |
-
$rows[] =
|
1202 |
'name' => 'Uses Socket',
|
1203 |
'value' => ! empty( $socket ),
|
1204 |
-
|
1205 |
-
$rows[] =
|
1206 |
'name' => 'Uses IPv6',
|
1207 |
'value' => ! empty( $is_ipv6 ),
|
1208 |
-
|
1209 |
}
|
1210 |
|
1211 |
-
$rows[] =
|
1212 |
'name' => 'Matomo tables found',
|
1213 |
'value' => $this->get_num_matomo_tables(),
|
1214 |
-
|
1215 |
-
|
1216 |
-
$missing_tables
|
1217 |
-
$has_missing_tables = ( count($missing_tables) > 0 );
|
1218 |
-
$rows[]
|
1219 |
-
'name'
|
1220 |
-
'value'
|
1221 |
-
'comment'
|
1222 |
-
'is_error'
|
1223 |
-
|
1224 |
-
|
1225 |
-
foreach (['user', 'site'] as $table) {
|
1226 |
-
$rows[] =
|
1227 |
-
'name' => 'Matomo '
|
1228 |
-
'value' => $this->get_num_entries_in_table($table),
|
1229 |
-
|
1230 |
}
|
1231 |
|
1232 |
$grants = $this->get_db_grants();
|
1233 |
|
1234 |
// we only show these grants for security reasons as only they are needed and we don't need to know any other ones
|
1235 |
-
$needed_grants =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1236 |
if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
|
1237 |
// ALL PRIVILEGES may be used pre MySQL 8.0
|
1238 |
$grants = $needed_grants;
|
@@ -1243,27 +1313,27 @@ class SystemReport {
|
|
1243 |
if ( empty( $grants )
|
1244 |
|| ! is_array( $grants )
|
1245 |
|| count( $grants_missing ) === count( $needed_grants ) ) {
|
1246 |
-
$rows[] =
|
1247 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1248 |
'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
|
1249 |
'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
|
1250 |
'is_warning' => false,
|
1251 |
-
|
1252 |
} else {
|
1253 |
if ( ! empty( $grants_missing ) ) {
|
1254 |
-
$rows[] =
|
1255 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1256 |
'value' => esc_html__( 'Error', 'matomo' ),
|
1257 |
'comment' => esc_html__( 'Missing permissions', 'matomo' ) . ': ' . implode( ', ', $grants_missing ) . '. ' . __( 'Please check if any of these MySQL permission (grants) are missing and add them if needed.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/troubleshooting/how-do-i-check-if-my-mysql-user-has-all-required-grants/',
|
1258 |
'is_warning' => true,
|
1259 |
-
|
1260 |
} else {
|
1261 |
-
$rows[] =
|
1262 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1263 |
'value' => esc_html__( 'OK', 'matomo' ),
|
1264 |
'comment' => '',
|
1265 |
'is_warning' => false,
|
1266 |
-
|
1267 |
}
|
1268 |
}
|
1269 |
|
@@ -1276,31 +1346,33 @@ class SystemReport {
|
|
1276 |
public function get_missing_tables() {
|
1277 |
global $wpdb;
|
1278 |
|
1279 |
-
$required_matomo_tables = $this->
|
1280 |
-
$required_matomo_tables = array_map(
|
1281 |
|
1282 |
-
$existing_tables =
|
1283 |
try {
|
1284 |
-
$prefix
|
1285 |
$existing_tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $prefix . '%"' );
|
1286 |
-
} catch (
|
1287 |
$this->logger->log( 'no show tables: ' . $e->getMessage() );
|
1288 |
}
|
|
|
1289 |
return array_diff( $required_matomo_tables, $existing_tables );
|
1290 |
}
|
1291 |
|
1292 |
-
private function get_num_entries_in_table($table) {
|
1293 |
global $wpdb;
|
1294 |
|
1295 |
-
$prefix = $this->
|
1296 |
|
1297 |
$results = null;
|
1298 |
try {
|
1299 |
-
$results = $wpdb->get_var('select count(*) from '
|
1300 |
-
} catch (
|
|
|
1301 |
}
|
1302 |
|
1303 |
-
if (isset($results) && is_numeric($results)) {
|
1304 |
return $results;
|
1305 |
}
|
1306 |
|
@@ -1310,17 +1382,17 @@ class SystemReport {
|
|
1310 |
private function get_num_matomo_tables() {
|
1311 |
global $wpdb;
|
1312 |
|
1313 |
-
$prefix = $this->
|
1314 |
|
1315 |
$results = null;
|
1316 |
try {
|
1317 |
-
$results = $wpdb->get_results('show tables like "'
|
1318 |
-
} catch (
|
1319 |
-
$this->logger->log('no show tables: ' . $e->getMessage());
|
1320 |
}
|
1321 |
|
1322 |
-
if (is_array($results)) {
|
1323 |
-
return count($results);
|
1324 |
}
|
1325 |
|
1326 |
return 'show tables not working';
|
@@ -1334,24 +1406,24 @@ class SystemReport {
|
|
1334 |
|
1335 |
try {
|
1336 |
$values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
|
1337 |
-
} catch (
|
1338 |
// We ignore any possible error in case of permission or not supported etc.
|
1339 |
-
$values =
|
1340 |
}
|
1341 |
|
1342 |
$wpdb->suppress_errors( $suppress_errors );
|
1343 |
|
1344 |
-
$grants =
|
1345 |
foreach ( $values as $index => $value ) {
|
1346 |
if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
|
1347 |
continue;
|
1348 |
}
|
1349 |
|
1350 |
if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
|
1351 |
-
return
|
1352 |
}
|
1353 |
|
1354 |
-
foreach (
|
1355 |
if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
|
1356 |
// make sure to never show by any accident a db user or password by cutting anything after on/to
|
1357 |
$values[ $index ][0] = substr( $value[0], 0, stripos( $value[0], $keyword ) );
|
@@ -1362,40 +1434,48 @@ class SystemReport {
|
|
1362 |
}
|
1363 |
}
|
1364 |
// make sure to never show by any accident a db user or password
|
1365 |
-
$values[ $index ][0] = str_replace(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1366 |
|
1367 |
$grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
|
1368 |
}
|
1369 |
$grants = array_map( 'trim', $grants );
|
1370 |
$grants = array_map( 'strtoupper', $grants );
|
1371 |
$grants = array_unique( $grants );
|
|
|
1372 |
return $grants;
|
1373 |
}
|
1374 |
|
1375 |
private function get_plugins_info() {
|
1376 |
-
$rows =
|
1377 |
$mu_plugins = get_mu_plugins();
|
1378 |
|
1379 |
if ( ! empty( $mu_plugins ) ) {
|
1380 |
-
$rows[] =
|
1381 |
'section' => 'MU Plugins',
|
1382 |
-
|
1383 |
|
1384 |
foreach ( $mu_plugins as $mu_pin ) {
|
1385 |
$comment = '';
|
1386 |
if ( ! empty( $plugin['Network'] ) ) {
|
1387 |
$comment = 'Network enabled';
|
1388 |
}
|
1389 |
-
$rows[] =
|
1390 |
'name' => $mu_pin['Name'],
|
1391 |
'value' => $mu_pin['Version'],
|
1392 |
'comment' => $comment,
|
1393 |
-
|
1394 |
}
|
1395 |
|
1396 |
-
$rows[] =
|
1397 |
'section' => 'Plugins',
|
1398 |
-
|
1399 |
}
|
1400 |
|
1401 |
$plugins = get_plugins();
|
@@ -1405,79 +1485,76 @@ class SystemReport {
|
|
1405 |
if ( ! empty( $plugin['Network'] ) ) {
|
1406 |
$comment = 'Network enabled';
|
1407 |
}
|
1408 |
-
$rows[] =
|
1409 |
'name' => $plugin['Name'],
|
1410 |
'value' => $plugin['Version'],
|
1411 |
'comment' => $comment,
|
1412 |
-
|
1413 |
}
|
1414 |
|
1415 |
-
$active_plugins = get_option( 'active_plugins',
|
1416 |
|
1417 |
if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
|
1418 |
$active_plugins = array_map(
|
1419 |
function ( $active_plugin ) {
|
1420 |
$parts = explode( '/', trim( $active_plugin ) );
|
|
|
1421 |
return trim( $parts[0] );
|
1422 |
},
|
1423 |
$active_plugins
|
1424 |
);
|
1425 |
|
1426 |
-
$rows[] =
|
1427 |
'name' => 'Active Plugins',
|
1428 |
'value' => count( $active_plugins ),
|
1429 |
'comment' => implode( ' ', $active_plugins ),
|
1430 |
-
|
1431 |
|
1432 |
$used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
|
1433 |
if ( ! empty( $used_not_compatible ) ) {
|
1434 |
-
|
1435 |
$additional_comment = '';
|
1436 |
-
if (in_array('tweet-old-post-pro', $used_not_compatible)) {
|
1437 |
$additional_comment .= '<br><br>A workaround for Revive Old Posts Pro may be to add the following line to your "wp-config.php". <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
|
1438 |
}
|
1439 |
-
if (in_array('secupress', $used_not_compatible)) {
|
1440 |
$additional_comment .= '<br><br>If reports aren\'t being generated then you may need to disable the feature "Firewall -> Block Bad Request Methods" in SecuPress (if it is enabled) or add the following line to your "wp-config.php": <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
|
1441 |
}
|
1442 |
|
1443 |
$is_warning = true;
|
1444 |
-
$is_error
|
1445 |
-
if (in_array('cookiebot', $used_not_compatible)) {
|
1446 |
$is_warning = false;
|
1447 |
-
$is_error
|
1448 |
}
|
1449 |
|
1450 |
-
$rows[] =
|
1451 |
-
'name'
|
1452 |
-
'value'
|
1453 |
-
'comment'
|
1454 |
'is_warning' => $is_warning,
|
1455 |
-
'is_error'
|
1456 |
-
|
1457 |
}
|
1458 |
}
|
1459 |
|
1460 |
-
$rows[] =
|
1461 |
-
'name'
|
1462 |
-
'value'
|
1463 |
-
'comment' => get_option('stylesheet')
|
1464 |
-
|
1465 |
-
|
1466 |
|
1467 |
-
if ( is_plugin_active('better-wp-security/better-wp-security.php')) {
|
1468 |
-
if (method_exists('\ITSEC_Modules', 'get_setting')
|
1469 |
-
|
1470 |
-
$rows[] =
|
1471 |
'name' => 'iThemes Security Long URLs Enabled',
|
1472 |
'value' => true,
|
1473 |
'comment' => 'Tracking might not work because it looks like you have Long URLs disabled in iThemes Security. To fix this please go to "Security -> Settings -> System Tweaks" and disable the setting "Long URL Strings".',
|
1474 |
'is_error' => true,
|
1475 |
-
|
1476 |
}
|
1477 |
}
|
1478 |
|
1479 |
return $rows;
|
1480 |
}
|
1481 |
-
|
1482 |
-
|
1483 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
+
use Exception;
|
13 |
+
use ITSEC_Modules;
|
14 |
use Piwik\CliMulti;
|
15 |
use Piwik\Common;
|
16 |
use Piwik\Config;
|
25 |
use Piwik\SettingsPiwik;
|
26 |
use Piwik\Tracker\Failures;
|
27 |
use Piwik\Version;
|
28 |
+
use WpMatomo;
|
29 |
use WpMatomo\Bootstrap;
|
30 |
use WpMatomo\Capabilities;
|
31 |
use WpMatomo\Installer;
|
42 |
exit; // if accessed directly
|
43 |
}
|
44 |
|
45 |
+
/**
|
46 |
+
* error_reporting is required for this page
|
47 |
+
* phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
|
48 |
+
*
|
49 |
+
* We want a real data, not something coming from cache
|
50 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
|
51 |
+
*
|
52 |
+
* This is a report error, so silent the possible errors
|
53 |
+
* phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
|
54 |
+
*
|
55 |
+
* We cannot use parameters of statements as this is the table names we build
|
56 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
|
57 |
+
* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
58 |
+
*/
|
59 |
class SystemReport {
|
60 |
const NONCE_NAME = 'matomo_troubleshooting';
|
61 |
const TROUBLESHOOT_SYNC_USERS = 'matomo_troubleshooting_action_site_users';
|
68 |
const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
|
69 |
const TROUBLESHOOT_RUN_UPDATER = 'matomo_troubleshooting_action_run_updater';
|
70 |
|
71 |
+
private $not_compatible_plugins = [
|
72 |
+
'background-manager',
|
73 |
+
// Uses an old version of Twig and plugin is no longer maintained.
|
74 |
+
'all-in-one-event-calendar',
|
75 |
+
// Uses an old version of Twig
|
76 |
+
'data-tables-generator-by-supsystic',
|
77 |
+
// uses an old version of twig causing some styles to go funny in the reporting and admin
|
78 |
+
'tweet-old-post-pro',
|
79 |
+
// uses a newer version of monolog
|
80 |
+
'wp-rss-aggregator',
|
81 |
+
// see https://wordpress.org/support/topic/critical-error-after-upgrade/ conflict re php-di version
|
82 |
+
'wp-defender',
|
83 |
+
// see https://wordpress.org/support/topic/critical-error-after-upgrade/ conflict re php-di version
|
84 |
+
'age-verification-for-woocommerce',
|
85 |
+
// see https://github.com/matomo-org/wp-matomo/issues/428
|
86 |
+
'minify-html-markup',
|
87 |
+
// see https://wordpress.org/support/topic/graphs-are-not-displayed-in-the-visits-overview-widget/#post-14298068
|
88 |
+
'bigbuy-wc-dropshipping-connector',
|
89 |
+
// see https://wordpress.org/support/topic/20-total-errors-during-this-script-execution/
|
90 |
+
'google-listings-and-ads',
|
91 |
+
// see https://wordpress.org/support/topic/20-total-errors-during-this-script-execution/
|
92 |
+
'accelerated-mobile-pages',
|
93 |
+
// see https://wordpress.org/support/topic/receiving-errors-from-my-plesk-server/
|
94 |
+
];
|
95 |
+
|
96 |
+
private $valid_tabs = [ 'troubleshooting' ];
|
97 |
|
98 |
/**
|
99 |
* @var Settings
|
109 |
/**
|
110 |
* @var \WpMatomo\Db\Settings
|
111 |
*/
|
112 |
+
public $db_settings;
|
113 |
|
114 |
public function __construct( Settings $settings ) {
|
115 |
+
$this->settings = $settings;
|
116 |
+
$this->logger = new Logger();
|
117 |
+
$this->db_settings = new \WpMatomo\Db\Settings();
|
118 |
}
|
119 |
|
120 |
public function get_not_compatible_plugins() {
|
130 |
Bootstrap::do_bootstrap();
|
131 |
$scheduled_tasks = new ScheduledTasks( $this->settings );
|
132 |
|
133 |
+
if ( ! defined( 'PIWIK_ARCHIVE_NO_TRUNCATE' ) ) {
|
134 |
+
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
|
135 |
+
define( 'PIWIK_ARCHIVE_NO_TRUNCATE', 1 ); // when triggering it manually, we prefer the full error message
|
136 |
}
|
137 |
|
138 |
try {
|
139 |
// force invalidation of archive to ensure it actually will rearchive the data
|
140 |
+
$site = new Site();
|
141 |
$idsite = $site->get_current_matomo_site_id();
|
142 |
+
if ( $idsite ) {
|
143 |
+
$timezone = \Piwik\Site::getTimezoneFor( $idsite );
|
144 |
+
$now_string = \Piwik\Date::factory( 'now', $timezone )->toString();
|
145 |
+
foreach ( [ 'day' ] as $period ) {
|
146 |
+
API::getInstance()->invalidateArchivedReports( $idsite, $now_string, $period, false, false );
|
147 |
}
|
148 |
}
|
149 |
+
} catch ( Exception $e ) {
|
150 |
+
$this->logger->log_exception( 'archive_invalidate', $e );
|
151 |
}
|
152 |
|
153 |
try {
|
154 |
+
$errors = $scheduled_tasks->archive( true, false );
|
155 |
+
} catch ( Exception $e ) {
|
156 |
+
echo '<div class="error"><p>' . esc_html__( 'Matomo Archive Error', 'matomo' ) . ': ' . esc_html( matomo_anonymize_value( $e->getMessage() . ' =>' . $this->logger->get_readable_trace( $e ) ) ) . '</p></div>';
|
157 |
throw $e;
|
158 |
}
|
159 |
|
160 |
if ( ! empty( $errors ) ) {
|
161 |
echo '<div class="notice notice-warning"><p>Matomo Archive Warnings: ';
|
162 |
+
foreach ( $errors as $error ) {
|
163 |
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
|
164 |
+
echo nl2br( esc_html( matomo_anonymize_value( var_export( $error, 1 ) ) ) );
|
165 |
echo '<br/>';
|
166 |
}
|
167 |
echo '</p></div>';
|
221 |
$settings = $this->settings;
|
222 |
|
223 |
$matomo_active_tab = '';
|
224 |
+
|
225 |
+
if ( isset( $_GET['tab'] ) ) {
|
226 |
+
$tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
|
227 |
+
if ( in_array( $tab, $this->valid_tabs, true ) ) {
|
228 |
+
$matomo_active_tab = $tab;
|
229 |
+
}
|
230 |
}
|
231 |
|
232 |
+
$matomo_tables = [];
|
233 |
if ( empty( $matomo_active_tab ) ) {
|
234 |
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
|
235 |
$this->initial_error_reporting = @error_reporting();
|
236 |
+
$matomo_tables = [
|
237 |
+
[
|
238 |
'title' => 'Matomo',
|
239 |
'rows' => $this->get_matomo_info(),
|
240 |
'has_comments' => true,
|
241 |
+
],
|
242 |
+
[
|
243 |
+
'title' => 'WordPress',
|
244 |
+
'rows' => $this->get_wordpress_info(),
|
245 |
'has_comments' => true,
|
246 |
+
],
|
247 |
+
[
|
248 |
'title' => 'WordPress Plugins',
|
249 |
'rows' => $this->get_plugins_info(),
|
250 |
'has_comments' => true,
|
251 |
+
],
|
252 |
+
[
|
253 |
'title' => 'Server',
|
254 |
'rows' => $this->get_server_info(),
|
255 |
'has_comments' => true,
|
256 |
+
],
|
257 |
+
[
|
258 |
'title' => 'Database',
|
259 |
'rows' => $this->get_db_info(),
|
260 |
'has_comments' => true,
|
261 |
+
],
|
262 |
+
[
|
263 |
'title' => 'Browser',
|
264 |
'rows' => $this->get_browser_info(),
|
265 |
'has_comments' => true,
|
266 |
+
],
|
267 |
+
];
|
268 |
}
|
269 |
+
$matomo_tables = apply_filters( 'matomo_systemreport_tables', $matomo_tables );
|
270 |
$matomo_tables = $this->add_errors_first( $matomo_tables );
|
271 |
$matomo_has_warning_and_no_errors = $this->has_only_warnings_no_error( $matomo_tables );
|
272 |
|
293 |
}
|
294 |
|
295 |
private function add_errors_first( $report_tables ) {
|
296 |
+
$errors = [
|
297 |
'title' => 'Errors',
|
298 |
+
'rows' => [],
|
299 |
'has_comments' => true,
|
300 |
+
];
|
301 |
foreach ( $report_tables as $report_table ) {
|
302 |
foreach ( $report_table['rows'] as $row ) {
|
303 |
if ( ! empty( $row['is_error'] ) ) {
|
328 |
$comment .= sprintf( esc_html__( '%s is not writable. ', 'matomo' ), $title );
|
329 |
}
|
330 |
|
331 |
+
$rows[] = [
|
332 |
'name' => sprintf( esc_html__( '%s exists and is writable.', 'matomo' ), $title ),
|
333 |
'value' => $file_exists && $file_readable && $file_writable ? esc_html__( 'Yes', 'matomo' ) : esc_html__( 'No', 'matomo' ),
|
334 |
'comment' => $comment,
|
335 |
'is_error' => $required && ( ! $file_exists || ! $file_readable ),
|
336 |
'is_warning' => ! $required && ( ! $file_exists || ! $file_readable ),
|
337 |
+
];
|
338 |
|
339 |
return $rows;
|
340 |
}
|
341 |
|
342 |
private function get_matomo_info() {
|
343 |
+
$rows = [];
|
344 |
|
345 |
$plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
|
346 |
+
$install_time = get_option( Installer::OPTION_NAME_INSTALL_DATE );
|
347 |
|
348 |
+
$rows[] = [
|
349 |
'name' => esc_html__( 'Matomo Plugin Version', 'matomo' ),
|
350 |
'value' => $plugin_data['Version'],
|
351 |
'comment' => '',
|
352 |
+
];
|
353 |
|
354 |
$paths = new Paths();
|
355 |
$path_config_file = $paths->get_config_ini_path();
|
358 |
$path_tracker_file = $paths->get_matomo_js_upload_path();
|
359 |
$rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );
|
360 |
|
361 |
+
$rows[] = [
|
362 |
'name' => esc_html__( 'Plugin directories', 'matomo' ),
|
363 |
'value' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? 'Yes' : 'No',
|
364 |
'comment' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? wp_json_encode( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) : '',
|
365 |
+
];
|
366 |
|
367 |
$tmp_dir = $paths->get_tmp_dir();
|
368 |
|
369 |
+
$rows[] = [
|
370 |
'name' => esc_html__( 'Tmp directory writable', 'matomo' ),
|
371 |
'value' => is_writable( $tmp_dir ),
|
372 |
'comment' => $tmp_dir,
|
373 |
+
];
|
374 |
|
375 |
if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] ) ) {
|
376 |
+
// we can have / in this value
|
377 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
378 |
$custom_path = rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php';
|
379 |
$path_exists = file_exists( $custom_path );
|
380 |
$comment = '';
|
382 |
$comment = 'It seems the path does not point to the WP root directory.';
|
383 |
}
|
384 |
|
385 |
+
$rows[] = [
|
386 |
'name' => 'Custom MATOMO_WP_ROOT_PATH',
|
387 |
'value' => $path_exists,
|
388 |
'is_error' => ! $path_exists,
|
389 |
'comment' => $comment,
|
390 |
+
];
|
391 |
}
|
392 |
|
393 |
$report = null;
|
394 |
|
395 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
396 |
try {
|
397 |
Bootstrap::do_bootstrap();
|
398 |
/** @var DiagnosticService $service */
|
399 |
$service = StaticContainer::get( DiagnosticService::class );
|
400 |
$report = $service->runDiagnostics();
|
401 |
|
402 |
+
$rows[] = [
|
403 |
'name' => esc_html__( 'Matomo Version', 'matomo' ),
|
404 |
'value' => \Piwik\Version::VERSION,
|
405 |
'comment' => '',
|
406 |
+
];
|
407 |
+
} catch ( Exception $e ) {
|
408 |
+
$rows[] = [
|
409 |
'name' => esc_html__( 'Matomo System Check', 'matomo' ),
|
410 |
'value' => 'Failed to run Matomo system check.',
|
411 |
'comment' => $e->getMessage(),
|
412 |
+
];
|
413 |
}
|
414 |
}
|
415 |
|
416 |
$site = new Site();
|
417 |
$idsite = $site->get_current_matomo_site_id();
|
418 |
|
419 |
+
$rows[] = [
|
420 |
'name' => esc_html__( 'Matomo Blog idSite', 'matomo' ),
|
421 |
'value' => $idsite,
|
422 |
'comment' => '',
|
423 |
+
];
|
424 |
|
425 |
$install_date = '';
|
426 |
+
if ( ! empty( $install_time ) ) {
|
427 |
+
$install_date = 'Install date: ' . $this->convert_time_to_date( $install_time, true, false );
|
428 |
}
|
429 |
|
430 |
+
$rows[] = [
|
431 |
'name' => esc_html__( 'Matomo Install Version', 'matomo' ),
|
432 |
+
'value' => get_option( Installer::OPTION_NAME_INSTALL_VERSION ),
|
433 |
'comment' => $install_date,
|
434 |
+
];
|
|
|
|
|
|
|
435 |
|
436 |
+
$wpmatomo_updater = new \WpMatomo\Updater( $this->settings );
|
437 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
438 |
$outstanding_updates = $wpmatomo_updater->get_plugins_requiring_update();
|
439 |
$upgrade_in_progress = $wpmatomo_updater->is_upgrade_in_progress();
|
440 |
+
$rows[] = [
|
441 |
+
'name' => 'Upgrades outstanding',
|
442 |
+
'value' => ! empty( $outstanding_updates ),
|
443 |
+
'comment' => ! empty( $outstanding_updates ) ? wp_json_encode( $outstanding_updates ) : '',
|
444 |
+
];
|
445 |
+
$rows[] = [
|
446 |
+
'name' => 'Upgrade in progress',
|
447 |
+
'value' => $upgrade_in_progress,
|
448 |
+
'comment' => '',
|
449 |
+
];
|
450 |
}
|
451 |
|
452 |
+
if ( ! $wpmatomo_updater->load_plugin_functions() ) {
|
453 |
// this should actually never happen...
|
454 |
+
$rows[] = [
|
455 |
+
'name' => 'Matomo Upgrade Plugin Functions',
|
456 |
+
'is_warning' => true,
|
457 |
+
'value' => false,
|
458 |
+
'comment' => 'Function "get_plugin_data" not available. There may be an issue with upgrades not being executed. Please reach out to us.',
|
459 |
+
];
|
460 |
}
|
461 |
|
462 |
+
$rows[] = [
|
463 |
'section' => 'Endpoints',
|
464 |
+
];
|
465 |
|
466 |
+
$rows[] = [
|
467 |
'name' => 'Matomo JavaScript Tracker URL',
|
468 |
'value' => '',
|
469 |
'comment' => $paths->get_js_tracker_url_in_matomo_dir(),
|
470 |
+
];
|
471 |
|
472 |
+
$rows[] = [
|
473 |
'name' => 'Matomo JavaScript Tracker - WP Rest API',
|
474 |
'value' => '',
|
475 |
'comment' => $paths->get_js_tracker_rest_api_endpoint(),
|
476 |
+
];
|
477 |
|
478 |
+
$rows[] = [
|
479 |
'name' => 'Matomo HTTP Tracking API',
|
480 |
'value' => '',
|
481 |
'comment' => $paths->get_tracker_api_url_in_matomo_dir(),
|
482 |
+
];
|
483 |
|
484 |
+
$rows[] = [
|
485 |
'name' => 'Matomo HTTP Tracking API - WP Rest API',
|
486 |
'value' => '',
|
487 |
'comment' => $paths->get_tracker_api_rest_api_endpoint(),
|
488 |
+
];
|
489 |
|
490 |
+
$matomo_plugin_dir_name = basename( dirname( MATOMO_ANALYTICS_FILE ) );
|
491 |
+
if ( 'matomo' !== $matomo_plugin_dir_name ) {
|
492 |
+
$rows[] = [
|
493 |
+
'name' => 'Matomo Plugin Name is correct',
|
494 |
+
'value' => false,
|
495 |
'is_error' => true,
|
496 |
+
'comment' => 'The plugin name should be "matomo" but seems to be "' . $matomo_plugin_dir_name . '". As a result, admin pages and other features might not work. You might need to rename the directory name of this plugin and reactive the plugin.',
|
497 |
+
];
|
498 |
+
} elseif ( ! is_plugin_active( 'matomo/matomo.php' ) ) {
|
499 |
+
$rows[] = [
|
500 |
+
'name' => 'Matomo Plugin not active',
|
501 |
+
'value' => false,
|
502 |
'is_error' => true,
|
503 |
+
'comment' => 'It seems WordPress thinks that `matomo/matomo.php` is not active. As a result Matomo reporting and admin pages may not work. You may be able to fix this by deactivating and activating the Matomo Analytics plugin. One of the reasons this could happen is that you used to have Matomo installed in the wrong folder.',
|
504 |
+
];
|
505 |
}
|
506 |
|
507 |
+
$rows[] = [
|
508 |
'section' => 'Crons',
|
509 |
+
];
|
510 |
|
511 |
$scheduled_tasks = new ScheduledTasks( $this->settings );
|
512 |
$all_events = $scheduled_tasks->get_all_events();
|
513 |
|
514 |
+
$rows[] = [
|
515 |
'name' => esc_html__( 'Server time', 'matomo' ),
|
516 |
'value' => $this->convert_time_to_date( time(), false ),
|
517 |
'comment' => '',
|
518 |
+
];
|
519 |
|
520 |
+
$rows[] = [
|
521 |
'name' => esc_html__( 'Blog time', 'matomo' ),
|
522 |
'value' => $this->convert_time_to_date( time(), true ),
|
523 |
'comment' => esc_html__( 'Below dates are shown in blog timezone', 'matomo' ),
|
524 |
+
];
|
525 |
|
526 |
foreach ( $all_events as $event_name => $event_config ) {
|
527 |
$last_run_before = $scheduled_tasks->get_last_time_before_cron( $event_name );
|
533 |
$comment .= ' Last ended: ' . $this->convert_time_to_date( $last_run_after, true, true ) . '.';
|
534 |
$comment .= ' Interval: ' . $event_config['interval'];
|
535 |
|
536 |
+
$rows[] = [
|
537 |
'name' => $event_config['name'],
|
538 |
'value' => 'Next run: ' . $this->convert_time_to_date( $next_scheduled, true, true ),
|
539 |
'comment' => $comment,
|
540 |
+
];
|
541 |
}
|
542 |
|
543 |
$suports_async = false;
|
544 |
+
if ( ! WpMatomo::is_safe_mode() && $report ) {
|
545 |
+
$rows[] = [
|
546 |
'section' => esc_html__( 'Mandatory checks', 'matomo' ),
|
547 |
+
];
|
548 |
|
549 |
$rows = $this->add_diagnostic_results( $rows, $report->getMandatoryDiagnosticResults() );
|
550 |
|
551 |
+
$rows[] = [
|
552 |
'section' => esc_html__( 'Optional checks', 'matomo' ),
|
553 |
+
];
|
554 |
$rows = $this->add_diagnostic_results( $rows, $report->getOptionalDiagnosticResults() );
|
555 |
|
556 |
+
$cli_multi = new CliMulti();
|
557 |
$suports_async = $cli_multi->supportsAsync();
|
558 |
|
559 |
+
$rows[] = [
|
560 |
'name' => 'Supports Async Archiving',
|
561 |
'value' => $suports_async,
|
562 |
'comment' => '',
|
563 |
+
];
|
564 |
|
565 |
$location_provider = LocationProvider::getCurrentProvider();
|
566 |
+
if ( $location_provider ) {
|
567 |
+
$rows[] = [
|
568 |
'name' => 'Location provider ID',
|
569 |
'value' => $location_provider->getId(),
|
570 |
'comment' => '',
|
571 |
+
];
|
572 |
+
$rows[] = [
|
573 |
'name' => 'Location provider available',
|
574 |
'value' => $location_provider->isAvailable(),
|
575 |
'comment' => '',
|
576 |
+
];
|
577 |
+
$rows[] = [
|
578 |
'name' => 'Location provider working',
|
579 |
'value' => $location_provider->isWorking(),
|
580 |
'comment' => '',
|
581 |
+
];
|
582 |
}
|
583 |
|
584 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
585 |
Bootstrap::do_bootstrap();
|
586 |
$general = Config::getInstance()->General;
|
587 |
+
|
588 |
+
if ( empty( $general['proxy_client_headers'] ) ) {
|
589 |
+
foreach ( AdvancedSettings::$valid_host_headers as $header ) {
|
590 |
+
if ( ! empty( $_SERVER[ $header ] ) ) {
|
591 |
+
$rows[] = [
|
592 |
+
'name' => 'Proxy header',
|
593 |
+
'value' => $header,
|
594 |
'is_warning' => true,
|
595 |
+
'comment' => 'A proxy header is set which means you maybe need to configure a proxy header in the Advanced settings to make location reporting work. If the location in your reports is detected correctly, you can ignore this warning. Learn more: https://matomo.org/faq/wordpress/how-do-i-fix-the-proxy-header-warning-in-the-matomo-for-wordpress-system-report/',
|
596 |
+
];
|
597 |
}
|
598 |
}
|
599 |
}
|
600 |
+
$incompatible_plugins = Plugin\Manager::getInstance()->getIncompatiblePlugins( Version::VERSION );
|
601 |
+
if ( ! empty( $incompatible_plugins ) ) {
|
602 |
+
$rows[] = [
|
603 |
+
'section' => esc_html__( 'Incompatible Matomo plugins', 'matomo' ),
|
604 |
+
];
|
605 |
+
foreach ( $incompatible_plugins as $plugin ) {
|
606 |
+
$rows[] = [
|
607 |
+
'name' => 'Plugin has missing dependencies',
|
608 |
+
'value' => $plugin->getPluginName(),
|
609 |
+
'is_error' => true,
|
610 |
+
'comment' => $plugin->getMissingDependenciesAsString( Version::VERSION ) . ' If the plugin requires a different Matomo version you may need to update it. If you no longer use it consider uninstalling it.',
|
611 |
+
];
|
612 |
+
}
|
613 |
+
}
|
|
|
614 |
}
|
615 |
|
616 |
$num_days_check_visits = 5;
|
617 |
+
$had_visits = $this->had_visits_in_last_days( $num_days_check_visits );
|
618 |
+
if ( false === $had_visits || true === $had_visits ) {
|
619 |
// do not show info if we could not detect it (had_visits === null)
|
620 |
$comment = '';
|
621 |
+
if ( ! $had_visits ) {
|
622 |
$comment = 'It looks like there were no visits in the last ' . $num_days_check_visits . ' days. This may be expected if tracking is disabled, you have not added the tracking code, or your website does not have many visitors in general and you exclude your own visits.';
|
623 |
}
|
624 |
|
625 |
+
$rows[] = [
|
626 |
+
'name' => 'Had visit in last ' . $num_days_check_visits . ' days',
|
627 |
+
'value' => $had_visits,
|
628 |
+
'is_warning' => ! $had_visits && $this->settings->is_tracking_enabled(),
|
629 |
+
'comment' => $comment,
|
630 |
+
];
|
631 |
}
|
632 |
|
633 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
634 |
Bootstrap::do_bootstrap();
|
635 |
$matomo_url = SettingsPiwik::getPiwikUrl();
|
636 |
+
$rows[] = [
|
637 |
'name' => 'Matomo URL',
|
638 |
'comment' => $matomo_url,
|
639 |
'value' => ! empty( $matomo_url ),
|
640 |
+
];
|
641 |
}
|
|
|
642 |
}
|
643 |
|
644 |
+
$rows[] = [
|
645 |
'section' => 'Matomo Settings',
|
646 |
+
];
|
647 |
|
648 |
// always show these settings
|
649 |
+
$global_settings_always_show = [
|
650 |
'track_mode',
|
651 |
'track_codeposition',
|
652 |
'track_api_endpoint',
|
653 |
'track_js_endpoint',
|
654 |
+
];
|
655 |
foreach ( $global_settings_always_show as $key ) {
|
656 |
+
$rows[] = [
|
657 |
'name' => ucfirst( str_replace( '_', ' ', $key ) ),
|
658 |
'value' => $this->settings->get_global_option( $key ),
|
659 |
'comment' => '',
|
660 |
+
];
|
661 |
}
|
662 |
|
663 |
// otherwise show only few customised settings
|
669 |
$val = implode( ', ', $val );
|
670 |
}
|
671 |
|
672 |
+
$rows[] = [
|
673 |
'name' => ucfirst( str_replace( '_', ' ', $key ) ),
|
674 |
'value' => $val,
|
675 |
'comment' => '',
|
676 |
+
];
|
677 |
}
|
678 |
}
|
679 |
|
680 |
+
$rows[] = [
|
681 |
'section' => 'Logs',
|
682 |
+
];
|
683 |
|
684 |
$error_log_entries = $this->logger->get_last_logged_entries();
|
|
|
|
|
685 |
|
686 |
+
if ( ! empty( $error_log_entries ) ) {
|
687 |
foreach ( $error_log_entries as $error ) {
|
688 |
+
if ( ! empty( $install_time )
|
689 |
+
&& is_numeric( $install_time )
|
690 |
+
&& ! empty( $error['name'] )
|
691 |
+
&& ! empty( $error['value'] )
|
692 |
+
&& is_numeric( $error['value'] )
|
693 |
+
&& 'cron_sync' === $error['name']
|
694 |
+
&& $error['value'] < ( $install_time + 300 ) ) {
|
695 |
// the first sync might right after the installation
|
696 |
continue;
|
697 |
}
|
698 |
|
699 |
// we only consider plugin_updates as errors only if there are still outstanding updates
|
700 |
+
$is_plugin_update_error = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
|
701 |
+
&& ! empty( $outstanding_updates );
|
702 |
|
703 |
+
$skip_plugin_update = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
|
704 |
+
&& empty( $outstanding_updates );
|
705 |
|
706 |
+
if ( empty( $error['comment'] ) && '0' !== $error['comment'] ) {
|
707 |
$error['comment'] = '';
|
708 |
}
|
709 |
|
710 |
+
$error['value'] = $this->convert_time_to_date( $error['value'], true, false );
|
711 |
+
$error['is_warning'] = ! empty( $error['name'] ) && stripos( $error['name'], 'archiv' ) !== false && 'archive_boot' !== $error['name'];
|
712 |
+
$error['is_error'] = $is_plugin_update_error;
|
713 |
+
if ( $is_plugin_update_error ) {
|
714 |
$error['comment'] = 'Please reach out to us and include the copied system report (see https://matomo.org/faq/wordpress/how-do-i-troubleshoot-a-failed-database-upgrade-in-matomo-for-wordpress/ for more info)<br><br>You can also retry the update manually by clicking in the top on the "Troubleshooting" tab and then clicking on the "Run updater" button.' . $error['comment'];
|
715 |
+
} elseif ( $skip_plugin_update ) {
|
716 |
$error['comment'] = 'As there are no outstanding plugin updates it looks like this log can be ignored.<br><br>' . $error['comment'];
|
717 |
}
|
718 |
+
$error['comment'] = matomo_anonymize_value( $error['comment'] );
|
719 |
+
$rows[] = $error;
|
720 |
}
|
721 |
|
722 |
foreach ( $error_log_entries as $error ) {
|
723 |
+
if ( $suports_async
|
724 |
+
&& ! empty( $error['value'] ) && is_string( $error['value'] )
|
725 |
+
&& strpos( $error['value'], __( 'Your PHP installation appears to be missing the MySQL extension which is required by WordPress.', 'matomo' ) ) > 0 ) {
|
726 |
+
$rows[] = [
|
727 |
+
'name' => 'Cli has no MySQL',
|
728 |
+
'value' => true,
|
729 |
+
'comment' => 'It looks like MySQL is not available on CLI. Please read our FAQ on how to fix this issue: https://matomo.org/faq/wordpress/how-do-i-fix-the-error-your-php-installation-appears-to-be-missing-the-mysql-extension-which-is-required-by-wordpress-in-matomo-system-report/ ',
|
730 |
+
'is_error' => true,
|
731 |
+
];
|
|
|
732 |
}
|
733 |
}
|
734 |
} else {
|
735 |
+
$rows[] = [
|
736 |
+
'name' => __( 'None', 'matomo' ),
|
737 |
'value' => '',
|
738 |
'comment' => '',
|
739 |
+
];
|
740 |
}
|
741 |
|
742 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
|
|
743 |
Bootstrap::do_bootstrap();
|
744 |
$trackfailures = [];
|
745 |
try {
|
746 |
$tracking_failures = new Failures();
|
747 |
+
$trackfailures = $tracking_failures->getAllFailures();
|
748 |
+
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
749 |
+
} catch ( Exception $e ) {
|
750 |
// ignored in case not set up yet etc.
|
751 |
}
|
752 |
+
if ( ! empty( $trackfailures ) ) {
|
753 |
+
$rows[] = [
|
754 |
'section' => 'Tracking failures',
|
755 |
+
];
|
756 |
+
foreach ( $trackfailures as $failure ) {
|
757 |
+
$comment = sprintf(
|
758 |
+
'Solution: %s<br>More info: %s<br>Date: %s<br>Request URL: %s',
|
759 |
+
$failure['solution'],
|
760 |
+
$failure['solution_url'],
|
761 |
+
$failure['pretty_date_first_occurred'],
|
762 |
+
$failure['request_url']
|
|
|
|
|
763 |
);
|
764 |
+
$rows[] = [
|
765 |
+
'name' => $failure['problem'],
|
766 |
+
'is_warning' => true,
|
767 |
+
'value' => '',
|
768 |
+
'comment' => $comment,
|
769 |
+
];
|
770 |
}
|
|
|
771 |
}
|
|
|
772 |
}
|
773 |
|
|
|
774 |
return $rows;
|
775 |
}
|
776 |
|
777 |
+
private function had_visits_in_last_days( $num_days ) {
|
|
|
778 |
global $wpdb;
|
779 |
|
780 |
+
if ( WpMatomo::is_safe_mode() ) {
|
781 |
return null;
|
782 |
}
|
783 |
|
784 |
+
$days_in_seconds = $num_days * 86400;
|
785 |
|
786 |
+
$prefix_table = $this->db_settings->prefix_table_name( 'log_visit' );
|
787 |
|
788 |
$suppress_errors = $wpdb->suppress_errors;
|
789 |
$wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
|
790 |
|
791 |
try {
|
792 |
$time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
|
793 |
+
$sql = $wpdb->prepare( 'SELECT idsite from ' . $prefix_table . ' where visit_last_action_time > %s LIMIT 1', $time );
|
794 |
+
$row = $wpdb->get_var( $sql );
|
795 |
+
} catch ( Exception $e ) {
|
796 |
$row = null;
|
797 |
}
|
798 |
|
801 |
// 0 === had no visit
|
802 |
// 1 === had visit
|
803 |
// null === sum error... eg table was not correctly installed
|
804 |
+
if ( null !== $row ) {
|
805 |
+
$row = ! empty( $row );
|
806 |
}
|
807 |
|
808 |
return $row;
|
813 |
return esc_html__( 'Unknown', 'matomo' );
|
814 |
}
|
815 |
|
816 |
+
$date = gmdate( 'Y-m-d H:i:s', (int) $time );
|
817 |
|
818 |
if ( $in_blog_timezone ) {
|
819 |
$date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
|
821 |
|
822 |
if ( $print_diff && class_exists( '\Piwik\Metrics\Formatter' ) ) {
|
823 |
$formatter = new \Piwik\Metrics\Formatter();
|
824 |
+
$date .= ' (' . $formatter->getPrettyTimeFromSeconds( $time - time(), true, false ) . ')';
|
825 |
}
|
826 |
|
827 |
return $date;
|
850 |
}
|
851 |
}
|
852 |
|
853 |
+
$rows[] = [
|
854 |
'name' => $result->getLabel(),
|
855 |
'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
|
856 |
'comment' => $comment,
|
857 |
'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
|
858 |
'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
|
859 |
+
];
|
860 |
}
|
861 |
|
862 |
return $rows;
|
874 |
$is_network_enabled = $settings->is_network_enabled();
|
875 |
}
|
876 |
|
877 |
+
$rows = [];
|
878 |
+
$rows[] = [
|
879 |
'name' => 'Home URL',
|
880 |
'value' => home_url(),
|
881 |
+
];
|
882 |
+
$rows[] = [
|
883 |
'name' => 'Site URL',
|
884 |
'value' => site_url(),
|
885 |
+
];
|
886 |
+
$rows[] = [
|
887 |
'name' => 'WordPress Version',
|
888 |
'value' => get_bloginfo( 'version' ),
|
889 |
+
];
|
890 |
+
$rows[] = [
|
891 |
'name' => 'Number of blogs',
|
892 |
'value' => $num_blogs,
|
893 |
+
];
|
894 |
+
$rows[] = [
|
895 |
'name' => 'Multisite Enabled',
|
896 |
'value' => $is_multi_site,
|
897 |
+
];
|
898 |
+
$rows[] = [
|
899 |
'name' => 'Network Enabled',
|
900 |
'value' => $is_network_enabled,
|
901 |
+
];
|
902 |
+
$consts = [
|
903 |
+
'WP_DEBUG',
|
904 |
+
'WP_DEBUG_DISPLAY',
|
905 |
+
'WP_DEBUG_LOG',
|
906 |
+
'DISABLE_WP_CRON',
|
907 |
+
'FORCE_SSL_ADMIN',
|
908 |
+
'WP_CACHE',
|
909 |
+
'CONCATENATE_SCRIPTS',
|
910 |
+
'COMPRESS_SCRIPTS',
|
911 |
+
'COMPRESS_CSS',
|
912 |
+
'ENFORCE_GZIP',
|
913 |
+
'WP_LOCAL_DEV',
|
914 |
+
'WP_CONTENT_URL',
|
915 |
+
'WP_CONTENT_DIR',
|
916 |
+
'UPLOADS',
|
917 |
+
'BLOGUPLOADDIR',
|
918 |
+
'DIEONDBERROR',
|
919 |
+
'WPLANG',
|
920 |
+
'ALTERNATE_WP_CRON',
|
921 |
+
'WP_CRON_LOCK_TIMEOUT',
|
922 |
+
'WP_DISABLE_FATAL_ERROR_HANDLER',
|
923 |
+
'MATOMO_SUPPORT_ASYNC_ARCHIVING',
|
924 |
+
'MATOMO_TRIGGER_BROWSER_ARCHIVING',
|
925 |
+
'MATOMO_ENABLE_TAG_MANAGER',
|
926 |
+
'MATOMO_SUPPRESS_DB_ERRORS',
|
927 |
+
'MATOMO_ENABLE_AUTO_UPGRADE',
|
928 |
+
'MATOMO_DEBUG',
|
929 |
+
'MATOMO_SAFE_MODE',
|
930 |
+
'MATOMO_GLOBAL_UPLOAD_DIR',
|
931 |
+
'MATOMO_LOGIN_REDIRECT',
|
932 |
+
];
|
933 |
+
foreach ( $consts as $const ) {
|
934 |
+
$rows[] = [
|
935 |
'name' => $const,
|
936 |
+
'value' => defined( $const ) ? constant( $const ) : '-',
|
937 |
+
];
|
938 |
}
|
939 |
|
940 |
+
$rows[] = [
|
941 |
'name' => 'Permalink Structure',
|
942 |
'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
|
943 |
+
];
|
944 |
|
945 |
+
$rows[] = [
|
946 |
'name' => 'Possibly uses symlink',
|
947 |
'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
|
948 |
+
];
|
949 |
|
950 |
$upload_dir = wp_upload_dir();
|
951 |
+
$rows[] = [
|
952 |
'name' => 'Upload base url',
|
953 |
'value' => $upload_dir['baseurl'],
|
954 |
+
];
|
955 |
|
956 |
+
$rows[] = [
|
957 |
'name' => 'Upload base dir',
|
958 |
'value' => $upload_dir['basedir'],
|
959 |
+
];
|
960 |
|
961 |
+
$rows[] = [
|
962 |
'name' => 'Upload url',
|
963 |
'value' => $upload_dir['url'],
|
964 |
+
];
|
965 |
|
966 |
+
foreach ( [ 'upload_path', 'upload_url_path' ] as $option_read ) {
|
967 |
+
$rows[] = [
|
968 |
'name' => 'Custom ' . $option_read,
|
969 |
'value' => get_option( $option_read ),
|
970 |
+
];
|
971 |
}
|
972 |
|
973 |
+
if ( is_plugin_active( 'wp-piwik/wp-piwik.php' ) ) {
|
974 |
+
$rows[] = [
|
975 |
+
'name' => 'WP-Matomo (WP-Piwik) activated',
|
976 |
+
'value' => true,
|
977 |
'is_warning' => true,
|
978 |
+
'comment' => 'It is usually not recommended or needed to run Matomo for WordPress and WP-Matomo at the same time. To learn more about the differences between the two plugins view this URL: https://matomo.org/faq/wordpress/why-are-there-two-different-matomo-for-wordpress-plugins-what-is-the-difference-to-wp-matomo-integration-plugin/',
|
979 |
+
];
|
980 |
|
981 |
+
$mode = get_option( 'wp-piwik_global-piwik_mode' );
|
982 |
+
if ( function_exists( 'get_site_option' ) && is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ) ) {
|
983 |
+
$mode = get_site_option( 'wp-piwik_global-piwik_mode' );
|
984 |
}
|
985 |
+
if ( ! empty( $mode ) ) {
|
986 |
+
$rows[] = [
|
987 |
+
'name' => 'WP-Matomo mode',
|
988 |
+
'value' => $mode,
|
989 |
+
'is_warning' => 'php' === $mode || 'PHP' === $mode,
|
990 |
+
'comment' => 'WP-Matomo is configured in "PHP mode". This is known to cause issues with Matomo for WordPress. We recommend you either deactivate WP-Matomo or you go "Settings => WP-Matomo" and change the "Matomo Mode" in the "Connect to Matomo" section to "Self-hosted HTTP API".',
|
991 |
+
];
|
992 |
}
|
993 |
}
|
994 |
|
995 |
$compatible_content_dir = matomo_has_compatible_content_dir();
|
996 |
+
if ( true === $compatible_content_dir ) {
|
997 |
+
$rows[] = [
|
998 |
'name' => 'Compatible content directory',
|
999 |
'value' => true,
|
1000 |
+
];
|
1001 |
} else {
|
1002 |
+
$rows[] = [
|
1003 |
+
'name' => 'Compatible content directory',
|
1004 |
+
'value' => $compatible_content_dir,
|
1005 |
'is_warning' => true,
|
1006 |
+
'comment' => __( 'It looks like you are maybe using a custom WordPress content directory. The Matomo reporting/admin pages might not work. You may be able to workaround this.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/wordpress/how-do-i-make-matomo-for-wordpress-work-when-i-have-a-custom-content-directory/',
|
1007 |
+
];
|
1008 |
}
|
1009 |
|
1010 |
return $rows;
|
1011 |
}
|
1012 |
|
1013 |
private function get_server_info() {
|
1014 |
+
$rows = [];
|
1015 |
|
1016 |
if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
1017 |
+
$rows[] = [
|
1018 |
'name' => 'Server Info',
|
1019 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
1020 |
+
'value' => sanitize_text_field( $_SERVER['SERVER_SOFTWARE'] ),
|
1021 |
+
];
|
1022 |
}
|
1023 |
if ( PHP_OS ) {
|
1024 |
+
$rows[] = [
|
1025 |
'name' => 'PHP OS',
|
1026 |
'value' => PHP_OS,
|
1027 |
+
];
|
1028 |
}
|
1029 |
+
$rows[] = [
|
1030 |
'name' => 'PHP Version',
|
1031 |
'value' => phpversion(),
|
1032 |
+
];
|
1033 |
+
$rows[] = [
|
1034 |
'name' => 'PHP SAPI',
|
1035 |
'value' => php_sapi_name(),
|
1036 |
+
];
|
1037 |
+
if ( defined( 'PHP_BINARY' ) && PHP_BINARY ) {
|
1038 |
+
$rows[] = [
|
1039 |
'name' => 'PHP Binary Name',
|
1040 |
+
'value' => @basename( PHP_BINARY ),
|
1041 |
+
];
|
1042 |
}
|
1043 |
// we report error reporting before matomo bootstraped and after to see if Matomo changed it successfully etc
|
1044 |
+
$rows[] = [
|
1045 |
'name' => 'PHP Error Reporting',
|
1046 |
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
|
1047 |
+
'value' => $this->initial_error_reporting . ' After bootstrap: ' . @error_reporting(),
|
1048 |
+
];
|
1049 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
1050 |
Bootstrap::do_bootstrap();
|
1051 |
+
$cli_php = new CliMulti\CliPhp();
|
1052 |
+
$binary = $cli_php->findPhpBinary();
|
1053 |
+
if ( ! empty( $binary ) ) {
|
1054 |
+
$binary = basename( $binary );
|
1055 |
+
$rows[] = [
|
1056 |
'name' => 'PHP Found Binary',
|
1057 |
'value' => $binary,
|
1058 |
+
];
|
1059 |
}
|
1060 |
}
|
1061 |
+
$rows[] = [
|
1062 |
'name' => 'Timezone',
|
1063 |
'value' => date_default_timezone_get(),
|
1064 |
+
];
|
1065 |
+
if ( function_exists( 'wp_timezone_string' ) ) {
|
1066 |
+
$rows[] = [
|
1067 |
'name' => 'WP timezone',
|
1068 |
'value' => wp_timezone_string(),
|
1069 |
+
];
|
1070 |
}
|
1071 |
+
$rows[] = [
|
1072 |
'name' => 'Locale',
|
1073 |
'value' => get_locale(),
|
1074 |
+
];
|
1075 |
+
if ( function_exists( 'get_user_locale' ) ) {
|
1076 |
+
$rows[] = [
|
1077 |
'name' => 'User Locale',
|
1078 |
'value' => get_user_locale(),
|
1079 |
+
];
|
1080 |
}
|
1081 |
|
1082 |
+
$rows[] = [
|
1083 |
'name' => 'Memory Limit',
|
1084 |
'value' => @ini_get( 'memory_limit' ),
|
1085 |
'comment' => 'At least 128MB recommended. Depending on your traffic 256MB or more may be needed.',
|
1086 |
+
];
|
1087 |
|
1088 |
+
$rows[] = [
|
1089 |
'name' => 'WP Memory Limit',
|
1090 |
'value' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '',
|
1091 |
'comment' => '',
|
1092 |
+
];
|
1093 |
|
1094 |
+
$rows[] = [
|
1095 |
'name' => 'WP Max Memory Limit',
|
1096 |
'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
|
1097 |
'comment' => '',
|
1098 |
+
];
|
1099 |
+
|
1100 |
+
if ( function_exists( 'timezone_version_get' ) ) {
|
1101 |
+
$rows[] = [
|
1102 |
'name' => 'Timezone version',
|
1103 |
'value' => timezone_version_get(),
|
1104 |
+
];
|
1105 |
}
|
1106 |
+
|
1107 |
+
$rows[] = [
|
1108 |
'name' => 'Time',
|
1109 |
'value' => time(),
|
1110 |
+
];
|
1111 |
|
1112 |
+
$rows[] = [
|
1113 |
'name' => 'Max Execution Time',
|
1114 |
'value' => ini_get( 'max_execution_time' ),
|
1115 |
+
];
|
1116 |
+
$rows[] = [
|
1117 |
'name' => 'Max Post Size',
|
1118 |
'value' => ini_get( 'post_max_size' ),
|
1119 |
+
];
|
1120 |
+
$rows[] = [
|
1121 |
'name' => 'Max Upload Size',
|
1122 |
'value' => wp_max_upload_size(),
|
1123 |
+
];
|
1124 |
+
$rows[] = [
|
1125 |
'name' => 'Max Input Vars',
|
1126 |
'value' => ini_get( 'max_input_vars' ),
|
1127 |
+
];
|
1128 |
|
1129 |
+
$disabled_functions = ini_get( 'disable_functions' );
|
1130 |
+
$rows[] = [
|
1131 |
+
'name' => 'Disabled PHP functions',
|
1132 |
+
'value' => ! empty( $disabled_functions ),
|
1133 |
+
'comment' => ! empty( $disabled_functions ) ? $disabled_functions : '',
|
1134 |
+
];
|
1135 |
|
1136 |
$zlib_compression = ini_get( 'zlib.output_compression' );
|
1137 |
+
$row = [
|
1138 |
'name' => 'zlib.output_compression is off',
|
1139 |
+
'value' => '1' !== $zlib_compression,
|
1140 |
+
];
|
1141 |
|
1142 |
+
if ( '1' === $zlib_compression ) {
|
1143 |
$row['is_error'] = true;
|
1144 |
$row['comment'] = 'You need to set "zlib.output_compression" in your php.ini to "Off".';
|
1145 |
}
|
1148 |
if ( function_exists( 'curl_version' ) ) {
|
1149 |
$curl_version = curl_version();
|
1150 |
$curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
|
1151 |
+
$rows[] = [
|
1152 |
'name' => 'Curl Version',
|
1153 |
'value' => $curl_version,
|
1154 |
+
];
|
1155 |
}
|
1156 |
|
1157 |
$suhosin_installed = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
|
1158 |
+
$rows[] = [
|
1159 |
+
'name' => 'Suhosin installed',
|
1160 |
+
'value' => ! empty( $suhosin_installed ),
|
1161 |
+
'comment' => '',
|
1162 |
+
];
|
1163 |
|
1164 |
return $rows;
|
1165 |
}
|
1166 |
|
1167 |
private function get_browser_info() {
|
1168 |
+
$rows = [];
|
1169 |
|
1170 |
+
if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
|
1171 |
+
$rows[] = [
|
1172 |
'name' => 'Browser',
|
1173 |
'value' => '',
|
1174 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
1175 |
+
'comment' => sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ),
|
1176 |
+
];
|
1177 |
}
|
1178 |
+
if ( ! WpMatomo::is_safe_mode() ) {
|
1179 |
Bootstrap::do_bootstrap();
|
1180 |
try {
|
1181 |
+
if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
|
1182 |
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
1183 |
+
$detector = StaticContainer::get( DeviceDetectorFactory::class )->makeInstance( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) );
|
1184 |
+
$client = $detector->getClient();
|
1185 |
+
if ( ! empty( $client['name'] ) && 'Microsoft Edge' === $client['name'] && (int) $client['version'] >= 85 ) {
|
1186 |
+
$rows[] = [
|
1187 |
+
'name' => 'Browser Compatibility',
|
1188 |
'is_warning' => true,
|
1189 |
+
'value' => 'Yes',
|
1190 |
+
'comment' => 'Because you are using MS Edge browser, you may see a warning like "This site has been reported as unsafe" from "Microsoft Defender SmartScreen" when you view the Matomo Reporting, Admin or Tag Manager page. This is a false alert and you can safely ignore this warning by clicking on the icon next to the URL (in the address bar) and choosing either "Report as safe" (preferred) or "Show unsafe content". We are hoping to get this false warning removed in the future.',
|
1191 |
+
];
|
1192 |
}
|
1193 |
}
|
1194 |
+
} catch ( Exception $e ) {
|
1195 |
+
$this->logger->log( $e->getMessage() );
|
|
|
1196 |
}
|
1197 |
|
1198 |
+
$rows[] = [
|
1199 |
'name' => 'Language',
|
1200 |
'value' => Common::getBrowserLanguage(),
|
1201 |
+
'comment' => '',
|
1202 |
+
];
|
1203 |
}
|
1204 |
|
|
|
1205 |
return $rows;
|
1206 |
}
|
1207 |
|
1208 |
private function get_db_info() {
|
1209 |
global $wpdb;
|
1210 |
+
$rows = [];
|
1211 |
|
1212 |
+
$rows[] = [
|
1213 |
'name' => 'MySQL Version',
|
1214 |
'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
|
1215 |
'comment' => '',
|
1216 |
+
];
|
1217 |
|
1218 |
+
$rows[] = [
|
1219 |
'name' => 'Mysqli Connect',
|
1220 |
'value' => function_exists( 'mysqli_connect' ),
|
1221 |
'comment' => '',
|
1222 |
+
];
|
1223 |
+
$rows[] = [
|
1224 |
'name' => 'Force MySQL over Mysqli',
|
1225 |
'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
|
1226 |
'comment' => '',
|
1227 |
+
];
|
1228 |
|
1229 |
+
$rows[] = [
|
1230 |
'name' => 'DB Prefix',
|
1231 |
'value' => $wpdb->prefix,
|
1232 |
+
];
|
1233 |
|
1234 |
+
$rows[] = [
|
1235 |
'name' => 'DB CHARSET',
|
1236 |
+
'value' => defined( 'DB_CHARSET' ) ? DB_CHARSET : '',
|
1237 |
+
];
|
1238 |
|
1239 |
+
$rows[] = [
|
1240 |
'name' => 'DB COLLATE',
|
1241 |
+
'value' => defined( 'DB_COLLATE' ) ? DB_COLLATE : '',
|
1242 |
+
];
|
1243 |
|
1244 |
+
$rows[] = [
|
1245 |
'name' => 'SHOW ERRORS',
|
1246 |
+
'value' => ! empty( $wpdb->show_errors ),
|
1247 |
+
];
|
1248 |
|
1249 |
+
$rows[] = [
|
1250 |
'name' => 'SUPPRESS ERRORS',
|
1251 |
+
'value' => ! empty( $wpdb->suppress_errors ),
|
1252 |
+
];
|
1253 |
|
1254 |
if ( method_exists( $wpdb, 'parse_db_host' ) ) {
|
1255 |
$host_data = $wpdb->parse_db_host( DB_HOST );
|
1257 |
list( $host, $port, $socket, $is_ipv6 ) = $host_data;
|
1258 |
}
|
1259 |
|
1260 |
+
$rows[] = [
|
1261 |
'name' => 'Uses Socket',
|
1262 |
'value' => ! empty( $socket ),
|
1263 |
+
];
|
1264 |
+
$rows[] = [
|
1265 |
'name' => 'Uses IPv6',
|
1266 |
'value' => ! empty( $is_ipv6 ),
|
1267 |
+
];
|
1268 |
}
|
1269 |
|
1270 |
+
$rows[] = [
|
1271 |
'name' => 'Matomo tables found',
|
1272 |
'value' => $this->get_num_matomo_tables(),
|
1273 |
+
];
|
1274 |
+
|
1275 |
+
$missing_tables = $this->get_missing_tables();
|
1276 |
+
$has_missing_tables = ( count( $missing_tables ) > 0 );
|
1277 |
+
$rows[] = [
|
1278 |
+
'name' => 'DB tables exist',
|
1279 |
+
'value' => ( ! $has_missing_tables ),
|
1280 |
+
'comment' => $has_missing_tables ? sprintf( __( 'Some tables may be missing: %s', 'matomo' ), implode( ', ', $missing_tables ) ) : '',
|
1281 |
+
'is_error' => $has_missing_tables,
|
1282 |
+
];
|
1283 |
+
|
1284 |
+
foreach ( [ 'user', 'site' ] as $table ) {
|
1285 |
+
$rows[] = [
|
1286 |
+
'name' => 'Matomo ' . $table . 's found',
|
1287 |
+
'value' => $this->get_num_entries_in_table( $table ),
|
1288 |
+
];
|
1289 |
}
|
1290 |
|
1291 |
$grants = $this->get_db_grants();
|
1292 |
|
1293 |
// we only show these grants for security reasons as only they are needed and we don't need to know any other ones
|
1294 |
+
$needed_grants = [
|
1295 |
+
'SELECT',
|
1296 |
+
'INSERT',
|
1297 |
+
'UPDATE',
|
1298 |
+
'INDEX',
|
1299 |
+
'DELETE',
|
1300 |
+
'CREATE',
|
1301 |
+
'DROP',
|
1302 |
+
'ALTER',
|
1303 |
+
'CREATE TEMPORARY TABLES',
|
1304 |
+
'LOCK TABLES',
|
1305 |
+
];
|
1306 |
if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
|
1307 |
// ALL PRIVILEGES may be used pre MySQL 8.0
|
1308 |
$grants = $needed_grants;
|
1313 |
if ( empty( $grants )
|
1314 |
|| ! is_array( $grants )
|
1315 |
|| count( $grants_missing ) === count( $needed_grants ) ) {
|
1316 |
+
$rows[] = [
|
1317 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1318 |
'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
|
1319 |
'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
|
1320 |
'is_warning' => false,
|
1321 |
+
];
|
1322 |
} else {
|
1323 |
if ( ! empty( $grants_missing ) ) {
|
1324 |
+
$rows[] = [
|
1325 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1326 |
'value' => esc_html__( 'Error', 'matomo' ),
|
1327 |
'comment' => esc_html__( 'Missing permissions', 'matomo' ) . ': ' . implode( ', ', $grants_missing ) . '. ' . __( 'Please check if any of these MySQL permission (grants) are missing and add them if needed.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/troubleshooting/how-do-i-check-if-my-mysql-user-has-all-required-grants/',
|
1328 |
'is_warning' => true,
|
1329 |
+
];
|
1330 |
} else {
|
1331 |
+
$rows[] = [
|
1332 |
'name' => esc_html__( 'Required permissions', 'matomo' ),
|
1333 |
'value' => esc_html__( 'OK', 'matomo' ),
|
1334 |
'comment' => '',
|
1335 |
'is_warning' => false,
|
1336 |
+
];
|
1337 |
}
|
1338 |
}
|
1339 |
|
1346 |
public function get_missing_tables() {
|
1347 |
global $wpdb;
|
1348 |
|
1349 |
+
$required_matomo_tables = $this->db_settings->get_matomo_tables();
|
1350 |
+
$required_matomo_tables = array_map( [ $this->db_settings, 'prefix_table_name' ], $required_matomo_tables );
|
1351 |
|
1352 |
+
$existing_tables = [];
|
1353 |
try {
|
1354 |
+
$prefix = $this->db_settings->prefix_table_name( '' );
|
1355 |
$existing_tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $prefix . '%"' );
|
1356 |
+
} catch ( Exception $e ) {
|
1357 |
$this->logger->log( 'no show tables: ' . $e->getMessage() );
|
1358 |
}
|
1359 |
+
|
1360 |
return array_diff( $required_matomo_tables, $existing_tables );
|
1361 |
}
|
1362 |
|
1363 |
+
private function get_num_entries_in_table( $table ) {
|
1364 |
global $wpdb;
|
1365 |
|
1366 |
+
$prefix = $this->db_settings->prefix_table_name( $table );
|
1367 |
|
1368 |
$results = null;
|
1369 |
try {
|
1370 |
+
$results = $wpdb->get_var( 'select count(*) from ' . $prefix );
|
1371 |
+
} catch ( Exception $e ) {
|
1372 |
+
$this->logger->log( 'no count(*): ' . $e->getMessage() );
|
1373 |
}
|
1374 |
|
1375 |
+
if ( isset( $results ) && is_numeric( $results ) ) {
|
1376 |
return $results;
|
1377 |
}
|
1378 |
|
1382 |
private function get_num_matomo_tables() {
|
1383 |
global $wpdb;
|
1384 |
|
1385 |
+
$prefix = $this->db_settings->prefix_table_name( '' );
|
1386 |
|
1387 |
$results = null;
|
1388 |
try {
|
1389 |
+
$results = $wpdb->get_results( 'show tables like "' . $prefix . '%"' );
|
1390 |
+
} catch ( Exception $e ) {
|
1391 |
+
$this->logger->log( 'no show tables: ' . $e->getMessage() );
|
1392 |
}
|
1393 |
|
1394 |
+
if ( is_array( $results ) ) {
|
1395 |
+
return count( $results );
|
1396 |
}
|
1397 |
|
1398 |
return 'show tables not working';
|
1406 |
|
1407 |
try {
|
1408 |
$values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
|
1409 |
+
} catch ( Exception $e ) {
|
1410 |
// We ignore any possible error in case of permission or not supported etc.
|
1411 |
+
$values = [];
|
1412 |
}
|
1413 |
|
1414 |
$wpdb->suppress_errors( $suppress_errors );
|
1415 |
|
1416 |
+
$grants = [];
|
1417 |
foreach ( $values as $index => $value ) {
|
1418 |
if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
|
1419 |
continue;
|
1420 |
}
|
1421 |
|
1422 |
if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
|
1423 |
+
return [ 'ALL PRIVILEGES' ]; // the split on empty string wouldn't work otherwise
|
1424 |
}
|
1425 |
|
1426 |
+
foreach ( [ ' ON ', ' TO ', ' IDENTIFIED ', ' BY ' ] as $keyword ) {
|
1427 |
if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
|
1428 |
// make sure to never show by any accident a db user or password by cutting anything after on/to
|
1429 |
$values[ $index ][0] = substr( $value[0], 0, stripos( $value[0], $keyword ) );
|
1434 |
}
|
1435 |
}
|
1436 |
// make sure to never show by any accident a db user or password
|
1437 |
+
$values[ $index ][0] = str_replace(
|
1438 |
+
[ DB_USER, DB_PASSWORD ],
|
1439 |
+
[
|
1440 |
+
'DB_USER',
|
1441 |
+
'DB_PASS',
|
1442 |
+
],
|
1443 |
+
$values[ $index ][0]
|
1444 |
+
);
|
1445 |
|
1446 |
$grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
|
1447 |
}
|
1448 |
$grants = array_map( 'trim', $grants );
|
1449 |
$grants = array_map( 'strtoupper', $grants );
|
1450 |
$grants = array_unique( $grants );
|
1451 |
+
|
1452 |
return $grants;
|
1453 |
}
|
1454 |
|
1455 |
private function get_plugins_info() {
|
1456 |
+
$rows = [];
|
1457 |
$mu_plugins = get_mu_plugins();
|
1458 |
|
1459 |
if ( ! empty( $mu_plugins ) ) {
|
1460 |
+
$rows[] = [
|
1461 |
'section' => 'MU Plugins',
|
1462 |
+
];
|
1463 |
|
1464 |
foreach ( $mu_plugins as $mu_pin ) {
|
1465 |
$comment = '';
|
1466 |
if ( ! empty( $plugin['Network'] ) ) {
|
1467 |
$comment = 'Network enabled';
|
1468 |
}
|
1469 |
+
$rows[] = [
|
1470 |
'name' => $mu_pin['Name'],
|
1471 |
'value' => $mu_pin['Version'],
|
1472 |
'comment' => $comment,
|
1473 |
+
];
|
1474 |
}
|
1475 |
|
1476 |
+
$rows[] = [
|
1477 |
'section' => 'Plugins',
|
1478 |
+
];
|
1479 |
}
|
1480 |
|
1481 |
$plugins = get_plugins();
|
1485 |
if ( ! empty( $plugin['Network'] ) ) {
|
1486 |
$comment = 'Network enabled';
|
1487 |
}
|
1488 |
+
$rows[] = [
|
1489 |
'name' => $plugin['Name'],
|
1490 |
'value' => $plugin['Version'],
|
1491 |
'comment' => $comment,
|
1492 |
+
];
|
1493 |
}
|
1494 |
|
1495 |
+
$active_plugins = get_option( 'active_plugins', [] );
|
1496 |
|
1497 |
if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
|
1498 |
$active_plugins = array_map(
|
1499 |
function ( $active_plugin ) {
|
1500 |
$parts = explode( '/', trim( $active_plugin ) );
|
1501 |
+
|
1502 |
return trim( $parts[0] );
|
1503 |
},
|
1504 |
$active_plugins
|
1505 |
);
|
1506 |
|
1507 |
+
$rows[] = [
|
1508 |
'name' => 'Active Plugins',
|
1509 |
'value' => count( $active_plugins ),
|
1510 |
'comment' => implode( ' ', $active_plugins ),
|
1511 |
+
];
|
1512 |
|
1513 |
$used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
|
1514 |
if ( ! empty( $used_not_compatible ) ) {
|
|
|
1515 |
$additional_comment = '';
|
1516 |
+
if ( in_array( 'tweet-old-post-pro', $used_not_compatible, true ) ) {
|
1517 |
$additional_comment .= '<br><br>A workaround for Revive Old Posts Pro may be to add the following line to your "wp-config.php". <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
|
1518 |
}
|
1519 |
+
if ( in_array( 'secupress', $used_not_compatible, true ) ) {
|
1520 |
$additional_comment .= '<br><br>If reports aren\'t being generated then you may need to disable the feature "Firewall -> Block Bad Request Methods" in SecuPress (if it is enabled) or add the following line to your "wp-config.php": <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
|
1521 |
}
|
1522 |
|
1523 |
$is_warning = true;
|
1524 |
+
$is_error = false;
|
1525 |
+
if ( in_array( 'cookiebot', $used_not_compatible, true ) ) {
|
1526 |
$is_warning = false;
|
1527 |
+
$is_error = true;
|
1528 |
}
|
1529 |
|
1530 |
+
$rows[] = [
|
1531 |
+
'name' => __( 'Not compatible plugins', 'matomo' ),
|
1532 |
+
'value' => count( $used_not_compatible ),
|
1533 |
+
'comment' => implode( ', ', $used_not_compatible ) . '<br><br> Matomo may work fine when using these plugins but there may be some issues. For more information see<br>https://matomo.org/faq/wordpress/which-plugins-is-matomo-for-wordpress-known-to-be-not-compatible-with/ ' . $additional_comment,
|
1534 |
'is_warning' => $is_warning,
|
1535 |
+
'is_error' => $is_error,
|
1536 |
+
];
|
1537 |
}
|
1538 |
}
|
1539 |
|
1540 |
+
$rows[] = [
|
1541 |
+
'name' => 'Theme',
|
1542 |
+
'value' => function_exists( 'get_template' ) ? get_template() : '',
|
1543 |
+
'comment' => get_option( 'stylesheet' ),
|
1544 |
+
];
|
|
|
1545 |
|
1546 |
+
if ( is_plugin_active( 'better-wp-security/better-wp-security.php' ) ) {
|
1547 |
+
if ( method_exists( '\ITSEC_Modules', 'get_setting' )
|
1548 |
+
&& ITSEC_Modules::get_setting( 'system-tweaks', 'long_url_strings' ) ) {
|
1549 |
+
$rows[] = [
|
1550 |
'name' => 'iThemes Security Long URLs Enabled',
|
1551 |
'value' => true,
|
1552 |
'comment' => 'Tracking might not work because it looks like you have Long URLs disabled in iThemes Security. To fix this please go to "Security -> Settings -> System Tweaks" and disable the setting "Long URL Strings".',
|
1553 |
'is_error' => true,
|
1554 |
+
];
|
1555 |
}
|
1556 |
}
|
1557 |
|
1558 |
return $rows;
|
1559 |
}
|
|
|
|
|
1560 |
}
|
classes/WpMatomo/Admin/TrackingSettings.php
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
|
|
12 |
use WpMatomo\Capabilities;
|
13 |
use WpMatomo\Settings;
|
14 |
use WpMatomo\Site;
|
@@ -18,7 +19,10 @@ use WpMatomo\TrackingCode\TrackingCodeGenerator;
|
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit; // if accessed directly
|
20 |
}
|
21 |
-
|
|
|
|
|
|
|
22 |
class TrackingSettings implements AdminSettingsInterface {
|
23 |
const FORM_NAME = 'matomo';
|
24 |
const NONCE_NAME = 'matomo_settings';
|
@@ -59,7 +63,7 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
59 |
}
|
60 |
|
61 |
private function apply_settings() {
|
62 |
-
$keys_to_keep =
|
63 |
'track_mode',
|
64 |
'track_across',
|
65 |
'track_across_alias',
|
@@ -96,46 +100,45 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
96 |
'track_js_endpoint',
|
97 |
'track_jserrors',
|
98 |
'track_api_endpoint',
|
99 |
-
Settings::SITE_CURRENCY
|
100 |
-
|
101 |
|
102 |
if ( matomo_has_tag_manager() ) {
|
103 |
$keys_to_keep[] = 'tagmanger_container_ids';
|
104 |
}
|
105 |
|
106 |
-
$values =
|
107 |
|
108 |
// default value in case no role/ post type is selected to make sure we unset it if no role /post type is selected
|
109 |
-
$values['add_post_annotations'] =
|
110 |
-
$values['tagmanger_container_ids'] =
|
111 |
|
112 |
$valid_currencies = $this->get_supported_currencies();
|
113 |
|
114 |
-
if ( !empty( $_POST[ self::FORM_NAME ]['tracker_debug'] ) ) {
|
115 |
$site_config_sync = new SiteConfigSync( $this->settings );
|
116 |
-
switch ($_POST[ self::FORM_NAME ]['tracker_debug']) {
|
117 |
case 'always':
|
118 |
-
$site_config_sync->set_config_value('Tracker', 'debug', 1);
|
119 |
-
$site_config_sync->set_config_value('Tracker', 'debug_on_demand', 0);
|
120 |
break;
|
121 |
case 'on_demand':
|
122 |
-
$site_config_sync->set_config_value('Tracker', 'debug', 0);
|
123 |
-
$site_config_sync->set_config_value('Tracker', 'debug_on_demand', 1);
|
124 |
break;
|
125 |
default:
|
126 |
-
$site_config_sync->set_config_value('Tracker', 'debug', 0);
|
127 |
-
$site_config_sync->set_config_value('Tracker', 'debug_on_demand', 0);
|
128 |
}
|
129 |
}
|
130 |
|
131 |
-
if ( empty( $_POST[ self::FORM_NAME ][Settings::SITE_CURRENCY] )
|
132 |
-
|
133 |
-
$_POST[ self::FORM_NAME ][Settings::SITE_CURRENCY] = 'USD';
|
134 |
}
|
135 |
|
136 |
if ( ! empty( $_POST[ self::FORM_NAME ]['track_mode'] ) ) {
|
137 |
-
$track_mode
|
138 |
-
|
139 |
if ( self::TRACK_MODE_TAGMANAGER === $track_mode ) {
|
140 |
// no noscript mode in this case
|
141 |
$_POST[ self::FORM_NAME ]['track_noscript'] = '';
|
@@ -146,12 +149,18 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
146 |
if ( $this->must_update_tracker() === true ) {
|
147 |
// We want to keep the tracking code when user switches between disabled and manually or disabled to disabled.
|
148 |
if ( ! empty( $_POST[ self::FORM_NAME ]['tracking_code'] ) ) {
|
|
|
|
|
149 |
$_POST[ self::FORM_NAME ]['tracking_code'] = stripslashes( $_POST[ self::FORM_NAME ]['tracking_code'] );
|
|
|
150 |
} else {
|
151 |
$_POST[ self::FORM_NAME ]['tracking_code'] = '';
|
152 |
}
|
153 |
if ( ! empty( $_POST[ self::FORM_NAME ]['noscript_code'] ) ) {
|
|
|
|
|
154 |
$_POST[ self::FORM_NAME ]['noscript_code'] = stripslashes( $_POST[ self::FORM_NAME ]['noscript_code'] );
|
|
|
155 |
} else {
|
156 |
$_POST[ self::FORM_NAME ]['noscript_code'] = '';
|
157 |
}
|
@@ -160,42 +169,50 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
160 |
$_POST[ self::FORM_NAME ]['tracking_code'] = '';
|
161 |
}
|
162 |
}
|
163 |
-
|
164 |
foreach ( $_POST[ self::FORM_NAME ] as $name => $value ) {
|
165 |
if ( in_array( $name, $keys_to_keep, true ) ) {
|
166 |
$values[ $name ] = $value;
|
167 |
}
|
168 |
}
|
169 |
-
|
170 |
$this->settings->apply_tracking_related_changes( $values );
|
171 |
|
172 |
return true;
|
173 |
}
|
174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
/**
|
176 |
* Reauires form to be posted
|
|
|
177 |
* @return bool
|
178 |
*/
|
179 |
-
private function must_update_tracker
|
180 |
-
$track_mode = $
|
181 |
$previus_track_mode = $this->settings->get_global_option( 'track_mode' );
|
182 |
$must_update = false;
|
183 |
if ( self::TRACK_MODE_MANUALLY === $track_mode
|
184 |
-
|
185 |
-
|
186 |
// We want to keep the tracking code when user switches between disabled and manually or disabled to disabled.
|
187 |
$must_update = true;
|
188 |
}
|
|
|
189 |
return $must_update;
|
190 |
}
|
191 |
|
192 |
/**
|
193 |
* @return bool
|
194 |
*/
|
195 |
-
private function form_submitted
|
196 |
-
return isset( $_POST )
|
197 |
-
|
198 |
-
|
199 |
}
|
200 |
|
201 |
/**
|
@@ -203,50 +220,55 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
203 |
*
|
204 |
* @return bool
|
205 |
*/
|
206 |
-
private function has_valid_html_comments
|
207 |
$valid = true;
|
208 |
if ( $this->form_submitted() === true ) {
|
209 |
if ( $this->must_update_tracker() === true ) {
|
210 |
-
if ( ! empty( $_POST[ self::FORM_NAME ][$field] ) ) {
|
211 |
-
|
|
|
|
|
212 |
}
|
213 |
}
|
214 |
}
|
|
|
215 |
return $valid;
|
216 |
}
|
217 |
|
218 |
/**
|
219 |
* @param string $html html content to validate
|
|
|
220 |
* @returns boolean
|
221 |
*/
|
222 |
public function validate_html_comments( $html ) {
|
223 |
-
|
224 |
-
|
225 |
-
|
|
|
226 |
}
|
227 |
|
228 |
public function show_settings() {
|
229 |
-
$was_updated
|
230 |
-
$
|
231 |
if ( $this->has_valid_html_comments( 'tracking_code' ) !== true ) {
|
232 |
-
$
|
233 |
}
|
234 |
if ( $this->has_valid_html_comments( 'noscript_code' ) !== true ) {
|
235 |
-
$
|
236 |
}
|
237 |
-
if ( count($
|
238 |
$was_updated = $this->update_if_submitted();
|
239 |
}
|
240 |
|
241 |
-
$settings
|
242 |
|
243 |
$containers = $this->get_active_containers();
|
244 |
|
245 |
-
$track_modes =
|
246 |
self::TRACK_MODE_DISABLED => esc_html__( 'Disabled', 'matomo' ),
|
247 |
self::TRACK_MODE_DEFAULT => esc_html__( 'Default tracking', 'matomo' ),
|
248 |
self::TRACK_MODE_MANUALLY => esc_html__( 'Enter manually', 'matomo' ),
|
249 |
-
|
250 |
|
251 |
if ( ! empty( $containers ) ) {
|
252 |
$track_modes[ self::TRACK_MODE_TAGMANAGER ] = esc_html__( 'Tag Manager', 'matomo' );
|
@@ -268,46 +290,46 @@ class TrackingSettings implements AdminSettingsInterface {
|
|
268 |
/**
|
269 |
* @return string[]
|
270 |
*/
|
271 |
-
private function get_cookie_consent_modes()
|
272 |
-
{
|
273 |
$modes = [];
|
274 |
-
foreach(CookieConsent::get_available_options() as $option => $description) {
|
275 |
-
$modes[$option] = $description;
|
276 |
}
|
|
|
277 |
return $modes;
|
278 |
}
|
279 |
|
280 |
-
private function get_supported_currencies()
|
281 |
-
|
282 |
-
$
|
283 |
-
$
|
284 |
-
|
285 |
-
$currencies[$key] = $single[0] . ' ' . $single[1];
|
286 |
}
|
|
|
287 |
return $currencies;
|
288 |
}
|
289 |
|
290 |
public function get_active_containers() {
|
291 |
// we don't use Matomo API here to avoid needing to bootstrap Matomo which is slow and could break things
|
292 |
-
$containers =
|
293 |
if ( matomo_has_tag_manager() ) {
|
294 |
global $wpdb;
|
295 |
-
$
|
296 |
-
$container_table = $
|
297 |
try {
|
|
|
298 |
$containers = $wpdb->get_results( sprintf( 'SELECT `idcontainer`, `name` FROM %s where `status` = "active"', $container_table ) );
|
299 |
-
|
|
|
300 |
// table may not exist yet etc
|
301 |
-
$containers =
|
302 |
}
|
303 |
}
|
304 |
-
$by_id =
|
305 |
foreach ( $containers as $container ) {
|
306 |
$by_id[ $container->idcontainer ] = $container->name;
|
307 |
}
|
308 |
|
309 |
return $by_id;
|
310 |
}
|
311 |
-
|
312 |
-
|
313 |
}
|
9 |
|
10 |
namespace WpMatomo\Admin;
|
11 |
|
12 |
+
use Exception;
|
13 |
use WpMatomo\Capabilities;
|
14 |
use WpMatomo\Settings;
|
15 |
use WpMatomo\Site;
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
21 |
}
|
22 |
+
/**
|
23 |
+
* @todo set up the nonce verification
|
24 |
+
* phpcs:disable WordPress.Security.NonceVerification.Missing
|
25 |
+
*/
|
26 |
class TrackingSettings implements AdminSettingsInterface {
|
27 |
const FORM_NAME = 'matomo';
|
28 |
const NONCE_NAME = 'matomo_settings';
|
63 |
}
|
64 |
|
65 |
private function apply_settings() {
|
66 |
+
$keys_to_keep = [
|
67 |
'track_mode',
|
68 |
'track_across',
|
69 |
'track_across_alias',
|
100 |
'track_js_endpoint',
|
101 |
'track_jserrors',
|
102 |
'track_api_endpoint',
|
103 |
+
Settings::SITE_CURRENCY,
|
104 |
+
];
|
105 |
|
106 |
if ( matomo_has_tag_manager() ) {
|
107 |
$keys_to_keep[] = 'tagmanger_container_ids';
|
108 |
}
|
109 |
|
110 |
+
$values = [];
|
111 |
|
112 |
// default value in case no role/ post type is selected to make sure we unset it if no role /post type is selected
|
113 |
+
$values['add_post_annotations'] = [];
|
114 |
+
$values['tagmanger_container_ids'] = [];
|
115 |
|
116 |
$valid_currencies = $this->get_supported_currencies();
|
117 |
|
118 |
+
if ( ! empty( $_POST[ self::FORM_NAME ]['tracker_debug'] ) ) {
|
119 |
$site_config_sync = new SiteConfigSync( $this->settings );
|
120 |
+
switch ( $_POST[ self::FORM_NAME ]['tracker_debug'] ) {
|
121 |
case 'always':
|
122 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug', 1 );
|
123 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug_on_demand', 0 );
|
124 |
break;
|
125 |
case 'on_demand':
|
126 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug', 0 );
|
127 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug_on_demand', 1 );
|
128 |
break;
|
129 |
default:
|
130 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug', 0 );
|
131 |
+
$site_config_sync->set_config_value( 'Tracker', 'debug_on_demand', 0 );
|
132 |
}
|
133 |
}
|
134 |
|
135 |
+
if ( empty( $_POST[ self::FORM_NAME ][ Settings::SITE_CURRENCY ] )
|
136 |
+
|| ! array_key_exists( sanitize_text_field( wp_unslash( $_POST[ self::FORM_NAME ][ Settings::SITE_CURRENCY ] ) ), $valid_currencies ) ) {
|
137 |
+
$_POST[ self::FORM_NAME ][ Settings::SITE_CURRENCY ] = 'USD';
|
138 |
}
|
139 |
|
140 |
if ( ! empty( $_POST[ self::FORM_NAME ]['track_mode'] ) ) {
|
141 |
+
$track_mode = $this->get_track_mode();
|
|
|
142 |
if ( self::TRACK_MODE_TAGMANAGER === $track_mode ) {
|
143 |
// no noscript mode in this case
|
144 |
$_POST[ self::FORM_NAME ]['track_noscript'] = '';
|
149 |
if ( $this->must_update_tracker() === true ) {
|
150 |
// We want to keep the tracking code when user switches between disabled and manually or disabled to disabled.
|
151 |
if ( ! empty( $_POST[ self::FORM_NAME ]['tracking_code'] ) ) {
|
152 |
+
// don't process, this is a script
|
153 |
+
// phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
154 |
$_POST[ self::FORM_NAME ]['tracking_code'] = stripslashes( $_POST[ self::FORM_NAME ]['tracking_code'] );
|
155 |
+
// phpcs:enable WordPress.Security.ValidatedSanitizedInput
|
156 |
} else {
|
157 |
$_POST[ self::FORM_NAME ]['tracking_code'] = '';
|
158 |
}
|
159 |
if ( ! empty( $_POST[ self::FORM_NAME ]['noscript_code'] ) ) {
|
160 |
+
// don't process, this is a script
|
161 |
+
// phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
162 |
$_POST[ self::FORM_NAME ]['noscript_code'] = stripslashes( $_POST[ self::FORM_NAME ]['noscript_code'] );
|
163 |
+
// phpcs:enable WordPress.Security.ValidatedSanitizedInput
|
164 |
} else {
|
165 |
$_POST[ self::FORM_NAME ]['noscript_code'] = '';
|
166 |
}
|
169 |
$_POST[ self::FORM_NAME ]['tracking_code'] = '';
|
170 |
}
|
171 |
}
|
172 |
+
// phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
173 |
foreach ( $_POST[ self::FORM_NAME ] as $name => $value ) {
|
174 |
if ( in_array( $name, $keys_to_keep, true ) ) {
|
175 |
$values[ $name ] = $value;
|
176 |
}
|
177 |
}
|
178 |
+
// phpcs:enable WordPress.Security.ValidatedSanitizedInput
|
179 |
$this->settings->apply_tracking_related_changes( $values );
|
180 |
|
181 |
return true;
|
182 |
}
|
183 |
|
184 |
+
private function get_track_mode() {
|
185 |
+
if ( ! empty( $_POST[ self::FORM_NAME ]['track_mode'] ) ) {
|
186 |
+
return sanitize_text_field( wp_unslash( $_POST[ self::FORM_NAME ]['track_mode'] ) );
|
187 |
+
}
|
188 |
+
return '';
|
189 |
+
}
|
190 |
/**
|
191 |
* Reauires form to be posted
|
192 |
+
*
|
193 |
* @return bool
|
194 |
*/
|
195 |
+
private function must_update_tracker() {
|
196 |
+
$track_mode = $this->get_track_mode();
|
197 |
$previus_track_mode = $this->settings->get_global_option( 'track_mode' );
|
198 |
$must_update = false;
|
199 |
if ( self::TRACK_MODE_MANUALLY === $track_mode
|
200 |
+
|| ( self::TRACK_MODE_DISABLED === $track_mode &&
|
201 |
+
in_array( $previus_track_mode, [ self::TRACK_MODE_DISABLED, self::TRACK_MODE_MANUALLY ], true ) ) ) {
|
202 |
// We want to keep the tracking code when user switches between disabled and manually or disabled to disabled.
|
203 |
$must_update = true;
|
204 |
}
|
205 |
+
|
206 |
return $must_update;
|
207 |
}
|
208 |
|
209 |
/**
|
210 |
* @return bool
|
211 |
*/
|
212 |
+
private function form_submitted() {
|
213 |
+
return isset( $_POST ) && ! empty( $_POST[ self::FORM_NAME ] )
|
214 |
+
&& is_admin()
|
215 |
+
&& $this->can_user_manage();
|
216 |
}
|
217 |
|
218 |
/**
|
220 |
*
|
221 |
* @return bool
|
222 |
*/
|
223 |
+
private function has_valid_html_comments( $field ) {
|
224 |
$valid = true;
|
225 |
if ( $this->form_submitted() === true ) {
|
226 |
if ( $this->must_update_tracker() === true ) {
|
227 |
+
if ( ! empty( $_POST[ self::FORM_NAME ][ $field ] ) ) {
|
228 |
+
// phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
229 |
+
$valid = $this->validate_html_comments( $_POST[ self::FORM_NAME ][ $field ] );
|
230 |
+
// phpcs:enable WordPress.Security.ValidatedSanitizedInput
|
231 |
}
|
232 |
}
|
233 |
}
|
234 |
+
|
235 |
return $valid;
|
236 |
}
|
237 |
|
238 |
/**
|
239 |
* @param string $html html content to validate
|
240 |
+
*
|
241 |
* @returns boolean
|
242 |
*/
|
243 |
public function validate_html_comments( $html ) {
|
244 |
+
$opening = substr_count( $html, '<!--' );
|
245 |
+
$closing = substr_count( $html, '-->' );
|
246 |
+
|
247 |
+
return ( $opening === $closing );
|
248 |
}
|
249 |
|
250 |
public function show_settings() {
|
251 |
+
$was_updated = false;
|
252 |
+
$settings_errors = [];
|
253 |
if ( $this->has_valid_html_comments( 'tracking_code' ) !== true ) {
|
254 |
+
$settings_errors[] = __( 'Settings have not been saved. There is an issue with the HTML comments in the field "Tracking code". Make sure all opened comments (<!--) are closed (-->) correctly.', 'matomo' );
|
255 |
}
|
256 |
if ( $this->has_valid_html_comments( 'noscript_code' ) !== true ) {
|
257 |
+
$settings_errors[] = __( 'Settings have not been saved. There is an issue with the HTML comments in the field "Noscript code". Make sure all opened comments (<!--) are closed (-->) correctly.', 'matomo' );
|
258 |
}
|
259 |
+
if ( count( $settings_errors ) === 0 ) {
|
260 |
$was_updated = $this->update_if_submitted();
|
261 |
}
|
262 |
|
263 |
+
$settings = $this->settings;
|
264 |
|
265 |
$containers = $this->get_active_containers();
|
266 |
|
267 |
+
$track_modes = [
|
268 |
self::TRACK_MODE_DISABLED => esc_html__( 'Disabled', 'matomo' ),
|
269 |
self::TRACK_MODE_DEFAULT => esc_html__( 'Default tracking', 'matomo' ),
|
270 |
self::TRACK_MODE_MANUALLY => esc_html__( 'Enter manually', 'matomo' ),
|
271 |
+
];
|
272 |
|
273 |
if ( ! empty( $containers ) ) {
|
274 |
$track_modes[ self::TRACK_MODE_TAGMANAGER ] = esc_html__( 'Tag Manager', 'matomo' );
|
290 |
/**
|
291 |
* @return string[]
|
292 |
*/
|
293 |
+
private function get_cookie_consent_modes() {
|
|
|
294 |
$modes = [];
|
295 |
+
foreach ( CookieConsent::get_available_options() as $option => $description ) {
|
296 |
+
$modes[ $option ] = $description;
|
297 |
}
|
298 |
+
|
299 |
return $modes;
|
300 |
}
|
301 |
|
302 |
+
private function get_supported_currencies() {
|
303 |
+
$all = include dirname( MATOMO_ANALYTICS_FILE ) . '/app/core/Intl/Data/Resources/currencies.php';
|
304 |
+
$currencies = [];
|
305 |
+
foreach ( $all as $key => $single ) {
|
306 |
+
$currencies[ $key ] = $single[0] . ' ' . $single[1];
|
|
|
307 |
}
|
308 |
+
|
309 |
return $currencies;
|
310 |
}
|
311 |
|
312 |
public function get_active_containers() {
|
313 |
// we don't use Matomo API here to avoid needing to bootstrap Matomo which is slow and could break things
|
314 |
+
$containers = [];
|
315 |
if ( matomo_has_tag_manager() ) {
|
316 |
global $wpdb;
|
317 |
+
$db_settings = new \WpMatomo\Db\Settings();
|
318 |
+
$container_table = $db_settings->prefix_table_name( 'tagmanager_container' );
|
319 |
try {
|
320 |
+
// phpcs:disable WordPress.DB
|
321 |
$containers = $wpdb->get_results( sprintf( 'SELECT `idcontainer`, `name` FROM %s where `status` = "active"', $container_table ) );
|
322 |
+
// phpcs:enable WordPress.DB
|
323 |
+
} catch ( Exception $e ) {
|
324 |
// table may not exist yet etc
|
325 |
+
$containers = [];
|
326 |
}
|
327 |
}
|
328 |
+
$by_id = [];
|
329 |
foreach ( $containers as $container ) {
|
330 |
$by_id[ $container->idcontainer ] = $container->name;
|
331 |
}
|
332 |
|
333 |
return $by_id;
|
334 |
}
|
|
|
|
|
335 |
}
|
classes/WpMatomo/Admin/TrackingSettings/Forms.php
CHANGED
@@ -14,6 +14,7 @@
|
|
14 |
namespace WpMatomo\Admin\TrackingSettings;
|
15 |
|
16 |
use Piwik\Config;
|
|
|
17 |
use WpMatomo\Admin\TrackingSettings;
|
18 |
use WpMatomo\Bootstrap;
|
19 |
use WpMatomo\Settings;
|
@@ -21,7 +22,10 @@ use WpMatomo\Settings;
|
|
21 |
if ( ! defined( 'ABSPATH' ) ) {
|
22 |
exit; // if accessed directly
|
23 |
}
|
24 |
-
|
|
|
|
|
|
|
25 |
class Forms {
|
26 |
/**
|
27 |
* @var Settings
|
@@ -59,7 +63,7 @@ class Forms {
|
|
59 |
* @param string $on_change javascript for onchange event (default: empty)
|
60 |
*/
|
61 |
public function show_checkbox( $id, $name, $description, $is_hidden = false, $group_name = '', $hide_description = true, $on_change = '' ) {
|
62 |
-
printf( '<tr class="' . esc_attr( $group_name ) . ( $is_hidden ? ' hidden' : '' ) . '"><th scope="row"><label for="%2$s">%s</label>:</th><td><input type="checkbox" value="1"' . ( $this->settings->get_global_option( $id ) ? ' checked="checked"' : '' ) . ' onchange="jQuery(\'#%s\').val(this.checked?1:0);%s" /><input id="%2$s" type="hidden" name="' . esc_attr( TrackingSettings::FORM_NAME ) . '[%2$s]" value="' . (int) $this->settings->get_global_option( $id ) . '" /> %s</td></tr>', esc_html( $name ), $id, $on_change, $this->get_description( $id, $description, $hide_description ) );
|
63 |
}
|
64 |
|
65 |
/**
|
@@ -92,8 +96,8 @@ class Forms {
|
|
92 |
*
|
93 |
* @param string $text Text to show
|
94 |
*/
|
95 |
-
public function show_text( $text
|
96 |
-
printf( '<tr class="%s"><td colspan="2"><p>%s</p></td></tr>', $group_name, esc_html( $text ) );
|
97 |
}
|
98 |
|
99 |
/**
|
@@ -101,8 +105,8 @@ class Forms {
|
|
101 |
*
|
102 |
* @param string $text Text to show
|
103 |
*/
|
104 |
-
public function show_headline( $text
|
105 |
-
printf( '<tr class="%s"><td colspan="2"><h3>%s</h3></td></tr>', $group_name, esc_html( $text ) );
|
106 |
}
|
107 |
|
108 |
/**
|
@@ -134,23 +138,24 @@ class Forms {
|
|
134 |
* @param boolean $hide_description $hideDescription set to false to show description initially (default: true)
|
135 |
* @param boolean $global set to false if the textarea shows a site-specific option (default: true)
|
136 |
*/
|
137 |
-
public function show_select( $id, $name, $options =
|
138 |
$options_list = '';
|
139 |
|
140 |
-
if (
|
141 |
Bootstrap::do_bootstrap();
|
142 |
-
if (Config::getInstance()->Tracker['debug']) {
|
143 |
$default = 'always';
|
144 |
-
} elseif (Config::getInstance()->Tracker['debug_on_demand']) {
|
145 |
$default = 'on_demand';
|
146 |
} else {
|
147 |
$default = 'disabled';
|
148 |
}
|
149 |
} else {
|
150 |
-
$default
|
151 |
}
|
152 |
if ( is_array( $options ) ) {
|
153 |
foreach ( $options as $key => $value ) {
|
|
|
154 |
$options_list .= sprintf( '<option value="%s"' . ( $key == $default ? ' selected="selected"' : '' ) . '>%s</option>', esc_attr( $key ), esc_html( $value ) );
|
155 |
}
|
156 |
}
|
@@ -172,5 +177,4 @@ class Forms {
|
|
172 |
public function show_box( $type, $icon, $content ) {
|
173 |
printf( '<tr><td colspan="2"><div class="%s"><p><span class="dashicons dashicons-%s"></span> %s</p></div></td></tr>', esc_attr( $type ), esc_attr( $icon ), esc_html( $content ) );
|
174 |
}
|
175 |
-
|
176 |
}
|
14 |
namespace WpMatomo\Admin\TrackingSettings;
|
15 |
|
16 |
use Piwik\Config;
|
17 |
+
use WpMatomo;
|
18 |
use WpMatomo\Admin\TrackingSettings;
|
19 |
use WpMatomo\Bootstrap;
|
20 |
use WpMatomo\Settings;
|
22 |
if ( ! defined( 'ABSPATH' ) ) {
|
23 |
exit; // if accessed directly
|
24 |
}
|
25 |
+
/**
|
26 |
+
* we deal with HTML
|
27 |
+
* phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
28 |
+
*/
|
29 |
class Forms {
|
30 |
/**
|
31 |
* @var Settings
|
63 |
* @param string $on_change javascript for onchange event (default: empty)
|
64 |
*/
|
65 |
public function show_checkbox( $id, $name, $description, $is_hidden = false, $group_name = '', $hide_description = true, $on_change = '' ) {
|
66 |
+
printf( '<tr class="' . esc_attr( $group_name ) . ( $is_hidden ? ' hidden' : '' ) . '"><th scope="row"><label for="%2$s">%s</label>:</th><td><input type="checkbox" value="1"' . ( $this->settings->get_global_option( $id ) ? ' checked="checked"' : '' ) . ' onchange="jQuery(\'#%s\').val(this.checked?1:0);%s" /><input id="%2$s" type="hidden" name="' . esc_attr( TrackingSettings::FORM_NAME ) . '[%2$s]" value="' . (int) $this->settings->get_global_option( $id ) . '" /> %s</td></tr>', esc_html( $name ), esc_attr( $id ), $on_change, $this->get_description( $id, $description, $hide_description ) );
|
67 |
}
|
68 |
|
69 |
/**
|
96 |
*
|
97 |
* @param string $text Text to show
|
98 |
*/
|
99 |
+
public function show_text( $text, $group_name = '' ) {
|
100 |
+
printf( '<tr class="%s"><td colspan="2"><p>%s</p></td></tr>', esc_attr( $group_name ), esc_html( $text ) );
|
101 |
}
|
102 |
|
103 |
/**
|
105 |
*
|
106 |
* @param string $text Text to show
|
107 |
*/
|
108 |
+
public function show_headline( $text, $group_name = '' ) {
|
109 |
+
printf( '<tr class="%s"><td colspan="2"><h3>%s</h3></td></tr>', esc_attr( $group_name ), esc_html( $text ) );
|
110 |
}
|
111 |
|
112 |
/**
|
138 |
* @param boolean $hide_description $hideDescription set to false to show description initially (default: true)
|
139 |
* @param boolean $global set to false if the textarea shows a site-specific option (default: true)
|
140 |
*/
|
141 |
+
public function show_select( $id, $name, $options = [], $description = '', $on_change = '', $is_hidden = false, $group_name = '', $hide_description = true, $global = true ) {
|
142 |
$options_list = '';
|
143 |
|
144 |
+
if ( 'tracker_debug' === $id && ! WpMatomo::is_safe_mode() && ! $this->settings->is_network_enabled() ) {
|
145 |
Bootstrap::do_bootstrap();
|
146 |
+
if ( Config::getInstance()->Tracker['debug'] ) {
|
147 |
$default = 'always';
|
148 |
+
} elseif ( Config::getInstance()->Tracker['debug_on_demand'] ) {
|
149 |
$default = 'on_demand';
|
150 |
} else {
|
151 |
$default = 'disabled';
|
152 |
}
|
153 |
} else {
|
154 |
+
$default = $global ? $this->settings->get_global_option( $id ) : $this->settings->get_option( $id );
|
155 |
}
|
156 |
if ( is_array( $options ) ) {
|
157 |
foreach ( $options as $key => $value ) {
|
158 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
159 |
$options_list .= sprintf( '<option value="%s"' . ( $key == $default ? ' selected="selected"' : '' ) . '>%s</option>', esc_attr( $key ), esc_html( $value ) );
|
160 |
}
|
161 |
}
|
177 |
public function show_box( $type, $icon, $content ) {
|
178 |
printf( '<tr><td colspan="2"><div class="%s"><p><span class="dashicons dashicons-%s"></span> %s</p></div></td></tr>', esc_attr( $type ), esc_attr( $icon ), esc_html( $content ) );
|
179 |
}
|
|
|
180 |
}
|
classes/WpMatomo/Admin/views/access.php
CHANGED
@@ -12,10 +12,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
12 |
|
13 |
use WpMatomo\Access;
|
14 |
use WpMatomo\Admin\AccessSettings;
|
|
|
|
|
15 |
|
16 |
/** @var Access $access */
|
17 |
-
/** @var
|
18 |
-
/** @var
|
19 |
?>
|
20 |
|
21 |
<p><?php esc_html_e( 'Manage which roles can view and manage your reporting data.', 'matomo' ); ?></p>
|
@@ -35,7 +37,7 @@ use WpMatomo\Admin\AccessSettings;
|
|
35 |
foreach ( $roles->get_available_roles_for_configuration() as $matomo_role_id => $matomo_role_name ) {
|
36 |
echo '<tr><td>';
|
37 |
echo esc_html( $matomo_role_name ) . '</td>';
|
38 |
-
echo "<td><select name='" . AccessSettings::FORM_NAME . '[' . esc_attr( $matomo_role_id ) . "]'>";
|
39 |
$matomo_value = $access->get_permission_for_role( $matomo_role_id );
|
40 |
foreach ( Access::$matomo_permissions as $matomo_permission => $matomo_display_name ) {
|
41 |
echo "<option value='" . esc_attr( $matomo_permission ) . "' " . ( $matomo_value === $matomo_permission ? 'selected' : '' ) . '>' . esc_html( $matomo_display_name ) . '</option>';
|
@@ -52,30 +54,34 @@ use WpMatomo\Admin\AccessSettings;
|
|
52 |
</form>
|
53 |
|
54 |
<p>
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
<?php esc_html_e( 'Learn about the differences between these Matomo roles:', 'matomo' ); ?>
|
61 |
-
<a href="https://matomo.org/faq/general/faq_70/" target="_blank"
|
|
|
62 |
<a href="https://matomo.org/faq/general/faq_26910/" target="_blank"
|
63 |
rel="noopener"><?php esc_html_e( 'Write', 'matomo' ); ?></a>,
|
64 |
-
<a href="https://matomo.org/faq/general/faq_69/" target="_blank"
|
|
|
65 |
<a href="https://matomo.org/faq/general/faq_35/" target="_blank"
|
66 |
rel="noopener"><?php esc_html_e( 'Super User', 'matomo' ); ?></a><br/>
|
67 |
-
<?php esc_html_e( 'Want to redirect to the home page when not logged in?', 'matomo' ); ?> <a
|
|
|
|
|
68 |
</p>
|
69 |
|
70 |
<h2><?php esc_html_e( 'Roles', 'matomo' ); ?></h2>
|
71 |
<p>
|
72 |
-
<?php
|
73 |
-
esc_html_e(
|
74 |
-
|
75 |
to the user:',
|
76 |
-
|
77 |
-
)
|
78 |
-
?>
|
79 |
</p>
|
80 |
<ul class="matomo-list">
|
81 |
<?php foreach ( $roles->get_matomo_roles() as $matomo_role_config ) { ?>
|
@@ -85,18 +91,20 @@ esc_html_e(
|
|
85 |
|
86 |
<h2><?php esc_html_e( 'Capabilities', 'matomo' ); ?></h2>
|
87 |
<p>
|
88 |
-
<?php
|
89 |
-
esc_html_e(
|
90 |
-
|
91 |
the supported capabilities:',
|
92 |
-
|
93 |
-
)
|
94 |
-
?>
|
95 |
</p>
|
96 |
<ul class="matomo-list">
|
97 |
<?php
|
98 |
foreach ( $capabilites->get_all_capabilities_sorted_by_highest_permission() as $matomo_cap_name ) {
|
99 |
?>
|
100 |
<li><?php echo esc_html( $matomo_cap_name ); ?></li>
|
101 |
-
|
102 |
-
|
|
|
|
12 |
|
13 |
use WpMatomo\Access;
|
14 |
use WpMatomo\Admin\AccessSettings;
|
15 |
+
use WpMatomo\Capabilities;
|
16 |
+
use WpMatomo\Roles;
|
17 |
|
18 |
/** @var Access $access */
|
19 |
+
/** @var Roles $roles */
|
20 |
+
/** @var Capabilities $capabilites */
|
21 |
?>
|
22 |
|
23 |
<p><?php esc_html_e( 'Manage which roles can view and manage your reporting data.', 'matomo' ); ?></p>
|
37 |
foreach ( $roles->get_available_roles_for_configuration() as $matomo_role_id => $matomo_role_name ) {
|
38 |
echo '<tr><td>';
|
39 |
echo esc_html( $matomo_role_name ) . '</td>';
|
40 |
+
echo "<td><select name='" . esc_attr( AccessSettings::FORM_NAME ) . '[' . esc_attr( $matomo_role_id ) . "]'>";
|
41 |
$matomo_value = $access->get_permission_for_role( $matomo_role_id );
|
42 |
foreach ( Access::$matomo_permissions as $matomo_permission => $matomo_display_name ) {
|
43 |
echo "<option value='" . esc_attr( $matomo_permission ) . "' " . ( $matomo_value === $matomo_permission ? 'selected' : '' ) . '>' . esc_html( $matomo_display_name ) . '</option>';
|
54 |
</form>
|
55 |
|
56 |
<p>
|
57 |
+
<?php
|
58 |
+
if ( ! is_multisite() ) {
|
59 |
+
esc_html_e( 'A user with role administrator automatically has the super user role.', 'matomo' );
|
60 |
+
}
|
61 |
+
?>
|
62 |
<?php esc_html_e( 'Learn about the differences between these Matomo roles:', 'matomo' ); ?>
|
63 |
+
<a href="https://matomo.org/faq/general/faq_70/" target="_blank"
|
64 |
+
rel="noopener"><?php esc_html_e( 'View', 'matomo' ); ?></a>,
|
65 |
<a href="https://matomo.org/faq/general/faq_26910/" target="_blank"
|
66 |
rel="noopener"><?php esc_html_e( 'Write', 'matomo' ); ?></a>,
|
67 |
+
<a href="https://matomo.org/faq/general/faq_69/" target="_blank"
|
68 |
+
rel="noopener"><?php esc_html_e( 'Admin', 'matomo' ); ?></a>,
|
69 |
<a href="https://matomo.org/faq/general/faq_35/" target="_blank"
|
70 |
rel="noopener"><?php esc_html_e( 'Super User', 'matomo' ); ?></a><br/>
|
71 |
+
<?php esc_html_e( 'Want to redirect to the home page when not logged in?', 'matomo' ); ?> <a
|
72 |
+
href="https://matomo.org/faq/wordpress/how-do-i-hide-my-wordpress-login-url-when-someone-accesses-a-matomo-report-directly/"
|
73 |
+
target="_blank" rel="noreferrer noopener"><?php esc_html_e( 'Learn more', 'matomo' ); ?></a>
|
74 |
</p>
|
75 |
|
76 |
<h2><?php esc_html_e( 'Roles', 'matomo' ); ?></h2>
|
77 |
<p>
|
78 |
+
<?php
|
79 |
+
esc_html_e(
|
80 |
+
'Want to give individual users access to Matomo? Simply create a user in your WordPress and assign of these roles
|
81 |
to the user:',
|
82 |
+
'matomo'
|
83 |
+
)
|
84 |
+
?>
|
85 |
</p>
|
86 |
<ul class="matomo-list">
|
87 |
<?php foreach ( $roles->get_matomo_roles() as $matomo_role_config ) { ?>
|
91 |
|
92 |
<h2><?php esc_html_e( 'Capabilities', 'matomo' ); ?></h2>
|
93 |
<p>
|
94 |
+
<?php
|
95 |
+
esc_html_e(
|
96 |
+
'You can also install a WordPress plugin which lets you manage capabilities for each individual users. These are
|
97 |
the supported capabilities:',
|
98 |
+
'matomo'
|
99 |
+
)
|
100 |
+
?>
|
101 |
</p>
|
102 |
<ul class="matomo-list">
|
103 |
<?php
|
104 |
foreach ( $capabilites->get_all_capabilities_sorted_by_highest_permission() as $matomo_cap_name ) {
|
105 |
?>
|
106 |
<li><?php echo esc_html( $matomo_cap_name ); ?></li>
|
107 |
+
<?php
|
108 |
+
}
|
109 |
+
?>
|
110 |
+
</ul>
|
classes/WpMatomo/Admin/views/advanced_settings.php
CHANGED
@@ -10,7 +10,10 @@
|
|
10 |
* https://github.com/braekling/WP-Matomo
|
11 |
*
|
12 |
*/
|
13 |
-
|
|
|
|
|
|
|
14 |
use WpMatomo\Admin\AdvancedSettings;
|
15 |
|
16 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -29,45 +32,55 @@ if ( $was_updated ) {
|
|
29 |
<form method="post">
|
30 |
<?php wp_nonce_field( AdvancedSettings::NONCE_NAME ); ?>
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
39 |
<?php
|
40 |
-
echo '<span style="white-space: nowrap;display: inline-block;"><input type="radio" ' . ( empty($matomo_client_headers) ? 'checked="checked" ' : '' ) . ' value="REMOTE_ADDR" name="matomo[proxy_client_header]" /> <code>REMOTE_ADDR</code> ' . ( ! empty( $_SERVER[
|
41 |
foreach ( AdvancedSettings::$valid_host_headers as $host_header ) {
|
42 |
-
|
|
|
43 |
}
|
44 |
?>
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
* https://github.com/braekling/WP-Matomo
|
11 |
*
|
12 |
*/
|
13 |
+
/**
|
14 |
+
* phpcs consider all our variables as global and want them prefixed with matomo
|
15 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
16 |
+
*/
|
17 |
use WpMatomo\Admin\AdvancedSettings;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
32 |
<form method="post">
|
33 |
<?php wp_nonce_field( AdvancedSettings::NONCE_NAME ); ?>
|
34 |
|
35 |
+
<p><?php esc_html_e( 'Advanced settings', 'matomo' ); ?></p>
|
36 |
+
<table class="matomo-tracking-form widefat">
|
37 |
+
<tbody>
|
38 |
+
<tr>
|
39 |
+
<th width="20%" scope="row"><label
|
40 |
+
for="matomo[proxy_client_header]"><?php esc_html_e( 'Proxy IP headers', 'matomo' ); ?>:</label>
|
41 |
+
</th>
|
42 |
+
<td>
|
43 |
<?php
|
44 |
+
echo '<span style="white-space: nowrap;display: inline-block;"><input type="radio" ' . ( empty( $matomo_client_headers ) ? 'checked="checked" ' : '' ) . ' value="REMOTE_ADDR" name="matomo[proxy_client_header]" /> <code>REMOTE_ADDR</code> ' . ( ! empty( $_SERVER['REMOTE_ADDR'] ) ? esc_html( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) : esc_html__( 'No value found', 'matomo' ) ) . ' (' . esc_html__( 'Default', 'matomo' ) . ')</span>';
|
45 |
foreach ( AdvancedSettings::$valid_host_headers as $host_header ) {
|
46 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
47 |
+
echo '<span style="white-space: nowrap;display: inline-block;"><input type="radio" ' . ( in_array( $host_header, $matomo_client_headers, true ) ? 'checked="checked" ' : '' ) . 'value="' . esc_attr( $host_header ) . '" name="matomo[proxy_client_header]" /> <code>' . esc_html( $host_header ) . '</code> ' . ( ! empty( $_SERVER[ $host_header ] ) ? ( '<strong>' . esc_html( sanitize_text_field( wp_unslash( $_SERVER[ $host_header ] ) ) ) . '</strong>' ) : esc_html__( 'No value found', 'matomo' ) ) . ' </span>';
|
48 |
}
|
49 |
?>
|
50 |
+
</td>
|
51 |
+
<td width="50%">
|
52 |
+
<?php esc_html_e( 'We detected you have the following IP address:', 'matomo' ); ?>
|
53 |
+
<?php echo esc_html( $matomo_detected_ip ); ?> <br>
|
54 |
+
<?php echo sprintf( esc_html__( 'To compare this value with your actual IP address %1$splease click here%2$s.', 'matomo' ), '<a rel="noreferrer noopener" target="_blank" href="https://matomo.org/ip.php">', '</a>' ); ?>
|
55 |
+
<br><br>
|
56 |
+
<?php esc_html_e( 'Should your IP address not match the above value, your WordPress might be behind a proxy and you may need to select a different HTTP header depending on which of the values on the left shows your correct IP address.', 'matomo' ); ?>
|
57 |
+
</td>
|
58 |
+
</tr>
|
59 |
+
<?php if ( ! defined( 'MATOMO_REMOVE_ALL_DATA' ) ) { ?>
|
60 |
+
<tr>
|
61 |
+
<th width="20%" scope="row"><label
|
62 |
+
for="matomo[delete_all_data]"><?php esc_html_e( 'Delete all data on uninstall', 'matomo' ); ?>
|
63 |
+
:</label>
|
64 |
+
</th>
|
65 |
+
<td>
|
66 |
+
<?php
|
67 |
+
echo '<span style="white-space: nowrap;display: inline-block;"><input type="checkbox" ' . ( ! empty( $matomo_delete_all_data ) ? 'checked="checked" ' : '' ) . ' value="1" name="matomo[delete_all_data]" /> ' . esc_html__( 'Yes', 'matomo' ) . '</span>';
|
68 |
+
?>
|
69 |
+
</td>
|
70 |
+
<td width="50%">
|
71 |
+
By default, when you uninstall the Matomo plugin, all data is deleted and cannot be restored unless
|
72 |
+
you have backups. When you disable this feature, the tracked data in the database will be kept. This
|
73 |
+
can be useful to prevent accidental deletion of all your historical analytics data when you
|
74 |
+
uninstall the plugin. <a
|
75 |
+
href="https://matomo.org/faq/wordpress/how-do-i-delete-or-reset-the-matomo-for-wordpress-data-completely/"
|
76 |
+
target="_blank" rel="noreferrer noopener">Learn more</a>
|
77 |
+
</td>
|
78 |
+
</tr>
|
79 |
+
<?php } ?>
|
80 |
+
<tr>
|
81 |
+
<td colspan="3"><p class="submit"><input name="Submit" type="submit" class="button-primary"
|
82 |
+
value="<?php esc_attr_e( 'Save Changes', 'matomo' ); ?>"/></p></td>
|
83 |
+
</tr>
|
84 |
+
</tbody>
|
85 |
+
</table>
|
86 |
+
</form>
|
classes/WpMatomo/Admin/views/exclusion_settings.php
CHANGED
@@ -7,13 +7,13 @@
|
|
7 |
* @package matomo
|
8 |
* Code Based on
|
9 |
* @author André Bräkling
|
10 |
-
* @package WP_Matomo
|
11 |
* https://github.com/braekling/matomo
|
12 |
*
|
13 |
*/
|
14 |
|
15 |
use Piwik\Piwik;
|
16 |
use WpMatomo\Admin\ExclusionSettings;
|
|
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit;
|
@@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
25 |
/** @var string $excluded_user_agents */
|
26 |
/** @var string $excluded_query_params */
|
27 |
/** @var bool|string|int $keep_url_fragments */
|
28 |
-
/** @var
|
29 |
|
30 |
?>
|
31 |
|
@@ -34,120 +34,132 @@ if ( $was_updated ) {
|
|
34 |
include 'update_notice_clear_cache.php';
|
35 |
}
|
36 |
?>
|
37 |
-
<?php if ($settings->is_network_enabled() && is_network_admin()) { ?>
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
<?php } else { ?>
|
45 |
|
46 |
-
<form method="post">
|
47 |
-
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
<
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
86 |
)
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
</
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
|
|
|
|
|
|
|
|
126 |
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
|
|
|
|
|
|
134 |
)
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
<?php echo esc_html( Piwik::translate( 'SitesManager_KeepURLFragmentsHelp2' ) ); ?>
|
139 |
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
|
149 |
-
|
150 |
-
|
151 |
-
</form>
|
152 |
|
153 |
-
<?php } ?>
|
7 |
* @package matomo
|
8 |
* Code Based on
|
9 |
* @author André Bräkling
|
|
|
10 |
* https://github.com/braekling/matomo
|
11 |
*
|
12 |
*/
|
13 |
|
14 |
use Piwik\Piwik;
|
15 |
use WpMatomo\Admin\ExclusionSettings;
|
16 |
+
use WpMatomo\Settings;
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit;
|
25 |
/** @var string $excluded_user_agents */
|
26 |
/** @var string $excluded_query_params */
|
27 |
/** @var bool|string|int $keep_url_fragments */
|
28 |
+
/** @var Settings $settings */
|
29 |
|
30 |
?>
|
31 |
|
34 |
include 'update_notice_clear_cache.php';
|
35 |
}
|
36 |
?>
|
37 |
+
<?php if ( $settings->is_network_enabled() && is_network_admin() ) { ?>
|
38 |
+
<h2>Exclusion settings</h2>
|
39 |
+
<p>
|
40 |
+
Exclusion settings have to be configured on a per blog basis.
|
41 |
+
Should you wish to change any setting, please go to the Matomo exclusion settings within each blog.
|
42 |
+
We are hoping to improve this in the future.
|
43 |
+
</p>
|
44 |
<?php } else { ?>
|
45 |
|
46 |
+
<form method="post">
|
47 |
+
<?php wp_nonce_field( ExclusionSettings::NONCE_NAME ); ?>
|
48 |
|
49 |
+
<p><?php esc_html_e( 'Configure exclusions.', 'matomo' ); ?></p>
|
50 |
+
<table class="matomo-tracking-form widefat">
|
51 |
+
<tbody>
|
52 |
|
53 |
+
<tr>
|
54 |
+
<th width="20%" scope="row"><label><?php esc_html_e( 'Tracking filter', 'matomo' ); ?></label>:
|
55 |
+
</th>
|
56 |
+
<td>
|
57 |
+
<?php
|
58 |
+
$matomo_tracking_caps = Settings::OPTION_KEY_STEALTH;
|
59 |
+
$matomo_filter = $settings->get_global_option( $matomo_tracking_caps );
|
60 |
+
foreach ( $wp_roles->role_names as $matomo_key => $matomo_name ) {
|
61 |
+
echo '<input type="checkbox" ' . ( isset( $matomo_filter [ $matomo_key ] ) && $matomo_filter [ $matomo_key ] ? 'checked="checked" ' : '' ) . 'value="1" name="' . esc_attr( ExclusionSettings::FORM_NAME ) . '[' . esc_attr( $matomo_tracking_caps ) . '][' . esc_attr( $matomo_key ) . ']" /> ' . esc_html( $matomo_name ) . ' <br />';
|
62 |
+
}
|
63 |
+
?>
|
64 |
+
</td>
|
65 |
+
<td width="50%">
|
66 |
+
<?php echo sprintf( esc_html__( 'Choose users by user role you do %1$snot%2$s want to track.', 'matomo' ), '<strong>', '</strong>' ); ?>
|
67 |
+
<?php if ( $settings->is_network_enabled() ) { ?>
|
68 |
+
<br><p><strong>This setting will be applied to all blogs. Changing it here also changes it for
|
69 |
+
other blogs.</strong></p>
|
70 |
+
<?php } ?>
|
71 |
+
</td>
|
72 |
+
</tr>
|
73 |
+
<tr>
|
74 |
+
<th width="20%" scope="row">
|
75 |
+
<label><?php echo esc_html( Piwik::translate( 'SitesManager_GlobalListExcludedIps' ) ); ?></label>:
|
76 |
+
</th>
|
77 |
+
<td width="30%">
|
78 |
+
<?php echo sprintf( '<textarea cols="40" rows="4" id="%1$s" name="' . esc_attr( ExclusionSettings::FORM_NAME ) . '[%1$s]">%2$s</textarea>', 'excluded_ips', esc_html( $excluded_ips ) ); ?>
|
79 |
+
</td>
|
80 |
+
<td width="50%">
|
81 |
+
<?php
|
82 |
+
echo esc_html(
|
83 |
+
Piwik::translate(
|
84 |
+
'SitesManager_HelpExcludedIpAddresses',
|
85 |
+
[
|
86 |
+
'1.2.3.4/24',
|
87 |
+
'1.2.3.*',
|
88 |
+
'1.2.*.*',
|
89 |
+
]
|
90 |
+
)
|
91 |
)
|
92 |
+
?>
|
93 |
+
<br/>
|
94 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_YourCurrentIpAddressIs', esc_html( $current_ip ) ) ); ?>
|
95 |
+
</td>
|
96 |
+
</tr>
|
97 |
+
<tr>
|
98 |
+
<th scope="row">
|
99 |
+
<label><?php echo esc_html( Piwik::translate( 'SitesManager_GlobalListExcludedQueryParameters' ) ); ?></label>:
|
100 |
+
</th>
|
101 |
+
<td>
|
102 |
+
<?php echo sprintf( '<textarea cols="40" rows="4" id="%1$s" name="' . esc_attr( ExclusionSettings::FORM_NAME ) . '[%1$s]">%2$s</textarea>', 'excluded_query_parameters', esc_html( $excluded_query_params ) ); ?>
|
103 |
+
</td>
|
104 |
+
<td>
|
105 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_ListOfQueryParametersToExclude', '/^sess.*|.*[dD]ate$/' ) ); ?>
|
106 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_PiwikWillAutomaticallyExcludeCommonSessionParameters', 'phpsessid, sessionid, ...' ) ); ?>
|
107 |
+
</td>
|
108 |
+
</tr>
|
109 |
+
<tr>
|
110 |
+
<th scope="row">
|
111 |
+
<label><?php echo esc_html( Piwik::translate( 'SitesManager_GlobalListExcludedUserAgents' ) ); ?></label>:
|
112 |
+
</th>
|
113 |
+
<td>
|
114 |
+
<?php echo sprintf( '<textarea cols="40" rows="4" id="%1$s" name="' . esc_attr( ExclusionSettings::FORM_NAME ) . '[%1$s]">%2$s</textarea>', 'excluded_user_agents', esc_html( $excluded_user_agents ) ); ?>
|
115 |
+
</td>
|
116 |
+
<td>
|
117 |
|
118 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_GlobalExcludedUserAgentHelp1' ) ); ?>
|
119 |
+
<br/>
|
120 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_GlobalListExcludedUserAgents_Desc' ) ); ?>
|
121 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_GlobalExcludedUserAgentHelp2' ) ); ?>
|
122 |
|
123 |
+
</td>
|
124 |
+
</tr>
|
125 |
+
<tr>
|
126 |
+
<th scope="row">
|
127 |
+
<label><?php echo esc_html( Piwik::translate( 'SitesManager_KeepURLFragmentsLong' ) ); ?></label>:
|
128 |
+
</th>
|
129 |
+
<td>
|
130 |
+
<?php
|
131 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
132 |
+
echo sprintf( '<input type="checkbox" value="1" %2$s name="' . esc_attr( ExclusionSettings::FORM_NAME ) . '[%1$s]">', 'keep_url_fragments', $keep_url_fragments ? ' checked="checked"' : '' );
|
133 |
+
?>
|
134 |
+
</td>
|
135 |
+
<td>
|
136 |
|
137 |
+
<?php
|
138 |
+
echo esc_html(
|
139 |
+
Piwik::translate(
|
140 |
+
'SitesManager_KeepURLFragmentsHelp',
|
141 |
+
[
|
142 |
+
'<em>#</em>',
|
143 |
+
'<em>example.org/index.html#first_section</em>',
|
144 |
+
'<em>example.org/index.html</em>',
|
145 |
+
]
|
146 |
+
)
|
147 |
)
|
148 |
+
?>
|
149 |
+
<br/>
|
150 |
+
<?php echo esc_html( Piwik::translate( 'SitesManager_KeepURLFragmentsHelp2' ) ); ?>
|
|
|
151 |
|
152 |
+
</td>
|
153 |
+
</tr>
|
154 |
+
<tr>
|
155 |
+
<td colspan="3">
|
156 |
+
<p class="submit"><input name="Submit" type="submit" class="button-primary"
|
157 |
+
value="<?php echo esc_attr__( 'Save Changes', 'matomo' ); ?>"/></p>
|
158 |
+
</td>
|
159 |
+
</tr>
|
160 |
|
161 |
+
</tbody>
|
162 |
+
</table>
|
163 |
+
</form>
|
164 |
|
165 |
+
<?php } ?>
|
classes/WpMatomo/Admin/views/geolocation_settings.php
CHANGED
@@ -7,9 +7,7 @@
|
|
7 |
* @package matomo
|
8 |
* Code Based on
|
9 |
* @author André Bräkling
|
10 |
-
* @package WP_Matomo
|
11 |
* https://github.com/braekling/matomo
|
12 |
-
*
|
13 |
*/
|
14 |
|
15 |
use WpMatomo\Admin\GeolocationSettings;
|
@@ -21,11 +19,11 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
21 |
/** @var bool $invalid_format */
|
22 |
/** @var string $current_maxmind_license */
|
23 |
|
24 |
-
if ($invalid_format) { ?>
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
<?php
|
29 |
}
|
30 |
?>
|
31 |
|
@@ -33,39 +31,43 @@ if ($invalid_format) { ?>
|
|
33 |
<?php wp_nonce_field( GeolocationSettings::NONCE_NAME ); ?>
|
34 |
|
35 |
<p>
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
|
|
48 |
|
49 |
<table class="matomo-tracking-form widefat">
|
50 |
<tbody>
|
51 |
<tr>
|
52 |
-
<th
|
53 |
-
|
54 |
</th>
|
55 |
<td>
|
56 |
<input size="20" type="text" maxlength="20"
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
</td>
|
60 |
-
<td>
|
61 |
-
<?php if (!empty($current_maxmind_license)) {?>
|
62 |
-
<p style="color: green;"><span class="dashicons dashicons-yes" ></span> <?php esc_html_e('MaxMind is configured.', 'matomo') ?></p>
|
63 |
-
<?php }?>
|
64 |
-
<p>
|
65 |
-
<?php esc_html_e('Leave the field empty and click on "Save Changes" to configure the default DB-IP database.', 'matomo') ?>
|
66 |
-
<?php esc_html_e('When configured, your WordPress will send an HTTP request to a MaxMind server to download an approx. 60MB database and store it in your "wp-content/uploads/matomo" directory.', 'matomo') ?>
|
67 |
-
</p>
|
68 |
-
</td>
|
69 |
</tr>
|
70 |
<tr>
|
71 |
<td colspan="3">
|
7 |
* @package matomo
|
8 |
* Code Based on
|
9 |
* @author André Bräkling
|
|
|
10 |
* https://github.com/braekling/matomo
|
|
|
11 |
*/
|
12 |
|
13 |
use WpMatomo\Admin\GeolocationSettings;
|
19 |
/** @var bool $invalid_format */
|
20 |
/** @var string $current_maxmind_license */
|
21 |
|
22 |
+
if ( $invalid_format ) { ?>
|
23 |
+
<div class="updated notice error">
|
24 |
+
<p><?php esc_html_e( 'It looks like the MaxMind license key has a wrong format.', 'matomo' ); ?></p>
|
25 |
+
</div>
|
26 |
+
<?php
|
27 |
}
|
28 |
?>
|
29 |
|
31 |
<?php wp_nonce_field( GeolocationSettings::NONCE_NAME ); ?>
|
32 |
|
33 |
<p>
|
34 |
+
<?php esc_html_e( 'On this page you can configure how Matomo detects the locations of your visitors.', 'matomo' ); ?>
|
35 |
+
</p>
|
36 |
+
<p>
|
37 |
+
<?php esc_html_e( 'To detect the location of a visitor, the IP address of a visitor is looked up in a so called geolocation database. This is automatically taken care of for you. However, the freely available database DB-IP we are using is sometimes less accurate than other freely available geolocation databases. This applies to the free and paid version of DB-IP. An alternative geolocation database is called MaxMind which has a free and a paid version as well. Because of GDPR we cannot configure this database automatically for you.', 'matomo' ); ?>
|
38 |
+
<br><br>
|
39 |
+
<?php
|
40 |
+
echo sprintf(
|
41 |
+
esc_html__( 'To use MaxMind instead of the default DB-IP geolocation database %1$s get a MaxMind license key%2$s and then configure this key below.', 'matomo' ),
|
42 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://matomo.org/faq/how-to/how-do-i-get-a-license-key-for-the-maxmind-geolocation-database/">',
|
43 |
+
'</a>'
|
44 |
+
);
|
45 |
+
?>
|
46 |
+
</p>
|
47 |
|
48 |
<table class="matomo-tracking-form widefat">
|
49 |
<tbody>
|
50 |
<tr>
|
51 |
+
<th scope="row" style="vertical-align: top;">
|
52 |
+
<label for="<?php echo esc_attr( GeolocationSettings::FORM_NAME ); ?>"><?php esc_html_e( 'MaxMind License Key', 'matomo' ); ?></label>:
|
53 |
</th>
|
54 |
<td>
|
55 |
<input size="20" type="text" maxlength="20"
|
56 |
+
id="<?php echo esc_attr( GeolocationSettings::FORM_NAME ); ?>"
|
57 |
+
name="<?php echo esc_attr( GeolocationSettings::FORM_NAME ); ?>"
|
58 |
+
value="<?php echo esc_attr( $current_maxmind_license ); ?>">
|
59 |
+
</td>
|
60 |
+
<td>
|
61 |
+
<?php if ( ! empty( $current_maxmind_license ) ) { ?>
|
62 |
+
<p style="color: green;"><span
|
63 |
+
class="dashicons dashicons-yes"></span> <?php esc_html_e( 'MaxMind is configured.', 'matomo' ); ?>
|
64 |
+
</p>
|
65 |
+
<?php } ?>
|
66 |
+
<p>
|
67 |
+
<?php esc_html_e( 'Leave the field empty and click on "Save Changes" to configure the default DB-IP database.', 'matomo' ); ?>
|
68 |
+
<?php esc_html_e( 'When configured, your WordPress will send an HTTP request to a MaxMind server to download an approx. 60MB database and store it in your "wp-content/uploads/matomo" directory.', 'matomo' ); ?>
|
69 |
+
</p>
|
70 |
</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
</tr>
|
72 |
<tr>
|
73 |
<td colspan="3">
|
classes/WpMatomo/Admin/views/get_started.php
CHANGED
@@ -6,17 +6,21 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
-
|
|
|
|
|
|
|
10 |
use WpMatomo\Admin\AdminSettings;
|
|
|
11 |
use WpMatomo\Admin\Menu;
|
12 |
use WpMatomo\Admin\TrackingSettings;
|
13 |
-
use WpMatomo\
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit;
|
17 |
}
|
18 |
|
19 |
-
/** @var
|
20 |
/** @var bool $can_user_edit */
|
21 |
/** @var bool $was_updated */
|
22 |
/** @var bool $show_this_page */
|
@@ -38,15 +42,18 @@ if ( empty( $show_this_page ) ) {
|
|
38 |
?>
|
39 |
|
40 |
<?php if ( $settings->is_tracking_enabled() ) { ?>
|
41 |
-
<h2>1. <?php esc_html_e( 'Tracking is enabled', 'matomo' ); ?> <span class="dashicons dashicons-yes"
|
42 |
-
|
|
|
|
|
|
|
43 |
|
44 |
<?php } else { ?>
|
45 |
<h2>1. <?php esc_html_e( 'Enable tracking', 'matomo' ); ?></h2>
|
46 |
|
47 |
<form
|
48 |
-
|
49 |
-
<input type="hidden" name="<?php echo GetStarted::FORM_NAME; ?>[track_mode]"
|
50 |
value="<?php echo esc_attr( TrackingSettings::TRACK_MODE_DEFAULT ); ?>">
|
51 |
<input type="submit" class="button-primary" value="<?php esc_html_e( 'Enable tracking now', 'matomo' ); ?>">
|
52 |
</form>
|
@@ -54,17 +61,18 @@ if ( empty( $show_this_page ) ) {
|
|
54 |
|
55 |
<h2>2. <?php esc_html_e( 'Update your privacy page', 'matomo' ); ?></h2>
|
56 |
|
57 |
-
<?php echo sprintf( esc_html__( 'Give your users the chance to opt-out of tracking by adding the shortcode %1$s to your privacy page. You can %2$stweak the opt-out to your liking - see the Privacy Settings%3$s.', 'matomo' ), '<code>[matomo_opt_out]</code>', '<a href="' . AdminSettings::make_url( AdminSettings::TAB_PRIVACY ) . '">', '</a>' ); ?>
|
58 |
|
59 |
<?php esc_html_e( 'You may also need to mention that you are using Matomo Analytics on your website.', 'matomo' ); ?>
|
60 |
-
<?php echo sprintf(esc_html__( 'By %1$sdisabling cookies in the tracking settings%2$s, you might not need to ask for any cookie or tracking consent if the GDPR or ePrivacy applies to you %3$s(learn more)%4$s.', 'matomo' ), '<a href="'.AdminSettings::make_url( AdminSettings::TAB_TRACKING ).'" target="_blank" rel="noreferrer noopener">', '</a>', '<a href="https://matomo.org/faq/new-to-piwik/how-do-i-use-matomo-analytics-without-consent-or-cookie-banner/" target="_blank" rel="noreferrer noopener">', '</a>'); ?>
|
61 |
|
62 |
<h2>3. <?php esc_html_e( 'Done', 'matomo' ); ?></h2>
|
63 |
<form method="post">
|
64 |
<?php wp_nonce_field( GetStarted::NONCE_NAME ); ?>
|
65 |
<input type="hidden" name="<?php echo esc_attr( GetStarted::FORM_NAME ); ?>[show_get_started_page]"
|
66 |
value="no">
|
67 |
-
<input type="submit" class="button-primary"
|
|
|
68 |
</form>
|
69 |
<p>
|
70 |
<br/>
|
@@ -73,5 +81,6 @@ if ( empty( $show_this_page ) ) {
|
|
73 |
<?php require 'info_shared.php'; ?>
|
74 |
<?php
|
75 |
$show_troubleshooting_link = false;
|
76 |
-
require 'info_help.php';
|
|
|
77 |
</div>
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
/**
|
10 |
+
* phpcs considers all of our variables as global and want them prefixed with matomo
|
11 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
12 |
+
*/
|
13 |
use WpMatomo\Admin\AdminSettings;
|
14 |
+
use WpMatomo\Admin\GetStarted;
|
15 |
use WpMatomo\Admin\Menu;
|
16 |
use WpMatomo\Admin\TrackingSettings;
|
17 |
+
use WpMatomo\Settings;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit;
|
21 |
}
|
22 |
|
23 |
+
/** @var Settings $settings */
|
24 |
/** @var bool $can_user_edit */
|
25 |
/** @var bool $was_updated */
|
26 |
/** @var bool $show_this_page */
|
42 |
?>
|
43 |
|
44 |
<?php if ( $settings->is_tracking_enabled() ) { ?>
|
45 |
+
<h2>1. <?php esc_html_e( 'Tracking is enabled', 'matomo' ); ?> <span class="dashicons dashicons-yes"
|
46 |
+
style="color: green;"></span></h2>
|
47 |
+
<p><?php esc_html_e( 'Tracking should be working now and you don\'t have to do anything else to set up tracking.', 'matomo' ); ?>
|
48 |
+
<a href="<?php echo esc_url( AdminSettings::make_url( AdminSettings::TAB_TRACKING ) ); ?>"><?php esc_html_e( 'Click here to optionally configure the tracking code to your liking (not required).', 'matomo' ); ?></a>
|
49 |
+
</p>
|
50 |
|
51 |
<?php } else { ?>
|
52 |
<h2>1. <?php esc_html_e( 'Enable tracking', 'matomo' ); ?></h2>
|
53 |
|
54 |
<form
|
55 |
+
method="post"><?php esc_html_e( 'Tracking is currently disabled', 'matomo' ); ?> <?php wp_nonce_field( GetStarted::NONCE_NAME ); ?>
|
56 |
+
<input type="hidden" name="<?php echo esc_attr( GetStarted::FORM_NAME ); ?>[track_mode]"
|
57 |
value="<?php echo esc_attr( TrackingSettings::TRACK_MODE_DEFAULT ); ?>">
|
58 |
<input type="submit" class="button-primary" value="<?php esc_html_e( 'Enable tracking now', 'matomo' ); ?>">
|
59 |
</form>
|
61 |
|
62 |
<h2>2. <?php esc_html_e( 'Update your privacy page', 'matomo' ); ?></h2>
|
63 |
|
64 |
+
<?php echo sprintf( esc_html__( 'Give your users the chance to opt-out of tracking by either adding the shortcode %1$s or by adding the "Matomo opt out" block to your privacy page. You can %2$stweak the opt-out to your liking - see the Privacy Settings%3$s.', 'matomo' ), '<code>[matomo_opt_out]</code>', '<a href="' . esc_url( AdminSettings::make_url( AdminSettings::TAB_PRIVACY ) ) . '">', '</a>' ); ?>
|
65 |
|
66 |
<?php esc_html_e( 'You may also need to mention that you are using Matomo Analytics on your website.', 'matomo' ); ?>
|
67 |
+
<?php echo sprintf( esc_html__( 'By %1$sdisabling cookies in the tracking settings%2$s, you might not need to ask for any cookie or tracking consent if the GDPR or ePrivacy applies to you %3$s(learn more)%4$s.', 'matomo' ), '<a href="' . esc_url( AdminSettings::make_url( AdminSettings::TAB_TRACKING ) ) . '" target="_blank" rel="noreferrer noopener">', '</a>', '<a href="https://matomo.org/faq/new-to-piwik/how-do-i-use-matomo-analytics-without-consent-or-cookie-banner/" target="_blank" rel="noreferrer noopener">', '</a>' ); ?>
|
68 |
|
69 |
<h2>3. <?php esc_html_e( 'Done', 'matomo' ); ?></h2>
|
70 |
<form method="post">
|
71 |
<?php wp_nonce_field( GetStarted::NONCE_NAME ); ?>
|
72 |
<input type="hidden" name="<?php echo esc_attr( GetStarted::FORM_NAME ); ?>[show_get_started_page]"
|
73 |
value="no">
|
74 |
+
<input type="submit" class="button-primary"
|
75 |
+
value="<?php esc_html_e( 'Don\'t show this page anymore', 'matomo' ); ?>">
|
76 |
</form>
|
77 |
<p>
|
78 |
<br/>
|
81 |
<?php require 'info_shared.php'; ?>
|
82 |
<?php
|
83 |
$show_troubleshooting_link = false;
|
84 |
+
require 'info_help.php';
|
85 |
+
?>
|
86 |
</div>
|
classes/WpMatomo/Admin/views/info.php
CHANGED
@@ -6,8 +6,11 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
11 |
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit;
|
@@ -26,36 +29,36 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
26 |
|
27 |
<h2><?php esc_html_e( 'Support the project', 'matomo' ); ?></h2>
|
28 |
<p>
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
%3$splease give us a review%4$s and spread the word about us.',
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
<br/><br/>
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
echo sprintf(
|
47 |
esc_html__(
|
48 |
'You can also help us by %1$sdonating%2$s or by %3$spurchasing premium plugins%4$s which fund the
|
49 |
development of the free Matomo Analytics version.',
|
50 |
'matomo'
|
51 |
),
|
52 |
-
'<a href="' . Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ADMIN ) . '">',
|
53 |
'</a>',
|
54 |
'<a href="https://plugins.matomo.org/premium" target="_blank" rel="noreferrer noopener">',
|
55 |
'</a>'
|
56 |
);
|
57 |
?>
|
58 |
-
|
59 |
</p>
|
60 |
|
61 |
<?php require 'info_newsletter.php'; ?>
|
@@ -69,18 +72,18 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
69 |
<ul>
|
70 |
<li>
|
71 |
<a target="_blank" rel="noreferrer noopener" href="https://matomo.org/newsletter/"><span
|
72 |
-
|
73 |
<a target="_blank" rel="noreferrer noopener"
|
74 |
href="https://matomo.org/newsletter/"><?php esc_html_e( 'Newsletter', 'matomo' ); ?></a>
|
75 |
</li>
|
76 |
<li>
|
77 |
<a target="_blank" rel="noreferrer noopener" href="https://www.facebook.com/Matomo.org"><span
|
78 |
-
|
79 |
<a target="_blank" rel="noreferrer noopener" href="https://www.facebook.com/Matomo.org">Facebook</a>
|
80 |
</li>
|
81 |
<li>
|
82 |
<a target="_blank" rel="noreferrer noopener" href="https://twitter.com/matomo_org"><span
|
83 |
-
|
84 |
<a target="_blank" rel="noreferrer noopener" href="https://twitter.com/matomo_org">Twitter</a>
|
85 |
</li>
|
86 |
<li>
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
/**
|
10 |
+
* phpcs considers all of our variables as global and want them prefixed with matomo
|
11 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
12 |
+
*/
|
13 |
+
use WpMatomo\Admin\Menu;
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit;
|
29 |
|
30 |
<h2><?php esc_html_e( 'Support the project', 'matomo' ); ?></h2>
|
31 |
<p>
|
32 |
+
<?php
|
33 |
+
echo sprintf(
|
34 |
+
esc_html__(
|
35 |
+
'Matomo is a collaborative project brought to you by %1$sMatomo team%2$s members as well as many other contributors around the globe. If you like Matomo,
|
36 |
%3$splease give us a review%4$s and spread the word about us.',
|
37 |
+
'matomo'
|
38 |
+
),
|
39 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://matomo.org/team/">',
|
40 |
+
'</a>',
|
41 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://wordpress.org/support/plugin/matomo/reviews/?rate=5#new-post">',
|
42 |
+
'<span class="dashicons-before dashicons-star-filled" style="color:gold;"></span><span class="dashicons-before dashicons-star-filled" style="color:gold;"></span><span class="dashicons-before dashicons-star-filled" style="color:gold;"></span><span class="dashicons-before dashicons-star-filled" style="color:gold;"></span><span class="dashicons-before dashicons-star-filled" style="color:gold;"></span></a>'
|
43 |
+
);
|
44 |
+
?>
|
45 |
<br/><br/>
|
46 |
+
Matomo will always cost you nothing to use, but that doesn't mean it costs us nothing to make.
|
47 |
+
Matomo needs your continued support to grow and thrive.
|
48 |
+
<?php
|
49 |
echo sprintf(
|
50 |
esc_html__(
|
51 |
'You can also help us by %1$sdonating%2$s or by %3$spurchasing premium plugins%4$s which fund the
|
52 |
development of the free Matomo Analytics version.',
|
53 |
'matomo'
|
54 |
),
|
55 |
+
'<a href="' . esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ADMIN ) ) . '">',
|
56 |
'</a>',
|
57 |
'<a href="https://plugins.matomo.org/premium" target="_blank" rel="noreferrer noopener">',
|
58 |
'</a>'
|
59 |
);
|
60 |
?>
|
61 |
+
Every penny will help.
|
62 |
</p>
|
63 |
|
64 |
<?php require 'info_newsletter.php'; ?>
|
72 |
<ul>
|
73 |
<li>
|
74 |
<a target="_blank" rel="noreferrer noopener" href="https://matomo.org/newsletter/"><span
|
75 |
+
class="dashicons-before dashicons-email"></span></a>
|
76 |
<a target="_blank" rel="noreferrer noopener"
|
77 |
href="https://matomo.org/newsletter/"><?php esc_html_e( 'Newsletter', 'matomo' ); ?></a>
|
78 |
</li>
|
79 |
<li>
|
80 |
<a target="_blank" rel="noreferrer noopener" href="https://www.facebook.com/Matomo.org"><span
|
81 |
+
class="dashicons-before dashicons-facebook"></span></a>
|
82 |
<a target="_blank" rel="noreferrer noopener" href="https://www.facebook.com/Matomo.org">Facebook</a>
|
83 |
</li>
|
84 |
<li>
|
85 |
<a target="_blank" rel="noreferrer noopener" href="https://twitter.com/matomo_org"><span
|
86 |
+
class="dashicons-before dashicons-twitter"></span></a>
|
87 |
<a target="_blank" rel="noreferrer noopener" href="https://twitter.com/matomo_org">Twitter</a>
|
88 |
</li>
|
89 |
<li>
|
classes/WpMatomo/Admin/views/info_bug_report.php
CHANGED
@@ -13,17 +13,17 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
13 |
?>
|
14 |
<h2><?php esc_html_e( 'Do you have a bug to report or a feature request?', 'matomo' ); ?></h2>
|
15 |
<p>
|
16 |
-
<?php
|
17 |
-
echo sprintf(
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
);
|
28 |
-
?>
|
29 |
-
|
13 |
?>
|
14 |
<h2><?php esc_html_e( 'Do you have a bug to report or a feature request?', 'matomo' ); ?></h2>
|
15 |
<p>
|
16 |
+
<?php
|
17 |
+
echo sprintf(
|
18 |
+
esc_html__( 'Please read the recommendations on writing a good %1$sbug report%2$s or %3$sfeature request%4$s. Then register or login to %5$sour issue tracker%6$s and create a %7$snew issue%8$s.', 'matomo' ),
|
19 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://developer.matomo.org/guides/core-team-workflow#submitting-a-bug-report">',
|
20 |
+
'</a>',
|
21 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://developer.matomo.org/guides/core-team-workflow#submitting-a-feature-request">',
|
22 |
+
'</a>',
|
23 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://github.com/matomo-org/wp-matomo/issues">',
|
24 |
+
'</a>',
|
25 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://github.com/matomo-org/wp-matomo/issues/new">',
|
26 |
+
'</a>'
|
27 |
+
);
|
28 |
+
?>
|
29 |
+
</p>
|
classes/WpMatomo/Admin/views/info_help.php
CHANGED
@@ -7,6 +7,8 @@
|
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
|
|
|
|
10 |
if ( ! defined( 'ABSPATH' ) ) {
|
11 |
exit; // if accessed directly
|
12 |
}
|
@@ -22,10 +24,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
22 |
href="https://matomo.org/docs/"><?php esc_html_e( 'User guides', 'matomo' ); ?></a>
|
23 |
- <?php esc_html_e( 'Learn how to configure Matomo and how to effectively analyse your data', 'matomo' ); ?>
|
24 |
</li>
|
25 |
-
<li><a target="_blank" rel="noreferrer noopener"
|
|
|
26 |
- <?php esc_html_e( 'Get answers to frequently asked questions', 'matomo' ); ?>
|
27 |
</li>
|
28 |
-
<li><a target="_blank" rel="noreferrer noopener"
|
|
|
29 |
- <?php esc_html_e( 'Get answers to frequently asked questions', 'matomo' ); ?>
|
30 |
</li>
|
31 |
<li><a target="_blank" rel="noreferrer noopener"
|
@@ -41,9 +45,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
41 |
- <?php esc_html_e( 'Let our experienced team assist you online on how to best utilise Matomo', 'matomo' ); ?>
|
42 |
</li>
|
43 |
<?php if ( ! empty( $show_troubleshooting_link ) ) { ?>
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
<?php } ?>
|
49 |
</ul>
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
10 |
+
use WpMatomo\Admin\Menu;
|
11 |
+
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
24 |
href="https://matomo.org/docs/"><?php esc_html_e( 'User guides', 'matomo' ); ?></a>
|
25 |
- <?php esc_html_e( 'Learn how to configure Matomo and how to effectively analyse your data', 'matomo' ); ?>
|
26 |
</li>
|
27 |
+
<li><a target="_blank" rel="noreferrer noopener"
|
28 |
+
href="https://matomo.org/faq/wordpress/"><?php esc_html_e( 'Matomo for WordPress FAQs', 'matomo' ); ?></a>
|
29 |
- <?php esc_html_e( 'Get answers to frequently asked questions', 'matomo' ); ?>
|
30 |
</li>
|
31 |
+
<li><a target="_blank" rel="noreferrer noopener"
|
32 |
+
href="https://matomo.org/faq/"><?php esc_html_e( 'General FAQs', 'matomo' ); ?></a>
|
33 |
- <?php esc_html_e( 'Get answers to frequently asked questions', 'matomo' ); ?>
|
34 |
</li>
|
35 |
<li><a target="_blank" rel="noreferrer noopener"
|
45 |
- <?php esc_html_e( 'Let our experienced team assist you online on how to best utilise Matomo', 'matomo' ); ?>
|
46 |
</li>
|
47 |
<?php if ( ! empty( $show_troubleshooting_link ) ) { ?>
|
48 |
+
<li><a
|
49 |
+
href="<?php echo esc_url( add_query_arg( [ 'tab' => 'troubleshooting' ], menu_page_url( Menu::SLUG_SYSTEM_REPORT, false ) ) ); ?>"><?php esc_html_e( 'Troubleshooting', 'matomo' ); ?></a>
|
50 |
+
- <?php esc_html_e( 'Click here if you are having Trouble with Matomo', 'matomo' ); ?>
|
51 |
+
</li>
|
52 |
<?php } ?>
|
53 |
</ul>
|
classes/WpMatomo/Admin/views/info_high_traffic.php
CHANGED
@@ -12,17 +12,17 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
12 |
}
|
13 |
?>
|
14 |
<p>If your website gets a lot of traffic we recommend installing <a href="https://matomo.org/matomo-on-premise/"
|
15 |
-
|
16 |
On-Premise</a>
|
17 |
separately (it's free as well) in combination with the <a href="https://wordpress.org/plugins/wp-piwik/"
|
18 |
-
|
19 |
-
|
20 |
plugin.
|
21 |
It's free to install and has the same requirements as WordPress.
|
22 |
Your Matomo will then run a lot faster and it allows you to put your Matomo installation on a separate server if
|
23 |
needed.
|
24 |
<br/><br/>Don't want all the hassle of maintaining a Matomo? <a href="http://matomo.org/start-free-analytics-trial/"
|
25 |
-
|
26 |
for a free Matomo Cloud trial</a>. We can migrate all your data onto our Cloud for free. 100% data ownership
|
27 |
guaranteed.
|
28 |
</p>
|
12 |
}
|
13 |
?>
|
14 |
<p>If your website gets a lot of traffic we recommend installing <a href="https://matomo.org/matomo-on-premise/"
|
15 |
+
target="_blank" rel="noreferrer noopener">Matomo
|
16 |
On-Premise</a>
|
17 |
separately (it's free as well) in combination with the <a href="https://wordpress.org/plugins/wp-piwik/"
|
18 |
+
target="_blank"
|
19 |
+
rel="noreferrer noopener">WP-Matomo</a> WordPress
|
20 |
plugin.
|
21 |
It's free to install and has the same requirements as WordPress.
|
22 |
Your Matomo will then run a lot faster and it allows you to put your Matomo installation on a separate server if
|
23 |
needed.
|
24 |
<br/><br/>Don't want all the hassle of maintaining a Matomo? <a href="http://matomo.org/start-free-analytics-trial/"
|
25 |
+
rel="noreferrer noopener" target="_blank">Sign up
|
26 |
for a free Matomo Cloud trial</a>. We can migrate all your data onto our Cloud for free. 100% data ownership
|
27 |
guaranteed.
|
28 |
</p>
|
classes/WpMatomo/Admin/views/info_multisite.php
CHANGED
@@ -6,11 +6,14 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
|
|
|
|
|
|
9 |
if ( ! defined( 'ABSPATH' ) ) {
|
10 |
exit;
|
11 |
}
|
12 |
|
13 |
-
/** @var
|
14 |
?>
|
15 |
|
16 |
<div class="wrap">
|
@@ -29,25 +32,25 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
29 |
</p>
|
30 |
<h2><?php esc_html_e( 'Managing many sites?', 'matomo' ); ?></h2>
|
31 |
<p>
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
Your Matomo will then run a lot faster, you can put Matomo on a separate server if needed, and it allows you to make use of additional features such as %5$sRoll-Up Reporting%6$s.',
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
|
48 |
<br/><br/><?php esc_html_e( 'Don\'t want all the hassle of maintaining a Matomo?', 'matomo' ); ?> <a
|
49 |
-
|
50 |
-
|
51 |
</p>
|
52 |
|
53 |
<h2><?php esc_html_e( 'Matomo sites', 'matomo' ); ?></h2>
|
@@ -57,8 +60,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
57 |
foreach ( get_sites() as $matomo_site ) {
|
58 |
/** @var WP_Site $matomo_site */
|
59 |
switch_to_blog( $matomo_site->blog_id );
|
60 |
-
if (function_exists('is_plugin_active') && is_plugin_active('matomo/matomo.php')) {
|
61 |
-
echo '<li><a href="' . esc_url(admin_url( 'admin.php?page=matomo-reporting' )) . '">' . esc_html($matomo_site->blogname) . ' (Site ID: ' . esc_html($matomo_site->blog_id) . ')</a></li>';
|
62 |
}
|
63 |
restore_current_blog();
|
64 |
}
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
|
10 |
+
use WpMatomo\Settings;
|
11 |
+
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit;
|
14 |
}
|
15 |
|
16 |
+
/** @var Settings $settings */
|
17 |
?>
|
18 |
|
19 |
<div class="wrap">
|
32 |
</p>
|
33 |
<h2><?php esc_html_e( 'Managing many sites?', 'matomo' ); ?></h2>
|
34 |
<p>
|
35 |
+
<?php
|
36 |
+
echo sprintf(
|
37 |
+
esc_html__(
|
38 |
+
'If you are managing quite a few sites or have quite a bit of traffic then we recommend installing %1$sMatomo On-Premise%2$s separately outside WordPress (it\'s free as well) and use it in combination with the %3$sWP-Matomo%4$s WordPress plugin.
|
39 |
Your Matomo will then run a lot faster, you can put Matomo on a separate server if needed, and it allows you to make use of additional features such as %5$sRoll-Up Reporting%6$s.',
|
40 |
+
'matomo'
|
41 |
+
),
|
42 |
+
'<a href="https://matomo.org/matomo-on-premise/" target="_blank" rel="noreferrer noopener">',
|
43 |
+
'</a>',
|
44 |
+
'<a href="https://wordpress.org/plugins/wp-piwik/" target="_blank" rel="noreferrer noopener">',
|
45 |
+
'</a>',
|
46 |
+
'<a href="https://plugins.matomo.org/RollUpReporting" target="_blank" rel="noreferrer noopener">',
|
47 |
+
'</a>'
|
48 |
+
);
|
49 |
+
?>
|
50 |
|
51 |
<br/><br/><?php esc_html_e( 'Don\'t want all the hassle of maintaining a Matomo?', 'matomo' ); ?> <a
|
52 |
+
href="http://matomo.org/start-free-analytics-trial/" rel="noreferrer noopener"
|
53 |
+
target="_blank"><?php esc_html_e( 'Sign up for a free Matomo Cloud trial', 'matomo' ); ?></a>. <?php esc_html_e( 'We can migrate all your data onto our Cloud for free. 100% data ownership guaranteed.', 'matomo' ); ?>
|
54 |
</p>
|
55 |
|
56 |
<h2><?php esc_html_e( 'Matomo sites', 'matomo' ); ?></h2>
|
60 |
foreach ( get_sites() as $matomo_site ) {
|
61 |
/** @var WP_Site $matomo_site */
|
62 |
switch_to_blog( $matomo_site->blog_id );
|
63 |
+
if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'matomo/matomo.php' ) ) {
|
64 |
+
echo '<li><a href="' . esc_url( admin_url( 'admin.php?page=matomo-reporting' ) ) . '">' . esc_html( $matomo_site->blogname ) . ' (Site ID: ' . esc_html( $matomo_site->blog_id ) . ')</a></li>';
|
65 |
}
|
66 |
restore_current_blog();
|
67 |
}
|
classes/WpMatomo/Admin/views/info_newsletter.php
CHANGED
@@ -7,7 +7,7 @@
|
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
10 |
-
use
|
11 |
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit;
|
@@ -15,15 +15,15 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
15 |
/** @var bool $signedup_newsletter */
|
16 |
/** @var bool $show_newsletter */
|
17 |
|
18 |
-
if ($signedup_newsletter) {
|
19 |
-
?>
|
20 |
-
<div class="notice notice-success is-dismissible">
|
21 |
-
|
22 |
-
</div>
|
23 |
-
<?php
|
24 |
return;
|
25 |
}
|
26 |
-
if (
|
27 |
return;
|
28 |
}
|
29 |
?>
|
@@ -33,14 +33,14 @@ if (!$show_newsletter) {
|
|
33 |
<form method="post">
|
34 |
<p>
|
35 |
<?php wp_nonce_field( Info::NONCE_NAME ); ?>
|
36 |
-
<input type="checkbox" id="<?php echo Info::FORM_NAME ?>" name="<?php echo Info::FORM_NAME ?>" value="1">
|
37 |
-
<label for="<?php echo Info::FORM_NAME ?>">
|
38 |
-
<?php esc_html_e('Subscribe to our newsletter to receive regular information about Matomo, web analytics, and privacy. You can unsubscribe from it any time.', 'matomo'); ?>
|
39 |
-
<?php esc_html_e('This service uses MadMimi.', 'matomo'); ?>
|
40 |
-
<?php echo sprintf(esc_html__('Learn more about it on our %1$sPrivacy Policy page%2$s.', 'matomo'), '<a href="https://matomo.org/privacy-policy/" target="_blank" rel="noreferrer noopener">', '</a>'); ?>
|
41 |
</label>
|
42 |
<br><br>
|
43 |
-
<input type="submit" class="button-secondary" value="<?php esc_attr_e('Subscribe', 'matomo')
|
44 |
</p>
|
45 |
</form>
|
46 |
-
</div>
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
10 |
+
use WpMatomo\Admin\Info;
|
11 |
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit;
|
15 |
/** @var bool $signedup_newsletter */
|
16 |
/** @var bool $show_newsletter */
|
17 |
|
18 |
+
if ( $signedup_newsletter ) {
|
19 |
+
?>
|
20 |
+
<div class="notice notice-success is-dismissible">
|
21 |
+
<p><?php esc_html_e( 'Thank you for signing up to our newsletter.', 'matomo' ); ?></p>
|
22 |
+
</div>
|
23 |
+
<?php
|
24 |
return;
|
25 |
}
|
26 |
+
if ( ! $show_newsletter ) {
|
27 |
return;
|
28 |
}
|
29 |
?>
|
33 |
<form method="post">
|
34 |
<p>
|
35 |
<?php wp_nonce_field( Info::NONCE_NAME ); ?>
|
36 |
+
<input type="checkbox" id="<?php echo esc_attr( Info::FORM_NAME ); ?>" name="<?php echo esc_attr( Info::FORM_NAME ); ?>" value="1">
|
37 |
+
<label for="<?php echo esc_attr( Info::FORM_NAME ); ?>">
|
38 |
+
<?php esc_html_e( 'Subscribe to our newsletter to receive regular information about Matomo, web analytics, and privacy. You can unsubscribe from it any time.', 'matomo' ); ?>
|
39 |
+
<?php esc_html_e( 'This service uses MadMimi.', 'matomo' ); ?>
|
40 |
+
<?php echo sprintf( esc_html__( 'Learn more about it on our %1$sPrivacy Policy page%2$s.', 'matomo' ), '<a href="https://matomo.org/privacy-policy/" target="_blank" rel="noreferrer noopener">', '</a>' ); ?>
|
41 |
</label>
|
42 |
<br><br>
|
43 |
+
<input type="submit" class="button-secondary" value="<?php esc_attr_e( 'Subscribe', 'matomo' ); ?>">
|
44 |
</p>
|
45 |
</form>
|
46 |
+
</div>
|
classes/WpMatomo/Admin/views/info_shared.php
CHANGED
@@ -11,12 +11,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
11 |
exit; // if accessed directly
|
12 |
}
|
13 |
?>
|
14 |
-
<h1><?php esc_html_e( 'About', 'matomo' );
|
15 |
|
16 |
<p>
|
17 |
<?php
|
18 |
echo sprintf(
|
19 |
-
|
20 |
'%1$sMatomo Analytics%2$s is the most powerful
|
21 |
analytics platform for WordPress, designed for your success. It is our mission to help you grow
|
22 |
your business while giving you %3$sfull control over your data%4$s. All
|
11 |
exit; // if accessed directly
|
12 |
}
|
13 |
?>
|
14 |
+
<h1><?php esc_html_e( 'About', 'matomo' ); ?><?php matomo_header_icon( true ); ?> </h1>
|
15 |
|
16 |
<p>
|
17 |
<?php
|
18 |
echo sprintf(
|
19 |
+
esc_html__(
|
20 |
'%1$sMatomo Analytics%2$s is the most powerful
|
21 |
analytics platform for WordPress, designed for your success. It is our mission to help you grow
|
22 |
your business while giving you %3$sfull control over your data%4$s. All
|
classes/WpMatomo/Admin/views/marketplace.php
CHANGED
@@ -13,11 +13,11 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
13 |
|
14 |
/** @var \WpMatomo\Settings $settings */
|
15 |
$matomo_extra_url_params = '&' . http_build_query(
|
16 |
-
|
17 |
'php' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
|
18 |
'matomo' => $settings->get_global_option( 'core_version' ),
|
19 |
'wp_version' => ! empty( $GLOBALS['wp_version'] ) ? $GLOBALS['wp_version'] : '',
|
20 |
-
|
21 |
);
|
22 |
?>
|
23 |
<div class="wrap">
|
@@ -30,256 +30,287 @@ $matomo_extra_url_params = '&' . http_build_query(
|
|
30 |
|
31 |
<div id="icon-plugins" class="icon32"></div>
|
32 |
|
33 |
-
<h1><?php matomo_header_icon();
|
34 |
|
35 |
<?php if ( ! is_plugin_active( MATOMO_MARKETPLACE_PLUGIN_NAME ) ) { ?>
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
40 |
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
44 |
<?php } ?>
|
45 |
|
46 |
<?php
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
$matomo_num_features_in_block = count( $matomo_feature_section['features'] );
|
52 |
|
53 |
-
|
54 |
-
|
55 |
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
|
|
|
|
|
|
76 |
|
77 |
-
|
78 |
-
|
79 |
<?php
|
80 |
-
|
81 |
-
|
82 |
-
name column-name
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
<?php
|
103 |
-
|
104 |
-
|
105 |
-
desc column-description
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
|
138 |
-
$matomo_feature_sections =
|
139 |
-
|
140 |
-
'title'
|
141 |
-
'more_url'
|
142 |
'more_text' => 'Browse all free plugins',
|
143 |
-
'features'
|
144 |
-
|
145 |
-
|
146 |
'name' => 'Marketing Campaigns Reporting',
|
147 |
'description' => 'Measure the effectiveness of your marketing campaigns. Track up to five channels instead of two: campaign, source, medium, keyword, content.',
|
148 |
'price' => 'free',
|
149 |
'download_url' => 'https://plugins.matomo.org/api/2.0/plugins/MarketingCampaignsReporting/download/latest?wp=1' . $matomo_extra_url_params,
|
150 |
'url' => 'https://plugins.matomo.org/MarketingCampaignsReporting?wp=1&pk_campaign=WP&pk_source=Plugin',
|
151 |
'image' => '',
|
152 |
-
|
153 |
-
|
154 |
'name' => 'Custom Alerts',
|
155 |
'description' => 'Create custom Alerts to be notified of important changes on your website or app!',
|
156 |
'price' => 'free',
|
157 |
'download_url' => 'https://plugins.matomo.org/api/2.0/plugins/CustomAlerts/download/latest?wp=1' . $matomo_extra_url_params,
|
158 |
'url' => 'https://plugins.matomo.org/CustomAlerts?wp=1&pk_campaign=WP&pk_source=Plugin',
|
159 |
'image' => '',
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
|
165 |
-
|
166 |
|
167 |
-
|
168 |
|
169 |
-
$matomo_feature_sections =
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
'title' => 'Most popular content engagement',
|
200 |
'features' =>
|
201 |
-
|
202 |
-
|
203 |
'name' => 'Form Analytics',
|
204 |
'description' => 'Increase conversions on your online forms and lose less visitors by learning everything about your users behavior and their pain points on your forms.',
|
205 |
'price' => '79EUR / 89USD',
|
206 |
'url' => 'https://plugins.matomo.org/FormAnalytics?wp=1',
|
207 |
'image' => '',
|
208 |
-
|
209 |
-
|
210 |
'name' => 'Video & Audio Analytics',
|
211 |
'description' => 'Grow your business with advanced video & audio analytics. Get powerful insights into how your audience watches your videos and listens to your audio.',
|
212 |
'price' => '79EUR / 89USD',
|
213 |
'url' => 'https://plugins.matomo.org/MediaAnalytics?wp=1',
|
214 |
'image' => '',
|
215 |
-
|
216 |
-
|
217 |
'name' => 'Users Flow',
|
218 |
'description' => 'Users Flow is a visual representation of the most popular paths your users take through your website & app which lets you understand your users needs.',
|
219 |
'price' => '39EUR / 39USD',
|
220 |
'url' => 'https://plugins.matomo.org/UsersFlow?wp=1',
|
221 |
'image' => '',
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
'title' => 'Most popular acquisition & SEO features',
|
227 |
'features' =>
|
228 |
-
|
229 |
-
|
230 |
'name' => 'Search Engine Keywords Performance',
|
231 |
'description' => 'All keywords searched by your users on search engines are now visible into your Referrers reports! The ultimate solution to \'Keyword not defined\'.',
|
232 |
'price' => '69EUR / 79USD',
|
233 |
'url' => 'https://plugins.matomo.org/SearchEngineKeywordsPerformance?wp=1',
|
234 |
'image' => '',
|
235 |
-
|
236 |
-
|
237 |
'name' => 'Advertising Conversion Export',
|
238 |
'description' => 'Provides an export of attributed goal conversions for usage in ad networks like Google Ads so you no longer need a conversion pixel.',
|
239 |
'price' => '79EUR / 89USD',
|
240 |
'url' => 'https://plugins.matomo.org/AdvertisingConversionExport?wp=1',
|
241 |
'image' => '',
|
242 |
-
|
243 |
-
|
244 |
'name' => 'Multi Attribution',
|
245 |
'description' => 'Get a clear understanding of how much credit each of your marketing channel is actually responsible for to shift your marketing efforts wisely.',
|
246 |
'price' => '39EUR / 39USD',
|
247 |
'url' => 'https://plugins.matomo.org/MultiChannelConversionAttribution?wp=1',
|
248 |
'image' => '',
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
'description' => 'Truly understand your visitors by seeing where they click, hover, type and scroll. Replay their actions in a video and ultimately increase conversions',
|
254 |
-
'price' => '19EUR / 19USD',
|
255 |
-
'url' => 'https://plugins.matomo.org/ActivityLog?wp=1',
|
256 |
-
'image' => '',
|
257 |
-
),*/
|
258 |
-
),
|
259 |
-
),
|
260 |
-
array(
|
261 |
'title' => 'Other premium features',
|
262 |
'features' =>
|
263 |
-
|
264 |
-
|
265 |
'name' => 'Funnels',
|
266 |
'description' => 'Identify and understand where your visitors drop off to increase your conversions, sales and revenue with your existing traffic.',
|
267 |
'price' => '89EUR / 99USD',
|
268 |
'url' => 'https://plugins.matomo.org/Funnels?wp=1',
|
269 |
'image' => '',
|
270 |
-
|
271 |
-
|
272 |
'name' => 'Cohorts',
|
273 |
'description' => 'Track your retention efforts over time and keep your visitors engaged and coming back for more.',
|
274 |
'price' => '49EUR / 59USD',
|
275 |
'url' => 'https://plugins.matomo.org/Cohorts?wp=1',
|
276 |
'image' => '',
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
|
282 |
-
|
283 |
|
284 |
?>
|
285 |
|
13 |
|
14 |
/** @var \WpMatomo\Settings $settings */
|
15 |
$matomo_extra_url_params = '&' . http_build_query(
|
16 |
+
[
|
17 |
'php' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
|
18 |
'matomo' => $settings->get_global_option( 'core_version' ),
|
19 |
'wp_version' => ! empty( $GLOBALS['wp_version'] ) ? $GLOBALS['wp_version'] : '',
|
20 |
+
]
|
21 |
);
|
22 |
?>
|
23 |
<div class="wrap">
|
30 |
|
31 |
<div id="icon-plugins" class="icon32"></div>
|
32 |
|
33 |
+
<h1><?php matomo_header_icon(); ?><?php esc_html_e( 'Discover new functionality for your Matomo', 'matomo' ); ?></h1>
|
34 |
|
35 |
<?php if ( ! is_plugin_active( MATOMO_MARKETPLACE_PLUGIN_NAME ) ) { ?>
|
36 |
+
<div class="updated notice matomo-marketplace-notice">
|
37 |
+
<p><?php echo sprintf( esc_html__( 'Easily install over 100 free plugins & %1$spremium features%2$s for Matomo with just a click', 'matomo' ), '<span style="white-space: nowrap;">', '</span>' ); ?>
|
38 |
+
</p>
|
39 |
+
<p><a href="https://builds.matomo.org/matomo-marketplace-for-wordpress-latest.zip" rel="noreferrer noopener"
|
40 |
+
class="button"><?php esc_html_e( 'Download Matomo Marketplace for WordPress', 'matomo' ); ?></a>
|
41 |
|
42 |
+
<a target="_blank"
|
43 |
+
href="https://matomo.org/faq/wordpress/how-do-i-install-a-matomo-marketplace-plugin-in-matomo-for-wordpress/"><span
|
44 |
+
class="dashicons-before dashicons-video-alt3"></span></a> <a target="_blank"
|
45 |
+
href="https://matomo.org/faq/wordpress/how-do-i-install-a-matomo-marketplace-plugin-in-matomo-for-wordpress/"><?php esc_html_e( 'Install instructions', 'matomo' ); ?></a>
|
46 |
+
</p>
|
47 |
+
</div>
|
48 |
<?php } ?>
|
49 |
|
50 |
<?php
|
51 |
+
function matomo_show_tables( $matomo_feature_sections ) {
|
52 |
+
foreach ( $matomo_feature_sections as $matomo_feature_section ) {
|
53 |
+
$matomo_feature_section['features'] = array_filter( $matomo_feature_section['features'] );
|
54 |
+
$matomo_num_features_in_block = count( $matomo_feature_section['features'] );
|
|
|
55 |
|
56 |
+
echo '<h2>' . esc_html( $matomo_feature_section['title'] ) . '</h2>';
|
57 |
+
echo '<div class="wp-list-table widefat plugin-install matomo-plugin-list matomo-plugin-row-' . esc_html( $matomo_num_features_in_block ) . '"><div id="the-list">';
|
58 |
|
59 |
+
foreach ( $matomo_feature_section['features'] as $matomo_index => $matomo_feature ) {
|
60 |
+
$matomo_style = '';
|
61 |
+
$matomo_is_3_columns = 3 === $matomo_num_features_in_block;
|
62 |
+
if ( $matomo_is_3_columns ) {
|
63 |
+
$matomo_style = 'width: calc(33% - 8px);min-width:282px;max-width:350px;';
|
64 |
+
if ( 2 === $matomo_index % 3 ) {
|
65 |
+
$matomo_style .= 'clear: inherit;margin-right: 0;margin-left: 16px;';
|
66 |
+
}
|
67 |
+
}
|
68 |
+
?>
|
69 |
+
<div class="plugin-card" style="<?php echo esc_attr( $matomo_style ); ?>">
|
70 |
+
<?php
|
71 |
+
if ( $matomo_is_3_columns && ! empty( $matomo_feature['image'] ) ) {
|
72 |
+
?>
|
73 |
+
<a
|
74 |
+
href="<?php echo esc_url( $matomo_feature['url'] ); ?>"
|
75 |
+
rel="noreferrer noopener" target="_blank"
|
76 |
+
class="thickbox open-plugin-details-modal"><img
|
77 |
+
src="<?php echo esc_url( $matomo_feature['image'] ); ?>"
|
78 |
+
style="height: 80px;width:100%;object-fit: cover;" alt=""></a>
|
79 |
+
<?php
|
80 |
+
}
|
81 |
+
?>
|
82 |
|
83 |
+
<div class="plugin-card-top">
|
84 |
+
<div class="
|
85 |
<?php
|
86 |
+
if ( ! $matomo_is_3_columns ) {
|
87 |
+
?>
|
88 |
+
name column-name
|
89 |
+
<?php
|
90 |
+
}
|
91 |
+
?>
|
92 |
+
" style="margin-right: 0;
|
93 |
+
<?php
|
94 |
+
if ( empty( $matomo_feature['image'] ) ) {
|
95 |
+
echo 'margin-left: 0;';
|
96 |
+
}
|
97 |
+
?>
|
98 |
+
">
|
99 |
+
<h3>
|
100 |
+
<a href="<?php echo esc_url( ! empty( $matomo_feature['video'] ) ? $matomo_feature['video'] : $matomo_feature['url'] ); ?>"
|
101 |
+
rel="noreferrer noopener" target="_blank"
|
102 |
+
class="thickbox open-plugin-details-modal">
|
103 |
+
<?php echo esc_html( $matomo_feature['name'] ); ?>
|
104 |
+
</a>
|
105 |
+
<?php
|
106 |
+
if ( ! $matomo_is_3_columns && ! empty( $matomo_feature['image'] ) ) {
|
107 |
+
?>
|
108 |
+
<a
|
109 |
+
href="<?php echo esc_url( $matomo_feature['url'] ); ?>"
|
110 |
+
rel="noreferrer noopener" target="_blank"
|
111 |
+
class="thickbox open-plugin-details-modal"><img
|
112 |
+
src="<?php echo esc_url( $matomo_feature['image'] ); ?>" class="plugin-icon"
|
113 |
+
style="object-fit: cover;"
|
114 |
+
alt=""></a>
|
115 |
+
<?php
|
116 |
+
}
|
117 |
+
?>
|
118 |
+
</h3>
|
119 |
+
</div>
|
120 |
+
<div class="
|
121 |
<?php
|
122 |
+
if ( ! $matomo_is_3_columns ) {
|
123 |
+
?>
|
124 |
+
desc column-description
|
125 |
+
<?php
|
126 |
+
}
|
127 |
+
?>
|
128 |
+
"
|
129 |
+
style="margin-right: 0;
|
130 |
+
<?php
|
131 |
+
if ( empty( $matomo_feature['image'] ) ) {
|
132 |
+
echo 'margin-left: 0;';
|
133 |
+
}
|
134 |
+
?>
|
135 |
+
">
|
136 |
+
<p class="matomo-description"><?php echo esc_html( $matomo_feature['description'] ); ?>
|
137 |
+
<?php
|
138 |
+
if ( ! empty( $matomo_feature['video'] ) ) {
|
139 |
+
echo ' <a target="_blank" rel="noreferrer noopener" style="white-space: nowrap;" href="' . esc_url( $matomo_feature['video'] ) . '"><span class="dashicons dashicons-video-alt3"></span> ' . esc_html__( 'Learn more', 'matomo' ) . '</a>';
|
140 |
+
} elseif ( ! empty( $matomo_feature['url'] ) ) {
|
141 |
+
echo ' <a target="_blank" rel="noreferrer noopener" style="white-space: nowrap;" href="' . esc_url( $matomo_feature['url'] ) . '">' . esc_html__( 'Learn more', 'matomo' ) . '</a>';
|
142 |
+
}
|
143 |
+
?>
|
144 |
+
</p>
|
145 |
+
<?php
|
146 |
+
if ( ! empty( $matomo_feature['price'] ) ) {
|
147 |
+
?>
|
148 |
+
<p class="authors"><a class="button-primary"
|
149 |
+
rel="noreferrer noopener" target="_blank"
|
150 |
+
href="<?php echo esc_url( ! empty( $matomo_feature['download_url'] ) ? $matomo_feature['download_url'] : $matomo_feature['url'] ); ?>">
|
151 |
+
<?php
|
152 |
+
if ( 'free' === $matomo_feature['price'] ) {
|
153 |
+
esc_html_e( 'Download', 'matomo' );
|
154 |
+
} else {
|
155 |
+
echo esc_html( $matomo_feature['price'] );
|
156 |
+
}
|
157 |
+
?>
|
158 |
+
</a>
|
159 |
+
</p>
|
160 |
+
<?php
|
161 |
+
}
|
162 |
+
?>
|
163 |
+
</div>
|
164 |
+
</div>
|
165 |
+
</div>
|
166 |
+
<?php
|
167 |
+
}
|
168 |
+
echo '';
|
169 |
+
echo '</div><div style="clear: both"></div>';
|
170 |
+
if ( ! empty( $matomo_feature_section['more_url'] ) ) {
|
171 |
+
echo '<a target="_blank" rel="noreferrer noopener" href="' . esc_attr( $matomo_feature_section['more_url'] ) . '"><span class="dashicons dashicons-arrow-right-alt2"></span>' . esc_html( $matomo_feature_section['more_text'] ) . '</a>';
|
172 |
+
}
|
173 |
+
echo '</div>';
|
174 |
+
}
|
175 |
+
}
|
176 |
|
177 |
+
$matomo_feature_sections = [
|
178 |
+
[
|
179 |
+
'title' => 'Top free plugins',
|
180 |
+
'more_url' => 'https://plugins.matomo.org/free?wp=1',
|
181 |
'more_text' => 'Browse all free plugins',
|
182 |
+
'features' =>
|
183 |
+
[
|
184 |
+
[
|
185 |
'name' => 'Marketing Campaigns Reporting',
|
186 |
'description' => 'Measure the effectiveness of your marketing campaigns. Track up to five channels instead of two: campaign, source, medium, keyword, content.',
|
187 |
'price' => 'free',
|
188 |
'download_url' => 'https://plugins.matomo.org/api/2.0/plugins/MarketingCampaignsReporting/download/latest?wp=1' . $matomo_extra_url_params,
|
189 |
'url' => 'https://plugins.matomo.org/MarketingCampaignsReporting?wp=1&pk_campaign=WP&pk_source=Plugin',
|
190 |
'image' => '',
|
191 |
+
],
|
192 |
+
[
|
193 |
'name' => 'Custom Alerts',
|
194 |
'description' => 'Create custom Alerts to be notified of important changes on your website or app!',
|
195 |
'price' => 'free',
|
196 |
'download_url' => 'https://plugins.matomo.org/api/2.0/plugins/CustomAlerts/download/latest?wp=1' . $matomo_extra_url_params,
|
197 |
'url' => 'https://plugins.matomo.org/CustomAlerts?wp=1&pk_campaign=WP&pk_source=Plugin',
|
198 |
'image' => '',
|
199 |
+
],
|
200 |
+
],
|
201 |
+
],
|
202 |
+
];
|
203 |
|
204 |
+
matomo_show_tables( $matomo_feature_sections );
|
205 |
|
206 |
+
echo '<br>';
|
207 |
|
208 |
+
$matomo_feature_sections = [
|
209 |
+
[
|
210 |
+
'title' => 'Most popular premium features',
|
211 |
+
'features' =>
|
212 |
+
[
|
213 |
+
[
|
214 |
+
'name' => 'Heatmap & Session Recording',
|
215 |
+
'description' => 'Truly understand your visitors by seeing where they click, hover, type and scroll. Replay their actions in a video and ultimately increase conversions.',
|
216 |
+
'price' => '99EUR / 119USD',
|
217 |
+
'url' => 'https://plugins.matomo.org/HeatmapSessionRecording?wp=1',
|
218 |
+
'image' => '',
|
219 |
+
],
|
220 |
+
[
|
221 |
+
'name' => 'Custom Reports',
|
222 |
+
'description' => 'Pull out the information you need in order to be successful. Develop your custom strategy to meet your individualized goals while saving money & time.',
|
223 |
+
'price' => '99EUR / 119USD',
|
224 |
+
'url' => 'https://plugins.matomo.org/CustomReports?wp=1',
|
225 |
+
'image' => '',
|
226 |
+
],
|
227 |
|
228 |
+
[
|
229 |
+
'name' => 'Premium Bundle',
|
230 |
+
'description' => 'All premium features in one bundle, make the most out of your Matomo for WordPress and enjoy discounts of over 25%!',
|
231 |
+
'price' => '499EUR / 579USD',
|
232 |
+
'url' => 'https://plugins.matomo.org/WpPremiumBundle?wp=1',
|
233 |
+
'image' => '',
|
234 |
+
],
|
235 |
+
],
|
236 |
+
],
|
237 |
+
[
|
238 |
'title' => 'Most popular content engagement',
|
239 |
'features' =>
|
240 |
+
[
|
241 |
+
[
|
242 |
'name' => 'Form Analytics',
|
243 |
'description' => 'Increase conversions on your online forms and lose less visitors by learning everything about your users behavior and their pain points on your forms.',
|
244 |
'price' => '79EUR / 89USD',
|
245 |
'url' => 'https://plugins.matomo.org/FormAnalytics?wp=1',
|
246 |
'image' => '',
|
247 |
+
],
|
248 |
+
[
|
249 |
'name' => 'Video & Audio Analytics',
|
250 |
'description' => 'Grow your business with advanced video & audio analytics. Get powerful insights into how your audience watches your videos and listens to your audio.',
|
251 |
'price' => '79EUR / 89USD',
|
252 |
'url' => 'https://plugins.matomo.org/MediaAnalytics?wp=1',
|
253 |
'image' => '',
|
254 |
+
],
|
255 |
+
[
|
256 |
'name' => 'Users Flow',
|
257 |
'description' => 'Users Flow is a visual representation of the most popular paths your users take through your website & app which lets you understand your users needs.',
|
258 |
'price' => '39EUR / 39USD',
|
259 |
'url' => 'https://plugins.matomo.org/UsersFlow?wp=1',
|
260 |
'image' => '',
|
261 |
+
],
|
262 |
+
],
|
263 |
+
],
|
264 |
+
[
|
265 |
'title' => 'Most popular acquisition & SEO features',
|
266 |
'features' =>
|
267 |
+
[
|
268 |
+
[
|
269 |
'name' => 'Search Engine Keywords Performance',
|
270 |
'description' => 'All keywords searched by your users on search engines are now visible into your Referrers reports! The ultimate solution to \'Keyword not defined\'.',
|
271 |
'price' => '69EUR / 79USD',
|
272 |
'url' => 'https://plugins.matomo.org/SearchEngineKeywordsPerformance?wp=1',
|
273 |
'image' => '',
|
274 |
+
],
|
275 |
+
[
|
276 |
'name' => 'Advertising Conversion Export',
|
277 |
'description' => 'Provides an export of attributed goal conversions for usage in ad networks like Google Ads so you no longer need a conversion pixel.',
|
278 |
'price' => '79EUR / 89USD',
|
279 |
'url' => 'https://plugins.matomo.org/AdvertisingConversionExport?wp=1',
|
280 |
'image' => '',
|
281 |
+
],
|
282 |
+
[
|
283 |
'name' => 'Multi Attribution',
|
284 |
'description' => 'Get a clear understanding of how much credit each of your marketing channel is actually responsible for to shift your marketing efforts wisely.',
|
285 |
'price' => '39EUR / 39USD',
|
286 |
'url' => 'https://plugins.matomo.org/MultiChannelConversionAttribution?wp=1',
|
287 |
'image' => '',
|
288 |
+
],
|
289 |
+
],
|
290 |
+
],
|
291 |
+
[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
'title' => 'Other premium features',
|
293 |
'features' =>
|
294 |
+
[
|
295 |
+
[
|
296 |
'name' => 'Funnels',
|
297 |
'description' => 'Identify and understand where your visitors drop off to increase your conversions, sales and revenue with your existing traffic.',
|
298 |
'price' => '89EUR / 99USD',
|
299 |
'url' => 'https://plugins.matomo.org/Funnels?wp=1',
|
300 |
'image' => '',
|
301 |
+
],
|
302 |
+
[
|
303 |
'name' => 'Cohorts',
|
304 |
'description' => 'Track your retention efforts over time and keep your visitors engaged and coming back for more.',
|
305 |
'price' => '49EUR / 59USD',
|
306 |
'url' => 'https://plugins.matomo.org/Cohorts?wp=1',
|
307 |
'image' => '',
|
308 |
+
],
|
309 |
+
],
|
310 |
+
],
|
311 |
+
];
|
312 |
|
313 |
+
matomo_show_tables( $matomo_feature_sections );
|
314 |
|
315 |
?>
|
316 |
|
classes/WpMatomo/Admin/views/privacy_gdpr.php
CHANGED
@@ -11,81 +11,89 @@
|
|
11 |
|
12 |
use WpMatomo\Admin\Menu;
|
13 |
use WpMatomo\Admin\PrivacySettings;
|
|
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit;
|
17 |
}
|
18 |
-
/** @var
|
19 |
|
20 |
?>
|
21 |
|
22 |
<h2><?php esc_html_e( 'Matomo ensures the privacy of your users and analytics data! YOU keep control of your data.', 'matomo' ); ?></h2>
|
23 |
|
24 |
<blockquote
|
25 |
-
|
26 |
<p>
|
27 |
<?php esc_html_e( 'Matomo Analytics is privacy by design. All data collected is stored only within your own MySQL database, no other business (or Matomo team member) can access any of this information, and logs or report data will never be sent to other servers by Matomo', 'matomo' ); ?>
|
28 |
.
|
29 |
|
30 |
<?php
|
31 |
echo sprintf(
|
32 |
-
|
33 |
'<a href="https://matomo.org/security/" rel="noreferrer noopener">',
|
34 |
'</a>'
|
35 |
);
|
36 |
?>
|
37 |
</p>
|
38 |
-
<?php if ($matomo_settings->is_network_enabled() && is_network_admin()) { ?>
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
45 |
<?php } else { ?>
|
46 |
|
47 |
-
<h2>
|
48 |
-
|
49 |
-
</h2>
|
50 |
-
<p><?php esc_html_e( 'Although Matomo Analytics is a web analytics software that has a purpose to track user activity on your website, we take privacy very seriously.', 'matomo' ); ?></p>
|
51 |
-
<p><?php esc_html_e( 'Privacy is a fundamental right so by using Matomo you can rest assured you have 100% control over that data and can protect your user\'s privacy as it\'s on your own server.', 'matomo' ); ?></p>
|
52 |
|
53 |
-
<ul class="matomo-list">
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
</ul>
|
74 |
<?php } ?>
|
75 |
<h2>
|
76 |
<?php esc_html_e( 'Let users opt-out of tracking', 'matomo' ); ?>
|
77 |
</h2>
|
78 |
-
<p>
|
|
|
|
|
79 |
<?php
|
80 |
echo sprintf(
|
81 |
-
|
82 |
'<code>' . esc_html( PrivacySettings::EXAMPLE_MINIMAL ) . '</code>'
|
83 |
);
|
84 |
?>
|
85 |
-
|
86 |
<?php esc_html_e( 'You can use these short code options:', 'matomo' ); ?>
|
87 |
-
</p>
|
88 |
<ul class="matomo-list">
|
89 |
-
<li>language - eg de or
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
</ul>
|
91 |
-
<p><?php esc_html_e( 'Example', 'matomo' ); ?>: <code><?php echo esc_html( PrivacySettings::EXAMPLE_FULL ); ?></code></p>
|
11 |
|
12 |
use WpMatomo\Admin\Menu;
|
13 |
use WpMatomo\Admin\PrivacySettings;
|
14 |
+
use WpMatomo\Settings;
|
15 |
|
16 |
if ( ! defined( 'ABSPATH' ) ) {
|
17 |
exit;
|
18 |
}
|
19 |
+
/** @var Settings $matomo_settings */
|
20 |
|
21 |
?>
|
22 |
|
23 |
<h2><?php esc_html_e( 'Matomo ensures the privacy of your users and analytics data! YOU keep control of your data.', 'matomo' ); ?></h2>
|
24 |
|
25 |
<blockquote
|
26 |
+
class="matomo-blockquote"><?php esc_html_e( 'One of Matomo\'s guiding principles: respecting privacy', 'matomo' ); ?></blockquote>
|
27 |
<p>
|
28 |
<?php esc_html_e( 'Matomo Analytics is privacy by design. All data collected is stored only within your own MySQL database, no other business (or Matomo team member) can access any of this information, and logs or report data will never be sent to other servers by Matomo', 'matomo' ); ?>
|
29 |
.
|
30 |
|
31 |
<?php
|
32 |
echo sprintf(
|
33 |
+
esc_html__( 'The source code of the software is open-source so hundreds of people have reviewed it to ensure it is %1$ssecure%2$s and keeps your data private.', 'matomo' ),
|
34 |
'<a href="https://matomo.org/security/" rel="noreferrer noopener">',
|
35 |
'</a>'
|
36 |
);
|
37 |
?>
|
38 |
</p>
|
39 |
+
<?php if ( $matomo_settings->is_network_enabled() && is_network_admin() ) { ?>
|
40 |
+
<h2>Configure privacy settings</h2>
|
41 |
+
<p>
|
42 |
+
Currently, privacy settings have to be configured on a per blog basis.
|
43 |
+
IP addresses are anonmyised by default. Should you wish to change any privacy setting, please go to the Matomo
|
44 |
+
privacy settings within each blog.
|
45 |
+
We are hoping to improve this in the future.
|
46 |
+
</p>
|
47 |
<?php } else { ?>
|
48 |
|
49 |
+
<h2>
|
50 |
+
<?php esc_html_e( 'Ways Matomo protects the privacy of your users and customers', 'matomo' ); ?>
|
51 |
+
</h2>
|
52 |
+
<p><?php esc_html_e( 'Although Matomo Analytics is a web analytics software that has a purpose to track user activity on your website, we take privacy very seriously.', 'matomo' ); ?></p>
|
53 |
+
<p><?php esc_html_e( 'Privacy is a fundamental right so by using Matomo you can rest assured you have 100% control over that data and can protect your user\'s privacy as it\'s on your own server.', 'matomo' ); ?></p>
|
54 |
|
55 |
+
<ul class="matomo-list">
|
56 |
+
<li>
|
57 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ANONYMIZE_DATA ) ); ?>"><?php esc_html_e( 'Anonymise data and IP addresses', 'matomo' ); ?></a>
|
58 |
+
</li>
|
59 |
+
<li>
|
60 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_DATA_RETENTION ) ); ?>"><?php esc_html_e( 'Configure data retention', 'matomo' ); ?></a>
|
61 |
+
</li>
|
62 |
+
<li>
|
63 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_OPTOUT ) ); ?>"><?php esc_html_e( 'Matomo has an opt-out mechanism which lets users opt-out of web analytics tracking', 'matomo' ); ?></a>
|
64 |
+
(<?php esc_html_e( 'see below for the shortcode', 'matomo' ); ?>)
|
65 |
+
</li>
|
66 |
+
<li>
|
67 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ASK_CONSENT ) ); ?>"><?php esc_html_e( 'Asking for consent', 'matomo' ); ?></a>
|
68 |
+
</li>
|
69 |
+
<li>
|
70 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_GDPR_OVERVIEW ) ); ?>"><?php esc_html_e( 'GDPR overview', 'matomo' ); ?></a>
|
71 |
+
</li>
|
72 |
+
<li>
|
73 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_GDPR_TOOLS ) ); ?>"><?php esc_html_e( 'GDPR tools', 'matomo' ); ?></a>
|
74 |
+
</li>
|
75 |
+
</ul>
|
76 |
<?php } ?>
|
77 |
<h2>
|
78 |
<?php esc_html_e( 'Let users opt-out of tracking', 'matomo' ); ?>
|
79 |
</h2>
|
80 |
+
<p><?php esc_html_e( 'You have two options to embed the opt out iframe into your website:', 'matomo' ); ?></p>
|
81 |
+
<ul class="matomo-list">
|
82 |
+
<li>
|
83 |
<?php
|
84 |
echo sprintf(
|
85 |
+
esc_html__( 'Use the short code %1$s.', 'matomo' ),
|
86 |
'<code>' . esc_html( PrivacySettings::EXAMPLE_MINIMAL ) . '</code>'
|
87 |
);
|
88 |
?>
|
89 |
+
<br/>
|
90 |
<?php esc_html_e( 'You can use these short code options:', 'matomo' ); ?>
|
|
|
91 |
<ul class="matomo-list">
|
92 |
+
<li>language - eg de or
|
93 |
+
en. <?php esc_html_e( 'By default the language is detected automatically based on the user\'s browser', 'matomo' ); ?></li>
|
94 |
+
</ul>
|
95 |
+
|
96 |
+
<?php esc_html_e( 'Example', 'matomo' ); ?>: <code><?php echo esc_html( PrivacySettings::EXAMPLE_FULL ); ?></code>
|
97 |
+
</li>
|
98 |
+
<li><?php esc_html_e( 'Or you can add the "Matomo opt out" block directly to your page.', 'matomo' ); ?></li>
|
99 |
</ul>
|
|
classes/WpMatomo/Admin/views/settings.php
CHANGED
@@ -11,6 +11,7 @@ use WpMatomo\Admin\AdminSettings;
|
|
11 |
use WpMatomo\Admin\AdminSettingsInterface;
|
12 |
use WpMatomo\Admin\Menu;
|
13 |
use WpMatomo\Capabilities;
|
|
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit;
|
@@ -18,37 +19,39 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
18 |
/** @var AdminSettingsInterface[] $setting_tabs */
|
19 |
/** @var AdminSettingsInterface $content_tab */
|
20 |
/** @var string $active_tab */
|
21 |
-
/** @var
|
22 |
?>
|
23 |
<div class="wrap">
|
24 |
<div id="icon-plugins" class="icon32"></div>
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
<h2 class="nav-tab-wrapper">
|
37 |
<?php foreach ( $setting_tabs as $matomo_setting_slug => $matomo_setting_tab ) { ?>
|
38 |
-
<a href="<?php echo AdminSettings::make_url( $matomo_setting_slug ); ?>"
|
39 |
class="nav-tab <?php echo $active_tab === $matomo_setting_slug ? 'nav-tab-active' : ''; ?>"
|
40 |
><?php echo esc_html( $matomo_setting_tab->get_title() ); ?></a>
|
41 |
<?php } ?>
|
42 |
|
43 |
<?php
|
44 |
if ( current_user_can( Capabilities::KEY_SUPERUSER )
|
45 |
-
|
46 |
?>
|
47 |
-
<a href="<?php echo Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ADMIN ); ?>" class="nav-tab"
|
48 |
><?php esc_html_e( 'Matomo Admin', 'matomo' ); ?> <span class="dashicons-before dashicons-external"></span></a>
|
49 |
|
50 |
-
|
|
|
|
|
51 |
</h2>
|
52 |
|
53 |
-
<?php
|
54 |
</div>
|
11 |
use WpMatomo\Admin\AdminSettingsInterface;
|
12 |
use WpMatomo\Admin\Menu;
|
13 |
use WpMatomo\Capabilities;
|
14 |
+
use WpMatomo\Settings;
|
15 |
|
16 |
if ( ! defined( 'ABSPATH' ) ) {
|
17 |
exit;
|
19 |
/** @var AdminSettingsInterface[] $setting_tabs */
|
20 |
/** @var AdminSettingsInterface $content_tab */
|
21 |
/** @var string $active_tab */
|
22 |
+
/** @var Settings $matomo_settings */
|
23 |
?>
|
24 |
<div class="wrap">
|
25 |
<div id="icon-plugins" class="icon32"></div>
|
26 |
+
<h1><?php matomo_header_icon(); ?><?php esc_html_e( 'Settings', 'matomo' ); ?></h1>
|
27 |
+
<?php
|
28 |
+
if ( $matomo_settings->is_network_enabled() && is_network_admin() ) {
|
29 |
+
echo '<div class="notice notice-info is-dismissible"><br>You are running Matomo in network mode. This means below settings will be applied to all blogs in your network.<br><br></div>';
|
30 |
+
} elseif ( $matomo_settings->is_network_enabled() && ! is_network_admin() ) {
|
31 |
+
echo '<div class="notice notice-info is-dismissible"><br>';
|
32 |
+
esc_html_e( 'You are running Matomo in network mode.', 'matomo' );
|
33 |
+
echo ' ';
|
34 |
+
echo 'Below settings aren\'t applied for all blogs but have to be configured for each blog separately. We are hoping to improve this in the future. Any setting within the Matomo admin is configured on a per blog basis as well. Only you as a Matomo super user can see these settings.<br><br></div>';
|
35 |
+
}
|
36 |
+
?>
|
37 |
<h2 class="nav-tab-wrapper">
|
38 |
<?php foreach ( $setting_tabs as $matomo_setting_slug => $matomo_setting_tab ) { ?>
|
39 |
+
<a href="<?php echo esc_url( AdminSettings::make_url( $matomo_setting_slug ) ); ?>"
|
40 |
class="nav-tab <?php echo $active_tab === $matomo_setting_slug ? 'nav-tab-active' : ''; ?>"
|
41 |
><?php echo esc_html( $matomo_setting_tab->get_title() ); ?></a>
|
42 |
<?php } ?>
|
43 |
|
44 |
<?php
|
45 |
if ( current_user_can( Capabilities::KEY_SUPERUSER )
|
46 |
+
&& ! is_network_admin() ) {
|
47 |
?>
|
48 |
+
<a href="<?php echo esc_url( Menu::get_matomo_goto_url( Menu::REPORTING_GOTO_ADMIN ) ); ?>" class="nav-tab"
|
49 |
><?php esc_html_e( 'Matomo Admin', 'matomo' ); ?> <span class="dashicons-before dashicons-external"></span></a>
|
50 |
|
51 |
+
<?php
|
52 |
+
}
|
53 |
+
?>
|
54 |
</h2>
|
55 |
|
56 |
+
<?php $content_tab->show_settings(); ?>
|
57 |
</div>
|
classes/WpMatomo/Admin/views/settings_errors.php
CHANGED
@@ -6,13 +6,17 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
-
/**
|
|
|
|
|
|
|
|
|
10 |
if ( ! defined( 'ABSPATH' ) ) {
|
11 |
exit;
|
12 |
}
|
13 |
?>
|
14 |
<div class="updated error">
|
15 |
-
<?php foreach ( $
|
16 |
-
|
17 |
<?php endforeach; ?>
|
18 |
</div>
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
/**
|
10 |
+
* phpcs considers all of our variables as global and want them prefixed with matomo
|
11 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
12 |
+
*/
|
13 |
+
/** @var string[] $settings_errors */
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit;
|
16 |
}
|
17 |
?>
|
18 |
<div class="updated error">
|
19 |
+
<?php foreach ( $settings_errors as $setting_error ) : ?>
|
20 |
+
<p><?php echo esc_html( $setting_error ); ?></p>
|
21 |
<?php endforeach; ?>
|
22 |
</div>
|
classes/WpMatomo/Admin/views/summary.php
CHANGED
@@ -6,8 +6,13 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
-
|
|
|
|
|
|
|
|
|
10 |
use WpMatomo\Admin\Menu;
|
|
|
11 |
use WpMatomo\Report\Dates;
|
12 |
|
13 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -23,32 +28,34 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
23 |
/** @var bool $matomo_pinned */
|
24 |
/** @var bool $is_tracking */
|
25 |
/** @var bool $matomo_is_version_pre55 */
|
26 |
-
/** @var
|
27 |
global $wp;
|
28 |
|
29 |
-
$matomo_dashboard_nonce = wp_create_nonce(
|
30 |
?>
|
31 |
<?php
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
?>
|
39 |
<?php if ( ! $is_tracking ) { ?>
|
40 |
-
<div class="notice notice-warning"
|
|
|
|
|
41 |
<?php } ?>
|
42 |
<div class="wrap">
|
43 |
<div id="icon-plugins" class="icon32"></div>
|
44 |
-
<h1><?php matomo_header_icon();
|
45 |
<?php
|
46 |
if ( Dates::TODAY === $report_date ) {
|
47 |
echo '<div class="notice notice-info" style="padding:8px;">' . esc_html__( 'Reports for today are only refreshed approximately every hour through the WordPress cronjob.', 'matomo' ) . '</div>';
|
48 |
}
|
49 |
?>
|
50 |
<p><?php esc_html_e( 'Looking for all reports and advanced features like segmentation, real time reports, and more?', 'matomo' ); ?>
|
51 |
-
<a href="<?php echo
|
52 |
><?php esc_html_e( 'View full reporting', 'matomo' ); ?></a>
|
53 |
<br/><br/>
|
54 |
<?php esc_html_e( 'Change date:', 'matomo' ); ?>
|
@@ -58,16 +65,16 @@ $matomo_dashboard_nonce = wp_create_nonce(\WpMatomo\Admin\Summary::NONCE_DASHBOA
|
|
58 |
if ( $report_date === $matomo_report_date_key ) {
|
59 |
$matomo_button_class = 'button-primary';
|
60 |
}
|
61 |
-
echo '<a href="' . esc_url( add_query_arg(
|
62 |
}
|
63 |
?>
|
64 |
|
65 |
<div id="dashboard-widgets" class="metabox-holder has-right-sidebar matomo-dashboard-container">
|
66 |
<?php
|
67 |
-
$matomo_columns =
|
68 |
foreach ( $matomo_columns as $matomo_column_index => $matomo_column_modulo ) {
|
69 |
?>
|
70 |
-
<div id="postbox-container-<?php echo( $matomo_column_index + 1 ); ?>" class="postbox-container">
|
71 |
<div id="normal-sortables" class="meta-box-sortables ui-sortable">
|
72 |
<?php
|
73 |
foreach ( $reports_to_show as $matomo_index => $matomo_report_meta ) {
|
@@ -77,58 +84,84 @@ $matomo_dashboard_nonce = wp_create_nonce(\WpMatomo\Admin\Summary::NONCE_DASHBOA
|
|
77 |
$shortcode = sprintf( '[matomo_report unique_id=%s report_date=%s limit=10]', $matomo_report_meta['uniqueId'], $report_date );
|
78 |
?>
|
79 |
<div class="postbox ">
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
<?php
|
92 |
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
101 |
?>
|
102 |
-
" style="color: inherit;text-decoration: none;" target="_blank"
|
103 |
-
|
104 |
-
|
|
|
|
|
105 |
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
112 |
<?php
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
|
|
|
|
|
|
|
|
|
|
119 |
?>
|
120 |
-
" style="color: inherit;text-decoration: none
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
-
|
|
|
124 |
<div>
|
125 |
<?php echo do_shortcode( $shortcode ); ?>
|
126 |
</div>
|
127 |
</div>
|
128 |
-
|
|
|
|
|
129 |
</div>
|
130 |
</div>
|
131 |
-
|
|
|
|
|
132 |
</div>
|
133 |
|
134 |
<p style="clear:both;">
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
/**
|
10 |
+
* phpcs considers all of our variables as global and want them prefixed with matomo
|
11 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
12 |
+
*/
|
13 |
+
use WpMatomo\Admin\Dashboard;
|
14 |
use WpMatomo\Admin\Menu;
|
15 |
+
use WpMatomo\Admin\Summary;
|
16 |
use WpMatomo\Report\Dates;
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
28 |
/** @var bool $matomo_pinned */
|
29 |
/** @var bool $is_tracking */
|
30 |
/** @var bool $matomo_is_version_pre55 */
|
31 |
+
/** @var Dashboard $matomo_dashboard */
|
32 |
global $wp;
|
33 |
|
34 |
+
$matomo_dashboard_nonce = wp_create_nonce( Summary::NONCE_DASHBOARD );
|
35 |
?>
|
36 |
<?php
|
37 |
+
if ( $matomo_pinned ) {
|
38 |
+
echo '<div class="notice notice-success"><p>' . esc_html__( 'Dashboard updated.', 'matomo' ) . '</p></div>';
|
39 |
+
}
|
40 |
+
if ( $matomo_is_version_pre55 ) {
|
41 |
+
echo '<style type="text/css">.handle-actions { position: absolute; right: 0;top: 0;}</style>';
|
42 |
+
}
|
43 |
?>
|
44 |
<?php if ( ! $is_tracking ) { ?>
|
45 |
+
<div class="notice notice-warning">
|
46 |
+
<p><?php esc_html_e( 'Matomo Tracking is not enabled. If you have added the Matomo tracking code in a different way, for example using a consent plugin, then you can ignore this message.', 'matomo' ); ?></p>
|
47 |
+
</div>
|
48 |
<?php } ?>
|
49 |
<div class="wrap">
|
50 |
<div id="icon-plugins" class="icon32"></div>
|
51 |
+
<h1><?php matomo_header_icon(); ?><?php esc_html_e( 'Summary', 'matomo' ); ?></h1>
|
52 |
<?php
|
53 |
if ( Dates::TODAY === $report_date ) {
|
54 |
echo '<div class="notice notice-info" style="padding:8px;">' . esc_html__( 'Reports for today are only refreshed approximately every hour through the WordPress cronjob.', 'matomo' ) . '</div>';
|
55 |
}
|
56 |
?>
|
57 |
<p><?php esc_html_e( 'Looking for all reports and advanced features like segmentation, real time reports, and more?', 'matomo' ); ?>
|
58 |
+
<a href="<?php echo esc_url( add_query_arg( [ 'report_date' => $report_date ], menu_page_url( Menu::SLUG_REPORTING, false ) ) ); ?>"
|
59 |
><?php esc_html_e( 'View full reporting', 'matomo' ); ?></a>
|
60 |
<br/><br/>
|
61 |
<?php esc_html_e( 'Change date:', 'matomo' ); ?>
|
65 |
if ( $report_date === $matomo_report_date_key ) {
|
66 |
$matomo_button_class = 'button-primary';
|
67 |
}
|
68 |
+
echo '<a href="' . esc_url( add_query_arg( [ 'report_date' => $matomo_report_date_key ], menu_page_url( Menu::SLUG_REPORT_SUMMARY, false ) ) ) . '" class="' . esc_attr( $matomo_button_class ) . '">' . esc_html( $matomo_report_name ) . '</a> ';
|
69 |
}
|
70 |
?>
|
71 |
|
72 |
<div id="dashboard-widgets" class="metabox-holder has-right-sidebar matomo-dashboard-container">
|
73 |
<?php
|
74 |
+
$matomo_columns = [ 1, 0 ];
|
75 |
foreach ( $matomo_columns as $matomo_column_index => $matomo_column_modulo ) {
|
76 |
?>
|
77 |
+
<div id="postbox-container-<?php echo ( esc_html( $matomo_column_index + 1 ) ); ?>" class="postbox-container">
|
78 |
<div id="normal-sortables" class="meta-box-sortables ui-sortable">
|
79 |
<?php
|
80 |
foreach ( $reports_to_show as $matomo_index => $matomo_report_meta ) {
|
84 |
$shortcode = sprintf( '[matomo_report unique_id=%s report_date=%s limit=10]', $matomo_report_meta['uniqueId'], $report_date );
|
85 |
?>
|
86 |
<div class="postbox ">
|
87 |
+
<div class="postbox-header">
|
88 |
+
<h2 class="hndle ui-sortable-handle"
|
89 |
+
style="cursor: help;"
|
90 |
+
title="<?php echo ! empty( $matomo_report_meta['documentation'] ) ? ( esc_html( wp_strip_all_tags( $matomo_report_meta['documentation'] ) . ' ' ) ) : null; ?><?php esc_html_e( 'You can embed this report on any page using the shortcode:', 'matomo' ); ?> <?php echo esc_attr( $shortcode ); ?>">
|
91 |
+
<?php echo esc_html( $matomo_report_meta['name'] ); ?></h2>
|
92 |
+
<div class="handle-actions hide-if-no-js">
|
93 |
+
<?php if ( ! empty( $matomo_report_meta['page'] ) ) { ?>
|
94 |
+
<button type="button" class="handlediv" aria-expanded="true"
|
95 |
+
title="<?php esc_html_e( 'Click to view the report in detail', 'matomo' ); ?>">
|
96 |
+
<a
|
97 |
+
href="
|
98 |
<?php
|
99 |
|
100 |
+
echo esc_url(
|
101 |
+
Menu::get_matomo_reporting_url(
|
102 |
+
$matomo_report_meta['page']['category'],
|
103 |
+
$matomo_report_meta['page']['subcategory'],
|
104 |
+
[
|
105 |
+
'period' => $report_period_selected,
|
106 |
+
'date' => $report_date_selected,
|
107 |
+
]
|
108 |
+
)
|
109 |
+
);
|
110 |
?>
|
111 |
+
" style="color: inherit;text-decoration: none;" target="_blank"
|
112 |
+
rel="noreferrer noopener"
|
113 |
+
class="dashicons-before dashicons-external" aria-hidden="true"></a>
|
114 |
+
</button>
|
115 |
+
<?php } ?>
|
116 |
|
117 |
+
<?php $matomo_is_dashboard_widget = $matomo_dashboard->has_widget( $matomo_report_meta['uniqueId'], $report_date ); ?>
|
118 |
+
<?php // phpcs:ignore Squiz.PHP.EmbeddedPhp.ContentBeforeOpen ?>
|
119 |
+
<button type="button" class="handlediv" aria-expanded="true" title="<?php
|
120 |
+
if ( $matomo_is_dashboard_widget ) {
|
121 |
+
esc_html_e( 'Click to remove this report from the WordPress admin dashboard', 'matomo' );
|
122 |
+
} else {
|
123 |
+
esc_html_e( 'Click to add this report to the WordPress admin dashboard', 'matomo' );
|
124 |
+
}
|
125 |
+
// phpcs:ignore Squiz.PHP.EmbeddedPhp.ContentAfterEnd
|
126 |
+
?>"><a
|
127 |
+
href="
|
128 |
<?php
|
129 |
+
echo esc_url(
|
130 |
+
add_query_arg(
|
131 |
+
[
|
132 |
+
'pin' => true,
|
133 |
+
'_wpnonce' => $matomo_dashboard_nonce,
|
134 |
+
'report_uniqueid' => $matomo_report_meta['uniqueId'],
|
135 |
+
'report_date' => $report_date,
|
136 |
+
],
|
137 |
+
menu_page_url( Menu::SLUG_REPORT_SUMMARY, false )
|
138 |
+
)
|
139 |
+
);
|
140 |
?>
|
141 |
+
" style="color: inherit;text-decoration: none;
|
142 |
+
<?php
|
143 |
+
if ( $matomo_is_dashboard_widget ) {
|
144 |
+
echo 'opacity: 0.4 !important';
|
145 |
+
}
|
146 |
+
?>
|
147 |
+
"
|
148 |
+
class="dashicons-before dashicons-admin-post" aria-hidden="true"></a>
|
149 |
+
</button>
|
150 |
|
151 |
+
</div>
|
152 |
+
</div>
|
153 |
<div>
|
154 |
<?php echo do_shortcode( $shortcode ); ?>
|
155 |
</div>
|
156 |
</div>
|
157 |
+
<?php
|
158 |
+
}
|
159 |
+
?>
|
160 |
</div>
|
161 |
</div>
|
162 |
+
<?php
|
163 |
+
}
|
164 |
+
?>
|
165 |
</div>
|
166 |
|
167 |
<p style="clear:both;">
|
classes/WpMatomo/Admin/views/systemreport.php
CHANGED
@@ -6,7 +6,10 @@
|
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
-
|
|
|
|
|
|
|
10 |
if ( ! defined( 'ABSPATH' ) ) {
|
11 |
exit;
|
12 |
}
|
@@ -25,11 +28,11 @@ use WpMatomo\Admin\SystemReport;
|
|
25 |
if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
26 |
function matomo_format_value_text( $value ) {
|
27 |
if ( is_string( $value ) && ! empty( $value ) ) {
|
28 |
-
$matomo_format =
|
29 |
'<br />' => ' ',
|
30 |
'<br/>' => ' ',
|
31 |
'<br>' => ' ',
|
32 |
-
|
33 |
foreach ( $matomo_format as $search => $replace ) {
|
34 |
$value = str_replace( $search, $replace, $value );
|
35 |
}
|
@@ -44,9 +47,9 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
44 |
<?php
|
45 |
if ( $matomo_has_warning_and_no_errors ) {
|
46 |
?>
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
<?php
|
51 |
}
|
52 |
?>
|
@@ -56,12 +59,12 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
56 |
</div>
|
57 |
<?php } ?>
|
58 |
<div id="icon-plugins" class="icon32"></div>
|
59 |
-
|
60 |
|
61 |
<h2 class="nav-tab-wrapper">
|
62 |
-
<a href="?page=<?php echo Menu::SLUG_SYSTEM_REPORT; ?>"
|
63 |
class="nav-tab <?php echo empty( $matomo_active_tab ) ? 'nav-tab-active' : ''; ?>"> System report</a>
|
64 |
-
<a href="?page=<?php echo Menu::SLUG_SYSTEM_REPORT; ?>&tab=troubleshooting"
|
65 |
class="nav-tab <?php echo 'troubleshooting' === $matomo_active_tab ? 'nav-tab-active' : ''; ?>">Troubleshooting</a>
|
66 |
</h2>
|
67 |
|
@@ -78,7 +81,7 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
78 |
id="matomo_system_report_info">
|
79 |
<?php
|
80 |
foreach ( $matomo_tables as $matomo_table ) {
|
81 |
-
if (empty($matomo_table['rows'])) {
|
82 |
continue;
|
83 |
}
|
84 |
echo '# ' . esc_html( $matomo_table['title'] ) . "\n";
|
@@ -111,9 +114,9 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
111 |
|
112 |
<?php
|
113 |
foreach ( $matomo_tables as $matomo_table ) {
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
echo '<h2>' . esc_html( $matomo_table['title'] ) . "</h2><table class='widefat'><thead></thead><tbody>";
|
118 |
foreach ( $matomo_table['rows'] as $matomo_row ) {
|
119 |
if ( ! empty( $matomo_row['section'] ) ) {
|
@@ -136,7 +139,7 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
136 |
echo "<td width='30%'>" . esc_html( $matomo_row['name'] ) . '</td>';
|
137 |
echo "<td width='" . ( ! empty( $matomo_table['has_comments'] ) ? 20 : 70 ) . "%'>" . esc_html( $matomo_value ) . '</td>';
|
138 |
if ( ! empty( $matomo_table['has_comments'] ) ) {
|
139 |
-
$matomo_replaced_elements =
|
140 |
'<code>' => '__#CODEBACKUP#__',
|
141 |
'</code>' => '__##CODEBACKUP##__',
|
142 |
'<pre style="overflow-x: scroll;max-width: 600px;">' => '__#PREBACKUP#__',
|
@@ -144,10 +147,11 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
144 |
'<br/>' => '__#BRBACKUP#__',
|
145 |
'<br />' => '__#BRBACKUP#__',
|
146 |
'<br>' => '__#BRBACKUP#__',
|
147 |
-
|
148 |
$matomo_comment = isset( $matomo_row['comment'] ) ? $matomo_row['comment'] : '';
|
149 |
$matomo_replaced = str_replace( array_keys( $matomo_replaced_elements ), array_values( $matomo_replaced_elements ), $matomo_comment );
|
150 |
$matomo_escaped = esc_html( $matomo_replaced );
|
|
|
151 |
echo "<td width='50%' class='matomo-systemreport-comment'>" . str_replace( array_values( $matomo_replaced_elements ), array_keys( $matomo_replaced_elements ), $matomo_escaped ) . '</td>';
|
152 |
}
|
153 |
|
@@ -163,51 +167,54 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
163 |
<form method="post">
|
164 |
<?php wp_nonce_field( SystemReport::NONCE_NAME ); ?>
|
165 |
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
<?php if (!empty($matomo_has_exception_logs)) { ?>
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
<?php } ?>
|
183 |
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
<?php if ( ! $settings->is_network_enabled() || ! is_network_admin() ) { ?>
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
|
|
|
|
|
|
202 |
<?php } ?>
|
203 |
<?php if ( $settings->is_network_enabled() ) { ?>
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_SYNC_ALL_SITES ); ?>" type="submit"
|
210 |
-
|
211 |
class='button-primary'
|
212 |
value="<?php esc_html_e( 'Sync all sites (blogs)', 'matomo' ); ?>">
|
213 |
<?php } ?>
|
@@ -219,11 +226,25 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
219 |
?>
|
220 |
<h3><?php esc_html_e( 'Popular Troubleshooting FAQs', 'matomo' ); ?></h3>
|
221 |
<ul class="matomo-list">
|
222 |
-
<li
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
</ul>
|
228 |
<?php include 'info_bug_report.php'; ?>
|
229 |
<h4><?php esc_html_e( 'Before you create an issue', 'matomo' ); ?></h4>
|
@@ -235,15 +256,15 @@ if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
|
235 |
</p>
|
236 |
<h3><?php esc_html_e( 'Having performance issues?', 'matomo' ); ?></h3>
|
237 |
<p>
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
</p>
|
248 |
<?php include 'info_high_traffic.php'; ?>
|
249 |
<?php } ?>
|
6 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
7 |
* @package matomo
|
8 |
*/
|
9 |
+
/**
|
10 |
+
* phpcs considers all of our variables as global and want them prefixed with matomo
|
11 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
12 |
+
*/
|
13 |
if ( ! defined( 'ABSPATH' ) ) {
|
14 |
exit;
|
15 |
}
|
28 |
if ( ! function_exists( 'matomo_format_value_text' ) ) {
|
29 |
function matomo_format_value_text( $value ) {
|
30 |
if ( is_string( $value ) && ! empty( $value ) ) {
|
31 |
+
$matomo_format = [
|
32 |
'<br />' => ' ',
|
33 |
'<br/>' => ' ',
|
34 |
'<br>' => ' ',
|
35 |
+
];
|
36 |
foreach ( $matomo_format as $search => $replace ) {
|
37 |
$value = str_replace( $search, $replace, $value );
|
38 |
}
|
47 |
<?php
|
48 |
if ( $matomo_has_warning_and_no_errors ) {
|
49 |
?>
|
50 |
+
<div class="notice notice-warning">
|
51 |
+
<p><?php esc_html_e( 'There are some issues with your system. Matomo will run, but you might experience some minor problems. See below for more information.', 'matomo' ); ?></p>
|
52 |
+
</div>
|
53 |
<?php
|
54 |
}
|
55 |
?>
|
59 |
</div>
|
60 |
<?php } ?>
|
61 |
<div id="icon-plugins" class="icon32"></div>
|
62 |
+
<h1><?php matomo_header_icon(); ?><?php esc_html_e( 'Diagnostics', 'matomo' ); ?></h1>
|
63 |
|
64 |
<h2 class="nav-tab-wrapper">
|
65 |
+
<a href="?page=<?php echo esc_attr( Menu::SLUG_SYSTEM_REPORT ); ?>"
|
66 |
class="nav-tab <?php echo empty( $matomo_active_tab ) ? 'nav-tab-active' : ''; ?>"> System report</a>
|
67 |
+
<a href="?page=<?php echo esc_attr( Menu::SLUG_SYSTEM_REPORT ); ?>&tab=troubleshooting"
|
68 |
class="nav-tab <?php echo 'troubleshooting' === $matomo_active_tab ? 'nav-tab-active' : ''; ?>">Troubleshooting</a>
|
69 |
</h2>
|
70 |
|
81 |
id="matomo_system_report_info">
|
82 |
<?php
|
83 |
foreach ( $matomo_tables as $matomo_table ) {
|
84 |
+
if ( empty( $matomo_table['rows'] ) ) {
|
85 |
continue;
|
86 |
}
|
87 |
echo '# ' . esc_html( $matomo_table['title'] ) . "\n";
|
114 |
|
115 |
<?php
|
116 |
foreach ( $matomo_tables as $matomo_table ) {
|
117 |
+
if ( empty( $matomo_table['rows'] ) ) {
|
118 |
+
continue;
|
119 |
+
}
|
120 |
echo '<h2>' . esc_html( $matomo_table['title'] ) . "</h2><table class='widefat'><thead></thead><tbody>";
|
121 |
foreach ( $matomo_table['rows'] as $matomo_row ) {
|
122 |
if ( ! empty( $matomo_row['section'] ) ) {
|
139 |
echo "<td width='30%'>" . esc_html( $matomo_row['name'] ) . '</td>';
|
140 |
echo "<td width='" . ( ! empty( $matomo_table['has_comments'] ) ? 20 : 70 ) . "%'>" . esc_html( $matomo_value ) . '</td>';
|
141 |
if ( ! empty( $matomo_table['has_comments'] ) ) {
|
142 |
+
$matomo_replaced_elements = [
|
143 |
'<code>' => '__#CODEBACKUP#__',
|
144 |
'</code>' => '__##CODEBACKUP##__',
|
145 |
'<pre style="overflow-x: scroll;max-width: 600px;">' => '__#PREBACKUP#__',
|
147 |
'<br/>' => '__#BRBACKUP#__',
|
148 |
'<br />' => '__#BRBACKUP#__',
|
149 |
'<br>' => '__#BRBACKUP#__',
|
150 |
+
];
|
151 |
$matomo_comment = isset( $matomo_row['comment'] ) ? $matomo_row['comment'] : '';
|
152 |
$matomo_replaced = str_replace( array_keys( $matomo_replaced_elements ), array_values( $matomo_replaced_elements ), $matomo_comment );
|
153 |
$matomo_escaped = esc_html( $matomo_replaced );
|
154 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
155 |
echo "<td width='50%' class='matomo-systemreport-comment'>" . str_replace( array_values( $matomo_replaced_elements ), array_keys( $matomo_replaced_elements ), $matomo_escaped ) . '</td>';
|
156 |
}
|
157 |
|
167 |
<form method="post">
|
168 |
<?php wp_nonce_field( SystemReport::NONCE_NAME ); ?>
|
169 |
|
170 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_ARCHIVE_NOW ); ?>" type="submit"
|
171 |
+
class='button-primary'
|
172 |
+
title="<?php esc_attr_e( 'If reports show no data even though they should, you may try to see if report generation works when manually triggering the report generation.', 'matomo' ); ?>"
|
173 |
+
value="<?php esc_html_e( 'Archive reports', 'matomo' ); ?>">
|
174 |
+
<br/><br/>
|
175 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_CLEAR_MATOMO_CACHE ); ?>" type="submit"
|
176 |
+
class='button-primary'
|
177 |
+
title="<?php esc_attr_e( 'Will reset / empty the Matomo cache which can be helpful if something is not working as expected for example after an update.', 'matomo' ); ?>"
|
178 |
+
value="<?php esc_html_e( 'Clear Matomo cache', 'matomo' ); ?>">
|
179 |
+
<br/><br/>
|
180 |
+
<?php if ( ! empty( $matomo_has_exception_logs ) ) { ?>
|
181 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_CLEAR_LOGS ); ?>" type="submit"
|
182 |
+
class='button-primary'
|
183 |
+
title="<?php esc_attr_e( 'Removes all stored Matomo logs that are shown in the system report', 'matomo' ); ?>"
|
184 |
+
value="<?php esc_html_e( 'Clear system report logs', 'matomo' ); ?>">
|
185 |
+
<br/><br/>
|
186 |
<?php } ?>
|
187 |
|
188 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_UPDATE_GEOIP_DB ); ?>" type="submit"
|
189 |
+
class='button-primary'
|
190 |
+
title="<?php esc_attr_e( 'Updates the geolocation database which is used to detect the location (city/region/country) of visitors. This task is performed automatically. If the geolocation DB is not loaded or updated, you may need to trigger it manually to find the error which is causing it.', 'matomo' ); ?>"
|
191 |
+
value="<?php esc_html_e( 'Install/Update Geo-IP DB', 'matomo' ); ?>">
|
192 |
+
<br/><br/>
|
193 |
+
|
194 |
<?php if ( ! $settings->is_network_enabled() || ! is_network_admin() ) { ?>
|
195 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_SYNC_USERS ); ?>" type="submit"
|
196 |
+
class='button-primary'
|
197 |
+
title="<?php esc_attr_e( 'Users are synced automatically. If for some reason a user cannot access Matomo pages even though the user has the permission, then triggering a manual sync may help to fix this issue immediately or it may show which error prevents the automatic syncing.', 'matomo' ); ?>"
|
198 |
+
value="<?php esc_html_e( 'Sync users', 'matomo' ); ?>">
|
199 |
+
<br/><br/>
|
200 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_SYNC_SITE ); ?>" type="submit"
|
201 |
+
class='button-primary'
|
202 |
+
title="<?php esc_attr_e( 'Sites / blogs are synced automatically. If for some reason Matomo is not showing up for a specific blog, then triggering a manual sync may help to fix this issue immediately or it may show which error prevents the automatic syncing.', 'matomo' ); ?>"
|
203 |
+
value="<?php esc_html_e( 'Sync site (blog)', 'matomo' ); ?>">
|
204 |
+
<br/><br/>
|
205 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_RUN_UPDATER ); ?>" type="submit"
|
206 |
+
class='button-primary'
|
207 |
+
title="<?php esc_attr_e( 'Force trigger a Matomo update in case it failed error', 'matomo' ); ?>"
|
208 |
+
value="<?php esc_html_e( 'Run Updater', 'matomo' ); ?>">
|
209 |
<?php } ?>
|
210 |
<?php if ( $settings->is_network_enabled() ) { ?>
|
211 |
+
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_SYNC_ALL_USERS ); ?>" type="submit"
|
212 |
+
class='button-primary'
|
213 |
+
title="<?php esc_attr_e( 'Users are synced automatically. If for some reason a user cannot access Matomo pages even though the user has the permission, then triggering a manual sync may help to fix this issue immediately or it may show which error prevents the automatic syncing.', 'matomo' ); ?>"
|
214 |
+
value="<?php esc_html_e( 'Sync all users across sites / blogs', 'matomo' ); ?>">
|
215 |
+
<br/><br/>
|
216 |
<input name="<?php echo esc_attr( SystemReport::TROUBLESHOOT_SYNC_ALL_SITES ); ?>" type="submit"
|
217 |
+
title="<?php esc_attr_e( 'Sites / blogs are synced automatically. If for some reason Matomo is not showing up for a specific blog, then triggering a manual sync may help to fix this issue immediately or it may show which error prevents the automatic syncing.', 'matomo' ); ?>"
|
218 |
class='button-primary'
|
219 |
value="<?php esc_html_e( 'Sync all sites (blogs)', 'matomo' ); ?>">
|
220 |
<?php } ?>
|
226 |
?>
|
227 |
<h3><?php esc_html_e( 'Popular Troubleshooting FAQs', 'matomo' ); ?></h3>
|
228 |
<ul class="matomo-list">
|
229 |
+
<li>
|
230 |
+
<a href="https://matomo.org/faq/wordpress/matomo-for-wordpress-is-not-showing-any-statistics-not-archiving-how-do-i-fix-it/"
|
231 |
+
target="_blank"
|
232 |
+
rel="noreferrer noopener"><?php esc_html_e( 'Matomo is not showing any statistics / reports, how do I fix it?', 'matomo' ); ?></a>
|
233 |
+
</li>
|
234 |
+
<li><a href="https://matomo.org/faq/wordpress/i-cannot-open-backend-page-how-do-i-troubleshoot-it/"
|
235 |
+
target="_blank"
|
236 |
+
rel="noreferrer noopener"><?php esc_html_e( 'I cannot open the Matomo Reporting, Admin, or Tag Manager page, how do I troubleshoot it?', 'matomo' ); ?></a>
|
237 |
+
</li>
|
238 |
+
<li><a href="https://matomo.org/faq/wordpress/i-have-a-problem-how-do-i-troubleshoot-and-enable-wp_debug/"
|
239 |
+
target="_blank"
|
240 |
+
rel="noreferrer noopener"><?php esc_html_e( 'I have an issue with the plugin, how do I troubleshoot and enable debug mode?', 'matomo' ); ?></a>
|
241 |
+
</li>
|
242 |
+
<li><a href="https://matomo.org/faq/wordpress/how-do-i-manually-delete-all-matomo-for-wordpress-data/"
|
243 |
+
target="_blank"
|
244 |
+
rel="noreferrer noopener"><?php esc_html_e( 'How do I manually delete or reset all Matomo for WordPress data?', 'matomo' ); ?></a>
|
245 |
+
</li>
|
246 |
+
<li><a href="https://matomo.org/faq/wordpress/" target="_blank"
|
247 |
+
rel="noreferrer noopener"><?php esc_html_e( 'View all FAQs', 'matomo' ); ?></a></li>
|
248 |
</ul>
|
249 |
<?php include 'info_bug_report.php'; ?>
|
250 |
<h4><?php esc_html_e( 'Before you create an issue', 'matomo' ); ?></h4>
|
256 |
</p>
|
257 |
<h3><?php esc_html_e( 'Having performance issues?', 'matomo' ); ?></h3>
|
258 |
<p>
|
259 |
+
<?php
|
260 |
+
echo sprintf(
|
261 |
+
esc_html__( 'You may want to disable %1$s in your %2$s and set up an actual cronjob and %3$scheck out our recommended server sizing%4$s.', 'matomo' ),
|
262 |
+
'<code>DISABLE_WP_CRON</code>',
|
263 |
+
'<code>wp-config.php</code>',
|
264 |
+
'<a target="_blank" rel="noreferrer noopener" href="https://matomo.org/docs/requirements/#recommended-servers-sizing-cpu-ram-disks">',
|
265 |
+
'</a>'
|
266 |
+
);
|
267 |
+
?>
|
268 |
</p>
|
269 |
<?php include 'info_high_traffic.php'; ?>
|
270 |
<?php } ?>
|
classes/WpMatomo/Admin/views/tracking.php
CHANGED
@@ -10,7 +10,9 @@
|
|
10 |
* https://github.com/braekling/WP-Matomo
|
11 |
*
|
12 |
*/
|
13 |
-
|
|
|
|
|
14 |
use WpMatomo\Admin\TrackingSettings;
|
15 |
use WpMatomo\Paths;
|
16 |
|
@@ -23,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
23 |
/** @var array $containers */
|
24 |
/** @var array $track_modes */
|
25 |
/** @var array $matomo_currencies */
|
26 |
-
/** @var string[] $
|
27 |
/** @var array $cookie_consent_modes */
|
28 |
|
29 |
$matomo_form = new \WpMatomo\Admin\TrackingSettings\Forms( $settings );
|
@@ -34,18 +36,18 @@ $matomo_paths = new Paths();
|
|
34 |
if ( $was_updated ) {
|
35 |
include 'update_notice_clear_cache.php';
|
36 |
}
|
37 |
-
if ( count( $
|
38 |
-
|
39 |
}
|
40 |
|
41 |
?>
|
42 |
<form method="post">
|
43 |
<?php wp_nonce_field( TrackingSettings::NONCE_NAME ); ?>
|
44 |
<p>
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
<table class="matomo-tracking-form widefat">
|
50 |
<tbody>
|
51 |
|
@@ -56,22 +58,22 @@ if ( count( $errors ) ) {
|
|
56 |
$matomo_is_not_generated_tracking = $matomo_is_not_tracking || $settings->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_MANUALLY;
|
57 |
$matomo_full_generated_tracking_group = 'matomo-track-option matomo-track-option-default ';
|
58 |
|
59 |
-
$matomo_description = sprintf( '%s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s', esc_html__( 'You can choose between four tracking code modes:', 'matomo' ), esc_html__( 'Disabled', 'matomo' ), esc_html__( 'matomo will not add the tracking code. Use this, if you want to add the tracking code to your template files or you use another plugin to add the tracking code.', 'matomo' ), esc_html__( 'Default tracking', 'matomo' ), esc_html__( 'matomo will use Matomo\'s standard tracking code.', 'matomo' ) . ' ' .esc_html__('This mode is recommended for most use cases.', 'matomo'), esc_html__( 'Enter manually', 'matomo' ), esc_html__( 'Enter your own tracking code manually. You can choose one of the prior options, pre-configure your tracking code and switch to manually editing at last.', 'matomo' ) . ( $settings->is_network_enabled() ? ' ' . esc_html__( 'Use the placeholder {ID} to add the Matomo site ID.', 'matomo' ) : '' ), esc_html__( 'Tag Manager', 'matomo' ), esc_html__( 'If you have created containers in the Tag Manager, you can select one of them and it will embed the code for the container automatically.', 'matomo' ) );
|
60 |
$matomo_form->show_select( 'track_mode', esc_html__( 'Add tracking code', 'matomo' ), $track_modes, $matomo_description, 'jQuery(\'tr.matomo-track-option\').addClass(\'hidden\'); jQuery(\'tr.matomo-track-option-\' + jQuery(\'#track_mode\').val()).removeClass(\'hidden\'); jQuery(\'#tracking_code, #noscript_code\').prop(\'readonly\', jQuery(\'#track_mode\').val() != \'manually\');' );
|
61 |
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
|
67 |
if ( ! empty( $containers ) ) {
|
68 |
echo '<tr class="matomo-track-option matomo-track-option-tagmanager ' . ( $matomo_is_not_tracking ? ' hidden' : '' ) . '">';
|
69 |
echo '<th scope="row"><label for="tagmanger_container_ids">' . esc_html__( 'Add these Tag Manager containers', 'matomo' ) . '</label>:</th><td>';
|
70 |
$selected_container_ids = $settings->get_global_option( 'tagmanger_container_ids' );
|
71 |
foreach ( $containers as $container_id => $container_name ) {
|
72 |
-
echo '<input type="checkbox" ' . ( isset( $selected_container_ids [ $container_id ] ) && $selected_container_ids [ $container_id ] ? 'checked="checked" ' : '' ) . 'value="1" name="matomo[tagmanger_container_ids][' . $container_id . ']" /> ID:' . esc_html( $container_id ) . ' Name: ' . esc_html( $container_name ) . ' <br />';
|
73 |
}
|
74 |
-
echo '<br /><br /><a href="' . menu_page_url( \WpMatomo\Admin\Menu::SLUG_TAGMANAGER, false ) . '" rel="noreferrer noopener" target="_blank">Edit containers <span class="dashicons-before dashicons-external"></span></a>';
|
75 |
echo '<br /><span class="dashicons dashicons-info-outline"></span> For Matomo to track you will need to add a Matomo Tag to the container. It otherwise won\'t track automatically.';
|
76 |
echo '</td></tr>';
|
77 |
}
|
@@ -79,9 +81,9 @@ if ( count( $errors ) ) {
|
|
79 |
$matomo_form->show_textarea( 'tracking_code', esc_html__( 'Tracking code', 'matomo' ), 15, 'This is a preview of your current tracking code based on your configuration below. You don\'t need to do anything with it and this is purely for your information. If you choose to enter your tracking code manually, you can change it here. The tracking code is a piece of code that will be automatically embedded into your site and it is repsonsible for tracking your visitors. Have a look at the system report to get a list of all available JS tracker and tracking API endpoints. You don\'t need to embed this tracking code into your website, our plugin does this automatically.' . $matomo_manually_network, $matomo_is_not_tracking, 'matomo-track-option matomo-track-option-default matomo-track-option-tagmanager matomo-track-option-manually', ! $settings->is_network_enabled(), '', ( $settings->get_global_option( 'track_mode' ) !== 'manually' ), false );
|
80 |
|
81 |
|
82 |
-
$matomo_form->show_select( \WpMatomo\Settings::SITE_CURRENCY, esc_html__( 'Currency', 'matomo' ), $matomo_currencies, esc_html__('Choose the currency which will be used in reports. The currency will be used if you have an ecommerce store or if you are using the Matomo goals feature and assign a monetary value to a goal.', 'matomo'), '' );
|
83 |
|
84 |
-
$matomo_form->show_headline(esc_html__('Customise tracking (optional)', 'matomo'), 'matomo-track-option matomo-track-option-default matomo-track-option-manually matomo-track-option-tagmanager');
|
85 |
|
86 |
$matomo_form->show_checkbox( 'disable_cookies', esc_html__( 'Disable cookies', 'matomo' ), esc_html__( 'Disable all tracking cookies for a visitor.', 'matomo' ), $matomo_is_not_generated_tracking, $matomo_full_generated_tracking_group );
|
87 |
|
@@ -93,22 +95,22 @@ if ( count( $errors ) ) {
|
|
93 |
|
94 |
$matomo_form->show_checkbox( 'track_jserrors', esc_html__( 'Track JS errors', 'matomo' ), esc_html__( 'Enable to track JavaScript errors that occur on your website as Matomo events.', 'matomo' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo FAQ%2$s.', 'matomo' ), '<a href="https://matomo.org/faq/how-to/how-do-i-enable-basic-javascript-error-tracking-and-reporting-in-matomo-browser-console-error-messages/" rel="noreferrer noopener" target="_BLANK">', '</a>' ), $matomo_is_not_tracking, $matomo_full_generated_tracking_group );
|
95 |
|
96 |
-
echo '<tr class="' . $matomo_full_generated_tracking_group . ' matomo-track-option-manually' . ( $matomo_is_not_tracking ? ' hidden' : '' ) . '">';
|
97 |
echo '<th scope="row"><label for="add_post_annotations">' . esc_html__( 'Add annotation on new post of type', 'matomo' ) . '</label>:</th><td>';
|
98 |
$matomo_filter = $settings->get_global_option( 'add_post_annotations' );
|
99 |
-
foreach ( get_post_types(
|
100 |
-
echo '<input type="checkbox" ' . ( isset( $matomo_filter [ $
|
101 |
}
|
102 |
echo '<span class="dashicons dashicons-editor-help" style="cursor: pointer;" onclick="jQuery(\'#add_post_annotations-desc\').toggleClass(\'hidden\');"></span> <p class="description hidden" id="add_post_annotations-desc">' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'matomo' ), '<a href="https://matomo.org/docs/annotations/" rel="noreferrer noopener" target="_BLANK">', '</a>' ) . '</p></td></tr>';
|
103 |
|
104 |
$matomo_form->show_select(
|
105 |
'track_content',
|
106 |
__( 'Enable content tracking', 'matomo' ),
|
107 |
-
|
108 |
'disabled' => esc_html__( 'Disabled', 'matomo' ),
|
109 |
'all' => esc_html__( 'Track all content blocks', 'matomo' ),
|
110 |
'visible' => esc_html__( 'Track only visible content blocks', 'matomo' ),
|
111 |
-
|
112 |
__( 'Content tracking allows you to track interaction with the content of a web page or application.', 'matomo' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'matomo' ), '<a href="https://developer.matomo.org/guides/content-tracking" rel="noreferrer noopener" target="_BLANK">', '</a>' ),
|
113 |
'',
|
114 |
$matomo_is_not_tracking,
|
@@ -146,13 +148,13 @@ if ( count( $errors ) ) {
|
|
146 |
$matomo_form->show_select(
|
147 |
'track_user_id',
|
148 |
__( 'User ID Tracking', 'matomo' ),
|
149 |
-
|
150 |
'disabled' => esc_html__( 'Disabled', 'matomo' ),
|
151 |
'uid' => esc_html__( 'WP User ID', 'matomo' ),
|
152 |
'email' => esc_html__( 'Email Address', 'matomo' ),
|
153 |
'username' => esc_html__( 'Username', 'matomo' ),
|
154 |
'displayname' => esc_html__( 'Display Name (Not Recommended!)', 'matomo' ),
|
155 |
-
|
156 |
__( 'When a user is logged in to WordPress, track their "User ID". You can select which field from the User\'s profile is tracked as the "User ID". When enabled, Tracking based on Email Address is recommended.', 'matomo' ),
|
157 |
'',
|
158 |
$matomo_is_not_tracking,
|
@@ -176,10 +178,10 @@ if ( count( $errors ) ) {
|
|
176 |
$matomo_form->show_select(
|
177 |
'force_protocol',
|
178 |
__( 'Force Matomo to use a specific protocol', 'matomo' ),
|
179 |
-
|
180 |
'disabled' => esc_html__( 'Disabled (default)', 'matomo' ),
|
181 |
'https' => esc_html__( 'https (SSL)', 'matomo' ),
|
182 |
-
|
183 |
__( 'Choose if you want to explicitly want to force Matomo to use HTTP or HTTPS. Does not work with a CDN URL.', 'matomo' ),
|
184 |
'',
|
185 |
$matomo_is_not_tracking,
|
@@ -188,10 +190,10 @@ if ( count( $errors ) ) {
|
|
188 |
$matomo_form->show_select(
|
189 |
'track_codeposition',
|
190 |
__( 'JavaScript code position', 'matomo' ),
|
191 |
-
|
192 |
'footer' => esc_html__( 'Footer', 'matomo' ),
|
193 |
'header' => esc_html__( 'Header', 'matomo' ),
|
194 |
-
|
195 |
__( 'Choose whether the JavaScript code is added to the footer or the header.', 'matomo' ),
|
196 |
'',
|
197 |
$matomo_is_not_tracking,
|
@@ -200,11 +202,11 @@ if ( count( $errors ) ) {
|
|
200 |
$matomo_form->show_select(
|
201 |
'track_api_endpoint',
|
202 |
__( 'Endpoint for HTTP Tracking API', 'matomo' ),
|
203 |
-
|
204 |
'default' => esc_html__( 'Default', 'matomo' ),
|
205 |
'restapi' => esc_html__( 'Through WordPress Rest API', 'matomo' ),
|
206 |
-
|
207 |
-
__( 'By default the HTTP Tracking API points to your Matomo plugin directory "
|
208 |
'',
|
209 |
$matomo_is_not_tracking,
|
210 |
$matomo_full_generated_tracking_group . ' matomo-track-option-manually matomo-track-option-tagmanager'
|
@@ -213,35 +215,35 @@ if ( count( $errors ) ) {
|
|
213 |
$matomo_form->show_select(
|
214 |
'track_js_endpoint',
|
215 |
__( 'Endpoint for JavaScript tracker', 'matomo' ),
|
216 |
-
|
217 |
'default' => esc_html__( 'Default', 'matomo' ),
|
218 |
'restapi' => esc_html__( 'Through WordPress Rest API (slower)', 'matomo' ),
|
219 |
-
'plugin'
|
220 |
-
|
221 |
-
__( 'By default the JS tracking code will be loaded from "
|
222 |
'',
|
223 |
$matomo_is_not_tracking,
|
224 |
$matomo_full_generated_tracking_group
|
225 |
);
|
226 |
|
227 |
-
$matomo_form->show_select( 'cookie_consent', esc_html__( 'Custom consent screen', 'matomo' ), $cookie_consent_modes, sprintf(esc_html__( 'Activates a specific Matomo consent mode. Only configure a consent mode if you are implementing a consent screen yourself. This requires a custom consent implementation. For more information please read this %1$sFAQ%2$s (this option will take care of step 1 for you). By default no consent mode is applied.', 'matomo' ), '<a href="https://developer.matomo.org/guides/tracking-consent" rel="noreferrer noopener" target="_blank">', '</a>'), '', $matomo_is_not_generated_tracking, $matomo_full_generated_tracking_group );
|
228 |
|
229 |
-
$matomo_form->show_headline(esc_html__('For Developers', 'matomo'), 'matomo-track-option matomo-track-option-default matomo-track-option-disabled matomo-track-option-manually matomo-track-option-tagmanager');
|
230 |
|
231 |
$matomo_form->show_select(
|
232 |
'tracker_debug',
|
233 |
__( 'Tracker Debug Mode', 'matomo' ),
|
234 |
-
|
235 |
-
'disabled'
|
236 |
'always' => esc_html__( 'Always enabled', 'matomo' ),
|
237 |
-
'on_demand'
|
238 |
-
|
239 |
__( 'For security and privacy reasons you should only enable this setting for as short time of a time as possible.', 'matomo' ),
|
240 |
'',
|
241 |
$matomo_is_not_tracking,
|
242 |
$matomo_full_generated_tracking_group . ' matomo-track-option-disabled matomo-track-option-manually matomo-track-option-tagmanager'
|
243 |
);
|
244 |
-
|
245 |
echo $matomo_submit_button;
|
246 |
?>
|
247 |
|
@@ -250,13 +252,13 @@ if ( count( $errors ) ) {
|
|
250 |
</form>
|
251 |
|
252 |
<?php if ( $matomo_is_not_tracking && ! $settings->is_network_enabled() ) { // Can't show it for multisite as idsite and url is always different. ?>
|
253 |
-
<div id="matomo_default_tracking_code">
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
</div>
|
262 |
<?php } ?>
|
10 |
* https://github.com/braekling/WP-Matomo
|
11 |
*
|
12 |
*/
|
13 |
+
/**
|
14 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
15 |
+
*/
|
16 |
use WpMatomo\Admin\TrackingSettings;
|
17 |
use WpMatomo\Paths;
|
18 |
|
25 |
/** @var array $containers */
|
26 |
/** @var array $track_modes */
|
27 |
/** @var array $matomo_currencies */
|
28 |
+
/** @var string[] $settings_errors */
|
29 |
/** @var array $cookie_consent_modes */
|
30 |
|
31 |
$matomo_form = new \WpMatomo\Admin\TrackingSettings\Forms( $settings );
|
36 |
if ( $was_updated ) {
|
37 |
include 'update_notice_clear_cache.php';
|
38 |
}
|
39 |
+
if ( count( $settings_errors ) ) {
|
40 |
+
include 'settings_errors.php';
|
41 |
}
|
42 |
|
43 |
?>
|
44 |
<form method="post">
|
45 |
<?php wp_nonce_field( TrackingSettings::NONCE_NAME ); ?>
|
46 |
<p>
|
47 |
+
<?php esc_html_e( 'Here you can optionally configure the tracking to your liking if you want (you don\'t have to configure it).', 'matomo' ); ?>
|
48 |
+
<?php esc_html_e( 'The configured tracking code will be embedded into your website automatically and you won\'t need to do anything unless you disabled the tracking.', 'matomo' ); ?>
|
49 |
+
<?php esc_html_e( 'If you are seeing a tracking code below, you don\'t have to embed this tracking code into your site. The plugin does this automatically for you.', 'matomo' ); ?>
|
50 |
+
</p>
|
51 |
<table class="matomo-tracking-form widefat">
|
52 |
<tbody>
|
53 |
|
58 |
$matomo_is_not_generated_tracking = $matomo_is_not_tracking || $settings->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_MANUALLY;
|
59 |
$matomo_full_generated_tracking_group = 'matomo-track-option matomo-track-option-default ';
|
60 |
|
61 |
+
$matomo_description = sprintf( '%s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s<br /><strong>%s:</strong> %s', esc_html__( 'You can choose between four tracking code modes:', 'matomo' ), esc_html__( 'Disabled', 'matomo' ), esc_html__( 'matomo will not add the tracking code. Use this, if you want to add the tracking code to your template files or you use another plugin to add the tracking code.', 'matomo' ), esc_html__( 'Default tracking', 'matomo' ), esc_html__( 'matomo will use Matomo\'s standard tracking code.', 'matomo' ) . ' ' . esc_html__( 'This mode is recommended for most use cases.', 'matomo' ), esc_html__( 'Enter manually', 'matomo' ), esc_html__( 'Enter your own tracking code manually. You can choose one of the prior options, pre-configure your tracking code and switch to manually editing at last.', 'matomo' ) . ( $settings->is_network_enabled() ? ' ' . esc_html__( 'Use the placeholder {ID} to add the Matomo site ID.', 'matomo' ) : '' ), esc_html__( 'Tag Manager', 'matomo' ), esc_html__( 'If you have created containers in the Tag Manager, you can select one of them and it will embed the code for the container automatically.', 'matomo' ) );
|
62 |
$matomo_form->show_select( 'track_mode', esc_html__( 'Add tracking code', 'matomo' ), $track_modes, $matomo_description, 'jQuery(\'tr.matomo-track-option\').addClass(\'hidden\'); jQuery(\'tr.matomo-track-option-\' + jQuery(\'#track_mode\').val()).removeClass(\'hidden\'); jQuery(\'#tracking_code, #noscript_code\').prop(\'readonly\', jQuery(\'#track_mode\').val() != \'manually\');' );
|
63 |
|
64 |
+
$matomo_manually_network = '';
|
65 |
+
if ( $settings->is_network_enabled() ) {
|
66 |
+
$matomo_manually_network = ' ' . sprintf( esc_html__( 'You can use these variables: %1$s. %2$sLearn more%3$s', 'matomo' ), '{MATOMO_IDSITE}, {MATOMO_API_ENDPOINT}, {MATOMO_JS_ENDPOINT}', '<a href="https://matomo.org/faq/wordpress/how-can-i-configure-the-tracking-code-manually-when-i-have-wordpress-network-enabled-in-multisite-mode/" target="_blank" rel="noreferrer noopener">', '</a>' );
|
67 |
+
}
|
68 |
|
69 |
if ( ! empty( $containers ) ) {
|
70 |
echo '<tr class="matomo-track-option matomo-track-option-tagmanager ' . ( $matomo_is_not_tracking ? ' hidden' : '' ) . '">';
|
71 |
echo '<th scope="row"><label for="tagmanger_container_ids">' . esc_html__( 'Add these Tag Manager containers', 'matomo' ) . '</label>:</th><td>';
|
72 |
$selected_container_ids = $settings->get_global_option( 'tagmanger_container_ids' );
|
73 |
foreach ( $containers as $container_id => $container_name ) {
|
74 |
+
echo '<input type="checkbox" ' . ( isset( $selected_container_ids [ $container_id ] ) && $selected_container_ids [ $container_id ] ? 'checked="checked" ' : '' ) . 'value="1" name="matomo[tagmanger_container_ids][' . esc_attr( $container_id ) . ']" /> ID:' . esc_html( $container_id ) . ' Name: ' . esc_html( $container_name ) . ' <br />';
|
75 |
}
|
76 |
+
echo '<br /><br /><a href="' . esc_url( menu_page_url( \WpMatomo\Admin\Menu::SLUG_TAGMANAGER, false ) ) . '" rel="noreferrer noopener" target="_blank">Edit containers <span class="dashicons-before dashicons-external"></span></a>';
|
77 |
echo '<br /><span class="dashicons dashicons-info-outline"></span> For Matomo to track you will need to add a Matomo Tag to the container. It otherwise won\'t track automatically.';
|
78 |
echo '</td></tr>';
|
79 |
}
|
81 |
$matomo_form->show_textarea( 'tracking_code', esc_html__( 'Tracking code', 'matomo' ), 15, 'This is a preview of your current tracking code based on your configuration below. You don\'t need to do anything with it and this is purely for your information. If you choose to enter your tracking code manually, you can change it here. The tracking code is a piece of code that will be automatically embedded into your site and it is repsonsible for tracking your visitors. Have a look at the system report to get a list of all available JS tracker and tracking API endpoints. You don\'t need to embed this tracking code into your website, our plugin does this automatically.' . $matomo_manually_network, $matomo_is_not_tracking, 'matomo-track-option matomo-track-option-default matomo-track-option-tagmanager matomo-track-option-manually', ! $settings->is_network_enabled(), '', ( $settings->get_global_option( 'track_mode' ) !== 'manually' ), false );
|
82 |
|
83 |
|
84 |
+
$matomo_form->show_select( \WpMatomo\Settings::SITE_CURRENCY, esc_html__( 'Currency', 'matomo' ), $matomo_currencies, esc_html__( 'Choose the currency which will be used in reports. The currency will be used if you have an ecommerce store or if you are using the Matomo goals feature and assign a monetary value to a goal.', 'matomo' ), '' );
|
85 |
|
86 |
+
$matomo_form->show_headline( esc_html__( 'Customise tracking (optional)', 'matomo' ), 'matomo-track-option matomo-track-option-default matomo-track-option-manually matomo-track-option-tagmanager' );
|
87 |
|
88 |
$matomo_form->show_checkbox( 'disable_cookies', esc_html__( 'Disable cookies', 'matomo' ), esc_html__( 'Disable all tracking cookies for a visitor.', 'matomo' ), $matomo_is_not_generated_tracking, $matomo_full_generated_tracking_group );
|
89 |
|
95 |
|
96 |
$matomo_form->show_checkbox( 'track_jserrors', esc_html__( 'Track JS errors', 'matomo' ), esc_html__( 'Enable to track JavaScript errors that occur on your website as Matomo events.', 'matomo' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo FAQ%2$s.', 'matomo' ), '<a href="https://matomo.org/faq/how-to/how-do-i-enable-basic-javascript-error-tracking-and-reporting-in-matomo-browser-console-error-messages/" rel="noreferrer noopener" target="_BLANK">', '</a>' ), $matomo_is_not_tracking, $matomo_full_generated_tracking_group );
|
97 |
|
98 |
+
echo '<tr class="' . esc_attr( $matomo_full_generated_tracking_group ) . ' matomo-track-option-manually' . ( $matomo_is_not_tracking ? ' hidden' : '' ) . '">';
|
99 |
echo '<th scope="row"><label for="add_post_annotations">' . esc_html__( 'Add annotation on new post of type', 'matomo' ) . '</label>:</th><td>';
|
100 |
$matomo_filter = $settings->get_global_option( 'add_post_annotations' );
|
101 |
+
foreach ( get_post_types( [], 'objects' ) as $object_post_type ) {
|
102 |
+
echo '<input type="checkbox" ' . ( isset( $matomo_filter [ $object_post_type->name ] ) && $matomo_filter [ $object_post_type->name ] ? 'checked="checked" ' : '' ) . 'value="1" name="matomo[add_post_annotations][' . esc_attr( $object_post_type->name ) . ']" /> ' . esc_html( $object_post_type->label ) . ' ';
|
103 |
}
|
104 |
echo '<span class="dashicons dashicons-editor-help" style="cursor: pointer;" onclick="jQuery(\'#add_post_annotations-desc\').toggleClass(\'hidden\');"></span> <p class="description hidden" id="add_post_annotations-desc">' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'matomo' ), '<a href="https://matomo.org/docs/annotations/" rel="noreferrer noopener" target="_BLANK">', '</a>' ) . '</p></td></tr>';
|
105 |
|
106 |
$matomo_form->show_select(
|
107 |
'track_content',
|
108 |
__( 'Enable content tracking', 'matomo' ),
|
109 |
+
[
|
110 |
'disabled' => esc_html__( 'Disabled', 'matomo' ),
|
111 |
'all' => esc_html__( 'Track all content blocks', 'matomo' ),
|
112 |
'visible' => esc_html__( 'Track only visible content blocks', 'matomo' ),
|
113 |
+
],
|
114 |
__( 'Content tracking allows you to track interaction with the content of a web page or application.', 'matomo' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'matomo' ), '<a href="https://developer.matomo.org/guides/content-tracking" rel="noreferrer noopener" target="_BLANK">', '</a>' ),
|
115 |
'',
|
116 |
$matomo_is_not_tracking,
|
148 |
$matomo_form->show_select(
|
149 |
'track_user_id',
|
150 |
__( 'User ID Tracking', 'matomo' ),
|
151 |
+
[
|
152 |
'disabled' => esc_html__( 'Disabled', 'matomo' ),
|
153 |
'uid' => esc_html__( 'WP User ID', 'matomo' ),
|
154 |
'email' => esc_html__( 'Email Address', 'matomo' ),
|
155 |
'username' => esc_html__( 'Username', 'matomo' ),
|
156 |
'displayname' => esc_html__( 'Display Name (Not Recommended!)', 'matomo' ),
|
157 |
+
],
|
158 |
__( 'When a user is logged in to WordPress, track their "User ID". You can select which field from the User\'s profile is tracked as the "User ID". When enabled, Tracking based on Email Address is recommended.', 'matomo' ),
|
159 |
'',
|
160 |
$matomo_is_not_tracking,
|
178 |
$matomo_form->show_select(
|
179 |
'force_protocol',
|
180 |
__( 'Force Matomo to use a specific protocol', 'matomo' ),
|
181 |
+
[
|
182 |
'disabled' => esc_html__( 'Disabled (default)', 'matomo' ),
|
183 |
'https' => esc_html__( 'https (SSL)', 'matomo' ),
|
184 |
+
],
|
185 |
__( 'Choose if you want to explicitly want to force Matomo to use HTTP or HTTPS. Does not work with a CDN URL.', 'matomo' ),
|
186 |
'',
|
187 |
$matomo_is_not_tracking,
|
190 |
$matomo_form->show_select(
|
191 |
'track_codeposition',
|
192 |
__( 'JavaScript code position', 'matomo' ),
|
193 |
+
[
|
194 |
'footer' => esc_html__( 'Footer', 'matomo' ),
|
195 |
'header' => esc_html__( 'Header', 'matomo' ),
|
196 |
+
],
|
197 |
__( 'Choose whether the JavaScript code is added to the footer or the header.', 'matomo' ),
|
198 |
'',
|
199 |
$matomo_is_not_tracking,
|
202 |
$matomo_form->show_select(
|
203 |
'track_api_endpoint',
|
204 |
__( 'Endpoint for HTTP Tracking API', 'matomo' ),
|
205 |
+
[
|
206 |
'default' => esc_html__( 'Default', 'matomo' ),
|
207 |
'restapi' => esc_html__( 'Through WordPress Rest API', 'matomo' ),
|
208 |
+
],
|
209 |
+
sprintf( __( 'By default the HTTP Tracking API points to your Matomo plugin directory "%1$s". You can choose to use the WP Rest API (%2$s) instead for example to hide matomo.php or if the other URL doesn\'t work for you. Note: If the tracking mode "Tag Manager" is selected, then this URL currently only applies to the feed tracking.', 'matomo' ), esc_html( $matomo_paths->get_tracker_api_url_in_matomo_dir() ), esc_html( $matomo_paths->get_tracker_api_rest_api_endpoint() ) ),
|
210 |
'',
|
211 |
$matomo_is_not_tracking,
|
212 |
$matomo_full_generated_tracking_group . ' matomo-track-option-manually matomo-track-option-tagmanager'
|
215 |
$matomo_form->show_select(
|
216 |
'track_js_endpoint',
|
217 |
__( 'Endpoint for JavaScript tracker', 'matomo' ),
|
218 |
+
[
|
219 |
'default' => esc_html__( 'Default', 'matomo' ),
|
220 |
'restapi' => esc_html__( 'Through WordPress Rest API (slower)', 'matomo' ),
|
221 |
+
'plugin' => esc_html__( 'Plugin (an alternative JS file if the default is blocked by the webserver)', 'matomo' ),
|
222 |
+
],
|
223 |
+
sprintf( __( 'By default the JS tracking code will be loaded from "%1$s". You can choose to serve the JS file through the WP Rest API (%2$s) for example to hide matomo.js. Please note that this means every request to the JavaScript file will launch WordPress PHP and therefore will be slower compared to your webserver serving the JS file directly. Using the "Plugin" method will cause issues with our paid Heatmap and Session Recording, Form Analytics, and Media Analyics plugin.', 'matomo' ), esc_html( $matomo_paths->get_js_tracker_url_in_matomo_dir() ), esc_html( $matomo_paths->get_js_tracker_rest_api_endpoint() ) ),
|
224 |
'',
|
225 |
$matomo_is_not_tracking,
|
226 |
$matomo_full_generated_tracking_group
|
227 |
);
|
228 |
|
229 |
+
$matomo_form->show_select( 'cookie_consent', esc_html__( 'Custom consent screen', 'matomo' ), $cookie_consent_modes, sprintf( esc_html__( 'Activates a specific Matomo consent mode. Only configure a consent mode if you are implementing a consent screen yourself. This requires a custom consent implementation. For more information please read this %1$sFAQ%2$s (this option will take care of step 1 for you). By default no consent mode is applied.', 'matomo' ), '<a href="https://developer.matomo.org/guides/tracking-consent" rel="noreferrer noopener" target="_blank">', '</a>' ), '', $matomo_is_not_generated_tracking, $matomo_full_generated_tracking_group );
|
230 |
|
231 |
+
$matomo_form->show_headline( esc_html__( 'For Developers', 'matomo' ), 'matomo-track-option matomo-track-option-default matomo-track-option-disabled matomo-track-option-manually matomo-track-option-tagmanager' );
|
232 |
|
233 |
$matomo_form->show_select(
|
234 |
'tracker_debug',
|
235 |
__( 'Tracker Debug Mode', 'matomo' ),
|
236 |
+
[
|
237 |
+
'disabled' => esc_html__( 'Disabled (recommended)', 'matomo' ),
|
238 |
'always' => esc_html__( 'Always enabled', 'matomo' ),
|
239 |
+
'on_demand' => esc_html__( 'Enabled on demand', 'matomo' ),
|
240 |
+
],
|
241 |
__( 'For security and privacy reasons you should only enable this setting for as short time of a time as possible.', 'matomo' ),
|
242 |
'',
|
243 |
$matomo_is_not_tracking,
|
244 |
$matomo_full_generated_tracking_group . ' matomo-track-option-disabled matomo-track-option-manually matomo-track-option-tagmanager'
|
245 |
);
|
246 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
247 |
echo $matomo_submit_button;
|
248 |
?>
|
249 |
|
252 |
</form>
|
253 |
|
254 |
<?php if ( $matomo_is_not_tracking && ! $settings->is_network_enabled() ) { // Can't show it for multisite as idsite and url is always different. ?>
|
255 |
+
<div id="matomo_default_tracking_code">
|
256 |
+
<h2><?php esc_html_e( 'JavaScript tracking code', 'matomo' ); ?></h2>
|
257 |
+
<p>
|
258 |
+
<?php echo sprintf( esc_html__( 'Wanting to embed the tracking code manually into your site or using a different plugin? No problem! Simply copy/paste below tracking code. Want to adjust it? %1$sCheck out our developer documentation.%2$s', 'matomo' ), '<a href="https://developer.matomo.org/guides/tracking-javascript-guide" target="_blank" rel="noreferrer noopener">', '</a>' ); ?>
|
259 |
+
</p>
|
260 |
+
<?php echo '<pre><textarea>' . esc_html( implode( ";\n", explode( ';', $matomo_default_tracking_code['script'] ) ) ) . '</textarea></pre>'; ?>
|
261 |
+
<h3><?php esc_html_e( 'NoScript tracking code', 'matomo' ); ?></h3>
|
262 |
+
<?php echo '<pre><textarea class="no_script">' . esc_html( $matomo_default_tracking_code['noscript'] ) . '</textarea></pre>'; ?>
|
263 |
+
</div>
|
264 |
<?php } ?>
|
classes/WpMatomo/Annotations.php
CHANGED
@@ -9,6 +9,8 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
@@ -33,7 +35,7 @@ class Annotations {
|
|
33 |
}
|
34 |
|
35 |
public function register_hooks() {
|
36 |
-
add_action( 'transition_post_status',
|
37 |
}
|
38 |
|
39 |
/**
|
@@ -70,13 +72,13 @@ class Annotations {
|
|
70 |
$logger = $this->logger;
|
71 |
\Piwik\Access::doAsSuperUser(
|
72 |
function () use ( $post, $logger, $idsite ) {
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
}
|
78 |
);
|
79 |
-
} catch (
|
80 |
$this->logger->log( 'Add post annotation failed: ' . $e->getMessage() );
|
81 |
|
82 |
return;
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Exception;
|
13 |
+
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
35 |
}
|
36 |
|
37 |
public function register_hooks() {
|
38 |
+
add_action( 'transition_post_status', [ $this, 'add_annotation' ], 10, 3 );
|
39 |
}
|
40 |
|
41 |
/**
|
72 |
$logger = $this->logger;
|
73 |
\Piwik\Access::doAsSuperUser(
|
74 |
function () use ( $post, $logger, $idsite ) {
|
75 |
+
$note = esc_html__( 'Published:', 'matomo' ) . ' ' . $post->post_title . ' - URL: ' . get_permalink( $post->ID );
|
76 |
+
\Piwik\Plugins\Annotations\API::unsetInstance();// make sure latest instance will be loaded with all up to date dependencies... mainly needed for tests
|
77 |
+
$id = \Piwik\Plugins\Annotations\API::getInstance()->add( $idsite, gmdate( 'Y-m-d' ), $note );
|
78 |
+
$logger->log( 'Add post annotation. ' . $note . ' - ' . wp_json_encode( $id ) );
|
79 |
}
|
80 |
);
|
81 |
+
} catch ( Exception $e ) {
|
82 |
$this->logger->log( 'Add post annotation failed: ' . $e->getMessage() );
|
83 |
|
84 |
return;
|
classes/WpMatomo/Bootstrap.php
CHANGED
@@ -9,10 +9,16 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
-
|
|
|
|
|
|
|
16 |
class Bootstrap {
|
17 |
/**
|
18 |
* Tests only
|
@@ -70,11 +76,11 @@ class Bootstrap {
|
|
70 |
|
71 |
include_once 'Db/WordPress.php';
|
72 |
|
73 |
-
$environment = new
|
74 |
$environment->init();
|
75 |
|
76 |
-
|
77 |
-
$controller =
|
78 |
$controller->init();
|
79 |
|
80 |
add_action(
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Piwik\Application\Environment;
|
13 |
+
use Piwik\FrontController;
|
14 |
+
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
+
/**
|
19 |
+
* piwik constants
|
20 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
|
21 |
+
*/
|
22 |
class Bootstrap {
|
23 |
/**
|
24 |
* Tests only
|
76 |
|
77 |
include_once 'Db/WordPress.php';
|
78 |
|
79 |
+
$environment = new Environment( null );
|
80 |
$environment->init();
|
81 |
|
82 |
+
FrontController::unsetInstance();
|
83 |
+
$controller = FrontController::getInstance();
|
84 |
$controller->init();
|
85 |
|
86 |
add_action(
|
classes/WpMatomo/Capabilities.php
CHANGED
@@ -17,6 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
17 |
}
|
18 |
|
19 |
class Capabilities {
|
|
|
20 |
const KEY_NONE = 'none_matomo';
|
21 |
|
22 |
/**
|
@@ -50,9 +51,9 @@ class Capabilities {
|
|
50 |
}
|
51 |
|
52 |
public function register_hooks() {
|
53 |
-
add_action( 'wp_roles_init',
|
54 |
-
add_filter( 'user_has_cap',
|
55 |
-
add_filter( 'map_meta_cap',
|
56 |
}
|
57 |
|
58 |
/**
|
@@ -61,9 +62,9 @@ class Capabilities {
|
|
61 |
* @internal
|
62 |
*/
|
63 |
public function remove_hooks() {
|
64 |
-
remove_action( 'wp_roles_init',
|
65 |
-
remove_filter( 'user_has_cap',
|
66 |
-
remove_filter( 'map_meta_cap',
|
67 |
}
|
68 |
|
69 |
public function map_meta_cap( $caps, $cap, $user_id, $args ) {
|
@@ -156,12 +157,12 @@ class Capabilities {
|
|
156 |
}
|
157 |
|
158 |
public function get_all_capabilities_sorted_by_highest_permission() {
|
159 |
-
return
|
160 |
self::KEY_SUPERUSER,
|
161 |
self::KEY_ADMIN,
|
162 |
self::KEY_WRITE,
|
163 |
self::KEY_VIEW,
|
164 |
-
|
165 |
}
|
166 |
|
167 |
protected function has_any_higher_permission( $cap_to_find, $allcaps ) {
|
@@ -182,5 +183,4 @@ class Capabilities {
|
|
182 |
|
183 |
return false;
|
184 |
}
|
185 |
-
|
186 |
}
|
17 |
}
|
18 |
|
19 |
class Capabilities {
|
20 |
+
|
21 |
const KEY_NONE = 'none_matomo';
|
22 |
|
23 |
/**
|
51 |
}
|
52 |
|
53 |
public function register_hooks() {
|
54 |
+
add_action( 'wp_roles_init', [ $this, 'add_capabilities_to_roles' ] );
|
55 |
+
add_filter( 'user_has_cap', [ $this, 'add_capabilities_to_user' ], 10, 4 );
|
56 |
+
add_filter( 'map_meta_cap', [ $this, 'map_meta_cap' ], 10, 4 );
|
57 |
}
|
58 |
|
59 |
/**
|
62 |
* @internal
|
63 |
*/
|
64 |
public function remove_hooks() {
|
65 |
+
remove_action( 'wp_roles_init', [ $this, 'add_capabilities_to_roles' ] );
|
66 |
+
remove_filter( 'user_has_cap', [ $this, 'add_capabilities_to_user' ], 10 );
|
67 |
+
remove_filter( 'map_meta_cap', [ $this, 'map_meta_cap' ], 10 );
|
68 |
}
|
69 |
|
70 |
public function map_meta_cap( $caps, $cap, $user_id, $args ) {
|
157 |
}
|
158 |
|
159 |
public function get_all_capabilities_sorted_by_highest_permission() {
|
160 |
+
return [
|
161 |
self::KEY_SUPERUSER,
|
162 |
self::KEY_ADMIN,
|
163 |
self::KEY_WRITE,
|
164 |
self::KEY_VIEW,
|
165 |
+
];
|
166 |
}
|
167 |
|
168 |
protected function has_any_higher_permission( $cap_to_find, $allcaps ) {
|
183 |
|
184 |
return false;
|
185 |
}
|
|
|
186 |
}
|
classes/WpMatomo/Commands/MatomoCommands.php
CHANGED
@@ -9,11 +9,12 @@
|
|
9 |
|
10 |
namespace WpMatomo\Commands;
|
11 |
|
|
|
|
|
|
|
12 |
use WpMatomo\Installer;
|
13 |
use WpMatomo\Settings;
|
14 |
use WpMatomo\Uninstaller;
|
15 |
-
use WP_CLI;
|
16 |
-
use WP_CLI_Command;
|
17 |
use WpMatomo\Updater;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -52,6 +53,7 @@ class MatomoCommands extends WP_CLI_Command {
|
|
52 |
|
53 |
WP_CLI::success( 'Uninstalled Matomo Analytics' );
|
54 |
}
|
|
|
55 |
/**
|
56 |
* Updates Matomo.
|
57 |
*
|
@@ -67,17 +69,17 @@ class MatomoCommands extends WP_CLI_Command {
|
|
67 |
* @when after_wp_load
|
68 |
*/
|
69 |
public function update( $args, $assoc_args ) {
|
70 |
-
if ( function_exists('is_multisite') && is_multisite() && function_exists( 'get_sites' ) ) {
|
71 |
foreach ( get_sites() as $site ) {
|
72 |
-
/** @var
|
73 |
switch_to_blog( $site->blog_id );
|
74 |
// this way we make sure all blogs get updated eventually
|
75 |
WP_CLI::log( 'Blog ID' . $site->blog_id );
|
76 |
-
$this->
|
77 |
restore_current_blog();
|
78 |
}
|
79 |
} else {
|
80 |
-
$this->
|
81 |
}
|
82 |
|
83 |
WP_CLI::success( 'Matomo Analytics Updater finished' );
|
@@ -86,12 +88,13 @@ class MatomoCommands extends WP_CLI_Command {
|
|
86 |
/**
|
87 |
* @param $assoc_args
|
88 |
*/
|
89 |
-
|
90 |
$settings = new Settings();
|
91 |
|
92 |
$installer = new Installer( $settings );
|
93 |
if ( ! $installer->looks_like_it_is_installed() ) {
|
94 |
WP_CLI::log( 'Skipping as looks like Matomo is not yet installed' );
|
|
|
95 |
return;
|
96 |
}
|
97 |
|
@@ -109,7 +112,7 @@ class MatomoCommands extends WP_CLI_Command {
|
|
109 |
WP_CLI::add_command(
|
110 |
'matomo',
|
111 |
'\WpMatomo\Commands\MatomoCommands',
|
112 |
-
|
113 |
'shortdesc' => 'Manage your Matomo Analytics. Commands are recommended only to be used in development mode',
|
114 |
-
|
115 |
);
|
9 |
|
10 |
namespace WpMatomo\Commands;
|
11 |
|
12 |
+
use WP_CLI;
|
13 |
+
use WP_CLI_Command;
|
14 |
+
use WP_Site;
|
15 |
use WpMatomo\Installer;
|
16 |
use WpMatomo\Settings;
|
17 |
use WpMatomo\Uninstaller;
|
|
|
|
|
18 |
use WpMatomo\Updater;
|
19 |
|
20 |
if ( ! defined( 'ABSPATH' ) ) {
|
53 |
|
54 |
WP_CLI::success( 'Uninstalled Matomo Analytics' );
|
55 |
}
|
56 |
+
|
57 |
/**
|
58 |
* Updates Matomo.
|
59 |
*
|
69 |
* @when after_wp_load
|
70 |
*/
|
71 |
public function update( $args, $assoc_args ) {
|
72 |
+
if ( function_exists( 'is_multisite' ) && is_multisite() && function_exists( 'get_sites' ) ) {
|
73 |
foreach ( get_sites() as $site ) {
|
74 |
+
/** @var WP_Site $site */
|
75 |
switch_to_blog( $site->blog_id );
|
76 |
// this way we make sure all blogs get updated eventually
|
77 |
WP_CLI::log( 'Blog ID' . $site->blog_id );
|
78 |
+
$this->do_update( ! empty( $assoc_args['force'] ) );
|
79 |
restore_current_blog();
|
80 |
}
|
81 |
} else {
|
82 |
+
$this->do_update( ! empty( $assoc_args['force'] ) );
|
83 |
}
|
84 |
|
85 |
WP_CLI::success( 'Matomo Analytics Updater finished' );
|
88 |
/**
|
89 |
* @param $assoc_args
|
90 |
*/
|
91 |
+
private function do_update( $force ) {
|
92 |
$settings = new Settings();
|
93 |
|
94 |
$installer = new Installer( $settings );
|
95 |
if ( ! $installer->looks_like_it_is_installed() ) {
|
96 |
WP_CLI::log( 'Skipping as looks like Matomo is not yet installed' );
|
97 |
+
|
98 |
return;
|
99 |
}
|
100 |
|
112 |
WP_CLI::add_command(
|
113 |
'matomo',
|
114 |
'\WpMatomo\Commands\MatomoCommands',
|
115 |
+
[
|
116 |
'shortdesc' => 'Manage your Matomo Analytics. Commands are recommended only to be used in development mode',
|
117 |
+
]
|
118 |
);
|
classes/WpMatomo/Compatibility.php
CHANGED
@@ -14,7 +14,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
14 |
}
|
15 |
|
16 |
class Compatibility {
|
17 |
-
|
18 |
public function register_hooks() {
|
19 |
$this->ithemes_security();
|
20 |
}
|
@@ -35,13 +34,13 @@ class Compatibility {
|
|
35 |
// todo ideally we would make the plugins path relative and match the specific path...
|
36 |
// like preg_quote(relative_wp_content_dir)...
|
37 |
$is_wp_content_dir_compatible = defined( 'WP_CONTENT_DIR' )
|
38 |
-
|
39 |
if ( $rules
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
$rules = '
|
46 |
<IfModule mod_rewrite.c>
|
47 |
RewriteEngine On
|
@@ -51,11 +50,11 @@ class Compatibility {
|
|
51 |
</IfModule>
|
52 |
' . $rules;
|
53 |
}
|
|
|
54 |
return $rules;
|
55 |
},
|
56 |
9999999991,
|
57 |
-
$
|
58 |
);
|
59 |
}
|
60 |
-
|
61 |
}
|
14 |
}
|
15 |
|
16 |
class Compatibility {
|
|
|
17 |
public function register_hooks() {
|
18 |
$this->ithemes_security();
|
19 |
}
|
34 |
// todo ideally we would make the plugins path relative and match the specific path...
|
35 |
// like preg_quote(relative_wp_content_dir)...
|
36 |
$is_wp_content_dir_compatible = defined( 'WP_CONTENT_DIR' )
|
37 |
+
&& ABSPATH . 'wp-content' === rtrim( WP_CONTENT_DIR, '/' );
|
38 |
if ( $rules
|
39 |
+
&& $is_wp_content_dir_compatible
|
40 |
+
&& is_string( $rules )
|
41 |
+
&& strpos( $rules, 'RewriteEngine On' ) > 0
|
42 |
+
&& strpos( $rules, 'content' ) > 0
|
43 |
+
&& strpos( $rules, 'plugins' ) > 0 ) {
|
44 |
$rules = '
|
45 |
<IfModule mod_rewrite.c>
|
46 |
RewriteEngine On
|
50 |
</IfModule>
|
51 |
' . $rules;
|
52 |
}
|
53 |
+
|
54 |
return $rules;
|
55 |
},
|
56 |
9999999991,
|
57 |
+
$accepted_args = 1
|
58 |
);
|
59 |
}
|
|
|
60 |
}
|
classes/WpMatomo/Db/Settings.php
CHANGED
@@ -12,7 +12,17 @@ namespace WpMatomo\Db;
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
class Settings {
|
17 |
|
18 |
/**
|
@@ -23,7 +33,7 @@ class Settings {
|
|
23 |
* @return string
|
24 |
* @api
|
25 |
*/
|
26 |
-
public function prefix_table_name( $table_name_to_prefix = '') {
|
27 |
global $wpdb;
|
28 |
|
29 |
return $wpdb->prefix . MATOMO_DATABASE_PREFIX . $table_name_to_prefix;
|
@@ -35,7 +45,7 @@ class Settings {
|
|
35 |
public function get_matomo_tables() {
|
36 |
// we need to hard code them unfortunately for tests cause there are temporary tables used and we can't find a
|
37 |
// list of existing temp tables
|
38 |
-
$tables =
|
39 |
'access',
|
40 |
'archive_invalidations',
|
41 |
'brute_force_log',
|
@@ -65,24 +75,30 @@ class Settings {
|
|
65 |
'user_dashboard',
|
66 |
'user_language',
|
67 |
'user_token_auth',
|
68 |
-
|
69 |
-
if ( !is_multisite() ) {
|
70 |
-
$tables = array_merge(
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
|
|
|
|
|
|
|
|
|
|
76 |
}
|
|
|
77 |
return $tables;
|
78 |
}
|
79 |
|
80 |
public function get_installed_matomo_tables() {
|
81 |
global $wpdb;
|
82 |
|
83 |
-
$table_names =
|
84 |
|
85 |
-
$tables
|
86 |
foreach ( $tables as $table_name_to_look_for ) {
|
87 |
$table_names[] = array_shift( $table_name_to_look_for );
|
88 |
}
|
@@ -98,7 +114,7 @@ class Settings {
|
|
98 |
$table_names_to_look_for = apply_filters( 'matomo_install_tables', $table_names_to_look_for );
|
99 |
|
100 |
foreach ( $table_names_to_look_for as $table_name_to_look_for ) {
|
101 |
-
$table_name_to_test = $this->prefix_table_name($table_name_to_look_for);
|
102 |
if ( ! in_array( $table_name_to_test, $table_names, true ) ) {
|
103 |
$table_names[] = $table_name_to_test;
|
104 |
}
|
@@ -106,5 +122,4 @@ class Settings {
|
|
106 |
|
107 |
return $table_names;
|
108 |
}
|
109 |
-
|
110 |
}
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
+
/**
|
16 |
+
* We want a real data, not something coming from cache
|
17 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
|
18 |
+
*
|
19 |
+
* This is a report error, so silent the possible errors
|
20 |
+
* phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
|
21 |
+
*
|
22 |
+
* We cannot use parameters of statements as this is the table names we build
|
23 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
|
24 |
+
* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
25 |
+
*/
|
26 |
class Settings {
|
27 |
|
28 |
/**
|
33 |
* @return string
|
34 |
* @api
|
35 |
*/
|
36 |
+
public function prefix_table_name( $table_name_to_prefix = '' ) {
|
37 |
global $wpdb;
|
38 |
|
39 |
return $wpdb->prefix . MATOMO_DATABASE_PREFIX . $table_name_to_prefix;
|
45 |
public function get_matomo_tables() {
|
46 |
// we need to hard code them unfortunately for tests cause there are temporary tables used and we can't find a
|
47 |
// list of existing temp tables
|
48 |
+
$tables = [
|
49 |
'access',
|
50 |
'archive_invalidations',
|
51 |
'brute_force_log',
|
75 |
'user_dashboard',
|
76 |
'user_language',
|
77 |
'user_token_auth',
|
78 |
+
];
|
79 |
+
if ( ! is_multisite() ) {
|
80 |
+
$tables = array_merge(
|
81 |
+
$tables,
|
82 |
+
[
|
83 |
+
'tagmanager_container',
|
84 |
+
'tagmanager_container_release',
|
85 |
+
'tagmanager_container_version',
|
86 |
+
'tagmanager_tag',
|
87 |
+
'tagmanager_trigger',
|
88 |
+
'tagmanager_variable',
|
89 |
+
]
|
90 |
+
);
|
91 |
}
|
92 |
+
|
93 |
return $tables;
|
94 |
}
|
95 |
|
96 |
public function get_installed_matomo_tables() {
|
97 |
global $wpdb;
|
98 |
|
99 |
+
$table_names = [];
|
100 |
|
101 |
+
$tables = $wpdb->get_results( 'SHOW TABLES LIKE "' . $this->prefix_table_name() . '%"', ARRAY_N );
|
102 |
foreach ( $tables as $table_name_to_look_for ) {
|
103 |
$table_names[] = array_shift( $table_name_to_look_for );
|
104 |
}
|
114 |
$table_names_to_look_for = apply_filters( 'matomo_install_tables', $table_names_to_look_for );
|
115 |
|
116 |
foreach ( $table_names_to_look_for as $table_name_to_look_for ) {
|
117 |
+
$table_name_to_test = $this->prefix_table_name( $table_name_to_look_for );
|
118 |
if ( ! in_array( $table_name_to_test, $table_names, true ) ) {
|
119 |
$table_names[] = $table_name_to_test;
|
120 |
}
|
122 |
|
123 |
return $table_names;
|
124 |
}
|
|
|
125 |
}
|
classes/WpMatomo/Db/WordPress.php
CHANGED
@@ -347,10 +347,10 @@ class WordPress extends Mysqli {
|
|
347 |
}
|
348 |
|
349 |
if ( defined( 'WP_DEBUG' )
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
// prevent showing some notices in frontend eg if cronjob runs there
|
355 |
|
356 |
$is_likely_dedicated_cron = defined( 'DOING_CRON' ) && DOING_CRON && defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
|
@@ -363,8 +363,8 @@ class WordPress extends Mysqli {
|
|
363 |
}
|
364 |
|
365 |
if ( ( stripos( $sql, '/* WP IGNORE ERROR */' ) !== false )
|
366 |
-
|
367 |
-
|
368 |
// prevent notices for queries that are expected to fail
|
369 |
// SELECT 1 FROM wp_matomo_logtmpsegment1cc77bce7a13181081e44ea6ffc0a9fd LIMIT 1 => runs to detect if temp table exists or not and regularly the query fails which is expected
|
370 |
// SELECT @@TX_ISOLATION => not available in all mysql versions
|
347 |
}
|
348 |
|
349 |
if ( defined( 'WP_DEBUG' )
|
350 |
+
&& WP_DEBUG
|
351 |
+
&& defined( 'WP_DEBUG_DISPLAY' )
|
352 |
+
&& WP_DEBUG_DISPLAY
|
353 |
+
&& ! is_admin() ) {
|
354 |
// prevent showing some notices in frontend eg if cronjob runs there
|
355 |
|
356 |
$is_likely_dedicated_cron = defined( 'DOING_CRON' ) && DOING_CRON && defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
|
363 |
}
|
364 |
|
365 |
if ( ( stripos( $sql, '/* WP IGNORE ERROR */' ) !== false )
|
366 |
+
|| stripos( $sql, 'SELECT @@TX_ISOLATION' ) !== false
|
367 |
+
|| stripos( $sql, 'SELECT @@transaction_isolation' ) !== false ) {
|
368 |
// prevent notices for queries that are expected to fail
|
369 |
// SELECT 1 FROM wp_matomo_logtmpsegment1cc77bce7a13181081e44ea6ffc0a9fd LIMIT 1 => runs to detect if temp table exists or not and regularly the query fails which is expected
|
370 |
// SELECT @@TX_ISOLATION => not available in all mysql versions
|
classes/WpMatomo/Db/WordPressDbStatement.php
CHANGED
@@ -9,12 +9,23 @@
|
|
9 |
|
10 |
namespace Piwik\Db\Adapter;
|
11 |
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
private $result;
|
19 |
private $sql;
|
20 |
|
9 |
|
10 |
namespace Piwik\Db\Adapter;
|
11 |
|
12 |
+
use Zend_Db_Statement;
|
13 |
+
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
17 |
+
/**
|
18 |
+
* We want a real data, not something coming from cache
|
19 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
|
20 |
+
*
|
21 |
+
* This is a report error, so silent the possible errors
|
22 |
+
* phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
|
23 |
+
*
|
24 |
+
* We cannot use parameters of statements as this is the table names we build
|
25 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
|
26 |
+
* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
27 |
+
*/
|
28 |
+
class WordPressDbStatement extends Zend_Db_Statement {
|
29 |
private $result;
|
30 |
private $sql;
|
31 |
|
classes/WpMatomo/Db/WordPressTracker.php
CHANGED
@@ -76,10 +76,10 @@ class WordPress extends Mysqli {
|
|
76 |
*/
|
77 |
private function before_execute_query( $wpdb, $sql ) {
|
78 |
if ( ! $wpdb->suppress_errors
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
// we want to prevent showing these notices
|
84 |
if ( defined( 'MATOMO_SUPPRESS_DB_ERRORS' ) ) {
|
85 |
if ( MATOMO_SUPPRESS_DB_ERRORS === true ) {
|
76 |
*/
|
77 |
private function before_execute_query( $wpdb, $sql ) {
|
78 |
if ( ! $wpdb->suppress_errors
|
79 |
+
&& defined( 'WP_DEBUG' )
|
80 |
+
&& WP_DEBUG
|
81 |
+
&& defined( 'WP_DEBUG_DISPLAY' )
|
82 |
+
&& WP_DEBUG_DISPLAY ) {
|
83 |
// we want to prevent showing these notices
|
84 |
if ( defined( 'MATOMO_SUPPRESS_DB_ERRORS' ) ) {
|
85 |
if ( MATOMO_SUPPRESS_DB_ERRORS === true ) {
|
classes/WpMatomo/Ecommerce/Base.php
CHANGED
@@ -9,10 +9,12 @@
|
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
|
|
|
|
12 |
use WpMatomo\Admin\TrackingSettings;
|
|
|
13 |
use WpMatomo\Logger;
|
14 |
use WpMatomo\Settings;
|
15 |
-
use WpMatomo\AjaxTracker;
|
16 |
|
17 |
if ( ! defined( 'ABSPATH' ) ) {
|
18 |
exit; // if accessed directly
|
@@ -40,7 +42,7 @@ class Base {
|
|
40 |
*/
|
41 |
protected $cart_update_queue = '';
|
42 |
|
43 |
-
private $ajax_tracker_calls =
|
44 |
|
45 |
public function __construct( AjaxTracker $tracker ) {
|
46 |
$this->logger = new Logger();
|
@@ -52,18 +54,20 @@ class Base {
|
|
52 |
|
53 |
public function register_hooks() {
|
54 |
if ( ! is_admin() ) {
|
55 |
-
add_action( 'wp_footer',
|
56 |
}
|
57 |
}
|
58 |
|
59 |
public function on_print_queues() {
|
60 |
// we need to queue in case there are multiple cart updates within one page load
|
61 |
if ( ! empty( $this->cart_update_queue ) ) {
|
|
|
62 |
echo $this->cart_update_queue;
|
63 |
}
|
64 |
}
|
65 |
|
66 |
protected function has_order_been_tracked_already( $order_id ) {
|
|
|
67 |
return get_post_meta( $order_id, $this->key_order_tracked, true ) == 1;
|
68 |
}
|
69 |
|
@@ -72,8 +76,8 @@ class Base {
|
|
72 |
}
|
73 |
|
74 |
protected function should_track_background() {
|
75 |
-
return (defined( 'DOING_AJAX' ) && DOING_AJAX)
|
76 |
-
|
77 |
}
|
78 |
|
79 |
protected function make_matomo_js_tracker_call( $params ) {
|
@@ -87,22 +91,22 @@ class Base {
|
|
87 |
protected function wrap_script( $script ) {
|
88 |
if ( $this->should_track_background() ) {
|
89 |
foreach ( $this->ajax_tracker_calls as $call ) {
|
90 |
-
$methods =
|
91 |
'addEcommerceItem' => 'addEcommerceItem',
|
92 |
'trackEcommerceOrder' => 'doTrackEcommerceOrder',
|
93 |
'trackEcommerceCartUpdate' => 'doTrackEcommerceCartUpdate',
|
94 |
-
|
95 |
if ( ! empty( $call[0] ) && ! empty( $methods[ $call[0] ] ) ) {
|
96 |
try {
|
97 |
$tracker_method = $methods[ $call[0] ];
|
98 |
array_shift( $call );
|
99 |
-
call_user_func_array(
|
100 |
-
} catch (
|
101 |
-
$this->logger->log_exception($call[0], $e);
|
102 |
}
|
103 |
}
|
104 |
}
|
105 |
-
$this->ajax_tracker_calls =
|
106 |
|
107 |
return '';
|
108 |
}
|
@@ -111,7 +115,13 @@ class Base {
|
|
111 |
return '';
|
112 |
}
|
113 |
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
116 |
|
|
|
|
|
117 |
}
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
12 |
+
use Exception;
|
13 |
+
use WpMatomo;
|
14 |
use WpMatomo\Admin\TrackingSettings;
|
15 |
+
use WpMatomo\AjaxTracker;
|
16 |
use WpMatomo\Logger;
|
17 |
use WpMatomo\Settings;
|
|
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
42 |
*/
|
43 |
protected $cart_update_queue = '';
|
44 |
|
45 |
+
private $ajax_tracker_calls = [];
|
46 |
|
47 |
public function __construct( AjaxTracker $tracker ) {
|
48 |
$this->logger = new Logger();
|
54 |
|
55 |
public function register_hooks() {
|
56 |
if ( ! is_admin() ) {
|
57 |
+
add_action( 'wp_footer', [ $this, 'on_print_queues' ], 99999, 0 );
|
58 |
}
|
59 |
}
|
60 |
|
61 |
public function on_print_queues() {
|
62 |
// we need to queue in case there are multiple cart updates within one page load
|
63 |
if ( ! empty( $this->cart_update_queue ) ) {
|
64 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
65 |
echo $this->cart_update_queue;
|
66 |
}
|
67 |
}
|
68 |
|
69 |
protected function has_order_been_tracked_already( $order_id ) {
|
70 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
71 |
return get_post_meta( $order_id, $this->key_order_tracked, true ) == 1;
|
72 |
}
|
73 |
|
76 |
}
|
77 |
|
78 |
protected function should_track_background() {
|
79 |
+
return ( defined( 'DOING_AJAX' ) && DOING_AJAX )
|
80 |
+
|| WpMatomo::$settings->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_TAGMANAGER;
|
81 |
}
|
82 |
|
83 |
protected function make_matomo_js_tracker_call( $params ) {
|
91 |
protected function wrap_script( $script ) {
|
92 |
if ( $this->should_track_background() ) {
|
93 |
foreach ( $this->ajax_tracker_calls as $call ) {
|
94 |
+
$methods = [
|
95 |
'addEcommerceItem' => 'addEcommerceItem',
|
96 |
'trackEcommerceOrder' => 'doTrackEcommerceOrder',
|
97 |
'trackEcommerceCartUpdate' => 'doTrackEcommerceCartUpdate',
|
98 |
+
];
|
99 |
if ( ! empty( $call[0] ) && ! empty( $methods[ $call[0] ] ) ) {
|
100 |
try {
|
101 |
$tracker_method = $methods[ $call[0] ];
|
102 |
array_shift( $call );
|
103 |
+
call_user_func_array( [ $this->tracker, $tracker_method ], $call );
|
104 |
+
} catch ( Exception $e ) {
|
105 |
+
$this->logger->log_exception( $call[0], $e );
|
106 |
}
|
107 |
}
|
108 |
}
|
109 |
+
$this->ajax_tracker_calls = [];
|
110 |
|
111 |
return '';
|
112 |
}
|
115 |
return '';
|
116 |
}
|
117 |
|
118 |
+
if ( function_exists( 'wp_get_inline_script_tag' ) ) {
|
119 |
+
$script = wp_get_inline_script_tag( $script );
|
120 |
+
} else {
|
121 |
+
// line feed is required to match the wp_get_inline_script_tag output
|
122 |
+
$script = '<script >' . PHP_EOL . $script . PHP_EOL . '</script>' . PHP_EOL;
|
123 |
+
}
|
124 |
|
125 |
+
return $script;
|
126 |
+
}
|
127 |
}
|
classes/WpMatomo/Ecommerce/EasyDigitalDownloads.php
CHANGED
@@ -9,26 +9,27 @@
|
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
class EasyDigitalDownloads extends Base {
|
17 |
-
|
18 |
public function register_hooks() {
|
19 |
if ( ! is_admin() ) {
|
20 |
-
add_action( 'template_redirect',
|
21 |
}
|
22 |
|
23 |
parent::register_hooks();
|
24 |
|
25 |
// these actions may be triggered in admin when ajax is used
|
26 |
-
add_action( 'edd_payment_receipt_after_table',
|
27 |
-
add_action( 'edd_post_remove_from_cart',
|
28 |
-
add_action( 'edd_post_add_to_cart',
|
29 |
-
add_action( 'edd_cart_discounts_removed',
|
30 |
-
add_action( 'edd_after_set_cart_item_quantity',
|
31 |
-
add_action( 'edd_cart_discount_set',
|
32 |
}
|
33 |
|
34 |
public function on_cart_update() {
|
@@ -43,7 +44,7 @@ class EasyDigitalDownloads extends Base {
|
|
43 |
|
44 |
$tracking_code = '';
|
45 |
foreach ( $contents as $key => $item ) {
|
46 |
-
$download = new
|
47 |
|
48 |
// If the item is not a download or it's status has changed since it was added to the cart.
|
49 |
if ( empty( $download->ID ) || ! $download->can_purchase() ) {
|
@@ -64,7 +65,7 @@ class EasyDigitalDownloads extends Base {
|
|
64 |
$categories = $this->get_product_categories( $download->ID );
|
65 |
$quantity = isset( $item['quantity'] ) ? $item['quantity'] : 0;
|
66 |
|
67 |
-
$params =
|
68 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
69 |
}
|
70 |
|
@@ -75,7 +76,7 @@ class EasyDigitalDownloads extends Base {
|
|
75 |
$total = $cart->get_total();
|
76 |
}
|
77 |
|
78 |
-
$tracking_code .= $this->make_matomo_js_tracker_call(
|
79 |
|
80 |
// we can't echo directly as we wouldn't know where in the template rendering stage we are and whether
|
81 |
// we're supposed to print or not etc
|
@@ -90,7 +91,7 @@ class EasyDigitalDownloads extends Base {
|
|
90 |
}
|
91 |
|
92 |
/**
|
93 |
-
* @param
|
94 |
*
|
95 |
* @return mixed
|
96 |
*/
|
@@ -118,18 +119,18 @@ class EasyDigitalDownloads extends Base {
|
|
118 |
return;
|
119 |
}
|
120 |
|
121 |
-
$download = new
|
122 |
|
123 |
$sku = $this->get_sku( $download, $download_id );
|
124 |
|
125 |
-
$params =
|
126 |
'setEcommerceView',
|
127 |
$sku,
|
128 |
$download->get_name(),
|
129 |
$this->get_product_categories( $download_id ),
|
130 |
$download->get_price(),
|
131 |
-
|
132 |
-
|
133 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
134 |
}
|
135 |
|
@@ -175,7 +176,7 @@ class EasyDigitalDownloads extends Base {
|
|
175 |
$name .= ' - ' . edd_get_price_option_name( $item['id'], $price_id );
|
176 |
}
|
177 |
|
178 |
-
$download = new
|
179 |
$sku = $this->get_sku( $download, $item['id'] );
|
180 |
|
181 |
$price = 0;
|
@@ -183,14 +184,14 @@ class EasyDigitalDownloads extends Base {
|
|
183 |
$price = $item['item_price'];
|
184 |
}
|
185 |
|
186 |
-
$params =
|
187 |
'addEcommerceItem',
|
188 |
$sku,
|
189 |
$name,
|
190 |
$this->get_product_categories( $item['id'] ),
|
191 |
$price,
|
192 |
$item['quantity'],
|
193 |
-
|
194 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
195 |
}
|
196 |
}
|
@@ -208,7 +209,7 @@ class EasyDigitalDownloads extends Base {
|
|
208 |
$discount = reset( $discount );
|
209 |
}
|
210 |
|
211 |
-
$params =
|
212 |
'trackEcommerceOrder',
|
213 |
'' . $order_id_to_track,
|
214 |
$grand_total ? $grand_total : 0,
|
@@ -216,11 +217,10 @@ class EasyDigitalDownloads extends Base {
|
|
216 |
edd_use_taxes() ? edd_get_payment_tax( $payment->ID, $payment_meta ) : '0',
|
217 |
$shipping = 0,
|
218 |
$discount,
|
219 |
-
|
220 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
221 |
-
|
222 |
echo $this->wrap_script( $tracking_code );
|
223 |
}
|
224 |
}
|
225 |
-
|
226 |
}
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
12 |
+
use EDD_Download;
|
13 |
+
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
17 |
|
18 |
class EasyDigitalDownloads extends Base {
|
|
|
19 |
public function register_hooks() {
|
20 |
if ( ! is_admin() ) {
|
21 |
+
add_action( 'template_redirect', [ $this, 'on_product_view' ], 99999, 0 );
|
22 |
}
|
23 |
|
24 |
parent::register_hooks();
|
25 |
|
26 |
// these actions may be triggered in admin when ajax is used
|
27 |
+
add_action( 'edd_payment_receipt_after_table', [ $this, 'on_order' ], 99999, 2 );
|
28 |
+
add_action( 'edd_post_remove_from_cart', [ $this, 'on_cart_update' ], 99999, 0 );
|
29 |
+
add_action( 'edd_post_add_to_cart', [ $this, 'on_cart_update' ], 99999, 0 );
|
30 |
+
add_action( 'edd_cart_discounts_removed', [ $this, 'on_cart_update' ], 99999, 0 );
|
31 |
+
add_action( 'edd_after_set_cart_item_quantity', [ $this, 'on_cart_update' ], 99999, 0 );
|
32 |
+
add_action( 'edd_cart_discount_set', [ $this, 'on_cart_update' ], 99999, 0 );
|
33 |
}
|
34 |
|
35 |
public function on_cart_update() {
|
44 |
|
45 |
$tracking_code = '';
|
46 |
foreach ( $contents as $key => $item ) {
|
47 |
+
$download = new EDD_Download( $item['id'] );
|
48 |
|
49 |
// If the item is not a download or it's status has changed since it was added to the cart.
|
50 |
if ( empty( $download->ID ) || ! $download->can_purchase() ) {
|
65 |
$categories = $this->get_product_categories( $download->ID );
|
66 |
$quantity = isset( $item['quantity'] ) ? $item['quantity'] : 0;
|
67 |
|
68 |
+
$params = [ 'addEcommerceItem', $sku, $name, $categories, $price, $quantity ];
|
69 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
70 |
}
|
71 |
|
76 |
$total = $cart->get_total();
|
77 |
}
|
78 |
|
79 |
+
$tracking_code .= $this->make_matomo_js_tracker_call( [ 'trackEcommerceCartUpdate', $total ] );
|
80 |
|
81 |
// we can't echo directly as we wouldn't know where in the template rendering stage we are and whether
|
82 |
// we're supposed to print or not etc
|
91 |
}
|
92 |
|
93 |
/**
|
94 |
+
* @param EDD_Download $download
|
95 |
*
|
96 |
* @return mixed
|
97 |
*/
|
119 |
return;
|
120 |
}
|
121 |
|
122 |
+
$download = new EDD_Download( $download_id );
|
123 |
|
124 |
$sku = $this->get_sku( $download, $download_id );
|
125 |
|
126 |
+
$params = [
|
127 |
'setEcommerceView',
|
128 |
$sku,
|
129 |
$download->get_name(),
|
130 |
$this->get_product_categories( $download_id ),
|
131 |
$download->get_price(),
|
132 |
+
];
|
133 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
134 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
135 |
}
|
136 |
|
176 |
$name .= ' - ' . edd_get_price_option_name( $item['id'], $price_id );
|
177 |
}
|
178 |
|
179 |
+
$download = new EDD_Download( $item['id'] );
|
180 |
$sku = $this->get_sku( $download, $item['id'] );
|
181 |
|
182 |
$price = 0;
|
184 |
$price = $item['item_price'];
|
185 |
}
|
186 |
|
187 |
+
$params = [
|
188 |
'addEcommerceItem',
|
189 |
$sku,
|
190 |
$name,
|
191 |
$this->get_product_categories( $item['id'] ),
|
192 |
$price,
|
193 |
$item['quantity'],
|
194 |
+
];
|
195 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
196 |
}
|
197 |
}
|
209 |
$discount = reset( $discount );
|
210 |
}
|
211 |
|
212 |
+
$params = [
|
213 |
'trackEcommerceOrder',
|
214 |
'' . $order_id_to_track,
|
215 |
$grand_total ? $grand_total : 0,
|
217 |
edd_use_taxes() ? edd_get_payment_tax( $payment->ID, $payment_meta ) : '0',
|
218 |
$shipping = 0,
|
219 |
$discount,
|
220 |
+
];
|
221 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
222 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
223 |
echo $this->wrap_script( $tracking_code );
|
224 |
}
|
225 |
}
|
|
|
226 |
}
|
classes/WpMatomo/Ecommerce/MatomoTestEcommerce.php
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace WpMatomo\Ecommerce;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* this class is required only for phpunit tests.
|
7 |
+
* It allow to change the visibility of some methods of the Base class
|
8 |
+
* and so allow to test them in the unit tests
|
9 |
+
*
|
10 |
+
* phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
|
11 |
+
*/
|
12 |
+
class MatomoTestEcommerce extends Base {
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Render public the wrap_script method. Required for the unit tests
|
16 |
+
*
|
17 |
+
* @param string $script
|
18 |
+
*
|
19 |
+
* @return string
|
20 |
+
* @see Base::wrap_script()
|
21 |
+
*/
|
22 |
+
public function wrap_script( $script ) {
|
23 |
+
return parent::wrap_script( $script );
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Render public the wrap_script method. Required for the unit tests
|
28 |
+
*
|
29 |
+
* @param [] $params
|
30 |
+
*
|
31 |
+
* @return string
|
32 |
+
* @see Base::make_matomo_js_tracker_call()
|
33 |
+
*/
|
34 |
+
public function make_matomo_js_tracker_call( $params ) {
|
35 |
+
return parent::make_matomo_js_tracker_call( $params );
|
36 |
+
}
|
37 |
+
}
|
classes/WpMatomo/Ecommerce/MemberPress.php
CHANGED
@@ -9,41 +9,43 @@
|
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
|
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
class MemberPress extends Base {
|
17 |
-
|
18 |
public function register_hooks() {
|
19 |
if ( ! is_admin() ) {
|
20 |
parent::register_hooks();
|
21 |
|
22 |
-
add_action( 'template_redirect',
|
23 |
-
add_action( 'wp_footer',
|
24 |
-
add_action( 'mepr-signup',
|
25 |
}
|
26 |
}
|
27 |
|
28 |
/**
|
29 |
-
* @param
|
30 |
*/
|
31 |
public function on_cart_update( $transaction ) {
|
32 |
$tracking_code = '';
|
33 |
$sku = $transaction->id;
|
34 |
$product = $transaction->product();
|
35 |
-
$params =
|
36 |
'addEcommerceItem',
|
37 |
$sku,
|
38 |
$product->post_title,
|
39 |
-
$categories =
|
40 |
$transaction->amount,
|
41 |
1,
|
42 |
-
|
43 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
44 |
|
45 |
$total = $transaction->total;
|
46 |
-
$tracking_code .= $this->make_matomo_js_tracker_call(
|
47 |
|
48 |
// we can't echo directly as we wouldn't know where in the template rendering stage we are and whether
|
49 |
// we're supposed to print or not etc
|
@@ -66,18 +68,18 @@ class MemberPress extends Base {
|
|
66 |
return;
|
67 |
}
|
68 |
|
69 |
-
$product = new
|
70 |
|
71 |
$sku = $product_id;
|
72 |
|
73 |
-
$params =
|
74 |
'setEcommerceView',
|
75 |
'' . $sku,
|
76 |
$product->post_title,
|
77 |
-
$categories =
|
78 |
$product->price,
|
79 |
-
|
80 |
-
|
81 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
82 |
}
|
83 |
|
@@ -85,13 +87,13 @@ class MemberPress extends Base {
|
|
85 |
if ( isset( $_GET['membership'] )
|
86 |
&& isset( $_GET['trans_num'] )
|
87 |
&& class_exists( '\MeprTransaction' ) ) {
|
88 |
-
$txn =
|
89 |
if ( isset( $txn->id ) && $txn->id > 0 ) {
|
90 |
if ( $this->has_order_been_tracked_already( $txn->id ) ) {
|
91 |
return;
|
92 |
}
|
93 |
$this->set_order_been_tracked( $txn->id );
|
94 |
-
$transaction = new
|
95 |
$order_id_to_track = $txn->trans_num;
|
96 |
$product = $transaction->product();
|
97 |
|
@@ -101,16 +103,16 @@ class MemberPress extends Base {
|
|
101 |
$discount = $product->price - $txn->amount;
|
102 |
}
|
103 |
$tracking_code = '';
|
104 |
-
$params =
|
105 |
'addEcommerceItem',
|
106 |
'' . $product->ID,
|
107 |
$product->post_title,
|
108 |
-
|
109 |
$txn->amount,
|
110 |
1,
|
111 |
-
|
112 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
113 |
-
$params =
|
114 |
'trackEcommerceOrder',
|
115 |
'' . $order_id_to_track,
|
116 |
$txn->total,
|
@@ -118,12 +120,11 @@ class MemberPress extends Base {
|
|
118 |
$txn->tax_amount,
|
119 |
$shipping = 0,
|
120 |
$discount,
|
121 |
-
|
122 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
123 |
-
|
124 |
echo $this->wrap_script( $tracking_code );
|
125 |
}
|
126 |
}
|
127 |
}
|
128 |
-
|
129 |
}
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
12 |
+
use MeprProduct;
|
13 |
+
use MeprTransaction;
|
14 |
+
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
|
19 |
class MemberPress extends Base {
|
|
|
20 |
public function register_hooks() {
|
21 |
if ( ! is_admin() ) {
|
22 |
parent::register_hooks();
|
23 |
|
24 |
+
add_action( 'template_redirect', [ $this, 'on_product_view' ], 99999, 0 );
|
25 |
+
add_action( 'wp_footer', [ $this, 'on_order' ], 99999, 2 );
|
26 |
+
add_action( 'mepr-signup', [ $this, 'on_cart_update' ], 99999, 1 );
|
27 |
}
|
28 |
}
|
29 |
|
30 |
/**
|
31 |
+
* @param MeprTransaction $transaction
|
32 |
*/
|
33 |
public function on_cart_update( $transaction ) {
|
34 |
$tracking_code = '';
|
35 |
$sku = $transaction->id;
|
36 |
$product = $transaction->product();
|
37 |
+
$params = [
|
38 |
'addEcommerceItem',
|
39 |
$sku,
|
40 |
$product->post_title,
|
41 |
+
$categories = [],
|
42 |
$transaction->amount,
|
43 |
1,
|
44 |
+
];
|
45 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
46 |
|
47 |
$total = $transaction->total;
|
48 |
+
$tracking_code .= $this->make_matomo_js_tracker_call( [ 'trackEcommerceCartUpdate', $total ] );
|
49 |
|
50 |
// we can't echo directly as we wouldn't know where in the template rendering stage we are and whether
|
51 |
// we're supposed to print or not etc
|
68 |
return;
|
69 |
}
|
70 |
|
71 |
+
$product = new MeprProduct( $product_id );
|
72 |
|
73 |
$sku = $product_id;
|
74 |
|
75 |
+
$params = [
|
76 |
'setEcommerceView',
|
77 |
'' . $sku,
|
78 |
$product->post_title,
|
79 |
+
$categories = [],
|
80 |
$product->price,
|
81 |
+
];
|
82 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
83 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
84 |
}
|
85 |
|
87 |
if ( isset( $_GET['membership'] )
|
88 |
&& isset( $_GET['trans_num'] )
|
89 |
&& class_exists( '\MeprTransaction' ) ) {
|
90 |
+
$txn = MeprTransaction::get_one_by_trans_num( sanitize_text_field( wp_unslash( $_GET['trans_num'] ) ) );
|
91 |
if ( isset( $txn->id ) && $txn->id > 0 ) {
|
92 |
if ( $this->has_order_been_tracked_already( $txn->id ) ) {
|
93 |
return;
|
94 |
}
|
95 |
$this->set_order_been_tracked( $txn->id );
|
96 |
+
$transaction = new MeprTransaction( $txn->id );
|
97 |
$order_id_to_track = $txn->trans_num;
|
98 |
$product = $transaction->product();
|
99 |
|
103 |
$discount = $product->price - $txn->amount;
|
104 |
}
|
105 |
$tracking_code = '';
|
106 |
+
$params = [
|
107 |
'addEcommerceItem',
|
108 |
'' . $product->ID,
|
109 |
$product->post_title,
|
110 |
+
[],
|
111 |
$txn->amount,
|
112 |
1,
|
113 |
+
];
|
114 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
115 |
+
$params = [
|
116 |
'trackEcommerceOrder',
|
117 |
'' . $order_id_to_track,
|
118 |
$txn->total,
|
120 |
$txn->tax_amount,
|
121 |
$shipping = 0,
|
122 |
$discount,
|
123 |
+
];
|
124 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
125 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
126 |
echo $this->wrap_script( $tracking_code );
|
127 |
}
|
128 |
}
|
129 |
}
|
|
|
130 |
}
|
classes/WpMatomo/Ecommerce/Woocommerce.php
CHANGED
@@ -9,12 +9,15 @@
|
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
|
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
class Woocommerce extends Base {
|
17 |
-
private $order_status_ignore =
|
18 |
|
19 |
public function register_hooks() {
|
20 |
if ( is_admin() ) {
|
@@ -23,30 +26,38 @@ class Woocommerce extends Base {
|
|
23 |
|
24 |
parent::register_hooks();
|
25 |
|
26 |
-
add_action( 'wp_head',
|
27 |
-
add_action( 'woocommerce_after_single_product',
|
28 |
-
add_action( 'woocommerce_add_to_cart',
|
29 |
-
add_action( 'woocommerce_cart_item_removed',
|
30 |
-
add_action( 'woocommerce_cart_item_restored',
|
31 |
-
add_action( 'woocommerce_cart_item_set_quantity',
|
32 |
-
add_action('woocommerce_thankyou',
|
33 |
|
34 |
-
if (
|
35 |
// prevent possibly executing same event twice where eg first a PHP Matomo tracker request is created
|
36 |
// because of woocommerce_applied_coupon and then also because of woocommerce_update_cart_action_cart_updated itself
|
37 |
// causing two tracking requests to be issues from the server. refs #215
|
38 |
// when not ajax mode the later event will simply overwrite the first and it should be fine.
|
39 |
-
add_filter(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
}
|
41 |
|
42 |
-
add_action( 'woocommerce_applied_coupon',
|
43 |
-
add_action( 'woocommerce_removed_coupon',
|
44 |
}
|
45 |
|
46 |
-
public function anonymise_orderid_in_url($order_id)
|
47 |
-
|
48 |
-
if ( !empty($order_id) && is_numeric($order_id)) {
|
49 |
$order_id = (int) $order_id;
|
|
|
50 |
echo "<script>(function () {
|
51 |
if (location.href) {
|
52 |
window._paq = window._paq || [];
|
@@ -66,28 +77,28 @@ class Woocommerce extends Base {
|
|
66 |
if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
|
67 |
$order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0;
|
68 |
if ( ! empty( $order_id ) && $order_id > 0 ) {
|
|
|
69 |
echo $this->on_order( $order_id );
|
70 |
}
|
71 |
}
|
72 |
}
|
73 |
|
74 |
-
public function on_coupon_updated_safe(
|
75 |
-
|
76 |
try {
|
77 |
-
$val =
|
78 |
-
|
79 |
-
|
|
|
80 |
}
|
81 |
|
82 |
return $val;
|
83 |
}
|
84 |
|
85 |
public function on_cart_updated_safe( $val = null ) {
|
86 |
-
|
87 |
try {
|
88 |
-
$val = $this->on_cart_updated($val);
|
89 |
-
} catch (\Exception $e) {
|
90 |
-
$this->logger->log_exception('woo_on_cart_update', $e);
|
91 |
}
|
92 |
|
93 |
return $val;
|
@@ -104,7 +115,7 @@ class Woocommerce extends Base {
|
|
104 |
|
105 |
/** @var \WC_Cart $cart */
|
106 |
$cart = $woocommerce->cart;
|
107 |
-
if (
|
108 |
// can cause cart coupon not to be applied when WooCommerce Subscriptions is used.
|
109 |
$cart->calculate_totals();
|
110 |
}
|
@@ -113,7 +124,7 @@ class Woocommerce extends Base {
|
|
113 |
$tracking_code = '';
|
114 |
|
115 |
foreach ( $cart_content as $item ) {
|
116 |
-
/** @var
|
117 |
$product = wc_get_product( $item['product_id'] );
|
118 |
|
119 |
if ( $this->isWC3() ) {
|
@@ -126,7 +137,7 @@ class Woocommerce extends Base {
|
|
126 |
}
|
127 |
}
|
128 |
} else {
|
129 |
-
$order = new
|
130 |
$product_or_variation = $order->get_product_from_item( $item );
|
131 |
}
|
132 |
|
@@ -145,7 +156,7 @@ class Woocommerce extends Base {
|
|
145 |
$title = $product->get_title();
|
146 |
$categories = $this->get_product_categories( $product );
|
147 |
$quantity = isset( $item['quantity'] ) ? $item['quantity'] : 0;
|
148 |
-
$params =
|
149 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
150 |
}
|
151 |
|
@@ -156,7 +167,7 @@ class Woocommerce extends Base {
|
|
156 |
$total = $cart->cart_contents_total;
|
157 |
}
|
158 |
|
159 |
-
$tracking_code .= $this->make_matomo_js_tracker_call(
|
160 |
|
161 |
$this->cart_update_queue = $this->wrap_script( $tracking_code );
|
162 |
$this->logger->log( 'Tracked ecommerce cart update: ' . $this->cart_update_queue );
|
@@ -200,20 +211,20 @@ class Woocommerce extends Base {
|
|
200 |
$product_details = $this->get_product_details( $order, $item );
|
201 |
|
202 |
if ( ! empty( $product_details ) ) {
|
203 |
-
$params =
|
204 |
'addEcommerceItem',
|
205 |
'' . $product_details['sku'],
|
206 |
$product_details['title'],
|
207 |
$product_details['categories'],
|
208 |
$product_details['price'],
|
209 |
$product_details['quantity'],
|
210 |
-
|
211 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
212 |
}
|
213 |
}
|
214 |
}
|
215 |
|
216 |
-
$params =
|
217 |
'trackEcommerceOrder',
|
218 |
'' . $order_id_to_track,
|
219 |
$order->get_total(),
|
@@ -221,7 +232,7 @@ class Woocommerce extends Base {
|
|
221 |
$order->get_cart_tax(),
|
222 |
$this->isWC3() ? $order->get_shipping_total() : $order->get_total_shipping(),
|
223 |
$order->get_total_discount(),
|
224 |
-
|
225 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
226 |
|
227 |
$this->logger->log( sprintf( 'Tracked ecommerce order %s with number %s', $order_id, $order_id_to_track ) );
|
@@ -239,7 +250,7 @@ class Woocommerce extends Base {
|
|
239 |
}
|
240 |
|
241 |
/**
|
242 |
-
* @param
|
243 |
*/
|
244 |
private function get_sku( $product ) {
|
245 |
if ( $product && $product->get_sku() ) {
|
@@ -250,7 +261,7 @@ class Woocommerce extends Base {
|
|
250 |
}
|
251 |
|
252 |
/**
|
253 |
-
* @param
|
254 |
*/
|
255 |
private function get_product_id( $product ) {
|
256 |
if ( ! $product ) {
|
@@ -265,7 +276,7 @@ class Woocommerce extends Base {
|
|
265 |
}
|
266 |
|
267 |
/**
|
268 |
-
* @param
|
269 |
* @param $item
|
270 |
*
|
271 |
* @return mixed
|
@@ -273,10 +284,10 @@ class Woocommerce extends Base {
|
|
273 |
private function get_product_details( $order, $item ) {
|
274 |
$product_or_variation = false;
|
275 |
if ( $this->isWC3() && ! empty( $item ) && is_object( $item ) && method_exists( $item, 'get_product' ) && is_callable(
|
276 |
-
|
277 |
$item,
|
278 |
'get_product',
|
279 |
-
|
280 |
) ) {
|
281 |
$product_or_variation = $item->get_product();
|
282 |
} elseif ( method_exists( $order, 'get_product_from_item' ) ) {
|
@@ -303,17 +314,17 @@ class Woocommerce extends Base {
|
|
303 |
$categories = $this->get_product_categories( $product );
|
304 |
$quantity = $item['qty'];
|
305 |
|
306 |
-
return
|
307 |
'sku' => $sku,
|
308 |
'title' => $title,
|
309 |
'categories' => $categories,
|
310 |
'quantity' => $quantity,
|
311 |
'price' => $price,
|
312 |
-
|
313 |
}
|
314 |
|
315 |
/**
|
316 |
-
* @param
|
317 |
*
|
318 |
* @return array
|
319 |
*/
|
@@ -322,7 +333,7 @@ class Woocommerce extends Base {
|
|
322 |
|
323 |
$category_terms = get_the_terms( $product_id, 'product_cat' );
|
324 |
|
325 |
-
$categories =
|
326 |
|
327 |
if ( is_wp_error( $category_terms ) ) {
|
328 |
return $categories;
|
@@ -348,17 +359,17 @@ class Woocommerce extends Base {
|
|
348 |
return;
|
349 |
}
|
350 |
|
351 |
-
/** @var
|
352 |
-
$params =
|
353 |
'setEcommerceView',
|
354 |
$this->get_sku( $product ),
|
355 |
$product->get_title(),
|
356 |
$this->get_product_categories( $product ),
|
357 |
$product->get_price(),
|
358 |
-
|
359 |
|
360 |
// we're not using wc_enqueue_js eg to prevent sometimes this code from being minified on some JS minifier plugins
|
|
|
361 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
362 |
}
|
363 |
-
|
364 |
}
|
9 |
|
10 |
namespace WpMatomo\Ecommerce;
|
11 |
|
12 |
+
use WC_Order;
|
13 |
+
use WC_Product;
|
14 |
+
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
|
19 |
class Woocommerce extends Base {
|
20 |
+
private $order_status_ignore = [ 'cancelled', 'failed', 'refunded' ];
|
21 |
|
22 |
public function register_hooks() {
|
23 |
if ( is_admin() ) {
|
26 |
|
27 |
parent::register_hooks();
|
28 |
|
29 |
+
add_action( 'wp_head', [ $this, 'maybe_track_order_complete' ], 99999 );
|
30 |
+
add_action( 'woocommerce_after_single_product', [ $this, 'on_product_view' ], 99999, $args = 0 );
|
31 |
+
add_action( 'woocommerce_add_to_cart', [ $this, 'on_cart_updated_safe' ], 99999, 0 );
|
32 |
+
add_action( 'woocommerce_cart_item_removed', [ $this, 'on_cart_updated_safe' ], 99999, 0 );
|
33 |
+
add_action( 'woocommerce_cart_item_restored', [ $this, 'on_cart_updated_safe' ], 99999, 0 );
|
34 |
+
add_action( 'woocommerce_cart_item_set_quantity', [ $this, 'on_cart_updated_safe' ], 99999, 0 );
|
35 |
+
add_action( 'woocommerce_thankyou', [ $this, 'anonymise_orderid_in_url' ], 1, 1 );
|
36 |
|
37 |
+
if ( ! $this->should_track_background() ) {
|
38 |
// prevent possibly executing same event twice where eg first a PHP Matomo tracker request is created
|
39 |
// because of woocommerce_applied_coupon and then also because of woocommerce_update_cart_action_cart_updated itself
|
40 |
// causing two tracking requests to be issues from the server. refs #215
|
41 |
// when not ajax mode the later event will simply overwrite the first and it should be fine.
|
42 |
+
add_filter(
|
43 |
+
'woocommerce_update_cart_action_cart_updated',
|
44 |
+
[
|
45 |
+
$this,
|
46 |
+
'on_cart_updated_safe',
|
47 |
+
],
|
48 |
+
99999,
|
49 |
+
1
|
50 |
+
);
|
51 |
}
|
52 |
|
53 |
+
add_action( 'woocommerce_applied_coupon', [ $this, 'on_coupon_updated_safe' ], 99999, 0 );
|
54 |
+
add_action( 'woocommerce_removed_coupon', [ $this, 'on_coupon_updated_safe' ], 99999, 0 );
|
55 |
}
|
56 |
|
57 |
+
public function anonymise_orderid_in_url( $order_id ) {
|
58 |
+
if ( ! empty( $order_id ) && is_numeric( $order_id ) ) {
|
|
|
59 |
$order_id = (int) $order_id;
|
60 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
61 |
echo "<script>(function () {
|
62 |
if (location.href) {
|
63 |
window._paq = window._paq || [];
|
77 |
if ( function_exists( 'is_order_received_page' ) && is_order_received_page() ) {
|
78 |
$order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0;
|
79 |
if ( ! empty( $order_id ) && $order_id > 0 ) {
|
80 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
81 |
echo $this->on_order( $order_id );
|
82 |
}
|
83 |
}
|
84 |
}
|
85 |
|
86 |
+
public function on_coupon_updated_safe() {
|
|
|
87 |
try {
|
88 |
+
$val = null;
|
89 |
+
$val = $this->on_cart_updated( $val, true );
|
90 |
+
} catch ( \Exception $e ) {
|
91 |
+
$this->logger->log_exception( 'woo_on_cart_update', $e );
|
92 |
}
|
93 |
|
94 |
return $val;
|
95 |
}
|
96 |
|
97 |
public function on_cart_updated_safe( $val = null ) {
|
|
|
98 |
try {
|
99 |
+
$val = $this->on_cart_updated( $val );
|
100 |
+
} catch ( \Exception $e ) {
|
101 |
+
$this->logger->log_exception( 'woo_on_cart_update', $e );
|
102 |
}
|
103 |
|
104 |
return $val;
|
115 |
|
116 |
/** @var \WC_Cart $cart */
|
117 |
$cart = $woocommerce->cart;
|
118 |
+
if ( ! $is_coupon_update ) {
|
119 |
// can cause cart coupon not to be applied when WooCommerce Subscriptions is used.
|
120 |
$cart->calculate_totals();
|
121 |
}
|
124 |
$tracking_code = '';
|
125 |
|
126 |
foreach ( $cart_content as $item ) {
|
127 |
+
/** @var WC_Product $product */
|
128 |
$product = wc_get_product( $item['product_id'] );
|
129 |
|
130 |
if ( $this->isWC3() ) {
|
137 |
}
|
138 |
}
|
139 |
} else {
|
140 |
+
$order = new WC_Order( null );
|
141 |
$product_or_variation = $order->get_product_from_item( $item );
|
142 |
}
|
143 |
|
156 |
$title = $product->get_title();
|
157 |
$categories = $this->get_product_categories( $product );
|
158 |
$quantity = isset( $item['quantity'] ) ? $item['quantity'] : 0;
|
159 |
+
$params = [ 'addEcommerceItem', '' . $sku, $title, $categories, $price, $quantity ];
|
160 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
161 |
}
|
162 |
|
167 |
$total = $cart->cart_contents_total;
|
168 |
}
|
169 |
|
170 |
+
$tracking_code .= $this->make_matomo_js_tracker_call( [ 'trackEcommerceCartUpdate', $total ] );
|
171 |
|
172 |
$this->cart_update_queue = $this->wrap_script( $tracking_code );
|
173 |
$this->logger->log( 'Tracked ecommerce cart update: ' . $this->cart_update_queue );
|
211 |
$product_details = $this->get_product_details( $order, $item );
|
212 |
|
213 |
if ( ! empty( $product_details ) ) {
|
214 |
+
$params = [
|
215 |
'addEcommerceItem',
|
216 |
'' . $product_details['sku'],
|
217 |
$product_details['title'],
|
218 |
$product_details['categories'],
|
219 |
$product_details['price'],
|
220 |
$product_details['quantity'],
|
221 |
+
];
|
222 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
223 |
}
|
224 |
}
|
225 |
}
|
226 |
|
227 |
+
$params = [
|
228 |
'trackEcommerceOrder',
|
229 |
'' . $order_id_to_track,
|
230 |
$order->get_total(),
|
232 |
$order->get_cart_tax(),
|
233 |
$this->isWC3() ? $order->get_shipping_total() : $order->get_total_shipping(),
|
234 |
$order->get_total_discount(),
|
235 |
+
];
|
236 |
$tracking_code .= $this->make_matomo_js_tracker_call( $params );
|
237 |
|
238 |
$this->logger->log( sprintf( 'Tracked ecommerce order %s with number %s', $order_id, $order_id_to_track ) );
|
250 |
}
|
251 |
|
252 |
/**
|
253 |
+
* @param WC_Product $product
|
254 |
*/
|
255 |
private function get_sku( $product ) {
|
256 |
if ( $product && $product->get_sku() ) {
|
261 |
}
|
262 |
|
263 |
/**
|
264 |
+
* @param WC_Product $product
|
265 |
*/
|
266 |
private function get_product_id( $product ) {
|
267 |
if ( ! $product ) {
|
276 |
}
|
277 |
|
278 |
/**
|
279 |
+
* @param WC_Order $order
|
280 |
* @param $item
|
281 |
*
|
282 |
* @return mixed
|
284 |
private function get_product_details( $order, $item ) {
|
285 |
$product_or_variation = false;
|
286 |
if ( $this->isWC3() && ! empty( $item ) && is_object( $item ) && method_exists( $item, 'get_product' ) && is_callable(
|
287 |
+
[
|
288 |
$item,
|
289 |
'get_product',
|
290 |
+
]
|
291 |
) ) {
|
292 |
$product_or_variation = $item->get_product();
|
293 |
} elseif ( method_exists( $order, 'get_product_from_item' ) ) {
|
314 |
$categories = $this->get_product_categories( $product );
|
315 |
$quantity = $item['qty'];
|
316 |
|
317 |
+
return [
|
318 |
'sku' => $sku,
|
319 |
'title' => $title,
|
320 |
'categories' => $categories,
|
321 |
'quantity' => $quantity,
|
322 |
'price' => $price,
|
323 |
+
];
|
324 |
}
|
325 |
|
326 |
/**
|
327 |
+
* @param WC_Product $product
|
328 |
*
|
329 |
* @return array
|
330 |
*/
|
333 |
|
334 |
$category_terms = get_the_terms( $product_id, 'product_cat' );
|
335 |
|
336 |
+
$categories = [];
|
337 |
|
338 |
if ( is_wp_error( $category_terms ) ) {
|
339 |
return $categories;
|
359 |
return;
|
360 |
}
|
361 |
|
362 |
+
/** @var WC_Product $product */
|
363 |
+
$params = [
|
364 |
'setEcommerceView',
|
365 |
$this->get_sku( $product ),
|
366 |
$product->get_title(),
|
367 |
$this->get_product_categories( $product ),
|
368 |
$product->get_price(),
|
369 |
+
];
|
370 |
|
371 |
// we're not using wc_enqueue_js eg to prevent sometimes this code from being minified on some JS minifier plugins
|
372 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
373 |
echo $this->wrap_script( $this->make_matomo_js_tracker_call( $params ) );
|
374 |
}
|
|
|
375 |
}
|
classes/WpMatomo/Email.php
CHANGED
@@ -8,9 +8,13 @@
|
|
8 |
*/
|
9 |
|
10 |
namespace WpMatomo;
|
|
|
|
|
11 |
use PHPMailer\PHPMailer\PHPMailer;
|
12 |
use Piwik\Common;
|
13 |
use Piwik\Mail;
|
|
|
|
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
@@ -19,145 +23,144 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
19 |
class Email {
|
20 |
|
21 |
/**
|
22 |
-
* @var
|
23 |
*/
|
24 |
-
private $
|
25 |
|
26 |
-
private $
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
private $mail;
|
31 |
|
32 |
-
public function
|
33 |
-
$this->
|
34 |
}
|
35 |
|
36 |
-
public function
|
37 |
-
if (!empty($this->
|
38 |
-
return $this->
|
39 |
}
|
40 |
|
41 |
-
return $
|
42 |
}
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
$this->
|
47 |
-
$this->wpMailError = null;
|
48 |
|
49 |
$this->mail = $mail;
|
50 |
|
51 |
-
if ($mail->getBodyHtml()) {
|
52 |
-
$content
|
53 |
-
$this->
|
54 |
-
} elseif ($mail->getBodyText()) {
|
55 |
-
$content
|
56 |
-
$this->
|
57 |
} else {
|
58 |
// seems no content...
|
59 |
-
|
60 |
}
|
61 |
|
62 |
-
|
63 |
|
64 |
-
$recipients = array_keys($mail->getRecipients());
|
65 |
|
66 |
-
$this->
|
67 |
}
|
68 |
|
69 |
-
private function
|
70 |
-
|
71 |
-
$history = \WpMatomo::$settings->get_global_option( 'mail_history' );
|
72 |
if ( empty( $history ) || ! is_array( $history ) ) {
|
73 |
-
$history =
|
74 |
}
|
75 |
|
76 |
// allows us to see if there is a WP Mail issue or a Matomo issue
|
77 |
array_unshift( $history, gmdate( 'Y-m-d H:i:s', time() ) );
|
78 |
$history = array_slice( $history, 0, 3 ); // keep only the last 3 versions
|
79 |
-
|
80 |
-
|
81 |
}
|
82 |
|
83 |
-
private function
|
|
|
84 |
|
85 |
-
$this
|
|
|
86 |
|
87 |
-
|
88 |
-
add_filter( 'wp_mail_content_type' , array($this, 'setContentType'));
|
89 |
-
|
90 |
-
$this->rememberMailSent();
|
91 |
|
92 |
$header = '';
|
93 |
|
94 |
-
if (!empty($attachments)) {
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
|
|
|
|
137 |
|
138 |
$success = wp_mail( $recipients, $subject, $content, $header );
|
139 |
|
140 |
-
remove_action( 'wp_mail_failed',
|
141 |
-
remove_filter('wp_mail_content_type',
|
142 |
|
143 |
-
if (
|
144 |
$message = 'Error unknown.';
|
145 |
-
if (!empty($this->
|
146 |
-
$message = $this->
|
147 |
}
|
148 |
-
if ($this->mail && $this->mail->getAttachments()) {
|
149 |
$message .= ' (has attachments)';
|
150 |
}
|
151 |
-
if ($this->
|
152 |
-
$message .= ' (type '. $this->
|
153 |
}
|
154 |
$logger = new Logger();
|
155 |
-
$logger->log_exception('mail_error', new
|
156 |
-
$logger->log('Matomo mail failed with subject '. $subject . ': ' . $message);
|
157 |
}
|
158 |
|
159 |
-
$this->
|
160 |
-
$this->
|
161 |
}
|
162 |
-
|
163 |
}
|
8 |
*/
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
+
|
12 |
+
use Exception;
|
13 |
use PHPMailer\PHPMailer\PHPMailer;
|
14 |
use Piwik\Common;
|
15 |
use Piwik\Mail;
|
16 |
+
use WP_Error;
|
17 |
+
use WpMatomo;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
23 |
class Email {
|
24 |
|
25 |
/**
|
26 |
+
* @var WP_Error|null
|
27 |
*/
|
28 |
+
private $wp_email_error;
|
29 |
|
30 |
+
private $wp_content_type = null;
|
31 |
+
/**
|
32 |
+
* @var Mail
|
33 |
+
*/
|
34 |
private $mail;
|
35 |
|
36 |
+
public function on_error( $error ) {
|
37 |
+
$this->wp_email_error = $error;
|
38 |
}
|
39 |
|
40 |
+
public function set_content_type( $content_type ) {
|
41 |
+
if ( ! empty( $this->wp_content_type ) ) {
|
42 |
+
return $this->wp_content_type;
|
43 |
}
|
44 |
|
45 |
+
return $content_type;
|
46 |
}
|
47 |
|
48 |
+
public function send( Mail $mail ) {
|
49 |
+
$this->wp_content_type = null;
|
50 |
+
$this->wp_email_error = null;
|
|
|
51 |
|
52 |
$this->mail = $mail;
|
53 |
|
54 |
+
if ( $mail->getBodyHtml() ) {
|
55 |
+
$content = $mail->getBodyHtml();
|
56 |
+
$this->wp_content_type = 'text/html';
|
57 |
+
} elseif ( $mail->getBodyText() ) {
|
58 |
+
$content = $mail->getBodyText();
|
59 |
+
$this->wp_content_type = 'text/plain';
|
60 |
} else {
|
61 |
// seems no content...
|
62 |
+
$content = '';
|
63 |
}
|
64 |
|
65 |
+
$attachments = $mail->getAttachments();
|
66 |
|
67 |
+
$recipients = array_keys( $mail->getRecipients() );
|
68 |
|
69 |
+
$this->send_mail_through_wordpress( $recipients, $mail->getSubject(), $content, $attachments );
|
70 |
}
|
71 |
|
72 |
+
private function remember_mail_sent() {
|
73 |
+
$history = WpMatomo::$settings->get_global_option( 'mail_history' );
|
|
|
74 |
if ( empty( $history ) || ! is_array( $history ) ) {
|
75 |
+
$history = [];
|
76 |
}
|
77 |
|
78 |
// allows us to see if there is a WP Mail issue or a Matomo issue
|
79 |
array_unshift( $history, gmdate( 'Y-m-d H:i:s', time() ) );
|
80 |
$history = array_slice( $history, 0, 3 ); // keep only the last 3 versions
|
81 |
+
WpMatomo::$settings->set_global_option( 'mail_history', $history );
|
82 |
+
WpMatomo::$settings->save();
|
83 |
}
|
84 |
|
85 |
+
private function send_mail_through_wordpress( $recipients, $subject, $content, $attachments ) {
|
86 |
+
$this->wp_email_error = null;
|
87 |
|
88 |
+
add_action( 'wp_mail_failed', [ $this, 'on_error' ] );
|
89 |
+
add_filter( 'wp_mail_content_type', [ $this, 'set_content_type' ] );
|
90 |
|
91 |
+
$this->remember_mail_sent();
|
|
|
|
|
|
|
92 |
|
93 |
$header = '';
|
94 |
|
95 |
+
if ( ! empty( $attachments ) ) {
|
96 |
+
$random_id = Common::generateUniqId();
|
97 |
+
$header = 'X-Matomo: ' . $random_id;
|
98 |
+
$executed_action = false;
|
99 |
+
|
100 |
+
add_action(
|
101 |
+
'phpmailer_init',
|
102 |
+
function ( $phpmailer ) use ( $attachments, $subject, $random_id, &$executed_action ) {
|
103 |
+
/** @var PHPMailer $phpmailer */
|
104 |
+
if ( $executed_action ) {
|
105 |
+
return; // already done, do not execute another time
|
106 |
+
}
|
107 |
+
$executed_action = true;
|
108 |
+
$match = false;
|
109 |
+
foreach ( $phpmailer->getCustomHeaders() as $header ) {
|
110 |
+
if ( isset( $header[0] ) && isset( $header[1] ) &&
|
111 |
+
is_string( $header[0] ) && is_string( $header[1] ) &&
|
112 |
+
'x-matomo' === Common::mb_strtolower( $header[0] ) &&
|
113 |
+
trim( $header[1] ) === $random_id ) {
|
114 |
+
$match = true;
|
115 |
+
}
|
116 |
+
}
|
117 |
+
if ( ! $match ) {
|
118 |
+
return; // attachments aren't for this mail
|
119 |
+
}
|
120 |
+
foreach ( $attachments as $attachment ) {
|
121 |
+
if ( ! empty( $attachment['cid'] ) ) {
|
122 |
+
$phpmailer->addStringEmbeddedImage(
|
123 |
+
$attachment['content'],
|
124 |
+
$attachment['cid'],
|
125 |
+
$attachment['filename'],
|
126 |
+
PHPMailer::ENCODING_BASE64,
|
127 |
+
$attachment['mimetype']
|
128 |
+
);
|
129 |
+
} else {
|
130 |
+
$phpmailer->addStringAttachment(
|
131 |
+
$attachment['content'],
|
132 |
+
$attachment['filename'],
|
133 |
+
PHPMailer::ENCODING_BASE64,
|
134 |
+
$attachment['mimetype']
|
135 |
+
);
|
136 |
+
}
|
137 |
+
}
|
138 |
+
}
|
139 |
+
);
|
140 |
+
}
|
141 |
|
142 |
$success = wp_mail( $recipients, $subject, $content, $header );
|
143 |
|
144 |
+
remove_action( 'wp_mail_failed', [ $this, 'on_error' ] );
|
145 |
+
remove_filter( 'wp_mail_content_type', [ $this, 'set_content_type' ] );
|
146 |
|
147 |
+
if ( ! $success ) {
|
148 |
$message = 'Error unknown.';
|
149 |
+
if ( ! empty( $this->wp_email_error ) && is_object( $this->wp_email_error ) && $this->wp_email_error instanceof WP_Error ) {
|
150 |
+
$message = $this->wp_email_error->get_error_message();
|
151 |
}
|
152 |
+
if ( $this->mail && $this->mail->getAttachments() ) {
|
153 |
$message .= ' (has attachments)';
|
154 |
}
|
155 |
+
if ( $this->wp_content_type ) {
|
156 |
+
$message .= ' (type ' . $this->wp_content_type . ')';
|
157 |
}
|
158 |
$logger = new Logger();
|
159 |
+
$logger->log_exception( 'mail_error', new Exception( $message ) );
|
160 |
+
$logger->log( 'Matomo mail failed with subject ' . $subject . ': ' . $message );
|
161 |
}
|
162 |
|
163 |
+
$this->wp_content_type = null;
|
164 |
+
$this->wp_email_error = null;
|
165 |
}
|
|
|
166 |
}
|
classes/WpMatomo/Installer.php
CHANGED
@@ -9,6 +9,8 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
|
|
12 |
use Piwik\Common;
|
13 |
use Piwik\Config;
|
14 |
use Piwik\Container\StaticContainer;
|
@@ -16,6 +18,7 @@ use Piwik\DbHelper;
|
|
16 |
use Piwik\Exception\NotYetInstalledException;
|
17 |
use Piwik\Plugin\API as PluginApi;
|
18 |
use Piwik\SettingsPiwik;
|
|
|
19 |
use WpMatomo\Site\Sync;
|
20 |
|
21 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -23,8 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
23 |
}
|
24 |
|
25 |
class Installer {
|
26 |
-
|
27 |
-
const OPTION_NAME_INSTALL_DATE = 'matomo-install-date';
|
28 |
const OPTION_NAME_INSTALL_VERSION = 'matomo-install-version';
|
29 |
|
30 |
/**
|
@@ -43,7 +45,7 @@ class Installer {
|
|
43 |
}
|
44 |
|
45 |
public function register_hooks() {
|
46 |
-
add_action( 'activate_matomo',
|
47 |
}
|
48 |
|
49 |
public function looks_like_it_is_installed() {
|
@@ -65,9 +67,11 @@ class Installer {
|
|
65 |
return SettingsPiwik::isMatomoInstalled();
|
66 |
} catch ( NotYetInstalledException $e ) {
|
67 |
// not yet installed.... we will need to install it
|
|
|
|
|
|
|
|
|
68 |
}
|
69 |
-
|
70 |
-
return false;
|
71 |
}
|
72 |
|
73 |
public function can_be_installed() {
|
@@ -85,6 +89,7 @@ class Installer {
|
|
85 |
try {
|
86 |
// prevent session related errors during install making it more stable
|
87 |
if ( ! defined( 'PIWIK_ENABLE_SESSION_START' ) ) {
|
|
|
88 |
define( 'PIWIK_ENABLE_SESSION_START', false );
|
89 |
}
|
90 |
|
@@ -113,9 +118,9 @@ class Installer {
|
|
113 |
// also to set up all the other users
|
114 |
wp_schedule_single_event( time() + 45, ScheduledTasks::EVENT_SYNC );
|
115 |
|
116 |
-
update_option(self::OPTION_NAME_INSTALL_DATE, time());
|
117 |
$plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
|
118 |
-
if ( ! empty( $plugin_data['Version'] )) {
|
119 |
update_option( self::OPTION_NAME_INSTALL_VERSION, $plugin_data['Version'] );
|
120 |
}
|
121 |
|
@@ -127,7 +132,7 @@ class Installer {
|
|
127 |
$this->logger->log( 'Matomo will now init the environment' );
|
128 |
$environment = new \Piwik\Application\Environment( null );
|
129 |
$environment->init();
|
130 |
-
} catch (
|
131 |
$this->logger->log( 'Ignoring error environment init' );
|
132 |
$this->logger->log_exception( 'install_env_init', $e );
|
133 |
}
|
@@ -138,7 +143,7 @@ class Installer {
|
|
138 |
\Piwik\FrontController::unsetInstance(); // make sure we're loading the latest instance
|
139 |
$controller = \Piwik\FrontController::getInstance();
|
140 |
$controller->init();
|
141 |
-
} catch (
|
142 |
$this->logger->log( 'Ignoring error frontcontroller init' );
|
143 |
$this->logger->log_exception( 'install_front_init', $e );
|
144 |
}
|
@@ -147,14 +152,14 @@ class Installer {
|
|
147 |
// sync user now again after installing plugins...
|
148 |
// before eg the users_language table would not have been available yet
|
149 |
$this->create_user();
|
150 |
-
} catch (
|
151 |
$this->logger->log_exception( 'install_create_user', $e );
|
152 |
}
|
153 |
|
154 |
try {
|
155 |
// update plugins if there are any
|
156 |
$this->update_components();
|
157 |
-
} catch (
|
158 |
$this->logger->log_exception( 'install_update_comp', $e );
|
159 |
}
|
160 |
|
@@ -166,9 +171,9 @@ class Installer {
|
|
166 |
|
167 |
$this->logger->log( 'Emptying some caches' );
|
168 |
|
169 |
-
|
170 |
PluginApi::unsetAllInstances();
|
171 |
-
|
172 |
|
173 |
$this->logger->log( 'Matomo install finished' );
|
174 |
}
|
@@ -176,38 +181,37 @@ class Installer {
|
|
176 |
return true;
|
177 |
}
|
178 |
|
179 |
-
public function set_matomo_url()
|
180 |
-
{
|
181 |
// note that the full url might not be possible to be set if the cron is executed on cli and it maybe doesn't have
|
182 |
// the host or if a plugin overwrites the constant of WP_PLUGIN_URL which is used in plugins_url() to not include domain
|
183 |
// see https://www.google.com/url?q=https://wordpress.org/support/topic/no-metrics-showing/%23topic-14362043-replies&source=gmail&ust=1620409922890000&usg=AFQjCNHyzG5-9v0A8bjg8aLVVbYSWxkTxg
|
184 |
|
185 |
-
$matomo_url
|
186 |
$plugins_url = plugins_url( 'app', MATOMO_ANALYTICS_FILE );
|
187 |
// need to make sure to update plugins url if it changes eg if installed somewhere else or domain changes
|
188 |
|
189 |
-
if ($matomo_url
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
) {
|
194 |
// if currently no scheme or host is set then we'll make sure to overwrite it
|
195 |
return;
|
196 |
}
|
197 |
|
198 |
-
if (
|
199 |
return;
|
200 |
}
|
201 |
|
202 |
-
$has_host =
|
203 |
|
204 |
-
if (
|
205 |
return;
|
206 |
}
|
207 |
|
208 |
-
$has_scheme =
|
209 |
|
210 |
-
if (
|
211 |
return;
|
212 |
}
|
213 |
|
@@ -226,16 +230,16 @@ class Installer {
|
|
226 |
|
227 |
try {
|
228 |
$db_infos = self::get_db_infos();
|
229 |
-
$config
|
230 |
-
if (isset($config)) {
|
231 |
-
$db_infos = array_merge($config->database, $db_infos);
|
232 |
}
|
233 |
$config->database = $db_infos;
|
234 |
|
235 |
DbHelper::checkDatabaseVersion();
|
236 |
-
} catch (
|
237 |
$message = sprintf( 'Database info detection failed with %s in %s:%s.', $e->getMessage(), $e->getFile(), $e->getLine() );
|
238 |
-
throw new
|
239 |
}
|
240 |
|
241 |
$tables_installed = DbHelper::getTablesInstalled();
|
@@ -254,29 +258,34 @@ class Installer {
|
|
254 |
private function create_config( $db_info ) {
|
255 |
$this->logger->log( 'Matomo is now creating the config' );
|
256 |
$domain = home_url();
|
257 |
-
$general =
|
258 |
-
'trusted_hosts' =>
|
259 |
'salt' => Common::generateUniqId(),
|
260 |
-
|
261 |
$config = Config::getInstance();
|
262 |
$path = $config->getLocalPath();
|
263 |
if ( ! is_dir( dirname( $path ) ) ) {
|
264 |
wp_mkdir_p( dirname( $path ) );
|
265 |
}
|
266 |
-
$db_default =
|
267 |
-
$general_default =
|
268 |
if ( $config->database ) {
|
269 |
$db_default = $config->database;
|
270 |
}
|
|
|
271 |
if ( $config->General ) {
|
|
|
272 |
$general_default = $config->General;
|
273 |
}
|
274 |
$config->database = array_merge( $db_default, $db_info );
|
275 |
-
|
|
|
276 |
$config->forceSave();
|
277 |
|
278 |
$mode = 0664;
|
279 |
-
|
|
|
|
|
280 |
}
|
281 |
|
282 |
private function create_website() {
|
@@ -293,33 +302,34 @@ class Installer {
|
|
293 |
|
294 |
/**
|
295 |
* @param array $default params
|
|
|
296 |
* @return array
|
297 |
*/
|
298 |
-
public static function get_db_infos( $default =
|
299 |
global $wpdb;
|
300 |
|
301 |
-
$socket
|
302 |
$host_data = null;
|
303 |
-
$host
|
304 |
-
$port
|
305 |
-
if (method_exists($wpdb, 'parse_db_host')) {
|
306 |
// WP 4.9+
|
307 |
$host_data = $wpdb->parse_db_host( DB_HOST );
|
308 |
-
if ($host_data) {
|
309 |
list( $host, $port, $socket, $is_ipv6 ) = $host_data;
|
310 |
-
if (
|
311 |
$port = 3306;
|
312 |
}
|
313 |
}
|
314 |
}
|
315 |
|
316 |
-
if (
|
317 |
// WP 4.8 and older
|
318 |
-
// in case DB credentials change in
|
319 |
-
$
|
320 |
-
$host
|
321 |
-
if (count($
|
322 |
-
$port = $
|
323 |
} else {
|
324 |
$port = 3306;
|
325 |
}
|
@@ -327,20 +337,20 @@ class Installer {
|
|
327 |
|
328 |
$charset = $wpdb->charset ? $wpdb->charset : 'utf8';
|
329 |
|
330 |
-
$database =
|
331 |
-
'host'
|
332 |
-
'port'
|
333 |
-
'username'
|
334 |
-
'password'
|
335 |
-
'dbname'
|
336 |
-
'charset'
|
337 |
'tables_prefix' => $wpdb->prefix . MATOMO_DATABASE_PREFIX,
|
338 |
-
'adapter'
|
339 |
-
|
340 |
-
if (!empty($socket)) {
|
341 |
$database['unix_socket'] = $socket;
|
342 |
}
|
343 |
-
$database = array_merge($default, $database);
|
344 |
|
345 |
return $database;
|
346 |
}
|
@@ -351,5 +361,4 @@ class Installer {
|
|
351 |
$updater = new Updater( $this->settings );
|
352 |
$updater->update();
|
353 |
}
|
354 |
-
|
355 |
}
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Exception;
|
13 |
+
use Piwik\Cache;
|
14 |
use Piwik\Common;
|
15 |
use Piwik\Config;
|
16 |
use Piwik\Container\StaticContainer;
|
18 |
use Piwik\Exception\NotYetInstalledException;
|
19 |
use Piwik\Plugin\API as PluginApi;
|
20 |
use Piwik\SettingsPiwik;
|
21 |
+
use Piwik\Singleton;
|
22 |
use WpMatomo\Site\Sync;
|
23 |
|
24 |
if ( ! defined( 'ABSPATH' ) ) {
|
26 |
}
|
27 |
|
28 |
class Installer {
|
29 |
+
const OPTION_NAME_INSTALL_DATE = 'matomo-install-date';
|
|
|
30 |
const OPTION_NAME_INSTALL_VERSION = 'matomo-install-version';
|
31 |
|
32 |
/**
|
45 |
}
|
46 |
|
47 |
public function register_hooks() {
|
48 |
+
add_action( 'activate_matomo', [ $this, 'install' ] );
|
49 |
}
|
50 |
|
51 |
public function looks_like_it_is_installed() {
|
67 |
return SettingsPiwik::isMatomoInstalled();
|
68 |
} catch ( NotYetInstalledException $e ) {
|
69 |
// not yet installed.... we will need to install it
|
70 |
+
return false;
|
71 |
+
} catch ( \Zend_Db_Statement_Exception $e ) {
|
72 |
+
// not yet installed.... we will need to install it
|
73 |
+
return false;
|
74 |
}
|
|
|
|
|
75 |
}
|
76 |
|
77 |
public function can_be_installed() {
|
89 |
try {
|
90 |
// prevent session related errors during install making it more stable
|
91 |
if ( ! defined( 'PIWIK_ENABLE_SESSION_START' ) ) {
|
92 |
+
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
|
93 |
define( 'PIWIK_ENABLE_SESSION_START', false );
|
94 |
}
|
95 |
|
118 |
// also to set up all the other users
|
119 |
wp_schedule_single_event( time() + 45, ScheduledTasks::EVENT_SYNC );
|
120 |
|
121 |
+
update_option( self::OPTION_NAME_INSTALL_DATE, time() );
|
122 |
$plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
|
123 |
+
if ( ! empty( $plugin_data['Version'] ) ) {
|
124 |
update_option( self::OPTION_NAME_INSTALL_VERSION, $plugin_data['Version'] );
|
125 |
}
|
126 |
|
132 |
$this->logger->log( 'Matomo will now init the environment' );
|
133 |
$environment = new \Piwik\Application\Environment( null );
|
134 |
$environment->init();
|
135 |
+
} catch ( Exception $e ) {
|
136 |
$this->logger->log( 'Ignoring error environment init' );
|
137 |
$this->logger->log_exception( 'install_env_init', $e );
|
138 |
}
|
143 |
\Piwik\FrontController::unsetInstance(); // make sure we're loading the latest instance
|
144 |
$controller = \Piwik\FrontController::getInstance();
|
145 |
$controller->init();
|
146 |
+
} catch ( Exception $e ) {
|
147 |
$this->logger->log( 'Ignoring error frontcontroller init' );
|
148 |
$this->logger->log_exception( 'install_front_init', $e );
|
149 |
}
|
152 |
// sync user now again after installing plugins...
|
153 |
// before eg the users_language table would not have been available yet
|
154 |
$this->create_user();
|
155 |
+
} catch ( Exception $e ) {
|
156 |
$this->logger->log_exception( 'install_create_user', $e );
|
157 |
}
|
158 |
|
159 |
try {
|
160 |
// update plugins if there are any
|
161 |
$this->update_components();
|
162 |
+
} catch ( Exception $e ) {
|
163 |
$this->logger->log_exception( 'install_update_comp', $e );
|
164 |
}
|
165 |
|
171 |
|
172 |
$this->logger->log( 'Emptying some caches' );
|
173 |
|
174 |
+
Singleton::clearAll();
|
175 |
PluginApi::unsetAllInstances();
|
176 |
+
Cache::flushAll();
|
177 |
|
178 |
$this->logger->log( 'Matomo install finished' );
|
179 |
}
|
181 |
return true;
|
182 |
}
|
183 |
|
184 |
+
public function set_matomo_url() {
|
|
|
185 |
// note that the full url might not be possible to be set if the cron is executed on cli and it maybe doesn't have
|
186 |
// the host or if a plugin overwrites the constant of WP_PLUGIN_URL which is used in plugins_url() to not include domain
|
187 |
// see https://www.google.com/url?q=https://wordpress.org/support/topic/no-metrics-showing/%23topic-14362043-replies&source=gmail&ust=1620409922890000&usg=AFQjCNHyzG5-9v0A8bjg8aLVVbYSWxkTxg
|
188 |
|
189 |
+
$matomo_url = SettingsPiwik::getPiwikUrl();
|
190 |
$plugins_url = plugins_url( 'app', MATOMO_ANALYTICS_FILE );
|
191 |
// need to make sure to update plugins url if it changes eg if installed somewhere else or domain changes
|
192 |
|
193 |
+
if ( $matomo_url
|
194 |
+
&& $plugins_url === $matomo_url
|
195 |
+
&& wp_parse_url( $matomo_url, PHP_URL_SCHEME )
|
196 |
+
&& wp_parse_url( $matomo_url, PHP_URL_HOST )
|
197 |
) {
|
198 |
// if currently no scheme or host is set then we'll make sure to overwrite it
|
199 |
return;
|
200 |
}
|
201 |
|
202 |
+
if ( ! $plugins_url ) {
|
203 |
return;
|
204 |
}
|
205 |
|
206 |
+
$has_host = wp_parse_url( $plugins_url, PHP_URL_HOST );
|
207 |
|
208 |
+
if ( ! $has_host ) {
|
209 |
return;
|
210 |
}
|
211 |
|
212 |
+
$has_scheme = wp_parse_url( $plugins_url, PHP_URL_SCHEME );
|
213 |
|
214 |
+
if ( ! $has_scheme ) {
|
215 |
return;
|
216 |
}
|
217 |
|
230 |
|
231 |
try {
|
232 |
$db_infos = self::get_db_infos();
|
233 |
+
$config = Config::getInstance();
|
234 |
+
if ( isset( $config ) ) {
|
235 |
+
$db_infos = array_merge( $config->database, $db_infos );
|
236 |
}
|
237 |
$config->database = $db_infos;
|
238 |
|
239 |
DbHelper::checkDatabaseVersion();
|
240 |
+
} catch ( Exception $e ) {
|
241 |
$message = sprintf( 'Database info detection failed with %s in %s:%s.', $e->getMessage(), $e->getFile(), $e->getLine() );
|
242 |
+
throw new Exception( $message, $e->getCode(), $e );
|
243 |
}
|
244 |
|
245 |
$tables_installed = DbHelper::getTablesInstalled();
|
258 |
private function create_config( $db_info ) {
|
259 |
$this->logger->log( 'Matomo is now creating the config' );
|
260 |
$domain = home_url();
|
261 |
+
$general = [
|
262 |
+
'trusted_hosts' => [ $domain ],
|
263 |
'salt' => Common::generateUniqId(),
|
264 |
+
];
|
265 |
$config = Config::getInstance();
|
266 |
$path = $config->getLocalPath();
|
267 |
if ( ! is_dir( dirname( $path ) ) ) {
|
268 |
wp_mkdir_p( dirname( $path ) );
|
269 |
}
|
270 |
+
$db_default = [];
|
271 |
+
$general_default = [];
|
272 |
if ( $config->database ) {
|
273 |
$db_default = $config->database;
|
274 |
}
|
275 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
276 |
if ( $config->General ) {
|
277 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
278 |
$general_default = $config->General;
|
279 |
}
|
280 |
$config->database = array_merge( $db_default, $db_info );
|
281 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
282 |
+
$config->General = array_merge( $general_default, $general );
|
283 |
$config->forceSave();
|
284 |
|
285 |
$mode = 0664;
|
286 |
+
if ( ! chmod( $config->getLocalPath(), $mode ) ) {
|
287 |
+
$this->logger->log( "Can't chmod " . $config->getLocalPath() );
|
288 |
+
}
|
289 |
}
|
290 |
|
291 |
private function create_website() {
|
302 |
|
303 |
/**
|
304 |
* @param array $default params
|
305 |
+
*
|
306 |
* @return array
|
307 |
*/
|
308 |
+
public static function get_db_infos( $default = [] ) {
|
309 |
global $wpdb;
|
310 |
|
311 |
+
$socket = '';
|
312 |
$host_data = null;
|
313 |
+
$host = null;
|
314 |
+
$port = 3306;
|
315 |
+
if ( method_exists( $wpdb, 'parse_db_host' ) ) {
|
316 |
// WP 4.9+
|
317 |
$host_data = $wpdb->parse_db_host( DB_HOST );
|
318 |
+
if ( $host_data ) {
|
319 |
list( $host, $port, $socket, $is_ipv6 ) = $host_data;
|
320 |
+
if ( ! $port && ! $socket ) {
|
321 |
$port = 3306;
|
322 |
}
|
323 |
}
|
324 |
}
|
325 |
|
326 |
+
if ( ! $host_data || ! $host ) {
|
327 |
// WP 4.8 and older
|
328 |
+
// in case DB credentials change in WordPress, we need to apply these changes here as well on demand
|
329 |
+
$host_parts = explode( ':', DB_HOST );
|
330 |
+
$host = $host_parts[0];
|
331 |
+
if ( count( $host_parts ) === 2 && is_numeric( $host_parts[1] ) ) {
|
332 |
+
$port = $host_parts[1];
|
333 |
} else {
|
334 |
$port = 3306;
|
335 |
}
|
337 |
|
338 |
$charset = $wpdb->charset ? $wpdb->charset : 'utf8';
|
339 |
|
340 |
+
$database = [
|
341 |
+
'host' => $host,
|
342 |
+
'port' => $port,
|
343 |
+
'username' => DB_USER,
|
344 |
+
'password' => DB_PASSWORD,
|
345 |
+
'dbname' => DB_NAME,
|
346 |
+
'charset' => $charset,
|
347 |
'tables_prefix' => $wpdb->prefix . MATOMO_DATABASE_PREFIX,
|
348 |
+
'adapter' => 'WordPress',
|
349 |
+
];
|
350 |
+
if ( ! empty( $socket ) ) {
|
351 |
$database['unix_socket'] = $socket;
|
352 |
}
|
353 |
+
$database = array_merge( $default, $database );
|
354 |
|
355 |
return $database;
|
356 |
}
|
361 |
$updater = new Updater( $this->settings );
|
362 |
$updater->update();
|
363 |
}
|
|
|
364 |
}
|
classes/WpMatomo/Logger.php
CHANGED
@@ -16,26 +16,26 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
16 |
}
|
17 |
|
18 |
class Logger {
|
19 |
-
const LEVEL_NONE
|
20 |
const LEVEL_DEBUG = 1;
|
21 |
-
const LEVEL_INFO
|
22 |
|
23 |
-
private function get_log_level()
|
24 |
-
|
25 |
-
|
26 |
-
if (MATOMO_DEBUG) {
|
27 |
return self::LEVEL_DEBUG;
|
28 |
}
|
|
|
29 |
return self::LEVEL_NONE;
|
30 |
}
|
31 |
|
32 |
return self::LEVEL_INFO;
|
33 |
}
|
34 |
|
35 |
-
public function log( $message
|
36 |
$log_level = $this->get_log_level();
|
37 |
|
38 |
-
if ($log_level > $mode) {
|
39 |
return;
|
40 |
}
|
41 |
|
@@ -55,12 +55,12 @@ class Logger {
|
|
55 |
private function persist( $key, $message ) {
|
56 |
$id = $this->make_id();
|
57 |
$logs = $this->get_last_logged_entries();
|
58 |
-
$logs[] =
|
59 |
'name' => $key,
|
60 |
'value' => time(),
|
61 |
'comment' => $message,
|
62 |
-
|
63 |
-
$logs = array_slice( $logs, -6 );
|
64 |
update_option( $id, $logs );
|
65 |
}
|
66 |
|
@@ -72,7 +72,7 @@ class Logger {
|
|
72 |
$id = $this->make_id();
|
73 |
$logs = get_option( $id );
|
74 |
if ( empty( $logs ) ) {
|
75 |
-
$logs =
|
76 |
}
|
77 |
|
78 |
// remove any entry older than 1 week
|
@@ -80,9 +80,11 @@ class Logger {
|
|
80 |
$logs,
|
81 |
function ( $log ) {
|
82 |
$one_week_seconds = 604800;
|
|
|
83 |
return ! empty( $log['value'] ) && is_numeric( $log['value'] ) && ( time() - $log['value'] ) <= $one_week_seconds;
|
84 |
}
|
85 |
);
|
|
|
86 |
return $logs;
|
87 |
}
|
88 |
|
@@ -104,10 +106,9 @@ class Logger {
|
|
104 |
}
|
105 |
|
106 |
public function log_exception( $key, Exception $e ) {
|
107 |
-
$trace = $this->get_readable_trace($e);
|
108 |
$message = $e->getMessage() . ' => ' . $trace;
|
109 |
$this->log( 'Matomo error: ' . $message );
|
110 |
$this->persist( $key, $message );
|
111 |
}
|
112 |
-
|
113 |
}
|
16 |
}
|
17 |
|
18 |
class Logger {
|
19 |
+
const LEVEL_NONE = 99;
|
20 |
const LEVEL_DEBUG = 1;
|
21 |
+
const LEVEL_INFO = 3;
|
22 |
|
23 |
+
private function get_log_level() {
|
24 |
+
if ( defined( 'MATOMO_DEBUG' ) ) {
|
25 |
+
if ( MATOMO_DEBUG ) {
|
|
|
26 |
return self::LEVEL_DEBUG;
|
27 |
}
|
28 |
+
|
29 |
return self::LEVEL_NONE;
|
30 |
}
|
31 |
|
32 |
return self::LEVEL_INFO;
|
33 |
}
|
34 |
|
35 |
+
public function log( $message, $mode = 3 ) {
|
36 |
$log_level = $this->get_log_level();
|
37 |
|
38 |
+
if ( $log_level > $mode ) {
|
39 |
return;
|
40 |
}
|
41 |
|
55 |
private function persist( $key, $message ) {
|
56 |
$id = $this->make_id();
|
57 |
$logs = $this->get_last_logged_entries();
|
58 |
+
$logs[] = [
|
59 |
'name' => $key,
|
60 |
'value' => time(),
|
61 |
'comment' => $message,
|
62 |
+
];
|
63 |
+
$logs = array_slice( $logs, - 6 );
|
64 |
update_option( $id, $logs );
|
65 |
}
|
66 |
|
72 |
$id = $this->make_id();
|
73 |
$logs = get_option( $id );
|
74 |
if ( empty( $logs ) ) {
|
75 |
+
$logs = [];
|
76 |
}
|
77 |
|
78 |
// remove any entry older than 1 week
|
80 |
$logs,
|
81 |
function ( $log ) {
|
82 |
$one_week_seconds = 604800;
|
83 |
+
|
84 |
return ! empty( $log['value'] ) && is_numeric( $log['value'] ) && ( time() - $log['value'] ) <= $one_week_seconds;
|
85 |
}
|
86 |
);
|
87 |
+
|
88 |
return $logs;
|
89 |
}
|
90 |
|
106 |
}
|
107 |
|
108 |
public function log_exception( $key, Exception $e ) {
|
109 |
+
$trace = $this->get_readable_trace( $e );
|
110 |
$message = $e->getMessage() . ' => ' . $trace;
|
111 |
$this->log( 'Matomo error: ' . $message );
|
112 |
$this->persist( $key, $message );
|
113 |
}
|
|
|
114 |
}
|
classes/WpMatomo/OptOut.php
CHANGED
@@ -11,83 +11,103 @@ namespace WpMatomo;
|
|
11 |
|
12 |
use Piwik\Piwik;
|
13 |
use Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker;
|
|
|
14 |
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
|
19 |
class OptOut {
|
20 |
-
|
21 |
private $language = null;
|
22 |
|
23 |
public function register_hooks() {
|
24 |
add_shortcode( 'matomo_opt_out', array( $this, 'show_opt_out' ) );
|
25 |
add_action( 'wp_enqueue_scripts', array( $this, 'load_scripts' ) );
|
|
|
26 |
}
|
27 |
|
28 |
public function load_scripts() {
|
29 |
-
if (!is_admin()) {
|
30 |
-
wp_register_script( 'matomo_opt_out_js', plugins_url( 'assets/js/optout.js', MATOMO_ANALYTICS_FILE ),
|
31 |
}
|
32 |
}
|
33 |
-
|
34 |
-
private function translate($id)
|
35 |
-
|
36 |
-
return esc_html(Piwik::translate($id, array(), $this->language));
|
37 |
}
|
38 |
|
39 |
public function show_opt_out( $atts ) {
|
40 |
$a = shortcode_atts(
|
41 |
-
|
42 |
'language' => null,
|
43 |
-
|
44 |
$atts
|
45 |
);
|
46 |
-
if (!empty($a['language']) && strlen($a['language']) < 6) {
|
47 |
$this->language = $a['language'];
|
48 |
}
|
49 |
|
50 |
try {
|
51 |
Bootstrap::do_bootstrap();
|
52 |
-
} catch (
|
53 |
$logger = new Logger();
|
54 |
-
$logger->log_exception('optout', $e);
|
|
|
55 |
return '<p>An error occurred. Please check Matomo system report in WP-Admin.</p>';
|
56 |
}
|
57 |
|
58 |
$dnt_checker = new DoNotTrackHeaderChecker();
|
59 |
$dnt_enabled = $dnt_checker->isDoNotTrackFound();
|
60 |
|
61 |
-
if (!empty($dnt_enabled)) {
|
62 |
-
return '<p>'. $this->translate('CoreAdminHome_OptOutDntFound').'</p>';
|
63 |
}
|
64 |
|
65 |
wp_enqueue_script( 'matomo_opt_out_js' );
|
66 |
|
67 |
-
$track_visits = empty($_COOKIE['mtm_consent_removed']);
|
68 |
|
69 |
-
$style_tracking_enabled
|
70 |
$style_tracking_disabled = '';
|
71 |
-
$checkbox_attr
|
72 |
-
if ($track_visits) {
|
73 |
$style_tracking_enabled = 'style="display:none;"';
|
74 |
-
$checkbox_attr
|
75 |
} else {
|
76 |
$style_tracking_disabled = 'style="display:none;"';
|
77 |
}
|
78 |
|
79 |
-
$content
|
80 |
-
$content .= '<p id="matomo_opted_in_intro" '
|
81 |
|
82 |
$content .= '<form>
|
83 |
-
<input type="checkbox" id="matomo_optout_checkbox" '
|
84 |
<label for="matomo_optout_checkbox"><strong>
|
85 |
-
<span id="matomo_opted_in_label" '
|
86 |
-
<span id="matomo_opted_out_label" '
|
87 |
</strong></label></form>';
|
88 |
$content .= '<noscript><p><strong style="color: #ff0000;">This opt out feature requires JavaScript.</strong></p></noscript>';
|
89 |
-
$content .= '<p id="matomo_outout_err_cookies" style="display: none;"><strong>' . $this->translate('CoreAdminHome_OptOutErrorNoCookies') . '</strong></p>';
|
|
|
90 |
return $content;
|
91 |
}
|
92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
}
|
11 |
|
12 |
use Piwik\Piwik;
|
13 |
use Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker;
|
14 |
+
use Throwable;
|
15 |
|
16 |
if ( ! defined( 'ABSPATH' ) ) {
|
17 |
exit; // if accessed directly
|
18 |
}
|
19 |
|
20 |
class OptOut {
|
|
|
21 |
private $language = null;
|
22 |
|
23 |
public function register_hooks() {
|
24 |
add_shortcode( 'matomo_opt_out', array( $this, 'show_opt_out' ) );
|
25 |
add_action( 'wp_enqueue_scripts', array( $this, 'load_scripts' ) );
|
26 |
+
add_action( 'init', [$this, 'load_block'] );
|
27 |
}
|
28 |
|
29 |
public function load_scripts() {
|
30 |
+
if ( ! is_admin() ) {
|
31 |
+
wp_register_script( 'matomo_opt_out_js', plugins_url( 'assets/js/optout.js', MATOMO_ANALYTICS_FILE ), [], 1, true );
|
32 |
}
|
33 |
}
|
34 |
+
|
35 |
+
private function translate( $id ) {
|
36 |
+
return esc_html( Piwik::translate( $id, [], $this->language ) );
|
|
|
37 |
}
|
38 |
|
39 |
public function show_opt_out( $atts ) {
|
40 |
$a = shortcode_atts(
|
41 |
+
[
|
42 |
'language' => null,
|
43 |
+
],
|
44 |
$atts
|
45 |
);
|
46 |
+
if ( ! empty( $a['language'] ) && strlen( $a['language'] ) < 6 ) {
|
47 |
$this->language = $a['language'];
|
48 |
}
|
49 |
|
50 |
try {
|
51 |
Bootstrap::do_bootstrap();
|
52 |
+
} catch ( Throwable $e ) {
|
53 |
$logger = new Logger();
|
54 |
+
$logger->log_exception( 'optout', $e );
|
55 |
+
|
56 |
return '<p>An error occurred. Please check Matomo system report in WP-Admin.</p>';
|
57 |
}
|
58 |
|
59 |
$dnt_checker = new DoNotTrackHeaderChecker();
|
60 |
$dnt_enabled = $dnt_checker->isDoNotTrackFound();
|
61 |
|
62 |
+
if ( ! empty( $dnt_enabled ) ) {
|
63 |
+
return '<p>' . $this->translate( 'CoreAdminHome_OptOutDntFound' ) . '</p>';
|
64 |
}
|
65 |
|
66 |
wp_enqueue_script( 'matomo_opt_out_js' );
|
67 |
|
68 |
+
$track_visits = empty( $_COOKIE['mtm_consent_removed'] );
|
69 |
|
70 |
+
$style_tracking_enabled = '';
|
71 |
$style_tracking_disabled = '';
|
72 |
+
$checkbox_attr = '';
|
73 |
+
if ( $track_visits ) {
|
74 |
$style_tracking_enabled = 'style="display:none;"';
|
75 |
+
$checkbox_attr = 'checked="checked"';
|
76 |
} else {
|
77 |
$style_tracking_disabled = 'style="display:none;"';
|
78 |
}
|
79 |
|
80 |
+
$content = '<p id="matomo_opted_out_intro" ' . $style_tracking_enabled . '>' . $this->translate( 'CoreAdminHome_OptOutComplete' ) . ' ' . $this->translate( 'CoreAdminHome_OptOutCompleteBis' ) . '</p>';
|
81 |
+
$content .= '<p id="matomo_opted_in_intro" ' . $style_tracking_disabled . '>' . $this->translate( 'CoreAdminHome_YouMayOptOut2' ) . ' ' . $this->translate( 'CoreAdminHome_YouMayOptOut3' ) . '</p>';
|
82 |
|
83 |
$content .= '<form>
|
84 |
+
<input type="checkbox" id="matomo_optout_checkbox" ' . $checkbox_attr . '/>
|
85 |
<label for="matomo_optout_checkbox"><strong>
|
86 |
+
<span id="matomo_opted_in_label" ' . $style_tracking_disabled . '>' . $this->translate( 'CoreAdminHome_YouAreNotOptedOut' ) . ' ' . $this->translate( 'CoreAdminHome_UncheckToOptOut' ) . '</span>
|
87 |
+
<span id="matomo_opted_out_label" ' . $style_tracking_enabled . '>' . $this->translate( 'CoreAdminHome_YouAreOptedOut' ) . ' ' . $this->translate( 'CoreAdminHome_CheckToOptIn' ) . '</span>
|
88 |
</strong></label></form>';
|
89 |
$content .= '<noscript><p><strong style="color: #ff0000;">This opt out feature requires JavaScript.</strong></p></noscript>';
|
90 |
+
$content .= '<p id="matomo_outout_err_cookies" style="display: none;"><strong>' . $this->translate( 'CoreAdminHome_OptOutErrorNoCookies' ) . '</strong></p>';
|
91 |
+
|
92 |
return $content;
|
93 |
}
|
94 |
|
95 |
+
public function load_block() {
|
96 |
+
// before wordpress 5.0
|
97 |
+
if ( ! function_exists( 'register_block_type' ) ) {
|
98 |
+
// Gutenberg is not active.
|
99 |
+
return;
|
100 |
+
}
|
101 |
+
|
102 |
+
wp_register_script(
|
103 |
+
'matomo-opt-out',
|
104 |
+
plugins_url( '/assets/js/blocks/matomo_opt_out.js', MATOMO_ANALYTICS_FILE ),
|
105 |
+
array( 'wp-blocks', 'wp-i18n', 'wp-element' ),
|
106 |
+
filemtime( plugin_dir_path( MATOMO_ANALYTICS_FILE ) . '/assets/js/blocks/matomo_opt_out.js' )
|
107 |
+
);
|
108 |
+
|
109 |
+
register_block_type( 'matomo/matomo-opt-out', array(
|
110 |
+
'editor_script' => 'matomo-opt-out',
|
111 |
+
) );
|
112 |
+
}
|
113 |
}
|
classes/WpMatomo/Paths.php
CHANGED
@@ -9,24 +9,32 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
class Paths {
|
17 |
|
18 |
-
private
|
|
|
|
|
19 |
if ( ! function_exists( 'WP_Filesystem' ) ) {
|
20 |
require_once ABSPATH . '/wp-admin/includes/file.php';
|
21 |
-
WP_Filesystem();
|
22 |
}
|
23 |
|
|
|
|
|
|
|
|
|
24 |
if ( ! class_exists( '\WP_Filesystem_Direct' ) ) {
|
25 |
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-base.php';
|
26 |
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-direct.php';
|
27 |
}
|
28 |
|
29 |
-
return new
|
30 |
}
|
31 |
|
32 |
public function get_upload_base_url() {
|
@@ -113,7 +121,7 @@ class Paths {
|
|
113 |
$matomo_dir_parts = explode( DIRECTORY_SEPARATOR, $matomo_dir );
|
114 |
$target_dir_parts = explode( DIRECTORY_SEPARATOR, $target_dir );
|
115 |
$relative_directory = '';
|
116 |
-
$add_at_the_end =
|
117 |
$was_previous_same = false;
|
118 |
|
119 |
foreach ( $target_dir_parts as $index => $part ) {
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use stdClass;
|
13 |
+
use WP_Filesystem_Direct;
|
14 |
+
|
15 |
if ( ! defined( 'ABSPATH' ) ) {
|
16 |
exit; // if accessed directly
|
17 |
}
|
18 |
|
19 |
class Paths {
|
20 |
|
21 |
+
private static $host_init_filesystem = false;
|
22 |
+
|
23 |
+
public function get_file_system() {
|
24 |
if ( ! function_exists( 'WP_Filesystem' ) ) {
|
25 |
require_once ABSPATH . '/wp-admin/includes/file.php';
|
|
|
26 |
}
|
27 |
|
28 |
+
if ( ! self::$host_init_filesystem ) {
|
29 |
+
self::$host_init_filesystem = true;
|
30 |
+
WP_Filesystem();
|
31 |
+
}
|
32 |
if ( ! class_exists( '\WP_Filesystem_Direct' ) ) {
|
33 |
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-base.php';
|
34 |
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-direct.php';
|
35 |
}
|
36 |
|
37 |
+
return new WP_Filesystem_Direct( new stdClass() );
|
38 |
}
|
39 |
|
40 |
public function get_upload_base_url() {
|
121 |
$matomo_dir_parts = explode( DIRECTORY_SEPARATOR, $matomo_dir );
|
122 |
$target_dir_parts = explode( DIRECTORY_SEPARATOR, $target_dir );
|
123 |
$relative_directory = '';
|
124 |
+
$add_at_the_end = [];
|
125 |
$was_previous_same = false;
|
126 |
|
127 |
foreach ( $target_dir_parts as $index => $part ) {
|
classes/WpMatomo/PrivacyBadge.php
CHANGED
@@ -14,17 +14,16 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
14 |
}
|
15 |
|
16 |
class PrivacyBadge {
|
17 |
-
|
18 |
public function register_hooks() {
|
19 |
-
add_shortcode( 'matomo_privacy_badge',
|
20 |
}
|
21 |
|
22 |
public function show_privacy_page( $atts ) {
|
23 |
$a = shortcode_atts(
|
24 |
-
|
25 |
'size' => '120',
|
26 |
'align' => '',
|
27 |
-
|
28 |
$atts
|
29 |
);
|
30 |
|
@@ -40,5 +39,4 @@ class PrivacyBadge {
|
|
40 |
|
41 |
return sprintf( '<img alt="%s" src="%s" %s>', $title, esc_attr( $url ), $option );
|
42 |
}
|
43 |
-
|
44 |
}
|
14 |
}
|
15 |
|
16 |
class PrivacyBadge {
|
|
|
17 |
public function register_hooks() {
|
18 |
+
add_shortcode( 'matomo_privacy_badge', [ $this, 'show_privacy_page' ] );
|
19 |
}
|
20 |
|
21 |
public function show_privacy_page( $atts ) {
|
22 |
$a = shortcode_atts(
|
23 |
+
[
|
24 |
'size' => '120',
|
25 |
'align' => '',
|
26 |
+
],
|
27 |
$atts
|
28 |
);
|
29 |
|
39 |
|
40 |
return sprintf( '<img alt="%s" src="%s" %s>', $title, esc_attr( $url ), $option );
|
41 |
}
|
|
|
42 |
}
|
classes/WpMatomo/RedirectOnActivation.php
CHANGED
@@ -9,7 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
-
use
|
13 |
|
14 |
class RedirectOnActivation {
|
15 |
/**
|
@@ -22,7 +22,7 @@ class RedirectOnActivation {
|
|
22 |
}
|
23 |
|
24 |
public function register_hooks() {
|
25 |
-
register_activation_hook(MATOMO_ANALYTICS_FILE, [ $this, 'matomo_activate' ] );
|
26 |
add_action( 'admin_init', [ $this, 'matomo_plugin_redirect' ] );
|
27 |
}
|
28 |
|
@@ -36,6 +36,7 @@ class RedirectOnActivation {
|
|
36 |
$this->redirect_to_getting_started();
|
37 |
}
|
38 |
}
|
|
|
39 |
/**
|
40 |
* We don't test the result of the wp_redirect method and we silent this method
|
41 |
* as this method will not work during unit tests.
|
@@ -46,16 +47,17 @@ class RedirectOnActivation {
|
|
46 |
*/
|
47 |
public function redirect_to_getting_started() {
|
48 |
$redirect = false;
|
49 |
-
if(!isset($_GET['activate-multi'])) {
|
50 |
-
if
|
51 |
-
(
|
52 |
( self::$settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE ) === 1 ) &&
|
53 |
( self::$settings->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_DISABLED )
|
54 |
) {
|
55 |
$redirect = true;
|
56 |
-
|
|
|
57 |
}
|
58 |
}
|
|
|
59 |
return $redirect;
|
60 |
}
|
61 |
}
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use WpMatomo\Admin\TrackingSettings;
|
13 |
|
14 |
class RedirectOnActivation {
|
15 |
/**
|
22 |
}
|
23 |
|
24 |
public function register_hooks() {
|
25 |
+
register_activation_hook( MATOMO_ANALYTICS_FILE, [ $this, 'matomo_activate' ] );
|
26 |
add_action( 'admin_init', [ $this, 'matomo_plugin_redirect' ] );
|
27 |
}
|
28 |
|
36 |
$this->redirect_to_getting_started();
|
37 |
}
|
38 |
}
|
39 |
+
|
40 |
/**
|
41 |
* We don't test the result of the wp_redirect method and we silent this method
|
42 |
* as this method will not work during unit tests.
|
47 |
*/
|
48 |
public function redirect_to_getting_started() {
|
49 |
$redirect = false;
|
50 |
+
if ( ! isset( $_GET['activate-multi'] ) ) {
|
51 |
+
if (
|
|
|
52 |
( self::$settings->get_global_option( Settings::SHOW_GET_STARTED_PAGE ) === 1 ) &&
|
53 |
( self::$settings->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_DISABLED )
|
54 |
) {
|
55 |
$redirect = true;
|
56 |
+
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
57 |
+
@wp_safe_redirect( admin_url( 'admin.php?page=matomo-get-started' ) );
|
58 |
}
|
59 |
}
|
60 |
+
|
61 |
return $redirect;
|
62 |
}
|
63 |
}
|
classes/WpMatomo/Referral.php
CHANGED
@@ -17,10 +17,10 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
17 |
* Every 90 days we show a please review notice until the user dismisses this notice or clicks on rate us.
|
18 |
* We only show this notice on Matomo screens.
|
19 |
*
|
20 |
-
* @
|
|
|
21 |
*/
|
22 |
class Referral {
|
23 |
-
|
24 |
const OPTION_NAME_REFERRAL_DISMISSED = 'matomo-referral-dismissed';
|
25 |
|
26 |
/**
|
@@ -33,8 +33,9 @@ class Referral {
|
|
33 |
}
|
34 |
|
35 |
/**
|
36 |
-
* @internal for tests only
|
37 |
* @param int $time
|
|
|
|
|
38 |
*/
|
39 |
public function set_time( $time ) {
|
40 |
$this->time = $time;
|
@@ -75,6 +76,7 @@ class Referral {
|
|
75 |
return false;
|
76 |
}
|
77 |
$screen = get_current_screen();
|
|
|
78 |
return $screen && $screen->id && strpos( $screen->id, 'matomo-' ) === 0;
|
79 |
}
|
80 |
|
@@ -83,8 +85,8 @@ class Referral {
|
|
83 |
}
|
84 |
|
85 |
public function dismiss_forever() {
|
86 |
-
$
|
87 |
-
update_option( self::OPTION_NAME_REFERRAL_DISMISSED, $this->time + $
|
88 |
}
|
89 |
|
90 |
public function dismiss() {
|
@@ -106,17 +108,16 @@ class Referral {
|
|
106 |
// the first time we check... we set it back 30 days cause we want to see first rating after 60 days
|
107 |
$this->time = $this->time - $this->get_days_in_seconds( 30 );
|
108 |
$this->dismiss();
|
|
|
109 |
return false;
|
110 |
}
|
111 |
|
112 |
-
$
|
113 |
|
114 |
-
if ( $this->time > ( $dismissed + $
|
115 |
return true;
|
116 |
}
|
117 |
|
118 |
return false;
|
119 |
}
|
120 |
-
|
121 |
-
|
122 |
}
|
17 |
* Every 90 days we show a please review notice until the user dismisses this notice or clicks on rate us.
|
18 |
* We only show this notice on Matomo screens.
|
19 |
*
|
20 |
+
* @todo validate the nonce
|
21 |
+
* phpcs:disable WordPress.Security.NonceVerification.Missing
|
22 |
*/
|
23 |
class Referral {
|
|
|
24 |
const OPTION_NAME_REFERRAL_DISMISSED = 'matomo-referral-dismissed';
|
25 |
|
26 |
/**
|
33 |
}
|
34 |
|
35 |
/**
|
|
|
36 |
* @param int $time
|
37 |
+
*
|
38 |
+
* @internal for tests only
|
39 |
*/
|
40 |
public function set_time( $time ) {
|
41 |
$this->time = $time;
|
76 |
return false;
|
77 |
}
|
78 |
$screen = get_current_screen();
|
79 |
+
|
80 |
return $screen && $screen->id && strpos( $screen->id, 'matomo-' ) === 0;
|
81 |
}
|
82 |
|
85 |
}
|
86 |
|
87 |
public function dismiss_forever() {
|
88 |
+
$ten_years = 60 * 60 * 24 * 365 * 10;
|
89 |
+
update_option( self::OPTION_NAME_REFERRAL_DISMISSED, $this->time + $ten_years );
|
90 |
}
|
91 |
|
92 |
public function dismiss() {
|
108 |
// the first time we check... we set it back 30 days cause we want to see first rating after 60 days
|
109 |
$this->time = $this->time - $this->get_days_in_seconds( 30 );
|
110 |
$this->dismiss();
|
111 |
+
|
112 |
return false;
|
113 |
}
|
114 |
|
115 |
+
$ninety_days_in_seconds = $this->get_days_in_seconds( 90 );
|
116 |
|
117 |
+
if ( $this->time > ( $dismissed + $ninety_days_in_seconds ) ) {
|
118 |
return true;
|
119 |
}
|
120 |
|
121 |
return false;
|
122 |
}
|
|
|
|
|
123 |
}
|
classes/WpMatomo/Report/Data.php
CHANGED
@@ -35,10 +35,10 @@ class Data {
|
|
35 |
Bootstrap::do_bootstrap();
|
36 |
|
37 |
if ( empty( $idsite ) ) {
|
38 |
-
return
|
39 |
}
|
40 |
|
41 |
-
$params =
|
42 |
'apiModule' => $report_metadata['module'],
|
43 |
'apiAction' => $report_metadata['action'],
|
44 |
'filter_limit' => $filter_limit,
|
@@ -46,7 +46,7 @@ class Data {
|
|
46 |
'period' => $period,
|
47 |
'date' => $date,
|
48 |
'idSite' => $idsite,
|
49 |
-
|
50 |
if ( ! empty( $report_metadata['parameters'] ) ) {
|
51 |
$params = array_merge( $params, $report_metadata['parameters'] );
|
52 |
}
|
@@ -55,5 +55,4 @@ class Data {
|
|
55 |
|
56 |
return $report;
|
57 |
}
|
58 |
-
|
59 |
}
|
35 |
Bootstrap::do_bootstrap();
|
36 |
|
37 |
if ( empty( $idsite ) ) {
|
38 |
+
return [];
|
39 |
}
|
40 |
|
41 |
+
$params = [
|
42 |
'apiModule' => $report_metadata['module'],
|
43 |
'apiAction' => $report_metadata['action'],
|
44 |
'filter_limit' => $filter_limit,
|
46 |
'period' => $period,
|
47 |
'date' => $date,
|
48 |
'idSite' => $idsite,
|
49 |
+
];
|
50 |
if ( ! empty( $report_metadata['parameters'] ) ) {
|
51 |
$params = array_merge( $params, $report_metadata['parameters'] );
|
52 |
}
|
55 |
|
56 |
return $report;
|
57 |
}
|
|
|
58 |
}
|
classes/WpMatomo/Report/Dates.php
CHANGED
@@ -23,7 +23,7 @@ class Dates {
|
|
23 |
const THIS_YEAR = 'thisyear';
|
24 |
|
25 |
public function get_supported_dates() {
|
26 |
-
return
|
27 |
self::YESTERDAY => 'Yesterday',
|
28 |
self::TODAY => 'Today',
|
29 |
self::THIS_WEEK => 'This week',
|
@@ -31,7 +31,7 @@ class Dates {
|
|
31 |
self::THIS_MONTH => 'This month',
|
32 |
self::LAST_MONTH => 'Last month',
|
33 |
self::THIS_YEAR => 'This year',
|
34 |
-
|
35 |
}
|
36 |
|
37 |
public function detect_period_and_date( $report_date ) {
|
@@ -74,8 +74,6 @@ class Dates {
|
|
74 |
}
|
75 |
}
|
76 |
|
77 |
-
return
|
78 |
}
|
79 |
-
|
80 |
-
|
81 |
}
|
23 |
const THIS_YEAR = 'thisyear';
|
24 |
|
25 |
public function get_supported_dates() {
|
26 |
+
return [
|
27 |
self::YESTERDAY => 'Yesterday',
|
28 |
self::TODAY => 'Today',
|
29 |
self::THIS_WEEK => 'This week',
|
31 |
self::THIS_MONTH => 'This month',
|
32 |
self::LAST_MONTH => 'Last month',
|
33 |
self::THIS_YEAR => 'This year',
|
34 |
+
];
|
35 |
}
|
36 |
|
37 |
public function detect_period_and_date( $report_date ) {
|
74 |
}
|
75 |
}
|
76 |
|
77 |
+
return [ $period, $date ];
|
78 |
}
|
|
|
|
|
79 |
}
|
classes/WpMatomo/Report/Metadata.php
CHANGED
@@ -18,12 +18,13 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
18 |
}
|
19 |
|
20 |
class Metadata {
|
21 |
-
|
22 |
-
public static $
|
|
|
23 |
|
24 |
public function get_all_reports() {
|
25 |
-
if ( ! empty( self::$
|
26 |
-
return self::$
|
27 |
}
|
28 |
|
29 |
$site = new Site();
|
@@ -34,19 +35,19 @@ class Metadata {
|
|
34 |
|
35 |
$all_reports = Request::processRequest(
|
36 |
'API.getReportMetadata',
|
37 |
-
|
38 |
'idSite' => $idsite,
|
39 |
'filter_limit' => - 1,
|
40 |
-
|
41 |
);
|
42 |
foreach ( $all_reports as $single_report ) {
|
43 |
if ( isset( $single_report['uniqueId'] ) ) {
|
44 |
-
self::$
|
45 |
}
|
46 |
}
|
47 |
}
|
48 |
|
49 |
-
return self::$
|
50 |
}
|
51 |
|
52 |
/**
|
@@ -54,13 +55,16 @@ class Metadata {
|
|
54 |
* tests only
|
55 |
*/
|
56 |
public static function clear_cache() {
|
57 |
-
self::$
|
58 |
-
self::$
|
59 |
}
|
60 |
|
61 |
public function find_report_by_unique_id( $unique_id ) {
|
62 |
-
if (
|
63 |
-
return
|
|
|
|
|
|
|
64 |
}
|
65 |
$all_reports = self::get_all_reports();
|
66 |
|
@@ -70,8 +74,8 @@ class Metadata {
|
|
70 |
}
|
71 |
|
72 |
public function get_all_report_pages() {
|
73 |
-
if ( ! empty( self::$
|
74 |
-
return self::$
|
75 |
}
|
76 |
|
77 |
$site = new Site();
|
@@ -80,22 +84,22 @@ class Metadata {
|
|
80 |
if ( $idsite ) {
|
81 |
Bootstrap::do_bootstrap();
|
82 |
|
83 |
-
self::$
|
84 |
'API.getReportPagesMetadata',
|
85 |
-
|
86 |
'idSite' => $idsite,
|
87 |
'filter_limit' => - 1,
|
88 |
-
|
89 |
);
|
90 |
}
|
91 |
|
92 |
-
return self::$
|
93 |
}
|
94 |
|
95 |
public function find_report_page_params_by_report_metadata( $report_metadata ) {
|
96 |
if ( empty( $report_metadata['module'] )
|
97 |
|| empty( $report_metadata['action'] ) ) {
|
98 |
-
return
|
99 |
}
|
100 |
|
101 |
$report_pages = self::get_all_report_pages();
|
@@ -105,10 +109,10 @@ class Metadata {
|
|
105 |
foreach ( $report_page['widgets'] as $widget ) {
|
106 |
if ( ! empty( $widget['module'] ) && $widget['module'] === $report_metadata['module']
|
107 |
&& ! empty( $widget['action'] ) && $widget['action'] === $report_metadata['action'] ) {
|
108 |
-
return
|
109 |
'category' => $report_page['category']['id'],
|
110 |
'subcategory' => $report_page['subcategory']['id'],
|
111 |
-
|
112 |
}
|
113 |
}
|
114 |
}
|
@@ -118,28 +122,27 @@ class Metadata {
|
|
118 |
// we're hard coding some manually
|
119 |
|
120 |
if ( 'Actions_get' === $report_metadata['uniqueId'] ) {
|
121 |
-
return
|
122 |
'category' => 'General_Visitors',
|
123 |
'subcategory' => 'General_Overview',
|
124 |
-
|
125 |
} elseif ( 'Goals_get' === $report_metadata['uniqueId'] ) {
|
126 |
-
return
|
127 |
'category' => 'Goals_Goals',
|
128 |
'subcategory' => 'General_Overview',
|
129 |
-
|
130 |
} elseif ( 'Goals_get_idGoal--ecommerceOrder' === $report_metadata['uniqueId'] ) {
|
131 |
-
return
|
132 |
'category' => 'Goals_Ecommerce',
|
133 |
'subcategory' => 'General_Overview',
|
134 |
-
|
135 |
} elseif ( 'Goals_getItemsName' === $report_metadata['uniqueId'] ) {
|
136 |
-
return
|
137 |
'category' => 'Goals_Ecommerce',
|
138 |
'subcategory' => 'Goals_Products',
|
139 |
-
|
140 |
}
|
141 |
|
142 |
-
return
|
143 |
}
|
144 |
-
|
145 |
}
|
18 |
}
|
19 |
|
20 |
class Metadata {
|
21 |
+
|
22 |
+
public static $cache_all_reports = [];
|
23 |
+
public static $cache_all_report_pages = [];
|
24 |
|
25 |
public function get_all_reports() {
|
26 |
+
if ( ! empty( self::$cache_all_reports ) ) {
|
27 |
+
return self::$cache_all_reports;
|
28 |
}
|
29 |
|
30 |
$site = new Site();
|
35 |
|
36 |
$all_reports = Request::processRequest(
|
37 |
'API.getReportMetadata',
|
38 |
+
[
|
39 |
'idSite' => $idsite,
|
40 |
'filter_limit' => - 1,
|
41 |
+
]
|
42 |
);
|
43 |
foreach ( $all_reports as $single_report ) {
|
44 |
if ( isset( $single_report['uniqueId'] ) ) {
|
45 |
+
self::$cache_all_reports[ $single_report['uniqueId'] ] = $single_report;
|
46 |
}
|
47 |
}
|
48 |
}
|
49 |
|
50 |
+
return self::$cache_all_reports;
|
51 |
}
|
52 |
|
53 |
/**
|
55 |
* tests only
|
56 |
*/
|
57 |
public static function clear_cache() {
|
58 |
+
self::$cache_all_reports = [];
|
59 |
+
self::$cache_all_report_pages = [];
|
60 |
}
|
61 |
|
62 |
public function find_report_by_unique_id( $unique_id ) {
|
63 |
+
if ( Renderer::CUSTOM_UNIQUE_ID_VISITS_OVER_TIME === $unique_id ) {
|
64 |
+
return [
|
65 |
+
'uniqueId' => Renderer::CUSTOM_UNIQUE_ID_VISITS_OVER_TIME,
|
66 |
+
'name' => 'Visits over time',
|
67 |
+
];
|
68 |
}
|
69 |
$all_reports = self::get_all_reports();
|
70 |
|
74 |
}
|
75 |
|
76 |
public function get_all_report_pages() {
|
77 |
+
if ( ! empty( self::$cache_all_report_pages ) ) {
|
78 |
+
return self::$cache_all_report_pages;
|
79 |
}
|
80 |
|
81 |
$site = new Site();
|
84 |
if ( $idsite ) {
|
85 |
Bootstrap::do_bootstrap();
|
86 |
|
87 |
+
self::$cache_all_report_pages = Request::processRequest(
|
88 |
'API.getReportPagesMetadata',
|
89 |
+
[
|
90 |
'idSite' => $idsite,
|
91 |
'filter_limit' => - 1,
|
92 |
+
]
|
93 |
);
|
94 |
}
|
95 |
|
96 |
+
return self::$cache_all_report_pages;
|
97 |
}
|
98 |
|
99 |
public function find_report_page_params_by_report_metadata( $report_metadata ) {
|
100 |
if ( empty( $report_metadata['module'] )
|
101 |
|| empty( $report_metadata['action'] ) ) {
|
102 |
+
return [];
|
103 |
}
|
104 |
|
105 |
$report_pages = self::get_all_report_pages();
|
109 |
foreach ( $report_page['widgets'] as $widget ) {
|
110 |
if ( ! empty( $widget['module'] ) && $widget['module'] === $report_metadata['module']
|
111 |
&& ! empty( $widget['action'] ) && $widget['action'] === $report_metadata['action'] ) {
|
112 |
+
return [
|
113 |
'category' => $report_page['category']['id'],
|
114 |
'subcategory' => $report_page['subcategory']['id'],
|
115 |
+
];
|
116 |
}
|
117 |
}
|
118 |
}
|
122 |
// we're hard coding some manually
|
123 |
|
124 |
if ( 'Actions_get' === $report_metadata['uniqueId'] ) {
|
125 |
+
return [
|
126 |
'category' => 'General_Visitors',
|
127 |
'subcategory' => 'General_Overview',
|
128 |
+
];
|
129 |
} elseif ( 'Goals_get' === $report_metadata['uniqueId'] ) {
|
130 |
+
return [
|
131 |
'category' => 'Goals_Goals',
|
132 |
'subcategory' => 'General_Overview',
|
133 |
+
];
|
134 |
} elseif ( 'Goals_get_idGoal--ecommerceOrder' === $report_metadata['uniqueId'] ) {
|
135 |
+
return [
|
136 |
'category' => 'Goals_Ecommerce',
|
137 |
'subcategory' => 'General_Overview',
|
138 |
+
];
|
139 |
} elseif ( 'Goals_getItemsName' === $report_metadata['uniqueId'] ) {
|
140 |
+
return [
|
141 |
'category' => 'Goals_Ecommerce',
|
142 |
'subcategory' => 'Goals_Products',
|
143 |
+
];
|
144 |
}
|
145 |
|
146 |
+
return [];
|
147 |
}
|
|
|
148 |
}
|
classes/WpMatomo/Report/Renderer.php
CHANGED
@@ -16,31 +16,33 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
16 |
}
|
17 |
|
18 |
class Renderer {
|
19 |
-
|
20 |
|
21 |
public function register_hooks() {
|
22 |
-
add_shortcode( 'matomo_report',
|
23 |
}
|
24 |
|
25 |
-
public function show_visits_over_time($limit)
|
26 |
-
{
|
27 |
$cannot_view = $this->check_cannot_view();
|
28 |
-
if ($cannot_view) {
|
29 |
return $cannot_view;
|
30 |
}
|
31 |
|
32 |
-
if (is_numeric($limit)) {
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
|
38 |
-
$report_meta =
|
|
|
|
|
|
|
39 |
|
40 |
-
$data
|
41 |
-
$report
|
42 |
$first_metric_name = 'nb_visits';
|
43 |
-
|
44 |
ob_start();
|
45 |
|
46 |
include 'views/table_map_no_dimension.php';
|
@@ -48,8 +50,7 @@ class Renderer {
|
|
48 |
return ob_get_clean();
|
49 |
}
|
50 |
|
51 |
-
private function check_cannot_view()
|
52 |
-
{
|
53 |
if ( ! current_user_can( Capabilities::KEY_VIEW ) ) {
|
54 |
// not needed as processRequest checks permission anyway but it's faster this way and double ensures to not
|
55 |
// letting users view it when they have no access.
|
@@ -59,25 +60,29 @@ class Renderer {
|
|
59 |
|
60 |
public function show_report( $atts ) {
|
61 |
$a = shortcode_atts(
|
62 |
-
|
63 |
'unique_id' => '',
|
64 |
'report_date' => Dates::YESTERDAY,
|
65 |
'limit' => 10,
|
66 |
-
|
67 |
$atts
|
68 |
);
|
69 |
|
70 |
$cannot_view = $this->check_cannot_view();
|
71 |
-
if ($cannot_view) {
|
72 |
return $cannot_view;
|
73 |
}
|
74 |
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
|
|
|
|
|
|
|
|
81 |
}
|
82 |
|
83 |
$metadata = new Metadata();
|
@@ -91,9 +96,6 @@ class Renderer {
|
|
91 |
$first_metric_name = reset( $metric_keys );
|
92 |
$first_metric_display_name = reset( $report_meta['metrics'] );
|
93 |
|
94 |
-
$dates = new Dates();
|
95 |
-
list( $period, $date ) = $dates->detect_period_and_date( $a['report_date'] );
|
96 |
-
|
97 |
$report_data = new Data();
|
98 |
$report = $report_data->fetch_report( $report_meta, $period, $date, $first_metric_name, $a['limit'] );
|
99 |
$has_report_data = ! empty( $report['reportData'] ) && $report['reportData']->getRowsCount();
|
@@ -110,5 +112,4 @@ class Renderer {
|
|
110 |
|
111 |
return ob_get_clean();
|
112 |
}
|
113 |
-
|
114 |
}
|
16 |
}
|
17 |
|
18 |
class Renderer {
|
19 |
+
const CUSTOM_UNIQUE_ID_VISITS_OVER_TIME = 'visits_over_time';
|
20 |
|
21 |
public function register_hooks() {
|
22 |
+
add_shortcode( 'matomo_report', [ $this, 'show_report' ] );
|
23 |
}
|
24 |
|
25 |
+
public function show_visits_over_time( $limit, $period ) {
|
|
|
26 |
$cannot_view = $this->check_cannot_view();
|
27 |
+
if ( $cannot_view ) {
|
28 |
return $cannot_view;
|
29 |
}
|
30 |
|
31 |
+
if ( is_numeric( $limit ) ) {
|
32 |
+
$limit = (int) $limit;
|
33 |
+
} else {
|
34 |
+
$limit = 14;
|
35 |
+
}
|
36 |
|
37 |
+
$report_meta = [
|
38 |
+
'module' => 'VisitsSummary',
|
39 |
+
'action' => 'get',
|
40 |
+
];
|
41 |
|
42 |
+
$data = new Data();
|
43 |
+
$report = $data->fetch_report( $report_meta, $period, 'last' . $limit, 'label', $limit );
|
44 |
$first_metric_name = 'nb_visits';
|
45 |
+
$matomo_graph_data = ' data-chart="VisitsSumary"';
|
46 |
ob_start();
|
47 |
|
48 |
include 'views/table_map_no_dimension.php';
|
50 |
return ob_get_clean();
|
51 |
}
|
52 |
|
53 |
+
private function check_cannot_view() {
|
|
|
54 |
if ( ! current_user_can( Capabilities::KEY_VIEW ) ) {
|
55 |
// not needed as processRequest checks permission anyway but it's faster this way and double ensures to not
|
56 |
// letting users view it when they have no access.
|
60 |
|
61 |
public function show_report( $atts ) {
|
62 |
$a = shortcode_atts(
|
63 |
+
[
|
64 |
'unique_id' => '',
|
65 |
'report_date' => Dates::YESTERDAY,
|
66 |
'limit' => 10,
|
67 |
+
],
|
68 |
$atts
|
69 |
);
|
70 |
|
71 |
$cannot_view = $this->check_cannot_view();
|
72 |
+
if ( $cannot_view ) {
|
73 |
return $cannot_view;
|
74 |
}
|
75 |
|
76 |
+
$dates = new Dates();
|
77 |
+
list( $period, $date ) = $dates->detect_period_and_date( $a['report_date'] );
|
78 |
+
|
79 |
+
if ( 'visits_over_time' === $a['unique_id'] ) {
|
80 |
+
$is_default_limit = 10 === $a['limit'];
|
81 |
+
if ( $is_default_limit ) {
|
82 |
+
$a['limit'] = 14;
|
83 |
+
}
|
84 |
+
|
85 |
+
return $this->show_visits_over_time( $a['limit'], $period );
|
86 |
}
|
87 |
|
88 |
$metadata = new Metadata();
|
96 |
$first_metric_name = reset( $metric_keys );
|
97 |
$first_metric_display_name = reset( $report_meta['metrics'] );
|
98 |
|
|
|
|
|
|
|
99 |
$report_data = new Data();
|
100 |
$report = $report_data->fetch_report( $report_meta, $period, $date, $first_metric_name, $a['limit'] );
|
101 |
$has_report_data = ! empty( $report['reportData'] ) && $report['reportData']->getRowsCount();
|
112 |
|
113 |
return ob_get_clean();
|
114 |
}
|
|
|
115 |
}
|
classes/WpMatomo/Report/views/table.php
CHANGED
@@ -38,7 +38,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
38 |
$matomo_logo_image = '<img height="16" src="' . plugins_url( 'app/' . $matomo_logo, MATOMO_ANALYTICS_FILE ) . '"> ';
|
39 |
}
|
40 |
}
|
41 |
-
|
42 |
echo '<tr><td width="75%">' . $matomo_logo_image . esc_html( $matomo_report_row['label'] ) . '</td><td width="25%">' . esc_html( $matomo_report_row[ $first_metric_name ] ) . '</td></tr>';
|
43 |
}
|
44 |
}
|
38 |
$matomo_logo_image = '<img height="16" src="' . plugins_url( 'app/' . $matomo_logo, MATOMO_ANALYTICS_FILE ) . '"> ';
|
39 |
}
|
40 |
}
|
41 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
42 |
echo '<tr><td width="75%">' . $matomo_logo_image . esc_html( $matomo_report_row['label'] ) . '</td><td width="25%">' . esc_html( $matomo_report_row[ $first_metric_name ] ) . '</td></tr>';
|
43 |
}
|
44 |
}
|
classes/WpMatomo/Report/views/table_map_no_dimension.php
CHANGED
@@ -7,6 +7,8 @@
|
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
|
|
|
|
10 |
if ( ! defined( 'ABSPATH' ) ) {
|
11 |
exit; // if accessed directly
|
12 |
}
|
@@ -14,18 +16,22 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
14 |
/** @var array $report */
|
15 |
/** @var array $report_meta */
|
16 |
/** @var string $first_metric_name */
|
|
|
|
|
|
|
|
|
17 |
?>
|
18 |
<div class="table">
|
19 |
-
<table class="widefat matomo-table"
|
20 |
|
21 |
<tbody>
|
22 |
<?php
|
23 |
$matomo_report_metadata = $report['reportMetadata'];
|
24 |
-
$matomo_tables
|
25 |
-
foreach (array_reverse($matomo_tables)
|
26 |
-
/** @var
|
27 |
echo '<tr><td width="75%">' . esc_html( $matomo_report_date ) . '</td><td width="25%">';
|
28 |
-
if ($matomo_report_table->getFirstRow()) {
|
29 |
echo esc_html( $matomo_report_table->getFirstRow()->getColumn( $first_metric_name ) );
|
30 |
} else {
|
31 |
echo '-';
|
7 |
* @package matomo
|
8 |
*/
|
9 |
|
10 |
+
use Piwik\DataTable\Simple;
|
11 |
+
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
16 |
/** @var array $report */
|
17 |
/** @var array $report_meta */
|
18 |
/** @var string $first_metric_name */
|
19 |
+
/** @var string $matomo_graph_data */
|
20 |
+
if ( ! isset( $matomo_graph_data ) ) :
|
21 |
+
$matomo_graph_data = '';
|
22 |
+
endif;
|
23 |
?>
|
24 |
<div class="table">
|
25 |
+
<table class="widefat matomo-table" <?php echo esc_html( $matomo_graph_data ); ?>>
|
26 |
|
27 |
<tbody>
|
28 |
<?php
|
29 |
$matomo_report_metadata = $report['reportMetadata'];
|
30 |
+
$matomo_tables = $report['reportData']->getDataTables();
|
31 |
+
foreach ( array_reverse( $matomo_tables, true ) as $matomo_report_date => $matomo_report_table ) {
|
32 |
+
/** @var Simple $matomo_report_table */
|
33 |
echo '<tr><td width="75%">' . esc_html( $matomo_report_date ) . '</td><td width="25%">';
|
34 |
+
if ( $matomo_report_table->getFirstRow() ) {
|
35 |
echo esc_html( $matomo_report_table->getFirstRow()->getColumn( $first_metric_name ) );
|
36 |
} else {
|
37 |
echo '-';
|
classes/WpMatomo/Report/views/table_no_dimension.php
CHANGED
@@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
21 |
<table class="widefat matomo-table">
|
22 |
<tbody>
|
23 |
<?php
|
24 |
-
$matomo_columns = ! empty( $report['columns'] ) ? $report['columns'] :
|
25 |
foreach ( $report['reportData']->getRows() as $matomo_val => $matomo_row ) {
|
26 |
foreach ( $matomo_row as $matomo_metric_name => $matomo_value ) {
|
27 |
$matomo_display_name = ! empty( $matomo_columns[ $matomo_metric_name ] ) ? $matomo_columns[ $matomo_metric_name ] : $matomo_metric_name;
|
21 |
<table class="widefat matomo-table">
|
22 |
<tbody>
|
23 |
<?php
|
24 |
+
$matomo_columns = ! empty( $report['columns'] ) ? $report['columns'] : [];
|
25 |
foreach ( $report['reportData']->getRows() as $matomo_val => $matomo_row ) {
|
26 |
foreach ( $matomo_row as $matomo_metric_name => $matomo_value ) {
|
27 |
$matomo_display_name = ! empty( $matomo_columns[ $matomo_metric_name ] ) ? $matomo_columns[ $matomo_metric_name ] : $matomo_metric_name;
|
classes/WpMatomo/Roles.php
CHANGED
@@ -31,13 +31,13 @@ class Roles {
|
|
31 |
}
|
32 |
|
33 |
public function register_hooks() {
|
34 |
-
add_action( 'init',
|
35 |
}
|
36 |
|
37 |
public function get_available_roles_for_configuration() {
|
38 |
global $wp_roles;
|
39 |
$is_network_enabled = $this->settings->is_network_enabled();
|
40 |
-
$roles =
|
41 |
|
42 |
foreach ( $wp_roles->role_names as $role_name => $name ) {
|
43 |
if ( ! $is_network_enabled && 'administrator' === $role_name ) {
|
@@ -62,30 +62,30 @@ class Roles {
|
|
62 |
}
|
63 |
|
64 |
public function get_matomo_roles() {
|
65 |
-
return
|
66 |
-
self::ROLE_VIEW =>
|
67 |
'name' => 'Matomo View',
|
68 |
'defaultCap' => Capabilities::KEY_VIEW,
|
69 |
-
|
70 |
-
self::ROLE_WRITE =>
|
71 |
'name' => 'Matomo Write',
|
72 |
'defaultCap' => Capabilities::KEY_WRITE,
|
73 |
-
|
74 |
-
self::ROLE_ADMIN =>
|
75 |
'name' => 'Matomo Admin',
|
76 |
'defaultCap' => Capabilities::KEY_ADMIN,
|
77 |
-
|
78 |
-
self::ROLE_SUPERUSER =>
|
79 |
'name' => 'Matomo Super User',
|
80 |
'defaultCap' => Capabilities::KEY_SUPERUSER,
|
81 |
-
|
82 |
-
|
83 |
}
|
84 |
|
85 |
public function add_roles() {
|
86 |
if ( ! $this->has_set_up_roles() ) {
|
87 |
foreach ( $this->get_matomo_roles() as $role_name => $config ) {
|
88 |
-
add_role( $role_name, $config['name'],
|
89 |
}
|
90 |
$this->mark_roles_set_up();
|
91 |
}
|
31 |
}
|
32 |
|
33 |
public function register_hooks() {
|
34 |
+
add_action( 'init', [ $this, 'add_roles' ] );
|
35 |
}
|
36 |
|
37 |
public function get_available_roles_for_configuration() {
|
38 |
global $wp_roles;
|
39 |
$is_network_enabled = $this->settings->is_network_enabled();
|
40 |
+
$roles = [];
|
41 |
|
42 |
foreach ( $wp_roles->role_names as $role_name => $name ) {
|
43 |
if ( ! $is_network_enabled && 'administrator' === $role_name ) {
|
62 |
}
|
63 |
|
64 |
public function get_matomo_roles() {
|
65 |
+
return [
|
66 |
+
self::ROLE_VIEW => [
|
67 |
'name' => 'Matomo View',
|
68 |
'defaultCap' => Capabilities::KEY_VIEW,
|
69 |
+
],
|
70 |
+
self::ROLE_WRITE => [
|
71 |
'name' => 'Matomo Write',
|
72 |
'defaultCap' => Capabilities::KEY_WRITE,
|
73 |
+
],
|
74 |
+
self::ROLE_ADMIN => [
|
75 |
'name' => 'Matomo Admin',
|
76 |
'defaultCap' => Capabilities::KEY_ADMIN,
|
77 |
+
],
|
78 |
+
self::ROLE_SUPERUSER => [
|
79 |
'name' => 'Matomo Super User',
|
80 |
'defaultCap' => Capabilities::KEY_SUPERUSER,
|
81 |
+
],
|
82 |
+
];
|
83 |
}
|
84 |
|
85 |
public function add_roles() {
|
86 |
if ( ! $this->has_set_up_roles() ) {
|
87 |
foreach ( $this->get_matomo_roles() as $role_name => $config ) {
|
88 |
+
add_role( $role_name, $config['name'], [ $config['defaultCap'] => true ] );
|
89 |
}
|
90 |
$this->mark_roles_set_up();
|
91 |
}
|
classes/WpMatomo/ScheduledTasks.php
CHANGED
@@ -9,7 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
-
use
|
13 |
use Piwik\CronArchive;
|
14 |
use Piwik\Filesystem;
|
15 |
use Piwik\Option;
|
@@ -20,17 +20,18 @@ use Piwik\Plugins\GeoIp2\LocationProvider\GeoIp2\Php;
|
|
20 |
use Piwik\Plugins\UserCountry\LocationProvider;
|
21 |
use WpMatomo\Site\Sync as SiteSync;
|
22 |
use WpMatomo\User\Sync as UserSync;
|
|
|
23 |
|
24 |
if ( ! defined( 'ABSPATH' ) ) {
|
25 |
exit; // if accessed directly
|
26 |
}
|
27 |
|
28 |
class ScheduledTasks {
|
29 |
-
const EVENT_SYNC
|
30 |
-
const EVENT_DISABLE_ADDHANDLER
|
31 |
-
const EVENT_ARCHIVE
|
32 |
-
const EVENT_GEOIP
|
33 |
-
const EVENT_UPDATE
|
34 |
|
35 |
const KEY_BEFORE_CRON = 'before-cron-';
|
36 |
const KEY_AFTER_CRON = 'after-cron-';
|
@@ -51,23 +52,28 @@ class ScheduledTasks {
|
|
51 |
}
|
52 |
|
53 |
public function add_weekly_schedule( $schedules ) {
|
54 |
-
$schedules['matomo_monthly'] =
|
55 |
'interval' => 60 * 60 * 24 * 30,
|
56 |
'display' => __( 'Monthly', 'matomo' ),
|
57 |
-
|
58 |
|
59 |
return $schedules;
|
60 |
}
|
61 |
|
62 |
public function schedule() {
|
63 |
-
|
64 |
-
|
|
|
65 |
|
66 |
$self = $this;
|
67 |
$event_priority = 10;
|
68 |
|
|
|
|
|
|
|
69 |
foreach ( $this->get_all_events() as $event_name => $event_config ) {
|
70 |
-
|
|
|
71 |
wp_schedule_event( time(), $event_config['interval'], $event_name );
|
72 |
}
|
73 |
|
@@ -82,7 +88,7 @@ class ScheduledTasks {
|
|
82 |
);
|
83 |
|
84 |
// actual event
|
85 |
-
add_action( $event_name,
|
86 |
|
87 |
// logging last execution end time
|
88 |
add_action(
|
@@ -95,7 +101,7 @@ class ScheduledTasks {
|
|
95 |
);
|
96 |
}
|
97 |
|
98 |
-
register_deactivation_hook( MATOMO_ANALYTICS_FILE,
|
99 |
}
|
100 |
|
101 |
public function get_last_time_before_cron( $event_name ) {
|
@@ -117,77 +123,80 @@ class ScheduledTasks {
|
|
117 |
}
|
118 |
|
119 |
public function get_all_events() {
|
120 |
-
$events =
|
121 |
-
self::EVENT_SYNC =>
|
122 |
'name' => 'Sync users & sites',
|
123 |
'interval' => 'daily',
|
124 |
'method' => 'sync',
|
125 |
-
|
126 |
-
self::EVENT_ARCHIVE =>
|
127 |
'name' => 'Archive',
|
128 |
'interval' => 'hourly',
|
129 |
'method' => 'archive',
|
130 |
-
|
131 |
-
self::EVENT_GEOIP =>
|
132 |
'name' => 'Update GeoIP DB',
|
133 |
'interval' => 'matomo_monthly',
|
134 |
'method' => 'update_geo_ip2_db',
|
135 |
-
|
136 |
-
|
137 |
-
if ($this->settings->should_disable_addhandler()) {
|
138 |
-
$events[self::EVENT_DISABLE_ADDHANDLER] =
|
139 |
'name' => 'Disable AddHandler',
|
140 |
'interval' => 'hourly',
|
141 |
'method' => 'disable_add_handler',
|
142 |
-
|
143 |
}
|
|
|
144 |
return $events;
|
145 |
}
|
146 |
|
147 |
-
public function disable_add_handler($
|
148 |
-
{
|
149 |
$disable_addhandler = $this->settings->should_disable_addhandler();
|
150 |
-
if ($disable_addhandler) {
|
151 |
$this->logger->log( 'Scheduled tasks disabling addhandler' );
|
152 |
try {
|
153 |
Bootstrap::do_bootstrap();
|
154 |
|
155 |
-
$files = Filesystem::globr(dirname(MATOMO_ANALYTICS_FILE), '.htaccess');
|
156 |
-
foreach ($files as $file) {
|
157 |
-
if (is_readable($file)) {
|
158 |
-
|
159 |
-
|
|
|
|
|
160 |
$replace = '# AddHandler';
|
161 |
-
if ($
|
162 |
-
$search
|
163 |
$replace = 'AddHandler';
|
164 |
}
|
165 |
-
if (strpos($content, $search) !== false && ($
|
166 |
-
if (is_writeable($file)) {
|
167 |
-
$content
|
168 |
-
|
|
|
|
|
169 |
} else {
|
170 |
-
$this->logger->log('Cannot update file as not writable ' . $file);
|
171 |
}
|
172 |
}
|
173 |
}
|
174 |
}
|
175 |
-
} catch (
|
176 |
$this->logger->log_exception( 'disable_addhandler', $e );
|
177 |
throw $e;
|
178 |
}
|
179 |
}
|
180 |
}
|
181 |
|
182 |
-
private function check_try_update()
|
183 |
-
{
|
184 |
try {
|
185 |
$installer = new Installer( $this->settings );
|
186 |
if ( $installer->looks_like_it_is_installed() ) {
|
187 |
$updater = new Updater( $this->settings );
|
188 |
$updater->update_if_needed();
|
189 |
}
|
190 |
-
} catch (
|
191 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
192 |
$this->logger->log_exception( 'check_try_update', $e );
|
193 |
}
|
@@ -199,7 +208,7 @@ class ScheduledTasks {
|
|
199 |
try {
|
200 |
$updater = new Updater( $this->settings );
|
201 |
$updater->update();
|
202 |
-
} catch (
|
203 |
$this->logger->log_exception( 'cron_update', $e );
|
204 |
throw $e;
|
205 |
}
|
@@ -210,21 +219,21 @@ class ScheduledTasks {
|
|
210 |
try {
|
211 |
Bootstrap::do_bootstrap();
|
212 |
|
213 |
-
$maxmind_license = $this->settings->get_global_option('maxmind_license_key');
|
214 |
-
if (empty($maxmind_license)) {
|
215 |
-
$db_url
|
216 |
-
$asn_url = GeoIp2::getDbIpLiteUrl('asn');
|
217 |
} else {
|
218 |
-
$db_url
|
219 |
$asn_url = 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&suffix=tar.gz&license_key=' . $maxmind_license;
|
220 |
}
|
221 |
|
222 |
-
Option::set( GeoIP2AutoUpdater::LOC_URL_OPTION_NAME, $db_url);
|
223 |
|
224 |
-
if (Manager::getInstance()->isPluginActivated('Provider')) {
|
225 |
-
Option::set( GeoIP2AutoUpdater::ISP_URL_OPTION_NAME, $asn_url);
|
226 |
} else {
|
227 |
-
Option::delete(GeoIP2AutoUpdater::ISP_URL_OPTION_NAME);
|
228 |
}
|
229 |
|
230 |
$updater = new GeoIP2AutoUpdater();
|
@@ -232,7 +241,7 @@ class ScheduledTasks {
|
|
232 |
if ( LocationProvider::getCurrentProviderId() !== Php::ID && LocationProvider::getProviderById( Php::ID ) ) {
|
233 |
LocationProvider::setCurrentProvider( Php::ID );
|
234 |
}
|
235 |
-
} catch (
|
236 |
$this->logger->log_exception( 'update_geoip2', $e );
|
237 |
throw $e;
|
238 |
}
|
@@ -246,11 +255,11 @@ class ScheduledTasks {
|
|
246 |
try {
|
247 |
// we update the matomo url if needed/when possible. eg an update may be needed when site_url changes
|
248 |
$installer = new Installer( $this->settings );
|
249 |
-
if ($installer->looks_like_it_is_installed()) {
|
250 |
Bootstrap::do_bootstrap();
|
251 |
$installer->set_matomo_url();
|
252 |
}
|
253 |
-
} catch (
|
254 |
$this->logger->log_exception( 'matomo_url_sync', $e );
|
255 |
}
|
256 |
|
@@ -259,7 +268,7 @@ class ScheduledTasks {
|
|
259 |
$site->sync_all();
|
260 |
$user = new UserSync();
|
261 |
$user->sync_all();
|
262 |
-
} catch (
|
263 |
$this->logger->log_exception( 'cron_sync', $e );
|
264 |
throw $e;
|
265 |
}
|
@@ -274,27 +283,31 @@ class ScheduledTasks {
|
|
274 |
|
275 |
// we don't want any error triggered when a user vistis the website
|
276 |
// that's because cron might be triggered during a regular request from a regular user (unless WP CRON is disabled and triggered manually)
|
277 |
-
$should_rethrow_exception = is_admin() || (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) || (defined('MATOMO_PHPUNIT_TEST') && MATOMO_PHPUNIT_TEST);
|
278 |
|
279 |
$this->logger->log( 'Scheduled tasks archive data' );
|
280 |
|
281 |
try {
|
282 |
Bootstrap::do_bootstrap();
|
283 |
-
} catch (
|
284 |
$this->logger->log_exception( 'archive_bootstrap', $e );
|
285 |
-
if ($should_rethrow_exception || $force) {
|
286 |
// we want to trigger an exception if it was forced from the UI
|
287 |
throw $e;
|
288 |
}
|
289 |
}
|
290 |
|
291 |
-
$archiver
|
|
|
292 |
$archiver->concurrentRequestsPerWebsite = 1;
|
293 |
-
|
|
|
294 |
|
295 |
if ( $force ) {
|
296 |
-
|
297 |
-
$archiver->
|
|
|
|
|
298 |
}
|
299 |
|
300 |
if ( is_multisite() ) {
|
@@ -304,7 +317,8 @@ class ScheduledTasks {
|
|
304 |
$blog_id = get_current_blog_id();
|
305 |
$idsite = Site::get_matomo_site_id( $blog_id );
|
306 |
if ( ! empty( $idsite ) ) {
|
307 |
-
|
|
|
308 |
} else {
|
309 |
// there is no site mapped to it so there's no point in archiving it
|
310 |
return;
|
@@ -316,22 +330,22 @@ class ScheduledTasks {
|
|
316 |
$archiver->main();
|
317 |
|
318 |
$archive_errors = $archiver->getErrors();
|
319 |
-
|
320 |
-
|
321 |
-
$this->logger->log_exception( 'archive_main' , $e);
|
322 |
$archive_errors = $archiver->getErrors();
|
323 |
|
324 |
-
if (!empty($archive_errors)) {
|
325 |
$message = '';
|
326 |
-
foreach ($archiver->getErrors() as $error) {
|
327 |
-
|
|
|
328 |
}
|
329 |
-
$message = new
|
330 |
-
$this->logger->log_exception('archive_errors', $message);
|
331 |
}
|
332 |
|
333 |
-
if ($throw_exception) {
|
334 |
-
if ($should_rethrow_exception) {
|
335 |
throw $e;
|
336 |
}
|
337 |
// we otherwise only log the error but don't throw an exception
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Exception;
|
13 |
use Piwik\CronArchive;
|
14 |
use Piwik\Filesystem;
|
15 |
use Piwik\Option;
|
20 |
use Piwik\Plugins\UserCountry\LocationProvider;
|
21 |
use WpMatomo\Site\Sync as SiteSync;
|
22 |
use WpMatomo\User\Sync as UserSync;
|
23 |
+
use WpMatomo\Paths;
|
24 |
|
25 |
if ( ! defined( 'ABSPATH' ) ) {
|
26 |
exit; // if accessed directly
|
27 |
}
|
28 |
|
29 |
class ScheduledTasks {
|
30 |
+
const EVENT_SYNC = 'matomo_scheduled_sync';
|
31 |
+
const EVENT_DISABLE_ADDHANDLER = 'matomo_scheduled_disable_addhandler';
|
32 |
+
const EVENT_ARCHIVE = 'matomo_scheduled_archive';
|
33 |
+
const EVENT_GEOIP = 'matomo_scheduled_geoipdb';
|
34 |
+
const EVENT_UPDATE = 'matomo_update_core';
|
35 |
|
36 |
const KEY_BEFORE_CRON = 'before-cron-';
|
37 |
const KEY_AFTER_CRON = 'after-cron-';
|
52 |
}
|
53 |
|
54 |
public function add_weekly_schedule( $schedules ) {
|
55 |
+
$schedules['matomo_monthly'] = [
|
56 |
'interval' => 60 * 60 * 24 * 30,
|
57 |
'display' => __( 'Monthly', 'matomo' ),
|
58 |
+
];
|
59 |
|
60 |
return $schedules;
|
61 |
}
|
62 |
|
63 |
public function schedule() {
|
64 |
+
|
65 |
+
add_action( self::EVENT_UPDATE, [ $this, 'perform_update' ] );
|
66 |
+
add_filter( 'cron_schedules', [ $this, 'add_weekly_schedule' ] );
|
67 |
|
68 |
$self = $this;
|
69 |
$event_priority = 10;
|
70 |
|
71 |
+
$installer = new Installer( $this->settings );
|
72 |
+
$looks_installed = $installer->looks_like_it_is_installed(); // we only schedule events when Matomo looks installed but we still listen to the actions in case the app triggers a one time update.
|
73 |
+
|
74 |
foreach ( $this->get_all_events() as $event_name => $event_config ) {
|
75 |
+
|
76 |
+
if ( $looks_installed && ! wp_next_scheduled( $event_name ) ) {
|
77 |
wp_schedule_event( time(), $event_config['interval'], $event_name );
|
78 |
}
|
79 |
|
88 |
);
|
89 |
|
90 |
// actual event
|
91 |
+
add_action( $event_name, [ $this, $event_config['method'] ], $event_priority, $accepted_args = 0 );
|
92 |
|
93 |
// logging last execution end time
|
94 |
add_action(
|
101 |
);
|
102 |
}
|
103 |
|
104 |
+
register_deactivation_hook( MATOMO_ANALYTICS_FILE, [ $this, 'uninstall' ] );
|
105 |
}
|
106 |
|
107 |
public function get_last_time_before_cron( $event_name ) {
|
123 |
}
|
124 |
|
125 |
public function get_all_events() {
|
126 |
+
$events = [
|
127 |
+
self::EVENT_SYNC => [
|
128 |
'name' => 'Sync users & sites',
|
129 |
'interval' => 'daily',
|
130 |
'method' => 'sync',
|
131 |
+
],
|
132 |
+
self::EVENT_ARCHIVE => [
|
133 |
'name' => 'Archive',
|
134 |
'interval' => 'hourly',
|
135 |
'method' => 'archive',
|
136 |
+
],
|
137 |
+
self::EVENT_GEOIP => [
|
138 |
'name' => 'Update GeoIP DB',
|
139 |
'interval' => 'matomo_monthly',
|
140 |
'method' => 'update_geo_ip2_db',
|
141 |
+
],
|
142 |
+
];
|
143 |
+
if ( $this->settings->should_disable_addhandler() ) {
|
144 |
+
$events[ self::EVENT_DISABLE_ADDHANDLER ] = [
|
145 |
'name' => 'Disable AddHandler',
|
146 |
'interval' => 'hourly',
|
147 |
'method' => 'disable_add_handler',
|
148 |
+
];
|
149 |
}
|
150 |
+
|
151 |
return $events;
|
152 |
}
|
153 |
|
154 |
+
public function disable_add_handler( $force_undo = false ) {
|
|
|
155 |
$disable_addhandler = $this->settings->should_disable_addhandler();
|
156 |
+
if ( $disable_addhandler ) {
|
157 |
$this->logger->log( 'Scheduled tasks disabling addhandler' );
|
158 |
try {
|
159 |
Bootstrap::do_bootstrap();
|
160 |
|
161 |
+
$files = Filesystem::globr( dirname( MATOMO_ANALYTICS_FILE ), '.htaccess' );
|
162 |
+
foreach ( $files as $file ) {
|
163 |
+
if ( is_readable( $file ) ) {
|
164 |
+
// we don't need to access remote files
|
165 |
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
166 |
+
$content = file_get_contents( $file );
|
167 |
+
$search = 'AddHandler';
|
168 |
$replace = '# AddHandler';
|
169 |
+
if ( $force_undo ) {
|
170 |
+
$search = '# AddHandler';
|
171 |
$replace = 'AddHandler';
|
172 |
}
|
173 |
+
if ( strpos( $content, $search ) !== false && ( $force_undo || strpos( $content, $replace ) === false ) ) {
|
174 |
+
if ( is_writeable( $file ) ) {
|
175 |
+
$content = str_replace( $search, $replace, $content );
|
176 |
+
$paths = new Paths();
|
177 |
+
$wp_filesystem = $paths->get_file_system();
|
178 |
+
$wp_filesystem->put_contents( $file, $content );
|
179 |
} else {
|
180 |
+
$this->logger->log( 'Cannot update file as not writable ' . $file );
|
181 |
}
|
182 |
}
|
183 |
}
|
184 |
}
|
185 |
+
} catch ( Exception $e ) {
|
186 |
$this->logger->log_exception( 'disable_addhandler', $e );
|
187 |
throw $e;
|
188 |
}
|
189 |
}
|
190 |
}
|
191 |
|
192 |
+
private function check_try_update() {
|
|
|
193 |
try {
|
194 |
$installer = new Installer( $this->settings );
|
195 |
if ( $installer->looks_like_it_is_installed() ) {
|
196 |
$updater = new Updater( $this->settings );
|
197 |
$updater->update_if_needed();
|
198 |
}
|
199 |
+
} catch ( Exception $e ) {
|
200 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
201 |
$this->logger->log_exception( 'check_try_update', $e );
|
202 |
}
|
208 |
try {
|
209 |
$updater = new Updater( $this->settings );
|
210 |
$updater->update();
|
211 |
+
} catch ( Exception $e ) {
|
212 |
$this->logger->log_exception( 'cron_update', $e );
|
213 |
throw $e;
|
214 |
}
|
219 |
try {
|
220 |
Bootstrap::do_bootstrap();
|
221 |
|
222 |
+
$maxmind_license = $this->settings->get_global_option( 'maxmind_license_key' );
|
223 |
+
if ( empty( $maxmind_license ) ) {
|
224 |
+
$db_url = GeoIp2::getDbIpLiteUrl();
|
225 |
+
$asn_url = GeoIp2::getDbIpLiteUrl( 'asn' );
|
226 |
} else {
|
227 |
+
$db_url = 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&suffix=tar.gz&license_key=' . $maxmind_license;
|
228 |
$asn_url = 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&suffix=tar.gz&license_key=' . $maxmind_license;
|
229 |
}
|
230 |
|
231 |
+
Option::set( GeoIP2AutoUpdater::LOC_URL_OPTION_NAME, $db_url );
|
232 |
|
233 |
+
if ( Manager::getInstance()->isPluginActivated( 'Provider' ) ) {
|
234 |
+
Option::set( GeoIP2AutoUpdater::ISP_URL_OPTION_NAME, $asn_url );
|
235 |
} else {
|
236 |
+
Option::delete( GeoIP2AutoUpdater::ISP_URL_OPTION_NAME );
|
237 |
}
|
238 |
|
239 |
$updater = new GeoIP2AutoUpdater();
|
241 |
if ( LocationProvider::getCurrentProviderId() !== Php::ID && LocationProvider::getProviderById( Php::ID ) ) {
|
242 |
LocationProvider::setCurrentProvider( Php::ID );
|
243 |
}
|
244 |
+
} catch ( Exception $e ) {
|
245 |
$this->logger->log_exception( 'update_geoip2', $e );
|
246 |
throw $e;
|
247 |
}
|
255 |
try {
|
256 |
// we update the matomo url if needed/when possible. eg an update may be needed when site_url changes
|
257 |
$installer = new Installer( $this->settings );
|
258 |
+
if ( $installer->looks_like_it_is_installed() ) {
|
259 |
Bootstrap::do_bootstrap();
|
260 |
$installer->set_matomo_url();
|
261 |
}
|
262 |
+
} catch ( Exception $e ) {
|
263 |
$this->logger->log_exception( 'matomo_url_sync', $e );
|
264 |
}
|
265 |
|
268 |
$site->sync_all();
|
269 |
$user = new UserSync();
|
270 |
$user->sync_all();
|
271 |
+
} catch ( Exception $e ) {
|
272 |
$this->logger->log_exception( 'cron_sync', $e );
|
273 |
throw $e;
|
274 |
}
|
283 |
|
284 |
// we don't want any error triggered when a user vistis the website
|
285 |
// that's because cron might be triggered during a regular request from a regular user (unless WP CRON is disabled and triggered manually)
|
286 |
+
$should_rethrow_exception = is_admin() || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) || ( defined( 'MATOMO_PHPUNIT_TEST' ) && MATOMO_PHPUNIT_TEST );
|
287 |
|
288 |
$this->logger->log( 'Scheduled tasks archive data' );
|
289 |
|
290 |
try {
|
291 |
Bootstrap::do_bootstrap();
|
292 |
+
} catch ( Exception $e ) {
|
293 |
$this->logger->log_exception( 'archive_bootstrap', $e );
|
294 |
+
if ( $should_rethrow_exception || $force ) {
|
295 |
// we want to trigger an exception if it was forced from the UI
|
296 |
throw $e;
|
297 |
}
|
298 |
}
|
299 |
|
300 |
+
$archiver = new CronArchive();
|
301 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
302 |
$archiver->concurrentRequestsPerWebsite = 1;
|
303 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
304 |
+
$archiver->maxConcurrentArchivers = 1;
|
305 |
|
306 |
if ( $force ) {
|
307 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
308 |
+
$archiver->shouldArchiveAllSites = true;
|
309 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
310 |
+
$archiver->disableScheduledTasks = true;
|
311 |
}
|
312 |
|
313 |
if ( is_multisite() ) {
|
317 |
$blog_id = get_current_blog_id();
|
318 |
$idsite = Site::get_matomo_site_id( $blog_id );
|
319 |
if ( ! empty( $idsite ) ) {
|
320 |
+
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
321 |
+
$archiver->shouldArchiveSpecifiedSites = [ $idsite ];
|
322 |
} else {
|
323 |
// there is no site mapped to it so there's no point in archiving it
|
324 |
return;
|
330 |
$archiver->main();
|
331 |
|
332 |
$archive_errors = $archiver->getErrors();
|
333 |
+
} catch ( Exception $e ) {
|
334 |
+
$this->logger->log_exception( 'archive_main', $e );
|
|
|
335 |
$archive_errors = $archiver->getErrors();
|
336 |
|
337 |
+
if ( ! empty( $archive_errors ) ) {
|
338 |
$message = '';
|
339 |
+
foreach ( $archiver->getErrors() as $error ) {
|
340 |
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
|
341 |
+
$message .= var_export( $error, 1 ) . ' ';
|
342 |
}
|
343 |
+
$message = new Exception( trim( $message ) );
|
344 |
+
$this->logger->log_exception( 'archive_errors', $message );
|
345 |
}
|
346 |
|
347 |
+
if ( $throw_exception ) {
|
348 |
+
if ( $should_rethrow_exception ) {
|
349 |
throw $e;
|
350 |
}
|
351 |
// we otherwise only log the error but don't throw an exception
|
classes/WpMatomo/Settings.php
CHANGED
@@ -21,7 +21,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
21 |
}
|
22 |
|
23 |
class Settings {
|
24 |
-
|
25 |
const OPTION_PREFIX = 'matomo-';
|
26 |
const GLOBAL_OPTION_PREFIX = 'matomo_global-';
|
27 |
const OPTION = 'matomo-option';
|
@@ -55,13 +54,13 @@ class Settings {
|
|
55 |
*
|
56 |
* @var array
|
57 |
*/
|
58 |
-
private $default_global_settings =
|
59 |
// Plugin settings
|
60 |
'last_settings_update' => 0,
|
61 |
self::OPTION_LAST_TRACKING_SETTINGS_CHANGE => 0,
|
62 |
-
self::OPTION_KEY_STEALTH =>
|
63 |
-
self::OPTION_KEY_CAPS_ACCESS =>
|
64 |
-
self::NETWORK_CONFIG_OPTIONS =>
|
65 |
self::DELETE_ALL_DATA_ON_UNINSTALL => true,
|
66 |
self::SITE_CURRENCY => 'USD',
|
67 |
// User settings: Stats configuration
|
@@ -75,8 +74,8 @@ class Settings {
|
|
75 |
'track_ecommerce' => true,
|
76 |
'track_search' => false,
|
77 |
'track_404' => false,
|
78 |
-
'tagmanger_container_ids' =>
|
79 |
-
'add_post_annotations' =>
|
80 |
'add_customvars_box' => false,
|
81 |
'js_manually' => '',
|
82 |
'noscript_manually' => '',
|
@@ -85,8 +84,8 @@ class Settings {
|
|
85 |
'set_link_classes' => '',
|
86 |
'set_download_classes' => '',
|
87 |
'core_version' => '',
|
88 |
-
'version_history' =>
|
89 |
-
'mail_history' =>
|
90 |
'disable_cookies' => false,
|
91 |
'cookie_consent' => CookieConsent::REQUIRE_NONE,
|
92 |
'force_post' => false,
|
@@ -108,23 +107,23 @@ class Settings {
|
|
108 |
'force_protocol' => 'disabled',
|
109 |
'maxmind_license_key' => '',
|
110 |
self::SHOW_GET_STARTED_PAGE => 1,
|
111 |
-
|
112 |
|
113 |
/**
|
114 |
* Settings stored per blog
|
115 |
*
|
116 |
* @var array
|
117 |
*/
|
118 |
-
private $default_blog_settings =
|
119 |
'noscript_code' => '',
|
120 |
'tracking_code' => '',
|
121 |
self::OPTION_LAST_TRACKING_CODE_UPDATE => 0,
|
122 |
-
|
123 |
|
124 |
-
private $global_settings =
|
125 |
-
private $blog_settings =
|
126 |
|
127 |
-
private $settings_changed =
|
128 |
|
129 |
/**
|
130 |
* @var Logger
|
@@ -142,21 +141,21 @@ class Settings {
|
|
142 |
}
|
143 |
|
144 |
public function init_settings() {
|
145 |
-
$this->settings_changed =
|
146 |
-
$this->global_settings =
|
147 |
-
$this->blog_settings =
|
148 |
|
149 |
if ( $this->is_network_enabled() ) {
|
150 |
-
$global_settings = get_site_option( self::OPTION_GLOBAL,
|
151 |
} else {
|
152 |
-
$global_settings = get_option( self::OPTION_GLOBAL,
|
153 |
}
|
154 |
|
155 |
if ( ! empty( $global_settings ) && is_array( $global_settings ) ) {
|
156 |
$this->global_settings = $global_settings;
|
157 |
}
|
158 |
|
159 |
-
$settings = get_option( self::OPTION,
|
160 |
|
161 |
if ( ! empty( $settings ) && is_array( $settings ) ) {
|
162 |
$this->blog_settings = $settings;
|
@@ -164,10 +163,11 @@ class Settings {
|
|
164 |
}
|
165 |
|
166 |
public function get_customised_global_settings() {
|
167 |
-
$custom_settings =
|
168 |
|
169 |
foreach ( $this->global_settings as $key => $val ) {
|
170 |
if ( isset( $this->default_global_settings[ $key ] )
|
|
|
171 |
&& $this->default_global_settings[ $key ] != $val ) {
|
172 |
$custom_settings[ $key ] = $val;
|
173 |
}
|
@@ -223,7 +223,7 @@ class Settings {
|
|
223 |
update_option( self::OPTION, $this->blog_settings );
|
224 |
|
225 |
$keys_changed = array_values( array_unique( $this->settings_changed ) );
|
226 |
-
$this->settings_changed =
|
227 |
|
228 |
foreach ( $keys_changed as $key_changed ) {
|
229 |
do_action( 'matomo_setting_change_' . $key_changed );
|
@@ -268,11 +268,12 @@ class Settings {
|
|
268 |
}
|
269 |
|
270 |
private function convert_type( $value, $type ) {
|
271 |
-
if (
|
272 |
-
$value =
|
273 |
} else {
|
274 |
settype( $value, $type );
|
275 |
}
|
|
|
276 |
return $value;
|
277 |
}
|
278 |
|
@@ -289,7 +290,7 @@ class Settings {
|
|
289 |
}
|
290 |
|
291 |
if ( ! isset( $this->global_settings[ $key ] )
|
292 |
-
|
293 |
$this->settings_changed[] = $key;
|
294 |
$this->logger->log( 'Changed global option ' . $key . ': ' . ( is_array( $value ) ? wp_json_encode( $value ) : $value ) );
|
295 |
|
@@ -310,7 +311,7 @@ class Settings {
|
|
310 |
}
|
311 |
|
312 |
if ( ! isset( $this->blog_settings[ $key ] )
|
313 |
-
|
314 |
$this->settings_changed[] = $key;
|
315 |
$this->logger->log( 'Changed option ' . $key . ': ' . $value );
|
316 |
$this->blog_settings[ $key ] = $value;
|
@@ -335,12 +336,12 @@ class Settings {
|
|
335 |
}
|
336 |
}
|
337 |
|
338 |
-
public function should_disable_addhandler()
|
339 |
-
|
340 |
-
if ($this->force_disable_addhandler) {
|
341 |
return true;
|
342 |
}
|
343 |
-
|
|
|
344 |
}
|
345 |
|
346 |
/**
|
@@ -376,7 +377,7 @@ class Settings {
|
|
376 |
|
377 |
private function should_save_tracking_code_across_sites() {
|
378 |
return $this->is_network_enabled()
|
379 |
-
|
380 |
}
|
381 |
|
382 |
public function get_js_tracking_code() {
|
@@ -402,7 +403,7 @@ class Settings {
|
|
402 |
public function get_tracking_cookie_domain() {
|
403 |
if ( $this->get_global_option( 'track_across' )
|
404 |
|| $this->get_global_option( 'track_crossdomain_linking' ) ) {
|
405 |
-
$host =
|
406 |
if ( ! empty( $host ) ) {
|
407 |
return '*.' . $host;
|
408 |
}
|
@@ -411,13 +412,12 @@ class Settings {
|
|
411 |
return '';
|
412 |
}
|
413 |
|
414 |
-
public function should_delete_all_data_on_uninstall()
|
415 |
-
|
416 |
-
if (defined( 'MATOMO_REMOVE_ALL_DATA' )) {
|
417 |
return (bool) MATOMO_REMOVE_ALL_DATA;
|
418 |
}
|
419 |
|
420 |
-
return (bool) $this->get_global_option(self::DELETE_ALL_DATA_ON_UNINSTALL);
|
421 |
}
|
422 |
|
423 |
/**
|
@@ -455,7 +455,7 @@ class Settings {
|
|
455 |
}
|
456 |
|
457 |
public function is_tracking_enabled() {
|
458 |
-
return $this->get_global_option( 'track_mode' )
|
459 |
}
|
460 |
|
461 |
/**
|
@@ -476,7 +476,7 @@ class Settings {
|
|
476 |
}
|
477 |
|
478 |
public function track_user_id_enabled() {
|
479 |
-
return $this->get_global_option( 'track_user_id' )
|
480 |
}
|
481 |
|
482 |
public function track_search_enabled() {
|
21 |
}
|
22 |
|
23 |
class Settings {
|
|
|
24 |
const OPTION_PREFIX = 'matomo-';
|
25 |
const GLOBAL_OPTION_PREFIX = 'matomo_global-';
|
26 |
const OPTION = 'matomo-option';
|
54 |
*
|
55 |
* @var array
|
56 |
*/
|
57 |
+
private $default_global_settings = [
|
58 |
// Plugin settings
|
59 |
'last_settings_update' => 0,
|
60 |
self::OPTION_LAST_TRACKING_SETTINGS_CHANGE => 0,
|
61 |
+
self::OPTION_KEY_STEALTH => [],
|
62 |
+
self::OPTION_KEY_CAPS_ACCESS => [],
|
63 |
+
self::NETWORK_CONFIG_OPTIONS => [],
|
64 |
self::DELETE_ALL_DATA_ON_UNINSTALL => true,
|
65 |
self::SITE_CURRENCY => 'USD',
|
66 |
// User settings: Stats configuration
|
74 |
'track_ecommerce' => true,
|
75 |
'track_search' => false,
|
76 |
'track_404' => false,
|
77 |
+
'tagmanger_container_ids' => [],
|
78 |
+
'add_post_annotations' => [],
|
79 |
'add_customvars_box' => false,
|
80 |
'js_manually' => '',
|
81 |
'noscript_manually' => '',
|
84 |
'set_link_classes' => '',
|
85 |
'set_download_classes' => '',
|
86 |
'core_version' => '',
|
87 |
+
'version_history' => [],
|
88 |
+
'mail_history' => [],
|
89 |
'disable_cookies' => false,
|
90 |
'cookie_consent' => CookieConsent::REQUIRE_NONE,
|
91 |
'force_post' => false,
|
107 |
'force_protocol' => 'disabled',
|
108 |
'maxmind_license_key' => '',
|
109 |
self::SHOW_GET_STARTED_PAGE => 1,
|
110 |
+
];
|
111 |
|
112 |
/**
|
113 |
* Settings stored per blog
|
114 |
*
|
115 |
* @var array
|
116 |
*/
|
117 |
+
private $default_blog_settings = [
|
118 |
'noscript_code' => '',
|
119 |
'tracking_code' => '',
|
120 |
self::OPTION_LAST_TRACKING_CODE_UPDATE => 0,
|
121 |
+
];
|
122 |
|
123 |
+
private $global_settings = [];
|
124 |
+
private $blog_settings = [];
|
125 |
|
126 |
+
private $settings_changed = [];
|
127 |
|
128 |
/**
|
129 |
* @var Logger
|
141 |
}
|
142 |
|
143 |
public function init_settings() {
|
144 |
+
$this->settings_changed = [];
|
145 |
+
$this->global_settings = [];
|
146 |
+
$this->blog_settings = [];
|
147 |
|
148 |
if ( $this->is_network_enabled() ) {
|
149 |
+
$global_settings = get_site_option( self::OPTION_GLOBAL, [] );
|
150 |
} else {
|
151 |
+
$global_settings = get_option( self::OPTION_GLOBAL, [] );
|
152 |
}
|
153 |
|
154 |
if ( ! empty( $global_settings ) && is_array( $global_settings ) ) {
|
155 |
$this->global_settings = $global_settings;
|
156 |
}
|
157 |
|
158 |
+
$settings = get_option( self::OPTION, [] );
|
159 |
|
160 |
if ( ! empty( $settings ) && is_array( $settings ) ) {
|
161 |
$this->blog_settings = $settings;
|
163 |
}
|
164 |
|
165 |
public function get_customised_global_settings() {
|
166 |
+
$custom_settings = [];
|
167 |
|
168 |
foreach ( $this->global_settings as $key => $val ) {
|
169 |
if ( isset( $this->default_global_settings[ $key ] )
|
170 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
171 |
&& $this->default_global_settings[ $key ] != $val ) {
|
172 |
$custom_settings[ $key ] = $val;
|
173 |
}
|
223 |
update_option( self::OPTION, $this->blog_settings );
|
224 |
|
225 |
$keys_changed = array_values( array_unique( $this->settings_changed ) );
|
226 |
+
$this->settings_changed = [];
|
227 |
|
228 |
foreach ( $keys_changed as $key_changed ) {
|
229 |
do_action( 'matomo_setting_change_' . $key_changed );
|
268 |
}
|
269 |
|
270 |
private function convert_type( $value, $type ) {
|
271 |
+
if ( 'array' === $type && empty( $value ) ) {
|
272 |
+
$value = []; // prevent eg converting '' to array('')
|
273 |
} else {
|
274 |
settype( $value, $type );
|
275 |
}
|
276 |
+
|
277 |
return $value;
|
278 |
}
|
279 |
|
290 |
}
|
291 |
|
292 |
if ( ! isset( $this->global_settings[ $key ] )
|
293 |
+
|| $this->global_settings[ $key ] !== $value ) {
|
294 |
$this->settings_changed[] = $key;
|
295 |
$this->logger->log( 'Changed global option ' . $key . ': ' . ( is_array( $value ) ? wp_json_encode( $value ) : $value ) );
|
296 |
|
311 |
}
|
312 |
|
313 |
if ( ! isset( $this->blog_settings[ $key ] )
|
314 |
+
|| $this->blog_settings[ $key ] !== $value ) {
|
315 |
$this->settings_changed[] = $key;
|
316 |
$this->logger->log( 'Changed option ' . $key . ': ' . $value );
|
317 |
$this->blog_settings[ $key ] = $value;
|
336 |
}
|
337 |
}
|
338 |
|
339 |
+
public function should_disable_addhandler() {
|
340 |
+
if ( $this->force_disable_addhandler ) {
|
|
|
341 |
return true;
|
342 |
}
|
343 |
+
|
344 |
+
return defined( 'MATOMO_DISABLE_ADDHANDLER' ) && MATOMO_DISABLE_ADDHANDLER;
|
345 |
}
|
346 |
|
347 |
/**
|
377 |
|
378 |
private function should_save_tracking_code_across_sites() {
|
379 |
return $this->is_network_enabled()
|
380 |
+
&& $this->get_global_option( 'track_mode' ) === TrackingSettings::TRACK_MODE_MANUALLY;
|
381 |
}
|
382 |
|
383 |
public function get_js_tracking_code() {
|
403 |
public function get_tracking_cookie_domain() {
|
404 |
if ( $this->get_global_option( 'track_across' )
|
405 |
|| $this->get_global_option( 'track_crossdomain_linking' ) ) {
|
406 |
+
$host = wp_parse_url( home_url(), PHP_URL_HOST );
|
407 |
if ( ! empty( $host ) ) {
|
408 |
return '*.' . $host;
|
409 |
}
|
412 |
return '';
|
413 |
}
|
414 |
|
415 |
+
public function should_delete_all_data_on_uninstall() {
|
416 |
+
if ( defined( 'MATOMO_REMOVE_ALL_DATA' ) ) {
|
|
|
417 |
return (bool) MATOMO_REMOVE_ALL_DATA;
|
418 |
}
|
419 |
|
420 |
+
return (bool) $this->get_global_option( self::DELETE_ALL_DATA_ON_UNINSTALL );
|
421 |
}
|
422 |
|
423 |
/**
|
455 |
}
|
456 |
|
457 |
public function is_tracking_enabled() {
|
458 |
+
return $this->get_global_option( 'track_mode' ) !== 'disabled';
|
459 |
}
|
460 |
|
461 |
/**
|
476 |
}
|
477 |
|
478 |
public function track_user_id_enabled() {
|
479 |
+
return $this->get_global_option( 'track_user_id' ) !== 'disabled';
|
480 |
}
|
481 |
|
482 |
public function track_search_enabled() {
|
classes/WpMatomo/Site.php
CHANGED
@@ -14,7 +14,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
14 |
}
|
15 |
|
16 |
class Site {
|
17 |
-
|
18 |
const SITE_MAPPING_PREFIX = 'matomo-site-id-';
|
19 |
|
20 |
/**
|
14 |
}
|
15 |
|
16 |
class Site {
|
|
|
17 |
const SITE_MAPPING_PREFIX = 'matomo-site-id-';
|
18 |
|
19 |
/**
|
classes/WpMatomo/Site/Sync.php
CHANGED
@@ -9,14 +9,14 @@
|
|
9 |
|
10 |
namespace WpMatomo\Site;
|
11 |
|
|
|
12 |
use Piwik\Access;
|
13 |
-
use Piwik\API\Request;
|
14 |
-
use Piwik\Common;
|
15 |
use Piwik\Config;
|
16 |
use Piwik\Container\StaticContainer;
|
17 |
use Piwik\Intl\Data\Provider\CurrencyDataProvider;
|
18 |
-
use Piwik\Plugins\SitesManager\Model;
|
19 |
use Piwik\Plugins\SitesManager;
|
|
|
|
|
20 |
use WpMatomo\Bootstrap;
|
21 |
use WpMatomo\Installer;
|
22 |
use WpMatomo\Logger;
|
@@ -28,6 +28,10 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
28 |
exit; // if accessed directly
|
29 |
}
|
30 |
|
|
|
|
|
|
|
|
|
31 |
class Sync {
|
32 |
const MAX_LENGTH_SITE_NAME = 90;
|
33 |
|
@@ -41,33 +45,32 @@ class Sync {
|
|
41 |
*/
|
42 |
private $settings;
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
private $config_sync;
|
48 |
|
49 |
public function __construct( Settings $settings ) {
|
50 |
-
$this->logger
|
51 |
-
$this->settings
|
52 |
$this->config_sync = new SyncConfig( $settings );
|
53 |
}
|
54 |
|
55 |
public function register_hooks() {
|
56 |
-
add_action( 'update_option_blogname',
|
57 |
-
add_action( 'update_option_home',
|
58 |
-
add_action( 'update_option_siteurl',
|
59 |
-
add_action( 'update_option_timezone_string',
|
60 |
-
add_action( 'matomo_setting_change_track_ecommerce',
|
61 |
-
add_action( 'matomo_setting_change_site_currency',
|
62 |
}
|
63 |
|
64 |
-
public function sync_current_site_ignore_error()
|
65 |
-
{
|
66 |
try {
|
67 |
$this->sync_current_site();
|
68 |
-
} catch (
|
69 |
-
$this->logger->log( 'Ignoring site sync error: ' . $e->getMessage());
|
70 |
-
$this->logger->log_exception('sync_site_ignore', $e);
|
71 |
}
|
72 |
}
|
73 |
|
@@ -78,7 +81,7 @@ class Sync {
|
|
78 |
|
79 |
if ( is_multisite() && function_exists( 'get_sites' ) ) {
|
80 |
foreach ( get_sites() as $site ) {
|
81 |
-
/** @var
|
82 |
switch_to_blog( $site->blog_id );
|
83 |
try {
|
84 |
$installer = new Installer( $this->settings );
|
@@ -90,7 +93,7 @@ class Sync {
|
|
90 |
Bootstrap::set_not_bootstrapped();
|
91 |
$config = Config::getInstance();
|
92 |
$installed = $config->PluginsInstalled;
|
93 |
-
$installed['PluginsInstalled'] =
|
94 |
$config->PluginsInstalled = $installed;
|
95 |
|
96 |
if ( $installer->can_be_installed() ) {
|
@@ -101,7 +104,7 @@ class Sync {
|
|
101 |
}
|
102 |
|
103 |
$success = $this->sync_site( $site->blog_id, $site->blogname, $site->siteurl );
|
104 |
-
} catch (
|
105 |
$success = false;
|
106 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
107 |
$this->logger->log( 'Matomo error syncing site: ' . $e->getMessage() );
|
@@ -134,76 +137,77 @@ class Sync {
|
|
134 |
$blog_name = substr( $blog_name, 0, self::MAX_LENGTH_SITE_NAME );
|
135 |
}
|
136 |
|
137 |
-
$track_ecommerce
|
138 |
-
$site_currency
|
139 |
$detected_timezone = $this->detect_timezone();
|
140 |
|
141 |
-
|
142 |
-
$valid_currencies = $
|
143 |
-
if (!array_key_exists($site_currency, $valid_currencies)){
|
144 |
$site_currency = 'USD';
|
145 |
}
|
146 |
|
147 |
if ( ! empty( $idsite ) ) {
|
148 |
$this->logger->log( 'Matomo site is known for blog (' . $idsite . ')... will update' );
|
149 |
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
180 |
}
|
181 |
|
182 |
$this->logger->log( 'Matomo site is not known for blog... will create site' );
|
183 |
|
184 |
-
/** @var
|
185 |
-
$idsite
|
186 |
|
187 |
$this->set_enable_sites_admin( 1 );
|
188 |
|
189 |
Access::doAsSuperUser(
|
190 |
function () use ( $blog_name, $blog_url, $detected_timezone, $track_ecommerce, &$idsite, $site_currency ) {
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
}
|
208 |
);
|
209 |
$this->set_enable_sites_admin( 0 );
|
@@ -218,7 +222,7 @@ class Sync {
|
|
218 |
|
219 |
Site::map_matomo_site_id( $blog_id, $idsite );
|
220 |
|
221 |
-
|
222 |
|
223 |
do_action( 'matomo_site_synced', $idsite, $blog_id );
|
224 |
|
@@ -252,7 +256,7 @@ class Sync {
|
|
252 |
return $timezone;
|
253 |
}
|
254 |
|
255 |
-
$dst = (bool)
|
256 |
foreach ( timezone_abbreviations_list() as $abbr ) {
|
257 |
foreach ( $abbr as $city ) {
|
258 |
if ( $dst === (bool) $city['dst']
|
@@ -280,16 +284,17 @@ class Sync {
|
|
280 |
|
281 |
private function check_and_try_to_set_default_timezone( $timezone ) {
|
282 |
try {
|
283 |
-
Access::doAsSuperUser(
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
|
|
|
|
289 |
return false;
|
290 |
}
|
291 |
|
292 |
return true;
|
293 |
}
|
294 |
-
|
295 |
}
|
9 |
|
10 |
namespace WpMatomo\Site;
|
11 |
|
12 |
+
use Exception;
|
13 |
use Piwik\Access;
|
|
|
|
|
14 |
use Piwik\Config;
|
15 |
use Piwik\Container\StaticContainer;
|
16 |
use Piwik\Intl\Data\Provider\CurrencyDataProvider;
|
|
|
17 |
use Piwik\Plugins\SitesManager;
|
18 |
+
use Piwik\Plugins\SitesManager\Model;
|
19 |
+
use WP_Site;
|
20 |
use WpMatomo\Bootstrap;
|
21 |
use WpMatomo\Installer;
|
22 |
use WpMatomo\Logger;
|
28 |
exit; // if accessed directly
|
29 |
}
|
30 |
|
31 |
+
/**
|
32 |
+
* Properties coming from matomo
|
33 |
+
* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
34 |
+
*/
|
35 |
class Sync {
|
36 |
const MAX_LENGTH_SITE_NAME = 90;
|
37 |
|
45 |
*/
|
46 |
private $settings;
|
47 |
|
48 |
+
/**
|
49 |
+
* @var SyncConfig
|
50 |
+
*/
|
51 |
private $config_sync;
|
52 |
|
53 |
public function __construct( Settings $settings ) {
|
54 |
+
$this->logger = new Logger();
|
55 |
+
$this->settings = $settings;
|
56 |
$this->config_sync = new SyncConfig( $settings );
|
57 |
}
|
58 |
|
59 |
public function register_hooks() {
|
60 |
+
add_action( 'update_option_blogname', [ $this, 'sync_current_site_ignore_error' ] );
|
61 |
+
add_action( 'update_option_home', [ $this, 'sync_current_site_ignore_error' ] );
|
62 |
+
add_action( 'update_option_siteurl', [ $this, 'sync_current_site_ignore_error' ] );
|
63 |
+
add_action( 'update_option_timezone_string', [ $this, 'sync_current_site_ignore_error' ] );
|
64 |
+
add_action( 'matomo_setting_change_track_ecommerce', [ $this, 'sync_current_site_ignore_error' ] );
|
65 |
+
add_action( 'matomo_setting_change_site_currency', [ $this, 'sync_current_site_ignore_error' ] );
|
66 |
}
|
67 |
|
68 |
+
public function sync_current_site_ignore_error() {
|
|
|
69 |
try {
|
70 |
$this->sync_current_site();
|
71 |
+
} catch ( Exception $e ) {
|
72 |
+
$this->logger->log( 'Ignoring site sync error: ' . $e->getMessage() );
|
73 |
+
$this->logger->log_exception( 'sync_site_ignore', $e );
|
74 |
}
|
75 |
}
|
76 |
|
81 |
|
82 |
if ( is_multisite() && function_exists( 'get_sites' ) ) {
|
83 |
foreach ( get_sites() as $site ) {
|
84 |
+
/** @var WP_Site $site */
|
85 |
switch_to_blog( $site->blog_id );
|
86 |
try {
|
87 |
$installer = new Installer( $this->settings );
|
93 |
Bootstrap::set_not_bootstrapped();
|
94 |
$config = Config::getInstance();
|
95 |
$installed = $config->PluginsInstalled;
|
96 |
+
$installed['PluginsInstalled'] = [];
|
97 |
$config->PluginsInstalled = $installed;
|
98 |
|
99 |
if ( $installer->can_be_installed() ) {
|
104 |
}
|
105 |
|
106 |
$success = $this->sync_site( $site->blog_id, $site->blogname, $site->siteurl );
|
107 |
+
} catch ( Exception $e ) {
|
108 |
$success = false;
|
109 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
110 |
$this->logger->log( 'Matomo error syncing site: ' . $e->getMessage() );
|
137 |
$blog_name = substr( $blog_name, 0, self::MAX_LENGTH_SITE_NAME );
|
138 |
}
|
139 |
|
140 |
+
$track_ecommerce = (int) $this->settings->get_global_option( 'track_ecommerce' );
|
141 |
+
$site_currency = $this->settings->get_global_option( Settings::SITE_CURRENCY );
|
142 |
$detected_timezone = $this->detect_timezone();
|
143 |
|
144 |
+
$data_provider = StaticContainer::get( CurrencyDataProvider::class );
|
145 |
+
$valid_currencies = $data_provider->getCurrencyList();
|
146 |
+
if ( ! array_key_exists( $site_currency, $valid_currencies ) ) {
|
147 |
$site_currency = 'USD';
|
148 |
}
|
149 |
|
150 |
if ( ! empty( $idsite ) ) {
|
151 |
$this->logger->log( 'Matomo site is known for blog (' . $idsite . ')... will update' );
|
152 |
|
153 |
+
$sites_manager_model = new Model();
|
154 |
+
$site = $sites_manager_model->getSiteFromId( $idsite );
|
155 |
+
if ( ! empty( $site ) ) {
|
156 |
+
// if site doesn't exist for some reason then we have to create it
|
157 |
+
if ( $site['name'] !== $blog_name
|
158 |
+
|| $site['main_url'] !== $blog_url
|
159 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
160 |
+
|| $site['ecommerce'] != $track_ecommerce
|
161 |
+
|| $site['currency'] !== $site_currency
|
162 |
+
|| $site['timezone'] !== $detected_timezone ) {
|
163 |
+
|
164 |
+
/** @var WP_Site $site */
|
165 |
+
$params = [
|
166 |
+
'name' => $blog_name,
|
167 |
+
'main_url' => $blog_url,
|
168 |
+
'ecommerce' => $track_ecommerce,
|
169 |
+
'currency' => $site_currency,
|
170 |
+
'timezone' => $detected_timezone,
|
171 |
+
];
|
172 |
+
$sites_manager_model->updateSite( $params, $idsite );
|
173 |
+
|
174 |
+
do_action( 'matomo_site_synced', $idsite, $blog_id );
|
175 |
+
|
176 |
+
// no actual setting changed but we make sure the tracking code will be updated after an update
|
177 |
+
$this->settings->apply_tracking_related_changes( [] );
|
178 |
+
}
|
179 |
+
|
180 |
+
$this->config_sync->sync_config_for_current_site();
|
181 |
+
|
182 |
+
return true;
|
183 |
+
}
|
184 |
}
|
185 |
|
186 |
$this->logger->log( 'Matomo site is not known for blog... will create site' );
|
187 |
|
188 |
+
/** @var WP_Site $site */
|
189 |
+
$idsite = null;
|
190 |
|
191 |
$this->set_enable_sites_admin( 1 );
|
192 |
|
193 |
Access::doAsSuperUser(
|
194 |
function () use ( $blog_name, $blog_url, $detected_timezone, $track_ecommerce, &$idsite, $site_currency ) {
|
195 |
+
SitesManager\API::unsetInstance();
|
196 |
+
// we need to unset the instance to make sure it fetches the
|
197 |
+
// up to date dependencies eg current plugin manager etc
|
198 |
+
|
199 |
+
$idsite = SitesManager\API::getInstance()->addSite(
|
200 |
+
$blog_name,
|
201 |
+
[ $blog_url ],
|
202 |
+
$track_ecommerce,
|
203 |
+
$site_search = null,
|
204 |
+
$search_keyword_parameters = null,
|
205 |
+
$search_category_parameters = null,
|
206 |
+
$excluded_ips = null,
|
207 |
+
$excluded_query_parameters = null,
|
208 |
+
$detected_timezone,
|
209 |
+
$site_currency
|
210 |
+
);
|
211 |
}
|
212 |
);
|
213 |
$this->set_enable_sites_admin( 0 );
|
222 |
|
223 |
Site::map_matomo_site_id( $blog_id, $idsite );
|
224 |
|
225 |
+
$this->config_sync->sync_config_for_current_site();
|
226 |
|
227 |
do_action( 'matomo_site_synced', $idsite, $blog_id );
|
228 |
|
256 |
return $timezone;
|
257 |
}
|
258 |
|
259 |
+
$dst = (bool) gmdate( 'I' );
|
260 |
foreach ( timezone_abbreviations_list() as $abbr ) {
|
261 |
foreach ( $abbr as $city ) {
|
262 |
if ( $dst === (bool) $city['dst']
|
284 |
|
285 |
private function check_and_try_to_set_default_timezone( $timezone ) {
|
286 |
try {
|
287 |
+
Access::doAsSuperUser(
|
288 |
+
function () use ( $timezone ) {
|
289 |
+
// make sure we're loading the latest instance with all up to date dependencies... mainly needed for tests
|
290 |
+
SitesManager\API::unsetInstance();
|
291 |
+
SitesManager\API::getInstance()->setDefaultTimezone( $timezone );
|
292 |
+
}
|
293 |
+
);
|
294 |
+
} catch ( Exception $e ) {
|
295 |
return false;
|
296 |
}
|
297 |
|
298 |
return true;
|
299 |
}
|
|
|
300 |
}
|
classes/WpMatomo/Site/Sync/SyncConfig.php
CHANGED
@@ -10,121 +10,118 @@
|
|
10 |
namespace WpMatomo\Site\Sync;
|
11 |
|
12 |
use Piwik\Config as PiwikConfig;
|
|
|
13 |
use WpMatomo\Bootstrap;
|
14 |
use WpMatomo\Logger;
|
15 |
use WpMatomo\ScheduledTasks;
|
16 |
use WpMatomo\Settings;
|
17 |
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
-
|
20 |
}
|
21 |
|
22 |
-
class SyncConfig
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
$config->forceSave();
|
127 |
-
}
|
128 |
-
}
|
129 |
-
|
130 |
}
|
10 |
namespace WpMatomo\Site\Sync;
|
11 |
|
12 |
use Piwik\Config as PiwikConfig;
|
13 |
+
use WpMatomo;
|
14 |
use WpMatomo\Bootstrap;
|
15 |
use WpMatomo\Logger;
|
16 |
use WpMatomo\ScheduledTasks;
|
17 |
use WpMatomo\Settings;
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
+
exit; // if accessed directly
|
21 |
}
|
22 |
|
23 |
+
class SyncConfig {
|
24 |
+
|
25 |
+
/**
|
26 |
+
* @var Logger
|
27 |
+
*/
|
28 |
+
private $logger;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* @var Settings
|
32 |
+
*/
|
33 |
+
private $settings;
|
34 |
+
|
35 |
+
public function __construct( Settings $settings ) {
|
36 |
+
$this->logger = new Logger();
|
37 |
+
$this->settings = $settings;
|
38 |
+
}
|
39 |
+
|
40 |
+
public function sync_config_for_current_site() {
|
41 |
+
if ( $this->settings->is_network_enabled() ) {
|
42 |
+
$config = PiwikConfig::getInstance();
|
43 |
+
$has_change = false;
|
44 |
+
foreach ( $this->get_all() as $category => $keys ) {
|
45 |
+
$cat = $config->{$category};
|
46 |
+
if ( empty( $cat ) ) {
|
47 |
+
$cat = [];
|
48 |
+
}
|
49 |
+
|
50 |
+
if ( empty( $keys ) && ! empty( $cat ) ) {
|
51 |
+
// need to unset all values
|
52 |
+
$has_change = true;
|
53 |
+
$config->{$category} = [];
|
54 |
+
}
|
55 |
+
|
56 |
+
if ( ! empty( $keys ) ) {
|
57 |
+
foreach ( $keys as $key => $value ) {
|
58 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
59 |
+
if ( ! isset( $cat[ $key ] ) || $cat[ $key ] != $value ) {
|
60 |
+
$has_change = true;
|
61 |
+
$cat[ $key ] = $value;
|
62 |
+
$config->{$category} = $cat;
|
63 |
+
}
|
64 |
+
}
|
65 |
+
}
|
66 |
+
}
|
67 |
+
if ( $has_change ) {
|
68 |
+
$config->forceSave();
|
69 |
+
}
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
private function get_all() {
|
74 |
+
$options = $this->settings->get_global_option( Settings::NETWORK_CONFIG_OPTIONS );
|
75 |
+
|
76 |
+
if ( empty( $options ) || ! is_array( $options ) ) {
|
77 |
+
$options = [];
|
78 |
+
}
|
79 |
+
|
80 |
+
return $options;
|
81 |
+
}
|
82 |
+
|
83 |
+
public function get_config_value( $group, $key ) {
|
84 |
+
if ( $this->settings->is_network_enabled() ) {
|
85 |
+
$config = $this->get_all();
|
86 |
+
if ( isset( $config[ $group ][ $key ] ) ) {
|
87 |
+
return $config[ $group ][ $key ];
|
88 |
+
}
|
89 |
+
} else {
|
90 |
+
Bootstrap::do_bootstrap();
|
91 |
+
$config = PiwikConfig::getInstance();
|
92 |
+
$the_group = $config->{$group};
|
93 |
+
if ( ! empty( $the_group ) && isset( $the_group[ $key ] ) ) {
|
94 |
+
return $the_group[ $key ];
|
95 |
+
}
|
96 |
+
}
|
97 |
+
}
|
98 |
+
|
99 |
+
public function set_config_value( $group, $key, $value ) {
|
100 |
+
if ( $this->settings->is_network_enabled() ) {
|
101 |
+
$config = $this->get_all();
|
102 |
+
|
103 |
+
if ( ! isset( $config[ $group ] ) ) {
|
104 |
+
$config[ $group ] = [];
|
105 |
+
}
|
106 |
+
$config[ $group ][ $key ] = $value;
|
107 |
+
|
108 |
+
$this->settings->apply_changes(
|
109 |
+
[
|
110 |
+
Settings::NETWORK_CONFIG_OPTIONS => $config,
|
111 |
+
]
|
112 |
+
);
|
113 |
+
// need to update all config files
|
114 |
+
wp_schedule_single_event( time() + 5, ScheduledTasks::EVENT_SYNC );
|
115 |
+
} elseif ( ! WpMatomo::is_safe_mode() ) {
|
116 |
+
Bootstrap::do_bootstrap();
|
117 |
+
$config = PiwikConfig::getInstance();
|
118 |
+
$the_group = $config->{$group};
|
119 |
+
if ( empty( $the_group ) ) {
|
120 |
+
$the_group = [];
|
121 |
+
}
|
122 |
+
$the_group[ $key ] = $value;
|
123 |
+
$config->{$group} = $the_group;
|
124 |
+
$config->forceSave();
|
125 |
+
}
|
126 |
+
}
|
|
|
|
|
|
|
|
|
127 |
}
|
classes/WpMatomo/TrackingCode.php
CHANGED
@@ -13,9 +13,10 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
-
use
|
17 |
|
18 |
class TrackingCode {
|
|
|
19 |
/**
|
20 |
* @var Settings
|
21 |
*/
|
@@ -44,17 +45,17 @@ class TrackingCode {
|
|
44 |
public function register_hooks() {
|
45 |
if ( $this->settings->is_tracking_enabled() ) {
|
46 |
if ( $this->settings->is_track_feed() ) {
|
47 |
-
add_filter( 'the_excerpt_rss',
|
48 |
-
add_filter( 'the_content',
|
49 |
}
|
50 |
if ( $this->settings->is_add_feed_campaign() ) {
|
51 |
-
add_filter( 'post_link',
|
52 |
}
|
53 |
if ( $this->settings->is_cross_domain_linking_enabled() ) {
|
54 |
-
add_filter( 'wp_redirect',
|
55 |
}
|
56 |
|
57 |
-
$is_admin = is_admin() || !empty($GLOBALS['MATOMO_LOADED_DIRECTLY']);
|
58 |
|
59 |
if ( ! $is_admin || $this->settings->is_admin_tracking_enabled() ) {
|
60 |
$prefix = 'wp';
|
@@ -67,10 +68,10 @@ class TrackingCode {
|
|
67 |
$position = $prefix . '_footer';
|
68 |
}
|
69 |
|
70 |
-
add_action( $position,
|
71 |
|
72 |
if ( $this->settings->is_add_no_script_code() ) {
|
73 |
-
add_action( $prefix . '_footer',
|
74 |
}
|
75 |
}
|
76 |
}
|
@@ -106,11 +107,13 @@ class TrackingCode {
|
|
106 |
if ( $site_id ) {
|
107 |
$tracking_code = str_replace( '{MATOMO_API_ENDPOINT}', wp_json_encode( $this->generator->get_tracker_endpoint() ), $tracking_code );
|
108 |
$tracking_code = str_replace( '{MATOMO_JS_ENDPOINT}', wp_json_encode( $this->generator->get_js_endpoint() ), $tracking_code );
|
|
|
109 |
echo str_replace( '{MATOMO_IDSITE}', $site_id, $tracking_code );
|
110 |
} else {
|
111 |
echo '<!-- Site not yet synced with Matomo, tracking code will be added later -->';
|
112 |
}
|
113 |
} else {
|
|
|
114 |
echo $tracking_code;
|
115 |
}
|
116 |
}
|
@@ -129,12 +132,13 @@ class TrackingCode {
|
|
129 |
|
130 |
if ( ! empty( $code ) ) {
|
131 |
$this->logger->log( 'Add noscript code. Blog ID: ' . get_current_blog_id(), Logger::LEVEL_DEBUG );
|
132 |
-
$contains_noscript_tag = stripos($code, '<noscript') !== false;
|
133 |
-
if (
|
134 |
echo '<noscript>';
|
135 |
}
|
|
|
136 |
echo $code;
|
137 |
-
if (
|
138 |
echo '</noscript>';
|
139 |
}
|
140 |
echo "\n";
|
@@ -153,7 +157,7 @@ class TrackingCode {
|
|
153 |
*/
|
154 |
public function add_feed_campaign( $permalink ) {
|
155 |
global $post;
|
156 |
-
if ( is_feed() && !empty($post) ) {
|
157 |
$this->logger->log( 'Add campaign to feed permalink.' );
|
158 |
$sep = ( strpos( $permalink, '?' ) === false ? '?' : '&' );
|
159 |
$permalink .= $sep . 'pk_campaign=' . rawurlencode( $this->settings->get_global_option( 'track_feed_campaign' ) ) . '&pk_kwd=' . rawurlencode( $post->post_name );
|
@@ -203,10 +207,12 @@ class TrackingCode {
|
|
203 |
* @return string location extended by pk_vid URL parameter if the URL parameter is set
|
204 |
*/
|
205 |
public function forward_cross_domain_visitor_id( $location ) {
|
206 |
-
if ( ! empty( $_GET['pk_vid'] )
|
207 |
-
|
208 |
-
|
209 |
-
|
|
|
|
|
210 |
}
|
211 |
|
212 |
return $location;
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
+
use WpMatomo\TrackingCode\TrackingCodeGenerator;
|
17 |
|
18 |
class TrackingCode {
|
19 |
+
|
20 |
/**
|
21 |
* @var Settings
|
22 |
*/
|
45 |
public function register_hooks() {
|
46 |
if ( $this->settings->is_tracking_enabled() ) {
|
47 |
if ( $this->settings->is_track_feed() ) {
|
48 |
+
add_filter( 'the_excerpt_rss', [ $this, 'add_feed_tracking' ] );
|
49 |
+
add_filter( 'the_content', [ $this, 'add_feed_tracking' ] );
|
50 |
}
|
51 |
if ( $this->settings->is_add_feed_campaign() ) {
|
52 |
+
add_filter( 'post_link', [ $this, 'add_feed_campaign' ] );
|
53 |
}
|
54 |
if ( $this->settings->is_cross_domain_linking_enabled() ) {
|
55 |
+
add_filter( 'wp_redirect', [ $this, 'forward_cross_domain_visitor_id' ] );
|
56 |
}
|
57 |
|
58 |
+
$is_admin = is_admin() || ! empty( $GLOBALS['MATOMO_LOADED_DIRECTLY'] );
|
59 |
|
60 |
if ( ! $is_admin || $this->settings->is_admin_tracking_enabled() ) {
|
61 |
$prefix = 'wp';
|
68 |
$position = $prefix . '_footer';
|
69 |
}
|
70 |
|
71 |
+
add_action( $position, [ $this, 'add_javascript_code' ] );
|
72 |
|
73 |
if ( $this->settings->is_add_no_script_code() ) {
|
74 |
+
add_action( $prefix . '_footer', [ $this, 'add_noscript_code' ] );
|
75 |
}
|
76 |
}
|
77 |
}
|
107 |
if ( $site_id ) {
|
108 |
$tracking_code = str_replace( '{MATOMO_API_ENDPOINT}', wp_json_encode( $this->generator->get_tracker_endpoint() ), $tracking_code );
|
109 |
$tracking_code = str_replace( '{MATOMO_JS_ENDPOINT}', wp_json_encode( $this->generator->get_js_endpoint() ), $tracking_code );
|
110 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
111 |
echo str_replace( '{MATOMO_IDSITE}', $site_id, $tracking_code );
|
112 |
} else {
|
113 |
echo '<!-- Site not yet synced with Matomo, tracking code will be added later -->';
|
114 |
}
|
115 |
} else {
|
116 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
117 |
echo $tracking_code;
|
118 |
}
|
119 |
}
|
132 |
|
133 |
if ( ! empty( $code ) ) {
|
134 |
$this->logger->log( 'Add noscript code. Blog ID: ' . get_current_blog_id(), Logger::LEVEL_DEBUG );
|
135 |
+
$contains_noscript_tag = stripos( $code, '<noscript' ) !== false;
|
136 |
+
if ( ! $contains_noscript_tag ) {
|
137 |
echo '<noscript>';
|
138 |
}
|
139 |
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
140 |
echo $code;
|
141 |
+
if ( ! $contains_noscript_tag ) {
|
142 |
echo '</noscript>';
|
143 |
}
|
144 |
echo "\n";
|
157 |
*/
|
158 |
public function add_feed_campaign( $permalink ) {
|
159 |
global $post;
|
160 |
+
if ( is_feed() && ! empty( $post ) ) {
|
161 |
$this->logger->log( 'Add campaign to feed permalink.' );
|
162 |
$sep = ( strpos( $permalink, '?' ) === false ? '?' : '&' );
|
163 |
$permalink .= $sep . 'pk_campaign=' . rawurlencode( $this->settings->get_global_option( 'track_feed_campaign' ) ) . '&pk_kwd=' . rawurlencode( $post->post_name );
|
207 |
* @return string location extended by pk_vid URL parameter if the URL parameter is set
|
208 |
*/
|
209 |
public function forward_cross_domain_visitor_id( $location ) {
|
210 |
+
if ( ! empty( $_GET['pk_vid'] ) ) {
|
211 |
+
$pk_vid = sanitize_text_field( wp_unslash( $_GET['pk_vid'] ) );
|
212 |
+
if ( preg_match( '/^[a-zA-Z0-9]{24,60}$/', $pk_vid ) ) {
|
213 |
+
// currently, the pk_vid parameter is 32 characters long, but it may vary over time.
|
214 |
+
$location = add_query_arg( 'pk_vid', $pk_vid, $location );
|
215 |
+
}
|
216 |
}
|
217 |
|
218 |
return $location;
|
classes/WpMatomo/TrackingCode/TrackingCodeGenerator.php
CHANGED
@@ -9,19 +9,21 @@
|
|
9 |
|
10 |
namespace WpMatomo\TrackingCode;
|
11 |
|
12 |
-
use
|
13 |
use WpMatomo\Admin\CookieConsent;
|
|
|
14 |
use WpMatomo\Logger;
|
15 |
use WpMatomo\Paths;
|
16 |
use WpMatomo\Settings;
|
17 |
use WpMatomo\Site;
|
|
|
|
|
18 |
|
19 |
if ( ! defined( 'ABSPATH' ) ) {
|
20 |
exit; // if accessed directly
|
21 |
}
|
22 |
|
23 |
class TrackingCodeGenerator {
|
24 |
-
|
25 |
const TRACKPAGEVIEW = "_paq.push(['trackPageView']);";
|
26 |
const MTM_INIT = 'var _mtm = _mtm || [];';
|
27 |
|
@@ -44,8 +46,8 @@ class TrackingCodeGenerator {
|
|
44 |
}
|
45 |
|
46 |
public function register_hooks() {
|
47 |
-
add_action( 'matomo_site_synced',
|
48 |
-
add_action( 'matomo_tracking_settings_changed',
|
49 |
}
|
50 |
|
51 |
public function update_tracking_code() {
|
@@ -57,7 +59,7 @@ class TrackingCodeGenerator {
|
|
57 |
$track_mode = $this->settings->get_global_option( 'track_mode' );
|
58 |
|
59 |
if ( ! $this->settings->is_tracking_enabled()
|
60 |
-
||
|
61 |
return false;
|
62 |
}
|
63 |
|
@@ -79,10 +81,10 @@ class TrackingCodeGenerator {
|
|
79 |
} elseif ( TrackingSettings::TRACK_MODE_TAGMANAGER === $track_mode && matomo_has_tag_manager() ) {
|
80 |
$result = $this->prepare_tagmanger_code( $this->settings, $this->logger );
|
81 |
} else {
|
82 |
-
$result =
|
83 |
'script' => '<!-- Matomo: no supported track_mode selected -->',
|
84 |
'noscript' => '',
|
85 |
-
|
86 |
}
|
87 |
|
88 |
if ( ! empty( $result['script'] ) ) {
|
@@ -141,14 +143,14 @@ class TrackingCodeGenerator {
|
|
141 |
if ( $enabled
|
142 |
&& ctype_alnum( $container_id )
|
143 |
&& strlen( $container_id ) <= 16 ) {
|
144 |
-
$container_url = $upload_url . '/container_' .
|
145 |
|
146 |
$data_cf_async = '';
|
147 |
if ( $settings->get_global_option( 'track_datacfasync' ) ) {
|
148 |
$data_cf_async = 'data-cfasync="false"';
|
149 |
}
|
150 |
|
151 |
-
if ( $settings->get_global_option( 'force_protocol' )
|
152 |
$container_url = preg_replace( '(^http://)', 'https://', $container_url );
|
153 |
}
|
154 |
|
@@ -165,10 +167,10 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
165 |
|
166 |
$code .= '<!-- End Matomo Tag Manager -->';
|
167 |
|
168 |
-
return
|
169 |
'script' => $code,
|
170 |
'noscript' => '',
|
171 |
-
|
172 |
}
|
173 |
|
174 |
public function get_tracker_endpoint() {
|
@@ -190,11 +192,11 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
190 |
}
|
191 |
|
192 |
public function get_js_endpoint() {
|
193 |
-
|
194 |
if ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'restapi' ) {
|
195 |
$js_endpoint = $paths->get_js_tracker_rest_api_endpoint();
|
196 |
} elseif ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'plugin' ) {
|
197 |
-
$js_endpoint = plugins_url( 'app/matomo.js', MATOMO_ANALYTICS_FILE )
|
198 |
} else {
|
199 |
$js_endpoint = $paths->get_js_tracker_url_in_matomo_dir();
|
200 |
}
|
@@ -214,14 +216,14 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
214 |
* @return array
|
215 |
*/
|
216 |
public function prepare_tracking_code( $idsite ) {
|
217 |
-
$
|
218 |
|
219 |
-
$this->logger->log( 'Apply tracking code changes:', $
|
220 |
|
221 |
$tracker_endpoint = $this->get_tracker_endpoint();
|
222 |
$js_endpoint = $this->get_js_endpoint();
|
223 |
|
224 |
-
$options =
|
225 |
|
226 |
if ( $this->settings->get_global_option( 'set_download_extensions' ) ) {
|
227 |
$options[] = "_paq.push(['setDownloadExtensions', " . wp_json_encode( $this->settings->get_global_option( 'set_download_extensions' ) ) . ']);';
|
@@ -254,11 +256,11 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
254 |
|
255 |
if ( $track_across_alias ) {
|
256 |
// todo detect more hosts such as when using WPML etc
|
257 |
-
$hosts =
|
258 |
$hosts = array_filter( $hosts );
|
259 |
$hosts = array_map(
|
260 |
function ( $host ) {
|
261 |
-
|
262 |
},
|
263 |
$hosts
|
264 |
);
|
@@ -270,8 +272,8 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
270 |
$options[] = "_paq.push(['setRequestMethod', 'POST']);";
|
271 |
}
|
272 |
|
273 |
-
$
|
274 |
-
$cookie_consent_option = $
|
275 |
// for unit test cases
|
276 |
if ( ! empty( $cookie_consent_option ) ) {
|
277 |
$options[] = $cookie_consent_option;
|
@@ -291,14 +293,14 @@ g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.pare
|
|
291 |
$options[] = "_paq.push(['enableHeartBeatTimer', " . intval( $this->settings->get_global_option( 'track_heartbeat' ) ) . ']);';
|
292 |
}
|
293 |
|
294 |
-
$data_cf_async
|
295 |
$data_of_async_option = [];
|
296 |
if ( $this->settings->get_global_option( 'track_datacfasync' ) ) {
|
297 |
-
$data_cf_async
|
298 |
-
$data_of_async_option['data-cfasync'] =
|
299 |
}
|
300 |
|
301 |
-
$script
|
302 |
$script .= implode( "\n", $options );
|
303 |
$script .= self::TRACKPAGEVIEW;
|
304 |
$script .= "_paq.push(['enableLinkTracking']);_paq.push(['alwaysUseSendBeacon']);";
|
@@ -320,20 +322,20 @@ g.type='text/javascript'; g.async=true; g.src=" . wp_json_encode( $js_endpoint )
|
|
320 |
$script = '<script ' . $data_cf_async . ">\n" . $script . "\n</script>\n";
|
321 |
}
|
322 |
|
323 |
-
$script = '<!-- Matomo -->'
|
324 |
|
325 |
$no_script = '<noscript><p><img referrerpolicy="no-referrer-when-downgrade" src="' . esc_url( $tracker_endpoint ) . '?idsite=' . intval( $idsite ) . '&rec=1" style="border:0;" alt="" /></p></noscript>';
|
326 |
|
327 |
$script = apply_filters( 'matomo_tracking_code_script', $script, $idsite );
|
328 |
$script = apply_filters( 'matomo_tracking_code_noscript', $script, $idsite );
|
329 |
|
330 |
-
$this->logger->log( 'Finished tracking code: ' . $script, $
|
331 |
-
$this->logger->log( 'Finished noscript code: ' . $no_script, $
|
332 |
|
333 |
-
return
|
334 |
'script' => $script,
|
335 |
'noscript' => $no_script,
|
336 |
-
|
337 |
}
|
338 |
|
339 |
private function apply_404_changes( $tracking_code ) {
|
@@ -348,7 +350,7 @@ g.type='text/javascript'; g.async=true; g.src=" . wp_json_encode( $js_endpoint )
|
|
348 |
|
349 |
private function apply_search_changes( $tracking_code ) {
|
350 |
$this->logger->log( 'Apply search tracking changes. Blog ID: ' . get_current_blog_id() );
|
351 |
-
$obj_search = new
|
352 |
$int_result_count = $obj_search->post_count;
|
353 |
|
354 |
$code = "window._paq = window._paq || []; window._paq.push(['trackSiteSearch','" . get_search_query() . "', false, " . $int_result_count . "]);\n";
|
@@ -360,7 +362,7 @@ g.type='text/javascript'; g.async=true; g.src=" . wp_json_encode( $js_endpoint )
|
|
360 |
|
361 |
private function apply_user_tracking( $tracking_code ) {
|
362 |
$user_id_to_track = null;
|
363 |
-
if (
|
364 |
// Get the User ID Admin option, and the current user's data
|
365 |
$uid_from = $this->settings->get_global_option( 'track_user_id' );
|
366 |
$current_user = wp_get_current_user(); // current user
|
@@ -385,5 +387,4 @@ g.type='text/javascript'; g.async=true; g.src=" . wp_json_encode( $js_endpoint )
|
|
385 |
|
386 |
return $tracking_code;
|
387 |
}
|
388 |
-
|
389 |
}
|
9 |
|
10 |
namespace WpMatomo\TrackingCode;
|
11 |
|
12 |
+
use WP_Query;
|
13 |
use WpMatomo\Admin\CookieConsent;
|
14 |
+
use WpMatomo\Admin\TrackingSettings;
|
15 |
use WpMatomo\Logger;
|
16 |
use WpMatomo\Paths;
|
17 |
use WpMatomo\Settings;
|
18 |
use WpMatomo\Site;
|
19 |
+
// phpcs:ignore PHPCompatibility.UseDeclarations.NewUseConstFunction.Found
|
20 |
+
use function is_user_logged_in;
|
21 |
|
22 |
if ( ! defined( 'ABSPATH' ) ) {
|
23 |
exit; // if accessed directly
|
24 |
}
|
25 |
|
26 |
class TrackingCodeGenerator {
|
|
|
27 |
const TRACKPAGEVIEW = "_paq.push(['trackPageView']);";
|
28 |
const MTM_INIT = 'var _mtm = _mtm || [];';
|
29 |
|
46 |
}
|
47 |
|
48 |
public function register_hooks() {
|
49 |
+
add_action( 'matomo_site_synced', [ $this, 'update_tracking_code' ], $prio = 10, $args = 0 );
|
50 |
+
add_action( 'matomo_tracking_settings_changed', [ $this, 'update_tracking_code' ], $prio = 10, $args = 0 );
|
51 |
}
|
52 |
|
53 |
public function update_tracking_code() {
|
59 |
$track_mode = $this->settings->get_global_option( 'track_mode' );
|
60 |
|
61 |
if ( ! $this->settings->is_tracking_enabled()
|
62 |
+
|| TrackingSettings::TRACK_MODE_MANUALLY === $track_mode ) {
|
63 |
return false;
|
64 |
}
|
65 |
|
81 |
} elseif ( TrackingSettings::TRACK_MODE_TAGMANAGER === $track_mode && matomo_has_tag_manager() ) {
|
82 |
$result = $this->prepare_tagmanger_code( $this->settings, $this->logger );
|
83 |
} else {
|
84 |
+
$result = [
|
85 |
'script' => '<!-- Matomo: no supported track_mode selected -->',
|
86 |
'noscript' => '',
|
87 |
+
];
|
88 |
}
|
89 |
|
90 |
if ( ! empty( $result['script'] ) ) {
|
143 |
if ( $enabled
|
144 |
&& ctype_alnum( $container_id )
|
145 |
&& strlen( $container_id ) <= 16 ) {
|
146 |
+
$container_url = $upload_url . '/container_' . rawurlencode( $container_id ) . '.js';
|
147 |
|
148 |
$data_cf_async = '';
|
149 |
if ( $settings->get_global_option( 'track_datacfasync' ) ) {
|
150 |
$data_cf_async = 'data-cfasync="false"';
|
151 |
}
|
152 |
|
153 |
+
if ( $settings->get_global_option( 'force_protocol' ) === 'https' ) {
|
154 |
$container_url = preg_replace( '(^http://)', 'https://', $container_url );
|
155 |
}
|
156 |
|
167 |
|
168 |
$code .= '<!-- End Matomo Tag Manager -->';
|
169 |
|
170 |
+
return [
|
171 |
'script' => $code,
|
172 |
'noscript' => '',
|
173 |
+
];
|
174 |
}
|
175 |
|
176 |
public function get_tracker_endpoint() {
|
192 |
}
|
193 |
|
194 |
public function get_js_endpoint() {
|
195 |
+
$paths = new Paths();
|
196 |
if ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'restapi' ) {
|
197 |
$js_endpoint = $paths->get_js_tracker_rest_api_endpoint();
|
198 |
} elseif ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'plugin' ) {
|
199 |
+
$js_endpoint = plugins_url( 'app/matomo.js', MATOMO_ANALYTICS_FILE );
|
200 |
} else {
|
201 |
$js_endpoint = $paths->get_js_tracker_url_in_matomo_dir();
|
202 |
}
|
216 |
* @return array
|
217 |
*/
|
218 |
public function prepare_tracking_code( $idsite ) {
|
219 |
+
$log_level = is_admin() ? Logger::LEVEL_DEBUG : Logger::LEVEL_INFO;
|
220 |
|
221 |
+
$this->logger->log( 'Apply tracking code changes:', $log_level );
|
222 |
|
223 |
$tracker_endpoint = $this->get_tracker_endpoint();
|
224 |
$js_endpoint = $this->get_js_endpoint();
|
225 |
|
226 |
+
$options = [];
|
227 |
|
228 |
if ( $this->settings->get_global_option( 'set_download_extensions' ) ) {
|
229 |
$options[] = "_paq.push(['setDownloadExtensions', " . wp_json_encode( $this->settings->get_global_option( 'set_download_extensions' ) ) . ']);';
|
256 |
|
257 |
if ( $track_across_alias ) {
|
258 |
// todo detect more hosts such as when using WPML etc
|
259 |
+
$hosts = [ wp_parse_url( home_url(), PHP_URL_HOST ) ];
|
260 |
$hosts = array_filter( $hosts );
|
261 |
$hosts = array_map(
|
262 |
function ( $host ) {
|
263 |
+
return '*.' . $host;
|
264 |
},
|
265 |
$hosts
|
266 |
);
|
272 |
$options[] = "_paq.push(['setRequestMethod', 'POST']);";
|
273 |
}
|
274 |
|
275 |
+
$cookie_consent = new CookieConsent();
|
276 |
+
$cookie_consent_option = $cookie_consent->get_tracking_consent_option( $this->settings->get_global_option( 'cookie_consent' ) );
|
277 |
// for unit test cases
|
278 |
if ( ! empty( $cookie_consent_option ) ) {
|
279 |
$options[] = $cookie_consent_option;
|
293 |
$options[] = "_paq.push(['enableHeartBeatTimer', " . intval( $this->settings->get_global_option( 'track_heartbeat' ) ) . ']);';
|
294 |
}
|
295 |
|
296 |
+
$data_cf_async = '';
|
297 |
$data_of_async_option = [];
|
298 |
if ( $this->settings->get_global_option( 'track_datacfasync' ) ) {
|
299 |
+
$data_cf_async = 'data-cfasync="false"';
|
300 |
+
$data_of_async_option['data-cfasync'] = 'false';
|
301 |
}
|
302 |
|
303 |
+
$script = "var _paq = window._paq = window._paq || [];\n";
|
304 |
$script .= implode( "\n", $options );
|
305 |
$script .= self::TRACKPAGEVIEW;
|
306 |
$script .= "_paq.push(['enableLinkTracking']);_paq.push(['alwaysUseSendBeacon']);";
|
322 |
$script = '<script ' . $data_cf_async . ">\n" . $script . "\n</script>\n";
|
323 |
}
|
324 |
|
325 |
+
$script = '<!-- Matomo -->' . $script . '<!-- End Matomo Code -->';
|
326 |
|
327 |
$no_script = '<noscript><p><img referrerpolicy="no-referrer-when-downgrade" src="' . esc_url( $tracker_endpoint ) . '?idsite=' . intval( $idsite ) . '&rec=1" style="border:0;" alt="" /></p></noscript>';
|
328 |
|
329 |
$script = apply_filters( 'matomo_tracking_code_script', $script, $idsite );
|
330 |
$script = apply_filters( 'matomo_tracking_code_noscript', $script, $idsite );
|
331 |
|
332 |
+
$this->logger->log( 'Finished tracking code: ' . $script, $log_level );
|
333 |
+
$this->logger->log( 'Finished noscript code: ' . $no_script, $log_level );
|
334 |
|
335 |
+
return [
|
336 |
'script' => $script,
|
337 |
'noscript' => $no_script,
|
338 |
+
];
|
339 |
}
|
340 |
|
341 |
private function apply_404_changes( $tracking_code ) {
|
350 |
|
351 |
private function apply_search_changes( $tracking_code ) {
|
352 |
$this->logger->log( 'Apply search tracking changes. Blog ID: ' . get_current_blog_id() );
|
353 |
+
$obj_search = new WP_Query( 's=' . get_search_query() . '&showposts=-1' );
|
354 |
$int_result_count = $obj_search->post_count;
|
355 |
|
356 |
$code = "window._paq = window._paq || []; window._paq.push(['trackSiteSearch','" . get_search_query() . "', false, " . $int_result_count . "]);\n";
|
362 |
|
363 |
private function apply_user_tracking( $tracking_code ) {
|
364 |
$user_id_to_track = null;
|
365 |
+
if ( is_user_logged_in() ) {
|
366 |
// Get the User ID Admin option, and the current user's data
|
367 |
$uid_from = $this->settings->get_global_option( 'track_user_id' );
|
368 |
$current_user = wp_get_current_user(); // current user
|
387 |
|
388 |
return $tracking_code;
|
389 |
}
|
|
|
390 |
}
|
classes/WpMatomo/Uninstaller.php
CHANGED
@@ -14,7 +14,15 @@ use WpMatomo\Admin\Dashboard;
|
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
class Uninstaller {
|
19 |
|
20 |
/**
|
@@ -135,7 +143,7 @@ class Uninstaller {
|
|
135 |
private function drop_tables() {
|
136 |
global $wpdb;
|
137 |
|
138 |
-
$db_settings
|
139 |
$installed_tables = $db_settings->get_installed_matomo_tables();
|
140 |
$this->logger->log( sprintf( 'Matomo will now drop %s matomo tables', count( $installed_tables ) ) );
|
141 |
|
@@ -143,6 +151,7 @@ class Uninstaller {
|
|
143 |
// temporary table are used in tests and just making sure they are being removed
|
144 |
// $wpdb->query( "DROP TEMPORARY TABLE IF EXISTS `$tableName`" );
|
145 |
// two spaces between drop and table so it won't be replaced in WP tests
|
|
|
146 |
$wpdb->query( "DROP TABLE IF EXISTS `$table_name`" );
|
147 |
}
|
148 |
}
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
17 |
+
/**
|
18 |
+
* We need to access db not cache
|
19 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
|
20 |
+
* phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
|
21 |
+
*
|
22 |
+
* Table names management
|
23 |
+
* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
24 |
+
* phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
|
25 |
+
*/
|
26 |
class Uninstaller {
|
27 |
|
28 |
/**
|
143 |
private function drop_tables() {
|
144 |
global $wpdb;
|
145 |
|
146 |
+
$db_settings = new \WpMatomo\Db\Settings();
|
147 |
$installed_tables = $db_settings->get_installed_matomo_tables();
|
148 |
$this->logger->log( sprintf( 'Matomo will now drop %s matomo tables', count( $installed_tables ) ) );
|
149 |
|
151 |
// temporary table are used in tests and just making sure they are being removed
|
152 |
// $wpdb->query( "DROP TEMPORARY TABLE IF EXISTS `$tableName`" );
|
153 |
// two spaces between drop and table so it won't be replaced in WP tests
|
154 |
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange
|
155 |
$wpdb->query( "DROP TABLE IF EXISTS `$table_name`" );
|
156 |
}
|
157 |
}
|
classes/WpMatomo/Updater.php
CHANGED
@@ -9,12 +9,15 @@
|
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
|
|
12 |
use Piwik\Cache as PiwikCache;
|
13 |
use Piwik\Filesystem;
|
14 |
use Piwik\Option;
|
15 |
use Piwik\Plugins\Installation\ServerFilesGenerator;
|
16 |
use Piwik\SettingsServer;
|
17 |
use Piwik\Version;
|
|
|
|
|
18 |
use WpMatomo\Updater\UpdateInProgressException;
|
19 |
|
20 |
if ( ! defined( 'ABSPATH' ) ) {
|
@@ -47,13 +50,12 @@ class Updater {
|
|
47 |
return function_exists( 'get_plugin_data' );
|
48 |
}
|
49 |
|
50 |
-
public function get_plugins_requiring_update()
|
51 |
-
{
|
52 |
if ( ! $this->load_plugin_functions() ) {
|
53 |
return [];
|
54 |
}
|
55 |
|
56 |
-
$keys
|
57 |
$plugin_files = $GLOBALS['MATOMO_PLUGIN_FILES'];
|
58 |
if ( ! in_array( MATOMO_ANALYTICS_FILE, $plugin_files, true ) ) {
|
59 |
$plugin_files[] = MATOMO_ANALYTICS_FILE;
|
@@ -70,7 +72,7 @@ class Updater {
|
|
70 |
if ( ! Installer::is_intalled() ) {
|
71 |
return [];
|
72 |
}
|
73 |
-
$keys[$key] = $plugin_data['Version'];
|
74 |
}
|
75 |
}
|
76 |
|
@@ -78,16 +80,17 @@ class Updater {
|
|
78 |
}
|
79 |
|
80 |
public function update_if_needed() {
|
81 |
-
$executed_updates =
|
82 |
|
83 |
$plugins_requiring_update = $this->get_plugins_requiring_update();
|
84 |
-
foreach ($plugins_requiring_update as $key => $plugin_version) {
|
85 |
try {
|
86 |
$this->update();
|
87 |
} catch ( UpdateInProgressException $e ) {
|
88 |
-
$this->logger->log( 'Matomo update is already in progress');
|
|
|
89 |
return; // we also don't execute any further update as they should be executed in another process
|
90 |
-
}catch (
|
91 |
$this->logger->log_exception( 'plugin_update', $e );
|
92 |
continue;
|
93 |
}
|
@@ -117,11 +120,11 @@ class Updater {
|
|
117 |
|
118 |
$history = $this->settings->get_global_option( 'version_history' );
|
119 |
if ( empty( $history ) || ! is_array( $history ) ) {
|
120 |
-
$history =
|
121 |
}
|
122 |
|
123 |
if ( ! empty( $plugin_data['Version'] )
|
124 |
-
|
125 |
// this allows us to see which versions of matomo the user was using before this update so we better understand
|
126 |
// which version maybe regressed something
|
127 |
array_unshift( $history, $plugin_data['Version'] );
|
@@ -147,11 +150,13 @@ class Updater {
|
|
147 |
);
|
148 |
|
149 |
$upload_dir = $paths->get_upload_base_dir();
|
|
|
|
|
150 |
if ( is_dir( $upload_dir ) && is_writable( $upload_dir ) ) {
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
$upload_dir . '/.htaccess',
|
156 |
'<Files ~ "(\.mmdb)$">
|
157 |
' . ServerFilesGenerator::getDenyHtaccessContent() . '
|
@@ -163,12 +168,12 @@ class Updater {
|
|
163 |
}
|
164 |
$config_dir = $paths->get_config_ini_path();
|
165 |
if ( is_dir( $config_dir ) && is_writable( $config_dir ) ) {
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
}
|
170 |
|
171 |
-
if ($this->settings->should_disable_addhandler()) {
|
172 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_DISABLE_ADDHANDLER );
|
173 |
}
|
174 |
}
|
@@ -178,9 +183,10 @@ class Updater {
|
|
178 |
return 'no upgrader';
|
179 |
}
|
180 |
|
181 |
-
if (self::lock()) {
|
182 |
// we can get the lock meaning no update is in progress
|
183 |
self::unlock();
|
|
|
184 |
return false;
|
185 |
}
|
186 |
|
@@ -188,56 +194,57 @@ class Updater {
|
|
188 |
}
|
189 |
|
190 |
private static function load_upgrader() {
|
191 |
-
if (!class_exists('\WP_Upgrader', false)) {
|
|
|
192 |
@include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
193 |
}
|
194 |
-
|
|
|
195 |
}
|
196 |
|
197 |
-
public static function lock()
|
198 |
-
{
|
199 |
// prevent the upgrade from being started several times at once
|
200 |
// we lock for 4 minutes. In case of major Matomo upgrades the upgrade may take much longer but it should be
|
201 |
// safe in this case to run the upgrade several times
|
202 |
// important: we always need to use the same timeout otherwise if something did use `create_lock(2)` then
|
203 |
// even though another job locked it for 4 minutes, the other job that locks it only for 2 seconds would release
|
204 |
// the lock basically since WP does not remember the initialy set release timeout
|
205 |
-
return self::load_upgrader() &&
|
206 |
}
|
207 |
|
208 |
-
public static function unlock()
|
209 |
-
|
210 |
-
return self::load_upgrader() && \WP_Upgrader::release_lock(self::LOCK_NAME);
|
211 |
}
|
212 |
|
213 |
private static function update_components() {
|
214 |
$updater = new \Piwik\Updater();
|
215 |
-
$components_with_update_file = $updater->getComponentUpdates(
|
216 |
|
217 |
if ( empty( $components_with_update_file ) ) {
|
218 |
return false;
|
219 |
}
|
220 |
|
221 |
-
if (!self::lock()) {
|
222 |
throw new UpdateInProgressException();
|
223 |
}
|
224 |
|
225 |
try {
|
226 |
-
SettingsServer::setMaxExecutionTime(0);
|
227 |
|
228 |
-
if (function_exists('ignore_user_abort')) {
|
229 |
-
|
|
|
230 |
}
|
231 |
|
232 |
$result = $updater->updateComponents( $components_with_update_file );
|
233 |
-
} catch (
|
234 |
self::unlock();
|
235 |
throw $e;
|
236 |
}
|
237 |
self::unlock();
|
238 |
|
239 |
-
if (!empty($result['errors'])) {
|
240 |
-
throw new
|
241 |
}
|
242 |
|
243 |
\Piwik\Updater::recordComponentSuccessfullyUpdated( 'core', Version::VERSION );
|
9 |
|
10 |
namespace WpMatomo;
|
11 |
|
12 |
+
use Exception;
|
13 |
use Piwik\Cache as PiwikCache;
|
14 |
use Piwik\Filesystem;
|
15 |
use Piwik\Option;
|
16 |
use Piwik\Plugins\Installation\ServerFilesGenerator;
|
17 |
use Piwik\SettingsServer;
|
18 |
use Piwik\Version;
|
19 |
+
use WP_Upgrader;
|
20 |
+
use WpMatomo\Paths;
|
21 |
use WpMatomo\Updater\UpdateInProgressException;
|
22 |
|
23 |
if ( ! defined( 'ABSPATH' ) ) {
|
50 |
return function_exists( 'get_plugin_data' );
|
51 |
}
|
52 |
|
53 |
+
public function get_plugins_requiring_update() {
|
|
|
54 |
if ( ! $this->load_plugin_functions() ) {
|
55 |
return [];
|
56 |
}
|
57 |
|
58 |
+
$keys = [];
|
59 |
$plugin_files = $GLOBALS['MATOMO_PLUGIN_FILES'];
|
60 |
if ( ! in_array( MATOMO_ANALYTICS_FILE, $plugin_files, true ) ) {
|
61 |
$plugin_files[] = MATOMO_ANALYTICS_FILE;
|
72 |
if ( ! Installer::is_intalled() ) {
|
73 |
return [];
|
74 |
}
|
75 |
+
$keys[ $key ] = $plugin_data['Version'];
|
76 |
}
|
77 |
}
|
78 |
|
80 |
}
|
81 |
|
82 |
public function update_if_needed() {
|
83 |
+
$executed_updates = [];
|
84 |
|
85 |
$plugins_requiring_update = $this->get_plugins_requiring_update();
|
86 |
+
foreach ( $plugins_requiring_update as $key => $plugin_version ) {
|
87 |
try {
|
88 |
$this->update();
|
89 |
} catch ( UpdateInProgressException $e ) {
|
90 |
+
$this->logger->log( 'Matomo update is already in progress' );
|
91 |
+
|
92 |
return; // we also don't execute any further update as they should be executed in another process
|
93 |
+
} catch ( Exception $e ) {
|
94 |
$this->logger->log_exception( 'plugin_update', $e );
|
95 |
continue;
|
96 |
}
|
120 |
|
121 |
$history = $this->settings->get_global_option( 'version_history' );
|
122 |
if ( empty( $history ) || ! is_array( $history ) ) {
|
123 |
+
$history = [];
|
124 |
}
|
125 |
|
126 |
if ( ! empty( $plugin_data['Version'] )
|
127 |
+
&& ! in_array( $plugin_data['Version'], $history, true ) ) {
|
128 |
// this allows us to see which versions of matomo the user was using before this update so we better understand
|
129 |
// which version maybe regressed something
|
130 |
array_unshift( $history, $plugin_data['Version'] );
|
150 |
);
|
151 |
|
152 |
$upload_dir = $paths->get_upload_base_dir();
|
153 |
+
|
154 |
+
$wp_filesystem = $paths->get_file_system();
|
155 |
if ( is_dir( $upload_dir ) && is_writable( $upload_dir ) ) {
|
156 |
+
$wp_filesystem->put_contents( $upload_dir . '/index.php', '//hello' );
|
157 |
+
$wp_filesystem->put_contents( $upload_dir . '/index.html', '//hello' );
|
158 |
+
$wp_filesystem->put_contents( $upload_dir . '/index.htm', '//hello' );
|
159 |
+
$wp_filesystem->put_contents(
|
160 |
$upload_dir . '/.htaccess',
|
161 |
'<Files ~ "(\.mmdb)$">
|
162 |
' . ServerFilesGenerator::getDenyHtaccessContent() . '
|
168 |
}
|
169 |
$config_dir = $paths->get_config_ini_path();
|
170 |
if ( is_dir( $config_dir ) && is_writable( $config_dir ) ) {
|
171 |
+
$wp_filesystem->put_contents( $config_dir . '/index.php', '//hello' );
|
172 |
+
$wp_filesystem->put_contents( $config_dir . '/index.html', '//hello' );
|
173 |
+
$wp_filesystem->put_contents( $config_dir . '/index.htm', '//hello' );
|
174 |
}
|
175 |
|
176 |
+
if ( $this->settings->should_disable_addhandler() ) {
|
177 |
wp_schedule_single_event( time() + 10, ScheduledTasks::EVENT_DISABLE_ADDHANDLER );
|
178 |
}
|
179 |
}
|
183 |
return 'no upgrader';
|
184 |
}
|
185 |
|
186 |
+
if ( self::lock() ) {
|
187 |
// we can get the lock meaning no update is in progress
|
188 |
self::unlock();
|
189 |
+
|
190 |
return false;
|
191 |
}
|
192 |
|
194 |
}
|
195 |
|
196 |
private static function load_upgrader() {
|
197 |
+
if ( ! class_exists( '\WP_Upgrader', false ) ) {
|
198 |
+
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
199 |
@include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
200 |
}
|
201 |
+
|
202 |
+
return class_exists( '\WP_Upgrader', false );
|
203 |
}
|
204 |
|
205 |
+
public static function lock() {
|
|
|
206 |
// prevent the upgrade from being started several times at once
|
207 |
// we lock for 4 minutes. In case of major Matomo upgrades the upgrade may take much longer but it should be
|
208 |
// safe in this case to run the upgrade several times
|
209 |
// important: we always need to use the same timeout otherwise if something did use `create_lock(2)` then
|
210 |
// even though another job locked it for 4 minutes, the other job that locks it only for 2 seconds would release
|
211 |
// the lock basically since WP does not remember the initialy set release timeout
|
212 |
+
return self::load_upgrader() && WP_Upgrader::create_lock( self::LOCK_NAME, 60 * 4 );
|
213 |
}
|
214 |
|
215 |
+
public static function unlock() {
|
216 |
+
return self::load_upgrader() && WP_Upgrader::release_lock( self::LOCK_NAME );
|
|
|
217 |
}
|
218 |
|
219 |
private static function update_components() {
|
220 |
$updater = new \Piwik\Updater();
|
221 |
+
$components_with_update_file = $updater->getComponentUpdates();
|
222 |
|
223 |
if ( empty( $components_with_update_file ) ) {
|
224 |
return false;
|
225 |
}
|
226 |
|
227 |
+
if ( ! self::lock() ) {
|
228 |
throw new UpdateInProgressException();
|
229 |
}
|
230 |
|
231 |
try {
|
232 |
+
SettingsServer::setMaxExecutionTime( 0 );
|
233 |
|
234 |
+
if ( function_exists( 'ignore_user_abort' ) ) {
|
235 |
+
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
236 |
+
@ignore_user_abort( true );
|
237 |
}
|
238 |
|
239 |
$result = $updater->updateComponents( $components_with_update_file );
|
240 |
+
} catch ( Exception $e ) {
|
241 |
self::unlock();
|
242 |
throw $e;
|
243 |
}
|
244 |
self::unlock();
|
245 |
|
246 |
+
if ( ! empty( $result['errors'] ) ) {
|
247 |
+
throw new Exception( 'Error while updating components: ' . implode( ', ', $result['errors'] ) );
|
248 |
}
|
249 |
|
250 |
\Piwik\Updater::recordComponentSuccessfullyUpdated( 'core', Version::VERSION );
|
classes/WpMatomo/Updater/UpdateInProgressException.php
CHANGED
@@ -9,13 +9,17 @@
|
|
9 |
|
10 |
namespace WpMatomo\Updater;
|
11 |
|
|
|
|
|
12 |
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
exit; // if accessed directly
|
14 |
}
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
19 |
parent::__construct( $message, $code, $previous );
|
20 |
}
|
21 |
}
|
9 |
|
10 |
namespace WpMatomo\Updater;
|
11 |
|
12 |
+
use Exception;
|
13 |
+
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit; // if accessed directly
|
16 |
}
|
17 |
|
18 |
+
/**
|
19 |
+
* phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
|
20 |
+
*/
|
21 |
+
class UpdateInProgressException extends Exception {
|
22 |
+
public function __construct( $message = 'Matomo upgrade is already in progress', $code = 0, $previous = null ) {
|
23 |
parent::__construct( $message, $code, $previous );
|
24 |
}
|
25 |
}
|
classes/WpMatomo/User.php
CHANGED
@@ -14,7 +14,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
14 |
}
|
15 |
|
16 |
class User {
|
17 |
-
|
18 |
const USER_MAPPING_PREFIX = 'matomo-user-login-';
|
19 |
|
20 |
/**
|
@@ -41,6 +40,4 @@ class User {
|
|
41 |
public function uninstall() {
|
42 |
Uninstaller::uninstall_options( self::USER_MAPPING_PREFIX );
|
43 |
}
|
44 |
-
|
45 |
-
|
46 |
}
|
14 |
}
|
15 |
|
16 |
class User {
|
|
|
17 |
const USER_MAPPING_PREFIX = 'matomo-user-login-';
|
18 |
|
19 |
/**
|
40 |
public function uninstall() {
|
41 |
Uninstaller::uninstall_options( self::USER_MAPPING_PREFIX );
|
42 |
}
|
|
|
|
|
43 |
}
|
classes/WpMatomo/User/Sync.php
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
|
10 |
namespace WpMatomo\User;
|
11 |
|
|
|
12 |
use Piwik\Access;
|
13 |
use Piwik\Access\Role\Admin;
|
14 |
use Piwik\Access\Role\View;
|
@@ -18,8 +19,9 @@ use Piwik\Common;
|
|
18 |
use Piwik\Date;
|
19 |
use Piwik\Plugin;
|
20 |
use Piwik\Plugins\LanguagesManager\API;
|
21 |
-
use Piwik\Plugins\UsersManager\Model;
|
22 |
use Piwik\Plugins\UsersManager;
|
|
|
|
|
23 |
use WpMatomo\Bootstrap;
|
24 |
use WpMatomo\Capabilities;
|
25 |
use WpMatomo\Logger;
|
@@ -32,6 +34,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
32 |
}
|
33 |
|
34 |
class Sync {
|
|
|
35 |
/**
|
36 |
* actually allowed is 100 characters...
|
37 |
* but we do -5 to have some room to append `wp_`.$login.XYZ if needed
|
@@ -48,21 +51,20 @@ class Sync {
|
|
48 |
}
|
49 |
|
50 |
public function register_hooks() {
|
51 |
-
add_action( 'add_user_role',
|
52 |
-
add_action( 'remove_user_role',
|
53 |
-
add_action( 'add_user_to_blog',
|
54 |
-
add_action( 'remove_user_from_blog',
|
55 |
-
add_action( 'user_register',
|
56 |
-
add_action( 'profile_update',
|
57 |
}
|
58 |
|
59 |
-
public function sync_maybe_background()
|
60 |
-
{
|
61 |
global $pagenow;
|
62 |
-
if ( is_admin() &&
|
63 |
// eg for profile update we don't want to sync directly see #365 as it could cause issues with other plugins
|
64 |
// if they eg alter `get_users` option
|
65 |
-
wp_schedule_single_event(time() + 5, ScheduledTasks::EVENT_SYNC);
|
66 |
} else {
|
67 |
$this->sync_current_users_1000();
|
68 |
}
|
@@ -77,10 +79,10 @@ class Sync {
|
|
77 |
|
78 |
try {
|
79 |
if ( $idsite ) {
|
80 |
-
$users = $this->get_users(
|
81 |
$this->sync_users( $users, $idsite );
|
82 |
}
|
83 |
-
} catch (
|
84 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
85 |
$this->logger->log_exception( 'user_sync ', $e );
|
86 |
}
|
@@ -92,51 +94,51 @@ class Sync {
|
|
92 |
}
|
93 |
}
|
94 |
|
95 |
-
private function get_users($options =
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
|
141 |
public function sync_current_users() {
|
142 |
$idsite = Site::get_matomo_site_id( get_current_blog_id() );
|
@@ -149,9 +151,10 @@ class Sync {
|
|
149 |
/**
|
150 |
* similar method to sync_current_users which synchronise on the fly only if we have less than 1000 users.
|
151 |
* Otherwise it will be done by a background task
|
152 |
-
*
|
153 |
-
* @see https://github.com/matomo-org/matomo-for-wordpress/issues/460
|
154 |
* @return void
|
|
|
|
|
155 |
*/
|
156 |
public function sync_current_users_1000() {
|
157 |
$idsite = Site::get_matomo_site_id( get_current_blog_id() );
|
@@ -162,11 +165,12 @@ class Sync {
|
|
162 |
}
|
163 |
}
|
164 |
}
|
|
|
165 |
/**
|
166 |
* Sync all users. Make sure to always pass all sites that exist within a given site... you cannot just sync an individual
|
167 |
* user... we would delete all other users
|
168 |
*
|
169 |
-
* @param
|
170 |
* @param $idsite
|
171 |
*/
|
172 |
protected function sync_users( $users, $idsite ) {
|
@@ -174,8 +178,8 @@ class Sync {
|
|
174 |
|
175 |
$this->logger->log( 'Matomo will now sync ' . count( $users ) . ' users' );
|
176 |
|
177 |
-
$super_users =
|
178 |
-
$logins_with_some_view_access =
|
179 |
$user_model = new Model();
|
180 |
|
181 |
// need to make sure we recreate new instance later with latest dependencies in case they changed
|
@@ -197,34 +201,34 @@ class Sync {
|
|
197 |
$logins_with_some_view_access[] = $matomo_login;
|
198 |
} elseif ( user_can( $user, Capabilities::KEY_ADMIN ) ) {
|
199 |
$matomo_login = $this->ensure_user_exists( $user );
|
200 |
-
$user_model->deleteUserAccess( $mapped_matomo_login,
|
201 |
-
$user_model->addUserAccess( $matomo_login, Admin::ID,
|
202 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
203 |
$logins_with_some_view_access[] = $matomo_login;
|
204 |
} elseif ( user_can( $user, Capabilities::KEY_WRITE ) ) {
|
205 |
$matomo_login = $this->ensure_user_exists( $user );
|
206 |
-
$user_model->deleteUserAccess( $mapped_matomo_login,
|
207 |
-
$user_model->addUserAccess( $matomo_login, Write::ID,
|
208 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
209 |
$logins_with_some_view_access[] = $matomo_login;
|
210 |
} elseif ( user_can( $user, Capabilities::KEY_VIEW ) ) {
|
211 |
$matomo_login = $this->ensure_user_exists( $user );
|
212 |
-
$user_model->deleteUserAccess( $mapped_matomo_login,
|
213 |
-
$user_model->addUserAccess( $matomo_login, View::ID,
|
214 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
215 |
$logins_with_some_view_access[] = $matomo_login;
|
216 |
-
} elseif ($mapped_matomo_login) {
|
217 |
-
$user_model->deleteUserAccess( $mapped_matomo_login,
|
218 |
}
|
219 |
|
220 |
if ( $matomo_login ) {
|
221 |
-
$locale
|
222 |
-
$locale_dash = Common::mb_strtolower(str_replace('_', '-', $locale));
|
223 |
-
$parts
|
224 |
-
if ($locale && in_array($locale_dash, ['zh-cn', 'zh-tw', 'pt-br', 'es-ar'], true)) {
|
225 |
-
$parts = [$locale_dash];
|
226 |
-
} elseif (!empty($locale) && is_string($locale)) {
|
227 |
-
$parts
|
228 |
}
|
229 |
|
230 |
if ( ! empty( $parts[0] ) ) {
|
@@ -237,8 +241,8 @@ class Sync {
|
|
237 |
}
|
238 |
}
|
239 |
}
|
240 |
-
|
241 |
-
if ($idsite
|
242 |
// only needed if the actual site is not the default site... makes sure when they click in Matomo
|
243 |
// UI on "Dashboard" that the correct site is being opened by default
|
244 |
// eg if the linked site is actually idSite=2.
|
@@ -254,10 +258,10 @@ class Sync {
|
|
254 |
UsersManager\API::PREFERENCE_DEFAULT_REPORT,
|
255 |
$idsite
|
256 |
);
|
257 |
-
|
|
|
258 |
// ignore any error for now
|
259 |
}
|
260 |
-
|
261 |
}
|
262 |
);
|
263 |
}
|
@@ -268,11 +272,10 @@ class Sync {
|
|
268 |
}
|
269 |
|
270 |
$logins_with_some_view_access = array_unique( $logins_with_some_view_access );
|
271 |
-
$all_users = $user_model->getUsers(
|
272 |
foreach ( $all_users as $all_user ) {
|
273 |
if ( ! in_array( $all_user['login'], $logins_with_some_view_access, true )
|
274 |
&& ! empty( $all_user['login'] ) ) {
|
275 |
-
|
276 |
Access::doAsSuperUser(
|
277 |
function () use ( $user_model, $all_user ) {
|
278 |
$user_model->deleteUserOnly( $all_user['login'] );
|
@@ -285,7 +288,7 @@ class Sync {
|
|
285 |
}
|
286 |
|
287 |
/**
|
288 |
-
* @param
|
289 |
*/
|
290 |
protected function ensure_user_exists( $wp_user ) {
|
291 |
$user_model = new Model();
|
@@ -299,7 +302,7 @@ class Sync {
|
|
299 |
$user_in_matomo = $user_model->getUser( $matomo_user_login );
|
300 |
} else {
|
301 |
// wp usernames may include whitespace etc
|
302 |
-
$login = preg_replace('/[^A-Za-zÄäÖöÜüß0-9_.@+-]+/D', '_', $login);
|
303 |
$login = substr( $login, 0, self::MAX_USER_NAME_LENGTH );
|
304 |
|
305 |
if ( ! $user_model->getUser( $login ) ) {
|
@@ -334,7 +337,7 @@ class Sync {
|
|
334 |
User::map_matomo_user_login( $user_id, $matomo_user_login );
|
335 |
} elseif ( $user_in_matomo['email'] !== $wp_user->user_email ) {
|
336 |
$this->logger->log( 'Matomo is now updating the email for wpUserID ' . $user_id . ' matomo login ' . $matomo_user_login );
|
337 |
-
$user_model->updateUserFields( $matomo_user_login,
|
338 |
}
|
339 |
|
340 |
return $matomo_user_login;
|
9 |
|
10 |
namespace WpMatomo\User;
|
11 |
|
12 |
+
use Exception;
|
13 |
use Piwik\Access;
|
14 |
use Piwik\Access\Role\Admin;
|
15 |
use Piwik\Access\Role\View;
|
19 |
use Piwik\Date;
|
20 |
use Piwik\Plugin;
|
21 |
use Piwik\Plugins\LanguagesManager\API;
|
|
|
22 |
use Piwik\Plugins\UsersManager;
|
23 |
+
use Piwik\Plugins\UsersManager\Model;
|
24 |
+
use WP_User;
|
25 |
use WpMatomo\Bootstrap;
|
26 |
use WpMatomo\Capabilities;
|
27 |
use WpMatomo\Logger;
|
34 |
}
|
35 |
|
36 |
class Sync {
|
37 |
+
|
38 |
/**
|
39 |
* actually allowed is 100 characters...
|
40 |
* but we do -5 to have some room to append `wp_`.$login.XYZ if needed
|
51 |
}
|
52 |
|
53 |
public function register_hooks() {
|
54 |
+
add_action( 'add_user_role', [ $this, 'sync_current_users_1000' ], $prio = 10, $args = 0 );
|
55 |
+
add_action( 'remove_user_role', [ $this, 'sync_current_users_1000' ], $prio = 10, $args = 0 );
|
56 |
+
add_action( 'add_user_to_blog', [ $this, 'sync_current_users_1000' ], $prio = 10, $args = 0 );
|
57 |
+
add_action( 'remove_user_from_blog', [ $this, 'sync_current_users_1000' ], $prio = 10, $args = 0 );
|
58 |
+
add_action( 'user_register', [ $this, 'sync_current_users_1000' ], $prio = 10, $args = 0 );
|
59 |
+
add_action( 'profile_update', [ $this, 'sync_maybe_background' ], $prio = 10, $args = 0 );
|
60 |
}
|
61 |
|
62 |
+
public function sync_maybe_background() {
|
|
|
63 |
global $pagenow;
|
64 |
+
if ( is_admin() && 'users.php' === $pagenow ) {
|
65 |
// eg for profile update we don't want to sync directly see #365 as it could cause issues with other plugins
|
66 |
// if they eg alter `get_users` option
|
67 |
+
wp_schedule_single_event( time() + 5, ScheduledTasks::EVENT_SYNC );
|
68 |
} else {
|
69 |
$this->sync_current_users_1000();
|
70 |
}
|
79 |
|
80 |
try {
|
81 |
if ( $idsite ) {
|
82 |
+
$users = $this->get_users( [ 'blog_id' => $site->blog_id ] );
|
83 |
$this->sync_users( $users, $idsite );
|
84 |
}
|
85 |
+
} catch ( Exception $e ) {
|
86 |
// we don't want to rethrow exception otherwise some other blogs might never sync
|
87 |
$this->logger->log_exception( 'user_sync ', $e );
|
88 |
}
|
94 |
}
|
95 |
}
|
96 |
|
97 |
+
private function get_users( $options = [] ) {
|
98 |
+
/** @var WP_User[] $users */
|
99 |
+
$users = get_users( $options );
|
100 |
+
|
101 |
+
$current_user = wp_get_current_user();
|
102 |
+
if ( ! empty( $current_user ) && ! empty( $current_user->user_login ) ) {
|
103 |
+
// refs https://github.com/matomo-org/wp-matomo/issues/365
|
104 |
+
// some other plugins may under circumstances overwrite the get_users query and not return all users
|
105 |
+
// as a result we would delete some users in the matomo users table. this way we make sure at least the current
|
106 |
+
// user will be added and not deleted even if the list of users is not complete
|
107 |
+
$found = false;
|
108 |
+
foreach ( $users as $user ) {
|
109 |
+
if ( $user->user_login === $current_user->user_login ) {
|
110 |
+
$found = true;
|
111 |
+
break;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
if ( ! $found ) {
|
115 |
+
$users[] = $current_user;
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
if ( is_multisite() ) {
|
120 |
+
$super_admins = get_super_admins();
|
121 |
+
if ( ! empty( $super_admins ) ) {
|
122 |
+
foreach ( $super_admins as $super_admin ) {
|
123 |
+
$found = false;
|
124 |
+
foreach ( $users as $user ) {
|
125 |
+
if ( $user->user_login === $super_admin ) {
|
126 |
+
$found = true;
|
127 |
+
break;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
if ( ! $found ) {
|
131 |
+
$user = get_user_by( 'login', $super_admin );
|
132 |
+
if ( ! empty( $user ) ) {
|
133 |
+
$users[] = $user;
|
134 |
+
}
|
135 |
+
}
|
136 |
+
}
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
return $users;
|
141 |
+
}
|
142 |
|
143 |
public function sync_current_users() {
|
144 |
$idsite = Site::get_matomo_site_id( get_current_blog_id() );
|
151 |
/**
|
152 |
* similar method to sync_current_users which synchronise on the fly only if we have less than 1000 users.
|
153 |
* Otherwise it will be done by a background task
|
154 |
+
*
|
|
|
155 |
* @return void
|
156 |
+
* @see https://github.com/matomo-org/matomo-for-wordpress/issues/460
|
157 |
+
* @see Sync::sync_current_users()
|
158 |
*/
|
159 |
public function sync_current_users_1000() {
|
160 |
$idsite = Site::get_matomo_site_id( get_current_blog_id() );
|
165 |
}
|
166 |
}
|
167 |
}
|
168 |
+
|
169 |
/**
|
170 |
* Sync all users. Make sure to always pass all sites that exist within a given site... you cannot just sync an individual
|
171 |
* user... we would delete all other users
|
172 |
*
|
173 |
+
* @param WP_User[] $users
|
174 |
* @param $idsite
|
175 |
*/
|
176 |
protected function sync_users( $users, $idsite ) {
|
178 |
|
179 |
$this->logger->log( 'Matomo will now sync ' . count( $users ) . ' users' );
|
180 |
|
181 |
+
$super_users = [];
|
182 |
+
$logins_with_some_view_access = [ 'anonmyous' ]; // may or may not exist... we don't want to delete this user though
|
183 |
$user_model = new Model();
|
184 |
|
185 |
// need to make sure we recreate new instance later with latest dependencies in case they changed
|
201 |
$logins_with_some_view_access[] = $matomo_login;
|
202 |
} elseif ( user_can( $user, Capabilities::KEY_ADMIN ) ) {
|
203 |
$matomo_login = $this->ensure_user_exists( $user );
|
204 |
+
$user_model->deleteUserAccess( $mapped_matomo_login, [ $idsite ] );
|
205 |
+
$user_model->addUserAccess( $matomo_login, Admin::ID, [ $idsite ] );
|
206 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
207 |
$logins_with_some_view_access[] = $matomo_login;
|
208 |
} elseif ( user_can( $user, Capabilities::KEY_WRITE ) ) {
|
209 |
$matomo_login = $this->ensure_user_exists( $user );
|
210 |
+
$user_model->deleteUserAccess( $mapped_matomo_login, [ $idsite ] );
|
211 |
+
$user_model->addUserAccess( $matomo_login, Write::ID, [ $idsite ] );
|
212 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
213 |
$logins_with_some_view_access[] = $matomo_login;
|
214 |
} elseif ( user_can( $user, Capabilities::KEY_VIEW ) ) {
|
215 |
$matomo_login = $this->ensure_user_exists( $user );
|
216 |
+
$user_model->deleteUserAccess( $mapped_matomo_login, [ $idsite ] );
|
217 |
+
$user_model->addUserAccess( $matomo_login, View::ID, [ $idsite ] );
|
218 |
$user_model->setSuperUserAccess( $matomo_login, false );
|
219 |
$logins_with_some_view_access[] = $matomo_login;
|
220 |
+
} elseif ( $mapped_matomo_login ) {
|
221 |
+
$user_model->deleteUserAccess( $mapped_matomo_login, [ $idsite ] );
|
222 |
}
|
223 |
|
224 |
if ( $matomo_login ) {
|
225 |
+
$locale = get_user_locale( $user->ID );
|
226 |
+
$locale_dash = Common::mb_strtolower( str_replace( '_', '-', $locale ) );
|
227 |
+
$parts = [];
|
228 |
+
if ( $locale && in_array( $locale_dash, [ 'zh-cn', 'zh-tw', 'pt-br', 'es-ar' ], true ) ) {
|
229 |
+
$parts = [ $locale_dash ];
|
230 |
+
} elseif ( ! empty( $locale ) && is_string( $locale ) ) {
|
231 |
+
$parts = explode( '_', $locale );
|
232 |
}
|
233 |
|
234 |
if ( ! empty( $parts[0] ) ) {
|
241 |
}
|
242 |
}
|
243 |
}
|
244 |
+
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
245 |
+
if ( 1 != $idsite ) {
|
246 |
// only needed if the actual site is not the default site... makes sure when they click in Matomo
|
247 |
// UI on "Dashboard" that the correct site is being opened by default
|
248 |
// eg if the linked site is actually idSite=2.
|
258 |
UsersManager\API::PREFERENCE_DEFAULT_REPORT,
|
259 |
$idsite
|
260 |
);
|
261 |
+
//phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
262 |
+
} catch ( Exception $e ) {
|
263 |
// ignore any error for now
|
264 |
}
|
|
|
265 |
}
|
266 |
);
|
267 |
}
|
272 |
}
|
273 |
|
274 |
$logins_with_some_view_access = array_unique( $logins_with_some_view_access );
|
275 |
+
$all_users = $user_model->getUsers( [] );
|
276 |
foreach ( $all_users as $all_user ) {
|
277 |
if ( ! in_array( $all_user['login'], $logins_with_some_view_access, true )
|
278 |
&& ! empty( $all_user['login'] ) ) {
|
|
|
279 |
Access::doAsSuperUser(
|
280 |
function () use ( $user_model, $all_user ) {
|
281 |
$user_model->deleteUserOnly( $all_user['login'] );
|
288 |
}
|
289 |
|
290 |
/**
|
291 |
+
* @param WP_User $wp_user
|
292 |
*/
|
293 |
protected function ensure_user_exists( $wp_user ) {
|
294 |
$user_model = new Model();
|
302 |
$user_in_matomo = $user_model->getUser( $matomo_user_login );
|
303 |
} else {
|
304 |
// wp usernames may include whitespace etc
|
305 |
+
$login = preg_replace( '/[^A-Za-zÄäÖöÜüß0-9_.@+-]+/D', '_', $login );
|
306 |
$login = substr( $login, 0, self::MAX_USER_NAME_LENGTH );
|
307 |
|
308 |
if ( ! $user_model->getUser( $login ) ) {
|
337 |
User::map_matomo_user_login( $user_id, $matomo_user_login );
|
338 |
} elseif ( $user_in_matomo['email'] !== $wp_user->user_email ) {
|
339 |
$this->logger->log( 'Matomo is now updating the email for wpUserID ' . $user_id . ' matomo login ' . $matomo_user_login );
|
340 |
+
$user_model->updateUserFields( $matomo_user_login, [ 'email' => $wp_user->user_email ] );
|
341 |
}
|
342 |
|
343 |
return $matomo_user_login;
|
classes/WpMatomo/views/referral.php
CHANGED
@@ -12,11 +12,12 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
12 |
}
|
13 |
?>
|
14 |
<div class="notice notice-info is-dismissible" id="matomo-referral">
|
15 |
-
|
16 |
-
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
12 |
}
|
13 |
?>
|
14 |
<div class="notice notice-info is-dismissible" id="matomo-referral">
|
15 |
+
<p>
|
16 |
+
<?php esc_html_e( 'Like Matomo? We would really appreciate if you took 1 minute to rate us.', 'matomo' ); ?>
|
17 |
|
18 |
+
<a href="https://wordpress.org/support/plugin/matomo/reviews/?rate=5#new-post" target="_blank"
|
19 |
+
rel="noreferrer noopener"
|
20 |
+
class="button matomo-dismiss-forever"><?php esc_html_e( 'Rate Matomo', 'matomo' ); ?></a>
|
21 |
+
</p>
|
22 |
+
<div style="clear:both;"></div>
|
23 |
+
</div>
|
config/config.php
CHANGED
@@ -122,6 +122,7 @@ return array(
|
|
122 |
$class_name = get_class($check);
|
123 |
if ($class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\ForceSSLCheck'
|
124 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\LoadDataInfileCheck'
|
|
|
125 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\RequiredPrivateDirectories' // it doesn't resolve config path correctly as it is outside matomo dir etc
|
126 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\CronArchivingCheck'
|
127 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck') {
|
122 |
$class_name = get_class($check);
|
123 |
if ($class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\ForceSSLCheck'
|
124 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\LoadDataInfileCheck'
|
125 |
+
|| $class_name === 'Piwik\Plugins\CustomJsTracker\Diagnostic\TrackerJsCheck'
|
126 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\RequiredPrivateDirectories' // it doesn't resolve config path correctly as it is outside matomo dir etc
|
127 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\CronArchivingCheck'
|
128 |
|| $class_name === 'Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck') {
|
matomo.php
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
* Description: The #1 Google Analytics alternative that gives you full control over your data and protects the privacy for your users. Free, secure and open.
|
5 |
* Author: Matomo
|
6 |
* Author URI: https://matomo.org
|
7 |
-
* Version: 4.4.
|
8 |
* Domain Path: /languages
|
9 |
* WC requires at least: 2.4.0
|
10 |
* WC tested up to: 5.5.0
|
@@ -14,6 +14,9 @@
|
|
14 |
* @link https://matomo.org
|
15 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
16 |
* @package matomo
|
|
|
|
|
|
|
17 |
*/
|
18 |
if ( ! defined( 'ABSPATH' ) ) {
|
19 |
exit; // if accessed directly
|
@@ -25,7 +28,7 @@ if ( ! defined( 'MATOMO_ANALYTICS_FILE' ) ) {
|
|
25 |
define( 'MATOMO_ANALYTICS_FILE', __FILE__ );
|
26 |
}
|
27 |
|
28 |
-
if ( ! defined('MATOMO_MARKETPLACE_PLUGIN_NAME' )) {
|
29 |
define( 'MATOMO_MARKETPLACE_PLUGIN_NAME', 'matomo-marketplace-for-wordpress/matomo-marketplace-for-wordpress.php' );
|
30 |
}
|
31 |
|
@@ -35,8 +38,8 @@ $GLOBALS['MATOMO_PLUGINS_ENABLED'] = array();
|
|
35 |
$GLOBALS['MATOMO_PLUGIN_FILES'] = array( MATOMO_ANALYTICS_FILE );
|
36 |
|
37 |
function matomo_has_compatible_content_dir() {
|
38 |
-
if ( !empty( $_SERVER['MATOMO_WP_ROOT_PATH'] )
|
39 |
-
|
40 |
return true;
|
41 |
}
|
42 |
|
@@ -44,49 +47,50 @@ function matomo_has_compatible_content_dir() {
|
|
44 |
return false;
|
45 |
}
|
46 |
|
47 |
-
$
|
48 |
-
$
|
49 |
-
$
|
50 |
|
51 |
-
$
|
52 |
-
$
|
53 |
-
$
|
54 |
-
$
|
55 |
);
|
56 |
|
57 |
-
if (in_array($
|
58 |
return true;
|
59 |
}
|
60 |
|
61 |
$wpload_base = '../../../wp-load.php';
|
62 |
$wpload_full = dirname( __FILE__ ) . '/' . $wpload_base;
|
63 |
-
if ( file_exists($wpload_full ) && is_readable( $wpload_full ) ) {
|
64 |
return true;
|
65 |
-
} elseif (realpath( $wpload_full ) && file_exists(realpath( $wpload_full )) && is_readable(realpath( $wpload_full ))) {
|
66 |
return true;
|
67 |
-
} elseif (!empty($_SERVER['SCRIPT_FILENAME']) && file_exists($_SERVER['SCRIPT_FILENAME'])) {
|
68 |
// seems symlinked... eg the wp-content dir or wp-content/plugins dir is symlinked from some very much other place...
|
69 |
-
$wpload_full = dirname($_SERVER['SCRIPT_FILENAME']) . '/' . $wpload_base;
|
70 |
-
if ( file_exists($wpload_full ) ) {
|
71 |
return true;
|
72 |
-
} elseif (realpath( $wpload_full ) && file_exists(realpath( $wpload_full ))) {
|
73 |
return true;
|
74 |
-
} elseif (file_exists(dirname( $_SERVER['SCRIPT_FILENAME'] )) . '/wp-load.php') {
|
75 |
return true;
|
76 |
}
|
77 |
}
|
78 |
|
79 |
// look in plugins directory if there is a config file for us
|
80 |
-
$wpload_config = dirname(__FILE__) . '/../matomo.wpload_dir.php';
|
81 |
-
if (file_exists( $wpload_config) && is_readable($wpload_config)) {
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
$content =
|
86 |
-
$content =
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
90 |
}
|
91 |
}
|
92 |
}
|
@@ -96,10 +100,10 @@ function matomo_has_compatible_content_dir() {
|
|
96 |
|
97 |
function matomo_header_icon( $full = false ) {
|
98 |
$file = 'logo';
|
99 |
-
if ($full) {
|
100 |
$file = 'logo-full';
|
101 |
}
|
102 |
-
echo '<img height="32" src="' . plugins_url( 'assets/img/'
|
103 |
}
|
104 |
|
105 |
function matomo_is_app_request() {
|
@@ -109,7 +113,7 @@ function matomo_is_app_request() {
|
|
109 |
|
110 |
function matomo_has_tag_manager() {
|
111 |
if ( defined( 'MATOMO_ENABLE_TAG_MANAGER' ) ) {
|
112 |
-
return !empty(MATOMO_ENABLE_TAG_MANAGER);
|
113 |
}
|
114 |
|
115 |
$is_multisite = function_exists( 'is_multisite' ) && is_multisite();
|
@@ -123,34 +127,34 @@ function matomo_has_tag_manager() {
|
|
123 |
function matomo_anonymize_value( $value ) {
|
124 |
if ( is_string( $value ) && ! empty( $value ) ) {
|
125 |
$values_to_anonymize = array(
|
126 |
-
ABSPATH
|
127 |
-
str_replace( '/', '\/', ABSPATH )
|
128 |
-
str_replace( '/', '\\', ABSPATH )
|
129 |
-
WP_CONTENT_DIR
|
130 |
str_replace( '/', '\\', WP_CONTENT_DIR ) => '$WP_CONTENT_DIR\\',
|
131 |
-
home_url()
|
132 |
-
site_url()
|
133 |
-
DB_PASSWORD
|
134 |
-
DB_USER
|
135 |
-
DB_HOST
|
136 |
-
DB_NAME
|
137 |
);
|
138 |
-
$keys
|
139 |
-
foreach ($keys as $key) {
|
140 |
-
if (defined($key)) {
|
141 |
-
$const_value = constant($key);
|
142 |
-
if (!empty($const_value) && is_string($const_value) && strlen($key) > 3) {
|
143 |
-
$values_to_anonymize[$const_value] = '$' . $key;
|
144 |
}
|
145 |
}
|
146 |
}
|
147 |
foreach ( $values_to_anonymize as $search => $replace ) {
|
148 |
-
if ($search) {
|
149 |
$value = str_replace( $search, $replace, $value );
|
150 |
}
|
151 |
}
|
152 |
// replace anything like token_auth etc or md5 or sha1 ...
|
153 |
-
$value = preg_replace('/[[:xdigit:]]{31,80}/', 'TOKEN_REPLACED', $value);
|
154 |
}
|
155 |
|
156 |
return $value;
|
@@ -195,11 +199,11 @@ function matomo_add_plugin( $plugins_directory, $wp_plugin_file, $is_marketplace
|
|
195 |
);
|
196 |
}
|
197 |
|
198 |
-
if (matomo_is_app_request() || !empty($GLOBALS['MATOMO_LOADED_DIRECTLY'])) {
|
199 |
// prevent layout being broken when thegem theme is used. their lazy items class causes the reporting UI to not appear
|
200 |
// because it creates a JS error because of escaping " too often. only breaks when " Activate image loading optimization (for desktops)"
|
201 |
// is enabled in the general theme settings
|
202 |
-
add_filter('thegem_lazy_items_need_process_content', '__return_false', 99999999, $args = 0);
|
203 |
}
|
204 |
|
205 |
require_once __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'WpMatomo.php';
|
4 |
* Description: The #1 Google Analytics alternative that gives you full control over your data and protects the privacy for your users. Free, secure and open.
|
5 |
* Author: Matomo
|
6 |
* Author URI: https://matomo.org
|
7 |
+
* Version: 4.4.2
|
8 |
* Domain Path: /languages
|
9 |
* WC requires at least: 2.4.0
|
10 |
* WC tested up to: 5.5.0
|
14 |
* @link https://matomo.org
|
15 |
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
16 |
* @package matomo
|
17 |
+
* phpcs:disable WordPress.Security.ValidatedSanitizedInput
|
18 |
+
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
19 |
+
* phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
|
20 |
*/
|
21 |
if ( ! defined( 'ABSPATH' ) ) {
|
22 |
exit; // if accessed directly
|
28 |
define( 'MATOMO_ANALYTICS_FILE', __FILE__ );
|
29 |
}
|
30 |
|
31 |
+
if ( ! defined( 'MATOMO_MARKETPLACE_PLUGIN_NAME' ) ) {
|
32 |
define( 'MATOMO_MARKETPLACE_PLUGIN_NAME', 'matomo-marketplace-for-wordpress/matomo-marketplace-for-wordpress.php' );
|
33 |
}
|
34 |
|
38 |
$GLOBALS['MATOMO_PLUGIN_FILES'] = array( MATOMO_ANALYTICS_FILE );
|
39 |
|
40 |
function matomo_has_compatible_content_dir() {
|
41 |
+
if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] )
|
42 |
+
&& file_exists( rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php' ) ) {
|
43 |
return true;
|
44 |
}
|
45 |
|
47 |
return false;
|
48 |
}
|
49 |
|
50 |
+
$content_dir = rtrim( rtrim( WP_CONTENT_DIR, '/' ), DIRECTORY_SEPARATOR );
|
51 |
+
$content_dir = wp_normalize_path( $content_dir );
|
52 |
+
$abs_path = wp_normalize_path( ABSPATH );
|
53 |
|
54 |
+
$abs_paths = array(
|
55 |
+
$abs_path . 'wp-content',
|
56 |
+
$abs_path . '/wp-content',
|
57 |
+
$abs_path . DIRECTORY_SEPARATOR . 'wp-content',
|
58 |
);
|
59 |
|
60 |
+
if ( in_array( $content_dir, $abs_paths, true ) ) {
|
61 |
return true;
|
62 |
}
|
63 |
|
64 |
$wpload_base = '../../../wp-load.php';
|
65 |
$wpload_full = dirname( __FILE__ ) . '/' . $wpload_base;
|
66 |
+
if ( file_exists( $wpload_full ) && is_readable( $wpload_full ) ) {
|
67 |
return true;
|
68 |
+
} elseif ( realpath( $wpload_full ) && file_exists( realpath( $wpload_full ) ) && is_readable( realpath( $wpload_full ) ) ) {
|
69 |
return true;
|
70 |
+
} elseif ( ! empty( $_SERVER['SCRIPT_FILENAME'] ) && file_exists( $_SERVER['SCRIPT_FILENAME'] ) ) {
|
71 |
// seems symlinked... eg the wp-content dir or wp-content/plugins dir is symlinked from some very much other place...
|
72 |
+
$wpload_full = dirname( $_SERVER['SCRIPT_FILENAME'] ) . '/' . $wpload_base;
|
73 |
+
if ( file_exists( $wpload_full ) ) {
|
74 |
return true;
|
75 |
+
} elseif ( realpath( $wpload_full ) && file_exists( realpath( $wpload_full ) ) ) {
|
76 |
return true;
|
77 |
+
} elseif ( file_exists( dirname( $_SERVER['SCRIPT_FILENAME'] ) ) . '/wp-load.php' ) {
|
78 |
return true;
|
79 |
}
|
80 |
}
|
81 |
|
82 |
// look in plugins directory if there is a config file for us
|
83 |
+
$wpload_config = dirname( __FILE__ ) . '/../matomo.wpload_dir.php';
|
84 |
+
if ( file_exists( $wpload_config ) && is_readable( $wpload_config ) ) {
|
85 |
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
86 |
+
$content = @file_get_contents( $wpload_config ); // we do not include that file for security reasons
|
87 |
+
if ( ! empty( $content ) ) {
|
88 |
+
$content = str_replace( array( '<?php', 'exit;' ), '', $content );
|
89 |
+
$content = preg_replace( '/\s/', '', $content );
|
90 |
+
$content = trim( ltrim( trim( $content ), '#' ) ); // the path may be commented out # /abs/path
|
91 |
+
if ( strpos( $content, DIRECTORY_SEPARATOR ) === 0 ) {
|
92 |
+
$wpload_file = rtrim( $content, DIRECTORY_SEPARATOR ) . '/wp-load.php';
|
93 |
+
return file_exists( $wpload_file ) && is_readable( $wpload_file );
|
94 |
}
|
95 |
}
|
96 |
}
|
100 |
|
101 |
function matomo_header_icon( $full = false ) {
|
102 |
$file = 'logo';
|
103 |
+
if ( $full ) {
|
104 |
$file = 'logo-full';
|
105 |
}
|
106 |
+
echo '<img height="32" src="' . esc_url( plugins_url( 'assets/img/' . $file . '.png', MATOMO_ANALYTICS_FILE ) ) . '" class="matomo-header-icon">';
|
107 |
}
|
108 |
|
109 |
function matomo_is_app_request() {
|
113 |
|
114 |
function matomo_has_tag_manager() {
|
115 |
if ( defined( 'MATOMO_ENABLE_TAG_MANAGER' ) ) {
|
116 |
+
return ! empty( MATOMO_ENABLE_TAG_MANAGER );
|
117 |
}
|
118 |
|
119 |
$is_multisite = function_exists( 'is_multisite' ) && is_multisite();
|
127 |
function matomo_anonymize_value( $value ) {
|
128 |
if ( is_string( $value ) && ! empty( $value ) ) {
|
129 |
$values_to_anonymize = array(
|
130 |
+
ABSPATH => '$abs_path/',
|
131 |
+
str_replace( '/', '\/', ABSPATH ) => '$abs_path\/',
|
132 |
+
str_replace( '/', '\\', ABSPATH ) => '$abs_path\/',
|
133 |
+
WP_CONTENT_DIR => '$WP_CONTENT_DIR/',
|
134 |
str_replace( '/', '\\', WP_CONTENT_DIR ) => '$WP_CONTENT_DIR\\',
|
135 |
+
home_url() => '$home_url',
|
136 |
+
site_url() => '$site_url',
|
137 |
+
DB_PASSWORD => '$DB_PASSWORD',
|
138 |
+
DB_USER => '$DB_USER',
|
139 |
+
DB_HOST => '$DB_HOST',
|
140 |
+
DB_NAME => '$DB_NAME',
|
141 |
);
|
142 |
+
$keys = array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'AUTH_SALT', 'NONCE_KEY', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT' );
|
143 |
+
foreach ( $keys as $key ) {
|
144 |
+
if ( defined( $key ) ) {
|
145 |
+
$const_value = constant( $key );
|
146 |
+
if ( ! empty( $const_value ) && is_string( $const_value ) && strlen( $key ) > 3 ) {
|
147 |
+
$values_to_anonymize[ $const_value ] = '$' . $key;
|
148 |
}
|
149 |
}
|
150 |
}
|
151 |
foreach ( $values_to_anonymize as $search => $replace ) {
|
152 |
+
if ( $search ) {
|
153 |
$value = str_replace( $search, $replace, $value );
|
154 |
}
|
155 |
}
|
156 |
// replace anything like token_auth etc or md5 or sha1 ...
|
157 |
+
$value = preg_replace( '/[[:xdigit:]]{31,80}/', 'TOKEN_REPLACED', $value );
|
158 |
}
|
159 |
|
160 |
return $value;
|
199 |
);
|
200 |
}
|
201 |
|
202 |
+
if ( matomo_is_app_request() || ! empty( $GLOBALS['MATOMO_LOADED_DIRECTLY'] ) ) {
|
203 |
// prevent layout being broken when thegem theme is used. their lazy items class causes the reporting UI to not appear
|
204 |
// because it creates a JS error because of escaping " too often. only breaks when " Activate image loading optimization (for desktops)"
|
205 |
// is enabled in the general theme settings
|
206 |
+
add_filter( 'thegem_lazy_items_need_process_content', '__return_false', 99999999, $args = 0 );
|
207 |
}
|
208 |
|
209 |
require_once __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'WpMatomo.php';
|
node_modules/chart.js/LICENSE.md
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
The MIT License (MIT)
|
2 |
+
|
3 |
+
Copyright (c) 2014-2021 Chart.js Contributors
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6 |
+
|
7 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8 |
+
|
9 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
node_modules/chart.js/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<p align="center">
|
2 |
+
<img src="https://www.chartjs.org/media/logo-title.svg"><br/>
|
3 |
+
Simple yet flexible JavaScript charting for designers & developers
|
4 |
+
</p>
|
5 |
+
|
6 |
+
<p align="center">
|
7 |
+
<a href="https://www.chartjs.org/docs/latest/getting-started/installation.html"><img src="https://img.shields.io/github/release/chartjs/Chart.js.svg?style=flat-square&maxAge=600" alt="Downloads"></a>
|
8 |
+
<a href="https://github.com/chartjs/Chart.js/actions?query=workflow%3ACI+branch%3Amaster"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/chartjs/Chart.js/CI"></a>
|
9 |
+
<a href="https://coveralls.io/github/chartjs/Chart.js?branch=master"><img src="https://img.shields.io/coveralls/chartjs/Chart.js.svg?style=flat-square&maxAge=600" alt="Coverage"></a>
|
10 |
+
<a href="https://github.com/chartjs/awesome"><img src="https://awesome.re/badge-flat2.svg" alt="Awesome"></a>
|
11 |
+
<a href="https://chartjs-slack.herokuapp.com/"><img src="https://img.shields.io/badge/slack-chartjs-blue.svg?style=flat-square&maxAge=3600" alt="Slack"></a>
|
12 |
+
</p>
|
13 |
+
|
14 |
+
## Documentation
|
15 |
+
|
16 |
+
All the links point to the new version 3 of the lib.
|
17 |
+
|
18 |
+
* [Introduction](https://www.chartjs.org/docs/latest/)
|
19 |
+
* [Getting Started](https://www.chartjs.org/docs/latest/getting-started/index)
|
20 |
+
* [General](https://www.chartjs.org/docs/latest/general/data-structures)
|
21 |
+
* [Configuration](https://www.chartjs.org/docs/latest/configuration/index)
|
22 |
+
* [Charts](https://www.chartjs.org/docs/latest/charts/line)
|
23 |
+
* [Axes](https://www.chartjs.org/docs/latest/axes/index)
|
24 |
+
* [Developers](https://www.chartjs.org/docs/latest/developers/index)
|
25 |
+
* [Popular Extensions](https://github.com/chartjs/awesome)
|
26 |
+
* [Samples](https://www.chartjs.org/samples/)
|
27 |
+
|
28 |
+
In case you are looking for the docs of version 2, you will have to specify the specific version in the url like this: [https://www.chartjs.org/docs/2.9.4/](https://www.chartjs.org/docs/2.9.4/)
|
29 |
+
|
30 |
+
## Contributing
|
31 |
+
|
32 |
+
Instructions on building and testing Chart.js can be found in [the documentation](https://www.chartjs.org/docs/master/developers/contributing.html#building-and-testing). Before submitting an issue or a pull request, please take a moment to look over the [contributing guidelines](https://www.chartjs.org/docs/master/developers/contributing) first. For support, please post questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/chartjs) with the `chartjs` tag.
|
33 |
+
|
34 |
+
## License
|
35 |
+
|
36 |
+
Chart.js is available under the [MIT license](https://opensource.org/licenses/MIT).
|
node_modules/chart.js/dist/chart.min.js
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*!
|
2 |
+
* Chart.js v3.4.1
|
3 |
+
* https://www.chartjs.org
|
4 |
+
* (c) 2021 Chart.js Contributors
|
5 |
+
* Released under the MIT License
|
6 |
+
*/
|
7 |
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";const t="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function e(e,i,n){const o=n||(t=>Array.prototype.slice.call(t));let s=!1,a=[];return function(...n){a=o(n),s||(s=!0,t.call(window,(()=>{s=!1,e.apply(i,a)})))}}function i(t,e){let i;return function(){return e?(clearTimeout(i),i=setTimeout(t,e)):t(),e}}const n=t=>"start"===t?"left":"end"===t?"right":"center",o=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,s=(t,e,i,n)=>t===(n?"left":"right")?i:"center"===t?(e+i)/2:e;var a=new class{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,n){const o=e.listeners[n],s=e.duration;o.forEach((n=>n({chart:t,initial:e.initial,numSteps:s,currentStep:Math.min(i-e.start,s)})))}_refresh(){const e=this;e._request||(e._running=!0,e._request=t.call(window,(()=>{e._update(),e._request=null,e._running&&e._refresh()})))}_update(t=Date.now()){const e=this;let i=0;e._charts.forEach(((n,o)=>{if(!n.running||!n.items.length)return;const s=n.items;let a,r=s.length-1,l=!1;for(;r>=0;--r)a=s[r],a._active?(a._total>n.duration&&(n.duration=a._total),a.tick(t),l=!0):(s[r]=s[s.length-1],s.pop());l&&(o.draw(),e._notify(o,n,t,"progress")),s.length||(n.running=!1,e._notify(o,n,t,"complete"),n.initial=!1),i+=s.length})),e._lastDate=t,0===i&&(e._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let n=i.length-1;for(;n>=0;--n)i[n].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}};
|
8 |
+
/*!
|
9 |
+
* @kurkle/color v0.1.9
|
10 |
+
* https://github.com/kurkle/color#readme
|
11 |
+
* (c) 2020 Jukka Kurkela
|
12 |
+
* Released under the MIT License
|
13 |
+
*/const r={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},l="0123456789ABCDEF",c=t=>l[15&t],h=t=>l[(240&t)>>4]+l[15&t],d=t=>(240&t)>>4==(15&t);function u(t){var e=function(t){return d(t.r)&&d(t.g)&&d(t.b)&&d(t.a)}(t)?c:h;return t?"#"+e(t.r)+e(t.g)+e(t.b)+(t.a<255?e(t.a):""):t}function f(t){return t+.5|0}const g=(t,e,i)=>Math.max(Math.min(t,i),e);function p(t){return g(f(2.55*t),0,255)}function m(t){return g(f(255*t),0,255)}function x(t){return g(f(t/2.55)/100,0,1)}function b(t){return g(f(100*t),0,100)}const _=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const y=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function v(t,e,i){const n=e*Math.min(i,1-i),o=(e,o=(e+t/30)%12)=>i-n*Math.max(Math.min(o-3,9-o,1),-1);return[o(0),o(8),o(4)]}function w(t,e,i){const n=(n,o=(n+t/60)%6)=>i-i*e*Math.max(Math.min(o,4-o,1),0);return[n(5),n(3),n(1)]}function M(t,e,i){const n=v(t,1,.5);let o;for(e+i>1&&(o=1/(e+i),e*=o,i*=o),o=0;o<3;o++)n[o]*=1-e-i,n[o]+=e;return n}function k(t){const e=t.r/255,i=t.g/255,n=t.b/255,o=Math.max(e,i,n),s=Math.min(e,i,n),a=(o+s)/2;let r,l,c;return o!==s&&(c=o-s,l=a>.5?c/(2-o-s):c/(o+s),r=o===e?(i-n)/c+(i<n?6:0):o===i?(n-e)/c+2:(e-i)/c+4,r=60*r+.5),[0|r,l||0,a]}function S(t,e,i,n){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,n)).map(m)}function P(t,e,i){return S(v,t,e,i)}function D(t){return(t%360+360)%360}function C(t){const e=y.exec(t);let i,n=255;if(!e)return;e[5]!==i&&(n=e[6]?p(+e[5]):m(+e[5]));const o=D(+e[2]),s=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return S(M,t,e,i)}(o,s,a):"hsv"===e[1]?function(t,e,i){return S(w,t,e,i)}(o,s,a):P(o,s,a),{r:i[0],g:i[1],b:i[2],a:n}}const O={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},T={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let A;function L(t){A||(A=function(){const t={},e=Object.keys(T),i=Object.keys(O);let n,o,s,a,r;for(n=0;n<e.length;n++){for(a=r=e[n],o=0;o<i.length;o++)s=i[o],r=r.replace(s,O[s]);s=parseInt(T[a],16),t[r]=[s>>16&255,s>>8&255,255&s]}return t}(),A.transparent=[0,0,0,0]);const e=A[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}function R(t,e,i){if(t){let n=k(t);n[e]=Math.max(0,Math.min(n[e]+n[e]*i,0===e?360:1)),n=P(n),t.r=n[0],t.g=n[1],t.b=n[2]}}function E(t,e){return t?Object.assign(e||{},t):t}function z(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=m(t[3]))):(e=E(t,{r:0,g:0,b:0,a:1})).a=m(e.a),e}function I(t){return"r"===t.charAt(0)?function(t){const e=_.exec(t);let i,n,o,s=255;if(e){if(e[7]!==i){const t=+e[7];s=255&(e[8]?p(t):255*t)}return i=+e[1],n=+e[3],o=+e[5],i=255&(e[2]?p(i):i),n=255&(e[4]?p(n):n),o=255&(e[6]?p(o):o),{r:i,g:n,b:o,a:s}}}(t):C(t)}class F{constructor(t){if(t instanceof F)return t;const e=typeof t;let i;var n,o,s;"object"===e?i=z(t):"string"===e&&(s=(n=t).length,"#"===n[0]&&(4===s||5===s?o={r:255&17*r[n[1]],g:255&17*r[n[2]],b:255&17*r[n[3]],a:5===s?17*r[n[4]]:255}:7!==s&&9!==s||(o={r:r[n[1]]<<4|r[n[2]],g:r[n[3]]<<4|r[n[4]],b:r[n[5]]<<4|r[n[6]],a:9===s?r[n[7]]<<4|r[n[8]]:255})),i=o||L(t)||I(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=E(this._rgb);return t&&(t.a=x(t.a)),t}set rgb(t){this._rgb=z(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${x(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):this._rgb;var t}hexString(){return this._valid?u(this._rgb):this._rgb}hslString(){return this._valid?function(t){if(!t)return;const e=k(t),i=e[0],n=b(e[1]),o=b(e[2]);return t.a<255?`hsla(${i}, ${n}%, ${o}%, ${x(t.a)})`:`hsl(${i}, ${n}%, ${o}%)`}(this._rgb):this._rgb}mix(t,e){const i=this;if(t){const n=i.rgb,o=t.rgb;let s;const a=e===s?.5:e,r=2*a-1,l=n.a-o.a,c=((r*l==-1?r:(r+l)/(1+r*l))+1)/2;s=1-c,n.r=255&c*n.r+s*o.r+.5,n.g=255&c*n.g+s*o.g+.5,n.b=255&c*n.b+s*o.b+.5,n.a=a*n.a+(1-a)*o.a,i.rgb=n}return i}clone(){return new F(this.rgb)}alpha(t){return this._rgb.a=m(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=f(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return R(this._rgb,2,t),this}darken(t){return R(this._rgb,2,-t),this}saturate(t){return R(this._rgb,1,t),this}desaturate(t){return R(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=k(t);i[0]=D(i[0]+e),i=P(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function B(t){return new F(t)}const V=t=>t instanceof CanvasGradient||t instanceof CanvasPattern;function W(t){return V(t)?t:B(t)}function N(t){return V(t)?t:B(t).saturate(.5).darken(.1).hexString()}function H(){}const j=function(){let t=0;return function(){return t++}}();function $(t){return null==t}function Y(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)}function U(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}const X=t=>("number"==typeof t||t instanceof Number)&&isFinite(+t);function q(t,e){return X(t)?t:e}function K(t,e){return void 0===t?e:t}const G=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:t/e,Z=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function Q(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function J(t,e,i,n){let o,s,a;if(Y(t))if(s=t.length,n)for(o=s-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;o<s;o++)e.call(i,t[o],o);else if(U(t))for(a=Object.keys(t),s=a.length,o=0;o<s;o++)e.call(i,t[a[o]],a[o])}function tt(t,e){let i,n,o,s;if(!t||!e||t.length!==e.length)return!1;for(i=0,n=t.length;i<n;++i)if(o=t[i],s=e[i],o.datasetIndex!==s.datasetIndex||o.index!==s.index)return!1;return!0}function et(t){if(Y(t))return t.map(et);if(U(t)){const e=Object.create(null),i=Object.keys(t),n=i.length;let o=0;for(;o<n;++o)e[i[o]]=et(t[i[o]]);return e}return t}function it(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function nt(t,e,i,n){if(!it(t))return;const o=e[t],s=i[t];U(o)&&U(s)?ot(o,s,n):e[t]=et(s)}function ot(t,e,i){const n=Y(e)?e:[e],o=n.length;if(!U(t))return t;const s=(i=i||{}).merger||nt;for(let a=0;a<o;++a){if(!U(e=n[a]))continue;const o=Object.keys(e);for(let n=0,a=o.length;n<a;++n)s(o[n],t,e,i)}return t}function st(t,e){return ot(t,e,{merger:at})}function at(t,e,i){if(!it(t))return;const n=e[t],o=i[t];U(n)&&U(o)?st(n,o):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=et(o))}function rt(t,e){const i=t.indexOf(".",e);return-1===i?t.length:i}function lt(t,e){if(""===e)return t;let i=0,n=rt(e,i);for(;t&&n>i;)t=t[e.substr(i,n-i)],i=n+1,n=rt(e,i);return t}function ct(t){return t.charAt(0).toUpperCase()+t.slice(1)}const ht=t=>void 0!==t,dt=t=>"function"==typeof t,ut=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0},ft=Object.create(null),gt=Object.create(null);function pt(t,e){if(!e)return t;const i=e.split(".");for(let e=0,n=i.length;e<n;++e){const n=i[e];t=t[n]||(t[n]=Object.create(null))}return t}function mt(t,e,i){return"string"==typeof e?ot(pt(t,e),i):ot(pt(t,""),e)}var xt=new class{constructor(t){this.animation=void 0,this.backgroundColor="rgba(0,0,0,0.1)",this.borderColor="rgba(0,0,0,0.1)",this.color="#666",this.datasets={},this.devicePixelRatio=t=>t.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>N(e.backgroundColor),this.hoverBorderColor=(t,e)=>N(e.borderColor),this.hoverColor=(t,e)=>N(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.describe(t)}set(t,e){return mt(this,t,e)}get(t){return pt(this,t)}describe(t,e){return mt(gt,t,e)}override(t,e){return mt(ft,t,e)}route(t,e,i,n){const o=pt(this,t),s=pt(this,i),a="_"+e;Object.defineProperties(o,{[a]:{value:o[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[a],e=s[n];return U(t)?Object.assign({},e,t):K(t,e)},set(t){this[a]=t}}})}}({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});const bt=Math.PI,_t=2*bt,yt=_t+bt,vt=Number.POSITIVE_INFINITY,wt=bt/180,Mt=bt/2,kt=bt/4,St=2*bt/3,Pt=Math.log10,Dt=Math.sign;function Ct(t){const e=Math.round(t);t=At(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(Pt(t))),n=t/i;return(n<=1?1:n<=2?2:n<=5?5:10)*i}function Ot(t){const e=[],i=Math.sqrt(t);let n;for(n=1;n<i;n++)t%n==0&&(e.push(n),e.push(t/n));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function Tt(t){return!isNaN(parseFloat(t))&&isFinite(t)}function At(t,e,i){return Math.abs(t-e)<i}function Lt(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function Rt(t,e,i){let n,o,s;for(n=0,o=t.length;n<o;n++)s=t[n][i],isNaN(s)||(e.min=Math.min(e.min,s),e.max=Math.max(e.max,s))}function Et(t){return t*(bt/180)}function zt(t){return t*(180/bt)}function It(t){if(!X(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function Ft(t,e){const i=e.x-t.x,n=e.y-t.y,o=Math.sqrt(i*i+n*n);let s=Math.atan2(n,i);return s<-.5*bt&&(s+=_t),{angle:s,distance:o}}function Bt(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function Vt(t,e){return(t-e+yt)%_t-bt}function Wt(t){return(t%_t+_t)%_t}function Nt(t,e,i,n){const o=Wt(t),s=Wt(e),a=Wt(i),r=Wt(s-o),l=Wt(a-o),c=Wt(o-s),h=Wt(o-a);return o===s||o===a||n&&s===a||r>l&&c<h}function Ht(t,e,i){return Math.max(e,Math.min(i,t))}function jt(t){return Ht(t,-32768,32767)}function $t(t){return!t||$(t.size)||$(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Yt(t,e,i,n,o){let s=e[o];return s||(s=e[o]=t.measureText(o).width,i.push(o)),s>n&&(n=s),n}function Ut(t,e,i,n){let o=(n=n||{}).data=n.data||{},s=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(o=n.data={},s=n.garbageCollect=[],n.font=e),t.save(),t.font=e;let a=0;const r=i.length;let l,c,h,d,u;for(l=0;l<r;l++)if(d=i[l],null!=d&&!0!==Y(d))a=Yt(t,o,s,a,d);else if(Y(d))for(c=0,h=d.length;c<h;c++)u=d[c],null==u||Y(u)||(a=Yt(t,o,s,a,u));t.restore();const f=s.length/2;if(f>i.length){for(l=0;l<f;l++)delete o[s[l]];s.splice(0,f)}return a}function Xt(t,e,i){const n=t.currentDevicePixelRatio,o=0!==i?Math.max(i/2,.5):0;return Math.round((e-o)*n)/n+o}function qt(t,e){(e=e||t.getContext("2d")).save(),e.resetTransform(),e.clearRect(0,0,t.width,t.height),e.restore()}function Kt(t,e,i,n){let o,s,a,r,l;const c=e.pointStyle,h=e.rotation,d=e.radius;let u=(h||0)*wt;if(c&&"object"==typeof c&&(o=c.toString(),"[object HTMLImageElement]"===o||"[object HTMLCanvasElement]"===o))return t.save(),t.translate(i,n),t.rotate(u),t.drawImage(c,-c.width/2,-c.height/2,c.width,c.height),void t.restore();if(!(isNaN(d)||d<=0)){switch(t.beginPath(),c){default:t.arc(i,n,d,0,_t),t.closePath();break;case"triangle":t.moveTo(i+Math.sin(u)*d,n-Math.cos(u)*d),u+=St,t.lineTo(i+Math.sin(u)*d,n-Math.cos(u)*d),u+=St,t.lineTo(i+Math.sin(u)*d,n-Math.cos(u)*d),t.closePath();break;case"rectRounded":l=.516*d,r=d-l,s=Math.cos(u+kt)*r,a=Math.sin(u+kt)*r,t.arc(i-s,n-a,l,u-bt,u-Mt),t.arc(i+a,n-s,l,u-Mt,u),t.arc(i+s,n+a,l,u,u+Mt),t.arc(i-a,n+s,l,u+Mt,u+bt),t.closePath();break;case"rect":if(!h){r=Math.SQRT1_2*d,t.rect(i-r,n-r,2*r,2*r);break}u+=kt;case"rectRot":s=Math.cos(u)*d,a=Math.sin(u)*d,t.moveTo(i-s,n-a),t.lineTo(i+a,n-s),t.lineTo(i+s,n+a),t.lineTo(i-a,n+s),t.closePath();break;case"crossRot":u+=kt;case"cross":s=Math.cos(u)*d,a=Math.sin(u)*d,t.moveTo(i-s,n-a),t.lineTo(i+s,n+a),t.moveTo(i+a,n-s),t.lineTo(i-a,n+s);break;case"star":s=Math.cos(u)*d,a=Math.sin(u)*d,t.moveTo(i-s,n-a),t.lineTo(i+s,n+a),t.moveTo(i+a,n-s),t.lineTo(i-a,n+s),u+=kt,s=Math.cos(u)*d,a=Math.sin(u)*d,t.moveTo(i-s,n-a),t.lineTo(i+s,n+a),t.moveTo(i+a,n-s),t.lineTo(i-a,n+s);break;case"line":s=Math.cos(u)*d,a=Math.sin(u)*d,t.moveTo(i-s,n-a),t.lineTo(i+s,n+a);break;case"dash":t.moveTo(i,n),t.lineTo(i+Math.cos(u)*d,n+Math.sin(u)*d)}t.fill(),e.borderWidth>0&&t.stroke()}}function Gt(t,e,i){return i=i||.5,t&&t.x>e.left-i&&t.x<e.right+i&&t.y>e.top-i&&t.y<e.bottom+i}function Zt(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()}function Qt(t){t.restore()}function Jt(t,e,i,n,o){if(!e)return t.lineTo(i.x,i.y);if("middle"===o){const n=(e.x+i.x)/2;t.lineTo(n,e.y),t.lineTo(n,i.y)}else"after"===o!=!!n?t.lineTo(e.x,i.y):t.lineTo(i.x,e.y);t.lineTo(i.x,i.y)}function te(t,e,i,n){if(!e)return t.lineTo(i.x,i.y);t.bezierCurveTo(n?e.cp1x:e.cp2x,n?e.cp1y:e.cp2y,n?i.cp2x:i.cp1x,n?i.cp2y:i.cp1y,i.x,i.y)}function ee(t,e,i,n,o,s={}){const a=Y(e)?e:[e],r=s.strokeWidth>0&&""!==s.strokeColor;let l,c;for(t.save(),t.font=o.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]);$(e.rotation)||t.rotate(e.rotation);e.color&&(t.fillStyle=e.color);e.textAlign&&(t.textAlign=e.textAlign);e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,s),l=0;l<a.length;++l)c=a[l],r&&(s.strokeColor&&(t.strokeStyle=s.strokeColor),$(s.strokeWidth)||(t.lineWidth=s.strokeWidth),t.strokeText(c,i,n,s.maxWidth)),t.fillText(c,i,n,s.maxWidth),ie(t,i,n,c,s),n+=o.lineHeight;t.restore()}function ie(t,e,i,n,o){if(o.strikethrough||o.underline){const s=t.measureText(n),a=e-s.actualBoundingBoxLeft,r=e+s.actualBoundingBoxRight,l=i-s.actualBoundingBoxAscent,c=i+s.actualBoundingBoxDescent,h=o.strikethrough?(l+c)/2:c;t.strokeStyle=t.fillStyle,t.beginPath(),t.lineWidth=o.decorationWidth||2,t.moveTo(a,h),t.lineTo(r,h),t.stroke()}}function ne(t,e){const{x:i,y:n,w:o,h:s,radius:a}=e;t.arc(i+a.topLeft,n+a.topLeft,a.topLeft,-Mt,bt,!0),t.lineTo(i,n+s-a.bottomLeft),t.arc(i+a.bottomLeft,n+s-a.bottomLeft,a.bottomLeft,bt,Mt,!0),t.lineTo(i+o-a.bottomRight,n+s),t.arc(i+o-a.bottomRight,n+s-a.bottomRight,a.bottomRight,Mt,0,!0),t.lineTo(i+o,n+a.topRight),t.arc(i+o-a.topRight,n+a.topRight,a.topRight,0,-Mt,!0),t.lineTo(i+a.topLeft,n)}function oe(t,e,i){i=i||(i=>t[i]<e);let n,o=t.length-1,s=0;for(;o-s>1;)n=s+o>>1,i(n)?s=n:o=n;return{lo:s,hi:o}}const se=(t,e,i)=>oe(t,i,(n=>t[n][e]<i)),ae=(t,e,i)=>oe(t,i,(n=>t[n][e]>=i));function re(t,e,i){let n=0,o=t.length;for(;n<o&&t[n]<e;)n++;for(;o>n&&t[o-1]>i;)o--;return n>0||o<t.length?t.slice(n,o):t}const le=["push","pop","shift","splice","unshift"];function ce(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),le.forEach((e=>{const i="_onData"+ct(e),n=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const o=n.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),o}})})))}function he(t,e){const i=t._chartjs;if(!i)return;const n=i.listeners,o=n.indexOf(e);-1!==o&&n.splice(o,1),n.length>0||(le.forEach((e=>{delete t[e]})),delete t._chartjs)}function de(t){const e=new Set;let i,n;for(i=0,n=t.length;i<n;++i)e.add(t[i]);return e.size===n?t:Array.from(e)}function ue(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function fe(t,e,i){let n;return"string"==typeof t?(n=parseInt(t,10),-1!==t.indexOf("%")&&(n=n/100*e.parentNode[i])):n=t,n}const ge=t=>window.getComputedStyle(t,null);function pe(t,e){return ge(t).getPropertyValue(e)}const me=["top","right","bottom","left"];function xe(t,e,i){const n={};i=i?"-"+i:"";for(let o=0;o<4;o++){const s=me[o];n[s]=parseFloat(t[e+"-"+s+i])||0}return n.width=n.left+n.right,n.height=n.top+n.bottom,n}function be(t,e){const{canvas:i,currentDevicePixelRatio:n}=e,o=ge(i),s="border-box"===o.boxSizing,a=xe(o,"padding"),r=xe(o,"border","width"),{x:l,y:c,box:h}=function(t,e){const i=t.native||t,n=i.touches,o=n&&n.length?n[0]:i,{offsetX:s,offsetY:a}=o;let r,l,c=!1;if(((t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot))(s,a,i.target))r=s,l=a;else{const t=e.getBoundingClientRect();r=o.clientX-t.left,l=o.clientY-t.top,c=!0}return{x:r,y:l,box:c}}(t,i),d=a.left+(h&&r.left),u=a.top+(h&&r.top);let{width:f,height:g}=e;return s&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/n),y:Math.round((c-u)/g*i.height/n)}}const _e=t=>Math.round(10*t)/10;function ye(t,e,i,n){const o=ge(t),s=xe(o,"margin"),a=fe(o.maxWidth,t,"clientWidth")||vt,r=fe(o.maxHeight,t,"clientHeight")||vt,l=function(t,e,i){let n,o;if(void 0===e||void 0===i){const s=ue(t);if(s){const t=s.getBoundingClientRect(),a=ge(s),r=xe(a,"border","width"),l=xe(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,n=fe(a.maxWidth,s,"clientWidth"),o=fe(a.maxHeight,s,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:n||vt,maxHeight:o||vt}}(t,e,i);let{width:c,height:h}=l;if("content-box"===o.boxSizing){const t=xe(o,"border","width"),e=xe(o,"padding");c-=e.width+t.width,h-=e.height+t.height}return c=Math.max(0,c-s.width),h=Math.max(0,n?Math.floor(c/n):h-s.height),c=_e(Math.min(c,a,l.maxWidth)),h=_e(Math.min(h,r,l.maxHeight)),c&&!h&&(h=_e(c/2)),{width:c,height:h}}function ve(t,e,i){const n=e||1,o=Math.floor(t.height*n),s=Math.floor(t.width*n);t.height=o/n,t.width=s/n;const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==n||a.height!==o||a.width!==s)&&(t.currentDevicePixelRatio=n,a.height=o,a.width=s,t.ctx.setTransform(n,0,0,n,0,0),!0)}const we=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function Me(t,e){const i=pe(t,e),n=i&&i.match(/^(\d+)(\.\d+)?px$/);return n?+n[1]:void 0}function ke(t,e){return"native"in t?{x:t.x,y:t.y}:be(t,e)}function Se(t,e,i,n){const{controller:o,data:s,_sorted:a}=t,r=o._cachedMeta.iScale;if(r&&e===r.axis&&a&&s.length){const t=r._reversePixels?ae:se;if(!n)return t(s,e,i);if(o._sharedOptions){const n=s[0],o="function"==typeof n.getRange&&n.getRange(e);if(o){const n=t(s,e,i-o),a=t(s,e,i+o);return{lo:n.lo,hi:a.hi}}}}return{lo:0,hi:s.length-1}}function Pe(t,e,i,n,o){const s=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=s.length;t<i;++t){const{index:i,data:r}=s[t],{lo:l,hi:c}=Se(s[t],e,a,o);for(let t=l;t<=c;++t){const e=r[t];e.skip||n(e,i,t)}}}function De(t,e,i,n){const o=[];if(!Gt(e,t.chartArea,t._minPadding))return o;return Pe(t,i,e,(function(t,i,s){t.inRange(e.x,e.y,n)&&o.push({element:t,datasetIndex:i,index:s})}),!0),o}function Ce(t,e,i,n,o){const s=function(t){const e=-1!==t.indexOf("x"),i=-1!==t.indexOf("y");return function(t,n){const o=e?Math.abs(t.x-n.x):0,s=i?Math.abs(t.y-n.y):0;return Math.sqrt(Math.pow(o,2)+Math.pow(s,2))}}(i);let a=Number.POSITIVE_INFINITY,r=[];if(!Gt(e,t.chartArea,t._minPadding))return r;return Pe(t,i,e,(function(i,l,c){if(n&&!i.inRange(e.x,e.y,o))return;const h=i.getCenterPoint(o);if(!Gt(h,t.chartArea,t._minPadding))return;const d=s(e,h);d<a?(r=[{element:i,datasetIndex:l,index:c}],a=d):d===a&&r.push({element:i,datasetIndex:l,index:c})})),r}function Oe(t,e,i,n){const o=ke(e,t),s=[],a=i.axis,r="x"===a?"inXRange":"inYRange";let l=!1;return function(t,e){const i=t.getSortedVisibleDatasetMetas();let n,o,s;for(let t=0,a=i.length;t<a;++t){({index:n,data:o}=i[t]);for(let t=0,i=o.length;t<i;++t)s=o[t],s.skip||e(s,n,t)}}(t,((t,e,i)=>{t[r](o[a],n)&&s.push({element:t,datasetIndex:e,index:i}),t.inRange(o.x,o.y,n)&&(l=!0)})),i.intersect&&!l?[]:s}var Te={modes:{index(t,e,i,n){const o=ke(e,t),s=i.axis||"x",a=i.intersect?De(t,o,s,n):Ce(t,o,s,!1,n),r=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=a[0].index,i=t.data[e];i&&!i.skip&&r.push({element:i,datasetIndex:t.index,index:e})})),r):[]},dataset(t,e,i,n){const o=ke(e,t),s=i.axis||"xy";let a=i.intersect?De(t,o,s,n):Ce(t,o,s,!1,n);if(a.length>0){const e=a[0].datasetIndex,i=t.getDatasetMeta(e).data;a=[];for(let t=0;t<i.length;++t)a.push({element:i[t],datasetIndex:e,index:t})}return a},point:(t,e,i,n)=>De(t,ke(e,t),i.axis||"xy",n),nearest:(t,e,i,n)=>Ce(t,ke(e,t),i.axis||"xy",i.intersect,n),x:(t,e,i,n)=>(i.axis="x",Oe(t,e,i,n)),y:(t,e,i,n)=>(i.axis="y",Oe(t,e,i,n))}};const Ae=new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/),Le=new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);function Re(t,e){const i=(""+t).match(Ae);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}function Ee(t,e){const i={},n=U(e),o=n?Object.keys(e):e,s=U(t)?n?i=>K(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of o)i[t]=+s(t)||0;return i}function ze(t){return Ee(t,{top:"y",right:"x",bottom:"y",left:"x"})}function Ie(t){return Ee(t,["topLeft","topRight","bottomLeft","bottomRight"])}function Fe(t){const e=ze(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Be(t,e){t=t||{},e=e||xt.font;let i=K(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let n=K(t.style,e.style);n&&!(""+n).match(Le)&&(console.warn('Invalid font style specified: "'+n+'"'),n="");const o={family:K(t.family,e.family),lineHeight:Re(K(t.lineHeight,e.lineHeight),i),size:i,style:n,weight:K(t.weight,e.weight),string:""};return o.string=$t(o),o}function Ve(t,e,i,n){let o,s,a,r=!0;for(o=0,s=t.length;o<s;++o)if(a=t[o],void 0!==a&&(void 0!==e&&"function"==typeof a&&(a=a(e),r=!1),void 0!==i&&Y(a)&&(a=a[i%a.length],r=!1),void 0!==a))return n&&!r&&(n.cacheable=!1),a}function We(t,e){const{min:i,max:n}=t;return{min:i-Math.abs(Z(e,i)),max:n+Z(e,n)}}const Ne=["left","top","right","bottom"];function He(t,e){return t.filter((t=>t.pos===e))}function je(t,e){return t.filter((t=>-1===Ne.indexOf(t.pos)&&t.box.axis===e))}function $e(t,e){return t.sort(((t,i)=>{const n=e?i:t,o=e?t:i;return n.weight===o.weight?n.index-o.index:n.weight-o.weight}))}function Ye(t,e,i,n){return Math.max(t[i],e[i])+Math.max(t[n],e[n])}function Ue(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.left),t.bottom=Math.max(t.bottom,e.bottom),t.right=Math.max(t.right,e.right)}function Xe(t,e,i){const n=i.box,o=t.maxPadding;U(i.pos)||(i.size&&(t[i.pos]-=i.size),i.size=i.horizontal?n.height:n.width,t[i.pos]+=i.size),n.getPadding&&Ue(o,n.getPadding());const s=Math.max(0,e.outerWidth-Ye(o,t,"left","right")),a=Math.max(0,e.outerHeight-Ye(o,t,"top","bottom")),r=s!==t.w,l=a!==t.h;return t.w=s,t.h=a,i.horizontal?{same:r,other:l}:{same:l,other:r}}function qe(t,e){const i=e.maxPadding;function n(t){const n={left:0,top:0,right:0,bottom:0};return t.forEach((t=>{n[t]=Math.max(e[t],i[t])})),n}return n(t?["left","right"]:["top","bottom"])}function Ke(t,e,i){const n=[];let o,s,a,r,l,c;for(o=0,s=t.length,l=0;o<s;++o){a=t[o],r=a.box,r.update(a.width||e.w,a.height||e.h,qe(a.horizontal,e));const{same:s,other:h}=Xe(e,i,a);l|=s&&n.length,c=c||h,r.fullSize||n.push(a)}return l&&Ke(n,e,i)||c}function Ge(t,e,i){const n=i.padding;let o,s,a,r,l=e.x,c=e.y;for(o=0,s=t.length;o<s;++o)a=t[o],r=a.box,a.horizontal?(r.left=r.fullSize?n.left:e.left,r.right=r.fullSize?i.outerWidth-n.right:e.left+e.w,r.top=c,r.bottom=c+r.height,r.width=r.right-r.left,c=r.bottom):(r.left=l,r.right=l+r.width,r.top=r.fullSize?n.top:e.top,r.bottom=r.fullSize?i.outerHeight-n.bottom:e.top+e.h,r.height=r.bottom-r.top,l=r.right);e.x=l,e.y=c}xt.set("layout",{padding:{top:0,right:0,bottom:0,left:0}});var Ze={addBox(t,e){t.boxes||(t.boxes=[]),e.fullSize=e.fullSize||!1,e.position=e.position||"top",e.weight=e.weight||0,e._layers=e._layers||function(){return[{z:0,draw(t){e.draw(t)}}]},t.boxes.push(e)},removeBox(t,e){const i=t.boxes?t.boxes.indexOf(e):-1;-1!==i&&t.boxes.splice(i,1)},configure(t,e,i){e.fullSize=i.fullSize,e.position=i.position,e.weight=i.weight},update(t,e,i,n){if(!t)return;const o=Fe(t.options.layout.padding),s=Math.max(e-o.width,0),a=Math.max(i-o.height,0),r=function(t){const e=function(t){const e=[];let i,n,o;for(i=0,n=(t||[]).length;i<n;++i)o=t[i],e.push({index:i,box:o,pos:o.position,horizontal:o.isHorizontal(),weight:o.weight});return e}(t),i=$e(e.filter((t=>t.box.fullSize)),!0),n=$e(He(e,"left"),!0),o=$e(He(e,"right")),s=$e(He(e,"top"),!0),a=$e(He(e,"bottom")),r=je(e,"x"),l=je(e,"y");return{fullSize:i,leftAndTop:n.concat(s),rightAndBottom:o.concat(l).concat(a).concat(r),chartArea:He(e,"chartArea"),vertical:n.concat(o).concat(l),horizontal:s.concat(a).concat(r)}}(t.boxes),l=r.vertical,c=r.horizontal;J(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const h=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:o,availableWidth:s,availableHeight:a,vBoxMaxWidth:s/2/h,hBoxMaxHeight:a/2}),u=Object.assign({},o);Ue(u,Fe(n));const f=Object.assign({maxPadding:u,w:s,h:a,x:o.left,y:o.top},o);!function(t,e){let i,n,o;for(i=0,n=t.length;i<n;++i)o=t[i],o.horizontal?(o.width=o.box.fullSize&&e.availableWidth,o.height=e.hBoxMaxHeight):(o.width=e.vBoxMaxWidth,o.height=o.box.fullSize&&e.availableHeight)}(l.concat(c),d),Ke(r.fullSize,f,d),Ke(l,f,d),Ke(c,f,d)&&Ke(l,f,d),function(t){const e=t.maxPadding;function i(i){const n=Math.max(e[i]-t[i],0);return t[i]+=n,n}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(f),Ge(r.leftAndTop,f,d),f.x+=f.w,f.y+=f.h,Ge(r.rightAndBottom,f,d),t.chartArea={left:f.left,top:f.top,right:f.left+f.w,bottom:f.top+f.h,height:f.h,width:f.w},J(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(f.w,f.h)}))}};class Qe{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,n){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,n?Math.floor(e/n):i)}}isAttached(t){return!0}}class Je extends Qe{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}}const ti={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ei=t=>null===t||""===t;const ii=!!we&&{passive:!0};function ni(t,e,i){t.canvas.removeEventListener(e,i,ii)}function oi(t,e,i){const n=t.canvas,o=n&&ue(n)||n,s=new MutationObserver((t=>{const e=ue(o);t.forEach((t=>{for(let n=0;n<t.addedNodes.length;n++){const s=t.addedNodes[n];s!==o&&s!==e||i(t.target)}}))}));return s.observe(document,{childList:!0,subtree:!0}),s}function si(t,e,i){const n=t.canvas,o=n&&ue(n);if(!o)return;const s=new MutationObserver((t=>{t.forEach((t=>{for(let e=0;e<t.removedNodes.length;e++)if(t.removedNodes[e]===n){i();break}}))}));return s.observe(o,{childList:!0}),s}const ai=new Map;let ri=0;function li(){const t=window.devicePixelRatio;t!==ri&&(ri=t,ai.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ci(t,i,n){const o=t.canvas,s=o&&ue(o);if(!s)return;const a=e(((t,e)=>{const i=s.clientWidth;n(t,e),i<s.clientWidth&&n()}),window),r=new ResizeObserver((t=>{const e=t[0],i=e.contentRect.width,n=e.contentRect.height;0===i&&0===n||a(i,n)}));return r.observe(s),function(t,e){ai.size||window.addEventListener("resize",li),ai.set(t,e)}(t,a),r}function hi(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){ai.delete(t),ai.size||window.removeEventListener("resize",li)}(t)}function di(t,i,n){const o=t.canvas,s=e((e=>{null!==t.ctx&&n(function(t,e){const i=ti[t.type]||t.type,{x:n,y:o}=be(t,e);return{type:i,chart:e,native:t,x:void 0!==n?n:null,y:void 0!==o?o:null}}(e,t))}),t,(t=>{const e=t[0];return[e,e.offsetX,e.offsetY]}));return function(t,e,i){t.addEventListener(e,i,ii)}(o,i,s),s}class ui extends Qe{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,n=t.getAttribute("height"),o=t.getAttribute("width");if(t.$chartjs={initial:{height:n,width:o,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ei(o)){const e=Me(t,"width");void 0!==e&&(t.width=e)}if(ei(n))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Me(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e.$chartjs)return!1;const i=e.$chartjs.initial;["height","width"].forEach((t=>{const n=i[t];$(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e.$chartjs,!0}addEventListener(t,e,i){this.removeEventListener(t,e);const n=t.$proxies||(t.$proxies={}),o={attach:oi,detach:si,resize:ci}[e]||di;n[e]=o(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),n=i[e];if(!n)return;({attach:hi,detach:hi,resize:hi}[e]||ni)(t,e,n),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,n){return ye(t,e,i,n)}isAttached(t){const e=ue(t);return!(!e||!ue(e))}}var fi=Object.freeze({__proto__:null,BasePlatform:Qe,BasicPlatform:Je,DomPlatform:ui});const gi=t=>0===t||1===t,pi=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*_t/i),mi=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*_t/i)+1,xi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*Mt),easeOutSine:t=>Math.sin(t*Mt),easeInOutSine:t=>-.5*(Math.cos(bt*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>gi(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>gi(t)?t:pi(t,.075,.3),easeOutElastic:t=>gi(t)?t:mi(t,.075,.3),easeInOutElastic(t){const e=.1125;return gi(t)?t:t<.5?.5*pi(2*t,e,.45):.5+.5*mi(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-xi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*xi.easeInBounce(2*t):.5*xi.easeOutBounce(2*t-1)+.5},bi="transparent",_i={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const n=W(t||bi),o=n.valid&&W(e||bi);return o&&o.valid?o.mix(n,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class yi{constructor(t,e,i,n){const o=e[i];n=Ve([t.to,n,o,t.from]);const s=Ve([t.from,o,n]);this._active=!0,this._fn=t.fn||_i[t.type||typeof s],this._easing=xi[t.easing]||xi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=s,this._to=n,this._promises=void 0}active(){return this._active}update(t,e,i){const n=this;if(n._active){n._notify(!1);const o=n._target[n._prop],s=i-n._start,a=n._duration-s;n._start=i,n._duration=Math.floor(Math.max(a,t.duration)),n._total+=s,n._loop=!!t.loop,n._to=Ve([t.to,e,o,t.from]),n._from=Ve([t.from,o,e])}}cancel(){const t=this;t._active&&(t.tick(Date.now()),t._active=!1,t._notify(!1))}tick(t){const e=this,i=t-e._start,n=e._duration,o=e._prop,s=e._from,a=e._loop,r=e._to;let l;if(e._active=s!==r&&(a||i<n),!e._active)return e._target[o]=r,void e._notify(!0);i<0?e._target[o]=s:(l=i/n%2,l=a&&l>1?2-l:l,l=e._easing(Math.min(1,Math.max(0,l))),e._target[o]=e._fn(s,r,l))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t<i.length;t++)i[t][e]()}}xt.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0});const vi=Object.keys(xt.animation);xt.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),xt.set("animations",{colors:{type:"color",properties:["color","borderColor","backgroundColor"]},numbers:{type:"number",properties:["x","y","borderWidth","radius","tension"]}}),xt.describe("animations",{_fallback:"animation"}),xt.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}});class wi{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!U(t))return;const e=this._properties;Object.getOwnPropertyNames(t).forEach((i=>{const n=t[i];if(!U(n))return;const o={};for(const t of vi)o[t]=n[t];(Y(n.properties)&&n.properties||[i]).forEach((t=>{t!==i&&e.has(t)||e.set(t,o)}))}))}_animateOptions(t,e){const i=e.options,n=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!n)return[];const o=this._createAnimations(n,i);return i.$shared&&function(t,e){const i=[],n=Object.keys(e);for(let e=0;e<n.length;e++){const o=t[n[e]];o&&o.active()&&i.push(o.wait())}return Promise.all(i)}(t.options.$animations,i).then((()=>{t.options=i}),(()=>{})),o}_createAnimations(t,e){const i=this._properties,n=[],o=t.$animations||(t.$animations={}),s=Object.keys(e),a=Date.now();let r;for(r=s.length-1;r>=0;--r){const l=s[r];if("$"===l.charAt(0))continue;if("options"===l){n.push(...this._animateOptions(t,e));continue}const c=e[l];let h=o[l];const d=i.get(l);if(h){if(d&&h.active()){h.update(d,c,a);continue}h.cancel()}d&&d.duration?(o[l]=h=new yi(d,t,l,c),n.push(h)):t[l]=c}return n}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(a.add(this._chart,i),!0):void 0}}function Mi(t,e){const i=t&&t.options||{},n=i.reverse,o=void 0===i.min?e:0,s=void 0===i.max?e:0;return{start:n?s:o,end:n?o:s}}function ki(t,e){const i=[],n=t._getSortedDatasetMetas(e);let o,s;for(o=0,s=n.length;o<s;++o)i.push(n[o].index);return i}function Si(t,e,i,n){const o=t.keys,s="single"===n.mode;let a,r,l,c;if(null!==e){for(a=0,r=o.length;a<r;++a){if(l=+o[a],l===i){if(n.all)continue;break}c=t.values[l],X(c)&&(s||0===e||Dt(e)===Dt(c))&&(e+=c)}return e}}function Pi(t,e){const i=t&&t.options.stacked;return i||void 0===i&&void 0!==e.stack}function Di(t,e,i){const n=t[e]||(t[e]={});return n[i]||(n[i]={})}function Ci(t,e,i){for(const n of e.getMatchingVisibleMetas("bar").reverse()){const e=t[n.index];if(i&&e>0||!i&&e<0)return n.index}return null}function Oi(t,e){const{chart:i,_cachedMeta:n}=t,o=i._stacks||(i._stacks={}),{iScale:s,vScale:a,index:r}=n,l=s.axis,c=a.axis,h=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(s,a,n),d=e.length;let u;for(let t=0;t<d;++t){const i=e[t],{[l]:n,[c]:s}=i;u=(i._stacks||(i._stacks={}))[c]=Di(o,h,n),u[r]=s,u._top=Ci(u,a,!0),u._bottom=Ci(u,a,!1)}}function Ti(t,e){const i=t.scales;return Object.keys(i).filter((t=>i[t].axis===e)).shift()}function Ai(t,e){const i=t.vScale&&t.vScale.axis;if(i){e=e||t._parsed;for(const n of e){const e=n._stacks;if(!e||void 0===e[i]||void 0===e[i][t.index])return;delete e[i][t.index]}}}const Li=t=>"reset"===t||"none"===t,Ri=(t,e)=>e?t:Object.assign({},t);class Ei{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.$context=void 0,this._syncList=[],this.initialize()}initialize(){const t=this,e=t._cachedMeta;t.configure(),t.linkScales(),e._stacked=Pi(e.vScale,e),t.addElements()}updateIndex(t){this.index!==t&&Ai(this._cachedMeta),this.index=t}linkScales(){const t=this,e=t.chart,i=t._cachedMeta,n=t.getDataset(),o=(t,e,i,n)=>"x"===t?e:"r"===t?n:i,s=i.xAxisID=K(n.xAxisID,Ti(e,"x")),a=i.yAxisID=K(n.yAxisID,Ti(e,"y")),r=i.rAxisID=K(n.rAxisID,Ti(e,"r")),l=i.indexAxis,c=i.iAxisID=o(l,s,a,r),h=i.vAxisID=o(l,a,s,r);i.xScale=t.getScaleForId(s),i.yScale=t.getScaleForId(a),i.rScale=t.getScaleForId(r),i.iScale=t.getScaleForId(c),i.vScale=t.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&he(this._data,this),t._stacked&&Ai(t)}_dataCheck(){const t=this,e=t.getDataset(),i=e.data||(e.data=[]),n=t._data;if(U(i))t._data=function(t){const e=Object.keys(t),i=new Array(e.length);let n,o,s;for(n=0,o=e.length;n<o;++n)s=e[n],i[n]={x:s,y:t[s]};return i}(i);else if(n!==i){if(n){he(n,t);const e=t._cachedMeta;Ai(e),e._parsed=[]}i&&Object.isExtensible(i)&&ce(i,t),t._syncList=[],t._data=i}}addElements(){const t=this,e=t._cachedMeta;t._dataCheck(),t.datasetElementType&&(e.dataset=new t.datasetElementType)}buildOrUpdateElements(t){const e=this,i=e._cachedMeta,n=e.getDataset();let o=!1;e._dataCheck();const s=i._stacked;i._stacked=Pi(i.vScale,i),i.stack!==n.stack&&(o=!0,Ai(i),i.stack=n.stack),e._resyncElements(t),(o||s!==i._stacked)&&Oi(e,i._parsed)}configure(){const t=this,e=t.chart.config,i=e.datasetScopeKeys(t._type),n=e.getOptionScopes(t.getDataset(),i,!0);t.options=e.createResolver(n,t.getContext()),t._parsing=t.options.parsing}parse(t,e){const i=this,{_cachedMeta:n,_data:o}=i,{iScale:s,_stacked:a}=n,r=s.axis;let l,c,h,d=0===t&&e===o.length||n._sorted,u=t>0&&n._parsed[t-1];if(!1===i._parsing)n._parsed=o,n._sorted=!0,h=o;else{h=Y(o[t])?i.parseArrayData(n,o,t,e):U(o[t])?i.parseObjectData(n,o,t,e):i.parsePrimitiveData(n,o,t,e);const s=()=>null===c[r]||u&&c[r]<u[r];for(l=0;l<e;++l)n._parsed[l+t]=c=h[l],d&&(s()&&(d=!1),u=c);n._sorted=d}a&&Oi(i,h)}parsePrimitiveData(t,e,i,n){const{iScale:o,vScale:s}=t,a=o.axis,r=s.axis,l=o.getLabels(),c=o===s,h=new Array(n);let d,u,f;for(d=0,u=n;d<u;++d)f=d+i,h[d]={[a]:c||o.parse(l[f],f),[r]:s.parse(e[f],f)};return h}parseArrayData(t,e,i,n){const{xScale:o,yScale:s}=t,a=new Array(n);let r,l,c,h;for(r=0,l=n;r<l;++r)c=r+i,h=e[c],a[r]={x:o.parse(h[0],c),y:s.parse(h[1],c)};return a}parseObjectData(t,e,i,n){const{xScale:o,yScale:s}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l=new Array(n);let c,h,d,u;for(c=0,h=n;c<h;++c)d=c+i,u=e[d],l[c]={x:o.parse(lt(u,a),d),y:s.parse(lt(u,r),d)};return l}getParsed(t){return this._cachedMeta._parsed[t]}getDataElement(t){return this._cachedMeta.data[t]}applyStack(t,e,i){const n=this.chart,o=this._cachedMeta,s=e[t.axis];return Si({keys:ki(n,!0),values:e._stacks[t.axis]},s,o.index,{mode:i})}updateRangeFromParsed(t,e,i,n){const o=i[e.axis];let s=null===o?NaN:o;const a=n&&i._stacks[e.axis];n&&a&&(n.values=a,t.min=Math.min(t.min,s),t.max=Math.max(t.max,s),s=Si(n,o,this._cachedMeta.index,{all:!0})),t.min=Math.min(t.min,s),t.max=Math.max(t.max,s)}getMinMax(t,e){const i=this,n=i._cachedMeta,o=n._parsed,s=n._sorted&&t===n.iScale,a=o.length,r=i._getOtherScale(t),l=e&&n._stacked&&{keys:ki(i.chart,!0),values:null},c={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:h,max:d}=function(t){const{min:e,max:i,minDefined:n,maxDefined:o}=t.getUserBounds();return{min:n?e:Number.NEGATIVE_INFINITY,max:o?i:Number.POSITIVE_INFINITY}}(r);let u,f,g,p;function m(){return g=o[u],f=g[t.axis],p=g[r.axis],!X(f)||h>p||d<p}for(u=0;u<a&&(m()||(i.updateRangeFromParsed(c,t,g,l),!s));++u);if(s)for(u=a-1;u>=0;--u)if(!m()){i.updateRangeFromParsed(c,t,g,l);break}return c}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let n,o,s;for(n=0,o=e.length;n<o;++n)s=e[n][t.axis],X(s)&&i.push(s);return i}getMaxOverflow(){return!1}getLabelAndValue(t){const e=this._cachedMeta,i=e.iScale,n=e.vScale,o=this.getParsed(t);return{label:i?""+i.getLabelForValue(o[i.axis]):"",value:n?""+n.getLabelForValue(o[n.axis]):""}}_update(t){const e=this,i=e._cachedMeta;e.configure(),e._cachedDataOpts={},e.update(t||"default"),i._clip=function(t){let e,i,n,o;return U(t)?(e=t.top,i=t.right,n=t.bottom,o=t.left):e=i=n=o=t,{top:e,right:i,bottom:n,left:o,disabled:!1===t}}(K(e.options.clip,function(t,e,i){if(!1===i)return!1;const n=Mi(t,i),o=Mi(e,i);return{top:o.end,right:n.end,bottom:o.start,left:n.start}}(i.xScale,i.yScale,e.getMaxOverflow())))}update(t){}draw(){const t=this,e=t._ctx,i=t.chart,n=t._cachedMeta,o=n.data||[],s=i.chartArea,a=[],r=t._drawStart||0,l=t._drawCount||o.length-r;let c;for(n.dataset&&n.dataset.draw(e,s,r,l),c=r;c<r+l;++c){const t=o[c];t.active?a.push(t):t.draw(e,s)}for(c=0;c<a.length;++c)a[c].draw(e,s)}getStyle(t,e){const i=e?"active":"default";return void 0===t&&this._cachedMeta.dataset?this.resolveDatasetElementOptions(i):this.resolveDataElementOptions(t||0,i)}getContext(t,e,i){const n=this,o=n.getDataset();let s;if(t>=0&&t<n._cachedMeta.data.length){const e=n._cachedMeta.data[t];s=e.$context||(e.$context=function(t,e,i){return Object.assign(Object.create(t),{active:!1,dataIndex:e,parsed:void 0,raw:void 0,element:i,index:e,mode:"default",type:"data"})}(n.getContext(),t,e)),s.parsed=n.getParsed(t),s.raw=o.data[t],s.index=s.dataIndex=t}else s=n.$context||(n.$context=function(t,e){return Object.assign(Object.create(t),{active:!1,dataset:void 0,datasetIndex:e,index:e,mode:"default",type:"dataset"})}(n.chart.getContext(),n.index)),s.dataset=o,s.index=s.datasetIndex=n.index;return s.active=!!e,s.mode=i,s}resolveDatasetElementOptions(t){return this._resolveElementOptions(this.datasetElementType.id,t)}resolveDataElementOptions(t,e){return this._resolveElementOptions(this.dataElementType.id,e,t)}_resolveElementOptions(t,e="default",i){const n=this,o="active"===e,s=n._cachedDataOpts,a=t+"-"+e,r=s[a],l=n.enableOptionSharing&&ht(i);if(r)return Ri(r,l);const c=n.chart.config,h=c.datasetElementScopeKeys(n._type,t),d=o?[`${t}Hover`,"hover",t,""]:[t,""],u=c.getOptionScopes(n.getDataset(),h),f=Object.keys(xt.elements[t]),g=c.resolveNamedOptions(u,f,(()=>n.getContext(i,o)),d);return g.$shared&&(g.$shared=l,s[a]=Object.freeze(Ri(g,l))),g}_resolveAnimations(t,e,i){const n=this,o=n.chart,s=n._cachedDataOpts,a=`animation-${e}`,r=s[a];if(r)return r;let l;if(!1!==o.options.animation){const o=n.chart.config,s=o.datasetAnimationScopeKeys(n._type,e),a=o.getOptionScopes(n.getDataset(),s);l=o.createResolver(a,n.getContext(t,i,e))}const c=new wi(o,l&&l.animations);return l&&l._cacheable&&(s[a]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Li(t)||this.chart._animationsDisabled}updateElement(t,e,i,n){Li(n)?Object.assign(t,i):this._resolveAnimations(e,n).update(t,i)}updateSharedOptions(t,e,i){t&&!Li(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,n){t.active=n;const o=this.getStyle(e,n);this._resolveAnimations(e,i,n).update(t,{options:!n&&this.getSharedOptions(o)||o})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this,i=e._data,n=e._cachedMeta.data;for(const[t,i,n]of e._syncList)e[t](i,n);e._syncList=[];const o=n.length,s=i.length,a=Math.min(s,o);a&&e.parse(0,a),s>o?e._insertElements(o,s-o,t):s<o&&e._removeElements(s,o-s)}_insertElements(t,e,i=!0){const n=this,o=n._cachedMeta,s=o.data,a=t+e;let r;const l=t=>{for(t.length+=e,r=t.length-1;r>=a;r--)t[r]=t[r-e]};for(l(s),r=t;r<a;++r)s[r]=new n.dataElementType;n._parsing&&l(o._parsed),n.parse(t,e),i&&n.updateElements(s,t,e,"reset")}updateElements(t,e,i,n){}_removeElements(t,e){const i=this._cachedMeta;if(this._parsing){const n=i._parsed.splice(t,e);i._stacked&&Ai(i,n)}i.data.splice(t,e)}_onDataPush(){const t=arguments.length;this._syncList.push(["_insertElements",this.getDataset().data.length-t,t])}_onDataPop(){this._syncList.push(["_removeElements",this._cachedMeta.data.length-1,1])}_onDataShift(){this._syncList.push(["_removeElements",0,1])}_onDataSplice(t,e){this._syncList.push(["_removeElements",t,e]),this._syncList.push(["_insertElements",t,arguments.length-2])}_onDataUnshift(){this._syncList.push(["_insertElements",0,arguments.length])}}Ei.defaults={},Ei.prototype.datasetElementType=null,Ei.prototype.dataElementType=null;class zi{constructor(){this.x=void 0,this.y=void 0,this.active=!1,this.options=void 0,this.$animations=void 0}tooltipPosition(t){const{x:e,y:i}=this.getProps(["x","y"],t);return{x:e,y:i}}hasValue(){return Tt(this.x)&&Tt(this.y)}getProps(t,e){const i=this,n=this.$animations;if(!e||!n)return i;const o={};return t.forEach((t=>{o[t]=n[t]&&n[t].active()?n[t]._to:i[t]})),o}}zi.defaults={},zi.defaultRoutes=void 0;const Ii=new Map;function Fi(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let n=Ii.get(i);return n||(n=new Intl.NumberFormat(t,e),Ii.set(i,n)),n}(e,i).format(t)}const Bi={values:t=>Y(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const n=this.chart.options.locale;let o,s=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(o="scientific"),s=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=Pt(Math.abs(s)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:o,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),Fi(t,n,l)},logarithmic(t,e,i){if(0===t)return"0";const n=t/Math.pow(10,Math.floor(Pt(t)));return 1===n||2===n||5===n?Bi.numeric.call(this,t,e,i):""}};var Vi={formatters:Bi};function Wi(t,e){const i=t.options.ticks,n=i.maxTicksLimit||function(t){const e=t.options.offset,i=t._tickSize(),n=t._length/i+(e?0:1),o=t._maxLength/i;return Math.floor(Math.min(n,o))}(t),o=i.major.enabled?function(t){const e=[];let i,n;for(i=0,n=t.length;i<n;i++)t[i].major&&e.push(i);return e}(e):[],s=o.length,a=o[0],r=o[s-1],l=[];if(s>n)return function(t,e,i,n){let o,s=0,a=i[0];for(n=Math.ceil(n),o=0;o<t.length;o++)o===a&&(e.push(t[o]),s++,a=i[s*n])}(e,l,o,s/n),l;const c=function(t,e,i){const n=function(t){const e=t.length;let i,n;if(e<2)return!1;for(n=t[0],i=1;i<e;++i)if(t[i]-t[i-1]!==n)return!1;return n}(t),o=e.length/i;if(!n)return Math.max(o,1);const s=Ot(n);for(let t=0,e=s.length-1;t<e;t++){const e=s[t];if(e>o)return e}return Math.max(o,1)}(o,e,n);if(s>0){let t,i;const n=s>1?Math.round((r-a)/(s-1)):null;for(Ni(e,l,c,$(n)?0:a-n,a),t=0,i=s-1;t<i;t++)Ni(e,l,c,o[t],o[t+1]);return Ni(e,l,c,r,$(n)?e.length:r+n),l}return Ni(e,l,c),l}function Ni(t,e,i,n,o){const s=K(n,0),a=Math.min(K(o,t.length),t.length);let r,l,c,h=0;for(i=Math.ceil(i),o&&(r=o-n,i=r/Math.floor(r/i)),c=s;c<0;)h++,c=Math.round(s+h*i);for(l=Math.max(s,0);l<a;l++)l===c&&(e.push(t[l]),h++,c=Math.round(s+h*i))}xt.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Vi.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),xt.route("scale.ticks","color","","color"),xt.route("scale.grid","color","","borderColor"),xt.route("scale.grid","borderColor","","borderColor"),xt.route("scale.title","color","","color"),xt.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t}),xt.describe("scales",{_fallback:"scale"}),xt.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t});const Hi=(t,e,i)=>"top"===e||"left"===e?t[e]+i:t[e]-i;function ji(t,e){const i=[],n=t.length/e,o=t.length;let s=0;for(;s<o;s+=n)i.push(t[Math.floor(s)]);return i}function $i(t,e,i){const n=t.ticks.length,o=Math.min(e,n-1),s=t._startPixel,a=t._endPixel,r=1e-6;let l,c=t.getPixelForTick(o);if(!(i&&(l=1===n?Math.max(c-s,a-c):0===e?(t.getPixelForTick(1)-c)/2:(c-t.getPixelForTick(o-1))/2,c+=o<e?l:-l,c<s-r||c>a+r)))return c}function Yi(t){return t.drawTicks?t.tickLength:0}function Ui(t,e){if(!t.display)return 0;const i=Be(t.font,e),n=Fe(t.padding);return(Y(t.text)?t.text.length:1)*i.lineHeight+n.height}function Xi(t,e,i){let o=n(t);return(i&&"right"!==e||!i&&"right"===e)&&(o=(t=>"left"===t?"right":"right"===t?"left":t)(o)),o}class qi extends zi{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){const e=this;e.options=t.setContext(e.getContext()),e.axis=t.axis,e._userMin=e.parse(t.min),e._userMax=e.parse(t.max),e._suggestedMin=e.parse(t.suggestedMin),e._suggestedMax=e.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:n}=this;return t=q(t,Number.POSITIVE_INFINITY),e=q(e,Number.NEGATIVE_INFINITY),i=q(i,Number.POSITIVE_INFINITY),n=q(n,Number.NEGATIVE_INFINITY),{min:q(t,i),max:q(e,n),minDefined:X(t),maxDefined:X(e)}}getMinMax(t){const e=this;let i,{min:n,max:o,minDefined:s,maxDefined:a}=e.getUserBounds();if(s&&a)return{min:n,max:o};const r=e.getMatchingVisibleMetas();for(let l=0,c=r.length;l<c;++l)i=r[l].controller.getMinMax(e,t),s||(n=Math.min(n,i.min)),a||(o=Math.max(o,i.max));return{min:q(n,q(o,n)),max:q(o,q(n,o))}}getPadding(){const t=this;return{left:t.paddingLeft||0,top:t.paddingTop||0,right:t.paddingRight||0,bottom:t.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){Q(this.options.beforeUpdate,[this])}update(t,e,i){const n=this,o=n.options.ticks,s=o.sampleSize;n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),n.ticks=null,n._labelSizes=null,n._gridLineItems=null,n._labelItems=null,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n._maxLength=n.isHorizontal()?n.width+i.left+i.right:n.height+i.top+i.bottom,n._dataLimitsCached||(n.beforeDataLimits(),n.determineDataLimits(),n.afterDataLimits(),n._range=We(n,n.options.grace),n._dataLimitsCached=!0),n.beforeBuildTicks(),n.ticks=n.buildTicks()||[],n.afterBuildTicks();const a=s<n.ticks.length;n._convertTicksToLabels(a?ji(n.ticks,s):n.ticks),n.configure(),n.beforeCalculateLabelRotation(),n.calculateLabelRotation(),n.afterCalculateLabelRotation(),o.display&&(o.autoSkip||"auto"===o.source)&&(n.ticks=Wi(n,n.ticks),n._labelSizes=null),a&&n._convertTicksToLabels(n.ticks),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate()}configure(){const t=this;let e,i,n=t.options.reverse;t.isHorizontal()?(e=t.left,i=t.right):(e=t.top,i=t.bottom,n=!n),t._startPixel=e,t._endPixel=i,t._reversePixels=n,t._length=i-e,t._alignToPixels=t.options.alignToPixels}afterUpdate(){Q(this.options.afterUpdate,[this])}beforeSetDimensions(){Q(this.options.beforeSetDimensions,[this])}setDimensions(){const t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0}afterSetDimensions(){Q(this.options.afterSetDimensions,[this])}_callHooks(t){const e=this;e.chart.notifyPlugins(t,e.getContext()),Q(e.options[t],[e])}beforeDataLimits(){this._callHooks("beforeDataLimits")}determineDataLimits(){}afterDataLimits(){this._callHooks("afterDataLimits")}beforeBuildTicks(){this._callHooks("beforeBuildTicks")}buildTicks(){return[]}afterBuildTicks(){this._callHooks("afterBuildTicks")}beforeTickToLabelConversion(){Q(this.options.beforeTickToLabelConversion,[this])}generateTickLabels(t){const e=this,i=e.options.ticks;let n,o,s;for(n=0,o=t.length;n<o;n++)s=t[n],s.label=Q(i.callback,[s.value,n,t],e)}afterTickToLabelConversion(){Q(this.options.afterTickToLabelConversion,[this])}beforeCalculateLabelRotation(){Q(this.options.beforeCalculateLabelRotation,[this])}calculateLabelRotation(){const t=this,e=t.options,i=e.ticks,n=t.ticks.length,o=i.minRotation||0,s=i.maxRotation;let a,r,l,c=o;if(!t._isVisible()||!i.display||o>=s||n<=1||!t.isHorizontal())return void(t.labelRotation=o);const h=t._getLabelSizes(),d=h.widest.width,u=h.highest.height,f=Ht(t.chart.width-d,0,t.maxWidth);a=e.offset?t.maxWidth/n:f/(n-1),d+6>a&&(a=f/(n-(e.offset?.5:1)),r=t.maxHeight-Yi(e.grid)-i.padding-Ui(e.title,t.chart.options.font),l=Math.sqrt(d*d+u*u),c=zt(Math.min(Math.asin(Math.min((h.highest.height+6)/a,1)),Math.asin(Math.min(r/l,1))-Math.asin(u/l))),c=Math.max(o,Math.min(s,c))),t.labelRotation=c}afterCalculateLabelRotation(){Q(this.options.afterCalculateLabelRotation,[this])}beforeFit(){Q(this.options.beforeFit,[this])}fit(){const t=this,e={width:0,height:0},{chart:i,options:{ticks:n,title:o,grid:s}}=t,a=t._isVisible(),r=t.isHorizontal();if(a){const a=Ui(o,i.options.font);if(r?(e.width=t.maxWidth,e.height=Yi(s)+a):(e.height=t.maxHeight,e.width=Yi(s)+a),n.display&&t.ticks.length){const{first:i,last:o,widest:s,highest:a}=t._getLabelSizes(),l=2*n.padding,c=Et(t.labelRotation),h=Math.cos(c),d=Math.sin(c);if(r){const i=n.mirror?0:d*s.width+h*a.height;e.height=Math.min(t.maxHeight,e.height+i+l)}else{const i=n.mirror?0:h*s.width+d*a.height;e.width=Math.min(t.maxWidth,e.width+i+l)}t._calculatePadding(i,o,d,h)}}t._handleMargins(),r?(t.width=t._length=i.width-t._margins.left-t._margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=i.height-t._margins.top-t._margins.bottom)}_calculatePadding(t,e,i,n){const o=this,{ticks:{align:s,padding:a},position:r}=o.options,l=0!==o.labelRotation,c="top"!==r&&"x"===o.axis;if(o.isHorizontal()){const r=o.getPixelForTick(0)-o.left,h=o.right-o.getPixelForTick(o.ticks.length-1);let d=0,u=0;l?c?(d=n*t.width,u=i*e.height):(d=i*t.height,u=n*e.width):"start"===s?u=e.width:"end"===s?d=t.width:(d=t.width/2,u=e.width/2),o.paddingLeft=Math.max((d-r+a)*o.width/(o.width-r),0),o.paddingRight=Math.max((u-h+a)*o.width/(o.width-h),0)}else{let i=e.height/2,n=t.height/2;"start"===s?(i=0,n=t.height):"end"===s&&(i=e.height,n=0),o.paddingTop=i+a,o.paddingBottom=n+a}}_handleMargins(){const t=this;t._margins&&(t._margins.left=Math.max(t.paddingLeft,t._margins.left),t._margins.top=Math.max(t.paddingTop,t._margins.top),t._margins.right=Math.max(t.paddingRight,t._margins.right),t._margins.bottom=Math.max(t.paddingBottom,t._margins.bottom))}afterFit(){Q(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){const e=this;let i,n;for(e.beforeTickToLabelConversion(),e.generateTickLabels(t),i=0,n=t.length;i<n;i++)$(t[i].label)&&(t.splice(i,1),n--,i--);e.afterTickToLabelConversion()}_getLabelSizes(){const t=this;let e=t._labelSizes;if(!e){const i=t.options.ticks.sampleSize;let n=t.ticks;i<n.length&&(n=ji(n,i)),t._labelSizes=e=t._computeLabelSizes(n,n.length)}return e}_computeLabelSizes(t,e){const{ctx:i,_longestTextCache:n}=this,o=[],s=[];let a,r,l,c,h,d,u,f,g,p,m,x=0,b=0;for(a=0;a<e;++a){if(c=t[a].label,h=this._resolveTickFontOptions(a),i.font=d=h.string,u=n[d]=n[d]||{data:{},gc:[]},f=h.lineHeight,g=p=0,$(c)||Y(c)){if(Y(c))for(r=0,l=c.length;r<l;++r)m=c[r],$(m)||Y(m)||(g=Yt(i,u.data,u.gc,g,m),p+=f)}else g=Yt(i,u.data,u.gc,g,c),p=f;o.push(g),s.push(p),x=Math.max(g,x),b=Math.max(p,b)}!function(t,e){J(t,(t=>{const i=t.gc,n=i.length/2;let o;if(n>e){for(o=0;o<n;++o)delete t.data[i[o]];i.splice(0,n)}}))}(n,e);const _=o.indexOf(x),y=s.indexOf(b),v=t=>({width:o[t]||0,height:s[t]||0});return{first:v(0),last:v(e-1),widest:v(_),highest:v(y),widths:o,heights:s}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){const e=this;e._reversePixels&&(t=1-t);const i=e._startPixel+t*e._length;return jt(e._alignToPixels?Xt(e.chart,i,0):i)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this,i=e.ticks||[];if(t>=0&&t<i.length){const n=i[t];return n.$context||(n.$context=function(t,e,i){return Object.assign(Object.create(t),{tick:i,index:e,type:"tick"})}(e.getContext(),t,n))}return e.$context||(e.$context=(n=e.chart.getContext(),o=e,Object.assign(Object.create(n),{scale:o,type:"scale"})));var n,o}_tickSize(){const t=this,e=t.options.ticks,i=Et(t.labelRotation),n=Math.abs(Math.cos(i)),o=Math.abs(Math.sin(i)),s=t._getLabelSizes(),a=e.autoSkipPadding||0,r=s?s.widest.width+a:0,l=s?s.highest.height+a:0;return t.isHorizontal()?l*n>r*o?r/n:l/o:l*o<r*n?l/n:r/o}_isVisible(){const t=this.options.display;return"auto"!==t?!!t:this.getMatchingVisibleMetas().length>0}_computeGridLineItems(t){const e=this,i=e.axis,n=e.chart,o=e.options,{grid:s,position:a}=o,r=s.offset,l=e.isHorizontal(),c=e.ticks.length+(r?1:0),h=Yi(s),d=[],u=s.setContext(e.getContext()),f=u.drawBorder?u.borderWidth:0,g=f/2,p=function(t){return Xt(n,t,f)};let m,x,b,_,y,v,w,M,k,S,P,D;if("top"===a)m=p(e.bottom),v=e.bottom-h,M=m-g,S=p(t.top)+g,D=t.bottom;else if("bottom"===a)m=p(e.top),S=t.top,D=p(t.bottom)-g,v=m+g,M=e.top+h;else if("left"===a)m=p(e.right),y=e.right-h,w=m-g,k=p(t.left)+g,P=t.right;else if("right"===a)m=p(e.left),k=t.left,P=p(t.right)-g,y=m+g,w=e.left+h;else if("x"===i){if("center"===a)m=p((t.top+t.bottom)/2+.5);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}S=t.top,D=t.bottom,v=m+g,M=v+h}else if("y"===i){if("center"===a)m=p((t.left+t.right)/2);else if(U(a)){const t=Object.keys(a)[0],i=a[t];m=p(e.chart.scales[t].getPixelForValue(i))}y=m-g,w=y-h,k=t.left,P=t.right}const C=K(o.ticks.maxTicksLimit,c),O=Math.max(1,Math.ceil(c/C));for(x=0;x<c;x+=O){const t=s.setContext(e.getContext(x)),i=t.lineWidth,o=t.color,a=s.borderDash||[],c=t.borderDashOffset,h=t.tickWidth,u=t.tickColor,f=t.tickBorderDash||[],g=t.tickBorderDashOffset;b=$i(e,x,r),void 0!==b&&(_=Xt(n,b,i),l?y=w=k=P=_:v=M=S=D=_,d.push({tx1:y,ty1:v,tx2:w,ty2:M,x1:k,y1:S,x2:P,y2:D,width:i,color:o,borderDash:a,borderDashOffset:c,tickWidth:h,tickColor:u,tickBorderDash:f,tickBorderDashOffset:g}))}return e._ticksLength=c,e._borderValue=m,d}_computeLabelItems(t){const e=this,i=e.axis,n=e.options,{position:o,ticks:s}=n,a=e.isHorizontal(),r=e.ticks,{align:l,crossAlign:c,padding:h,mirror:d}=s,u=Yi(n.grid),f=u+h,g=d?-h:f,p=-Et(e.labelRotation),m=[];let x,b,_,y,v,w,M,k,S,P,D,C,O="middle";if("top"===o)w=e.bottom-g,M=e._getXAxisLabelAlignment();else if("bottom"===o)w=e.top+g,M=e._getXAxisLabelAlignment();else if("left"===o){const t=e._getYAxisLabelAlignment(u);M=t.textAlign,v=t.x}else if("right"===o){const t=e._getYAxisLabelAlignment(u);M=t.textAlign,v=t.x}else if("x"===i){if("center"===o)w=(t.top+t.bottom)/2+f;else if(U(o)){const t=Object.keys(o)[0],i=o[t];w=e.chart.scales[t].getPixelForValue(i)+f}M=e._getXAxisLabelAlignment()}else if("y"===i){if("center"===o)v=(t.left+t.right)/2-f;else if(U(o)){const t=Object.keys(o)[0],i=o[t];v=e.chart.scales[t].getPixelForValue(i)}M=e._getYAxisLabelAlignment(u).textAlign}"y"===i&&("start"===l?O="top":"end"===l&&(O="bottom"));const T=e._getLabelSizes();for(x=0,b=r.length;x<b;++x){_=r[x],y=_.label;const t=s.setContext(e.getContext(x));k=e.getPixelForTick(x)+s.labelOffset,S=e._resolveTickFontOptions(x),P=S.lineHeight,D=Y(y)?y.length:1;const i=D/2,n=t.color,l=t.textStrokeColor,h=t.textStrokeWidth;let u;if(a?(v=k,C="top"===o?"near"===c||0!==p?-D*P+P/2:"center"===c?-T.highest.height/2-i*P+P:-T.highest.height+P/2:"near"===c||0!==p?P/2:"center"===c?T.highest.height/2-i*P:T.highest.height-D*P,d&&(C*=-1)):(w=k,C=(1-D)*P/2),t.showLabelBackdrop){const e=Fe(t.backdropPadding),i=T.heights[x],n=T.widths[x];let o=w+C-e.top,s=v-e.left;switch(O){case"middle":o-=i/2;break;case"bottom":o-=i}switch(M){case"center":s-=n/2;break;case"right":s-=n}u={left:s,top:o,width:n+e.width,height:i+e.height,color:t.backdropColor}}m.push({rotation:p,label:y,font:S,color:n,strokeColor:l,strokeWidth:h,textOffset:C,textAlign:M,textBaseline:O,translation:[v,w],backdrop:u})}return m}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-Et(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align&&(i="right"),i}_getYAxisLabelAlignment(t){const e=this,{position:i,ticks:{crossAlign:n,mirror:o,padding:s}}=e.options,a=t+s,r=e._getLabelSizes().widest.width;let l,c;return"left"===i?o?(l="left",c=e.right+s):(c=e.right-a,"near"===n?l="right":"center"===n?(l="center",c-=r/2):(l="left",c=e.left)):"right"===i?o?(l="right",c=e.left+s):(c=e.left+a,"near"===n?l="left":"center"===n?(l="center",c+=r/2):(l="right",c=e.right)):l="right",{textAlign:l,x:c}}_computeLabelArea(){const t=this;if(t.options.ticks.mirror)return;const e=t.chart,i=t.options.position;return"left"===i||"right"===i?{top:0,left:t.left,bottom:e.height,right:t.right}:"top"===i||"bottom"===i?{top:t.top,left:0,bottom:t.bottom,right:e.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:n,width:o,height:s}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,n,o,s),t.restore())}getLineWidthForValue(t){const e=this,i=e.options.grid;if(!e._isVisible()||!i.display)return 0;const n=e.ticks.findIndex((e=>e.value===t));if(n>=0){return i.setContext(e.getContext(n)).lineWidth}return 0}drawGrid(t){const e=this,i=e.options.grid,n=e.ctx,o=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));let s,a;const r=(t,e,i)=>{i.width&&i.color&&(n.save(),n.lineWidth=i.width,n.strokeStyle=i.color,n.setLineDash(i.borderDash||[]),n.lineDashOffset=i.borderDashOffset,n.beginPath(),n.moveTo(t.x,t.y),n.lineTo(e.x,e.y),n.stroke(),n.restore())};if(i.display)for(s=0,a=o.length;s<a;++s){const t=o[s];i.drawOnChartArea&&r({x:t.x1,y:t.y1},{x:t.x2,y:t.y2},t),i.drawTicks&&r({x:t.tx1,y:t.ty1},{x:t.tx2,y:t.ty2},{color:t.tickColor,width:t.tickWidth,borderDash:t.tickBorderDash,borderDashOffset:t.tickBorderDashOffset})}}drawBorder(){const t=this,{chart:e,ctx:i,options:{grid:n}}=t,o=n.setContext(t.getContext()),s=n.drawBorder?o.borderWidth:0;if(!s)return;const a=n.setContext(t.getContext(0)).lineWidth,r=t._borderValue;let l,c,h,d;t.isHorizontal()?(l=Xt(e,t.left,s)-s/2,c=Xt(e,t.right,a)+a/2,h=d=r):(h=Xt(e,t.top,s)-s/2,d=Xt(e,t.bottom,a)+a/2,l=c=r),i.save(),i.lineWidth=o.borderWidth,i.strokeStyle=o.borderColor,i.beginPath(),i.moveTo(l,h),i.lineTo(c,d),i.stroke(),i.restore()}drawLabels(t){const e=this;if(!e.options.ticks.display)return;const i=e.ctx,n=e._computeLabelArea();n&&Zt(i,n);const o=e._labelItems||(e._labelItems=e._computeLabelItems(t));let s,a;for(s=0,a=o.length;s<a;++s){const t=o[s],e=t.font,n=t.label;t.backdrop&&(i.fillStyle=t.backdrop.color,i.fillRect(t.backdrop.left,t.backdrop.top,t.backdrop.width,t.backdrop.height)),ee(i,n,0,t.textOffset,e,t)}n&&Qt(i)}drawTitle(){const{ctx:t,options:{position:e,title:i,reverse:n}}=this;if(!i.display)return;const s=Be(i.font),a=Fe(i.padding),r=i.align;let l=s.lineHeight/2;"bottom"===e?(l+=a.bottom,Y(i.text)&&(l+=s.lineHeight*(i.text.length-1))):l+=a.top;const{titleX:c,titleY:h,maxWidth:d,rotation:u}=function(t,e,i,n){const{top:s,left:a,bottom:r,right:l}=t;let c,h,d,u=0;return t.isHorizontal()?(h=o(n,a,l),d=Hi(t,i,e),c=l-a):(h=Hi(t,i,e),d=o(n,r,s),u="left"===i?-Mt:Mt),{titleX:h,titleY:d,maxWidth:c,rotation:u}}(this,l,e,r);ee(t,i.text,0,0,s,{color:i.color,maxWidth:d,rotation:u,textAlign:Xi(r,e,n),textBaseline:"middle",translation:[c,h]})}draw(t){const e=this;e._isVisible()&&(e.drawBackground(),e.drawGrid(t),e.drawBorder(),e.drawTitle(),e.drawLabels(t))}_layers(){const t=this,e=t.options,i=e.ticks&&e.ticks.z||0,n=e.grid&&e.grid.z||0;return t._isVisible()&&t.draw===qi.prototype.draw?[{z:n,draw(e){t.drawBackground(),t.drawGrid(e),t.drawTitle()}},{z:n+1,draw(){t.drawBorder()}},{z:i,draw(e){t.drawLabels(e)}}]:[{z:i,draw(e){t.draw(e)}}]}getMatchingVisibleMetas(t){const e=this,i=e.chart.getSortedVisibleDatasetMetas(),n=e.axis+"AxisID",o=[];let s,a;for(s=0,a=i.length;s<a;++s){const a=i[s];a[n]!==e.id||t&&a.type!==t||o.push(a)}return o}_resolveTickFontOptions(t){return Be(this.options.ticks.setContext(this.getContext(t)).font)}_maxDigits(){const t=this,e=t._resolveTickFontOptions(0).lineHeight;return(t.isHorizontal()?t.width:t.height)/e}}function Ki(t,e=[""],i=t,n,o=(()=>t[0])){ht(n)||(n=rn("_fallback",t));const s={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:i,_fallback:n,_getTarget:o,override:o=>Ki([o,...t],e,i,n)};return new Proxy(s,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,n)=>tn(i,n,(()=>function(t,e,i,n){let o;for(const s of e)if(o=rn(Qi(s,t),i),ht(o))return Ji(t,o)?sn(i,n,t,o):o}(n,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ln(t).includes(e),ownKeys:t=>ln(t),set:(t,e,i)=>((t._storage||(t._storage=o()))[e]=i,delete t[e],delete t._keys,!0)})}function Gi(t,e,i,n){const o={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Zi(t,n),setContext:e=>Gi(t,e,i,n),override:o=>Gi(t.override(o),e,i,n)};return new Proxy(o,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>tn(t,e,(()=>function(t,e,i){const{_proxy:n,_context:o,_subProxy:s,_descriptors:a}=t;let r=n[e];dt(r)&&a.isScriptable(e)&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t),e=e(s,a||n),r.delete(t),U(e)&&(e=sn(o._scopes,o,t,e));return e}(e,r,t,i));Y(r)&&r.length&&(r=function(t,e,i,n){const{_proxy:o,_context:s,_subProxy:a,_descriptors:r}=i;if(ht(s.index)&&n(t))e=e[s.index%e.length];else if(U(e[0])){const i=e,n=o._scopes.filter((t=>t!==i));e=[];for(const l of i){const i=sn(n,o,t,l);e.push(Gi(i,s,a&&a[t],r))}}return e}(e,r,t,a.isIndexable));Ji(e,r)&&(r=Gi(r,o,s&&s[e],a));return r}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,n)=>(t[i]=n,delete e[i],!0)})}function Zi(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:n=e.indexable,_allKeys:o=e.allKeys}=t;return{allKeys:o,scriptable:i,indexable:n,isScriptable:dt(i)?i:()=>i,isIndexable:dt(n)?n:()=>n}}const Qi=(t,e)=>t?t+ct(e):e,Ji=(t,e)=>U(e)&&"adapters"!==t;function tn(t,e,i){let n=t[e];return ht(n)||(n=i(),ht(n)&&(t[e]=n)),n}function en(t,e,i){return dt(t)?t(e,i):t}const nn=(t,e)=>!0===t?e:"string"==typeof t?lt(e,t):void 0;function on(t,e,i,n){for(const o of e){const e=nn(i,o);if(e){t.add(e);const o=en(e._fallback,i,e);if(ht(o)&&o!==i&&o!==n)return o}else if(!1===e&&ht(n)&&i!==n)return null}return!1}function sn(t,e,i,n){const o=e._rootScopes,s=en(e._fallback,i,n),a=[...t,...o],r=new Set;r.add(n);let l=an(r,a,i,s||i);return null!==l&&((!ht(s)||s===i||(l=an(r,a,s,l),null!==l))&&Ki(Array.from(r),[""],o,s,(()=>function(t,e,i){const n=t._getTarget();e in n||(n[e]={});const o=n[e];if(Y(o)&&U(i))return i;return o}(e,i,n))))}function an(t,e,i,n){for(;i;)i=on(t,e,i,n);return i}function rn(t,e){for(const i of e){if(!i)continue;const e=i[t];if(ht(e))return e}}function ln(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}const cn=Number.EPSILON||1e-14,hn=(t,e)=>e<t.length&&!t[e].skip&&t[e],dn=t=>"x"===t?"y":"x";function un(t,e,i,n){const o=t.skip?e:t,s=e,a=i.skip?e:i,r=Bt(s,o),l=Bt(a,s);let c=r/(r+l),h=l/(r+l);c=isNaN(c)?0:c,h=isNaN(h)?0:h;const d=n*c,u=n*h;return{previous:{x:s.x-d*(a.x-o.x),y:s.y-d*(a.y-o.y)},next:{x:s.x+u*(a.x-o.x),y:s.y+u*(a.y-o.y)}}}function fn(t,e="x"){const i=dn(e),n=t.length,o=Array(n).fill(0),s=Array(n);let a,r,l,c=hn(t,0);for(a=0;a<n;++a)if(r=l,l=c,c=hn(t,a+1),l){if(c){const t=c[e]-l[e];o[a]=0!==t?(c[i]-l[i])/t:0}s[a]=r?c?Dt(o[a-1])!==Dt(o[a])?0:(o[a-1]+o[a])/2:o[a-1]:o[a]}!function(t,e,i){const n=t.length;let o,s,a,r,l,c=hn(t,0);for(let h=0;h<n-1;++h)l=c,c=hn(t,h+1),l&&c&&(At(e[h],0,cn)?i[h]=i[h+1]=0:(o=i[h]/e[h],s=i[h+1]/e[h],r=Math.pow(o,2)+Math.pow(s,2),r<=9||(a=3/Math.sqrt(r),i[h]=o*a*e[h],i[h+1]=s*a*e[h])))}(t,o,s),function(t,e,i="x"){const n=dn(i),o=t.length;let s,a,r,l=hn(t,0);for(let c=0;c<o;++c){if(a=r,r=l,l=hn(t,c+1),!r)continue;const o=r[i],h=r[n];a&&(s=(o-a[i])/3,r[`cp1${i}`]=o-s,r[`cp1${n}`]=h-s*e[c]),l&&(s=(l[i]-o)/3,r[`cp2${i}`]=o+s,r[`cp2${n}`]=h+s*e[c])}}(t,s,e)}function gn(t,e,i){return Math.max(Math.min(t,i),e)}function pn(t,e,i,n,o){let s,a,r,l;if(e.spanGaps&&(t=t.filter((t=>!t.skip))),"monotone"===e.cubicInterpolationMode)fn(t,o);else{let i=n?t[t.length-1]:t[0];for(s=0,a=t.length;s<a;++s)r=t[s],l=un(i,r,t[Math.min(s+1,a-(n?0:1))%a],e.tension),r.cp1x=l.previous.x,r.cp1y=l.previous.y,r.cp2x=l.next.x,r.cp2y=l.next.y,i=r}e.capBezierPoints&&function(t,e){let i,n,o,s,a,r=Gt(t[0],e);for(i=0,n=t.length;i<n;++i)a=s,s=r,r=i<n-1&&Gt(t[i+1],e),s&&(o=t[i],a&&(o.cp1x=gn(o.cp1x,e.left,e.right),o.cp1y=gn(o.cp1y,e.top,e.bottom)),r&&(o.cp2x=gn(o.cp2x,e.left,e.right),o.cp2y=gn(o.cp2y,e.top,e.bottom)))}(t,i)}function mn(t,e,i,n){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function xn(t,e,i,n){return{x:t.x+i*(e.x-t.x),y:"middle"===n?i<.5?t.y:e.y:"after"===n?i<1?t.y:e.y:i>0?e.y:t.y}}function bn(t,e,i,n){const o={x:t.cp2x,y:t.cp2y},s={x:e.cp1x,y:e.cp1y},a=mn(t,o,i),r=mn(o,s,i),l=mn(s,e,i),c=mn(a,r,i),h=mn(r,l,i);return mn(c,h,i)}function _n(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function yn(t,e){let i,n;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,n=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=n)}function vn(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function wn(t){return"angle"===t?{between:Nt,compare:Vt,normalize:Wt}:{between:(t,e,i)=>t>=Math.min(e,i)&&t<=Math.max(i,e),compare:(t,e)=>t-e,normalize:t=>t}}function Mn({start:t,end:e,count:i,loop:n,style:o}){return{start:t%i,end:e%i,loop:n&&(e-t+1)%i==0,style:o}}function kn(t,e,i){if(!i)return[t];const{property:n,start:o,end:s}=i,a=e.length,{compare:r,between:l,normalize:c}=wn(n),{start:h,end:d,loop:u,style:f}=function(t,e,i){const{property:n,start:o,end:s}=i,{between:a,normalize:r}=wn(n),l=e.length;let c,h,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,c=0,h=l;c<h&&a(r(e[d%l][n]),o,s);++c)d--,u--;d%=l,u%=l}return u<d&&(u+=l),{start:d,end:u,loop:f,style:t.style}}(t,e,i),g=[];let p,m,x,b=!1,_=null;const y=()=>b||l(o,x,p)&&0!==r(o,x),v=()=>!b||0===r(s,p)||l(s,x,p);for(let t=h,i=h;t<=d;++t)m=e[t%a],m.skip||(p=c(m[n]),p!==x&&(b=l(p,o,s),null===_&&y()&&(_=0===r(p,o)?t:i),null!==_&&v()&&(g.push(Mn({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(Mn({start:_,end:d,loop:u,count:a,style:f})),g}function Sn(t,e){const i=[],n=t.segments;for(let o=0;o<n.length;o++){const s=kn(n[o],t.points,e);s.length&&i.push(...s)}return i}function Pn(t,e){const i=t.points,n=t.options.spanGaps,o=i.length;if(!o)return[];const s=!!t._loop,{start:a,end:r}=function(t,e,i,n){let o=0,s=e-1;if(i&&!n)for(;o<e&&!t[o].skip;)o++;for(;o<e&&t[o].skip;)o++;for(o%=e,i&&(s+=o);s>o&&t[s%e].skip;)s--;return s%=e,{start:o,end:s}}(i,o,s,n);if(!0===n)return Dn([{start:a,end:r,loop:s}],i,e);return Dn(function(t,e,i,n){const o=t.length,s=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%o];i.skip||i.stop?l.skip||(n=!1,s.push({start:e%o,end:(a-1)%o,loop:n}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&s.push({start:e%o,end:r%o,loop:n}),s}(i,a,r<a?r+o:r,!!t._fullLoop&&0===a&&r===o-1),i,e)}function Dn(t,e,i){return i&&i.setContext&&e?function(t,e,i){const n=e.length,o=[];let s=t[0].start,a=s;for(const r of t){let t,l,c=e[s%n];for(a=s+1;a<=r.end;a++){const h=e[a%n];l=Cn(i.setContext({type:"segment",p0:c,p1:h})),On(l,t)&&(o.push({start:s,end:a-1,loop:r.loop,style:t}),t=l,s=a-1),c=h,t=l}s<a-1&&(o.push({start:s,end:a-1,loop:r.loop,style:l}),s=a-1)}return o}(t,e,i):t}function Cn(t){return{backgroundColor:t.backgroundColor,borderCapStyle:t.borderCapStyle,borderDash:t.borderDash,borderDashOffset:t.borderDashOffset,borderJoinStyle:t.borderJoinStyle,borderWidth:t.borderWidth,borderColor:t.borderColor}}function On(t,e){return e&&JSON.stringify(t)!==JSON.stringify(e)}var Tn=Object.freeze({__proto__:null,easingEffects:xi,color:W,getHoverColor:N,noop:H,uid:j,isNullOrUndef:$,isArray:Y,isObject:U,isFinite:X,finiteOrDefault:q,valueOrDefault:K,toPercentage:G,toDimension:Z,callback:Q,each:J,_elementsEqual:tt,clone:et,_merger:nt,merge:ot,mergeIf:st,_mergerIf:at,_deprecated:function(t,e,i,n){void 0!==e&&console.warn(t+': "'+i+'" is deprecated. Please use "'+n+'" instead')},resolveObjectKey:lt,_capitalize:ct,defined:ht,isFunction:dt,setsEqual:ut,toFontString:$t,_measureText:Yt,_longestText:Ut,_alignPixel:Xt,clearCanvas:qt,drawPoint:Kt,_isPointInArea:Gt,clipArea:Zt,unclipArea:Qt,_steppedLineTo:Jt,_bezierCurveTo:te,renderText:ee,addRoundedRectPath:ne,_lookup:oe,_lookupByKey:se,_rlookupByKey:ae,_filterBetween:re,listenArrayEvents:ce,unlistenArrayEvents:he,_arrayUnique:de,_createResolver:Ki,_attachContext:Gi,_descriptors:Zi,splineCurve:un,splineCurveMonotone:fn,_updateBezierControlPoints:pn,_getParentNode:ue,getStyle:pe,getRelativePosition:be,getMaximumSize:ye,retinaScale:ve,supportsEventListenerOptions:we,readUsedSize:Me,fontString:function(t,e,i){return e+" "+t+"px "+i},requestAnimFrame:t,throttled:e,debounce:i,_toLeftRightCenter:n,_alignStartEnd:o,_textX:s,_pointInLine:mn,_steppedInterpolation:xn,_bezierInterpolation:bn,formatNumber:Fi,toLineHeight:Re,_readValueToProps:Ee,toTRBL:ze,toTRBLCorners:Ie,toPadding:Fe,toFont:Be,resolve:Ve,_addGrace:We,PI:bt,TAU:_t,PITAU:yt,INFINITY:vt,RAD_PER_DEG:wt,HALF_PI:Mt,QUARTER_PI:kt,TWO_THIRDS_PI:St,log10:Pt,sign:Dt,niceNum:Ct,_factorize:Ot,isNumber:Tt,almostEquals:At,almostWhole:Lt,_setMinAndMaxByKey:Rt,toRadians:Et,toDegrees:zt,_decimalPlaces:It,getAngleFromPoint:Ft,distanceBetweenPoints:Bt,_angleDiff:Vt,_normalizeAngle:Wt,_angleBetween:Nt,_limitValue:Ht,_int16Range:jt,getRtlAdapter:_n,overrideTextDirection:yn,restoreTextDirection:vn,_boundSegment:kn,_boundSegments:Sn,_computeSegments:Pn});class An{constructor(t,e,i){this.type=t,this.scope=e,this.override=i,this.items=Object.create(null)}isForType(t){return Object.prototype.isPrototypeOf.call(this.type.prototype,t.prototype)}register(t){const e=this,i=Object.getPrototypeOf(t);let n;(function(t){return"id"in t&&"defaults"in t})(i)&&(n=e.register(i));const o=e.items,s=t.id,a=e.scope+"."+s;if(!s)throw new Error("class does not have id: "+t);return s in o||(o[s]=t,function(t,e,i){const n=ot(Object.create(null),[i?xt.get(i):{},xt.get(e),t.defaults]);xt.set(e,n),t.defaultRoutes&&function(t,e){Object.keys(e).forEach((i=>{const n=i.split("."),o=n.pop(),s=[t].concat(n).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");xt.route(s,o,l,r)}))}(e,t.defaultRoutes);t.descriptors&&xt.describe(e,t.descriptors)}(t,a,n),e.override&&xt.override(t.id,t.overrides)),a}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,n=this.scope;i in e&&delete e[i],n&&i in xt[n]&&(delete xt[n][i],this.override&&delete ft[i])}}var Ln=new class{constructor(){this.controllers=new An(Ei,"datasets",!0),this.elements=new An(zi,"elements"),this.plugins=new An(Object,"plugins"),this.scales=new An(qi,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){const n=this;[...e].forEach((e=>{const o=i||n._getRegistryForType(e);i||o.isForType(e)||o===n.plugins&&e.id?n._exec(t,o,e):J(e,(e=>{const o=i||n._getRegistryForType(e);n._exec(t,o,e)}))}))}_exec(t,e,i){const n=ct(t);Q(i["before"+n],[],i),e[t](i),Q(i["after"+n],[],i)}_getRegistryForType(t){for(let e=0;e<this._typedRegistries.length;e++){const i=this._typedRegistries[e];if(i.isForType(t))return i}return this.plugins}_get(t,e,i){const n=e.get(t);if(void 0===n)throw new Error('"'+t+'" is not a registered '+i+".");return n}};class Rn{constructor(){this._init=[]}notify(t,e,i,n){const o=this;"beforeInit"===e&&(o._init=o._createDescriptors(t,!0),o._notify(o._init,t,"install"));const s=n?o._descriptors(t).filter(n):o._descriptors(t),a=o._notify(s,t,e,i);return"destroy"===e&&(o._notify(s,t,"stop"),o._notify(o._init,t,"uninstall")),a}_notify(t,e,i,n){n=n||{};for(const o of t){const t=o.plugin;if(!1===Q(t[i],[e,n,o.options],t)&&n.cancelable)return!1}return!0}invalidate(){$(this._cache)||(this._oldCache=this._cache,this._cache=void 0)}_descriptors(t){if(this._cache)return this._cache;const e=this._cache=this._createDescriptors(t);return this._notifyStateChanges(t),e}_createDescriptors(t,e){const i=t&&t.config,n=K(i.options&&i.options.plugins,{}),o=function(t){const e=[],i=Object.keys(Ln.plugins.items);for(let t=0;t<i.length;t++)e.push(Ln.getPlugin(i[t]));const n=t.plugins||[];for(let t=0;t<n.length;t++){const i=n[t];-1===e.indexOf(i)&&e.push(i)}return e}(i);return!1!==n||e?function(t,e,i,n){const o=[],s=t.getContext();for(let a=0;a<e.length;a++){const r=e[a],l=En(i[r.id],n);null!==l&&o.push({plugin:r,options:zn(t.config,r,l,s)})}return o}(t,o,n,e):[]}_notifyStateChanges(t){const e=this._oldCache||[],i=this._cache,n=(t,e)=>t.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(n(e,i),t,"stop"),this._notify(n(i,e),t,"start")}}function En(t,e){return e||!1!==t?!0===t?{}:t:null}function zn(t,e,i,n){const o=t.pluginScopeKeys(e),s=t.getOptionScopes(i,o);return t.createResolver(s,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function In(t,e){const i=xt.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function Fn(t,e){return"x"===t||"y"===t?t:e.axis||("top"===(i=e.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.charAt(0).toLowerCase();var i}function Bn(t){const e=t.options||(t.options={});e.plugins=K(e.plugins,{}),e.scales=function(t,e){const i=ft[t.type]||{scales:{}},n=e.scales||{},o=In(t.type,e),s=Object.create(null),a=Object.create(null);return Object.keys(n).forEach((t=>{const e=n[t],r=Fn(t,e),l=function(t,e){return t===e?"_index_":"_value_"}(r,o),c=i.scales||{};s[r]=s[r]||t,a[t]=st(Object.create(null),[{axis:r},e,c[r],c[l]])})),t.data.datasets.forEach((i=>{const o=i.type||t.type,r=i.indexAxis||In(o,e),l=(ft[o]||{}).scales||{};Object.keys(l).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,r),o=i[e+"AxisID"]||s[e]||e;a[o]=a[o]||Object.create(null),st(a[o],[{axis:e},n[o],l[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];st(e,[xt.scales[e.type],xt.scale])})),a}(t,e)}function Vn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const Wn=new Map,Nn=new Set;function Hn(t,e){let i=Wn.get(t);return i||(i=e(),Wn.set(t,i),Nn.add(i)),i}const jn=(t,e,i)=>{const n=lt(e,i);void 0!==n&&t.add(n)};class $n{constructor(t){this._config=function(t){return(t=t||{}).data=Vn(t.data),Bn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Vn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),Bn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Hn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return Hn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return Hn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return Hn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let n=i.get(t);return n&&!e||(n=new Map,i.set(t,n)),n}getOptionScopes(t,e,i){const{options:n,type:o}=this,s=this._cachedScopes(t,i),a=s.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>jn(r,t,e)))),e.forEach((t=>jn(r,n,t))),e.forEach((t=>jn(r,ft[o]||{},t))),e.forEach((t=>jn(r,xt,t))),e.forEach((t=>jn(r,gt,t)))}));const l=Array.from(r);return Nn.has(e)&&s.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,ft[e]||{},xt.datasets[e]||{},{type:e},xt,gt]}resolveNamedOptions(t,e,i,n=[""]){const o={$shared:!0},{resolver:s,subPrefixes:a}=Yn(this._resolverCache,t,n);let r=s;if(function(t,e){const{isScriptable:i,isIndexable:n}=Zi(t);for(const o of e)if(i(o)&&dt(t[o])||n(o)&&Y(t[o]))return!0;return!1}(s,e)){o.$shared=!1;r=Gi(s,i=dt(i)?i():i,this.createResolver(t,i,a))}for(const t of e)o[t]=r[t];return o}createResolver(t,e,i=[""],n){const{resolver:o}=Yn(this._resolverCache,t,i);return U(e)?Gi(o,e,void 0,n):o}}function Yn(t,e,i){let n=t.get(e);n||(n=new Map,t.set(e,n));const o=i.join();let s=n.get(o);if(!s){s={resolver:Ki(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},n.set(o,s)}return s}const Un=["top","bottom","left","right","chartArea"];function Xn(t,e){return"top"===t||"bottom"===t||-1===Un.indexOf(t)&&"x"===e}function qn(t,e){return function(i,n){return i[t]===n[t]?i[e]-n[e]:i[t]-n[t]}}function Kn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),Q(i&&i.onComplete,[t],e)}function Gn(t){const e=t.chart,i=e.options.animation;Q(i&&i.onProgress,[t],e)}function Zn(){return"undefined"!=typeof window&&"undefined"!=typeof document}function Qn(t){return Zn()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Jn={},to=t=>{const e=Qn(t);return Object.values(Jn).filter((t=>t.canvas===e)).pop()};class eo{constructor(t,e){const n=this;this.config=e=new $n(e);const o=Qn(t),s=to(o);if(s)throw new Error("Canvas is already in use. Chart with ID '"+s.id+"' must be destroyed before the canvas can be reused.");const r=e.createResolver(e.chartOptionScopes(),n.getContext());this.platform=n._initializePlatform(o,e);const l=n.platform.acquireContext(o,r.aspectRatio),c=l&&l.canvas,h=c&&c.height,d=c&&c.width;this.id=j(),this.ctx=l,this.canvas=c,this.width=d,this.height=h,this._options=r,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this.scale=void 0,this._plugins=new Rn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=i((()=>this.update("resize")),r.resizeDelay||0),Jn[n.id]=n,l&&c?(a.listen(n,"complete",Kn),a.listen(n,"progress",Gn),n._initialize(),n.attached&&n.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return $(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){const t=this;return t.notifyPlugins("beforeInit"),t.options.responsive?t.resize():ve(t,t.options.devicePixelRatio),t.bindEvents(),t.notifyPlugins("afterInit"),t}_initializePlatform(t,e){return e.platform?new e.platform:!Zn()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?new Je:new ui}clear(){return qt(this.canvas,this.ctx),this}stop(){return a.stop(this),this}resize(t,e){a.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this,n=i.options,o=i.canvas,s=n.maintainAspectRatio&&i.aspectRatio,a=i.platform.getMaximumSize(o,t,e,s),r=n.devicePixelRatio||i.platform.getDevicePixelRatio();i.width=a.width,i.height=a.height,i._aspectRatio=i.aspectRatio,ve(i,r,!0)&&(i.notifyPlugins("resize",{size:a}),Q(n.onResize,[i,a],i),i.attached&&i._doResize()&&i.render())}ensureScalesHaveIDs(){J(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this,e=t.options,i=e.scales,n=t.scales,o=Object.keys(n).reduce(((t,e)=>(t[e]=!1,t)),{});let s=[];i&&(s=s.concat(Object.keys(i).map((t=>{const e=i[t],n=Fn(t,e),o="r"===n,s="x"===n;return{options:e,dposition:o?"chartArea":s?"bottom":"left",dtype:o?"radialLinear":s?"category":"linear"}})))),J(s,(i=>{const s=i.options,a=s.id,r=Fn(a,s),l=K(s.type,i.dtype);void 0!==s.position&&Xn(s.position,r)===Xn(i.dposition)||(s.position=i.dposition),o[a]=!0;let c=null;if(a in n&&n[a].type===l)c=n[a];else{c=new(Ln.getScale(l))({id:a,type:l,ctx:t.ctx,chart:t}),n[c.id]=c}c.init(s,e)})),J(o,((t,e)=>{t||delete n[e]})),J(n,(e=>{Ze.configure(t,e,e.options),Ze.addBox(t,e)}))}_updateMetasets(){const t=this,e=t._metasets,i=t.data.datasets.length,n=e.length;if(e.sort(((t,e)=>t.index-e.index)),n>i){for(let e=i;e<n;++e)t._destroyDatasetMeta(e);e.splice(i,n-i)}t._sortedMetasets=e.slice(0).sort(qn("order","index"))}_removeUnreferencedMetasets(){const t=this,{_metasets:e,data:{datasets:i}}=t;e.length>i.length&&delete t._stacks,e.forEach(((e,n)=>{0===i.filter((t=>t===e._dataset)).length&&t._destroyDatasetMeta(n)}))}buildOrUpdateControllers(){const t=this,e=[],i=t.data.datasets;let n,o;for(t._removeUnreferencedMetasets(),n=0,o=i.length;n<o;n++){const o=i[n];let s=t.getDatasetMeta(n);const a=o.type||t.config.type;if(s.type&&s.type!==a&&(t._destroyDatasetMeta(n),s=t.getDatasetMeta(n)),s.type=a,s.indexAxis=o.indexAxis||In(a,t.options),s.order=o.order||0,s.index=n,s.label=""+o.label,s.visible=t.isDatasetVisible(n),s.controller)s.controller.updateIndex(n),s.controller.linkScales();else{const i=Ln.getController(a),{datasetElementType:o,dataElementType:r}=xt.datasets[a];Object.assign(i.prototype,{dataElementType:Ln.getElement(r),datasetElementType:o&&Ln.getElement(o)}),s.controller=new i(t,n),e.push(s.controller)}}return t._updateMetasets(),e}_resetElements(){const t=this;J(t.data.datasets,((e,i)=>{t.getDatasetMeta(i).controller.reset()}),t)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this,i=e.config;i.update(),e._options=i.createResolver(i.chartOptionScopes(),e.getContext()),J(e.scales,(t=>{Ze.removeBox(e,t)}));const n=e._animationsDisabled=!e.options.animation;e.ensureScalesHaveIDs(),e.buildOrUpdateScales();const o=new Set(Object.keys(e._listeners)),s=new Set(e.options.events);if(ut(o,s)&&!!this._responsiveListeners===e.options.responsive||(e.unbindEvents(),e.bindEvents()),e._plugins.invalidate(),!1===e.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const a=e.buildOrUpdateControllers();e.notifyPlugins("beforeElementsUpdate");let r=0;for(let t=0,i=e.data.datasets.length;t<i;t++){const{controller:i}=e.getDatasetMeta(t),o=!n&&-1===a.indexOf(i);i.buildOrUpdateElements(o),r=Math.max(+i.getMaxOverflow(),r)}e._minPadding=r,e._updateLayout(r),n||J(a,(t=>{t.reset()})),e._updateDatasets(t),e.notifyPlugins("afterUpdate",{mode:t}),e._layers.sort(qn("z","_idx")),e._lastEvent&&e._eventHandler(e._lastEvent,!0),e.render()}_updateLayout(t){const e=this;if(!1===e.notifyPlugins("beforeLayout",{cancelable:!0}))return;Ze.update(e,e.width,e.height,t);const i=e.chartArea,n=i.width<=0||i.height<=0;e._layers=[],J(e.boxes,(t=>{n&&"chartArea"===t.position||(t.configure&&t.configure(),e._layers.push(...t._layers()))}),e),e._layers.forEach(((t,e)=>{t._idx=e})),e.notifyPlugins("afterLayout")}_updateDatasets(t){const e=this,i="function"==typeof t;if(!1!==e.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let n=0,o=e.data.datasets.length;n<o;++n)e._updateDataset(n,i?t({datasetIndex:n}):t);e.notifyPlugins("afterDatasetsUpdate",{mode:t})}}_updateDataset(t,e){const i=this,n=i.getDatasetMeta(t),o={meta:n,index:t,mode:e,cancelable:!0};!1!==i.notifyPlugins("beforeDatasetUpdate",o)&&(n.controller._update(e),o.cancelable=!1,i.notifyPlugins("afterDatasetUpdate",o))}render(){const t=this;!1!==t.notifyPlugins("beforeRender",{cancelable:!0})&&(a.has(t)?t.attached&&!a.running(t)&&a.start(t):(t.draw(),Kn({chart:t})))}draw(){const t=this;let e;if(t._resizeBeforeDraw){const{width:e,height:i}=t._resizeBeforeDraw;t._resize(e,i),t._resizeBeforeDraw=null}if(t.clear(),t.width<=0||t.height<=0)return;if(!1===t.notifyPlugins("beforeDraw",{cancelable:!0}))return;const i=t._layers;for(e=0;e<i.length&&i[e].z<=0;++e)i[e].draw(t.chartArea);for(t._drawDatasets();e<i.length;++e)i[e].draw(t.chartArea);t.notifyPlugins("afterDraw")}_getSortedDatasetMetas(t){const e=this._sortedMetasets,i=[];let n,o;for(n=0,o=e.length;n<o;++n){const o=e[n];t&&!o.visible||i.push(o)}return i}getSortedVisibleDatasetMetas(){return this._getSortedDatasetMetas(!0)}_drawDatasets(){const t=this;if(!1===t.notifyPlugins("beforeDatasetsDraw",{cancelable:!0}))return;const e=t.getSortedVisibleDatasetMetas();for(let i=e.length-1;i>=0;--i)t._drawDataset(e[i]);t.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this,i=e.ctx,n=t._clip,o=!n.disabled,s=e.chartArea,a={meta:t,index:t.index,cancelable:!0};!1!==e.notifyPlugins("beforeDatasetDraw",a)&&(o&&Zt(i,{left:!1===n.left?0:s.left-n.left,right:!1===n.right?e.width:s.right+n.right,top:!1===n.top?0:s.top-n.top,bottom:!1===n.bottom?e.height:s.bottom+n.bottom}),t.controller.draw(),o&&Qt(i),a.cancelable=!1,e.notifyPlugins("afterDatasetDraw",a))}getElementsAtEventForMode(t,e,i,n){const o=Te.modes[e];return"function"==typeof o?o(this,t,i,n):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let n=i.filter((t=>t&&t._dataset===e)).pop();return n||(n={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(n)),n}getContext(){return this.$context||(this.$context={chart:this,type:"chart"})}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateDatasetVisibility(t,e){const i=this,n=e?"show":"hide",o=i.getDatasetMeta(t),s=o.controller._resolveAnimations(void 0,n);i.setDatasetVisibility(t,e),s.update(o,{visible:e}),i.update((e=>e.datasetIndex===t?n:void 0))}hide(t){this._updateDatasetVisibility(t,!1)}show(t){this._updateDatasetVisibility(t,!0)}_destroyDatasetMeta(t){const e=this,i=e._metasets&&e._metasets[t];i&&i.controller&&(i.controller._destroy(),delete e._metasets[t])}destroy(){const t=this,{canvas:e,ctx:i}=t;let n,o;for(t.stop(),a.remove(t),n=0,o=t.data.datasets.length;n<o;++n)t._destroyDatasetMeta(n);t.config.clearCache(),e&&(t.unbindEvents(),qt(e,i),t.platform.releaseContext(i),t.canvas=null,t.ctx=null),t.notifyPlugins("destroy"),delete Jn[t.id]}toBase64Image(...t){return this.canvas.toDataURL(...t)}bindEvents(){this.bindUserEvents(),this.options.responsive?this.bindResponsiveEvents():this.attached=!0}bindUserEvents(){const t=this,e=t._listeners,i=t.platform,n=function(e,i,n){e.offsetX=i,e.offsetY=n,t._eventHandler(e)};J(t.options.events,(o=>((n,o)=>{i.addEventListener(t,n,o),e[n]=o})(o,n)))}bindResponsiveEvents(){const t=this;t._responsiveListeners||(t._responsiveListeners={});const e=t._responsiveListeners,i=t.platform,n=(n,o)=>{i.addEventListener(t,n,o),e[n]=o},o=(n,o)=>{e[n]&&(i.removeEventListener(t,n,o),delete e[n])},s=(e,i)=>{t.canvas&&t.resize(e,i)};let a;const r=()=>{o("attach",r),t.attached=!0,t.resize(),n("resize",s),n("detach",a)};a=()=>{t.attached=!1,o("resize",s),n("attach",r)},i.isAttached(t.canvas)?r():a()}unbindEvents(){const t=this;J(t._listeners,((e,i)=>{t.platform.removeEventListener(t,i,e)})),t._listeners={},J(t._responsiveListeners,((e,i)=>{t.platform.removeEventListener(t,i,e)})),t._responsiveListeners=void 0}updateHoverStyle(t,e,i){const n=i?"set":"remove";let o,s,a,r;for("dataset"===e&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+n+"DatasetHoverStyle"]()),a=0,r=t.length;a<r;++a){s=t[a];const e=s&&this.getDatasetMeta(s.datasetIndex).controller;e&&e[n+"HoverStyle"](s.element,s.datasetIndex,s.index)}}getActiveElements(){return this._active||[]}setActiveElements(t){const e=this,i=e._active||[],n=t.map((({datasetIndex:t,index:i})=>{const n=e.getDatasetMeta(t);if(!n)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:n.data[i],index:i}}));!tt(n,i)&&(e._active=n,e._updateHoverStyles(n,i))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}_updateHoverStyles(t,e,i){const n=this,o=n.options.hover,s=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),a=s(e,t),r=i?t:s(t,e);a.length&&n.updateHoverStyle(a,o.mode,!1),r.length&&o.mode&&n.updateHoverStyle(r,o.mode,!0)}_eventHandler(t,e){const i=this,n={event:t,replay:e,cancelable:!0},o=e=>(e.options.events||this.options.events).includes(t.type);if(!1===i.notifyPlugins("beforeEvent",n,o))return;const s=i._handleEvent(t,e);return n.cancelable=!1,i.notifyPlugins("afterEvent",n,o),(s||n.changed)&&i.render(),i}_handleEvent(t,e){const i=this,{_active:n=[],options:o}=i,s=o.hover,a=e;let r=[],l=!1,c=null;return"mouseout"!==t.type&&(r=i.getElementsAtEventForMode(t,s.mode,s,a),c="click"===t.type?i._lastEvent:t),i._lastEvent=null,Gt(t,i.chartArea,i._minPadding)&&(Q(o.onHover,[t,r,i],i),"mouseup"!==t.type&&"click"!==t.type&&"contextmenu"!==t.type||Q(o.onClick,[t,r,i],i)),l=!tt(r,n),(l||e)&&(i._active=r,i._updateHoverStyles(r,n,e)),i._lastEvent=c,l}}const io=()=>J(eo.instances,(t=>t._plugins.invalidate())),no=!0;function oo(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}Object.defineProperties(eo,{defaults:{enumerable:no,value:xt},instances:{enumerable:no,value:Jn},overrides:{enumerable:no,value:ft},registry:{enumerable:no,value:Ln},version:{enumerable:no,value:"3.4.1"},getChart:{enumerable:no,value:to},register:{enumerable:no,value:(...t)=>{Ln.add(...t),io()}},unregister:{enumerable:no,value:(...t)=>{Ln.remove(...t),io()}}});class so{constructor(t){this.options=t||{}}formats(){return oo()}parse(t,e){return oo()}format(t,e){return oo()}add(t,e,i){return oo()}diff(t,e,i){return oo()}startOf(t,e,i){return oo()}endOf(t,e){return oo()}}so.override=function(t){Object.assign(so.prototype,t)};var ao={_date:so};function ro(t){const e=function(t){if(!t._cache.$bar){const e=t.getMatchingVisibleMetas("bar");let i=[];for(let n=0,o=e.length;n<o;n++)i=i.concat(e[n].controller.getAllParsedValues(t));t._cache.$bar=de(i.sort(((t,e)=>t-e)))}return t._cache.$bar}(t);let i,n,o,s,a=t._length;const r=()=>{32767!==o&&-32768!==o&&(ht(s)&&(a=Math.min(a,Math.abs(o-s)||a)),s=o)};for(i=0,n=e.length;i<n;++i)o=t.getPixelForValue(e[i]),r();for(s=void 0,i=0,n=t.ticks.length;i<n;++i)o=t.getPixelForTick(i),r();return a}function lo(t,e,i,n){return Y(t)?function(t,e,i,n){const o=i.parse(t[0],n),s=i.parse(t[1],n),a=Math.min(o,s),r=Math.max(o,s);let l=a,c=r;Math.abs(a)>Math.abs(r)&&(l=r,c=a),e[i.axis]=c,e._custom={barStart:l,barEnd:c,start:o,end:s,min:a,max:r}}(t,e,i,n):e[i.axis]=i.parse(t,n),e}function co(t,e,i,n){const o=t.iScale,s=t.vScale,a=o.getLabels(),r=o===s,l=[];let c,h,d,u;for(c=i,h=i+n;c<h;++c)u=e[c],d={},d[o.axis]=r||o.parse(a[c],c),l.push(lo(u,d,s,c));return l}function ho(t){return t&&void 0!==t.barStart&&void 0!==t.barEnd}class uo extends Ei{parsePrimitiveData(t,e,i,n){return co(t,e,i,n)}parseArrayData(t,e,i,n){return co(t,e,i,n)}parseObjectData(t,e,i,n){const{iScale:o,vScale:s}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===o.axis?a:r,c="x"===s.axis?a:r,h=[];let d,u,f,g;for(d=i,u=i+n;d<u;++d)g=e[d],f={},f[o.axis]=o.parse(lt(g,l),d),h.push(lo(lt(g,c),f,s,d));return h}updateRangeFromParsed(t,e,i,n){super.updateRangeFromParsed(t,e,i,n);const o=i._custom;o&&e===this._cachedMeta.vScale&&(t.min=Math.min(t.min,o.min),t.max=Math.max(t.max,o.max))}getMaxOverflow(){return 0}getLabelAndValue(t){const e=this._cachedMeta,{iScale:i,vScale:n}=e,o=this.getParsed(t),s=o._custom,a=ho(s)?"["+s.start+", "+s.end+"]":""+n.getLabelForValue(o[n.axis]);return{label:""+i.getLabelForValue(o[i.axis]),value:a}}initialize(){const t=this;t.enableOptionSharing=!0,super.initialize();t._cachedMeta.stack=t.getDataset().stack}update(t){const e=this._cachedMeta;this.updateElements(e.data,0,e.data.length,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o._cachedMeta.vScale,r=a.getBasePixel(),l=a.isHorizontal(),c=o._getRuler(),h=o.resolveDataElementOptions(e,n),d=o.getSharedOptions(h),u=o.includeOptions(n,d);o.updateSharedOptions(d,n,h);for(let h=e;h<e+i;h++){const e=o.getParsed(h),i=s||$(e[a.axis])?{base:r,head:r}:o._calculateBarValuePixels(h),f=o._calculateBarIndexPixels(h,c),g=(e._stacks||{})[a.axis],p={horizontal:l,base:i.base,enableBorderRadius:!g||ho(e._custom)||o.index===g._top||o.index===g._bottom,x:l?i.head:f.center,y:l?f.center:i.head,height:l?f.size:Math.abs(i.size),width:l?Math.abs(i.size):f.size};u&&(p.options=d||o.resolveDataElementOptions(h,t[h].active?"active":n)),o.updateElement(t[h],h,p,n)}}_getStacks(t,e){const i=this._cachedMeta.iScale,n=i.getMatchingVisibleMetas(this._type),o=i.options.stacked,s=n.length,a=[];let r,l;for(r=0;r<s;++r)if(l=n[r],l.controller.options.grouped){if(void 0!==e){const t=l.controller.getParsed(e)[l.controller._cachedMeta.vScale.axis];if($(t)||isNaN(t))continue}if((!1===o||-1===a.indexOf(l.stack)||void 0===o&&void 0===l.stack)&&a.push(l.stack),l.index===t)break}return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const n=this._getStacks(t,i),o=void 0!==e?n.indexOf(e):-1;return-1===o?n.length-1:o}_getRuler(){const t=this,e=t.options,i=t._cachedMeta,n=i.iScale,o=[];let s,a;for(s=0,a=i.data.length;s<a;++s)o.push(n.getPixelForValue(t.getParsed(s)[n.axis],s));const r=e.barThickness;return{min:r||ro(n),pixels:o,start:n._startPixel,end:n._endPixel,stackCount:t._getStackCount(),scale:n,grouped:e.grouped,ratio:r?1:e.categoryPercentage*e.barPercentage}}_calculateBarValuePixels(t){const e=this,{vScale:i,_stacked:n}=e._cachedMeta,{base:o,minBarLength:s}=e.options,a=e.getParsed(t),r=a._custom,l=ho(r);let c,h,d=a[i.axis],u=0,f=n?e.applyStack(i,a,n):d;f!==d&&(u=f-d,f=d),l&&(d=r.barStart,f=r.barEnd-r.barStart,0!==d&&Dt(d)!==Dt(r.barEnd)&&(u=0),u+=d);const g=$(o)||l?u:o;let p=i.getPixelForValue(g);c=this.chart.getDataVisibility(t)?i.getPixelForValue(u+f):p,h=c-p,void 0!==s&&Math.abs(h)<s&&(h=h<0?-s:s,0===d&&(p-=h/2),c=p+h);const m=o||0;if(p===i.getPixelForValue(m)){const t=i.getLineWidthForValue(m)/2;h>0?(p+=t,h-=t):h<0&&(p-=t,h+=t)}return{size:h,base:p,head:c,center:c+h/2}}_calculateBarIndexPixels(t,e){const i=this,n=e.scale,o=i.options,s=o.skipNull,a=K(o.maxBarThickness,1/0);let r,l;if(e.grouped){const n=s?i._getStackCount(t):e.stackCount,c="flex"===o.barThickness?function(t,e,i,n){const o=e.pixels,s=o[t];let a=t>0?o[t-1]:null,r=t<o.length-1?o[t+1]:null;const l=i.categoryPercentage;null===a&&(a=s-(null===r?e.end-e.start:r-s)),null===r&&(r=s+s-a);const c=s-(s-Math.min(a,r))/2*l;return{chunk:Math.abs(r-a)/2*l/n,ratio:i.barPercentage,start:c}}(t,e,o,n):function(t,e,i,n){const o=i.barThickness;let s,a;return $(o)?(s=e.min*i.categoryPercentage,a=i.barPercentage):(s=o*n,a=1),{chunk:s/n,ratio:a,start:e.pixels[t]-s/2}}(t,e,o,n),h=i._getStackIndex(i.index,i._cachedMeta.stack,s?t:void 0);r=c.start+c.chunk*h+c.chunk/2,l=Math.min(a,c.chunk*c.ratio)}else r=n.getPixelForValue(i.getParsed(t)[n.axis],t),l=Math.min(a,e.min*e.ratio);return{base:r-l/2,head:r+l/2,center:r,size:l}}draw(){const t=this,e=t._cachedMeta,i=e.vScale,n=e.data,o=n.length;let s=0;for(;s<o;++s)null!==t.getParsed(s)[i.axis]&&n[s].draw(t._ctx)}}uo.id="bar",uo.defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}},uo.overrides={interaction:{mode:"index"},scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};class fo extends Ei{initialize(){this.enableOptionSharing=!0,super.initialize()}parseObjectData(t,e,i,n){const{xScale:o,yScale:s}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l=[];let c,h,d;for(c=i,h=i+n;c<h;++c)d=e[c],l.push({x:o.parse(lt(d,a),c),y:s.parse(lt(d,r),c),_custom:d&&d.r&&+d.r});return l}getMaxOverflow(){const{data:t,_parsed:e}=this._cachedMeta;let i=0;for(let n=t.length-1;n>=0;--n)i=Math.max(i,t[n].size()/2,e[n]._custom);return i>0&&i}getLabelAndValue(t){const e=this._cachedMeta,{xScale:i,yScale:n}=e,o=this.getParsed(t),s=i.getLabelForValue(o.x),a=n.getLabelForValue(o.y),r=o._custom;return{label:e.label,value:"("+s+", "+a+(r?", "+r:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{iScale:a,vScale:r}=o._cachedMeta,l=o.resolveDataElementOptions(e,n),c=o.getSharedOptions(l),h=o.includeOptions(n,c),d=a.axis,u=r.axis;for(let l=e;l<e+i;l++){const e=t[l],i=!s&&o.getParsed(l),c={},f=c[d]=s?a.getPixelForDecimal(.5):a.getPixelForValue(i[d]),g=c[u]=s?r.getBasePixel():r.getPixelForValue(i[u]);c.skip=isNaN(f)||isNaN(g),h&&(c.options=o.resolveDataElementOptions(l,e.active?"active":n),s&&(c.options.radius=0)),o.updateElement(e,l,c,n)}o.updateSharedOptions(c,n,l)}resolveDataElementOptions(t,e){const i=this.getParsed(t);let n=super.resolveDataElementOptions(t,e);n.$shared&&(n=Object.assign({},n,{$shared:!1}));const o=n.radius;return"active"!==e&&(n.radius=0),n.radius+=K(i&&i._custom,o),n}}fo.id="bubble",fo.defaults={datasetElementType:!1,dataElementType:"point",animations:{numbers:{type:"number",properties:["x","y","borderWidth","radius"]}}},fo.overrides={scales:{x:{type:"linear"},y:{type:"linear"}},plugins:{tooltip:{callbacks:{title:()=>""}}}};class go extends Ei{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,n=this._cachedMeta;let o,s;for(o=t,s=t+e;o<s;++o)n._parsed[o]=+i[o]}_getRotation(){return Et(this.options.rotation-90)}_getCircumference(){return Et(this.options.circumference)}_getRotationExtents(){let t=_t,e=-_t;const i=this;for(let n=0;n<i.chart.data.datasets.length;++n)if(i.chart.isDatasetVisible(n)){const o=i.chart.getDatasetMeta(n).controller,s=o._getRotation(),a=o._getCircumference();t=Math.min(t,s),e=Math.max(e,s+a)}return{rotation:t,circumference:e-t}}update(t){const e=this,i=e.chart,{chartArea:n}=i,o=e._cachedMeta,s=o.data,a=e.getMaxBorderWidth()+e.getMaxOffset(s)+e.options.spacing,r=Math.max((Math.min(n.width,n.height)-a)/2,0),l=Math.min(G(e.options.cutout,r),1),c=e._getRingWeight(e.index),{circumference:h,rotation:d}=e._getRotationExtents(),{ratioX:u,ratioY:f,offsetX:g,offsetY:p}=function(t,e,i){let n=1,o=1,s=0,a=0;if(e<_t){const r=t,l=r+e,c=Math.cos(r),h=Math.sin(r),d=Math.cos(l),u=Math.sin(l),f=(t,e,n)=>Nt(t,r,l,!0)?1:Math.max(e,e*i,n,n*i),g=(t,e,n)=>Nt(t,r,l,!0)?-1:Math.min(e,e*i,n,n*i),p=f(0,c,d),m=f(Mt,h,u),x=g(bt,c,d),b=g(bt+Mt,h,u);n=(p-x)/2,o=(m-b)/2,s=-(p+x)/2,a=-(m+b)/2}return{ratioX:n,ratioY:o,offsetX:s,offsetY:a}}(d,h,l),m=(n.width-a)/u,x=(n.height-a)/f,b=Math.max(Math.min(m,x)/2,0),_=Z(e.options.radius,b),y=(_-Math.max(_*l,0))/e._getVisibleDatasetWeightTotal();e.offsetX=g*_,e.offsetY=p*_,o.total=e.calculateTotal(),e.outerRadius=_-y*e._getRingWeightOffset(e.index),e.innerRadius=Math.max(e.outerRadius-y*c,0),e.updateElements(s,0,s.length,t)}_circumference(t,e){const i=this,n=i.options,o=i._cachedMeta,s=i._getCircumference();return e&&n.animation.animateRotate||!this.chart.getDataVisibility(t)||null===o._parsed[t]?0:i.calculateCircumference(o._parsed[t]*s/_t)}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=a.chartArea,l=a.options.animation,c=(r.left+r.right)/2,h=(r.top+r.bottom)/2,d=s&&l.animateScale,u=d?0:o.innerRadius,f=d?0:o.outerRadius,g=o.resolveDataElementOptions(e,n),p=o.getSharedOptions(g),m=o.includeOptions(n,p);let x,b=o._getRotation();for(x=0;x<e;++x)b+=o._circumference(x,s);for(x=e;x<e+i;++x){const e=o._circumference(x,s),i=t[x],a={x:c+o.offsetX,y:h+o.offsetY,startAngle:b,endAngle:b+e,circumference:e,outerRadius:f,innerRadius:u};m&&(a.options=p||o.resolveDataElementOptions(x,i.active?"active":n)),b+=e,o.updateElement(i,x,a,n)}o.updateSharedOptions(p,n,g)}calculateTotal(){const t=this._cachedMeta,e=t.data;let i,n=0;for(i=0;i<e.length;i++){const e=t._parsed[i];null!==e&&!isNaN(e)&&this.chart.getDataVisibility(i)&&(n+=Math.abs(e))}return n}calculateCircumference(t){const e=this._cachedMeta.total;return e>0&&!isNaN(t)?_t*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,n=i.data.labels||[],o=Fi(e._parsed[t],i.options.locale);return{label:n[t]||"",value:o}}getMaxBorderWidth(t){const e=this;let i=0;const n=e.chart;let o,s,a,r,l;if(!t)for(o=0,s=n.data.datasets.length;o<s;++o)if(n.isDatasetVisible(o)){a=n.getDatasetMeta(o),t=a.data,r=a.controller,r!==e&&r.configure();break}if(!t)return 0;for(o=0,s=t.length;o<s;++o)l=r.resolveDataElementOptions(o),"inner"!==l.borderAlign&&(i=Math.max(i,l.borderWidth||0,l.hoverBorderWidth||0));return i}getMaxOffset(t){let e=0;for(let i=0,n=t.length;i<n;++i){const t=this.resolveDataElementOptions(i);e=Math.max(e,t.offset||0,t.hoverOffset||0)}return e}_getRingWeightOffset(t){let e=0;for(let i=0;i<t;++i)this.chart.isDatasetVisible(i)&&(e+=this._getRingWeight(i));return e}_getRingWeight(t){return Math.max(K(this.chart.data.datasets[t].weight,1),0)}_getVisibleDatasetWeightTotal(){return this._getRingWeightOffset(this.chart.data.datasets.length)||1}}go.id="doughnut",go.defaults={datasetElementType:!1,dataElementType:"arc",animation:{animateRotate:!0,animateScale:!1},animations:{numbers:{type:"number",properties:["circumference","endAngle","innerRadius","outerRadius","startAngle","x","y","offset","borderWidth","spacing"]}},cutout:"50%",rotation:0,circumference:360,radius:"100%",spacing:0,indexAxis:"r"},go.descriptors={_scriptable:t=>"spacing"!==t,_indexable:t=>"spacing"!==t},go.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label(t){let e=t.label;const i=": "+t.formattedValue;return Y(e)?(e=e.slice(),e[0]+=i):e+=i,e}}}}};class po extends Ei{initialize(){this.enableOptionSharing=!0,super.initialize()}update(t){const e=this,i=e._cachedMeta,{dataset:n,data:o=[],_dataset:s}=i,a=e.chart._animationsDisabled;let{start:r,count:l}=function(t,e,i){const n=e.length;let o=0,s=n;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:c,max:h,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(o=Ht(Math.min(se(r,a.axis,c).lo,i?n:se(e,l,a.getPixelForValue(c)).lo),0,n-1)),s=u?Ht(Math.max(se(r,a.axis,h).hi+1,i?0:se(e,l,a.getPixelForValue(h)).hi+1),o,n)-o:n-o}return{start:o,count:s}}(i,o,a);e._drawStart=r,e._drawCount=l,function(t){const{xScale:e,yScale:i,_scaleRanges:n}=t,o={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!n)return t._scaleRanges=o,!0;const s=n.xmin!==e.min||n.xmax!==e.max||n.ymin!==i.min||n.ymax!==i.max;return Object.assign(n,o),s}(i)&&(r=0,l=o.length),n._decimated=!!s._decimated,n.points=o;const c=e.resolveDatasetElementOptions(t);e.options.showLine||(c.borderWidth=0),c.segment=e.options.segment,e.updateElement(n,void 0,{animated:!a,options:c},t),e.updateElements(o,r,l,t)}updateElements(t,e,i,n){const o=this,s="reset"===n,{iScale:a,vScale:r,_stacked:l}=o._cachedMeta,c=o.resolveDataElementOptions(e,n),h=o.getSharedOptions(c),d=o.includeOptions(n,h),u=a.axis,f=r.axis,g=o.options.spanGaps,p=Tt(g)?g:Number.POSITIVE_INFINITY,m=o.chart._animationsDisabled||s||"none"===n;let x=e>0&&o.getParsed(e-1);for(let c=e;c<e+i;++c){const e=t[c],i=o.getParsed(c),g=m?e:{},b=$(i[f]),_=g[u]=a.getPixelForValue(i[u],c),y=g[f]=s||b?r.getBasePixel():r.getPixelForValue(l?o.applyStack(r,i,l):i[f],c);g.skip=isNaN(_)||isNaN(y)||b,g.stop=c>0&&i[u]-x[u]>p,g.parsed=i,d&&(g.options=h||o.resolveDataElementOptions(c,e.active?"active":n)),m||o.updateElement(e,c,g,n),x=i}o.updateSharedOptions(h,n,c)}getMaxOverflow(){const t=this,e=t._cachedMeta,i=e.dataset,n=i.options&&i.options.borderWidth||0,o=e.data||[];if(!o.length)return n;const s=o[0].size(t.resolveDataElementOptions(0)),a=o[o.length-1].size(t.resolveDataElementOptions(o.length-1));return Math.max(n,s,a)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}po.id="line",po.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1},po.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};class mo extends Ei{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,n=i.data.labels||[],o=Fi(e._parsed[t].r,i.options.locale);return{label:n[t]||"",value:o}}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}_updateRadius(){const t=this,e=t.chart,i=e.chartArea,n=e.options,o=Math.min(i.right-i.left,i.bottom-i.top),s=Math.max(o/2,0),a=(s-Math.max(n.cutoutPercentage?s/100*n.cutoutPercentage:1,0))/e.getVisibleDatasetCount();t.outerRadius=s-a*t.index,t.innerRadius=t.outerRadius-a}updateElements(t,e,i,n){const o=this,s="reset"===n,a=o.chart,r=o.getDataset(),l=a.options.animation,c=o._cachedMeta.rScale,h=c.xCenter,d=c.yCenter,u=c.getIndexAngle(0)-.5*bt;let f,g=u;const p=360/o.countVisibleElements();for(f=0;f<e;++f)g+=o._computeAngle(f,n,p);for(f=e;f<e+i;f++){const e=t[f];let i=g,m=g+o._computeAngle(f,n,p),x=a.getDataVisibility(f)?c.getDistanceFromCenterForValue(r.data[f]):0;g=m,s&&(l.animateScale&&(x=0),l.animateRotate&&(i=m=u));const b={x:h,y:d,innerRadius:0,outerRadius:x,startAngle:i,endAngle:m,options:o.resolveDataElementOptions(f,e.active?"active":n)};o.updateElement(e,f,b,n)}}countVisibleElements(){const t=this.getDataset(),e=this._cachedMeta;let i=0;return e.data.forEach(((e,n)=>{!isNaN(t.data[n])&&this.chart.getDataVisibility(n)&&i++})),i}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?Et(this.resolveDataElementOptions(t,e).angle||i):0}}mo.id="polarArea",mo.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0},mo.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label:t=>t.chart.data.labels[t.dataIndex]+": "+t.formattedValue}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};class xo extends go{}xo.id="pie",xo.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};class bo extends Ei{getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}update(t){const e=this,i=e._cachedMeta,n=i.dataset,o=i.data||[],s=i.iScale.getLabels();if(n.points=o,"resize"!==t){const i=e.resolveDatasetElementOptions(t);e.options.showLine||(i.borderWidth=0);const a={_loop:!0,_fullLoop:s.length===o.length,options:i};e.updateElement(n,void 0,a,t)}e.updateElements(o,0,o.length,t)}updateElements(t,e,i,n){const o=this,s=o.getDataset(),a=o._cachedMeta.rScale,r="reset"===n;for(let l=e;l<e+i;l++){const e=t[l],i=o.resolveDataElementOptions(l,e.active?"active":n),c=a.getPointPositionForValue(l,s.data[l]),h=r?a.xCenter:c.x,d=r?a.yCenter:c.y,u={x:h,y:d,angle:c.angle,skip:isNaN(h)||isNaN(d),options:i};o.updateElement(e,l,u,n)}}}bo.id="radar",bo.defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}},bo.overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};class _o extends po{}_o.id="scatter",_o.defaults={showLine:!1,fill:!1},_o.overrides={interaction:{mode:"point"},plugins:{tooltip:{callbacks:{title:()=>"",label:t=>"("+t.label+", "+t.formattedValue+")"}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var yo=Object.freeze({__proto__:null,BarController:uo,BubbleController:fo,DoughnutController:go,LineController:po,PolarAreaController:mo,PieController:xo,RadarController:bo,ScatterController:_o});function vo(t,e,i){const{startAngle:n,pixelMargin:o,x:s,y:a,outerRadius:r,innerRadius:l}=e;let c=o/r;t.beginPath(),t.arc(s,a,r,n-c,i+c),l>o?(c=o/l,t.arc(s,a,l,i+c,n-c,!0)):t.arc(s,a,o,i+Mt,n-Mt),t.closePath(),t.clip()}function wo(t,e,i,n){const o=Ee(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const s=(i-e)/2,a=Math.min(s,n*e/2),r=t=>{const e=(i-Math.min(s,t))*n/2;return Ht(t,0,Math.min(s,e))};return{outerStart:r(o.outerStart),outerEnd:r(o.outerEnd),innerStart:Ht(o.innerStart,0,a),innerEnd:Ht(o.innerEnd,0,a)}}function Mo(t,e,i,n){return{x:i+t*Math.cos(e),y:n+t*Math.sin(e)}}function ko(t,e,i,n,o){const{x:s,y:a,startAngle:r,pixelMargin:l,innerRadius:c}=e,h=Math.max(e.outerRadius+n+i-l,0),d=c>0?c+n+i+l:0;let u=0;const f=o-r;if(n){const t=((c>0?c-n:0)+(h>0?h-n:0))/2;u=(f-(0!==t?f*t/(t+n):f))/2}const g=(f-Math.max(.001,f*h-i/bt)/h)/2,p=r+g+u,m=o-g-u,{outerStart:x,outerEnd:b,innerStart:_,innerEnd:y}=wo(e,d,h,m-p),v=h-x,w=h-b,M=p+x/v,k=m-b/w,S=d+_,P=d+y,D=p+_/S,C=m-y/P;if(t.beginPath(),t.arc(s,a,h,M,k),b>0){const e=Mo(w,k,s,a);t.arc(e.x,e.y,b,k,m+Mt)}const O=Mo(P,m,s,a);if(t.lineTo(O.x,O.y),y>0){const e=Mo(P,C,s,a);t.arc(e.x,e.y,y,m+Mt,C+Math.PI)}if(t.arc(s,a,d,m-y/d,p+_/d,!0),_>0){const e=Mo(S,D,s,a);t.arc(e.x,e.y,_,D+Math.PI,p-Mt)}const T=Mo(v,p,s,a);if(t.lineTo(T.x,T.y),x>0){const e=Mo(v,M,s,a);t.arc(e.x,e.y,x,p-Mt,M)}t.closePath()}function So(t,e,i,n,o){const{options:s}=e,a="inner"===s.borderAlign;s.borderWidth&&(a?(t.lineWidth=2*s.borderWidth,t.lineJoin="round"):(t.lineWidth=s.borderWidth,t.lineJoin="bevel"),e.fullCircles&&function(t,e,i){const{x:n,y:o,startAngle:s,pixelMargin:a,fullCircles:r}=e,l=Math.max(e.outerRadius-a,0),c=e.innerRadius+a;let h;for(i&&vo(t,e,s+_t),t.beginPath(),t.arc(n,o,c,s+_t,s,!0),h=0;h<r;++h)t.stroke();for(t.beginPath(),t.arc(n,o,l,s,s+_t),h=0;h<r;++h)t.stroke()}(t,e,a),a&&vo(t,e,o),ko(t,e,i,n,o),t.stroke())}class Po extends zi{constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const n=this.getProps(["x","y"],i),{angle:o,distance:s}=Ft(n,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:l,outerRadius:c,circumference:h}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),d=this.options.spacing/2;return(h>=_t||Nt(o,a,r))&&(s>=l+d&&s<=c+d)}getCenterPoint(t){const{x:e,y:i,startAngle:n,endAngle:o,innerRadius:s,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius","circumference"],t),{offset:r,spacing:l}=this.options,c=(n+o)/2,h=(s+a+l+r)/2;return{x:e+Math.cos(c)*h,y:i+Math.sin(c)*h}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const e=this,{options:i,circumference:n}=e,o=(i.offset||0)/2,s=(i.spacing||0)/2;if(e.pixelMargin="inner"===i.borderAlign?.33:0,e.fullCircles=n>_t?Math.floor(n/_t):0,0===n||e.innerRadius<0||e.outerRadius<0)return;t.save();let a=0;if(o){a=o/2;const i=(e.startAngle+e.endAngle)/2;t.translate(Math.cos(i)*a,Math.sin(i)*a),e.circumference>=bt&&(a=o)}t.fillStyle=i.backgroundColor,t.strokeStyle=i.borderColor;const r=function(t,e,i,n){const{fullCircles:o,startAngle:s,circumference:a}=e;let r=e.endAngle;if(o){ko(t,e,i,n,s+_t);for(let e=0;e<o;++e)t.fill();isNaN(a)||(r=s+a%_t,a%_t==0&&(r+=_t))}return ko(t,e,i,n,r),t.fill(),r}(t,e,a,s);So(t,e,a,s,r),t.restore()}}function Do(t,e,i=e){t.lineCap=K(i.borderCapStyle,e.borderCapStyle),t.setLineDash(K(i.borderDash,e.borderDash)),t.lineDashOffset=K(i.borderDashOffset,e.borderDashOffset),t.lineJoin=K(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=K(i.borderWidth,e.borderWidth),t.strokeStyle=K(i.borderColor,e.borderColor)}function Co(t,e,i){t.lineTo(i.x,i.y)}function Oo(t,e,i={}){const n=t.length,{start:o=0,end:s=n-1}=i,{start:a,end:r}=e,l=Math.max(o,a),c=Math.min(s,r),h=o<a&&s<a||o>r&&s>r;return{count:n,start:l,loop:e.loop,ilen:c<l&&!h?n+c-l:c-l}}function To(t,e,i,n){const{points:o,options:s}=e,{count:a,start:r,loop:l,ilen:c}=Oo(o,i,n),h=function(t){return t.stepped?Jt:t.tension||"monotone"===t.cubicInterpolationMode?te:Co}(s);let d,u,f,{move:g=!0,reverse:p}=n||{};for(d=0;d<=c;++d)u=o[(r+(p?c-d:d))%a],u.skip||(g?(t.moveTo(u.x,u.y),g=!1):h(t,f,u,p,s.stepped),f=u);return l&&(u=o[(r+(p?c:0))%a],h(t,f,u,p,s.stepped)),!!l}function Ao(t,e,i,n){const o=e.points,{count:s,start:a,ilen:r}=Oo(o,i,n),{move:l=!0,reverse:c}=n||{};let h,d,u,f,g,p,m=0,x=0;const b=t=>(a+(c?r-t:t))%s,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=o[b(0)],t.moveTo(d.x,d.y)),h=0;h<=r;++h){if(d=o[b(h)],d.skip)continue;const e=d.x,i=d.y,n=0|e;n===u?(i<f?f=i:i>g&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=n,x=0,f=g=i),p=i}_()}function Lo(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?Ao:To}Po.id="arc",Po.defaults={borderAlign:"center",borderColor:"#fff",borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0},Po.defaultRoutes={backgroundColor:"backgroundColor"};const Ro="function"==typeof Path2D;function Eo(t,e,i,n){Ro&&1===e.segments.length?function(t,e,i,n){let o=e._path;o||(o=e._path=new Path2D,e.path(o,i,n)&&o.closePath()),Do(t,e.options),t.stroke(o)}(t,e,i,n):function(t,e,i,n){const{segments:o,options:s}=e,a=Lo(e);for(const r of o)Do(t,s,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+n-1})&&t.closePath(),t.stroke()}(t,e,i,n)}class zo extends zi{constructor(t){super(),this.animated=!0,this.options=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this,n=i.options;if((n.tension||"monotone"===n.cubicInterpolationMode)&&!n.stepped&&!i._pointsUpdated){const o=n.spanGaps?i._loop:i._fullLoop;pn(i._points,n,t,o,e),i._pointsUpdated=!0}}set points(t){const e=this;e._points=t,delete e._segments,delete e._path,e._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Pn(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this,n=i.options,o=t[e],s=i.points,a=Sn(i,{property:e,start:o,end:o});if(!a.length)return;const r=[],l=function(t){return t.stepped?xn:t.tension||"monotone"===t.cubicInterpolationMode?bn:mn}(n);let c,h;for(c=0,h=a.length;c<h;++c){const{start:i,end:h}=a[c],d=s[i],u=s[h];if(d===u){r.push(d);continue}const f=l(d,u,Math.abs((o-d[e])/(u[e]-d[e])),n.stepped);f[e]=t[e],r.push(f)}return 1===r.length?r[0]:r}pathSegment(t,e,i){return Lo(this)(t,this,e,i)}path(t,e,i){const n=this,o=n.segments,s=Lo(n);let a=n._loop;e=e||0,i=i||n.points.length-e;for(const r of o)a&=s(t,n,r,{start:e,end:e+i-1});return!!a}draw(t,e,i,n){const o=this,s=o.options||{};(o.points||[]).length&&s.borderWidth&&(t.save(),Eo(t,o,i,n),t.restore(),o.animated&&(o._pointsUpdated=!1,o._path=void 0))}}function Io(t,e,i,n){const o=t.options,{[i]:s}=t.getProps([i],n);return Math.abs(e-s)<o.radius+o.hitRadius}zo.id="line",zo.defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0},zo.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"},zo.descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};class Fo extends zi{constructor(t){super(),this.options=void 0,this.parsed=void 0,this.skip=void 0,this.stop=void 0,t&&Object.assign(this,t)}inRange(t,e,i){const n=this.options,{x:o,y:s}=this.getProps(["x","y"],i);return Math.pow(t-o,2)+Math.pow(e-s,2)<Math.pow(n.hitRadius+n.radius,2)}inXRange(t,e){return Io(this,t,"x",e)}inYRange(t,e){return Io(this,t,"y",e)}getCenterPoint(t){const{x:e,y:i}=this.getProps(["x","y"],t);return{x:e,y:i}}size(t){let e=(t=t||this.options||{}).radius||0;e=Math.max(e,e&&t.hoverRadius||0);return 2*(e+(e&&t.borderWidth||0))}draw(t){const e=this,i=e.options;e.skip||i.radius<.1||(t.strokeStyle=i.borderColor,t.lineWidth=i.borderWidth,t.fillStyle=i.backgroundColor,Kt(t,i,e.x,e.y))}getRange(){const t=this.options||{};return t.radius+t.hitRadius}}function Bo(t,e){const{x:i,y:n,base:o,width:s,height:a}=t.getProps(["x","y","base","width","height"],e);let r,l,c,h,d;return t.horizontal?(d=a/2,r=Math.min(i,o),l=Math.max(i,o),c=n-d,h=n+d):(d=s/2,r=i-d,l=i+d,c=Math.min(n,o),h=Math.max(n,o)),{left:r,top:c,right:l,bottom:h}}function Vo(t){let e=t.options.borderSkipped;const i={};return e?(e=t.horizontal?Wo(e,"left","right",t.base>t.x):Wo(e,"bottom","top",t.base<t.y),i[e]=!0,i):i}function Wo(t,e,i,n){var o,s,a;return n?(a=i,t=No(t=(o=t)===(s=e)?a:o===a?s:o,i,e)):t=No(t,e,i),t}function No(t,e,i){return"start"===t?e:"end"===t?i:t}function Ho(t,e,i,n){return t?0:Math.max(Math.min(e,n),i)}function jo(t){const e=Bo(t),i=e.right-e.left,n=e.bottom-e.top,o=function(t,e,i){const n=t.options.borderWidth,o=Vo(t),s=ze(n);return{t:Ho(o.top,s.top,0,i),r:Ho(o.right,s.right,0,e),b:Ho(o.bottom,s.bottom,0,i),l:Ho(o.left,s.left,0,e)}}(t,i/2,n/2),s=function(t,e,i){const{enableBorderRadius:n}=t.getProps(["enableBorderRadius"]),o=t.options.borderRadius,s=Ie(o),a=Math.min(e,i),r=Vo(t),l=n||U(o);return{topLeft:Ho(!l||r.top||r.left,s.topLeft,0,a),topRight:Ho(!l||r.top||r.right,s.topRight,0,a),bottomLeft:Ho(!l||r.bottom||r.left,s.bottomLeft,0,a),bottomRight:Ho(!l||r.bottom||r.right,s.bottomRight,0,a)}}(t,i/2,n/2);return{outer:{x:e.left,y:e.top,w:i,h:n,radius:s},inner:{x:e.left+o.l,y:e.top+o.t,w:i-o.l-o.r,h:n-o.t-o.b,radius:{topLeft:Math.max(0,s.topLeft-Math.max(o.t,o.l)),topRight:Math.max(0,s.topRight-Math.max(o.t,o.r)),bottomLeft:Math.max(0,s.bottomLeft-Math.max(o.b,o.l)),bottomRight:Math.max(0,s.bottomRight-Math.max(o.b,o.r))}}}}function $o(t,e,i,n){const o=null===e,s=null===i,a=t&&!(o&&s)&&Bo(t,n);return a&&(o||e>=a.left&&e<=a.right)&&(s||i>=a.top&&i<=a.bottom)}function Yo(t,e){t.rect(e.x,e.y,e.w,e.h)}Fo.id="point",Fo.defaults={borderWidth:1,hitRadius:1,hoverBorderWidth:1,hoverRadius:4,pointStyle:"circle",radius:3,rotation:0},Fo.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};class Uo extends zi{constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const e=this.options,{inner:i,outer:n}=jo(this),o=(s=n.radius).topLeft||s.topRight||s.bottomLeft||s.bottomRight?ne:Yo;var s;t.save(),n.w===i.w&&n.h===i.h||(t.beginPath(),o(t,n),t.clip(),o(t,i),t.fillStyle=e.borderColor,t.fill("evenodd")),t.beginPath(),o(t,i),t.fillStyle=e.backgroundColor,t.fill(),t.restore()}inRange(t,e,i){return $o(this,t,e,i)}inXRange(t,e){return $o(this,t,null,e)}inYRange(t,e){return $o(this,null,t,e)}getCenterPoint(t){const{x:e,y:i,base:n,horizontal:o}=this.getProps(["x","y","base","horizontal"],t);return{x:o?(e+n)/2:e,y:o?i:(i+n)/2}}getRange(t){return"x"===t?this.width/2:this.height/2}}Uo.id="bar",Uo.defaults={borderSkipped:"start",borderWidth:0,borderRadius:0,enableBorderRadius:!0,pointStyle:void 0},Uo.defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};var Xo=Object.freeze({__proto__:null,ArcElement:Po,LineElement:zo,PointElement:Fo,BarElement:Uo});function qo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{value:e})}}function Ko(t){t.data.datasets.forEach((t=>{qo(t)}))}var Go={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Ko(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:s,indexAxis:a}=e,r=t.getDatasetMeta(o),l=s||e.data;if("y"===Ve([a,t.options.indexAxis]))return;if("line"!==r.type)return;const c=t.scales[r.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let h,{start:d,count:u}=function(t,e){const i=e.length;let n,o=0;const{iScale:s}=t,{min:a,max:r,minDefined:l,maxDefined:c}=s.getUserBounds();return l&&(o=Ht(se(e,s.axis,a).lo,0,i-1)),n=c?Ht(se(e,s.axis,r).hi+1,o,i)-o:i-o,{start:o,count:n}}(r,l);if(u<=4*n)qo(e);else{switch($(s)&&(e._data=l,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":h=function(t,e,i,n,o){const s=o.samples||n;if(s>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(s-2);let l=0;const c=e+i-1;let h,d,u,f,g,p=e;for(a[l++]=t[p],h=0;h<s-2;h++){let n,o=0,s=0;const c=Math.floor((h+1)*r)+1+e,m=Math.min(Math.floor((h+2)*r)+1,i)+e,x=m-c;for(n=c;n<m;n++)o+=t[n].x,s+=t[n].y;o/=x,s/=x;const b=Math.floor(h*r)+1+e,_=Math.floor((h+1)*r)+1+e,{x:y,y:v}=t[p];for(u=f=-1,n=b;n<_;n++)f=.5*Math.abs((y-o)*(t[n].y-v)-(y-t[n].x)*(s-v)),f>u&&(u=f,d=t[n],g=n);a[l++]=d,p=g}return a[l++]=t[c],a}(l,d,u,n,i);break;case"min-max":h=function(t,e,i,n){let o,s,a,r,l,c,h,d,u,f,g=0,p=0;const m=[],x=e+i-1,b=t[e].x,_=t[x].x-b;for(o=e;o<e+i;++o){s=t[o],a=(s.x-b)/_*n,r=s.y;const e=0|a;if(e===l)r<u?(u=r,c=o):r>f&&(f=r,h=o),g=(p*g+s.x)/++p;else{const i=o-1;if(!$(c)&&!$(h)){const e=Math.min(c,h),n=Math.max(c,h);e!==d&&e!==i&&m.push({...t[e],x:g}),n!==d&&n!==i&&m.push({...t[n],x:g})}o>0&&i!==d&&m.push(t[i]),m.push(s),l=e,p=0,u=f=r,c=h=d=o}}return m}(l,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=h}}))},destroy(t){Ko(t)}};function Zo(t,e,i){const n=function(t){const e=t.options,i=e.fill;let n=K(i&&i.target,i);return void 0===n&&(n=!!e.backgroundColor),!1!==n&&null!==n&&(!0===n?"origin":n)}(t);if(U(n))return!isNaN(n.value)&&n;let o=parseFloat(n);return X(o)&&Math.floor(o)===o?("-"!==n[0]&&"+"!==n[0]||(o=e+o),!(o===e||o<0||o>=i)&&o):["origin","start","end","stack"].indexOf(n)>=0&&n}class Qo{constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}pathSegment(t,e,i){const{x:n,y:o,radius:s}=this;return e=e||{start:0,end:_t},t.arc(n,o,s,e.end,e.start,!0),!i.bounds}interpolate(t){const{x:e,y:i,radius:n}=this,o=t.angle;return{x:e+Math.cos(o)*n,y:i+Math.sin(o)*n,angle:o}}}function Jo(t){return(t.scale||{}).getPointPositionForValue?function(t){const{scale:e,fill:i}=t,n=e.options,o=e.getLabels().length,s=[],a=n.reverse?e.max:e.min,r=n.reverse?e.min:e.max;let l,c,h;if(h="start"===i?a:"end"===i?r:U(i)?i.value:e.getBaseValue(),n.grid.circular)return c=e.getPointPositionForValue(0,a),new Qo({x:c.x,y:c.y,radius:e.getDistanceFromCenterForValue(h)});for(l=0;l<o;++l)s.push(e.getPointPositionForValue(l,h));return s}(t):function(t){const{scale:e={},fill:i}=t;let n,o=null;return"start"===i?o=e.bottom:"end"===i?o=e.top:U(i)?o=e.getPixelForValue(i.value):e.getBasePixel&&(o=e.getBasePixel()),X(o)?(n=e.isHorizontal(),{x:n?o:null,y:n?null:o}):null}(t)}function ts(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function es(t){const{chart:e,scale:i,index:n,line:o}=t,s=[],a=o.segments,r=o.points,l=function(t,e){const i=[],n=t.getSortedVisibleDatasetMetas();for(let t=0;t<n.length;t++){const o=n[t];if(o.index===e)break;is(o)&&i.unshift(o.dataset)}return i}(e,n);l.push(ss({x:null,y:i.bottom},o));for(let t=0;t<a.length;t++){const e=a[t];for(let t=e.start;t<=e.end;t++)ns(s,r[t],l)}return new zo({points:s,options:{}})}const is=t=>"line"===t.type&&!t.hidden;function ns(t,e,i){const n=[];for(let o=0;o<i.length;o++){const s=i[o],{first:a,last:r,point:l}=os(s,e,"x");if(!(!l||a&&r))if(a)n.unshift(l);else if(t.push(l),!r)break}t.push(...n)}function os(t,e,i){const n=t.interpolate(e,i);if(!n)return{};const o=n[i],s=t.segments,a=t.points;let r=!1,l=!1;for(let t=0;t<s.length;t++){const e=s[t],n=a[e.start][i],c=a[e.end][i];if(o>=n&&o<=c){r=o===n,l=o===c;break}}return{first:r,last:l,point:n}}function ss(t,e){let i=[],n=!1;return Y(t)?(n=!0,i=t):i=function(t,e){const{x:i=null,y:n=null}=t||{},o=e.points,s=[];return e.segments.forEach((({start:t,end:e})=>{e=ts(t,e,o);const a=o[t],r=o[e];null!==n?(s.push({x:a.x,y:n}),s.push({x:r.x,y:n})):null!==i&&(s.push({x:i,y:a.y}),s.push({x:i,y:r.y}))})),s}(t,e),i.length?new zo({points:i,options:{tension:0},_loop:n,_fullLoop:n}):null}function as(t,e,i){let n=t[e].fill;const o=[e];let s;if(!i)return n;for(;!1!==n&&-1===o.indexOf(n);){if(!X(n))return n;if(s=t[n],!s)return!1;if(s.visible)return n;o.push(n),n=s.fill}return!1}function rs(t,e,i){t.beginPath(),e.path(t),t.lineTo(e.last().x,i),t.lineTo(e.first().x,i),t.closePath(),t.clip()}function ls(t,e,i,n){if(n)return;let o=e[t],s=i[t];return"angle"===t&&(o=Wt(o),s=Wt(s)),{property:t,start:o,end:s}}function cs(t,e,i,n){return t&&e?n(t[i],e[i]):t?t[i]:e?e[i]:0}function hs(t,e,i){const{top:n,bottom:o}=e.chart.chartArea,{property:s,start:a,end:r}=i||{};"x"===s&&(t.beginPath(),t.rect(a,n,r-a,o-n),t.clip())}function ds(t,e,i,n){const o=e.interpolate(i,n);o&&t.lineTo(o.x,o.y)}function us(t,e){const{line:i,target:n,property:o,color:s,scale:a}=e,r=function(t,e,i){const n=t.segments,o=t.points,s=e.points,a=[];for(const t of n){let{start:n,end:r}=t;r=ts(n,r,o);const l=ls(i,o[n],o[r],t.loop);if(!e.segments){a.push({source:t,target:l,start:o[n],end:o[r]});continue}const c=Sn(e,l);for(const e of c){const n=ls(i,s[e.start],s[e.end],e.loop),r=kn(t,o,n);for(const t of r)a.push({source:t,target:e,start:{[i]:cs(l,n,"start",Math.max)},end:{[i]:cs(l,n,"end",Math.min)}})}}return a}(i,n,o);for(const{source:e,target:l,start:c,end:h}of r){const{style:{backgroundColor:r=s}={}}=e;t.save(),t.fillStyle=r,hs(t,a,ls(o,c,h)),t.beginPath();const d=!!i.pathSegment(t,e);d?t.closePath():ds(t,n,h,o);const u=!!n.pathSegment(t,l,{move:d,reverse:!0}),f=d&&u;f||ds(t,n,c,o),t.closePath(),t.fill(f?"evenodd":"nonzero"),t.restore()}}function fs(t,e,i){const n=function(t){const{chart:e,fill:i,line:n}=t;if(X(i))return function(t,e){const i=t.getDatasetMeta(e);return i&&t.isDatasetVisible(e)?i.dataset:null}(e,i);if("stack"===i)return es(t);const o=Jo(t);return o instanceof Qo?o:ss(o,n)}(e),{line:o,scale:s,axis:a}=e,r=o.options,l=r.fill,c=r.backgroundColor,{above:h=c,below:d=c}=l||{};n&&o.points.length&&(Zt(t,i),function(t,e){const{line:i,target:n,above:o,below:s,area:a,scale:r}=e,l=i._loop?"angle":e.axis;t.save(),"x"===l&&s!==o&&(rs(t,n,a.top),us(t,{line:i,target:n,color:o,scale:r,property:l}),t.restore(),t.save(),rs(t,n,a.bottom)),us(t,{line:i,target:n,color:s,scale:r,property:l}),t.restore()}(t,{line:o,target:n,above:h,below:d,area:i,scale:s,axis:a}),Qt(t))}var gs={id:"filler",afterDatasetsUpdate(t,e,i){const n=(t.data.datasets||[]).length,o=[];let s,a,r,l;for(a=0;a<n;++a)s=t.getDatasetMeta(a),r=s.dataset,l=null,r&&r.options&&r instanceof zo&&(l={visible:t.isDatasetVisible(a),index:a,fill:Zo(r,a,n),chart:t,axis:s.controller.options.indexAxis,scale:s.vScale,line:r}),s.$filler=l,o.push(l);for(a=0;a<n;++a)l=o[a],l&&!1!==l.fill&&(l.fill=as(o,a,i.propagate))},beforeDraw(t,e,i){const n="beforeDraw"===i.drawTime,o=t.getSortedVisibleDatasetMetas(),s=t.chartArea;for(let e=o.length-1;e>=0;--e){const i=o[e].$filler;i&&(i.line.updateControlPoints(s,i.axis),n&&fs(t.ctx,i,s))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const n=t.getSortedVisibleDatasetMetas();for(let e=n.length-1;e>=0;--e){const i=n[e].$filler;i&&fs(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const n=e.meta.$filler;n&&!1!==n.fill&&"beforeDatasetDraw"===i.drawTime&&fs(t.ctx,n,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ps=(t,e)=>{let{boxHeight:i=e,boxWidth:n=e}=t;return t.usePointStyle&&(i=Math.min(i,e),n=Math.min(n,e)),{boxWidth:n,boxHeight:i,itemHeight:Math.max(e,i)}};class ms extends zi{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){const n=this;n.maxWidth=t,n.maxHeight=e,n._margins=i,n.setDimensions(),n.buildLabels(),n.fit()}setDimensions(){const t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=t._margins.left,t.right=t.width):(t.height=t.maxHeight,t.top=t._margins.top,t.bottom=t.height)}buildLabels(){const t=this,e=t.options.labels||{};let i=Q(e.generateLabels,[t.chart],t)||[];e.filter&&(i=i.filter((i=>e.filter(i,t.chart.data)))),e.sort&&(i=i.sort(((i,n)=>e.sort(i,n,t.chart.data)))),t.options.reverse&&i.reverse(),t.legendItems=i}fit(){const t=this,{options:e,ctx:i}=t;if(!e.display)return void(t.width=t.height=0);const n=e.labels,o=Be(n.font),s=o.size,a=t._computeTitleHeight(),{boxWidth:r,itemHeight:l}=ps(n,s);let c,h;i.font=o.string,t.isHorizontal()?(c=t.maxWidth,h=t._fitRows(a,s,r,l)+10):(h=t.maxHeight,c=t._fitCols(a,s,r,l)+10),t.width=Math.min(c,e.maxWidth||t.maxWidth),t.height=Math.min(h,e.maxHeight||t.maxHeight)}_fitRows(t,e,i,n){const o=this,{ctx:s,maxWidth:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.lineWidths=[0],h=n+r;let d=t;s.textAlign="left",s.textBaseline="middle";let u=-1,f=-h;return o.legendItems.forEach(((t,o)=>{const g=i+e/2+s.measureText(t.text).width;(0===o||c[c.length-1]+g+2*r>a)&&(d+=h,c[c.length-(o>0?0:1)]=0,f+=h,u++),l[o]={left:0,top:f,row:u,width:g,height:n},c[c.length-1]+=g+r})),d}_fitCols(t,e,i,n){const o=this,{ctx:s,maxHeight:a,options:{labels:{padding:r}}}=o,l=o.legendHitBoxes=[],c=o.columnSizes=[],h=a-t;let d=r,u=0,f=0,g=0,p=0;return o.legendItems.forEach(((t,o)=>{const a=i+e/2+s.measureText(t.text).width;o>0&&f+n+2*r>h&&(d+=u+r,c.push({width:u,height:f}),g+=u+r,p++,u=f=0),l[o]={left:g,top:f,col:p,width:a,height:n},u=Math.max(u,a),f+=n+r})),d+=u,c.push({width:u,height:f}),d}adjustHitBoxes(){const t=this;if(!t.options.display)return;const e=t._computeTitleHeight(),{legendHitBoxes:i,options:{align:n,labels:{padding:s},rtl:a}}=t;if(this.isHorizontal()){let r=0,l=o(n,t.left+s,t.right-t.lineWidths[r]);for(const a of i)r!==a.row&&(r=a.row,l=o(n,t.left+s,t.right-t.lineWidths[r])),a.top+=t.top+e+s,a.left=l,l+=a.width+s;if(a){const e=i.reduce(((t,e)=>(t[e.row]=t[e.row]||[],t[e.row].push(e),t)),{}),n=[];Object.keys(e).forEach((t=>{e[t].reverse(),n.push(...e[t])})),t.legendHitBoxes=n}}else{let a=0,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height);for(const l of i)l.col!==a&&(a=l.col,r=o(n,t.top+e+s,t.bottom-t.columnSizes[a].height)),l.top=r,l.left+=t.left+s,r+=l.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){const t=this;if(t.options.display){const e=t.ctx;Zt(e,t),t._draw(),Qt(e)}}_draw(){const t=this,{options:e,columnSizes:i,lineWidths:n,ctx:a}=t,{align:r,labels:l}=e,c=xt.color,h=_n(e.rtl,t.left,t.width),d=Be(l.font),{color:u,padding:f}=l,g=d.size,p=g/2;let m;t.drawTitle(),a.textAlign=h.textAlign("left"),a.textBaseline="middle",a.lineWidth=.5,a.font=d.string;const{boxWidth:x,boxHeight:b,itemHeight:_}=ps(l,g),y=t.isHorizontal(),v=this._computeTitleHeight();m=y?{x:o(r,t.left+f,t.right-n[0]),y:t.top+f+v,line:0}:{x:t.left+f,y:o(r,t.top+v+f,t.bottom-i[0].height),line:0},yn(t.ctx,e.textDirection);const w=_+f;t.legendItems.forEach(((M,k)=>{a.strokeStyle=M.fontColor||u,a.fillStyle=M.fontColor||u;const S=a.measureText(M.text).width,P=h.textAlign(M.textAlign||(M.textAlign=l.textAlign)),D=x+p+S;let C=m.x,O=m.y;h.setWidth(t.width),y?k>0&&C+D+f>t.right&&(O=m.y+=w,m.line++,C=m.x=o(r,t.left+f,t.right-n[m.line])):k>0&&O+w>t.bottom&&(C=m.x=C+i[m.line].width+f,m.line++,O=m.y=o(r,t.top+v+f,t.bottom-i[m.line].height));!function(t,e,i){if(isNaN(x)||x<=0||isNaN(b)||b<0)return;a.save();const n=K(i.lineWidth,1);if(a.fillStyle=K(i.fillStyle,c),a.lineCap=K(i.lineCap,"butt"),a.lineDashOffset=K(i.lineDashOffset,0),a.lineJoin=K(i.lineJoin,"miter"),a.lineWidth=n,a.strokeStyle=K(i.strokeStyle,c),a.setLineDash(K(i.lineDash,[])),l.usePointStyle){const o={radius:x*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},s=h.xPlus(t,x/2);Kt(a,o,s,e+p)}else{const o=e+Math.max((g-b)/2,0),s=h.leftForLtr(t,x),r=Ie(i.borderRadius);a.beginPath(),Object.values(r).some((t=>0!==t))?ne(a,{x:s,y:o,w:x,h:b,radius:r}):a.rect(s,o,x,b),a.fill(),0!==n&&a.stroke()}a.restore()}(h.x(C),O,M),C=s(P,C+x+p,y?C+D:t.right,e.rtl),function(t,e,i){ee(a,i.text,t,e+_/2,d,{strikethrough:i.hidden,textAlign:h.textAlign(i.textAlign)})}(h.x(C),O,M),y?m.x+=D+f:m.y+=w})),vn(t.ctx,e.textDirection)}drawTitle(){const t=this,e=t.options,i=e.title,s=Be(i.font),a=Fe(i.padding);if(!i.display)return;const r=_n(e.rtl,t.left,t.width),l=t.ctx,c=i.position,h=s.size/2,d=a.top+h;let u,f=t.left,g=t.width;if(this.isHorizontal())g=Math.max(...t.lineWidths),u=t.top+d,f=o(e.align,f,t.right-g);else{const i=t.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);u=d+o(e.align,t.top,t.bottom-i-e.labels.padding-t._computeTitleHeight())}const p=o(c,f,f+g);l.textAlign=r.textAlign(n(c)),l.textBaseline="middle",l.strokeStyle=i.color,l.fillStyle=i.color,l.font=s.string,ee(l,i.text,p,u,s)}_computeTitleHeight(){const t=this.options.title,e=Be(t.font),i=Fe(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){const i=this;let n,o,s;if(t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom)for(s=i.legendHitBoxes,n=0;n<s.length;++n)if(o=s[n],t>=o.left&&t<=o.left+o.width&&e>=o.top&&e<=o.top+o.height)return i.legendItems[n];return null}handleEvent(t){const e=this,i=e.options;if(!function(t,e){if("mousemove"===t&&(e.onHover||e.onLeave))return!0;if(e.onClick&&("click"===t||"mouseup"===t))return!0;return!1}(t.type,i))return;const n=e._getLegendItemAt(t.x,t.y);if("mousemove"===t.type){const a=e._hoveredItem,r=(s=n,null!==(o=a)&&null!==s&&o.datasetIndex===s.datasetIndex&&o.index===s.index);a&&!r&&Q(i.onLeave,[t,a,e],e),e._hoveredItem=n,n&&!r&&Q(i.onHover,[t,n,e],e)}else n&&Q(i.onClick,[t,n,e],e);var o,s}}var xs={id:"legend",_element:ms,start(t,e,i){const n=t.legend=new ms({ctx:t.ctx,options:i,chart:t});Ze.configure(t,n,i),Ze.addBox(t,n)},stop(t){Ze.removeBox(t,t.legend),delete t.legend},beforeUpdate(t,e,i){const n=t.legend;Ze.configure(t,n,i),n.options=i},afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()},afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(t,e,i){const n=e.datasetIndex,o=i.chart;o.isDatasetVisible(n)?(o.hide(n),e.hidden=!0):(o.show(n),e.hidden=!1)},onHover:null,onLeave:null,labels:{color:t=>t.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:n,textAlign:o,color:s}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const a=t.controller.getStyle(i?0:void 0),r=Fe(a.borderWidth);return{text:e[t.index].label,fillStyle:a.backgroundColor,fontColor:s,hidden:!t.visible,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:(r.width+r.height)/4,strokeStyle:a.borderColor,pointStyle:n||a.pointStyle,rotation:a.rotation,textAlign:o||a.textAlign,borderRadius:0,datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class bs extends zi{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this,n=i.options;if(i.left=0,i.top=0,!n.display)return void(i.width=i.height=i.right=i.bottom=0);i.width=i.right=t,i.height=i.bottom=e;const o=Y(n.text)?n.text.length:1;i._padding=Fe(n.padding);const s=o*Be(n.font).lineHeight+i._padding.height;i.isHorizontal()?i.height=s:i.width=s}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:n,right:s,options:a}=this,r=a.align;let l,c,h,d=0;return this.isHorizontal()?(c=o(r,i,s),h=e+t,l=s-i):("left"===a.position?(c=i+t,h=o(r,n,e),d=-.5*bt):(c=s-t,h=o(r,e,n),d=.5*bt),l=n-e),{titleX:c,titleY:h,maxWidth:l,rotation:d}}draw(){const t=this,e=t.ctx,i=t.options;if(!i.display)return;const o=Be(i.font),s=o.lineHeight/2+t._padding.top,{titleX:a,titleY:r,maxWidth:l,rotation:c}=t._drawArgs(s);ee(e,i.text,0,0,o,{color:i.color,maxWidth:l,rotation:c,textAlign:n(i.align),textBaseline:"middle",translation:[a,r]})}}var _s={id:"title",_element:bs,start(t,e,i){!function(t,e){const i=new bs({ctx:t.ctx,options:e,chart:t});Ze.configure(t,i,e),Ze.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;Ze.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const n=t.titleBlock;Ze.configure(t,n,i),n.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const ys=new WeakMap;var vs={id:"subtitle",start(t,e,i){const n=new bs({ctx:t.ctx,options:i,chart:t});Ze.configure(t,n,i),Ze.addBox(t,n),ys.set(t,n)},stop(t){Ze.removeBox(t,ys.get(t)),ys.delete(t)},beforeUpdate(t,e,i){const n=ys.get(t);Ze.configure(t,n,i),n.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const ws={average(t){if(!t.length)return!1;let e,i,n=0,o=0,s=0;for(e=0,i=t.length;e<i;++e){const i=t[e].element;if(i&&i.hasValue()){const t=i.tooltipPosition();n+=t.x,o+=t.y,++s}}return{x:n/s,y:o/s}},nearest(t,e){if(!t.length)return!1;let i,n,o,s=e.x,a=e.y,r=Number.POSITIVE_INFINITY;for(i=0,n=t.length;i<n;++i){const n=t[i].element;if(n&&n.hasValue()){const t=Bt(e,n.getCenterPoint());t<r&&(r=t,o=n)}}if(o){const t=o.tooltipPosition();s=t.x,a=t.y}return{x:s,y:a}}};function Ms(t,e){return e&&(Y(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function ks(t){return("string"==typeof t||t instanceof String)&&t.indexOf("\n")>-1?t.split("\n"):t}function Ss(t,e){const{element:i,datasetIndex:n,index:o}=e,s=t.getDatasetMeta(n).controller,{label:a,value:r}=s.getLabelAndValue(o);return{chart:t,label:a,parsed:s.getParsed(o),raw:t.data.datasets[n].data[o],formattedValue:r,dataset:s.getDataset(),dataIndex:o,datasetIndex:n,element:i}}function Ps(t,e){const i=t._chart.ctx,{body:n,footer:o,title:s}=t,{boxWidth:a,boxHeight:r}=e,l=Be(e.bodyFont),c=Be(e.titleFont),h=Be(e.footerFont),d=s.length,u=o.length,f=n.length,g=Fe(e.padding);let p=g.height,m=0,x=n.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(p+=d*c.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){p+=f*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-f)*l.lineHeight+(x-1)*e.bodySpacing}u&&(p+=e.footerMarginTop+u*h.lineHeight+(u-1)*e.footerSpacing);let b=0;const _=function(t){m=Math.max(m,i.measureText(t).width+b)};return i.save(),i.font=c.string,J(t.title,_),i.font=l.string,J(t.beforeBody.concat(t.afterBody),_),b=e.displayColors?a+2:0,J(n,(t=>{J(t.before,_),J(t.lines,_),J(t.after,_)})),b=0,i.font=h.string,J(t.footer,_),i.restore(),m+=g.width,{width:m,height:p}}function Ds(t,e,i,n){const{x:o,width:s}=i,{width:a,chartArea:{left:r,right:l}}=t;let c="center";return"center"===n?c=o<=(r+l)/2?"left":"right":o<=s/2?c="left":o>=a-s/2&&(c="right"),function(t,e,i,n){const{x:o,width:s}=n,a=i.caretSize+i.caretPadding;return"left"===t&&o+s+a>e.width||"right"===t&&o-s-a<0||void 0}(c,t,e,i)&&(c="center"),c}function Cs(t,e,i){const n=e.yAlign||function(t,e){const{y:i,height:n}=e;return i<n/2?"top":i>t.height-n/2?"bottom":"center"}(t,i);return{xAlign:e.xAlign||Ds(t,e,i,n),yAlign:n}}function Os(t,e,i,n){const{caretSize:o,caretPadding:s,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,c=o+s,h=a+s;let d=function(t,e){let{x:i,width:n}=t;return"right"===e?i-=n:"center"===e&&(i-=n/2),i}(e,r);const u=function(t,e,i){let{y:n,height:o}=t;return"top"===e?n+=i:n-="bottom"===e?o+i:o/2,n}(e,l,c);return"center"===l?"left"===r?d+=c:"right"===r&&(d-=c):"left"===r?d-=h:"right"===r&&(d+=h),{x:Ht(d,0,n.width-e.width),y:Ht(u,0,n.height-e.height)}}function Ts(t,e,i){const n=Fe(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-n.right:t.x+n.left}function As(t){return Ms([],ks(t))}function Ls(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}class Rs extends zi{constructor(t){super(),this.opacity=0,this._active=[],this._chart=t._chart,this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this,e=t._cachedAnimations;if(e)return e;const i=t._chart,n=t.options.setContext(t.getContext()),o=n.enabled&&i.options.animation&&n.animations,s=new wi(t._chart,o);return o._cacheable&&(t._cachedAnimations=Object.freeze(s)),s}getContext(){const t=this;return t.$context||(t.$context=(e=t._chart.getContext(),i=t,n=t._tooltipItems,Object.assign(Object.create(e),{tooltip:i,tooltipItems:n,type:"tooltip"})));var e,i,n}getTitle(t,e){const i=this,{callbacks:n}=e,o=n.beforeTitle.apply(i,[t]),s=n.title.apply(i,[t]),a=n.afterTitle.apply(i,[t]);let r=[];return r=Ms(r,ks(o)),r=Ms(r,ks(s)),r=Ms(r,ks(a)),r}getBeforeBody(t,e){return As(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){const i=this,{callbacks:n}=e,o=[];return J(t,(t=>{const e={before:[],lines:[],after:[]},s=Ls(n,t);Ms(e.before,ks(s.beforeLabel.call(i,t))),Ms(e.lines,s.label.call(i,t)),Ms(e.after,ks(s.afterLabel.call(i,t))),o.push(e)})),o}getAfterBody(t,e){return As(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){const i=this,{callbacks:n}=e,o=n.beforeFooter.apply(i,[t]),s=n.footer.apply(i,[t]),a=n.afterFooter.apply(i,[t]);let r=[];return r=Ms(r,ks(o)),r=Ms(r,ks(s)),r=Ms(r,ks(a)),r}_createItems(t){const e=this,i=e._active,n=e._chart.data,o=[],s=[],a=[];let r,l,c=[];for(r=0,l=i.length;r<l;++r)c.push(Ss(e._chart,i[r]));return t.filter&&(c=c.filter(((e,i,o)=>t.filter(e,i,o,n)))),t.itemSort&&(c=c.sort(((e,i)=>t.itemSort(e,i,n)))),J(c,(i=>{const n=Ls(t.callbacks,i);o.push(n.labelColor.call(e,i)),s.push(n.labelPointStyle.call(e,i)),a.push(n.labelTextColor.call(e,i))})),e.labelColors=o,e.labelPointStyles=s,e.labelTextColors=a,e.dataPoints=c,c}update(t,e){const i=this,n=i.options.setContext(i.getContext()),o=i._active;let s,a=[];if(o.length){const t=ws[n.position].call(i,o,i._eventPosition);a=i._createItems(n),i.title=i.getTitle(a,n),i.beforeBody=i.getBeforeBody(a,n),i.body=i.getBody(a,n),i.afterBody=i.getAfterBody(a,n),i.footer=i.getFooter(a,n);const e=i._size=Ps(i,n),r=Object.assign({},t,e),l=Cs(i._chart,n,r),c=Os(n,r,l,i._chart);i.xAlign=l.xAlign,i.yAlign=l.yAlign,s={opacity:1,x:c.x,y:c.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==i.opacity&&(s={opacity:0});i._tooltipItems=a,i.$context=void 0,s&&i._resolveAnimations().update(i,s),t&&n.external&&n.external.call(i,{chart:i._chart,tooltip:i,replay:e})}drawCaret(t,e,i,n){const o=this.getCaretPosition(t,i,n);e.lineTo(o.x1,o.y1),e.lineTo(o.x2,o.y2),e.lineTo(o.x3,o.y3)}getCaretPosition(t,e,i){const{xAlign:n,yAlign:o}=this,{cornerRadius:s,caretSize:a}=i,{x:r,y:l}=t,{width:c,height:h}=e;let d,u,f,g,p,m;return"center"===o?(p=l+h/2,"left"===n?(d=r,u=d-a,g=p+a,m=p-a):(d=r+c,u=d+a,g=p-a,m=p+a),f=d):(u="left"===n?r+s+a:"right"===n?r+c-s-a:this.caretX,"top"===o?(g=l,p=g-a,d=u-a,f=u+a):(g=l+h,p=g+a,d=u+a,f=u-a),m=g),{x1:d,x2:u,x3:f,y1:g,y2:p,y3:m}}drawTitle(t,e,i){const n=this,o=n.title,s=o.length;let a,r,l;if(s){const c=_n(i.rtl,n.x,n.width);for(t.x=Ts(n,i.titleAlign,i),e.textAlign=c.textAlign(i.titleAlign),e.textBaseline="middle",a=Be(i.titleFont),r=i.titleSpacing,e.fillStyle=i.titleColor,e.font=a.string,l=0;l<s;++l)e.fillText(o[l],c.x(t.x),t.y+a.lineHeight/2),t.y+=a.lineHeight+r,l+1===s&&(t.y+=i.titleMarginBottom-r)}}_drawColorBox(t,e,i,n,o){const s=this,a=s.labelColors[i],r=s.labelPointStyles[i],{boxHeight:l,boxWidth:c}=o,h=Be(o.bodyFont),d=Ts(s,"left",o),u=n.x(d),f=l<h.lineHeight?(h.lineHeight-l)/2:0,g=e.y+f;if(o.usePointStyle){const e={radius:Math.min(c,l)/2,pointStyle:r.pointStyle,rotation:r.rotation,borderWidth:1},i=n.leftForLtr(u,c)+c/2,s=g+l/2;t.strokeStyle=o.multiKeyBackground,t.fillStyle=o.multiKeyBackground,Kt(t,e,i,s),t.strokeStyle=a.borderColor,t.fillStyle=a.backgroundColor,Kt(t,e,i,s)}else{t.lineWidth=a.borderWidth||1,t.strokeStyle=a.borderColor,t.setLineDash(a.borderDash||[]),t.lineDashOffset=a.borderDashOffset||0;const e=n.leftForLtr(u,c),i=n.leftForLtr(n.xPlus(u,1),c-2),s=Ie(a.borderRadius);Object.values(s).some((t=>0!==t))?(t.beginPath(),t.fillStyle=o.multiKeyBackground,ne(t,{x:e,y:g,w:c,h:l,radius:s}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),ne(t,{x:i,y:g+1,w:c-2,h:l-2,radius:s}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(e,g,c,l),t.strokeRect(e,g,c,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,c-2,l-2))}t.fillStyle=s.labelTextColors[i]}drawBody(t,e,i){const n=this,{body:o}=n,{bodySpacing:s,bodyAlign:a,displayColors:r,boxHeight:l,boxWidth:c}=i,h=Be(i.bodyFont);let d=h.lineHeight,u=0;const f=_n(i.rtl,n.x,n.width),g=function(i){e.fillText(i,f.x(t.x+u),t.y+d/2),t.y+=d+s},p=f.textAlign(a);let m,x,b,_,y,v,w;for(e.textAlign=a,e.textBaseline="middle",e.font=h.string,t.x=Ts(n,p,i),e.fillStyle=i.bodyColor,J(n.beforeBody,g),u=r&&"right"!==p?"center"===a?c/2+1:c+2:0,_=0,v=o.length;_<v;++_){for(m=o[_],x=n.labelTextColors[_],e.fillStyle=x,J(m.before,g),b=m.lines,r&&b.length&&(n._drawColorBox(e,t,_,f,i),d=Math.max(h.lineHeight,l)),y=0,w=b.length;y<w;++y)g(b[y]),d=h.lineHeight;J(m.after,g)}u=0,d=h.lineHeight,J(n.afterBody,g),t.y-=s}drawFooter(t,e,i){const n=this,o=n.footer,s=o.length;let a,r;if(s){const l=_n(i.rtl,n.x,n.width);for(t.x=Ts(n,i.footerAlign,i),t.y+=i.footerMarginTop,e.textAlign=l.textAlign(i.footerAlign),e.textBaseline="middle",a=Be(i.footerFont),e.fillStyle=i.footerColor,e.font=a.string,r=0;r<s;++r)e.fillText(o[r],l.x(t.x),t.y+a.lineHeight/2),t.y+=a.lineHeight+i.footerSpacing}}drawBackground(t,e,i,n){const{xAlign:o,yAlign:s}=this,{x:a,y:r}=t,{width:l,height:c}=i,h=n.cornerRadius;e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,e.lineWidth=n.borderWidth,e.beginPath(),e.moveTo(a+h,r),"top"===s&&this.drawCaret(t,e,i,n),e.lineTo(a+l-h,r),e.quadraticCurveTo(a+l,r,a+l,r+h),"center"===s&&"right"===o&&this.drawCaret(t,e,i,n),e.lineTo(a+l,r+c-h),e.quadraticCurveTo(a+l,r+c,a+l-h,r+c),"bottom"===s&&this.drawCaret(t,e,i,n),e.lineTo(a+h,r+c),e.quadraticCurveTo(a,r+c,a,r+c-h),"center"===s&&"left"===o&&this.drawCaret(t,e,i,n),e.lineTo(a,r+h),e.quadraticCurveTo(a,r,a+h,r),e.closePath(),e.fill(),n.borderWidth>0&&e.stroke()}_updateAnimationTarget(t){const e=this,i=e._chart,n=e.$animations,o=n&&n.x,s=n&&n.y;if(o||s){const n=ws[t.position].call(e,e._active,e._eventPosition);if(!n)return;const a=e._size=Ps(e,t),r=Object.assign({},n,e._size),l=Cs(i,t,r),c=Os(t,r,l,i);o._to===c.x&&s._to===c.y||(e.xAlign=l.xAlign,e.yAlign=l.yAlign,e.width=a.width,e.height=a.height,e.caretX=n.x,e.caretY=n.y,e._resolveAnimations().update(e,c))}}draw(t){const e=this,i=e.options.setContext(e.getContext());let n=e.opacity;if(!n)return;e._updateAnimationTarget(i);const o={width:e.width,height:e.height},s={x:e.x,y:e.y};n=Math.abs(n)<.001?0:n;const a=Fe(i.padding),r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;i.enabled&&r&&(t.save(),t.globalAlpha=n,e.drawBackground(s,t,o,i),yn(t,i.textDirection),s.y+=a.top,e.drawTitle(s,t,i),e.drawBody(s,t,i),e.drawFooter(s,t,i),vn(t,i.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this,n=i._active,o=t.map((({datasetIndex:t,index:e})=>{const n=i._chart.getDatasetMeta(t);if(!n)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:n.data[e],index:e}})),s=!tt(n,o),a=i._positionChanged(o,e);(s||a)&&(i._active=o,i._eventPosition=e,i.update(!0))}handleEvent(t,e){const i=this,n=i.options,o=i._active||[];let s=!1,a=[];"mouseout"!==t.type&&(a=i._chart.getElementsAtEventForMode(t,n.mode,n,e),n.reverse&&a.reverse());const r=i._positionChanged(a,t);return s=e||!tt(a,o)||r,s&&(i._active=a,(n.enabled||n.external)&&(i._eventPosition={x:t.x,y:t.y},i.update(!0,e))),s}_positionChanged(t,e){const{caretX:i,caretY:n,options:o}=this,s=ws[o.position].call(this,t,e);return!1!==s&&(i!==s.x||n!==s.y)}}Rs.positioners=ws;var Es={id:"tooltip",_element:Rs,positioners:ws,afterInit(t,e,i){i&&(t.tooltip=new Rs({_chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip,i={tooltip:e};!1!==t.notifyPlugins("beforeTooltipDraw",i)&&(e&&e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i))},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:H,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,n=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(n>0&&e.dataIndex<n)return i[e.dataIndex]}return""},afterTitle:H,beforeBody:H,beforeLabel:H,label(t){if(this&&this.options&&"dataset"===this.options.mode)return t.label+": "+t.formattedValue||t.formattedValue;let e=t.dataset.label||"";e&&(e+=": ");const i=t.formattedValue;return $(i)||(e+=i),e},labelColor(t){const e=t.chart.getDatasetMeta(t.datasetIndex).controller.getStyle(t.dataIndex);return{borderColor:e.borderColor,backgroundColor:e.backgroundColor,borderWidth:e.borderWidth,borderDash:e.borderDash,borderDashOffset:e.borderDashOffset,borderRadius:0}},labelTextColor(){return this.options.bodyColor},labelPointStyle(t){const e=t.chart.getDatasetMeta(t.datasetIndex).controller.getStyle(t.dataIndex);return{pointStyle:e.pointStyle,rotation:e.rotation}},afterLabel:H,afterBody:H,beforeFooter:H,footer:H,afterFooter:H}},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},zs=Object.freeze({__proto__:null,Decimation:Go,Filler:gs,Legend:xs,SubTitle:vs,Title:_s,Tooltip:Es});function Is(t,e,i){const n=t.indexOf(e);if(-1===n)return((t,e,i)=>"string"==typeof e?t.push(e)-1:isNaN(e)?null:i)(t,e,i);return n!==t.lastIndexOf(e)?i:n}class Fs extends qi{constructor(t){super(t),this._startValue=void 0,this._valueRange=0}parse(t,e){if($(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Ht(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:Is(i,t,K(e,t)),i.length-1)}determineDataLimits(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let{min:n,max:o}=t.getMinMax(!0);"ticks"===t.options.bounds&&(e||(n=0),i||(o=t.getLabels().length-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.min,i=t.max,n=t.options.offset,o=[];let s=t.getLabels();s=0===e&&i===s.length-1?s:s.slice(e,i+1),t._valueRange=Math.max(s.length-(n?0:1),1),t._startValue=t.min-(n?.5:0);for(let t=e;t<=i;t++)o.push({value:t});return o}getLabelForValue(t){const e=this.getLabels();return t>=0&&t<e.length?e[t]:t}configure(){const t=this;super.configure(),t.isHorizontal()||(t._reversePixels=!t._reversePixels)}getPixelForValue(t){const e=this;return"number"!=typeof t&&(t=e.parse(t)),null===t?NaN:e.getPixelForDecimal((t-e._startValue)/e._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){const e=this;return Math.round(e._startValue+e.getDecimalForPixel(t)*e._valueRange)}getBasePixel(){return this.bottom}}function Bs(t,e,{horizontal:i,minRotation:n}){const o=Et(n),s=(i?Math.sin(o):Math.cos(o))||.001,a=.75*e*(""+t).length;return Math.min(e/s,a)}Fs.id="category",Fs.defaults={ticks:{callback:Fs.prototype.getLabelForValue}};class Vs extends qi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,e){return $(t)||("number"==typeof t||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const t=this,{beginAtZero:e}=t.options,{minDefined:i,maxDefined:n}=t.getUserBounds();let{min:o,max:s}=t;const a=t=>o=i?o:t,r=t=>s=n?s:t;if(e){const t=Dt(o),e=Dt(s);t<0&&e<0?r(0):t>0&&e>0&&a(0)}o===s&&(r(s+1),e||a(o-1)),t.min=o,t.max=s}getTickLimit(){const t=this,e=t.options.ticks;let i,{maxTicksLimit:n,stepSize:o}=e;return o?i=Math.ceil(t.max/o)-Math.floor(t.min/o)+1:(i=t.computeTickLimit(),n=n||11),n&&(i=Math.min(n,i)),i}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this,e=t.options,i=e.ticks;let n=t.getTickLimit();n=Math.max(2,n);const o=function(t,e){const i=[],{bounds:n,step:o,min:s,max:a,precision:r,count:l,maxTicks:c,maxDigits:h,includeBounds:d}=t,u=o||1,f=c-1,{min:g,max:p}=e,m=!$(s),x=!$(a),b=!$(l),_=(p-g)/(h+1);let y,v,w,M,k=Ct((p-g)/f/u)*u;if(k<1e-14&&!m&&!x)return[{value:g},{value:p}];M=Math.ceil(p/k)-Math.floor(g/k),M>f&&(k=Ct(M*k/f/u)*u),$(r)||(y=Math.pow(10,r),k=Math.ceil(k*y)/y),"ticks"===n?(v=Math.floor(g/k)*k,w=Math.ceil(p/k)*k):(v=g,w=p),m&&x&&o&&Lt((a-s)/o,k/1e3)?(M=Math.round(Math.min((a-s)/k,c)),k=(a-s)/M,v=s,w=a):b?(v=m?s:v,w=x?a:w,M=l-1,k=(w-v)/M):(M=(w-v)/k,M=At(M,Math.round(M),k/1e3)?Math.round(M):Math.ceil(M));const S=Math.max(It(k),It(v));y=Math.pow(10,$(r)?S:r),v=Math.round(v*y)/y,w=Math.round(w*y)/y;let P=0;for(m&&(d&&v!==s?(i.push({value:s}),v<s&&P++,At(Math.round((v+P*k)*y)/y,s,Bs(s,_,t))&&P++):v<s&&P++);P<M;++P)i.push({value:Math.round((v+P*k)*y)/y});return x&&d&&w!==a?At(i[i.length-1].value,a,Bs(a,_,t))?i[i.length-1].value=a:i.push({value:a}):x&&w!==a||i.push({value:w}),i}({maxTicks:n,bounds:e.bounds,min:e.min,max:e.max,precision:i.precision,step:i.stepSize,count:i.count,maxDigits:t._maxDigits(),horizontal:t.isHorizontal(),minRotation:i.minRotation||0,includeBounds:!1!==i.includeBounds},t._range||t);return"ticks"===e.bounds&&Rt(o,t,"value"),e.reverse?(o.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),o}configure(){const t=this,e=t.ticks;let i=t.min,n=t.max;if(super.configure(),t.options.offset&&e.length){const t=(n-i)/Math.max(e.length-1,1)/2;i-=t,n+=t}t._startValue=i,t._endValue=n,t._valueRange=n-i}getLabelForValue(t){return Fi(t,this.chart.options.locale)}}class Ws extends Vs{determineDataLimits(){const t=this,{min:e,max:i}=t.getMinMax(!0);t.min=X(e)?e:0,t.max=X(i)?i:1,t.handleTickRangeOptions()}computeTickLimit(){const t=this,e=t.isHorizontal(),i=e?t.width:t.height,n=Et(t.options.ticks.minRotation),o=(e?Math.sin(n):Math.cos(n))||.001,s=t._resolveTickFontOptions(0);return Math.ceil(i/Math.min(40,s.lineHeight/o))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}function Ns(t){return 1===t/Math.pow(10,Math.floor(Pt(t)))}Ws.id="linear",Ws.defaults={ticks:{callback:Vi.formatters.numeric}};class Hs extends qi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=Vs.prototype.parse.apply(this,[t,e]);if(0!==i)return X(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const t=this,{min:e,max:i}=t.getMinMax(!0);t.min=X(e)?Math.max(0,e):null,t.max=X(i)?Math.max(0,i):null,t.options.beginAtZero&&(t._zero=!0),t.handleTickRangeOptions()}handleTickRangeOptions(){const t=this,{minDefined:e,maxDefined:i}=t.getUserBounds();let n=t.min,o=t.max;const s=t=>n=e?n:t,a=t=>o=i?o:t,r=(t,e)=>Math.pow(10,Math.floor(Pt(t))+e);n===o&&(n<=0?(s(1),a(10)):(s(r(n,-1)),a(r(o,1)))),n<=0&&s(r(o,-1)),o<=0&&a(r(n,1)),t._zero&&t.min!==t._suggestedMin&&n===r(t.min,0)&&s(r(n,-1)),t.min=n,t.max=o}buildTicks(){const t=this,e=t.options,i=function(t,e){const i=Math.floor(Pt(e.max)),n=Math.ceil(e.max/Math.pow(10,i)),o=[];let s=q(t.min,Math.pow(10,Math.floor(Pt(e.min)))),a=Math.floor(Pt(s)),r=Math.floor(s/Math.pow(10,a)),l=a<0?Math.pow(10,Math.abs(a)):1;do{o.push({value:s,major:Ns(s)}),++r,10===r&&(r=1,++a,l=a>=0?1:l),s=Math.round(r*Math.pow(10,a)*l)/l}while(a<i||a===i&&r<n);const c=q(t.max,s);return o.push({value:c,major:Ns(s)}),o}({min:t._userMin,max:t._userMax},t);return"ticks"===e.bounds&&Rt(i,t,"value"),e.reverse?(i.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),i}getLabelForValue(t){return void 0===t?"0":Fi(t,this.chart.options.locale)}configure(){const t=this,e=t.min;super.configure(),t._startValue=Pt(e),t._valueRange=Pt(t.max)-Pt(e)}getPixelForValue(t){const e=this;return void 0!==t&&0!==t||(t=e.min),null===t||isNaN(t)?NaN:e.getPixelForDecimal(t===e.min?0:(Pt(t)-e._startValue)/e._valueRange)}getValueForPixel(t){const e=this,i=e.getDecimalForPixel(t);return Math.pow(10,e._startValue+i*e._valueRange)}}function js(t){const e=t.ticks;if(e.display&&t.display){const t=Fe(e.backdropPadding);return K(e.font&&e.font.size,xt.font.size)+t.height}return 0}function $s(t,e,i,n,o){return t===n||t===o?{start:e-i/2,end:e+i/2}:t<n||t>o?{start:e-i,end:e}:{start:e,end:e+i}}function Ys(t){const e={l:0,r:t.width,t:0,b:t.height-t.paddingTop},i={},n=[],o=[],s=t.getLabels().length;for(let c=0;c<s;c++){const s=t.options.pointLabels.setContext(t.getContext(c));o[c]=s.padding;const h=t.getPointPosition(c,t.drawingArea+o[c]),d=Be(s.font),u=(a=t.ctx,r=d,l=Y(l=t._pointLabels[c])?l:[l],{w:Ut(a,r.string,l),h:l.length*r.lineHeight});n[c]=u;const f=t.getIndexAngle(c),g=zt(f),p=$s(g,h.x,u.w,0,180),m=$s(g,h.y,u.h,90,270);p.start<e.l&&(e.l=p.start,i.l=f),p.end>e.r&&(e.r=p.end,i.r=f),m.start<e.t&&(e.t=m.start,i.t=f),m.end>e.b&&(e.b=m.end,i.b=f)}var a,r,l;t._setReductions(t.drawingArea,e,i),t._pointLabelItems=function(t,e,i){const n=[],o=t.getLabels().length,s=t.options,a=js(s),r=t.getDistanceFromCenterForValue(s.ticks.reverse?t.min:t.max);for(let s=0;s<o;s++){const o=0===s?a/2:0,l=t.getPointPosition(s,r+o+i[s]),c=zt(t.getIndexAngle(s)),h=e[s],d=qs(l.y,h.h,c),u=Us(c),f=Xs(l.x,h.w,u);n.push({x:l.x,y:d,textAlign:u,left:f,top:d,right:f+h.w,bottom:d+h.h})}return n}(t,n,o)}function Us(t){return 0===t||180===t?"center":t<180?"left":"right"}function Xs(t,e,i){return"right"===i?t-=e:"center"===i&&(t-=e/2),t}function qs(t,e,i){return 90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e),t}function Ks(t,e,i,n){const{ctx:o}=t;if(i)o.arc(t.xCenter,t.yCenter,e,0,_t);else{let i=t.getPointPosition(0,e);o.moveTo(i.x,i.y);for(let s=1;s<n;s++)i=t.getPointPosition(s,e),o.lineTo(i.x,i.y)}}function Gs(t){return Tt(t)?t:0}Hs.id="logarithmic",Hs.defaults={ticks:{callback:Vi.formatters.logarithmic,major:{enabled:!0}}};class Zs extends Vs{constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=js(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2}determineDataLimits(){const t=this,{min:e,max:i}=t.getMinMax(!1);t.min=X(e)&&!isNaN(e)?e:0,t.max=X(i)&&!isNaN(i)?i:0,t.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/js(this.options))}generateTickLabels(t){const e=this;Vs.prototype.generateTickLabels.call(e,t),e._pointLabels=e.getLabels().map(((t,i)=>{const n=Q(e.options.pointLabels.callback,[t,i],e);return n||0===n?n:""}))}fit(){const t=this,e=t.options;e.display&&e.pointLabels.display?Ys(t):t.setCenterPoint(0,0,0,0)}_setReductions(t,e,i){const n=this;let o=e.l/Math.sin(i.l),s=Math.max(e.r-n.width,0)/Math.sin(i.r),a=-e.t/Math.cos(i.t),r=-Math.max(e.b-(n.height-n.paddingTop),0)/Math.cos(i.b);o=Gs(o),s=Gs(s),a=Gs(a),r=Gs(r),n.drawingArea=Math.max(t/2,Math.min(Math.floor(t-(o+s)/2),Math.floor(t-(a+r)/2))),n.setCenterPoint(o,s,a,r)}setCenterPoint(t,e,i,n){const o=this,s=o.width-e-o.drawingArea,a=t+o.drawingArea,r=i+o.drawingArea,l=o.height-o.paddingTop-n-o.drawingArea;o.xCenter=Math.floor((a+s)/2+o.left),o.yCenter=Math.floor((r+l)/2+o.top+o.paddingTop)}getIndexAngle(t){return Wt(t*(_t/this.getLabels().length)+Et(this.options.startAngle||0))}getDistanceFromCenterForValue(t){const e=this;if($(t))return NaN;const i=e.drawingArea/(e.max-e.min);return e.options.reverse?(e.max-t)*i:(t-e.min)*i}getValueForDistanceFromCenter(t){if($(t))return NaN;const e=this,i=t/(e.drawingArea/(e.max-e.min));return e.options.reverse?e.max-i:e.min+i}getPointPosition(t,e){const i=this,n=i.getIndexAngle(t)-Mt;return{x:Math.cos(n)*e+i.xCenter,y:Math.sin(n)*e+i.yCenter,angle:n}}getPointPositionForValue(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))}getBasePosition(t){return this.getPointPositionForValue(t||0,this.getBaseValue())}getPointLabelPosition(t){const{left:e,top:i,right:n,bottom:o}=this._pointLabelItems[t];return{left:e,top:i,right:n,bottom:o}}drawBackground(){const t=this,{backgroundColor:e,grid:{circular:i}}=t.options;if(e){const n=t.ctx;n.save(),n.beginPath(),Ks(t,t.getDistanceFromCenterForValue(t._endValue),i,t.getLabels().length),n.closePath(),n.fillStyle=e,n.fill(),n.restore()}}drawGrid(){const t=this,e=t.ctx,i=t.options,{angleLines:n,grid:o}=i,s=t.getLabels().length;let a,r,l;if(i.pointLabels.display&&function(t,e){const{ctx:i,options:{pointLabels:n}}=t;for(let o=e-1;o>=0;o--){const e=n.setContext(t.getContext(o)),s=Be(e.font),{x:a,y:r,textAlign:l,left:c,top:h,right:d,bottom:u}=t._pointLabelItems[o],{backdropColor:f}=e;if(!$(f)){const t=Fe(e.backdropPadding);i.fillStyle=f,i.fillRect(c-t.left,h-t.top,d-c+t.width,u-h+t.height)}ee(i,t._pointLabels[o],a,r+s.lineHeight/2,s,{color:e.color,textAlign:l,textBaseline:"middle"})}}(t,s),o.display&&t.ticks.forEach(((e,i)=>{if(0!==i){r=t.getDistanceFromCenterForValue(e.value);const n=o.setContext(t.getContext(i-1));!function(t,e,i,n){const o=t.ctx,s=e.circular,{color:a,lineWidth:r}=e;!s&&!n||!a||!r||i<0||(o.save(),o.strokeStyle=a,o.lineWidth=r,o.setLineDash(e.borderDash),o.lineDashOffset=e.borderDashOffset,o.beginPath(),Ks(t,i,s,n),o.closePath(),o.stroke(),o.restore())}(t,n,r,s)}})),n.display){for(e.save(),a=t.getLabels().length-1;a>=0;a--){const o=n.setContext(t.getContext(a)),{color:s,lineWidth:c}=o;c&&s&&(e.lineWidth=c,e.strokeStyle=s,e.setLineDash(o.borderDash),e.lineDashOffset=o.borderDashOffset,r=t.getDistanceFromCenterForValue(i.ticks.reverse?t.min:t.max),l=t.getPointPosition(a,r),e.beginPath(),e.moveTo(t.xCenter,t.yCenter),e.lineTo(l.x,l.y),e.stroke())}e.restore()}}drawBorder(){}drawLabels(){const t=this,e=t.ctx,i=t.options,n=i.ticks;if(!n.display)return;const o=t.getIndexAngle(0);let s,a;e.save(),e.translate(t.xCenter,t.yCenter),e.rotate(o),e.textAlign="center",e.textBaseline="middle",t.ticks.forEach(((o,r)=>{if(0===r&&!i.reverse)return;const l=n.setContext(t.getContext(r)),c=Be(l.font);if(s=t.getDistanceFromCenterForValue(t.ticks[r].value),l.showLabelBackdrop){e.font=c.string,a=e.measureText(o.label).width,e.fillStyle=l.backdropColor;const t=Fe(l.backdropPadding);e.fillRect(-a/2-t.left,-s-c.size/2-t.top,a+t.width,c.size+t.height)}ee(e,o.label,0,-s,c,{color:l.color})})),e.restore()}drawTitle(){}}Zs.id="radialLinear",Zs.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Vi.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback:t=>t,padding:5}},Zs.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"},Zs.descriptors={angleLines:{_fallback:"grid"}};const Qs={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Js=Object.keys(Qs);function ta(t,e){return t-e}function ea(t,e){if($(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:s}=t._parseOpts;let a=e;return"function"==typeof n&&(a=n(a)),X(a)||(a="string"==typeof n?i.parse(a,n):i.parse(a)),null===a?null:(o&&(a="week"!==o||!Tt(s)&&!0!==s?i.startOf(a,o):i.startOf(a,"isoWeek",s)),+a)}function ia(t,e,i,n){const o=Js.length;for(let s=Js.indexOf(t);s<o-1;++s){const t=Qs[Js[s]],o=t.steps?t.steps:Number.MAX_SAFE_INTEGER;if(t.common&&Math.ceil((i-e)/(o*t.size))<=n)return Js[s]}return Js[o-1]}function na(t,e,i){if(i){if(i.length){const{lo:n,hi:o}=oe(i,e);t[i[n]>=e?i[n]:i[o]]=!0}}else t[e]=!0}function oa(t,e,i){const n=[],o={},s=e.length;let a,r;for(a=0;a<s;++a)r=e[a],o[r]=a,n.push({value:r,major:!1});return 0!==s&&i?function(t,e,i,n){const o=t._adapter,s=+o.startOf(e[0].value,n),a=e[e.length-1].value;let r,l;for(r=s;r<=a;r=+o.add(r,1,n))l=i[r],l>=0&&(e[l].major=!0);return e}(t,n,o,i):n}class sa extends qi{constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e){const i=t.time||(t.time={}),n=this._adapter=new ao._date(t.adapters.date);st(i.displayFormats,n.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:ea(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this,e=t.options,i=t._adapter,n=e.time.unit||"day";let{min:o,max:s,minDefined:a,maxDefined:r}=t.getUserBounds();function l(t){a||isNaN(t.min)||(o=Math.min(o,t.min)),r||isNaN(t.max)||(s=Math.max(s,t.max))}a&&r||(l(t._getLabelBounds()),"ticks"===e.bounds&&"labels"===e.ticks.source||l(t.getMinMax(!1))),o=X(o)&&!isNaN(o)?o:+i.startOf(Date.now(),n),s=X(s)&&!isNaN(s)?s:+i.endOf(Date.now(),n)+1,t.min=Math.min(o,s-1),t.max=Math.max(o+1,s)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this,e=t.options,i=e.time,n=e.ticks,o="labels"===n.source?t.getLabelTimestamps():t._generate();"ticks"===e.bounds&&o.length&&(t.min=t._userMin||o[0],t.max=t._userMax||o[o.length-1]);const s=t.min,a=re(o,s,t.max);return t._unit=i.unit||(n.autoSkip?ia(i.minUnit,t.min,t.max,t._getLabelCapacity(s)):function(t,e,i,n,o){for(let s=Js.length-1;s>=Js.indexOf(i);s--){const i=Js[s];if(Qs[i].common&&t._adapter.diff(o,n,i)>=e-1)return i}return Js[i?Js.indexOf(i):0]}(t,a.length,i.minUnit,t.min,t.max)),t._majorUnit=n.major.enabled&&"year"!==t._unit?function(t){for(let e=Js.indexOf(t)+1,i=Js.length;e<i;++e)if(Qs[Js[e]].common)return Js[e]}(t._unit):void 0,t.initOffsets(o),e.reverse&&a.reverse(),oa(t,a,t._majorUnit)}initOffsets(t){const e=this;let i,n,o=0,s=0;e.options.offset&&t.length&&(i=e.getDecimalForValue(t[0]),o=1===t.length?1-i:(e.getDecimalForValue(t[1])-i)/2,n=e.getDecimalForValue(t[t.length-1]),s=1===t.length?n:(n-e.getDecimalForValue(t[t.length-2]))/2);const a=t.length<3?.5:.25;o=Ht(o,0,a),s=Ht(s,0,a),e._offsets={start:o,end:s,factor:1/(o+1+s)}}_generate(){const t=this,e=t._adapter,i=t.min,n=t.max,o=t.options,s=o.time,a=s.unit||ia(s.minUnit,i,n,t._getLabelCapacity(i)),r=K(s.stepSize,1),l="week"===a&&s.isoWeekday,c=Tt(l)||!0===l,h={};let d,u,f=i;if(c&&(f=+e.startOf(f,"isoWeek",l)),f=+e.startOf(f,c?"day":a),e.diff(n,i,a)>1e5*r)throw new Error(i+" and "+n+" are too far apart with stepSize of "+r+" "+a);const g="data"===o.ticks.source&&t.getDataTimestamps();for(d=f,u=0;d<n;d=+e.add(d,r,a),u++)na(h,d,g);return d!==n&&"ticks"!==o.bounds&&1!==u||na(h,d,g),Object.keys(h).sort(((t,e)=>t-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}_tickFormatFunction(t,e,i,n){const o=this,s=o.options,a=s.time.displayFormats,r=o._unit,l=o._majorUnit,c=r&&a[r],h=l&&a[l],d=i[e],u=l&&h&&d&&d.major,f=o._adapter.format(t,n||(u?h:c)),g=s.ticks.callback;return g?Q(g,[f,e,i],o):f}generateTickLabels(t){let e,i,n;for(e=0,i=t.length;e<i;++e)n=t[e],n.label=this._tickFormatFunction(n.value,e,t)}getDecimalForValue(t){const e=this;return null===t?NaN:(t-e.min)/(e.max-e.min)}getPixelForValue(t){const e=this,i=e._offsets,n=e.getDecimalForValue(t);return e.getPixelForDecimal((i.start+n)*i.factor)}getValueForPixel(t){const e=this,i=e._offsets,n=e.getDecimalForPixel(t)/i.factor-i.end;return e.min+n*(e.max-e.min)}_getLabelSize(t){const e=this,i=e.options.ticks,n=e.ctx.measureText(t).width,o=Et(e.isHorizontal()?i.maxRotation:i.minRotation),s=Math.cos(o),a=Math.sin(o),r=e._resolveTickFontOptions(0).size;return{w:n*s+r*a,h:n*a+r*s}}_getLabelCapacity(t){const e=this,i=e.options.time,n=i.displayFormats,o=n[i.unit]||n.millisecond,s=e._tickFormatFunction(t,0,oa(e,[t],e._majorUnit),o),a=e._getLabelSize(s),r=Math.floor(e.isHorizontal()?e.width/a.w:e.height/a.h)-1;return r>0?r:1}getDataTimestamps(){const t=this;let e,i,n=t._cache.data||[];if(n.length)return n;const o=t.getMatchingVisibleMetas();if(t._normalized&&o.length)return t._cache.data=o[0].controller.getAllParsedValues(t);for(e=0,i=o.length;e<i;++e)n=n.concat(o[e].controller.getAllParsedValues(t));return t._cache.data=t.normalize(n)}getLabelTimestamps(){const t=this,e=t._cache.labels||[];let i,n;if(e.length)return e;const o=t.getLabels();for(i=0,n=o.length;i<n;++i)e.push(ea(t,o[i]));return t._cache.labels=t._normalized?e:t.normalize(e)}normalize(t){return de(t.sort(ta))}}function aa(t,e,i){let n,o,s,a,r=0,l=t.length-1;i?(e>=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=se(t,"pos",e)),({pos:n,time:s}=t[r]),({pos:o,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=se(t,"time",e)),({time:n,pos:s}=t[r]),({time:o,pos:a}=t[l]));const c=o-n;return c?s+(a-s)*(e-n)/c:s}sa.id="time",sa.defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",major:{enabled:!1}}};class ra extends sa{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this,e=t._getTimestampsForTable(),i=t._table=t.buildLookupTable(e);t._minPos=aa(i,t.min),t._tableRange=aa(i,t.max)-t._minPos,super.initOffsets(e)}buildLookupTable(t){const{min:e,max:i}=this,n=[],o=[];let s,a,r,l,c;for(s=0,a=t.length;s<a;++s)l=t[s],l>=e&&l<=i&&n.push(l);if(n.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(s=0,a=n.length;s<a;++s)c=n[s+1],r=n[s-1],l=n[s],Math.round((c+r)/2)!==l&&o.push({time:l,pos:s/(a-1)});return o}_getTimestampsForTable(){const t=this;let e=t._cache.all||[];if(e.length)return e;const i=t.getDataTimestamps(),n=t.getLabelTimestamps();return e=i.length&&n.length?t.normalize(i.concat(n)):i.length?i:n,e=t._cache.all=e,e}getDecimalForValue(t){return(aa(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this,i=e._offsets,n=e.getDecimalForPixel(t)/i.factor-i.end;return aa(e._table,n*e._tableRange+e._minPos,!0)}}ra.id="timeseries",ra.defaults=sa.defaults;var la=Object.freeze({__proto__:null,CategoryScale:Fs,LinearScale:Ws,LogarithmicScale:Hs,RadialLinearScale:Zs,TimeScale:sa,TimeSeriesScale:ra});return eo.register(yo,la,Xo,zs),eo.helpers={...Tn},eo._adapters=ao,eo.Animation=yi,eo.Animations=wi,eo.animator=a,eo.controllers=Ln.controllers.items,eo.DatasetController=Ei,eo.Element=zi,eo.elements=Xo,eo.Interaction=Te,eo.layouts=Ze,eo.platforms=fi,eo.Scale=qi,eo.Ticks=Vi,Object.assign(eo,yo,la,Xo,zs,fi),eo.Chart=eo,"undefined"!=typeof window&&(window.Chart=eo),eo}));
|
package-lock.json
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"requires": true,
|
3 |
+
"lockfileVersion": 1,
|
4 |
+
"dependencies": {
|
5 |
+
"chart.js": {
|
6 |
+
"version": "3.4.1",
|
7 |
+
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.1.tgz",
|
8 |
+
"integrity": "sha512-0R4mL7WiBcYoazIhrzSYnWcOw6RmrRn7Q4nKZNsBQZCBrlkZKodQbfeojCCo8eETPRCs1ZNTsAcZhIfyhyP61g=="
|
9 |
+
}
|
10 |
+
}
|
11 |
+
}
|
plugins/WordPress/Menu.php
CHANGED
@@ -23,7 +23,6 @@ class Menu extends \Piwik\Plugin\Menu
|
|
23 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'SitesManager_MenuManage');
|
24 |
$menu->remove('SitesManager_Sites', 'SitesManager_MenuManage');
|
25 |
$menu->remove('CoreAdminHome_MenuSystem', 'UsersManager_MenuUsers');
|
26 |
-
$menu->remove('UsersManager_MenuPersonal', 'General_Settings');
|
27 |
$menu->remove('UsersManager_MenuPersonal', 'General_Security');
|
28 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'CoreAdminHome_TrackingCode');
|
29 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'General_Settings');
|
23 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'SitesManager_MenuManage');
|
24 |
$menu->remove('SitesManager_Sites', 'SitesManager_MenuManage');
|
25 |
$menu->remove('CoreAdminHome_MenuSystem', 'UsersManager_MenuUsers');
|
|
|
26 |
$menu->remove('UsersManager_MenuPersonal', 'General_Security');
|
27 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'CoreAdminHome_TrackingCode');
|
28 |
$menu->remove('CoreAdminHome_MenuMeasurables', 'General_Settings');
|
plugins/WordPress/WordPress.php
CHANGED
@@ -53,6 +53,7 @@ class WordPress extends Plugin
|
|
53 |
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
|
54 |
'CustomJsTracker.manipulateJsTracker' => 'updateHeatmapTrackerPath',
|
55 |
'Visualization.beforeRender' => 'onBeforeRenderView',
|
|
|
56 |
);
|
57 |
}
|
58 |
|
@@ -331,10 +332,9 @@ class WordPress extends Plugin
|
|
331 |
array('usersmanager', 'index'),
|
332 |
array('usersmanager', ''),
|
333 |
array('usersmanager', 'addnewtoken'),
|
334 |
-
array('usersmanager', 'usersettings'),
|
335 |
array('usersmanager', 'deletetoken'),
|
336 |
array('usersmanager', 'usersecurity'),
|
337 |
-
|
338 |
array('sitesmanager', 'globalsettings'),
|
339 |
array('feedback', ''),
|
340 |
array('feedback', 'index'),
|
@@ -397,4 +397,9 @@ class WordPress extends Plugin
|
|
397 |
throw new \Exception('This feature is not available');
|
398 |
}
|
399 |
|
|
|
|
|
|
|
|
|
|
|
400 |
}
|
53 |
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
|
54 |
'CustomJsTracker.manipulateJsTracker' => 'updateHeatmapTrackerPath',
|
55 |
'Visualization.beforeRender' => 'onBeforeRenderView',
|
56 |
+
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
57 |
);
|
58 |
}
|
59 |
|
332 |
array('usersmanager', 'index'),
|
333 |
array('usersmanager', ''),
|
334 |
array('usersmanager', 'addnewtoken'),
|
|
|
335 |
array('usersmanager', 'deletetoken'),
|
336 |
array('usersmanager', 'usersecurity'),
|
337 |
+
array('sitesmanager', ''),
|
338 |
array('sitesmanager', 'globalsettings'),
|
339 |
array('feedback', ''),
|
340 |
array('feedback', 'index'),
|
397 |
throw new \Exception('This feature is not available');
|
398 |
}
|
399 |
|
400 |
+
public function getStylesheetFiles(&$files)
|
401 |
+
{
|
402 |
+
$files[] = "../plugins/WordPress/stylesheets/user.css";
|
403 |
+
}
|
404 |
+
|
405 |
}
|
plugins/WordPress/stylesheets/user.css
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
div.siteSelector, #defaultReportSiteSelector, div[name="username"],div[name="language"], div[name="timeformat"],div[name="defaultReport"], #newsletterSignup {
|
2 |
+
display: none;
|
3 |
+
}
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i
|
|
4 |
Tags: matomo,piwik,analytics,statistics,stats,tracking,ecommerce
|
5 |
Requires at least: 4.8
|
6 |
Tested up to: 5.8
|
7 |
-
Stable tag: 4.4.
|
8 |
Requires PHP: 7.2.5
|
9 |
License: GPLv3 or later
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
4 |
Tags: matomo,piwik,analytics,statistics,stats,tracking,ecommerce
|
5 |
Requires at least: 4.8
|
6 |
Tested up to: 5.8
|
7 |
+
Stable tag: 4.4.2
|
8 |
Requires PHP: 7.2.5
|
9 |
License: GPLv3 or later
|
10 |
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|