From 1f3438440f34d8e11998004993acbdf7a4e9ae87 Mon Sep 17 00:00:00 2001 From: Lai Power Date: Wed, 3 Jun 2026 21:28:54 +0000 Subject: [PATCH] updated plugin `Connect Matomo` version 1.1.5 --- wp-content/plugins/wp-piwik/.editorconfig | 10 + wp-content/plugins/wp-piwik/.gitignore | 3 + .../plugins/wp-piwik/classes/WP_Piwik.php | 2961 +++++++------- .../classes/WP_Piwik/AIBotTracking.php | 234 ++ .../wp-piwik/classes/WP_Piwik/Admin.php | 50 +- .../classes/WP_Piwik/Admin/Network.php | 27 +- .../classes/WP_Piwik/Admin/Settings.php | 2117 ++++++---- .../classes/WP_Piwik/Admin/Sitebrowser.php | 244 +- .../classes/WP_Piwik/Admin/Statistics.php | 88 +- .../wp-piwik/classes/WP_Piwik/AjaxTracker.php | 175 + .../wp-piwik/classes/WP_Piwik/Logger.php | 94 +- .../classes/WP_Piwik/Logger/Dummy.php | 13 +- .../wp-piwik/classes/WP_Piwik/Logger/File.php | 97 +- .../classes/WP_Piwik/Logger/Screen.php | 50 +- .../wp-piwik/classes/WP_Piwik/Request.php | 211 +- .../wp-piwik/classes/WP_Piwik/Request/Php.php | 136 +- .../classes/WP_Piwik/Request/Rest.php | 189 +- .../wp-piwik/classes/WP_Piwik/Settings.php | 913 +++-- .../wp-piwik/classes/WP_Piwik/Shortcode.php | 58 +- .../wp-piwik/classes/WP_Piwik/Template.php | 66 +- .../WP_Piwik/Template/MetaBoxCustomVars.php | 117 +- .../classes/WP_Piwik/TrackingCode.php | 431 +- .../wp-piwik/classes/WP_Piwik/Widget.php | 911 +++-- .../WP_Piwik/Widget/BrowserDetails.php | 134 +- .../classes/WP_Piwik/Widget/Browsers.php | 138 +- .../classes/WP_Piwik/Widget/Chart.php | 168 +- .../wp-piwik/classes/WP_Piwik/Widget/City.php | 140 +- .../classes/WP_Piwik/Widget/Country.php | 136 +- .../classes/WP_Piwik/Widget/Ecommerce.php | 88 +- .../classes/WP_Piwik/Widget/Items.php | 95 +- .../classes/WP_Piwik/Widget/ItemsCategory.php | 91 +- .../classes/WP_Piwik/Widget/Keywords.php | 31 +- .../classes/WP_Piwik/Widget/Models.php | 140 +- .../classes/WP_Piwik/Widget/Noresult.php | 67 +- .../classes/WP_Piwik/Widget/OptOut.php | 59 +- .../classes/WP_Piwik/Widget/Overview.php | 129 +- .../classes/WP_Piwik/Widget/Pages.php | 33 +- .../classes/WP_Piwik/Widget/Plugins.php | 66 +- .../wp-piwik/classes/WP_Piwik/Widget/Post.php | 167 +- .../classes/WP_Piwik/Widget/Referrers.php | 31 +- .../classes/WP_Piwik/Widget/Screens.php | 131 +- .../classes/WP_Piwik/Widget/Search.php | 68 +- .../wp-piwik/classes/WP_Piwik/Widget/Seo.php | 52 +- .../classes/WP_Piwik/Widget/SystemDetails.php | 130 +- .../classes/WP_Piwik/Widget/Systems.php | 128 +- .../classes/WP_Piwik/Widget/Types.php | 140 +- .../classes/WP_Piwik/Widget/Visitors.php | 155 +- wp-content/plugins/wp-piwik/composer.json | 20 + wp-content/plugins/wp-piwik/composer.lock | 3529 +++++++++++++++++ wp-content/plugins/wp-piwik/config.php | 6 +- wp-content/plugins/wp-piwik/css/wp-piwik.css | 19 +- wp-content/plugins/wp-piwik/index.php | 3 +- .../libs/matomo-php-tracker/.gitattributes | 2 + .../libs/matomo-php-tracker/.gitignore | 5 + .../libs/matomo-php-tracker/CHANGELOG.md | 30 + .../wp-piwik/libs/matomo-php-tracker/LICENSE | 27 + .../libs/matomo-php-tracker/MatomoTracker.php | 2664 +++++++++++++ .../libs/matomo-php-tracker/PiwikTracker.php | 51 + .../libs/matomo-php-tracker/README.md | 53 + .../libs/matomo-php-tracker/composer.json | 44 + .../libs/matomo-php-tracker/phpunit.xml.dist | 12 + .../plugins/wp-piwik/misc/track_ai_bot.php | 108 + wp-content/plugins/wp-piwik/package-lock.json | 131 + wp-content/plugins/wp-piwik/package.json | 12 + wp-content/plugins/wp-piwik/proxy/config.php | 29 +- wp-content/plugins/wp-piwik/proxy/proxy.php | 26 +- wp-content/plugins/wp-piwik/readme.txt | 53 +- wp-content/plugins/wp-piwik/screenshot-4.gif | Bin 12691 -> 11417 bytes wp-content/plugins/wp-piwik/screenshot-5.gif | Bin 27708 -> 0 bytes wp-content/plugins/wp-piwik/uninstall.php | 223 +- .../plugins/wp-piwik/update/2015051101.php | 96 +- .../plugins/wp-piwik/update/2017080701.php | 17 +- .../plugins/wp-piwik/update/2021070701.php | 12 +- .../plugins/wp-piwik/update/2023052301.php | 8 +- wp-content/plugins/wp-piwik/update/90001.php | 25 +- wp-content/plugins/wp-piwik/update/90801.php | 11 +- wp-content/plugins/wp-piwik/update/91006.php | 26 +- wp-content/plugins/wp-piwik/wp-piwik.php | 160 +- 78 files changed, 13800 insertions(+), 5314 deletions(-) create mode 100644 wp-content/plugins/wp-piwik/.editorconfig create mode 100644 wp-content/plugins/wp-piwik/classes/WP_Piwik/AIBotTracking.php create mode 100644 wp-content/plugins/wp-piwik/classes/WP_Piwik/AjaxTracker.php create mode 100644 wp-content/plugins/wp-piwik/composer.json create mode 100644 wp-content/plugins/wp-piwik/composer.lock create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/.gitattributes create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/.gitignore create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/CHANGELOG.md create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/LICENSE create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/MatomoTracker.php create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/PiwikTracker.php create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/README.md create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/composer.json create mode 100644 wp-content/plugins/wp-piwik/libs/matomo-php-tracker/phpunit.xml.dist create mode 100644 wp-content/plugins/wp-piwik/misc/track_ai_bot.php create mode 100644 wp-content/plugins/wp-piwik/package-lock.json create mode 100644 wp-content/plugins/wp-piwik/package.json delete mode 100644 wp-content/plugins/wp-piwik/screenshot-5.gif diff --git a/wp-content/plugins/wp-piwik/.editorconfig b/wp-content/plugins/wp-piwik/.editorconfig new file mode 100644 index 00000000..682a2be1 --- /dev/null +++ b/wp-content/plugins/wp-piwik/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_style = tab +indent_size = 4 diff --git a/wp-content/plugins/wp-piwik/.gitignore b/wp-content/plugins/wp-piwik/.gitignore index df309478..bc00fe3c 100644 --- a/wp-content/plugins/wp-piwik/.gitignore +++ b/wp-content/plugins/wp-piwik/.gitignore @@ -1,2 +1,5 @@ .idea/ +node_modules/ +vendor/ + diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik.php index fc01f7ab..411790f3 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik.php @@ -1,1360 +1,1601 @@ - - * @package WP_Piwik - */ -class WP_Piwik { - - private static $revisionId = 2023092201, $version = '1.0.30', $blog_id, $pluginBasename = NULL, $logger, $settings, $request, $optionsPageId; - public $statsPageId; - - /** - * Constructor class to configure and register all WP-Piwik components - */ - public function __construct() { - global $blog_id; - self::$blog_id = (isset ( $blog_id ) ? $blog_id : 'n/a'); - $this->openLogger (); - $this->openSettings (); - $this->setup (); - $this->addFilters (); - $this->addActions (); - $this->addShortcodes (); - } - - /** - * Destructor class to finish logging - */ - public function __destruct() { - $this->closeLogger (); - } - - /** - * Setup class to prepare settings and check for installation and update - */ - private function setup() { - self::$pluginBasename = plugin_basename ( __FILE__ ); - if (! $this->isInstalled ()) - $this->installPlugin (); - elseif ($this->isUpdated ()) - $this->updatePlugin (); - if ($this->isConfigSubmitted ()) - $this->applySettings (); - self::$settings->save (); - } - - /** - * Register WordPress actions - */ - private function addActions() { - if ( is_admin () ) { - add_action ( 'admin_menu', array ( - $this, - 'buildAdminMenu' - ) ); - add_action ( 'admin_post_save_wp-piwik_stats', array ( - $this, - 'onStatsPageSaveChanges' - ) ); - add_action ( 'load-post.php', array ( - $this, - 'addPostMetaboxes' - ) ); - add_action ( 'load-post-new.php', array ( - $this, - 'addPostMetaboxes' - ) ); - if ($this->isNetworkMode ()) { - add_action ( 'network_admin_notices', array ( - $this, - 'showNotices' - ) ); - add_action ( 'network_admin_menu', array ( - $this, - 'buildNetworkAdminMenu' - ) ); - add_action ( 'update_site_option_blogname', array ( - $this, - 'onBlogNameChange' - ) ); - add_action ( 'update_site_option_siteurl', array ( - $this, - 'onSiteUrlChange' - ) ); - } else { - add_action ( 'admin_notices', array ( - $this, - 'showNotices' - ) ); - add_action ( 'update_option_blogname', array ( - $this, - 'onBlogNameChange' - ) ); - add_action ( 'update_option_siteurl', array ( - $this, - 'onSiteUrlChange' - ) ); - } - if ($this->isDashboardActive ()) - add_action ( 'wp_dashboard_setup', array ( - $this, - 'extendWordPressDashboard' - ) ); - } - if ($this->isToolbarActive ()) { - add_action ( is_admin () ? 'admin_head' : 'wp_head', array ( - $this, - 'loadToolbarRequirements' - ) ); - add_action ( 'admin_bar_menu', array ( - $this, - 'extendWordPressToolbar' - ), 1000 ); - } - if ($this->isTrackingActive ()) { - if ( !is_admin () || $this->isAdminTrackingActive ()) { - $prefix = is_admin ()?'admin':'wp'; - add_action ( self::$settings->getGlobalOption ( 'track_codeposition' ) == 'footer' ? $prefix.'_footer' : $prefix.'_head', array ( - $this, - 'addJavascriptCode' - ) ); - if (self::$settings->getGlobalOption ( 'dnsprefetch' )) - add_action ( $prefix.'_head', array ( - $this, - 'addDNSPrefetchTag' - ) ); - if ($this->isAddNoScriptCode ()) - add_action ( $prefix.'_footer', array ( - $this, - 'addNoscriptCode' - ) ); - } - if (self::$settings->getGlobalOption ( 'add_post_annotations' )) - add_action ( 'transition_post_status', array ( - $this, - 'addPiwikAnnotation' - ), 10, 3 ); - } - - } - - /** - * Register WordPress filters - */ - private function addFilters() { - if (is_admin()) { - add_filter ( 'plugin_row_meta', array ( - $this, - 'setPluginMeta' - ), 10, 2 ); - add_filter ( 'screen_layout_columns', array ( - $this, - 'onScreenLayoutColumns' - ), 10, 2 ); - } elseif ($this->isTrackingActive ()) { - if ($this->isTrackFeed ()) { - add_filter ( 'the_excerpt_rss', array ( - $this, - 'addFeedTracking' - ) ); - add_filter ( 'the_content', array ( - $this, - 'addFeedTracking' - ) ); - } - if ($this->isAddFeedCampaign ()) { - add_filter ( 'post_link', array ( - $this, - 'addFeedCampaign' - ) ); - } - if ($this->isCrossDomainLinkingEnabled ()) { - add_filter ( 'wp_redirect', array ( - $this, - 'forwardCrossDomainVisitorId' - ) ); - } - } - } - - /** - * Register WordPress shortcodes - */ - private function addShortcodes() { - if ($this->isAddShortcode ()) - add_shortcode ( 'wp-piwik', array ( - $this, - 'shortcode' - ) ); - } - - /** - * Install WP-Piwik for the first time - */ - private function installPlugin($isUpdate = false) { - self::$logger->log ( 'Running Connect Matomo installation' ); - if (! $isUpdate) - $this->addNotice ( 'install', sprintf ( __ ( '%s %s installed.', 'wp-piwik' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), self::$version ), __ ( 'Next you should connect to Matomo', 'wp-piwik' ) ); - self::$settings->setGlobalOption ( 'revision', self::$revisionId ); - self::$settings->setGlobalOption ( 'last_settings_update', time () ); - } - - /** - * Uninstall WP-Piwik - */ - public function uninstallPlugin() { - self::$logger->log ( 'Running Connect Matomo uninstallation' ); - if (! defined ( 'WP_UNINSTALL_PLUGIN' )) - exit (); - self::deleteWordPressOption ( 'wp-piwik-notices' ); - self::$settings->resetSettings ( true ); - } - - /** - * Update WP-Piwik - */ - private function updatePlugin() { - self::$logger->log ( 'Upgrade Connect Matomo to ' . self::$version ); - $patches = glob ( dirname ( __FILE__ ) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'update' . DIRECTORY_SEPARATOR . '*.php' ); - $isPatched = false; - if (is_array ( $patches )) { - sort ( $patches ); - foreach ( $patches as $patch ) { - $patchVersion = ( int ) pathinfo ( $patch, PATHINFO_FILENAME ); - if ($patchVersion && self::$settings->getGlobalOption ( 'revision' ) < $patchVersion) { - self::includeFile ( 'update' . DIRECTORY_SEPARATOR . $patchVersion ); - $isPatched = true; - } - } - } - if ((self::$settings->getGlobalOption('update_notice') == 'enabled') || ((self::$settings->getGlobalOption('update_notice') == 'script') && $isPatched)) - $this->addNotice ( 'update', sprintf ( __ ( '%s updated to %s.', 'wp-piwik' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), self::$version ), __ ( 'Please validate your configuration', 'wp-piwik' ) ); - $this->installPlugin ( true ); - } - - /** - * Define a notice - * - * @param string $type - * identifier - * @param string $subject - * notice headline - * @param string $text - * notice content - * @param boolean $stay - * set to true if the message should persist (default: false) - */ - private function addNotice($type, $subject, $text, $stay = false) { - $notices = $this->getWordPressOption ( 'wp-piwik-notices', array () ); - $notices [$type] = array ( - 'subject' => $subject, - 'text' => $text, - 'stay' => $stay - ); - $this->updateWordPressOption ( 'wp-piwik-notices', $notices ); - } - - /** - * Show all notices defined previously - * - * @see addNotice() - */ - public function showNotices() { - $link = sprintf ( '%s', __ ( 'Settings', 'wp-piwik' ) ); - if ($notices = $this->getWordPressOption ( 'wp-piwik-notices' )) { - foreach ( $notices as $type => $notice ) { - printf ( '

%s %s: %s: %s

', $notice ['subject'], __ ( 'Important', 'wp-piwik' ), $notice ['text'], $link ); - if (! $notice ['stay']) - unset ( $notices [$type] ); - } - } - $this->updateWordPressOption ( 'wp-piwik-notices', $notices ); - } - - /** - * Get the settings page URL - * - * @return string settings page URL - */ - private function getSettingsURL() { - return (self::$settings->checkNetworkActivation () ? 'settings' : 'options-general') . '.php?page=' . self::$pluginBasename; - } - - /** - * Echo javascript tracking code - */ - public function addJavascriptCode() { - if ($this->isHiddenUser ()) { - self::$logger->log ( 'Do not add tracking code to site (user should not be tracked) Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->getOption ( 'site_id' ) ); - return; - } - $trackingCode = new WP_Piwik\TrackingCode ( $this ); - $trackingCode->is404 = (is_404 () && self::$settings->getGlobalOption ( 'track_404' )); - $trackingCode->isUsertracking = self::$settings->getGlobalOption ( 'track_user_id' ) != 'disabled'; - $trackingCode->isSearch = (is_search () && self::$settings->getGlobalOption ( 'track_search' )); - self::$logger->log ( 'Add tracking code. Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->getOption ( 'site_id' ) ); - if ($this->isNetworkMode () && self::$settings->getGlobalOption ( 'track_mode' ) == 'manually') { - $siteId = $this->getPiwikSiteId (); - if ($siteId != 'n/a') - echo str_replace ( '{ID}', $siteId, $trackingCode->getTrackingCode () ); - else - echo ''; - } else - echo $trackingCode->getTrackingCode (); - } - - /** - * Echo DNS prefetch tag - */ - public function addDNSPrefetchTag() { - echo ''; - } - - /** - * Get Piwik Domain - */ - public function getPiwikDomain() { - switch (self::$settings->getGlobalOption ( 'piwik_mode' )) { - case 'php' : - return '//' . parse_url(self::$settings->getGlobalOption ( 'proxy_url' ), PHP_URL_HOST); - case 'cloud' : - return '//' . self::$settings->getGlobalOption ( 'piwik_user' ) . '.innocraft.cloud'; - case 'cloud-matomo' : - return '//' . self::$settings->getGlobalOption ( 'matomo_user' ) . '.matomo.cloud'; - default : - return '//' . parse_url(self::$settings->getGlobalOption ( 'piwik_url' ), PHP_URL_HOST); - } - } - - /** - * Echo noscript tracking code - */ - public function addNoscriptCode() { - if (self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy') - return; - if ($this->isHiddenUser ()) { - self::$logger->log ( 'Do not add noscript code to site (user should not be tracked) Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->getOption ( 'site_id' ) ); - return; - } - self::$logger->log ( 'Add noscript code. Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->getOption ( 'site_id' ) ); - echo self::$settings->getOption ( 'noscript_code' ) . "\n"; - } - - /** - * Register post view meta boxes - */ - public function addPostMetaboxes() { - if (self::$settings->getGlobalOption ( 'add_customvars_box' )) { - add_action ( 'add_meta_boxes', array ( - new WP_Piwik\Template\MetaBoxCustomVars ( $this, self::$settings ), - 'addMetabox' - ) ); - add_action ( 'save_post', array ( - new WP_Piwik\Template\MetaBoxCustomVars ( $this, self::$settings ), - 'saveCustomVars' - ), 10, 2 ); - } - if (self::$settings->getGlobalOption ( 'perpost_stats' ) != "disabled") { - add_action ( 'add_meta_boxes', array ( - $this, - 'onloadPostPage' - ) ); - } - } - - /** - * Register admin menu components - */ - public function buildAdminMenu() { - if (self::isConfigured ()) { - $cap = 'wp-piwik_read_stats'; - if (self::$settings->checkNetworkActivation ()) { - global $current_user; - $userRoles = $current_user->roles; - $allowed = self::$settings->getGlobalOption ( 'capability_read_stats' ); - if (is_array($userRoles) && is_array($allowed)) - foreach ($userRoles as $userRole) - if (isset( $allowed[$userRole] ) && $allowed[$userRole]) { - $cap = 'read'; - break; - } - } - $statsPage = new WP_Piwik\Admin\Statistics ( $this, self::$settings ); - $this->statsPageId = add_dashboard_page ( __ ( 'Matomo Statistics', 'wp-piwik' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), $cap, 'wp-piwik_stats', array ( - $statsPage, - 'show' - ) ); - $this->loadAdminStatsHeader ( $this->statsPageId, $statsPage ); - } - if (! self::$settings->checkNetworkActivation ()) { - $optionsPage = new WP_Piwik\Admin\Settings ( $this, self::$settings ); - self::$optionsPageId = add_options_page ( self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), 'activate_plugins', __FILE__, array ( - $optionsPage, - 'show' - ) ); - $this->loadAdminSettingsHeader ( self::$optionsPageId, $optionsPage ); - } - } - - /** - * Register network admin menu components - */ - public function buildNetworkAdminMenu() { - if (self::isConfigured ()) { - $statsPage = new WP_Piwik\Admin\Network ( $this, self::$settings ); - $this->statsPageId = add_dashboard_page ( __ ( 'Matomo Statistics', 'wp-piwik' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), 'manage_sites', 'wp-piwik_stats', array ( - $statsPage, - 'show' - ) ); - $this->loadAdminStatsHeader ( $this->statsPageId, $statsPage ); - } - $optionsPage = new WP_Piwik\Admin\Settings ( $this, self::$settings ); - self::$optionsPageId = add_submenu_page ( 'settings.php', self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ), 'manage_sites', __FILE__, array ( - $optionsPage, - 'show' - ) ); - $this->loadAdminSettingsHeader ( self::$optionsPageId, $optionsPage ); - } - - /** - * Register admin header extensions for stats page - * - * @param $optionsPageId options - * page id - * @param $optionsPage options - * page object - */ - public function loadAdminStatsHeader($statsPageId, $statsPage) { - add_action ( 'admin_print_scripts-' . $statsPageId, array ( - $statsPage, - 'printAdminScripts' - ) ); - add_action ( 'admin_print_styles-' . $statsPageId, array ( - $statsPage, - 'printAdminStyles' - ) ); - add_action ( 'load-' . $statsPageId, array ( - $this, - 'onloadStatsPage' - ) ); - } - - /** - * Register admin header extensions for settings page - * - * @param $optionsPageId options - * page id - * @param $optionsPage options - * page object - */ - public function loadAdminSettingsHeader($optionsPageId, $optionsPage) { - add_action ( 'admin_head-' . $optionsPageId, array ( - $optionsPage, - 'extendAdminHeader' - ) ); - add_action ( 'admin_print_styles-' . $optionsPageId, array ( - $optionsPage, - 'printAdminStyles' - ) ); - } - - /** - * Register WordPress dashboard widgets - */ - public function extendWordPressDashboard() { - if (current_user_can ( 'wp-piwik_read_stats' )) { - if (self::$settings->getGlobalOption ( 'dashboard_widget' ) != 'disabled') - new WP_Piwik\Widget\Overview ( $this, self::$settings, 'dashboard', 'side', 'default', array ( - 'date' => self::$settings->getGlobalOption ( 'dashboard_widget' ), - 'period' => 'day' - ) ); - if (self::$settings->getGlobalOption ( 'dashboard_chart' )) - new WP_Piwik\Widget\Chart ( $this, self::$settings ); - if (self::$settings->getGlobalOption ( 'dashboard_ecommerce' )) - new WP_Piwik\Widget\Ecommerce ( $this, self::$settings ); - if (self::$settings->getGlobalOption ( 'dashboard_seo' )) - new WP_Piwik\Widget\Seo ( $this, self::$settings ); - } - } - - /** - * Register WordPress toolbar components - */ - public function extendWordPressToolbar($toolbar) { - if (current_user_can ( 'wp-piwik_read_stats' ) && is_admin_bar_showing ()) { - $id = WP_Piwik\Request::register ( 'VisitsSummary.getUniqueVisitors', array ( - 'period' => 'day', - 'date' => 'last30' - ) ); - $unique = $this->request ( $id ); - $url = is_network_admin () ? $this->getSettingsURL () : false; - $content = is_network_admin () ? __('Configure WP-Matomo', 'wp-piwik') : ''; - // Leave if result array does contain a message instead of valid data - if (isset($unique['result'])) - $content .= ''; - elseif (is_array ( $unique ) ) { - $labels = ""; - for ($i = 0; $i < count($unique); $i++) { - $labels .= $i.","; - } - ob_start(); - ?> -
- -
- - getStatsURL (); - } - $toolbar->add_menu ( array ( - 'id' => 'wp-piwik_stats', - 'title' => $content, - 'href' => $url - ) ); - } - } - - /** - * Add plugin meta data - * - * @param array $links - * list of already defined plugin meta data - * @param string $file - * handled file - * @return array complete list of plugin meta data - */ - public function setPluginMeta($links, $file) { - if ($file == 'wp-piwik/wp-piwik.php' && (!$this->isNetworkMode () || is_network_admin()) ) - return array_merge ( $links, array ( - sprintf ( '%s', self::getSettingsURL (), __ ( 'Settings', 'wp-piwik' ) ) - ) ); - return $links; - } - - /** - * Prepare toolbar widget requirements - */ - public function loadToolbarRequirements() { - if (is_admin_bar_showing ()) { - wp_enqueue_script ( 'wp-piwik-chartjs', $this->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - } - } - - /** - * Add tracking pixels to feed content - * - * @param string $content - * post content - * @return string post content extended by tracking pixel - */ - public function addFeedTracking($content) { - global $post; - if (is_feed ()) { - self::$logger->log ( 'Add tracking image to feed entry.' ); - if (! self::$settings->getOption ( 'site_id' )) { - $siteId = $this->requestPiwikSiteId (); - if ($siteId != 'n/a') - self::$settings->setOption ( 'site_id', $siteId ); - else - return false; - } - $title = the_title ( null, null, false ); - $posturl = get_permalink ( $post->ID ); - $urlref = get_bloginfo ( 'rss2_url' ); - if (self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy') - $url = plugins_url ( 'wp-piwik' ) . '/proxy/matomo.php'; - else { - $url = self::$settings->getGlobalOption ( 'piwik_url' ); - if (substr($url, -10, 10) == '/index.php') - $url = str_replace('/index.php', '/matomo.php', $url); - else - $url .= 'piwik.php'; - } - $trackingImage = $url . '?idsite=' . self::$settings->getOption ( 'site_id' ) . '&rec=1&url=' . urlencode ( $posturl ) . '&action_name=' . urlencode ( $title ) . '&urlref=' . urlencode ( $urlref ); - $content .= ''; - } - return $content; - } - - /** - * Add a campaign parameter to feed permalink - * - * @param string $permalink - * permalink - * @return string permalink extended by campaign parameter - */ - public function addFeedCampaign($permalink) { - global $post; - if (is_feed ()) { - self::$logger->log ( 'Add campaign to feed permalink.' ); - $sep = (strpos ( $permalink, '?' ) === false ? '?' : '&'); - $permalink .= $sep . 'pk_campaign=' . urlencode ( self::$settings->getGlobalOption ( 'track_feed_campaign' ) ) . '&pk_kwd=' . urlencode ( $post->post_name ); - } - return $permalink; - } - - /** - * Forwards the cross domain parameter pk_vid if the URL parameter is set and a user is about to be redirected. - * When another website links to WooCommerce with a pk_vid parameter, and WooCommerce redirects the user to another - * URL, the pk_vid parameter would get lost and the visitorId would later not be applied by the tracking code - * due to the lost pk_vid URL parameter. If the URL parameter is set, we make sure to forward this parameter. - * - * @param string $location - * - * @return string location extended by pk_vid URL parameter if the URL parameter is set - */ - public function forwardCrossDomainVisitorId($location) { - - if (!empty($_GET['pk_vid']) - && preg_match('/^[a-zA-Z0-9]{24,48}$/', $_GET['pk_vid'])) { - // currently, the pk_vid parameter is 32 characters long, but it may vary over time. - $location = add_query_arg( 'pk_vid', $_GET['pk_vid'], $location ); - } - - return $location; - } - - /** - * Apply settings update - * - * @return boolean settings update applied - */ - private function applySettings() { - self::$settings->applyChanges ( $_POST ['wp-piwik'] ); - self::$settings->setGlobalOption ( 'revision', self::$revisionId ); - self::$settings->setGlobalOption ( 'last_settings_update', time () ); - return true; - } - - /** - * Check if WP-Piwik is configured - * - * @return boolean Is WP-Piwik configured? - */ - public static function isConfigured() { - return (self::$settings->getGlobalOption ( 'piwik_token' ) && (self::$settings->getGlobalOption ( 'piwik_mode' ) != 'disabled') && (((self::$settings->getGlobalOption ( 'piwik_mode' ) == 'http') && (self::$settings->getGlobalOption ( 'piwik_url' ))) || ((self::$settings->getGlobalOption ( 'piwik_mode' ) == 'php') && (self::$settings->getGlobalOption ( 'piwik_path' ))) || ((self::$settings->getGlobalOption ( 'piwik_mode' ) == 'cloud') && (self::$settings->getGlobalOption ( 'piwik_user' ))) || ((self::$settings->getGlobalOption ( 'piwik_mode' ) == 'cloud-matomo') && (self::$settings->getGlobalOption ( 'matomo_user' ))))); - } - - /** - * Check if WP-Piwik was updated - * - * @return boolean Was WP-Piwik updated? - */ - private function isUpdated() { - return self::$settings->getGlobalOption ( 'revision' ) && self::$settings->getGlobalOption ( 'revision' ) < self::$revisionId; - } - - /** - * Check if WP-Piwik is already installed - * - * @return boolean Is WP-Piwik installed? - */ - private function isInstalled() { - $oldSettings = $this->getWordPressOption ( 'wp-piwik_global-settings', false ); - if ($oldSettings && isset( $oldSettings['revision'] )) { - self::log('Save old settings'); - self::$settings->setGlobalOption ( 'revision', $oldSettings['revision'] ); - } else self::log( 'Current revision '.self::$settings->getGlobalOption ( 'revision' ) ); - return self::$settings->getGlobalOption ( 'revision' ) > 0; - } - - /** - * Check if new settings were submitted - * - * @return boolean Are new settings submitted? - */ - public static function isConfigSubmitted() { - return isset ( $_POST ) && isset ( $_POST ['wp-piwik'] ) && self::isValidOptionsPost(); - } - - /** - * Check if PHP mode is chosen - * - * @return Is PHP mode chosen? - */ - public function isPHPMode() { - return self::$settings->getGlobalOption ( 'piwik_mode' ) && self::$settings->getGlobalOption ( 'piwik_mode' ) == 'php'; - } - - /** - * Check if WordPress is running in network mode - * - * @return boolean Is WordPress running in network mode? - */ - public function isNetworkMode() { - return self::$settings->checkNetworkActivation (); - } - - /** - * Check if a WP-Piwik dashboard widget is enabled - * - * @return boolean Is a dashboard widget enabled? - */ - private function isDashboardActive() { - return self::$settings->getGlobalOption ( 'dashboard_widget' ) || self::$settings->getGlobalOption ( 'dashboard_chart' ) || self::$settings->getGlobalOption ( 'dashboard_seo' ); - } - - /** - * Check if a WP-Piwik toolbar widget is enabled - * - * @return boolean Is a toolbar widget enabled? - */ - private function isToolbarActive() { - return self::$settings->getGlobalOption ( 'toolbar' ); - } - - /** - * Check if WP-Piwik tracking code insertion is enabled - * - * @return boolean Insert tracking code? - */ - private function isTrackingActive() { - return self::$settings->getGlobalOption ( 'track_mode' ) != 'disabled'; - } - - /** - * Check if admin tracking is enabled - * - * @return boolean Is admin tracking enabled? - */ - private function isAdminTrackingActive() { - return self::$settings->getGlobalOption ( 'track_admin' ) && is_admin (); - } - - /** - * Check if WP-Piwik noscript code insertion is enabled - * - * @return boolean Insert noscript code? - */ - private function isAddNoScriptCode() { - return self::$settings->getGlobalOption ( 'track_noscript' ); - } - - /** - * Check if feed tracking is enabled - * - * @return boolean Is feed tracking enabled? - */ - private function isTrackFeed() { - return self::$settings->getGlobalOption ( 'track_feed' ); - } - - /** - * Check if feed permalinks get a campaign parameter - * - * @return boolean Add campaign parameter to feed permalinks? - */ - private function isAddFeedCampaign() { - return self::$settings->getGlobalOption ( 'track_feed_addcampaign' ); - } - - /** - * Check if feed permalinks get a campaign parameter - * - * @return boolean Add campaign parameter to feed permalinks? - */ - private function isCrossDomainLinkingEnabled() { - return self::$settings->getGlobalOption ( 'track_crossdomain_linking' ); - } - - /** - * Check if WP-Piwik shortcodes are enabled - * - * @return boolean Are shortcodes enabled? - */ - private function isAddShortcode() { - return self::$settings->getGlobalOption ( 'shortcodes' ); - } - - /** - * Define Piwik constants for PHP reporting API - */ - public static function definePiwikConstants() { - if (! defined ( 'PIWIK_INCLUDE_PATH' )) { - //@header('Content-type: text/html'); - define ( 'PIWIK_INCLUDE_PATH', self::$settings->getGlobalOption ( 'piwik_path' ) ); - define ( 'PIWIK_USER_PATH', self::$settings->getGlobalOption ( 'piwik_path' ) ); - define ( 'PIWIK_ENABLE_DISPATCH', false ); - define ( 'PIWIK_ENABLE_ERROR_HANDLER', false ); - define ( 'PIWIK_ENABLE_SESSION_START', false ); - } - } - - /** - * Start chosen logging method - */ - private function openLogger() { - switch (WP_PIWIK_ACTIVATE_LOGGER) { - case 1 : - self::$logger = new WP_Piwik\Logger\Screen ( __CLASS__ ); - break; - case 2 : - self::$logger = new WP_Piwik\Logger\File ( __CLASS__ ); - break; - default : - self::$logger = new WP_Piwik\Logger\Dummy ( __CLASS__ ); - } - } - - /** - * Log a message - * - * @param string $message - * logger message - */ - public static function log($message) { - self::$logger->log ( $message ); - } - - /** - * End logging - */ - private function closeLogger() { - self::$logger = null; - } - - /** - * Load WP-Piwik settings - */ - private function openSettings() { - self::$settings = new WP_Piwik\Settings ( $this, self::$logger ); - if (! $this->isConfigSubmitted () && $this->isPHPMode () && ! defined ( 'PIWIK_INCLUDE_PATH' )) - self::definePiwikConstants (); - } - - /** - * Include a WP-Piwik file - */ - private function includeFile($strFile) { - self::$logger->log ( 'Include ' . $strFile . '.php' ); - if (WP_PIWIK_PATH . $strFile . '.php') - include (WP_PIWIK_PATH . $strFile . '.php'); - } - - /** - * Check if user should not be tracked - * - * @return boolean Do not track user? - */ - private function isHiddenUser() { - if (is_multisite ()) - foreach ( self::$settings->getGlobalOption ( 'capability_stealth' ) as $key => $val ) - if ($val && current_user_can ( $key )) - return true; - return current_user_can ( 'wp-piwik_stealth' ); - } - - /** - * Check if tracking code is up to date - * - * @return boolean Is tracking code up to date? - */ - public function isCurrentTrackingCode() { - return (self::$settings->getOption ( 'last_tracking_code_update' ) && self::$settings->getOption ( 'last_tracking_code_update' ) > self::$settings->getGlobalOption ( 'last_settings_update' )); - } - - /** - * DEPRECTAED Add javascript code to site header - * - * @deprecated - * - */ - public function site_header() { - self::$logger->log ( 'Using deprecated function site_header' ); - $this->addJavascriptCode (); - } - - /** - * DEPRECTAED Add javascript code to site footer - * - * @deprecated - * - */ - public function site_footer() { - self::$logger->log ( 'Using deprecated function site_footer' ); - $this->addNoscriptCode (); - } - - /** - * Identify new posts if an annotation is required - * and create Piwik annotation - * - * @param string $newStatus - * new post status - * @param strint $oldStatus - * new post status - * @param object $post - * current post object - */ - public function addPiwikAnnotation($newStatus, $oldStatus, $post) { - $enabledPostTypes = self::$settings->getGlobalOption ( 'add_post_annotations' ); - if (isset($enabledPostTypes[$post->post_type]) && $enabledPostTypes[$post->post_type] && $newStatus == 'publish' && $oldStatus != 'publish') { - $note = 'Published: ' . $post->post_title . ' - URL: ' . get_permalink ( $post->ID ); - $id = WP_Piwik\Request::register ( 'Annotations.add', array ( - 'idSite' => $this->getPiwikSiteId (), - 'date' => date ( 'Y-m-d' ), - 'note' => $note - ) ); - $result = $this->request ( $id ); - self::$logger->log ( 'Add post annotation. ' . $note . ' - ' . serialize ( $result ) ); - } - } - - /** - * Get WP-Piwik's URL - */ - public function getPluginURL() { - return trailingslashit ( plugin_dir_url( dirname( __FILE__ ) ) ); - } - - /** - * Get WP-Piwik's version - */ - public function getPluginVersion() { - return self::$version; - } - - /** - * Enable three columns for WP-Piwik stats screen - * - * @param - * array full list of column settings - * @param - * mixed current screen id - * @return array updated list of column settings - */ - public function onScreenLayoutColumns($columns, $screen) { - if (isset( $this->statsPageId ) && $screen == $this->statsPageId) - $columns [$this->statsPageId] = 3; - return $columns; - } - - /** - * Add tracking code to admin header - */ - function addAdminHeaderTracking() { - $this->addJavascriptCode (); - } - - /** - * Get option value - * - * @param string $key - * option key - * @return mixed option value - */ - public function getOption($key) { - return self::$settings->getOption ( $key ); - } - - /** - * Get global option value - * - * @param string $key - * global option key - * @return mixed global option value - */ - public function getGlobalOption($key) { - return self::$settings->getGlobalOption ( $key ); - } - - /** - * Get stats page URL - * - * @return string stats page URL - */ - public function getStatsURL() { - return admin_url () . '?page=wp-piwik_stats'; - } - - /** - * Execute WP-Piwik test script - */ - private function loadTestscript() { - $this->includeFile ( 'debug' . DIRECTORY_SEPARATOR . 'testscript' ); - } - - /** - * Echo an error message - * - * @param string $message - * message content - */ - private static function showErrorMessage($message) { - echo '' . __ ( 'An error occured', 'wp-piwik' ) . ': ' . $message . ' [' . __ ( 'Support', 'wp-piwik' ) . ']'; - } - - /** - * Perform a Piwik request - * - * @param string $id - * request ID - * @return mixed request result - */ - public function request($id, $debug = false) { - if ( self::$settings->getGlobalOption ( 'piwik_mode' ) == 'disabled' ) - return 'n/a'; - if (! isset ( self::$request ) || empty ( self::$request )) - self::$request = (self::$settings->getGlobalOption ( 'piwik_mode' ) == 'http' || self::$settings->getGlobalOption ( 'piwik_mode' ) == 'cloud' || self::$settings->getGlobalOption ( 'piwik_mode' ) == 'cloud-matomo' ? new WP_Piwik\Request\Rest ( $this, self::$settings ) : new WP_Piwik\Request\Php ( $this, self::$settings )); - if ($debug) - return self::$request->getDebug ( $id ); - return self::$request->perform ( $id ); - } - - /** - * Reset request object - */ - public function resetRequest() { - if (is_object(self::$request)) - self::$request->reset(); - self::$request = NULL; - } - - /** - * Execute WP-Piwik shortcode - * - * @param array $attributes - * attribute list - */ - public function shortcode($attributes) { - shortcode_atts ( array ( - 'title' => '', - 'module' => 'overview', - 'period' => 'day', - 'date' => 'yesterday', - 'limit' => 10, - 'width' => '100%', - 'height' => '200px', - 'idsite' => '', - 'language' => 'en', - 'range' => false, - 'key' => 'sum_daily_nb_uniq_visitors' - ), $attributes ); - $shortcodeObject = new \WP_Piwik\Shortcode ( $attributes, $this, self::$settings ); - return $shortcodeObject->get(); - } - - /** - * Get Piwik site ID by blog ID - * - * @param int $blogId - * which blog's Piwik site ID to get, default is the current blog - * @return mixed Piwik site ID or n/a - */ - public function getPiwikSiteId($blogId = null, $forceFetch = false) { - if (! $blogId && $this->isNetworkMode ()) - $blogId = get_current_blog_id (); - $result = self::$settings->getOption ( 'site_id' ); - self::$logger->log ( 'Database result: ' . $result ); - return (! empty ( $result ) && ! $forceFetch ? $result : $this->requestPiwikSiteId ( $blogId )); - } - - /** - * Get a detailed list of all Piwik sites - * - * @return array Piwik sites - */ - public function getPiwikSiteDetails() { - $id = WP_Piwik\Request::register ( 'SitesManager.getSitesWithAtLeastViewAccess', array () ); - $piwikSiteDetails = $this->request ( $id ); - return $piwikSiteDetails; - } - - /** - * Estimate a Piwik site ID by blog ID - * - * @param int $blogId - * which blog's Piwik site ID to estimate, default is the current blog - * @return mixed Piwik site ID or n/a - */ - private function requestPiwikSiteId($blogId = null) { - $isCurrent = ! self::$settings->checkNetworkActivation () || empty ( $blogId ); - if (self::$settings->getGlobalOption ( 'auto_site_config' )) { - $id = WP_Piwik\Request::register ( 'SitesManager.getSitesIdFromSiteUrl', array ( - 'url' => $isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl - ) ); - $result = $this->request ( $id ); - $this->log ( 'Tried to identify current site, result: ' . serialize ( $result ) ); - if (is_array( $result ) && empty( $result )) - $result = $this->addPiwikSite ( $blogId ); - elseif ( $result != 'n/a' && isset($result [0]) ) - $result = $result [0] ['idsite']; - else $result = null; - } else $result = null; - self::$logger->log ( 'Get Matomo ID: WordPress site ' . ($isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl) . ' = Matomo ID ' . $result ); - if ($result !== null) { - self::$settings->setOption ( 'site_id', $result, $blogId ); - if (self::$settings->getGlobalOption ( 'track_mode' ) != 'disabled' && self::$settings->getGlobalOption ( 'track_mode' ) != 'manually') { - $code = $this->updateTrackingCode ( $result, $blogId ); - } - $this::$settings->save (); - return $result; - } - return 'n/a'; - } - - /** - * Add a new Piwik - * - * @param int $blogId - * which blog's Piwik site to create, default is the current blog - * @return int Piwik site ID - */ - public function addPiwikSite($blogId = null) { - $isCurrent = ! self::$settings->checkNetworkActivation () || empty ( $blogId ); - // Do not add site if Piwik connection is unreliable - if (! $this->request ( 'global.getPiwikVersion' )) - return null; - $id = WP_Piwik\Request::register ( 'SitesManager.addSite', array ( - 'urls' => $isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl, - 'siteName' => urlencode( $isCurrent ? get_bloginfo ( 'name' ) : get_blog_details ( $blogId )->blogname ) - ) ); - $result = $this->request ( $id ); - if ( is_array( $result ) && isset( $result['value'] ) ) { - $result = (int) $result['value']; - } else { - $result = (int) $result; - } - self::$logger->log ( 'Create Matomo ID: WordPress site ' . ($isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl) . ' = Matomo ID ' . $result ); - if (empty ( $result )) - return null; - else { - do_action('wp-piwik_site_created', $result); - return $result; - } - } - - /** - * Update a Piwik site's detail information - * - * @param int $siteId - * which Piwik site to updated - * @param int $blogId - * which blog's Piwik site ID to get, default is the current blog - */ - private function updatePiwikSite($siteId, $blogId = null) { - $isCurrent = ! self::$settings->checkNetworkActivation () || empty ( $blogId ); - $id = WP_Piwik\Request::register ( 'SitesManager.updateSite', array ( - 'idSite' => $siteId, - 'urls' => $isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl, - 'siteName' => $isCurrent ? get_bloginfo ( 'name' ) : get_blog_details ( $blogId )->blogname - ) ); - $this->request ( $id ); - self::$logger->log ( 'Update Matomo site: WordPress site ' . ($isCurrent ? get_bloginfo ( 'url' ) : get_blog_details ( $blogId )->siteurl) ); - } - - /** - * Update a site's tracking code - * - * @param int $siteId - * which Piwik site to updated - * @param int $blogId - * which blog's Piwik site ID to get, default is the current blog - * @return string tracking code - */ - public function updateTrackingCode($siteId = false, $blogId = null) { - if (!$siteId) - $siteId = $this->getPiwikSiteId (); - if (self::$settings->getGlobalOption ( 'track_mode' ) == 'disabled' || self::$settings->getGlobalOption ( 'track_mode' ) == 'manually') - return false; - $id = WP_Piwik\Request::register ( 'SitesManager.getJavascriptTag', array ( - 'idSite' => $siteId, - 'mergeSubdomains' => self::$settings->getGlobalOption ( 'track_across' ) ? 1 : 0, - 'mergeAliasUrls' => self::$settings->getGlobalOption ( 'track_across_alias' ) ? 1 : 0, - 'disableCookies' => self::$settings->getGlobalOption ( 'disable_cookies' ) ? 1 : 0, - 'crossDomain' => self::$settings->getGlobalOption ( 'track_crossdomain_linking' ) ? 1 : 0, - 'trackNoScript' => 1 - ) ); - $code = $this->request ( $id ); - if (is_array($code) && isset($code['value'])) - $code = $code['value']; - $result = !is_array ( $code ) ? html_entity_decode ( $code ) : ''; - self::$logger->log ( 'Delivered tracking code: ' . $result ); - $result = WP_Piwik\TrackingCode::prepareTrackingCode ( $result, self::$settings, self::$logger, true ); - if (isset ( $result ['script'] ) && ! empty ( $result ['script'] )) { - self::$settings->setOption ( 'tracking_code', $result ['script'], $blogId ); - self::$settings->setOption ( 'noscript_code', $result ['noscript'], $blogId ); - self::$settings->setGlobalOption ( 'proxy_url', $result ['proxy'] ); - } - return $result; - } - - /** - * Update Piwik site if blog name changes - * - * @param string $oldValue - * old blog name - * @param string $newValue - * new blog name - */ - public function onBlogNameChange($oldValue, $newValue = null) { - $this->updatePiwikSite ( self::$settings->getOption ( 'site_id' ) ); - } - - /** - * Update Piwik site if blog URL changes - * - * @param string $oldValue - * old blog URL - * @param string $newValue - * new blog URL - */ - public function onSiteUrlChange($oldValue, $newValue = null) { - $this->updatePiwikSite ( self::$settings->getOption ( 'site_id' ) ); - } - - /** - * Register stats page meta boxes - * - * @param mixed $statsPageId - * WordPress stats page ID - */ - public function onloadStatsPage($statsPageId) { - if (self::$settings->getGlobalOption ( 'disable_timelimit' )) - set_time_limit ( 0 ); - wp_enqueue_script ( 'common' ); - wp_enqueue_script ( 'wp-lists' ); - wp_enqueue_script ( 'postbox' ); - wp_enqueue_script ( 'wp-piwik', $this->getPluginURL () . 'js/wp-piwik.js', array (), self::$version, true ); - wp_enqueue_script ( 'wp-piwik-chartjs', $this->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - new \WP_Piwik\Widget\Chart ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Visitors ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Overview ( $this, self::$settings, $this->statsPageId ); - if (self::$settings->getGlobalOption ( 'stats_ecommerce' )) { - new \WP_Piwik\Widget\Ecommerce ($this, self::$settings, $this->statsPageId); - new \WP_Piwik\Widget\Items ($this, self::$settings, $this->statsPageId); - new \WP_Piwik\Widget\ItemsCategory ($this, self::$settings, $this->statsPageId); - } - if (self::$settings->getGlobalOption ( 'stats_seo' )) - new \WP_Piwik\Widget\Seo ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Pages ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Keywords ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Referrers ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Plugins ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Search ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Noresult ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Browsers ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\BrowserDetails ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Screens ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Types ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Models ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Systems ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\SystemDetails ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\City ( $this, self::$settings, $this->statsPageId ); - new \WP_Piwik\Widget\Country ( $this, self::$settings, $this->statsPageId ); - } - - /** - * Add per post statistics to a post's page - * - * @param mixed $postPageId - * WordPress post page ID - */ - public function onloadPostPage($postPageId) { - global $post; - $postUrl = get_permalink ( $post->ID ); - $this->log ( 'Load per post statistics: ' . $postUrl ); - $locations = apply_filters( 'wp-piwik_meta_boxes_locations', get_post_types( array( 'public' => true ), 'names' ) ); - array ( - new Post ( $this, self::$settings, $locations, 'side', 'default', array ( - 'date' => self::$settings->getGlobalOption ( 'perpost_stats' ), - 'period' => 'day', - 'url' => $postUrl - ) ), - 'show' - ); - } - - /** - * Stats page changes by POST submit - * - * @see http://tinyurl.com/5r5vnzs - */ - function onStatsPageSaveChanges() { - if (! current_user_can ( 'manage_options' )) - wp_die ( __ ( 'Cheatin’ uh?' ) ); - check_admin_referer ( 'wp-piwik_stats' ); - wp_redirect ( $_POST ['_wp_http_referer'] ); - } - - /** - * Get option value, choose method depending on network mode - * - * @param string $option option key - * @return string|array option value - */ - private function getWordPressOption($option, $default = null) { - return ($this->isNetworkMode () ? get_site_option ( $option, $default ) : get_option ( $option, $default )); - } - - /** - * Delete option, choose method depending on network mode - * - * @param string $option option key - */ - private function deleteWordPressOption($option) { - if ( $this->isNetworkMode () ) - delete_site_option ( $option ); - else - delete_option ( $option ); - } - - /** - * Set option value, choose method depending on network mode - * - * @param string $option option key - * @param mixed $value option value - */ - private function updateWordPressOption($option, $value) { - if ( $this->isNetworkMode () ) - update_site_option ( $option, $value ); - else - update_option ( $option, $value ); - } - - /** - * Check if WP-Piwik options page - * - * @return boolean True if current page is WP-Piwik's option page - */ - public static function isValidOptionsPost() { - return is_admin() && check_admin_referer( 'wp-piwik_settings' ) && current_user_can( 'manage_options' ) ; - } -} + + * @package WP_Piwik + */ +class WP_Piwik { + + private static $revision_id = 2023092201; + private static $version = '1.1.5'; + private static $blog_id; + private static $plugin_basename = null; + private static $logger; + + /** + * @var \WP_Piwik\Settings + */ + private static $settings; + + private static $request; + private static $options_page_id; + + public $stats_page_id; + + /** + * Constructor class to configure and register all WP-Piwik components + */ + public function __construct() { + global $blog_id; + self::$blog_id = ( isset( $blog_id ) ? $blog_id : 'n/a' ); + $this->open_logger(); + $this->open_settings(); + $this->setup(); + $this->add_filters(); + $this->add_actions(); + $this->add_shortcodes(); + $this->set_up_ai_bot_tracking(); + } + + public static function get_settings() { + return self::$settings; + } + + /** + * Destructor class to finish logging + */ + public function __destruct() { + $this->close_logger(); + } + + /** + * Setup class to prepare settings and check for installation and update + */ + private function setup() { + self::$plugin_basename = plugin_basename( __FILE__ ); + if ( ! $this->is_installed() ) { + $this->install_plugin(); + } elseif ( $this->is_updated() ) { + $this->update_plugin(); + } + if ( $this->is_config_submitted() ) { + $this->apply_settings(); + } + self::$settings->save(); + } + + /** + * Register WordPress actions + */ + private function add_actions() { + if ( is_admin() ) { + add_action( + 'admin_menu', + array( + $this, + 'build_admin_menu', + ) + ); + add_action( + 'admin_post_save_wp-piwik_stats', + array( + $this, + 'on_stats_page_save_changes', + ) + ); + add_action( + 'load-post.php', + array( + $this, + 'add_post_metaboxes', + ) + ); + add_action( + 'load-post-new.php', + array( + $this, + 'add_post_metaboxes', + ) + ); + if ( $this->is_network_mode() ) { + add_action( + 'network_admin_notices', + array( + $this, + 'show_notices', + ) + ); + add_action( + 'network_admin_menu', + array( + $this, + 'build_network_admin_menu', + ) + ); + add_action( + 'update_site_option_blogname', + array( + $this, + 'on_blog_name_change', + ) + ); + add_action( + 'update_site_option_siteurl', + array( + $this, + 'on_site_url_change', + ) + ); + } else { + add_action( + 'admin_notices', + array( + $this, + 'show_notices', + ) + ); + add_action( + 'update_option_blogname', + array( + $this, + 'on_blog_name_change', + ) + ); + add_action( + 'update_option_siteurl', + array( + $this, + 'on_site_url_change', + ) + ); + } + if ( $this->is_dashboard_active() ) { + add_action( + 'wp_dashboard_setup', + array( + $this, + 'extend_word_press_dashboard', + ) + ); + } + } + if ( $this->is_toolbar_active() ) { + add_action( + is_admin() ? 'admin_head' : 'wp_head', + array( + $this, + 'load_toolbar_requirements', + ) + ); + add_action( + 'admin_bar_menu', + array( + $this, + 'extend_word_press_toolbar', + ), + 1000 + ); + } + if ( $this->is_tracking_active() ) { + if ( ! is_admin() || $this->is_admin_tracking_active() ) { + $prefix = is_admin() ? 'admin' : 'wp'; + add_action( + 'footer' === self::$settings->get_global_option( 'track_codeposition' ) ? $prefix . '_footer' : $prefix . '_head', + array( + $this, + 'add_javascript_code', + ) + ); + if ( self::$settings->get_global_option( 'dnsprefetch' ) ) { + add_action( + $prefix . '_head', + array( + $this, + 'add_dns_prefetch_tag', + ) + ); + } + if ( $this->is_add_no_script_code() ) { + add_action( + $prefix . '_footer', + array( + $this, + 'add_noscript_code', + ) + ); + } + } + if ( self::$settings->get_global_option( 'add_post_annotations' ) ) { + add_action( + 'transition_post_status', + array( + $this, + 'add_piwik_annotation', + ), + 10, + 3 + ); + } + } + } + + /** + * Register WordPress filters + */ + private function add_filters() { + if ( is_admin() ) { + add_filter( + 'plugin_row_meta', + array( + $this, + 'set_plugin_meta', + ), + 10, + 2 + ); + add_filter( + 'screen_layout_columns', + array( + $this, + 'on_screen_layout_columns', + ), + 10, + 2 + ); + } elseif ( $this->is_tracking_active() ) { + if ( $this->is_track_feed() ) { + add_filter( + 'the_excerpt_rss', + array( + $this, + 'add_feed_tracking', + ) + ); + add_filter( + 'the_content', + array( + $this, + 'add_feed_tracking', + ) + ); + } + if ( $this->is_add_feed_campaign() ) { + add_filter( + 'post_link', + array( + $this, + 'add_feed_campaign', + ) + ); + } + if ( $this->is_cross_domain_linking_enabled() ) { + add_filter( + 'wp_redirect', + array( + $this, + 'forward_cross_domain_visitor_id', + ) + ); + } + } + } + + /** + * Register WordPress shortcodes + */ + private function add_shortcodes() { + if ( $this->is_add_shortcode() ) { + add_shortcode( + 'wp-piwik', + array( + $this, + 'shortcode', + ) + ); + } + } + + /** + * Install WP-Piwik for the first time + */ + private function install_plugin( $is_update = false ) { + self::$logger->log( 'Running Connect Matomo installation' ); + if ( ! $is_update ) { + $this->add_notice( 'install', sprintf( __( '%1$s %2$s installed.', 'wp-piwik' ), self::$settings->get_not_empty_global_option( 'plugin_display_name' ), self::$version ), __( 'Next you should connect to Matomo', 'wp-piwik' ) ); + } + self::$settings->set_global_option( 'revision', self::$revision_id ); + self::$settings->set_global_option( 'last_settings_update', time() ); + } + + /** + * Uninstall WP-Piwik + */ + public function uninstall_plugin() { + self::$logger->log( 'Running Connect Matomo uninstallation' ); + if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { + exit(); + } + self::delete_word_press_option( 'wp-piwik-notices' ); + self::$settings->reset_settings(); + } + + /** + * Update WP-Piwik + */ + private function update_plugin() { + self::$logger->log( 'Upgrade Connect Matomo to ' . self::$version ); + $patches = glob( __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'update' . DIRECTORY_SEPARATOR . '*.php' ); + $is_patched = false; + if ( is_array( $patches ) ) { + sort( $patches ); + foreach ( $patches as $patch ) { + $patch_version = (int) pathinfo( $patch, PATHINFO_FILENAME ); + if ( $patch_version && self::$settings->get_global_option( 'revision' ) < $patch_version ) { + self::include_file( 'update' . DIRECTORY_SEPARATOR . $patch_version ); + $is_patched = true; + } + } + } + if ( + 'enabled' === self::$settings->get_global_option( 'update_notice' ) + || ( 'script' === self::$settings->get_global_option( 'update_notice' ) && $is_patched ) + ) { + $this->add_notice( 'update', sprintf( __( '%1$s updated to %2$s.', 'wp-piwik' ), self::$settings->get_not_empty_global_option( 'plugin_display_name' ), self::$version ), __( 'Please validate your configuration', 'wp-piwik' ) ); + } + $this->install_plugin( true ); + } + + /** + * Define a notice + * + * @param string $type + * identifier + * @param string $subject + * notice headline + * @param string $text + * notice content + * @param boolean $stay + * set to true if the message should persist (default: false) + */ + private function add_notice( $type, $subject, $text, $stay = false ) { + $notices = $this->get_word_press_option( 'wp-piwik-notices', array() ); + $notices[ $type ] = array( + 'subject' => $subject, + 'text' => $text, + 'stay' => $stay, + ); + $this->update_word_press_option( 'wp-piwik-notices', $notices ); + } + + /** + * Show all notices defined previously + * + * @see add_notice() + */ + public function show_notices() { + $notices = $this->get_word_press_option( 'wp-piwik-notices' ); + if ( ! empty( $notices ) ) { + if ( ! is_array( $notices ) ) { + $notices = array(); + } + + foreach ( $notices as $type => $notice ) { + printf( + '

%s %s: %s: %s

', + esc_html( $notice ['subject'] ), + esc_html__( 'Important', 'wp-piwik' ), + esc_html( $notice ['text'] ), + esc_attr( $this->get_settings_url() ), + esc_html__( 'Settings', 'wp-piwik' ) + ); + if ( ! $notice ['stay'] ) { + unset( $notices [ $type ] ); + } + } + } + $this->update_word_press_option( 'wp-piwik-notices', $notices ); + } + + /** + * Get the settings page URL + * + * @return string settings page URL + */ + private function get_settings_url() { + return ( self::$settings->check_network_activation() ? 'settings' : 'options-general' ) . '.php?page=wp-matomo-settings'; + } + + /** + * Echo javascript tracking code + */ + public function add_javascript_code() { + if ( $this->is_hidden_user() ) { + self::$logger->log( 'Do not add tracking code to site (user should not be tracked) Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->get_option( 'site_id' ) ); + return; + } + $tracking_code = new WP_Piwik\TrackingCode( $this ); + $tracking_code->is_404 = ( is_404() && self::$settings->get_global_option( 'track_404' ) ); + $tracking_code->is_usertracking = 'disabled' !== self::$settings->get_global_option( 'track_user_id' ); + $tracking_code->is_search = ( is_search() && self::$settings->get_global_option( 'track_search' ) ); + self::$logger->log( 'Add tracking code. Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->get_option( 'site_id' ) ); + if ( $this->is_network_mode() && 'manually' === self::$settings->get_global_option( 'track_mode' ) ) { + $site_id = $this->get_piwik_site_id(); + if ( 'n/a' !== $site_id ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo str_replace( '{ID}', $site_id, $tracking_code->get_tracking_code() ); + } else { + echo ''; + } + } else { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $tracking_code->get_tracking_code(); + } + } + + /** + * Echo DNS prefetch tag + */ + public function add_dns_prefetch_tag() { + echo ''; + } + + /** + * Get Piwik Domain + */ + public function get_piwik_domain() { + switch ( self::$settings->get_global_option( 'piwik_mode' ) ) { + case 'php': + return '//' . wp_parse_url( self::$settings->get_global_option( 'proxy_url' ), PHP_URL_HOST ); + case 'cloud': + return '//' . self::$settings->get_global_option( 'piwik_user' ) . '.innocraft.cloud'; + case 'cloud-matomo': + return '//' . self::$settings->get_global_option( 'matomo_user' ) . '.matomo.cloud'; + default: + return '//' . wp_parse_url( self::$settings->get_global_option( 'piwik_url' ), PHP_URL_HOST ); + } + } + + /** + * Echo noscript tracking code + */ + public function add_noscript_code() { + if ( 'proxy' === self::$settings->get_global_option( 'track_mode' ) ) { + return; + } + if ( $this->is_hidden_user() ) { + self::$logger->log( 'Do not add noscript code to site (user should not be tracked) Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->get_option( 'site_id' ) ); + return; + } + self::$logger->log( 'Add noscript code. Blog ID: ' . self::$blog_id . ' Site ID: ' . self::$settings->get_option( 'site_id' ) ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo self::$settings->get_option( 'noscript_code' ) . "\n"; + } + + /** + * Register post view meta boxes + */ + public function add_post_metaboxes() { + if ( self::$settings->get_global_option( 'add_customvars_box' ) ) { + add_action( + 'add_meta_boxes', + array( + new WP_Piwik\Template\MetaBoxCustomVars( $this, self::$settings ), + 'addMetabox', + ) + ); + add_action( + 'save_post', + array( + new WP_Piwik\Template\MetaBoxCustomVars( $this, self::$settings ), + 'saveCustomVars', + ), + 10, + 2 + ); + } + if ( 'disabled' !== self::$settings->get_global_option( 'perpost_stats' ) ) { + add_action( + 'add_meta_boxes', + array( + $this, + 'onload_post_page', + ) + ); + } + } + + /** + * Register admin menu components + */ + public function build_admin_menu() { + if ( self::is_configured() ) { + $cap = 'wp-piwik_read_stats'; + if ( self::$settings->check_network_activation() ) { + global $current_user; + $user_roles = $current_user->roles; + $allowed = self::$settings->get_global_option( 'capability_read_stats' ); + if ( is_array( $user_roles ) && is_array( $allowed ) ) { + foreach ( $user_roles as $user_role ) { + if ( isset( $allowed[ $user_role ] ) && $allowed[ $user_role ] ) { + $cap = 'read'; + break; + } + } + } + } + $stats_page = new WP_Piwik\Admin\Statistics( $this, self::$settings ); + $this->stats_page_id = add_dashboard_page( + __( 'Matomo Statistics', 'wp-piwik' ), + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + $cap, + 'wp-piwik_stats', + array( + $stats_page, + 'show', + ) + ); + $this->load_admin_stats_header( $this->stats_page_id, $stats_page ); + } + if ( ! self::$settings->check_network_activation() ) { + $options_page = new WP_Piwik\Admin\Settings( $this, self::$settings ); + self::$options_page_id = add_options_page( + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + 'activate_plugins', + 'wp-matomo-settings', + array( + $options_page, + 'show', + ) + ); + $this->load_admin_settings_header( self::$options_page_id, $options_page ); + } + } + + /** + * Register network admin menu components + */ + public function build_network_admin_menu() { + if ( self::is_configured() ) { + $stats_page = new WP_Piwik\Admin\Network( $this, self::$settings ); + $this->stats_page_id = add_dashboard_page( + __( 'Matomo Statistics', 'wp-piwik' ), + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + 'manage_sites', + 'wp-piwik_stats', + array( + $stats_page, + 'show', + ) + ); + $this->load_admin_stats_header( $this->stats_page_id, $stats_page ); + } + $options_page = new WP_Piwik\Admin\Settings( $this, self::$settings ); + self::$options_page_id = add_submenu_page( + 'settings.php', + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + self::$settings->get_not_empty_global_option( 'plugin_display_name' ), + 'manage_sites', + 'wp-matomo-settings', + array( + $options_page, + 'show', + ) + ); + $this->load_admin_settings_header( self::$options_page_id, $options_page ); + } + + /** + * Register admin header extensions for stats page + * + * @param mixed $stats_page_id options page id + * @param mixed $stats_page options page object + */ + public function load_admin_stats_header( $stats_page_id, $stats_page ) { + add_action( + 'admin_print_scripts-' . $stats_page_id, + array( + $stats_page, + 'print_admin_scripts', + ) + ); + add_action( + 'admin_print_styles-' . $stats_page_id, + array( + $stats_page, + 'print_admin_styles', + ) + ); + add_action( + 'load-' . $stats_page_id, + array( + $this, + 'onload_stats_page', + ) + ); + } + + /** + * Register admin header extensions for settings page + * + * @param string $options_page_id options page id + * @param mixed $options_page options page object + */ + public function load_admin_settings_header( $options_page_id, $options_page ) { + add_action( + 'admin_head-' . $options_page_id, + array( + $options_page, + 'extend_admin_header', + ) + ); + add_action( + 'admin_print_styles-' . $options_page_id, + array( + $options_page, + 'print_admin_styles', + ) + ); + } + + /** + * Register WordPress dashboard widgets + */ + public function extend_word_press_dashboard() { + if ( current_user_can( 'wp-piwik_read_stats' ) ) { + if ( 'disabled' !== self::$settings->get_global_option( 'dashboard_widget' ) ) { + new WP_Piwik\Widget\Overview( + $this, + self::$settings, + 'dashboard', + 'side', + 'default', + array( + 'date' => self::$settings->get_global_option( 'dashboard_widget' ), + 'period' => 'day', + ) + ); + } + if ( self::$settings->get_global_option( 'dashboard_chart' ) ) { + new WP_Piwik\Widget\Chart( $this, self::$settings ); + } + if ( self::$settings->get_global_option( 'dashboard_ecommerce' ) ) { + new WP_Piwik\Widget\Ecommerce( $this, self::$settings ); + } + if ( self::$settings->get_global_option( 'dashboard_seo' ) ) { + new WP_Piwik\Widget\Seo( $this, self::$settings ); + } + } + } + + /** + * Register WordPress toolbar components + */ + public function extend_word_press_toolbar( $toolbar ) { + if ( current_user_can( 'wp-piwik_read_stats' ) && is_admin_bar_showing() ) { + $id = WP_Piwik\Request::register( + 'VisitsSummary.getUniqueVisitors', + array( + 'period' => 'day', + 'date' => 'last30', + ) + ); + $unique = $this->request( $id ); + $url = is_network_admin() ? $this->get_settings_url() : false; + $content = is_network_admin() ? __( 'Configure WP-Matomo', 'wp-piwik' ) : ''; + // Leave if result array does contain a message instead of valid data + if ( isset( $unique['result'] ) ) { + $content .= ''; + } elseif ( is_array( $unique ) ) { + $unique_count = count( $unique ); + + $labels = array(); + for ( $i = 0; $i < $unique_count; $i++ ) { + $labels [] = $i; + } + ob_start(); + ?> +
+ +
+ + get_stats_url(); + } + $toolbar->add_menu( + array( + 'id' => 'wp-piwik_stats', + 'title' => $content, + 'href' => $url, + ) + ); + } + } + + /** + * Add plugin meta data + * + * @param array $links + * list of already defined plugin meta data + * @param string $file + * handled file + * @return array complete list of plugin meta data + */ + public function set_plugin_meta( $links, $file ) { + if ( 'wp-piwik/wp-piwik.php' === $file && ( ! $this->is_network_mode() || is_network_admin() ) ) { + return array_merge( + $links, + array( + sprintf( '%s', esc_attr( self::get_settings_url() ), esc_html__( 'Settings', 'wp-piwik' ) ), + ) + ); + } + return $links; + } + + /** + * Prepare toolbar widget requirements + */ + public function load_toolbar_requirements() { + if ( is_admin_bar_showing() ) { + wp_enqueue_script( 'wp-piwik-chartjs', $this->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $this->get_plugin_version(), false ); + } + } + + /** + * Add tracking pixels to feed content + * + * @param string $content + * post content + * @return string|false post content extended by tracking pixel + */ + public function add_feed_tracking( $content ) { + global $post; + if ( is_feed() ) { + self::$logger->log( 'Add tracking image to feed entry.' ); + if ( ! self::$settings->get_option( 'site_id' ) ) { + $site_id = $this->request_piwik_site_id(); + if ( 'n/a' !== $site_id ) { + self::$settings->set_option( 'site_id', $site_id ); + } else { + return false; + } + } + $title = the_title( '', '', false ); + $posturl = get_permalink( $post->ID ); + $urlref = get_bloginfo( 'rss2_url' ); + if ( 'proxy' === self::$settings->get_global_option( 'track_mode' ) ) { + $url = plugins_url( 'wp-piwik' ) . '/proxy/matomo.php'; + } else { + $url = self::$settings->get_global_option( 'piwik_url' ); + if ( '/index.php' === substr( $url, -10, 10 ) ) { + $url = str_replace( '/index.php', '/matomo.php', $url ); + } else { + $url .= 'piwik.php'; + } + } + $tracking_image = $url . '?idsite=' . self::$settings->get_option( 'site_id' ) . '&rec=1&url=' . rawurlencode( $posturl ) . '&action_name=' . rawurlencode( $title ) . '&urlref=' . rawurlencode( $urlref ); + $content .= ''; + } + return $content; + } + + /** + * Add a campaign parameter to feed permalink + * + * @param string $permalink + * permalink + * @return string permalink extended by campaign parameter + */ + public function add_feed_campaign( $permalink ) { + global $post; + if ( is_feed() ) { + self::$logger->log( 'Add campaign to feed permalink.' ); + $sep = ( false === strpos( $permalink, '?' ) ? '?' : '&' ); + $permalink .= $sep . 'pk_campaign=' . rawurlencode( self::$settings->get_global_option( 'track_feed_campaign' ) ) . '&pk_kwd=' . rawurlencode( $post->post_name ); + } + return $permalink; + } + + /** + * Forwards the cross domain parameter pk_vid if the URL parameter is set and a user is about to be redirected. + * When another website links to WooCommerce with a pk_vid parameter, and WooCommerce redirects the user to another + * URL, the pk_vid parameter would get lost and the visitorId would later not be applied by the tracking code + * due to the lost pk_vid URL parameter. If the URL parameter is set, we make sure to forward this parameter. + * + * @param string $location + * + * @return string location extended by pk_vid URL parameter if the URL parameter is set + */ + public function forward_cross_domain_visitor_id( $location ) { + $pk_vid = ! empty( $_GET['pk_vid'] ) ? sanitize_key( wp_unslash( $_GET['pk_vid'] ) ) : null; + if ( ! empty( $pk_vid ) + && preg_match( '/^[a-zA-Z0-9]{24,48}$/', $pk_vid ) + ) { + // currently, the pk_vid parameter is 32 characters long, but it may vary over time. + $location = add_query_arg( 'pk_vid', $pk_vid, $location ); + } + + return $location; + } + + /** + * Apply settings update + * + * @return boolean settings update applied + * @phpcs:disable WordPress.Security.NonceVerification.Missing + */ + private function apply_settings() { + // TODO: shouldn't have to disable these + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + self::$settings->apply_changes( wp_unslash( $_POST['wp-piwik'] ) ); + self::$settings->set_global_option( 'revision', self::$revision_id ); + self::$settings->set_global_option( 'last_settings_update', time() ); + return true; + } + + /** + * Check if WP-Piwik is configured + * + * @return boolean Is WP-Piwik configured? + */ + public static function is_configured() { + return self::$settings->get_global_option( 'piwik_token' ) + && 'disabled' !== self::$settings->get_global_option( 'piwik_mode' ) + && ( + ( 'http' === self::$settings->get_global_option( 'piwik_mode' ) && self::$settings->get_global_option( 'piwik_url' ) ) + || ( 'php' === self::$settings->get_global_option( 'piwik_mode' ) && self::$settings->get_global_option( 'piwik_path' ) ) + || ( 'cloud' === self::$settings->get_global_option( 'piwik_mode' ) && self::$settings->get_global_option( 'piwik_user' ) ) + || ( 'cloud-matomo' === self::$settings->get_global_option( 'piwik_mode' ) && self::$settings->get_global_option( 'matomo_user' ) ) + ); + } + + /** + * Check if WP-Piwik was updated + * + * @return boolean Was WP-Piwik updated? + */ + private function is_updated() { + return self::$settings->get_global_option( 'revision' ) + && self::$settings->get_global_option( 'revision' ) < self::$revision_id; + } + + /** + * Check if WP-Piwik is already installed + * + * @return boolean Is WP-Piwik installed? + */ + private function is_installed() { + $old_settings = $this->get_word_press_option( 'wp-piwik_global-settings', false ); + if ( $old_settings && isset( $old_settings['revision'] ) ) { + self::log( 'Save old settings' ); + self::$settings->set_global_option( 'revision', $old_settings['revision'] ); + } else { + self::log( 'Current revision ' . self::$settings->get_global_option( 'revision' ) ); + } + return self::$settings->get_global_option( 'revision' ) > 0; + } + + /** + * Check if new settings were submitted + * + * @return boolean Are new settings submitted? + * @phpcs:disable WordPress.Security.NonceVerification.Missing + */ + public static function is_config_submitted() { + return isset( $_POST ['wp-piwik'] ) && self::is_valid_options_post(); + } + + /** + * Check if PHP mode is chosen + * + * @return bool Is PHP mode chosen? + */ + public function is_php_mode() { + return self::$settings->get_global_option( 'piwik_mode' ) && 'php' === self::$settings->get_global_option( 'piwik_mode' ); + } + + /** + * Check if WordPress is running in network mode + * + * @return boolean Is WordPress running in network mode? + */ + public function is_network_mode() { + return self::$settings->check_network_activation(); + } + + /** + * Check if a WP-Piwik dashboard widget is enabled + * + * @return boolean Is a dashboard widget enabled? + */ + private function is_dashboard_active() { + return self::$settings->get_global_option( 'dashboard_widget' ) || self::$settings->get_global_option( 'dashboard_chart' ) || self::$settings->get_global_option( 'dashboard_seo' ); + } + + /** + * Check if a WP-Piwik toolbar widget is enabled + * + * @return boolean Is a toolbar widget enabled? + */ + private function is_toolbar_active() { + return self::$settings->get_global_option( 'toolbar' ); + } + + /** + * Check if WP-Piwik tracking code insertion is enabled + * + * @return boolean Insert tracking code? + */ + private function is_tracking_active() { + return 'disabled' !== self::$settings->get_global_option( 'track_mode' ); + } + + /** + * Check if admin tracking is enabled + * + * @return boolean Is admin tracking enabled? + */ + private function is_admin_tracking_active() { + return self::$settings->get_global_option( 'track_admin' ) && is_admin(); + } + + /** + * Check if WP-Piwik noscript code insertion is enabled + * + * @return boolean Insert noscript code? + */ + private function is_add_no_script_code() { + return self::$settings->get_global_option( 'track_noscript' ); + } + + /** + * Check if feed tracking is enabled + * + * @return boolean Is feed tracking enabled? + */ + private function is_track_feed() { + return self::$settings->get_global_option( 'track_feed' ); + } + + /** + * Check if feed permalinks get a campaign parameter + * + * @return boolean Add campaign parameter to feed permalinks? + */ + private function is_add_feed_campaign() { + return self::$settings->get_global_option( 'track_feed_addcampaign' ); + } + + /** + * Check if feed permalinks get a campaign parameter + * + * @return boolean Add campaign parameter to feed permalinks? + */ + private function is_cross_domain_linking_enabled() { + return self::$settings->get_global_option( 'track_crossdomain_linking' ); + } + + /** + * Check if WP-Piwik shortcodes are enabled + * + * @return boolean Are shortcodes enabled? + */ + private function is_add_shortcode() { + return self::$settings->get_global_option( 'shortcodes' ); + } + + /** + * Define Piwik constants for PHP reporting API + * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound + */ + public static function define_piwik_constants() { + if ( ! defined( 'PIWIK_INCLUDE_PATH' ) ) { + define( 'PIWIK_INCLUDE_PATH', self::$settings->get_global_option( 'piwik_path' ) ); + define( 'PIWIK_USER_PATH', self::$settings->get_global_option( 'piwik_path' ) ); + define( 'PIWIK_ENABLE_DISPATCH', false ); + define( 'PIWIK_ENABLE_ERROR_HANDLER', false ); + define( 'PIWIK_ENABLE_SESSION_START', false ); + } + } + + /** + * Start chosen logging method + */ + private function open_logger() { + self::$logger = \WP_Piwik\Logger::make_logger(); + } + + public static function get_logger() { + return self::$logger; + } + + /** + * Log a message + * + * @param string $message + * logger message + */ + public static function log( $message ) { + self::$logger->log( $message ); + } + + /** + * End logging + */ + private function close_logger() { + self::$logger = null; + } + + /** + * Load WP-Piwik settings + */ + private function open_settings() { + self::$settings = new WP_Piwik\Settings( $this ); + if ( ! $this->is_config_submitted() && $this->is_php_mode() && ! defined( 'PIWIK_INCLUDE_PATH' ) ) { + self::define_piwik_constants(); + } + } + + /** + * Include a WP-Piwik file + */ + private function include_file( $str_file ) { + self::$logger->log( 'Include ' . $str_file . '.php' ); + if ( is_file( WP_PIWIK_PATH . $str_file . '.php' ) ) { + include WP_PIWIK_PATH . $str_file . '.php'; + } + } + + /** + * Check if user should not be tracked + * + * @return boolean Do not track user? + */ + private function is_hidden_user() { + if ( is_multisite() ) { + foreach ( self::$settings->get_global_option( 'capability_stealth' ) as $key => $val ) { + if ( $val && current_user_can( $key ) ) { + return true; + } + } + } + return current_user_can( 'wp-piwik_stealth' ); + } + + /** + * Check if tracking code is up to date + * + * @return boolean Is tracking code up to date? + */ + public function is_current_tracking_code() { + return ( self::$settings->get_option( 'last_tracking_code_update' ) && self::$settings->get_option( 'last_tracking_code_update' ) > self::$settings->get_global_option( 'last_settings_update' ) ); + } + + /** + * DEPRECTAED Add javascript code to site header + * + * @deprecated + * + */ + public function site_header() { + self::$logger->log( 'Using deprecated function site_header' ); + $this->add_javascript_code(); + } + + /** + * DEPRECTAED Add javascript code to site footer + * + * @deprecated + * + */ + public function site_footer() { + self::$logger->log( 'Using deprecated function site_footer' ); + $this->add_noscript_code(); + } + + /** + * Identify new posts if an annotation is required + * and create Piwik annotation + * + * @param string $new_status + * new post status + * @param string $old_status + * new post status + * @param object $post + * current post object + */ + public function add_piwik_annotation( $new_status, $old_status, $post ) { + $enabled_post_types = self::$settings->get_global_option( 'add_post_annotations' ); + if ( isset( $enabled_post_types[ $post->post_type ] ) && $enabled_post_types[ $post->post_type ] && 'publish' !== $new_status && 'publish' !== $old_status ) { + $note = 'Published: ' . $post->post_title . ' - URL: ' . get_permalink( $post->ID ); + $id = WP_Piwik\Request::register( + 'Annotations.add', + array( + 'idSite' => $this->get_piwik_site_id(), + 'date' => gmdate( 'Y-m-d' ), + 'note' => $note, + ) + ); + $result = $this->request( $id ); + self::$logger->log( 'Add post annotation. ' . $note . ' - ' . wp_json_encode( $result ) ); + } + } + + /** + * Get WP-Piwik's URL + */ + public function get_plugin_url() { + return trailingslashit( plugin_dir_url( __DIR__ ) ); + } + + /** + * Get WP-Piwik's version + */ + public function get_plugin_version() { + return self::$version; + } + + /** + * Enable three columns for WP-Piwik stats screen + * + * @param array $columns full list of column settings + * @param mixed $screen current screen id + * @return array updated list of column settings + */ + public function on_screen_layout_columns( $columns, $screen ) { + if ( isset( $this->stats_page_id ) && (string) $screen === $this->stats_page_id ) { + $columns [ $this->stats_page_id ] = 3; + } + return $columns; + } + + /** + * Add tracking code to admin header. + */ + public function add_admin_header_tracking() { + $this->add_javascript_code(); + } + + /** + * Get option value + * + * @param string $key + * option key + * @return mixed option value + */ + public function get_option( $key ) { + return self::$settings->get_option( $key ); + } + + /** + * Get global option value + * + * @param string $key + * global option key + * @return mixed global option value + */ + public function get_global_option( $key ) { + return self::$settings->get_global_option( $key ); + } + + /** + * Get stats page URL + * + * @return string stats page URL + */ + public function get_stats_url() { + return admin_url() . '?page=wp-piwik_stats'; + } + + /** + * Perform a Piwik request + * + * @param string $id + * request ID + * @return mixed request result + */ + public function request( $id, $debug = false ) { + if ( 'disabled' === self::$settings->get_global_option( 'piwik_mode' ) ) { + return 'n/a'; + } + if ( ! isset( self::$request ) || empty( self::$request ) ) { + self::$request = ( 'http' === self::$settings->get_global_option( 'piwik_mode' ) || 'cloud' === self::$settings->get_global_option( 'piwik_mode' ) || 'cloud-matomo' === self::$settings->get_global_option( 'piwik_mode' ) ? new WP_Piwik\Request\Rest( $this, self::$settings ) : new WP_Piwik\Request\Php( $this, self::$settings ) ); + } + if ( $debug ) { + return self::$request->get_debug( $id ); + } + return self::$request->perform( $id ); + } + + /** + * Reset request object + */ + public function reset_request() { + if ( is_object( self::$request ) ) { + self::$request->reset(); + } + self::$request = null; + } + + /** + * Execute WP-Piwik shortcode + * + * @param array $attributes + * attribute list + */ + public function shortcode( $attributes ) { + shortcode_atts( + array( + 'title' => '', + 'module' => 'overview', + 'period' => 'day', + 'date' => 'yesterday', + 'limit' => 10, + 'width' => '100%', + 'height' => '200px', + 'idsite' => '', + 'language' => 'en', + 'range' => false, + 'key' => 'sum_daily_nb_uniq_visitors', + ), + $attributes + ); + $shortcode_object = new \WP_Piwik\Shortcode( $attributes, $this, self::$settings ); + return $shortcode_object->get(); + } + + /** + * Get Piwik site ID by blog ID + * + * @param int|string $blog_id + * which blog's Piwik site ID to get, default is the current blog + * @return mixed Piwik site ID or n/a + */ + public function get_piwik_site_id( $blog_id = null, $force_fetch = false ) { + if ( ! $blog_id && $this->is_network_mode() ) { + $blog_id = get_current_blog_id(); + } + $result = self::$settings->get_option( 'site_id' ); + self::$logger->log( 'Database result: ' . $result ); + return ( ! empty( $result ) && ! $force_fetch ? $result : $this->request_piwik_site_id( $blog_id ) ); + } + + /** + * Get a detailed list of all Piwik sites + * + * @return mixed Piwik sites + */ + public function get_piwik_site_details() { + $id = WP_Piwik\Request::register( 'SitesManager.getSitesWithAtLeastViewAccess', array() ); + $piwik_site_details = $this->request( $id ); + return $piwik_site_details; + } + + /** + * Estimate a Piwik site ID by blog ID + * + * @param int $blog_id + * which blog's Piwik site ID to estimate, default is the current blog + * @return mixed Piwik site ID or n/a + */ + private function request_piwik_site_id( $blog_id = null ) { + $is_current = ! self::$settings->check_network_activation() || empty( $blog_id ); + if ( self::$settings->get_global_option( 'auto_site_config' ) ) { + $id = WP_Piwik\Request::register( + 'SitesManager.getSitesIdFromSiteUrl', + array( + 'url' => $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl, + ) + ); + $result = $this->request( $id ); + $this->log( 'Tried to identify current site, result: ' . wp_json_encode( $result ) ); + if ( is_array( $result ) && empty( $result ) ) { + $result = $this->add_piwik_site( $blog_id ); + } elseif ( 'n/a' !== $result && isset( $result [0] ) ) { + $result = $result [0] ['idsite']; + } else { + $result = null; + } + } else { + $result = null; + } + self::$logger->log( 'Get Matomo ID: WordPress site ' . ( $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl ) . ' = Matomo ID ' . $result ); + if ( null !== $result ) { + self::$settings->set_option( 'site_id', $result, $blog_id ); + if ( 'disabled' !== self::$settings->get_global_option( 'track_mode' ) && 'manually' !== self::$settings->get_global_option( 'track_mode' ) ) { + $code = $this->update_tracking_code( $result, $blog_id ); + } + $this::$settings->save(); + return $result; + } + return 'n/a'; + } + + /** + * Add a new Piwik + * + * @param int $blog_id + * which blog's Piwik site to create, default is the current blog + * @return int|null Piwik site ID + * @phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores + * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + */ + public function add_piwik_site( $blog_id = null ) { + $is_current = ! self::$settings->check_network_activation() || empty( $blog_id ); + // Do not add site if Piwik connection is unreliable + if ( ! $this->request( 'global.getPiwikVersion' ) ) { + return null; + } + $id = WP_Piwik\Request::register( + 'SitesManager.addSite', + array( + 'urls' => $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl, + 'siteName' => rawurlencode( $is_current ? get_bloginfo( 'name' ) : get_blog_details( $blog_id )->blogname ), + ) + ); + $result = $this->request( $id ); + if ( is_array( $result ) && isset( $result['value'] ) ) { + $result = (int) $result['value']; + } else { + $result = (int) $result; + } + self::$logger->log( 'Create Matomo ID: WordPress site ' . ( $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl ) . ' = Matomo ID ' . $result ); + if ( empty( $result ) ) { + return null; + } else { + // check disabled for bacwards compatibility + do_action( 'wp-piwik_site_created', $result ); + return $result; + } + } + + /** + * Update a Piwik site's detail information + * + * @param int $site_id + * which Piwik site to updated + * @param int $blog_id + * which blog's Piwik site ID to get, default is the current blog + */ + private function update_piwik_site( $site_id, $blog_id = null ) { + $is_current = ! self::$settings->check_network_activation() || empty( $blog_id ); + $id = WP_Piwik\Request::register( + 'SitesManager.updateSite', + array( + 'idSite' => $site_id, + 'urls' => $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl, + 'siteName' => $is_current ? get_bloginfo( 'name' ) : get_blog_details( $blog_id )->blogname, + ) + ); + $this->request( $id ); + self::$logger->log( 'Update Matomo site: WordPress site ' . ( $is_current ? get_bloginfo( 'url' ) : get_blog_details( $blog_id )->siteurl ) ); + } + + /** + * Update a site's tracking code + * + * @param int|false $site_id + * which Piwik site to updated + * @param int|null $blog_id + * which blog's Piwik site ID to get, default is the current blog + * @return string|false tracking code + */ + public function update_tracking_code( $site_id = false, $blog_id = null ) { + if ( ! $site_id ) { + $site_id = $this->get_piwik_site_id(); + } + if ( 'disabled' === self::$settings->get_global_option( 'track_mode' ) || 'manually' === self::$settings->get_global_option( 'track_mode' ) ) { + return false; + } + $id = WP_Piwik\Request::register( + 'SitesManager.getJavascriptTag', + array( + 'idSite' => $site_id, + 'mergeSubdomains' => self::$settings->get_global_option( 'track_across' ) ? 1 : 0, + 'mergeAliasUrls' => self::$settings->get_global_option( 'track_across_alias' ) ? 1 : 0, + 'disableCookies' => self::$settings->get_global_option( 'disable_cookies' ) ? 1 : 0, + 'crossDomain' => self::$settings->get_global_option( 'track_crossdomain_linking' ) ? 1 : 0, + 'trackNoScript' => 1, + ) + ); + $code = $this->request( $id ); + if ( is_array( $code ) && isset( $code['value'] ) ) { + $code = $code['value']; + } + $result = ! is_array( $code ) ? html_entity_decode( $code ) : ''; + self::$logger->log( 'Delivered tracking code: ' . $result ); + $result = WP_Piwik\TrackingCode::prepare_tracking_code( $result, self::$settings, self::$logger ); + if ( isset( $result ['script'] ) && ! empty( $result ['script'] ) ) { + self::$settings->set_option( 'tracking_code', $result ['script'], $blog_id ); + self::$settings->set_option( 'noscript_code', $result ['noscript'], $blog_id ); + self::$settings->set_global_option( 'proxy_url', $result ['proxy'] ); + return $result['script']; + } + return false; + } + + /** + * Update Piwik site if blog name changes + */ + public function on_blog_name_change() { + $this->update_piwik_site( self::$settings->get_option( 'site_id' ) ); + } + + /** + * Update Piwik site if blog URL changes + */ + public function on_site_url_change() { + $this->update_piwik_site( self::$settings->get_option( 'site_id' ) ); + } + + /** + * Register stats page meta boxes + */ + public function onload_stats_page() { + if ( self::$settings->get_global_option( 'disable_timelimit' ) ) { + set_time_limit( 0 ); + } + wp_enqueue_script( 'common' ); + wp_enqueue_script( 'wp-lists' ); + wp_enqueue_script( 'postbox' ); + wp_enqueue_script( 'wp-piwik', $this->get_plugin_url() . 'js/wp-piwik.js', array(), self::$version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', $this->get_plugin_url() . 'js/chartjs/chart.min.js', array(), self::$version, false ); + new \WP_Piwik\Widget\Chart( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Visitors( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Overview( $this, self::$settings, $this->stats_page_id ); + if ( self::$settings->get_global_option( 'stats_ecommerce' ) ) { + new \WP_Piwik\Widget\Ecommerce( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Items( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\ItemsCategory( $this, self::$settings, $this->stats_page_id ); + } + if ( self::$settings->get_global_option( 'stats_seo' ) ) { + new \WP_Piwik\Widget\Seo( $this, self::$settings, $this->stats_page_id ); + } + new \WP_Piwik\Widget\Pages( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Keywords( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Referrers( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Plugins( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Search( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Noresult( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Browsers( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\BrowserDetails( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Screens( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Types( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Models( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Systems( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\SystemDetails( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\City( $this, self::$settings, $this->stats_page_id ); + new \WP_Piwik\Widget\Country( $this, self::$settings, $this->stats_page_id ); + } + + /** + * Add per post statistics to a post's page + * @phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores + * @phpcs:disable WordPress.NamingConventions.ValidHookName.NonPrefixedHooknameFound + */ + public function onload_post_page() { + global $post; + $post_url = get_permalink( $post->ID ); + $this->log( 'Load per post statistics: ' . $post_url ); + $locations = apply_filters( 'wp-piwik_meta_boxes_locations', get_post_types( array( 'public' => true ), 'names' ) ); + array( + new Post( + $this, + self::$settings, + $locations, + 'side', + 'default', + array( + 'date' => self::$settings->get_global_option( 'perpost_stats' ), + 'period' => 'day', + 'url' => $post_url, + ) + ), + 'show', + ); + } + + /** + * Stats page changes by POST submit + * + * @see http://tinyurl.com/5r5vnzs + */ + public function on_stats_page_save_changes() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Cheatin’ uh?', 'wp-piwik' ) ); + } + check_admin_referer( 'wp-piwik_stats' ); + if ( ! empty( $_POST['_wp_http_referer'] ) ) { + wp_safe_redirect( sanitize_url( wp_unslash( $_POST['_wp_http_referer'] ) ) ); + } + } + + /** + * Get option value, choose method depending on network mode + * + * @param string $option option key + * @return string|array|null $default_value option value + */ + private function get_word_press_option( $option, $default_value = null ) { + return ( $this->is_network_mode() ? get_site_option( $option, $default_value ) : get_option( $option, $default_value ) ); + } + + /** + * Delete option, choose method depending on network mode + * + * @param string $option option key + */ + private function delete_word_press_option( $option ) { + if ( $this->is_network_mode() ) { + delete_site_option( $option ); + } else { + delete_option( $option ); + } + } + + /** + * Set option value, choose method depending on network mode + * + * @param string $option option key + * @param mixed $value option value + */ + private function update_word_press_option( $option, $value ) { + if ( $this->is_network_mode() ) { + update_site_option( $option, $value ); + } else { + update_option( $option, $value ); + } + } + + /** + * Check if WP-Piwik options page + * + * @return boolean True if current page is WP-Piwik's option page + */ + public static function is_valid_options_post() { + return is_admin() && check_admin_referer( 'wp-piwik_settings' ) && current_user_can( 'manage_options' ); + } + + private function set_up_ai_bot_tracking() { + $ai_bot_tracking = new \WP_Piwik\AIBotTracking( self::$settings, self::$logger ); + $ai_bot_tracking->register_hooks(); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/AIBotTracking.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/AIBotTracking.php new file mode 100644 index 00000000..1daa919f --- /dev/null +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/AIBotTracking.php @@ -0,0 +1,234 @@ +` directive that + * loads the misc/track_ai_bot.php script. This technique will only work + * for CDNs that support ESI. + * + * The misc/track_ai_bot.php script uses this class without all of WordPress loaded. + * It can also be loaded outside of WordPress. Because of this, it is important + * that the script and this class use as few total dependencies as possible. Otherwise, + * AI bot tracking reduce the performance of requests to cached content. + */ +class AIBotTracking { + + private static $ai_bot_tracked = false; + + private static $extensions_to_track = array( + '', // no extension + + 'htm', + 'html', + 'php', + ); + + /** + * @var Settings + */ + private $settings; + + /** + * @var Logger + */ + private $logger; + + /** + * @var AjaxTracker|null + */ + private $tracker = null; + + /** + * @param Settings $settings + * @param Logger $logger + */ + public function __construct( Settings $settings, Logger $logger ) { + $this->settings = $settings; + $this->logger = $logger; + } + + public function register_hooks() { + add_action( 'litespeed_init', array( $this, 'litespeed_init' ) ); + add_action( 'wp_footer', array( $this, 'do_ai_bot_tracking' ), 999999 ); + } + + public function litespeed_init() { + if ( class_exists( ESI::class ) + && $this->settings->is_ai_bot_tracking_enabled_via_esi_includes() + ) { + ESI::set_has_esi(); + } + } + + /** + * @param string|null $url + * @return void + */ + public function do_ai_bot_tracking( $url = null ) { + // track AI bots only once per request + if ( self::$ai_bot_tracked ) { + return; + } + + self::$ai_bot_tracked = true; + + // if using ESI to track, and not within the track_ai_bot.php script, output the appropriate ESI tag + $is_using_esi_to_track = $this->settings->is_ai_bot_tracking_enabled_via_esi_includes(); + if ( + $is_using_esi_to_track + && empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) + ) { + $track_script_url = plugins_url( '/misc/track_ai_bot.php', WP_PIWIK_FILE ) . '?mtm_esi=1&mtm_url=' . rawurlencode( AjaxTracker::getCurrentUrl() ); + echo ''; + return; + } + + if ( ! $this->is_doing_ai_bot_tracking_this_request() ) { + return; + } + + $response_code = http_response_code(); + $request_elapsed_ms = null; + + // cannot track request time if executed via an esi:include + if ( + empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) + && array_key_exists( 'REQUEST_TIME_FLOAT', $_SERVER ) + ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $request_elapsed_ms = (int) ( ( microtime( true ) - floatval( $_SERVER['REQUEST_TIME_FLOAT'] ) ) * 1000 ); + } + + if ( empty( $response_code ) ) { + $response_code = 200; + } + + // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled + $source = 'WordPress'; + + if ( empty( $url ) ) { + $url = AjaxTracker::getCurrentUrl(); + } + + $tracker = $this->get_tracker(); + + $tracker->setUrl( $url ); + + // cannot count bytes echo'd so no response size tracked + $tracker->doTrackPageViewIfAIBot( $response_code, null, $request_elapsed_ms, $source ); + } + + public function should_track_current_page() { + if ( is_admin() ) { + return false; + } + + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + return false; + } + + if ( wp_doing_ajax() ) { + return false; + } + + if ( empty( $_SERVER['REQUEST_URI'] ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $request_path = (string) wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ); + + if ( preg_match( '/matomo\.php$/', $request_path ) ) { + return false; + } + + if ( $this->is_request_for_file( $request_path ) ) { + return false; + } + + return true; + } + + private function is_request_for_file( $request_path ) { + if ( + ! empty( $_SERVER['DOCUMENT_ROOT'] ) + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + && is_dir( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) . $request_path ) + ) { + return false; + } + + $extension = pathinfo( $request_path, PATHINFO_EXTENSION ); + return ! in_array( $extension, self::$extensions_to_track, true ); + } + + public function is_js_execution_detected() { + return ! empty( $_COOKIE['matomo_has_js'] ) + && '1' === $_COOKIE['matomo_has_js']; + } + + private function is_doing_ai_bot_tracking_this_request() { + if ( ! empty( $GLOBALS['WP_PIWIK_IN_ESI'] ) ) { + return true; + } + + if ( ! $this->should_track_current_page() ) { + return false; + } + + if ( $this->is_js_execution_detected() ) { + return false; + } + + $tracker = $this->get_tracker(); + + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + if ( ! AjaxTracker::isUserAgentAIBot( $tracker->userAgent ) ) { + return false; + } + + if ( + ! $this->settings->is_ai_bot_tracking_enabled() + || ! $this->settings->is_tracking_enabled() + ) { + return false; + } + + return true; + } + + private function get_tracker() { + if ( empty( $this->tracker ) ) { + $this->tracker = new AjaxTracker( $this->settings, $this->logger ); + $this->tracker->setRequestTimeout( 1 ); + } + + return $this->tracker; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin.php index ed07dd42..49b94011 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin.php @@ -1,24 +1,34 @@ getPluginURL().'css/wp-piwik.css', array(), self::$wpPiwik->getPluginVersion()); - } - - public function onLoad() {} +abstract class Admin { - } \ No newline at end of file + /** + * @var \WP_Piwik + */ + protected static $wp_piwik; + protected static $page_id; + + /** + * @var Settings + */ + protected static $settings; + + public function __construct( $wp_piwik, $settings ) { + self::$wp_piwik = $wp_piwik; + self::$settings = $settings; + } + + abstract public function show(); + + abstract public function print_admin_scripts(); + + public function print_admin_styles() { + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), self::$wp_piwik->get_plugin_version() ); + } + + public function on_load() { + // empty + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Network.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Network.php index df3835db..0abf3ed4 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Network.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Network.php @@ -1,19 +1,16 @@ getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL() . 'js/chartjs/chart.min.js', "3.4.1" ); - } - - public function onLoad() { - self::$wpPiwik->onloadStatsPage(self::$pageID); - } - } \ No newline at end of file + public function print_admin_scripts() { + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + } + + public function on_load() { + self::$wp_piwik->onload_stats_page(); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Settings.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Settings.php index 504a665d..cf08fa8e 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Settings.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Admin/Settings.php @@ -1,816 +1,1301 @@ - - */ -class Settings extends \WP_Piwik\Admin { - - /** - * Builds and displays the settings page - */ - public function show() { - if (isset($_GET['sitebrowser']) && $_GET['sitebrowser']) { - new \WP_Piwik\Admin\Sitebrowser(self::$wpPiwik); - return; - } - if (isset($_GET['clear']) && $_GET['clear'] && check_admin_referer()) { - $this->clear($_GET['clear'] == 2); - self::$wpPiwik->resetRequest(); - echo '
'; - return; - } elseif (self::$wpPiwik->isConfigSubmitted()) { - $this->showBox ( 'updated', 'yes', __ ( 'Changes saved.' ) ); - self::$wpPiwik->resetRequest(); - if (self::$settings->getGlobalOption('piwik_mode') == 'php') { - self::$wpPiwik->definePiwikConstants(); - } - if (self::$settings->getGlobalOption ( 'auto_site_config' ) && self::$wpPiwik->isConfigured ()) { - $siteId = self::$wpPiwik->getPiwikSiteId (null, true); - self::$wpPiwik->updateTrackingCode ( $siteId ); - self::$settings->setOption ( 'site_id', $siteId ); - } else { - self::$wpPiwik->updateTrackingCode(); - } - } - global $wp_roles; - ?> -
- getHeadline ( 1, 'admin-generic', 'Settings', true ); - if (isset($_GET['testscript']) && $_GET['testscript']) - $this->runTestscript(); - ?> - isConfigured ()) { - $piwikVersion = self::$wpPiwik->request ( 'global.getPiwikVersion' ); - if (is_array ( $piwikVersion ) && isset( $piwikVersion['value'] )) - $piwikVersion = $piwikVersion['value']; - if (! empty ( $piwikVersion ) && !is_array( $piwikVersion )) - $this->showDonation(); - } - ?> -
- - - - - '; - printf ( '', __ ( 'Thanks for using WP-Matomo!', 'wp-piwik' ) ); - if (self::$wpPiwik->isConfigured ()) { - if (! empty ( $piwikVersion ) && !is_array( $piwikVersion )) { - $this->showText ( sprintf ( __ ( 'WP-Matomo %s is successfully connected to Matomo %s.', 'wp-piwik' ), self::$wpPiwik->getPluginVersion (), $piwikVersion ) . ' ' . (! self::$wpPiwik->isNetworkMode () ? sprintf ( __ ( 'You are running WordPress %s.', 'wp-piwik' ), get_bloginfo ( 'version' ) ) : sprintf ( __ ( 'You are running a WordPress %s blog network (WPMU). WP-Matomo will handle your sites as different websites.', 'wp-piwik' ), get_bloginfo ( 'version' ) )) ); - } else { - $errorMessage = \WP_Piwik\Request::getLastError(); - if ( empty( $errorMessage ) ) - $this->showBox ( 'error', 'no', sprintf ( __ ( 'WP-Matomo %s was not able to connect to Matomo using your configuration. Check the »Connect to Matomo« section below.', 'wp-piwik' ), self::$wpPiwik->getPluginVersion () ) ); - else - $this->showBox ( 'error', 'no', sprintf ( __ ( 'WP-Matomo %s was not able to connect to Matomo using your configuration. During connection the following error occured:
%s', 'wp-piwik' ), self::$wpPiwik->getPluginVersion (), $errorMessage ) ); - } - } else - $this->showBox ( 'error', 'no', sprintf ( __ ( 'WP-Matomo %s has to be connected to Matomo first. Check the »Connect to Matomo« section below.', 'wp-piwik' ), self::$wpPiwik->getPluginVersion () ) ); - - $tabs ['connect'] = array ( - 'icon' => 'admin-plugins', - 'name' => __('Connect to Matomo', 'wp-piwik') - ); - if (self::$wpPiwik->isConfigured ()) { - $tabs ['statistics'] = array ( - 'icon' => 'chart-pie', - 'name' => __('Show Statistics', 'wp-piwik') - ); - $tabs ['tracking'] = array ( - 'icon' => 'location-alt', - 'name' => __('Enable Tracking', 'wp-piwik') - ); - } - $tabs ['expert'] = array ( - 'icon' => 'shield', - 'name' => __('Expert Settings', 'wp-piwik') - ); - $tabs ['support'] = array ( - 'icon' => 'lightbulb', - 'name' => __('Support', 'wp-piwik') - ); - $tabs ['credits'] = array ( - 'icon' => 'groups', - 'name' => __('Credits', 'wp-piwik') - ); - - echo '

%s
'; - - if (! self::$wpPiwik->isConfigured ()) - $this->showBox ( 'updated', 'info', __ ( 'Before you can complete the setup, make sure you have a Matomo instance running. If you don\'t have one, you can', 'wp-piwik' ) .' ' . __ ('create a free account', 'wp-piwik' ) .' ' . __ ('or ', 'wp-piwik' ) .'' . __ ('install the "Matomo for WordPress" plugin', 'wp-piwik' ) .' ' . __ ('instead.', 'wp-piwik' ) ); - - if (! function_exists ( 'curl_init' ) && ! ini_get ( 'allow_url_fopen' )) - $this->showBox ( 'error', 'no', __ ( 'Neither cURL nor fopen are available. So WP-Matomo can not use the HTTP API and not connect to InnoCraft Cloud.' ) . ' ' . sprintf ( '%s.', 'https://wordpress.org/plugins/wp-piwik/faq/', __ ( 'More information', 'wp-piwik' ) ) ); - - $description = sprintf ( '%s
%s: %s
%s: %s
%s: %s', __ ( 'You can choose between three connection methods:', 'wp-piwik' ), __ ( 'Self-hosted (HTTP API, default)', 'wp-piwik' ), __ ( 'This is the default option for a self-hosted Matomo and should work for most configurations. WP-Matomo will connect to Matomo using http(s).', 'wp-piwik' ), __ ( 'Self-hosted (PHP API)', 'wp-piwik' ), __ ( 'Choose this, if your self-hosted Matomo and WordPress are running on the same machine and you know the full server path to your Matomo instance.', 'wp-piwik' ), __ ( 'Cloud-hosted', 'wp-piwik' ), __ ( 'If you are using a cloud-hosted Matomo by InnoCraft, you can simply use this option. Be carefull to choose the option which fits to your cloud domain (matomo.cloud or innocraft.cloud).', 'wp-piwik' ) ); - $this->showSelect ( 'piwik_mode', __ ( 'Matomo Mode', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled (WP-Matomo will not connect to Matomo)', 'wp-piwik' ), - 'http' => __ ( 'Self-hosted (HTTP API, default)', 'wp-piwik' ), - 'php' => __ ( 'Self-hosted (PHP API)', 'wp-piwik' ), - 'cloud-matomo' => __('Cloud-hosted (Innocraft Cloud, *.matomo.cloud)', 'wp-piwik'), - 'cloud' => __ ( 'Cloud-hosted (InnoCraft Cloud, *.innocraft.cloud)', 'wp-piwik' ) - ), $description, 'jQuery(\'tr.wp-piwik-mode-option\').addClass(\'hidden\'); jQuery(\'.wp-piwik-mode-option-\' + jQuery(\'#piwik_mode\').val()).removeClass(\'hidden\');', false, '', self::$wpPiwik->isConfigured () ); - - $this->showInput ( 'piwik_url', __ ( 'Matomo URL', 'wp-piwik' ), __( 'Enter your Matomo URL. This is the same URL you use to access your Matomo instance, e.g. http://www.example.com/matomo/.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'piwik_mode' ) != 'http', 'wp-piwik-mode-option', 'http', self::$wpPiwik->isConfigured (), true ); - $this->showInput ( 'piwik_path', __ ( 'Matomo path', 'wp-piwik' ), __( 'Enter the file path to your Matomo instance, e.g. /var/www/matomo/.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'piwik_mode' ) != 'php', 'wp-piwik-mode-option', 'php', self::$wpPiwik->isConfigured (), true ); - $this->showInput ( 'piwik_user', __ ( 'Innocraft subdomain', 'wp-piwik' ), __( 'Enter your InnoCraft Cloud subdomain. It is also part of your URL: https://SUBDOMAIN.innocraft.cloud.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'piwik_mode' ) != 'cloud', 'wp-piwik-mode-option', 'cloud', self::$wpPiwik->isConfigured () ); - $this->showInput ( 'matomo_user', __ ( 'Matomo subdomain', 'wp-piwik' ), __( 'Enter your Matomo Cloud subdomain. It is also part of your URL: https://SUBDOMAIN.matomo.cloud.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'piwik_mode' ) != 'cloud-matomo', 'wp-piwik-mode-option', 'cloud-matomo', self::$wpPiwik->isConfigured () ); - $this->showInput ( 'piwik_token', __ ( 'Auth token', 'wp-piwik' ), __( 'Enter your Matomo auth token here. It is an alphanumerical code like 0a1b2c34d56e78901fa2bc3d45678efa.', 'wp-piwik' ).' '.sprintf ( __ ( 'See %sWP-Matomo FAQ%s.', 'wp-piwik' ), '', '' ), false, '', '', self::$wpPiwik->isConfigured (), true ); - - // Site configuration - $piwikSiteId = self::$wpPiwik->isConfigured () ? self::$wpPiwik->getPiwikSiteId () : false; - if (! self::$wpPiwik->isNetworkMode() ) { - $this->showCheckbox ( - 'auto_site_config', - __ ( 'Auto config', 'wp-piwik' ), - __ ( 'Check this to automatically choose your blog from your Matomo sites by URL. If your blog is not added to Matomo yet, WP-Matomo will add a new site.', 'wp-piwik' ), - false, - '', - '', - 'jQuery(\'tr.wp-piwik-auto-option\').toggle(\'hidden\');' . ($piwikSiteId ? 'jQuery(\'#site_id\').val(' . $piwikSiteId . ');' : '') - ); - if (self::$wpPiwik->isConfigured ()) { - $piwikSiteList = self::$wpPiwik->getPiwikSiteDetails (); - if (isset($piwikSiteList['result']) && $piwikSiteList['result'] == 'error') { - $this->showBox ( 'error', 'no', sprintf ( __ ( 'WP-Matomo %s was not able to get sites with at least view access:
%s', 'wp-piwik' ), self::$wpPiwik->getPluginVersion (), $errorMessage ) ); - } else { - if (is_array($piwikSiteList)) - foreach ($piwikSiteList as $details) - $piwikSiteDetails[$details['idsite']] = $details; - unset($piwikSiteList); - if ($piwikSiteId != 'n/a' && isset($piwikSiteDetails) && is_array($piwikSiteDetails)) - $piwikSiteDescription = $piwikSiteDetails [$piwikSiteId] ['name'] . ' (' . $piwikSiteDetails [$piwikSiteId] ['main_url'] . ')'; - else - $piwikSiteDescription = 'n/a'; - echo ''; - if (isset ($piwikSiteDetails) && is_array($piwikSiteDetails)) - foreach ($piwikSiteDetails as $key => $siteData) - $siteList [$siteData['idsite']] = $siteData ['name'] . ' (' . $siteData ['main_url'] . ')'; - if (isset($siteList)) - $this->showSelect('site_id', __('Select site', 'wp-piwik'), $siteList, 'Choose the Matomo site corresponding to this blog.', '', self::$settings->getGlobalOption('auto_site_config'), 'wp-piwik-auto-option', true, false); - } - } - } else echo ''; - - echo $submitButton; - - echo '
' . __('Determined site', 'wp-piwik') . ':' . $piwikSiteDescription . '
'; - // Stats configuration - $this->showSelect ( 'default_date', __ ( 'Matomo default date', 'wp-piwik' ), array ( - 'today' => __ ( 'Today', 'wp-piwik' ), - 'yesterday' => __ ( 'Yesterday', 'wp-piwik' ), - 'current_month' => __ ( 'Current month', 'wp-piwik' ), - 'last_month' => __ ( 'Last month', 'wp-piwik' ), - 'current_week' => __ ( 'Current week', 'wp-piwik' ), - 'last_week' => __ ( 'Last week', 'wp-piwik' ) - ), __ ( 'Default date shown on statistics page.', 'wp-piwik' ) ); - - $this->showCheckbox ( 'stats_seo', __ ( 'Show SEO data', 'wp-piwik' ), __ ( 'Display SEO ranking data on statistics page.', 'wp-piwik' ) . ' (' . __ ( 'Slow!', 'wp-piwik' ) . ')' ); - $this->showCheckbox ( 'stats_ecommerce', __ ( 'Show e-commerce data', 'wp-piwik' ), __ ( 'Display e-commerce data on statistics page.', 'wp-piwik' ) ); - - $this->showSelect ( 'dashboard_widget', __ ( 'Dashboard overview', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'yesterday' => __ ( 'Yesterday', 'wp-piwik' ), - 'today' => __ ( 'Today', 'wp-piwik' ), - 'last30' => __ ( 'Last 30 days', 'wp-piwik' ), - 'last60' => __ ( 'Last 60 days', 'wp-piwik' ), - 'last90' => __ ( 'Last 90 days', 'wp-piwik' ) - ), __ ( 'Enable WP-Matomo dashboard widget "Overview".', 'wp-piwik' ) ); - - $this->showCheckbox ( 'dashboard_chart', __ ( 'Dashboard graph', 'wp-piwik' ), __ ( 'Enable WP-Matomo dashboard widget "Graph".', 'wp-piwik' ) ); - - $this->showCheckbox ( 'dashboard_seo', __ ( 'Dashboard SEO', 'wp-piwik' ), __ ( 'Enable WP-Matomo dashboard widget "SEO".', 'wp-piwik' ) . ' (' . __ ( 'Slow!', 'wp-piwik' ) . ')' ); - - $this->showCheckbox ( 'dashboard_ecommerce', __ ( 'Dashboard e-commerce', 'wp-piwik' ), __ ( 'Enable WP-Matomo dashboard widget "E-commerce".', 'wp-piwik' ) ); - - $this->showCheckbox ( 'toolbar', __ ( 'Show graph on WordPress Toolbar', 'wp-piwik' ), __ ( 'Display a last 30 days visitor graph on WordPress\' toolbar.', 'wp-piwik' ) ); - - echo ''; - - $this->showSelect ( 'perpost_stats', __ ( 'Show per post stats', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'yesterday' => __ ( 'Yesterday', 'wp-piwik' ), - 'today' => __ ( 'Today', 'wp-piwik' ), - 'last30' => __ ( 'Last 30 days', 'wp-piwik' ), - 'last60' => __ ( 'Last 60 days', 'wp-piwik' ), - 'last90' => __ ( 'Last 90 days', 'wp-piwik' ) - ), __ ( 'Show stats about single posts at the post edit admin page.', 'wp-piwik' ) ); - - - $this->showCheckbox ( 'piwik_shortcut', __ ( 'Matomo shortcut', 'wp-piwik' ), __ ( 'Display a shortcut to Matomo itself.', 'wp-piwik' ) ); - - $this->showInput ( 'plugin_display_name', __ ( 'WP-Matomo display name', 'wp-piwik' ), __ ( 'Plugin name shown in WordPress.', 'wp-piwik' ) ); - - $this->showCheckbox ( 'shortcodes', __ ( 'Enable shortcodes', 'wp-piwik' ), __ ( 'Enable shortcodes in post or page content.', 'wp-piwik' ) ); - - echo $submitButton; - - echo ''; - - // Tracking Configuration - $isNotTracking = self::$settings->getGlobalOption ( 'track_mode' ) == 'disabled'; - $isNotGeneratedTracking = $isNotTracking || self::$settings->getGlobalOption ( 'track_mode' ) == 'manually'; - $fullGeneratedTrackingGroup = 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy'; - - $description = sprintf ( '%s
%s: %s
%s: %s
%s: %s
%s: %s
%s: %s', __ ( 'You can choose between four tracking code modes:', 'wp-piwik' ), __ ( 'Disabled', 'wp-piwik' ), __ ( 'WP-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.', 'wp-piwik' ), __ ( 'Default tracking', 'wp-piwik' ), __ ( 'WP-Matomo will use Matomo\'s standard tracking code.', 'wp-piwik' ), __ ( 'Use js/index.php', 'wp-piwik' ), __ ( 'You can choose this tracking code, to deliver a minified proxy code and to avoid using the files called piwik.js or piwik.php.', 'wp-piwik' ).' '.sprintf( __( 'See %sreadme file%s.', 'wp-piwik' ), '', ''), __ ( 'Use proxy script', 'wp-piwik' ), __ ( 'Use this tracking code to not reveal the Matomo server URL.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo FAQ%s.', 'wp-piwik' ), '', '' ) , __ ( 'Enter manually', 'wp-piwik' ), __ ( '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.', 'wp-piwik' ).( self::$wpPiwik->isNetworkMode() ? ' '.__ ( 'Use the placeholder {ID} to add the Matomo site ID.', 'wp-piwik' ) : '' ) ); - $this->showSelect ( 'track_mode', __ ( 'Add tracking code', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'default' => __ ( 'Default tracking', 'wp-piwik' ), - 'js' => __ ( 'Use js/index.php', 'wp-piwik' ), - 'proxy' => __ ( 'Use proxy script', 'wp-piwik' ), - 'manually' => __ ( 'Enter manually', 'wp-piwik' ) - ), $description, 'jQuery(\'tr.wp-piwik-track-option\').addClass(\'hidden\'); jQuery(\'tr.wp-piwik-track-option-\' + jQuery(\'#track_mode\').val()).removeClass(\'hidden\'); jQuery(\'#tracking_code, #noscript_code\').prop(\'readonly\', jQuery(\'#track_mode\').val() != \'manually\');' ); - - $this->showTextarea ( 'tracking_code', __ ( 'Tracking code', 'wp-piwik' ), 15, 'This is a preview of your current tracking code. If you choose to enter your tracking code manually, you can change it here.', $isNotTracking, 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy wp-piwik-track-option-manually', true, '', (self::$settings->getGlobalOption ( 'track_mode' ) != 'manually'), false ); - - $this->showSelect ( 'track_codeposition', __ ( 'JavaScript code position', 'wp-piwik' ), array ( - 'footer' => __ ( 'Footer', 'wp-piwik' ), - 'header' => __ ( 'Header', 'wp-piwik' ) - ), __ ( 'Choose whether the JavaScript code is added to the footer or the header.', 'wp-piwik' ), '', $isNotTracking, 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy wp-piwik-track-option-manually' ); - - $this->showTextarea ( 'noscript_code', __ ( 'Noscript code', 'wp-piwik' ), 2, 'This is a preview of your <noscript> code which is part of your tracking code.', self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy', 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually', true, '', (self::$settings->getGlobalOption ( 'track_mode' ) != 'manually'), false ); - - $this->showCheckbox ( 'track_noscript', __ ( 'Add <noscript>', 'wp-piwik' ), __ ( 'Adds the <noscript> code to your footer.', 'wp-piwik' ) . ' ' . __ ( 'Disabled in proxy mode.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy', 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually' ); - - $this->showCheckbox ( 'track_nojavascript', __ ( 'Add rec parameter to noscript code', 'wp-piwik' ), __ ( 'Enable tracking for visitors without JavaScript (not recommended).', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo FAQ%s.', 'wp-piwik' ), '', '' ) . ' ' . __ ( 'Disabled in proxy mode.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy', 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually' ); - - $this->showSelect ( 'track_content', __ ( 'Enable content tracking', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'all' => __ ( 'Track all content blocks', 'wp-piwik' ), - 'visible' => __ ( 'Track only visible content blocks', 'wp-piwik' ) - ), __ ( 'Content tracking allows you to track interaction with the content of a web page or application.' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), '', $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - $this->showCheckbox ( 'track_search', __ ( 'Track search', 'wp-piwik' ), __ ( 'Use Matomo\'s advanced Site Search Analytics feature.' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - $this->showCheckbox ( 'track_404', __ ( 'Track 404', 'wp-piwik' ), __ ( 'WP-Matomo can automatically add a 404-category to track 404-page-visits.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo FAQ%s.', 'wp-piwik' ), '', '' ), $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - echo ''; - echo ''; - - $this->showCheckbox ( 'add_customvars_box', __ ( 'Show custom variables box', 'wp-piwik' ), __ ( ' Show a "custom variables" edit box on post edit page.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - $this->showInput ( 'add_download_extensions', __ ( 'Add new file types for download tracking', 'wp-piwik' ), __ ( 'Add file extensions for download tracking, divided by a vertical bar (|).', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showSelect ( 'require_consent', __ ( 'Tracking or cookie consent', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'consent' => __ ( 'Require consent', 'wp-piwik' ), - 'cookieconsent' => __ ( 'Require cookie consent', 'wp-piwik' ) - ), __ ( 'Enable support for consent managers.' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), '', $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showCheckbox ( 'disable_cookies', __ ( 'Disable cookies', 'wp-piwik' ), __ ( 'Disable all tracking cookies for a visitor.', 'wp-piwik' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showCheckbox ( 'limit_cookies', __ ( 'Limit cookie lifetime', 'wp-piwik' ), __ ( 'You can limit the cookie lifetime to avoid tracking your users over a longer period as necessary.', 'wp-piwik' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup, true, 'jQuery(\'tr.wp-piwik-cookielifetime-option\').toggleClass(\'wp-piwik-hidden\');' ); - - $this->showInput ( 'limit_cookies_visitor', __ ( 'Visitor timeout (seconds)', 'wp-piwik' ), false, $isNotGeneratedTracking || ! self::$settings->getGlobalOption ( 'limit_cookies' ), $fullGeneratedTrackingGroup.' wp-piwik-cookielifetime-option'. (self::$settings->getGlobalOption ( 'limit_cookies' )? '': ' wp-piwik-hidden') ); - - $this->showInput ( 'limit_cookies_session', __ ( 'Session timeout (seconds)', 'wp-piwik' ), false, $isNotGeneratedTracking || ! self::$settings->getGlobalOption ( 'limit_cookies' ), $fullGeneratedTrackingGroup .' wp-piwik-cookielifetime-option'. (self::$settings->getGlobalOption ( 'limit_cookies' )? '': ' wp-piwik-hidden') ); - - $this->showInput ( 'limit_cookies_referral', __ ( 'Referral timeout (seconds)', 'wp-piwik' ), false, $isNotGeneratedTracking || ! self::$settings->getGlobalOption ( 'limit_cookies' ), $fullGeneratedTrackingGroup .' wp-piwik-cookielifetime-option'. (self::$settings->getGlobalOption ( 'limit_cookies' )? '': ' wp-piwik-hidden') ); - - $this->showCheckbox ( 'track_admin', __ ( 'Track admin pages', 'wp-piwik' ), __ ( 'Enable to track users on admin pages (remember to configure the tracking filter appropriately).', 'wp-piwik' ), $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - echo ''; - echo ''; - - $this->showCheckbox ( 'track_across', __ ( 'Track subdomains in the same website', 'wp-piwik' ), __ ( 'Adds *.-prefix to cookie domain.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showCheckbox ( 'track_across_alias', __ ( 'Do not count subdomains as outlink', 'wp-piwik' ), __ ( 'Adds *.-prefix to tracked domain.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showCheckbox ( 'track_crossdomain_linking', __ ( 'Enable cross domain linking', 'wp-piwik' ), __ ( 'When enabled, it will make sure to use the same visitor ID for the same visitor across several domains. This works only when this feature is enabled because the visitor ID is stored in a cookie and cannot be read on the other domain by default. When this feature is enabled, it will append a URL parameter "pk_vid" that contains the visitor ID when a user clicks on a URL that belongs to one of your domains. For this feature to work, you also have to configure which domains should be treated as local in your Matomo website settings. This feature requires Matomo 3.0.2.', 'wp-piwik' ), self::$settings->getGlobalOption ( 'track_mode' ) == 'proxy', 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually'); - - $this->showCheckbox ( 'track_feed', __ ( 'Track RSS feeds', 'wp-piwik' ), __ ( 'Enable to track posts in feeds via tracking pixel.', 'wp-piwik' ), $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually' ); - - $this->showCheckbox ( 'track_feed_addcampaign', __ ( 'Track RSS feed links as campaign', 'wp-piwik' ), __ ( 'This will add Matomo campaign parameters to the RSS feed links.' . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ), 'wp-piwik' ), $isNotTracking, $fullGeneratedTrackingGroup . ' wp-piwik-track-option-manually', true, 'jQuery(\'tr.wp-piwik-feed_campaign-option\').toggle(\'hidden\');' ); - - $this->showInput ( 'track_feed_campaign', __ ( 'RSS feed campaign', 'wp-piwik' ), __ ( 'Keyword: post name.', 'wp-piwik' ), $isNotGeneratedTracking || ! self::$settings->getGlobalOption ( 'track_feed_addcampaign' ), $fullGeneratedTrackingGroup . ' wp-piwik-feed_campaign-option' ); - - $this->showInput ( 'track_heartbeat', __ ( 'Enable heartbeat timer', 'wp-piwik' ), __ ( 'Enable a heartbeat timer to get more accurate visit lengths by sending periodical HTTP ping requests as long as the site is opened. Enter the time between the pings in seconds (Matomo default: 15) to enable or 0 to disable this feature. Note: This will cause a lot of additional HTTP requests on your site.', 'wp-piwik' ), $isNotGeneratedTracking, $fullGeneratedTrackingGroup ); - - $this->showSelect ( 'track_user_id', __ ( 'User ID Tracking', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled', 'wp-piwik' ), - 'uid' => __ ( 'WP User ID', 'wp-piwik' ), - 'email' => __ ( 'Email Address', 'wp-piwik' ), - 'username' => __ ( 'Username', 'wp-piwik' ), - 'displayname' => __ ( 'Display Name (Not Recommended!)', 'wp-piwik' ) - ), __ ( '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.', 'wp-piwik' ), '', $isNotTracking, $fullGeneratedTrackingGroup ); - - echo $submitButton; - echo ''; - - $this->showText ( __ ( 'Usually, you do not need to change these settings. If you want to do so, you should know what you do or you got an expert\'s advice.', 'wp-piwik' ) ); - - $this->showCheckbox ( 'cache', __ ( 'Enable cache', 'wp-piwik' ), __ ( 'Cache API calls, which not contain today\'s values, for a week.', 'wp-piwik' ) ); - - if (function_exists('curl_init') && ini_get('allow_url_fopen')) - $this->showSelect ( 'http_connection', __ ( 'HTTP connection via', 'wp-piwik' ), array ( - 'curl' => __ ( 'cURL', 'wp-piwik' ), - 'fopen' => __ ( 'fopen', 'wp-piwik' ) - ), __('Choose whether WP-Matomo should use cURL or fopen to connect to Matomo in HTTP or Cloud mode.', 'wp-piwik' ) ); - - $this->showSelect ( 'http_method', __ ( 'HTTP method', 'wp-piwik' ), array ( - 'post' => __ ( 'POST', 'wp-piwik' ), - 'get' => __ ( 'GET', 'wp-piwik' ) - ), __('Choose whether WP-Matomo should use POST or GET in HTTP or Cloud mode.', 'wp-piwik' ) ); - - $this->showCheckbox ( 'disable_timelimit', __ ( 'Disable time limit', 'wp-piwik' ), __ ( 'Use set_time_limit(0) if stats page causes a time out.', 'wp-piwik' ) ); - - $this->showInput ( 'filter_limit', __ ( 'Filter limit', 'wp-piwik' ), __ ( 'Use filter_limit if you need to get more than 100 results per page.', 'wp-piwik' ) ); - - $this->showInput ( 'connection_timeout', __ ( 'Connection timeout', 'wp-piwik' ), 'Define a connection timeout for all HTTP requests done by WP-Matomo in seconds.' ); - - $this->showCheckbox ( 'disable_ssl_verify', __ ( 'Disable SSL peer verification', 'wp-piwik' ), '(' . __ ( 'not recommended', 'wp-piwik' ) . ')' ); - $this->showCheckbox ( 'disable_ssl_verify_host', __ ( 'Disable SSL host verification', 'wp-piwik' ), '(' . __ ( 'not recommended', 'wp-piwik' ) . ')' ); - - $this->showSelect ( 'piwik_useragent', __ ( 'User agent', 'wp-piwik' ), array ( - 'php' => __ ( 'Use the PHP default user agent', 'wp-piwik' ) . (ini_get ( 'user_agent' ) ? '(' . ini_get ( 'user_agent' ) . ')' : ' (' . __ ( 'empty', 'wp-piwik' ) . ')'), - 'own' => __ ( 'Define a specific user agent', 'wp-piwik' ) - ), 'WP-Matomo can send the default user agent defined by your PHP settings or use a specific user agent below. The user agent is send by WP-Matomo if HTTP requests are performed.', 'jQuery(\'tr.wp-piwik-useragent-option\').toggleClass(\'hidden\');' ); - $this->showInput ( 'piwik_useragent_string', __ ( 'Specific user agent', 'wp-piwik' ), 'Define a user agent description which is send by WP-Matomo if HTTP requests are performed.', self::$settings->getGlobalOption ( 'piwik_useragent' ) != 'own', 'wp-piwik-useragent-option' ); - - $this->showCheckbox ( 'dnsprefetch', __ ( 'Enable DNS prefetch', 'wp-piwik' ), __ ( 'Add a DNS prefetch tag.' . ' ' . sprintf ( __ ( 'See %sMatomo Blog%s.', 'wp-piwik' ), '', '' ), 'wp-piwik' ) ); - - $this->showCheckbox ( 'track_datacfasync', __ ( 'Add data-cfasync=false', 'wp-piwik' ), __ ( 'Adds data-cfasync=false to the script tag, e.g., to ask Rocket Loader to ignore the script.' . ' ' . sprintf ( __ ( 'See %sCloudFlare Knowledge Base%s.', 'wp-piwik' ), '', '' ), 'wp-piwik' ) ); - - $this->showInput ( 'track_cdnurl', __ ( 'CDN URL', 'wp-piwik' ).' http://', 'Enter URL if you want to load the tracking code via CDN.' ); - - $this->showInput ( 'track_cdnurlssl', __ ( 'CDN URL (SSL)', 'wp-piwik' ).' https://', 'Enter URL if you want to load the tracking code via a separate SSL CDN.' ); - - $this->showSelect ( 'force_protocol', __ ( 'Force Matomo to use a specific protocol', 'wp-piwik' ), array ( - 'disabled' => __ ( 'Disabled (default)', 'wp-piwik' ), - 'http' => __ ( 'http', 'wp-piwik' ), - 'https' => __ ( 'https (SSL)', 'wp-piwik' ) - ), __ ( 'Choose if you want to explicitly force Matomo to use HTTP or HTTPS. Does not work with a CDN URL.', 'wp-piwik' ) ); - - $this->showCheckbox ( 'remove_type_attribute', __ ( 'Remove type attribute', 'wp-piwik' ), __ ( 'Removes the type attribute from Matomo\'s tracking code script tag.', 'wp-piwik') ); - - $this->showSelect ( 'update_notice', __ ( 'Update notice', 'wp-piwik' ), array ( - 'enabled' => __ ( 'Show always if WP-Matomo is updated', 'wp-piwik' ), - 'script' => __ ( 'Show only if WP-Matomo is updated and settings were changed', 'wp-piwik' ), - 'disabled' => __ ( 'Disabled', 'wp-piwik' ) - ), __ ( 'Choose if you want to get an update notice if WP-Matomo is updated.', 'wp-piwik' ) ); - - $this->showInput ( 'set_download_extensions', __ ( 'Define all file types for download tracking', 'wp-piwik' ), __ ( 'Replace Matomo\'s default file extensions for download tracking, divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo documentation%s.', 'wp-piwik' ), '', '' ) ); - - $this->showInput ( 'set_download_classes', __ ( 'Set classes to be treated as downloads', 'wp-piwik' ), __ ( 'Set classes to be treated as downloads (in addition to piwik_download), divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo JavaScript Tracking Client reference%s.', 'wp-piwik' ), '', '' ) ); - - $this->showInput ( 'set_link_classes', __ ( 'Set classes to be treated as outlinks', 'wp-piwik' ), __ ( 'Set classes to be treated as outlinks (in addition to piwik_link), divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf ( __ ( 'See %sMatomo JavaScript Tracking Client reference%s.', 'wp-piwik' ), '', '' ) ); - - echo $submitButton; - ?> - - - - - - - - - - - - - -
-
-showInputWrapper($id, $name, $description, $isHidden, $groupName, $hideDescription, function() use ($id, $onChange) { - ?> - getGlobalOption ( $id ) ? ' checked="checked"' : '')?> onchange="jQuery('#').val(this.checked?1:0); " /> - - - - - - - - -

- -

- - - - showInputWrapper($id, $name, $description, $isHidden, $groupName, $hideDescription, function() use ($id, $onChange, $rows, $isReadonly, $global) { - ?> - -

%s

', $text ); - } - - /** - * Show an input option - * - * @param string $id option id - * @param string $name descriptive option name - * @param string $description option description - * @param boolean $isHidden set to true to initially hide the option (default: false) - * @param string $groupName define a class name to access a group of option rows by javascript (default: empty) - * @param string $rowName define a class name to access the specific option row by javascript (default: empty) - * @param boolean $hideDescription $hideDescription set to false to show description initially (default: true) - * @param boolean $wide Create a wide box (default: false) - */ - private function showInput($id, $name, $description, $isHidden = false, $groupName = '', $rowName = false, $hideDescription = true, $wide = false) { - $this->showInputWrapper($id, $name, $description, $isHidden, $groupName, $hideDescription, function() use ($id) { - ?> - - descriptive name) - * @param string $description option description - * @param string $onChange javascript for onchange event (default: empty) - * @param boolean $isHidden set to true to initially hide the option (default: false) - * @param string $groupName define a class name to access a group of option rows by javascript (default: empty) - * @param boolean $hideDescription $hideDescription set to false to show description initially (default: true) - * @param boolean $global set to false if the textarea shows a site-specific option (default: true) - */ - private function showSelect($id, $name, $options = array(), $description = '', $onChange = '', $isHidden = false, $groupName = '', $hideDescription = true, $global = true) { - $default = $global ? self::$settings->getGlobalOption ( $id ) : self::$settings->getOption ( $id ); - - $this->showInputWrapper($id, $name, $description, $isHidden, $groupName, $hideDescription, function() use ($id, $onChange, $options, $default) { - ?> - -

%s

', $type, $icon, $content ); - } - - /** - * Show headline - * @param int $order headline order (h?-tag), set to 0 to avoid headline-tagging - * @param string $icon headline icon, see https://developer.wordpress.org/resource/dashicons/ - * @param string $headline headline text - * @param string $addPluginName set to true to add the plugin name to the headline (default: false) - */ - private function showHeadline($order, $icon, $headline, $addPluginName = false) { - echo $this->getHeadline ( $order, $icon, $headline, $addPluginName = false ); - } - - /** - * Get headline HTML - * - * @param int $order headline order (h?-tag), set to 0 to avoid headline-tagging - * @param string $icon headline icon, see https://developer.wordpress.org/resource/dashicons/ - * @param string $headline headline text - * @param string $addPluginName set to true to add the plugin name to the headline (default: false) - */ - private function getHeadline($order, $icon, $headline, $addPluginName = false) { - echo ($order > 0 ? "" : '') . sprintf ( ' %s%s', $icon, ($addPluginName ? self::$settings->getNotEmptyGlobalOption ( 'plugin_display_name' ) . ' ' : ''), __ ( $headline, 'wp-piwik' ) ) . ($order > 0 ? "" : ''); - } - - /** - * Show donation info - */ - private function showDonation() { - ?> -
-

- -

-

- -

-
- Paypal -
- - - - -
-
-
- Bitcoin
- Bitcoin Address
-
-
- -
-
-

-

Chart.js (MIT License).','wp-piwik'); ?>

-

, !

-

-

you for using my plugin. It is the best commendation if my piece of code is really used!','wp-piwik'); ?>

- -

- -
- -
- - - -

-

or cURL has to be available:', 'wp-piwik'); ?>

-
    -
  1. '.(function_exists('curl_init')?'':__('not','wp-piwik')).' '; - _e('available','wp-piwik'); - ?>.
  2. -
  3. '.(ini_get('allow_url_fopen')?'':__('not','wp-piwik')).' '; - _e('enabled','wp-piwik'); - ?>.
  4. -
  5. getGlobalOption('http_connection') == 'curl') || (function_exists('curl_init') && !ini_get('allow_url_fopen')))?__('cURL', 'wp-piwik'):__('fopen', 'wp-piwik')).' ('.(self::$settings->getGlobalOption('http_method')=='post'?__('POST','wp-piwik'):__('GET','wp-piwik')).') '.__('is used.', 'wp-piwik'); ?>
  6. - getGlobalOption('piwik_mode') == 'php') { ?>
  7. '.(self::$settings->getGlobalOption('proxy_url')).''; - ?>
  8. -
-

:

-
    -
  1. -
  2. -
  3. -
  4. -
-

readRSSFeed('http://wordpress.org/support/rss/plugin/wp-piwik'); - if (!empty($supportThreads)) { - echo '
    '; - foreach ($supportThreads as $supportThread) - echo '
  1. '.$supportThread['title'].'
  2. '; - echo '
'; - } - } - - /** - * Read RSS feed - * - * @param string $feed - * feed URL - * @param int $cnt - * item limit - * @return array feed items array[](title, url) - * - */ - private function readRSSFeed($feed, $cnt = 5) { - $result = array (); - if (function_exists ( 'simplexml_load_file' ) && ! empty ( $feed )) { - $xml = @simplexml_load_file ( $feed ); - if (! $xml || ! isset ( $xml->channel [0]->item )) - return array ( - array ( - 'title' => 'Can\'t read RSS feed.', - 'url' => $xml - ) - ); - foreach ( $xml->channel [0]->item as $item ) { - if ($cnt -- == 0) - break; - $result [] = array ( - 'title' => $item->title [0], - 'url' => $item->link [0] - ); - } - } - return $result; - } - - /** - * Clear cache and reset settings - * - * @param boolean $clearSettings set to true to reset settings (default: false) - */ - private function clear($clearSettings = false) { - if ($clearSettings) { - self::$settings->resetSettings(); - $this->showBox ( 'updated', 'yes', __ ( 'Settings cleared (except connection settings).' ) ); - } - global $wpdb; - if (self::$settings->checkNetworkActivation()) { - $aryBlogs = \WP_Piwik\Settings::getBlogList(); - if (is_array($aryBlogs)) - foreach ($aryBlogs as $aryBlog) { - switch_to_blog($aryBlog['blog_id']); - $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wp-piwik_%'"); - $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_wp-piwik_%'"); - restore_current_blog(); - } - } else { - $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wp-piwik_%'"); - $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_wp-piwik_%'"); - } - $this->showBox ( 'updated', 'yes', __ ( 'Cache cleared.' ) ); - } - - /** - * Execute test script and display results - */ - private function runTestscript() { ?> -
-

Testscript Result

- isConfigured()) { - if (isset($_GET['testscript_id']) && $_GET['testscript_id']) - switch_to_blog((int) $_GET['testscript_id']); - ?> - - Please configure WP-Matomo first.

'; - ?> -
- + * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery + * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching + */ +class Settings extends \WP_Piwik\Admin { + + /** + * Builds and displays the settings page + */ + public function show() { + if ( ! empty( $_GET['sitebrowser'] ) ) { + new \WP_Piwik\Admin\Sitebrowser( self::$wp_piwik ); + return; + } + if ( ! empty( $_GET['clear'] ) && check_admin_referer() ) { + $this->clear( 2 === $_GET['clear'] ); + self::$wp_piwik->reset_request(); + $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; + echo '
'; + return; + } elseif ( self::$wp_piwik->is_config_submitted() ) { + $this->show_box( 'updated', 'yes', esc_html__( 'Changes saved.', 'wp-piwik' ) ); + self::$wp_piwik->reset_request(); + if ( 'php' === self::$settings->get_global_option( 'piwik_mode' ) ) { + self::$wp_piwik->define_piwik_constants(); + } + if ( self::$settings->get_global_option( 'auto_site_config' ) && self::$wp_piwik->is_configured() ) { + $site_id = self::$wp_piwik->get_piwik_site_id( null, true ); + self::$wp_piwik->update_tracking_code( $site_id ); + self::$settings->set_option( 'site_id', $site_id ); + } else { + self::$wp_piwik->update_tracking_code(); + } + } + global $wp_roles; + ?> +
+ get_headline( 1, 'admin-generic', 'Settings', true ); + if ( ! empty( $_GET['testscript'] ) ) { + $this->run_testscript(); + } + ?> + is_configured() ) { + $piwik_version = self::$wp_piwik->request( 'global.getPiwikVersion' ); + if ( is_array( $piwik_version ) && isset( $piwik_version['value'] ) ) { + $piwik_version = $piwik_version['value']; + } + } + $page = sanitize_key( wp_unslash( $_GET['page'] ) ); + ?> +
+ + + + + '; + printf( '', esc_html__( 'Thanks for using WP-Matomo!', 'wp-piwik' ) ); + if ( self::$wp_piwik->is_configured() ) { + if ( ! empty( $piwik_version ) && ! is_array( $piwik_version ) ) { + $this->show_text( sprintf( __( 'WP-Matomo %1$s is successfully connected to Matomo %2$s.', 'wp-piwik' ), self::$wp_piwik->get_plugin_version(), $piwik_version ) . ' ' . ( ! self::$wp_piwik->is_network_mode() ? sprintf( __( 'You are running WordPress %s.', 'wp-piwik' ), get_bloginfo( 'version' ) ) : sprintf( __( 'You are running a WordPress %s blog network (WPMU). WP-Matomo will handle your sites as different websites.', 'wp-piwik' ), get_bloginfo( 'version' ) ) ) ); + } else { + $error_message = \WP_Piwik\Request::get_last_error(); + if ( empty( $error_message ) ) { + $this->show_box( 'error', 'no', esc_html( sprintf( __( 'WP-Matomo %s was not able to connect to Matomo using your configuration. Check the »Connect to Matomo« section below.', 'wp-piwik' ), self::$wp_piwik->get_plugin_version() ) ) ); + } else { + $this->show_box( 'error', 'no', esc_html( sprintf( __( 'WP-Matomo %1$s was not able to connect to Matomo using your configuration. During connection the following error occured: ', 'wp-piwik' ), self::$wp_piwik->get_plugin_version() ) ) . '
' . esc_html( $error_message ) . '' ); + } + } + } else { + $this->show_box( 'error', 'no', esc_html( sprintf( __( 'WP-Matomo %s has to be connected to Matomo first. Check the »Connect to Matomo« section below.', 'wp-piwik' ), self::$wp_piwik->get_plugin_version() ) ) ); + } + + $tabs ['connect'] = array( + 'icon' => 'admin-plugins', + 'name' => __( 'Connect to Matomo', 'wp-piwik' ), + ); + if ( self::$wp_piwik->is_configured() ) { + $tabs ['statistics'] = array( + 'icon' => 'chart-pie', + 'name' => __( 'Show Statistics', 'wp-piwik' ), + ); + $tabs ['tracking'] = array( + 'icon' => 'location-alt', + 'name' => __( 'Enable Tracking', 'wp-piwik' ), + ); + } + $tabs ['expert'] = array( + 'icon' => 'shield', + 'name' => __( 'Expert Settings', 'wp-piwik' ), + ); + $tabs ['support'] = array( + 'icon' => 'lightbulb', + 'name' => __( 'Support', 'wp-piwik' ), + ); + $tabs ['credits'] = array( + 'icon' => 'groups', + 'name' => __( 'Credits', 'wp-piwik' ), + ); + + echo '

%s
'; + + if ( ! self::$wp_piwik->is_configured() ) { + $this->show_box( 'updated', 'info', esc_html__( 'Before you can complete the setup, make sure you have a Matomo instance running. If you don\'t have one, you can', 'wp-piwik' ) . ' ' . esc_html__( 'create a free account', 'wp-piwik' ) . ' ' . esc_html__( 'or ', 'wp-piwik' ) . '' . esc_html__( 'install the "Matomo for WordPress" plugin', 'wp-piwik' ) . ' ' . esc_html__( 'instead.', 'wp-piwik' ) ); + } + + if ( ! function_exists( 'curl_init' ) && ! ini_get( 'allow_url_fopen' ) ) { + $this->show_box( 'error', 'no', esc_html__( 'Neither cURL nor fopen are available. So WP-Matomo can not use the HTTP API and not connect to InnoCraft Cloud.', 'wp-piwik' ) . ' ' . sprintf( '%s.', 'https://wordpress.org/plugins/wp-piwik/faq/', esc_html__( 'More information', 'wp-piwik' ) ) ); + } + + $description = sprintf( '%s
%s: %s
%s: %s
%s: %s', esc_html__( 'You can choose between three connection methods:', 'wp-piwik' ), esc_html__( 'Self-hosted (HTTP API, default)', 'wp-piwik' ), esc_html__( 'This is the default option for a self-hosted Matomo and should work for most configurations. WP-Matomo will connect to Matomo using http(s).', 'wp-piwik' ), esc_html__( 'Self-hosted (PHP API)', 'wp-piwik' ), esc_html__( 'Choose this, if your self-hosted Matomo and WordPress are running on the same machine and you know the full server path to your Matomo instance.', 'wp-piwik' ), esc_html__( 'Cloud-hosted', 'wp-piwik' ), esc_html__( 'If you are using a cloud-hosted Matomo by InnoCraft, you can simply use this option. Be carefull to choose the option which fits to your cloud domain (matomo.cloud or innocraft.cloud).', 'wp-piwik' ) ); + $this->show_select( + 'piwik_mode', + __( 'Matomo Mode', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled (WP-Matomo will not connect to Matomo)', 'wp-piwik' ), + 'http' => __( 'Self-hosted (HTTP API, default)', 'wp-piwik' ), + 'php' => __( 'Self-hosted (PHP API)', 'wp-piwik' ), + 'cloud-matomo' => __( 'Cloud-hosted (Innocraft Cloud, *.matomo.cloud)', 'wp-piwik' ), + 'cloud' => __( 'Cloud-hosted (InnoCraft Cloud, *.innocraft.cloud)', 'wp-piwik' ), + ), + $description, + 'jQuery(\'tr.wp-piwik-mode-option\').addClass(\'hidden\'); jQuery(\'.wp-piwik-mode-option-\' + jQuery(\'#piwik_mode\').val()).removeClass(\'hidden\');', + false, + '', + self::$wp_piwik->is_configured() + ); + + $this->show_input( 'piwik_url', __( 'Matomo URL', 'wp-piwik' ), __( 'Enter your Matomo URL. This is the same URL you use to access your Matomo instance, e.g. http://www.example.com/matomo/.', 'wp-piwik' ), 'http' !== self::$settings->get_global_option( 'piwik_mode' ), 'wp-piwik-mode-option', 'http', self::$wp_piwik->is_configured(), true ); + $this->show_input( 'piwik_path', __( 'Matomo path', 'wp-piwik' ), __( 'Enter the file path to your Matomo instance, e.g. /var/www/matomo/.', 'wp-piwik' ), 'php' !== self::$settings->get_global_option( 'piwik_mode' ), 'wp-piwik-mode-option', 'php', self::$wp_piwik->is_configured(), true ); + $this->show_input( 'piwik_user', __( 'Innocraft subdomain', 'wp-piwik' ), __( 'Enter your InnoCraft Cloud subdomain. It is also part of your URL: https://SUBDOMAIN.innocraft.cloud.', 'wp-piwik' ), 'cloud' !== self::$settings->get_global_option( 'piwik_mode' ), 'wp-piwik-mode-option', 'cloud', self::$wp_piwik->is_configured() ); + $this->show_input( 'matomo_user', __( 'Matomo subdomain', 'wp-piwik' ), __( 'Enter your Matomo Cloud subdomain. It is also part of your URL: https://SUBDOMAIN.matomo.cloud.', 'wp-piwik' ), 'cloud-matomo' !== self::$settings->get_global_option( 'piwik_mode' ), 'wp-piwik-mode-option', 'cloud-matomo', self::$wp_piwik->is_configured() ); + $this->show_input( 'piwik_token', __( 'Auth token', 'wp-piwik' ), __( 'Enter your Matomo auth token here. It is an alphanumerical code like 0a1b2c34d56e78901fa2bc3d45678efa.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sWP-Matomo FAQ%2$s.', 'wp-piwik' ), '', '' ), false, '', '', self::$wp_piwik->is_configured(), true, 'password' ); + + // Site configuration + $piwik_site_id = self::$wp_piwik->is_configured() ? self::$wp_piwik->get_piwik_site_id() : false; + if ( ! self::$wp_piwik->is_network_mode() ) { + $this->show_checkbox( + 'auto_site_config', + __( 'Auto config', 'wp-piwik' ), + __( 'Check this to automatically choose your blog from your Matomo sites by URL. If your blog is not added to Matomo yet, WP-Matomo will add a new site.', 'wp-piwik' ), + false, + '', + false, + 'jQuery(\'tr.wp-piwik-auto-option\').toggle(\'hidden\');' . ( $piwik_site_id ? 'jQuery(\'#site_id\').val(' . $piwik_site_id . ');' : '' ) + ); + if ( self::$wp_piwik->is_configured() ) { + $piwik_site_list = self::$wp_piwik->get_piwik_site_details(); + if ( isset( $piwik_site_list['result'] ) && 'error' === $piwik_site_list['result'] ) { + $this->show_box( + 'error', + 'no', + esc_html( sprintf( __( 'WP-Matomo %1$s was not able to get sites with at least view access: ', 'wp-piwik' ), self::$wp_piwik->get_plugin_version() ) ) . '
' . esc_html( $piwik_site_list['message'] ) . '' + ); + } else { + if ( is_array( $piwik_site_list ) ) { + foreach ( $piwik_site_list as $details ) { + $piwik_site_details[ $details['idsite'] ] = $details; + } + } + unset( $piwik_site_list ); + if ( 'n/a' !== $piwik_site_id && isset( $piwik_site_details ) ) { + $piwik_site_description = $piwik_site_details [ $piwik_site_id ] ['name'] . ' (' . $piwik_site_details [ $piwik_site_id ] ['main_url'] . ')'; + } else { + $piwik_site_description = 'n/a'; + } + echo ''; + if ( isset( $piwik_site_details ) ) { + foreach ( $piwik_site_details as $key => $site_data ) { + $site_list[ $site_data['idsite'] ] = $site_data['name'] . ' (' . $site_data ['main_url'] . ')'; + } + } + if ( isset( $site_list ) ) { + $this->show_select( + 'site_id', + __( 'Select site', 'wp-piwik' ), + $site_list, + __( 'Choose the Matomo site corresponding to this blog.', 'wp-piwik' ), + '', + (int) self::$settings->get_global_option( 'auto_site_config' ) === 1, + 'wp-piwik-auto-option', + true, + false + ); + } + } + } + } else { + echo ''; + } + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $submit_button; + + echo '
' . esc_html__( 'Determined site', 'wp-piwik' ) . ':' . esc_html( $piwik_site_description ) . '
'; + // Stats configuration + $this->show_select( + 'default_date', + __( 'Matomo default date', 'wp-piwik' ), + array( + 'today' => __( 'Today', 'wp-piwik' ), + 'yesterday' => __( 'Yesterday', 'wp-piwik' ), + 'current_month' => __( 'Current month', 'wp-piwik' ), + 'last_month' => __( 'Last month', 'wp-piwik' ), + 'current_week' => __( 'Current week', 'wp-piwik' ), + 'last_week' => __( 'Last week', 'wp-piwik' ), + ), + __( 'Default date shown on statistics page.', 'wp-piwik' ) + ); + + $this->show_checkbox( 'stats_seo', __( 'Show SEO data', 'wp-piwik' ), __( 'Display SEO ranking data on statistics page.', 'wp-piwik' ) . ' (' . __( 'Slow!', 'wp-piwik' ) . ')' ); + $this->show_checkbox( 'stats_ecommerce', __( 'Show e-commerce data', 'wp-piwik' ), __( 'Display e-commerce data on statistics page.', 'wp-piwik' ) ); + + $this->show_select( + 'dashboard_widget', + __( 'Dashboard overview', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'yesterday' => __( 'Yesterday', 'wp-piwik' ), + 'today' => __( 'Today', 'wp-piwik' ), + 'last30' => __( 'Last 30 days', 'wp-piwik' ), + 'last60' => __( 'Last 60 days', 'wp-piwik' ), + 'last90' => __( 'Last 90 days', 'wp-piwik' ), + ), + __( 'Enable WP-Matomo dashboard widget "Overview".', 'wp-piwik' ) + ); + + $this->show_checkbox( 'dashboard_chart', __( 'Dashboard graph', 'wp-piwik' ), __( 'Enable WP-Matomo dashboard widget "Graph".', 'wp-piwik' ) ); + + $this->show_checkbox( 'dashboard_seo', __( 'Dashboard SEO', 'wp-piwik' ), __( 'Enable WP-Matomo dashboard widget "SEO".', 'wp-piwik' ) . ' (' . __( 'Slow!', 'wp-piwik' ) . ')' ); + + $this->show_checkbox( 'dashboard_ecommerce', __( 'Dashboard e-commerce', 'wp-piwik' ), __( 'Enable WP-Matomo dashboard widget "E-commerce".', 'wp-piwik' ) ); + + $this->show_checkbox( 'toolbar', __( 'Show graph on WordPress Toolbar', 'wp-piwik' ), __( 'Display a last 30 days visitor graph on WordPress\' toolbar.', 'wp-piwik' ) ); + + echo ''; + + $this->show_select( + 'perpost_stats', + __( 'Show per post stats', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'yesterday' => __( 'Yesterday', 'wp-piwik' ), + 'today' => __( 'Today', 'wp-piwik' ), + 'last30' => __( 'Last 30 days', 'wp-piwik' ), + 'last60' => __( 'Last 60 days', 'wp-piwik' ), + 'last90' => __( 'Last 90 days', 'wp-piwik' ), + ), + __( 'Show stats about single posts at the post edit admin page.', 'wp-piwik' ) + ); + + $this->show_checkbox( 'piwik_shortcut', __( 'Matomo shortcut', 'wp-piwik' ), __( 'Display a shortcut to Matomo itself.', 'wp-piwik' ) ); + + $this->show_input( 'plugin_display_name', __( 'WP-Matomo display name', 'wp-piwik' ), __( 'Plugin name shown in WordPress.', 'wp-piwik' ) ); + + $this->show_checkbox( 'shortcodes', __( 'Enable shortcodes', 'wp-piwik' ), __( 'Enable shortcodes in post or page content.', 'wp-piwik' ) ); + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $submit_button; + + echo ''; + + // Tracking Configuration + $is_not_tracking = 'disabled' === self::$settings->get_global_option( 'track_mode' ); + $is_not_generated_tracking = $is_not_tracking || 'manually' === self::$settings->get_global_option( 'track_mode' ); + $full_generated_tracking_group = 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy'; + + $description = sprintf( + '%s
%s: %s
%s: %s
%s: %s
%s: %s
%s: %s', + esc_html__( 'You can choose between four tracking code modes:', 'wp-piwik' ), + esc_html__( 'Disabled', 'wp-piwik' ), + esc_html__( 'WP-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.', 'wp-piwik' ), + esc_html__( 'Default tracking', 'wp-piwik' ), + esc_html__( 'WP-Matomo will use Matomo\'s standard tracking code.', 'wp-piwik' ), + esc_html__( 'Use js/index.php', 'wp-piwik' ), + esc_html__( 'You can choose this tracking code, to deliver a minified proxy code and to avoid using the files called piwik.js or piwik.php.', 'wp-piwik' ) + . ' ' + . sprintf( esc_html__( 'See %1$sreadme file%2$s.', 'wp-piwik' ), '', '' ), + esc_html__( 'Use proxy script', 'wp-piwik' ), + esc_html__( 'Use this tracking code to not reveal the Matomo server URL.', 'wp-piwik' ) + . ' ' + . sprintf( esc_html__( 'See %1$sMatomo FAQ%2$s.', 'wp-piwik' ), '', '' ), + esc_html__( 'Enter manually', 'wp-piwik' ), + 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.', 'wp-piwik' ) + . ( self::$wp_piwik->is_network_mode() ? ' ' . esc_html__( 'Use the placeholder {ID} to add the Matomo site ID.', 'wp-piwik' ) : '' ) + ); + $this->show_select( + 'track_mode', + __( 'Add tracking code', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'default' => __( 'Default tracking', 'wp-piwik' ), + 'js' => __( 'Use js/index.php', 'wp-piwik' ), + 'proxy' => __( 'Use proxy script', 'wp-piwik' ), + 'manually' => __( 'Enter manually', 'wp-piwik' ), + ), + $description, + 'jQuery(\'tr.wp-piwik-track-option\').addClass(\'hidden\'); jQuery(\'tr.wp-piwik-track-option-\' + jQuery(\'#track_mode\').val()).removeClass(\'hidden\'); jQuery(\'#tracking_code, #noscript_code\').prop(\'readonly\', jQuery(\'#track_mode\').val() != \'manually\');' + ); + + $this->show_textarea( + 'tracking_code', + __( 'Tracking code', 'wp-piwik' ), + 15, + esc_html__( 'This is a preview of your current tracking code. If you choose to enter your tracking code manually, you can change it here.', 'wp-piwik' ), + $is_not_tracking, + 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy wp-piwik-track-option-manually', + true, + '', + 'manually' !== self::$settings->get_global_option( 'track_mode' ), + false + ); + + $this->show_select( + 'track_codeposition', + __( 'JavaScript code position', 'wp-piwik' ), + array( + 'footer' => __( 'Footer', 'wp-piwik' ), + 'header' => __( 'Header', 'wp-piwik' ), + ), + __( 'Choose whether the JavaScript code is added to the footer or the header.', 'wp-piwik' ), + '', + $is_not_tracking, + 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-proxy wp-piwik-track-option-manually' + ); + + $this->show_textarea( + 'noscript_code', + __( 'Noscript code', 'wp-piwik' ), + 2, + esc_html__( 'This is a preview of your <noscript> code which is part of your tracking code.', 'wp-piwik' ), + 'proxy' === self::$settings->get_global_option( 'track_mode' ), + 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually', + true, + '', + 'manually' !== self::$settings->get_global_option( 'track_mode' ), + false + ); + + $this->show_checkbox( 'track_noscript', __( 'Add <noscript>', 'wp-piwik' ), __( 'Adds the <noscript> code to your footer.', 'wp-piwik' ) . ' ' . __( 'Disabled in proxy mode.', 'wp-piwik' ), 'proxy' === self::$settings->get_global_option( 'track_mode' ), 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually' ); + + $this->show_checkbox( 'track_nojavascript', __( 'Add rec parameter to noscript code', 'wp-piwik' ), __( 'Enable tracking for visitors without JavaScript (not recommended).', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo FAQ%2$s.', 'wp-piwik' ), '', '' ) . ' ' . __( 'Disabled in proxy mode.', 'wp-piwik' ), 'proxy' === self::$settings->get_global_option( 'track_mode' ), 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually' ); + + $this->show_select( + 'track_content', + __( 'Enable content tracking', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'all' => __( 'Track all content blocks', 'wp-piwik' ), + 'visible' => __( 'Track only visible content blocks', 'wp-piwik' ), + ), + esc_html__( 'Content tracking allows you to track interaction with the content of a web page or application.', 'wp-piwik' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), + '', + $is_not_tracking, + $full_generated_tracking_group . ' wp-piwik-track-option-manually' + ); + + $this->show_checkbox( 'track_search', __( 'Track search', 'wp-piwik' ), __( 'Use Matomo\'s advanced Site Search Analytics feature.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), $is_not_tracking, $full_generated_tracking_group . ' wp-piwik-track-option-manually' ); + + $this->show_checkbox( 'track_404', __( 'Track 404', 'wp-piwik' ), __( 'WP-Matomo can automatically add a 404-category to track 404-page-visits.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo FAQ%2$s.', 'wp-piwik' ), '', '' ), $is_not_tracking, $full_generated_tracking_group . ' wp-piwik-track-option-manually' ); + + echo ''; + echo ''; + + $this->show_checkbox( 'add_customvars_box', __( 'Show custom variables box', 'wp-piwik' ), __( ' Show a "custom variables" edit box on post edit page.', 'wp-piwik' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), $is_not_generated_tracking, $full_generated_tracking_group . ' wp-piwik-track-option-manually' ); + + $this->show_input( 'add_download_extensions', __( 'Add new file types for download tracking', 'wp-piwik' ), __( 'Add file extensions for download tracking, divided by a vertical bar (|).', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), $is_not_generated_tracking, $full_generated_tracking_group ); + + $this->show_select( + 'require_consent', + __( 'Tracking or cookie consent', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'consent' => __( 'Require consent', 'wp-piwik' ), + 'cookieconsent' => __( 'Require cookie consent', 'wp-piwik' ), + ), + esc_html__( 'Enable support for consent managers.', 'wp-piwik' ) . ' ' . sprintf( esc_html__( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), + '', + $is_not_generated_tracking, + $full_generated_tracking_group + ); + + $this->show_checkbox( 'disable_cookies', __( 'Disable cookies', 'wp-piwik' ), __( 'Disable all tracking cookies for a visitor.', 'wp-piwik' ), $is_not_generated_tracking, $full_generated_tracking_group ); + + $this->show_checkbox( 'limit_cookies', __( 'Limit cookie lifetime', 'wp-piwik' ), __( 'You can limit the cookie lifetime to avoid tracking your users over a longer period as necessary.', 'wp-piwik' ), $is_not_generated_tracking, $full_generated_tracking_group, true, 'jQuery(\'tr.wp-piwik-cookielifetime-option\').toggleClass(\'wp-piwik-hidden\');' ); + + $this->show_input( 'limit_cookies_visitor', __( 'Visitor timeout (seconds)', 'wp-piwik' ), false, $is_not_generated_tracking || ! self::$settings->get_global_option( 'limit_cookies' ), $full_generated_tracking_group . ' wp-piwik-cookielifetime-option' . ( self::$settings->get_global_option( 'limit_cookies' ) ? '' : ' wp-piwik-hidden' ) ); + + $this->show_input( 'limit_cookies_session', __( 'Session timeout (seconds)', 'wp-piwik' ), false, $is_not_generated_tracking || ! self::$settings->get_global_option( 'limit_cookies' ), $full_generated_tracking_group . ' wp-piwik-cookielifetime-option' . ( self::$settings->get_global_option( 'limit_cookies' ) ? '' : ' wp-piwik-hidden' ) ); + + $this->show_input( 'limit_cookies_referral', __( 'Referral timeout (seconds)', 'wp-piwik' ), false, $is_not_generated_tracking || ! self::$settings->get_global_option( 'limit_cookies' ), $full_generated_tracking_group . ' wp-piwik-cookielifetime-option' . ( self::$settings->get_global_option( 'limit_cookies' ) ? '' : ' wp-piwik-hidden' ) ); + + $this->show_checkbox( 'track_admin', __( 'Track admin pages', 'wp-piwik' ), __( 'Enable to track users on admin pages (remember to configure the tracking filter appropriately).', 'wp-piwik' ), $is_not_tracking, $full_generated_tracking_group . ' wp-piwik-track-option-manually' ); + + echo ''; + echo ''; + + $this->show_checkbox( 'track_across', __( 'Track subdomains in the same website', 'wp-piwik' ), __( 'Adds *.-prefix to cookie domain.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), $is_not_generated_tracking, $full_generated_tracking_group ); + + $this->show_checkbox( 'track_across_alias', __( 'Do not count subdomains as outlink', 'wp-piwik' ), __( 'Adds *.-prefix to tracked domain.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ), $is_not_generated_tracking, $full_generated_tracking_group ); + + $this->show_checkbox( 'track_crossdomain_linking', __( 'Enable cross domain linking', 'wp-piwik' ), __( 'When enabled, it will make sure to use the same visitor ID for the same visitor across several domains. This works only when this feature is enabled because the visitor ID is stored in a cookie and cannot be read on the other domain by default. When this feature is enabled, it will append a URL parameter "pk_vid" that contains the visitor ID when a user clicks on a URL that belongs to one of your domains. For this feature to work, you also have to configure which domains should be treated as local in your Matomo website settings. This feature requires Matomo 3.0.2.', 'wp-piwik' ), 'proxy' === self::$settings->get_global_option( 'track_mode' ), 'wp-piwik-track-option wp-piwik-track-option-default wp-piwik-track-option-js wp-piwik-track-option-manually' ); + + $this->show_checkbox( 'track_feed', __( 'Track RSS feeds', 'wp-piwik' ), __( 'Enable to track posts in feeds via tracking pixel.', 'wp-piwik' ), $is_not_tracking, $full_generated_tracking_group . ' wp-piwik-track-option-manually' ); + + $this->show_checkbox( + 'track_feed_addcampaign', + __( 'Track RSS feed links as campaign', 'wp-piwik' ), + esc_html__( 'This will add Matomo campaign parameters to the RSS feed links.', 'wp-piwik' ) + . ' ' + . sprintf( + __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), + '', + '' + ), + $is_not_tracking, + $full_generated_tracking_group . ' wp-piwik-track-option-manually', + true, + 'jQuery(\'tr.wp-piwik-feed_campaign-option\').toggle(\'hidden\');' + ); + + $this->show_input( 'track_feed_campaign', __( 'RSS feed campaign', 'wp-piwik' ), __( 'Keyword: post name.', 'wp-piwik' ), $is_not_generated_tracking || ! self::$settings->get_global_option( 'track_feed_addcampaign' ), $full_generated_tracking_group . ' wp-piwik-feed_campaign-option' ); + + $this->show_input( 'track_heartbeat', __( 'Enable heartbeat timer', 'wp-piwik' ), __( 'Enable a heartbeat timer to get more accurate visit lengths by sending periodical HTTP ping requests as long as the site is opened. Enter the time between the pings in seconds (Matomo default: 15) to enable or 0 to disable this feature. Note: This will cause a lot of additional HTTP requests on your site.', 'wp-piwik' ), $is_not_generated_tracking, $full_generated_tracking_group ); + + $this->show_select( + 'track_user_id', + __( 'User ID Tracking', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled', 'wp-piwik' ), + 'uid' => __( 'WP User ID', 'wp-piwik' ), + 'email' => __( 'Email Address', 'wp-piwik' ), + 'username' => __( 'Username', 'wp-piwik' ), + 'displayname' => __( 'Display Name (Not Recommended!)', 'wp-piwik' ), + ), + __( '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.', 'wp-piwik' ), + '', + $is_not_tracking, + $full_generated_tracking_group + ); + + ?> + + + + show_checkbox( + \WP_Piwik\Settings::TRACK_AI_BOTS, + esc_html__( 'Track AI Bots', 'wp-piwik' ), + esc_html__( 'If enabled, AI bots will trigger page views even if they do not execute JavaScript. These page views can be seen in the special AI Assistants report.', 'wp-piwik' ), + $is_not_tracking, + $full_generated_tracking_group . ' wp-piwik-track-option-manually', + true, + "window.jQuery('.wp-matomo-track-ai-warning').toggle();" + ); + + $matomo_is_track_ai_enabled = self::$settings->is_ai_bot_tracking_enabled(); + $matomo_is_advanced_cache_used = self::is_advanced_cache_used(); + $matomo_is_track_script_used_in_wp_config = self::is_track_script_used_in_wp_config(); + $matomo_is_htaccess_serving_cache_files = self::is_htaccess_serving_cache_files(); + + if ( $matomo_is_htaccess_serving_cache_files ) { + ?> + + + + + + + + show_checkbox( + \WP_Piwik\Settings::TRACK_AI_BOTS_USING_ESI, + esc_html__( 'Track AI Bots using Edge Side Includes', 'wp-piwik' ), + esc_html__( 'If you are using a CDN to serve your blog, you will not be able to track AI bots in the traditional method. If your CDN supports ESI (Edge Side Includes), however, you can enable this option to use this feature for tracking AI bots.', 'wp-piwik' ), + $is_not_tracking, + $full_generated_tracking_group . ' wp-piwik-track-option-manually' + ); + + $matomo_is_track_via_esi_enabled = self::$settings->is_track_via_esi_enabled(); + $matomo_is_using_litespeed = $this->is_using_litespeed_web_server(); + $matomo_is_using_litespeed_cache = $this->is_using_litespeed_cache_plugin(); + $matomo_is_esi_enabled_in_litespeed = $this->is_litespeed_esi_enabled_in_webserver(); + + if ( $matomo_is_using_litespeed && $matomo_is_using_litespeed_cache ) { + if ( ! $matomo_is_track_via_esi_enabled ) { + ?> + + + + + + + + '; + + $this->show_text( __( 'Usually, you do not need to change these settings. If you want to do so, you should know what you do or you got an expert\'s advice.', 'wp-piwik' ) ); + + $this->show_checkbox( 'cache', __( 'Enable cache', 'wp-piwik' ), __( 'Cache API calls, which not contain today\'s values, for a week.', 'wp-piwik' ) ); + + if ( function_exists( 'curl_init' ) && ini_get( 'allow_url_fopen' ) ) { + $this->show_select( + 'http_connection', + __( 'HTTP connection via', 'wp-piwik' ), + array( + 'curl' => __( 'cURL', 'wp-piwik' ), + 'fopen' => __( 'fopen', 'wp-piwik' ), + ), + __( 'Choose whether WP-Matomo should use cURL or fopen to connect to Matomo in HTTP or Cloud mode.', 'wp-piwik' ) + ); + } + + $this->show_select( + 'http_method', + __( 'HTTP method', 'wp-piwik' ), + array( + 'post' => __( 'POST', 'wp-piwik' ), + 'get' => __( 'GET', 'wp-piwik' ), + ), + __( 'Choose whether WP-Matomo should use POST or GET in HTTP or Cloud mode.', 'wp-piwik' ) + ); + + $this->show_checkbox( 'disable_timelimit', __( 'Disable time limit', 'wp-piwik' ), __( 'Use set_time_limit(0) if stats page causes a time out.', 'wp-piwik' ) ); + + $this->show_input( 'filter_limit', __( 'Filter limit', 'wp-piwik' ), __( 'Use filter_limit if you need to get more than 100 results per page.', 'wp-piwik' ) ); + + $this->show_input( 'connection_timeout', __( 'Connection timeout', 'wp-piwik' ), 'Define a connection timeout for all HTTP requests done by WP-Matomo in seconds.' ); + + $this->show_checkbox( 'disable_ssl_verify', __( 'Disable SSL peer verification', 'wp-piwik' ), '(' . __( 'not recommended', 'wp-piwik' ) . ')' ); + $this->show_checkbox( 'disable_ssl_verify_host', __( 'Disable SSL host verification', 'wp-piwik' ), '(' . __( 'not recommended', 'wp-piwik' ) . ')' ); + + $this->show_select( + 'piwik_useragent', + __( 'User agent', 'wp-piwik' ), + array( + 'php' => __( 'Use the PHP default user agent', 'wp-piwik' ) . ( ini_get( 'user_agent' ) ? '(' . ini_get( 'user_agent' ) . ')' : ' (' . __( 'empty', 'wp-piwik' ) . ')' ), + 'own' => __( 'Define a specific user agent', 'wp-piwik' ), + ), + 'WP-Matomo can send the default user agent defined by your PHP settings or use a specific user agent below. The user agent is send by WP-Matomo if HTTP requests are performed.', + 'jQuery(\'tr.wp-piwik-useragent-option\').toggleClass(\'hidden\');' + ); + $this->show_input( 'piwik_useragent_string', __( 'Specific user agent', 'wp-piwik' ), 'Define a user agent description which is send by WP-Matomo if HTTP requests are performed.', 'own' !== self::$settings->get_global_option( 'piwik_useragent' ), 'wp-piwik-useragent-option' ); + + $this->show_checkbox( + 'dnsprefetch', + __( 'Enable DNS prefetch', 'wp-piwik' ), + esc_html__( 'Add a DNS prefetch tag.', 'wp-piwik' ) + . ' ' + . sprintf( + esc_html__( 'See %1$sMatomo Blog%2$s.', 'wp-piwik' ), + '', + '' + ) + ); + + $this->show_checkbox( + 'track_datacfasync', + __( 'Add data-cfasync=false', 'wp-piwik' ), + esc_html__( 'Adds data-cfasync=false to the script tag, e.g., to ask Rocket Loader to ignore the script.', 'wp-piwik' ) + . ' ' + . sprintf( + esc_html__( 'See %1$sCloudFlare Knowledge Base%2$s.', 'wp-piwik' ), + '', + '' + ) + ); + + $this->show_input( 'track_cdnurl', __( 'CDN URL', 'wp-piwik' ) . ' http://', 'Enter URL if you want to load the tracking code via CDN.' ); + + $this->show_input( 'track_cdnurlssl', __( 'CDN URL (SSL)', 'wp-piwik' ) . ' https://', 'Enter URL if you want to load the tracking code via a separate SSL CDN.' ); + + $this->show_select( + 'force_protocol', + __( 'Force Matomo to use a specific protocol', 'wp-piwik' ), + array( + 'disabled' => __( 'Disabled (default)', 'wp-piwik' ), + 'http' => __( 'http', 'wp-piwik' ), + 'https' => __( 'https (SSL)', 'wp-piwik' ), + ), + __( 'Choose if you want to explicitly force Matomo to use HTTP or HTTPS. Does not work with a CDN URL.', 'wp-piwik' ) + ); + + $this->show_checkbox( 'remove_type_attribute', __( 'Remove type attribute', 'wp-piwik' ), __( 'Removes the type attribute from Matomo\'s tracking code script tag.', 'wp-piwik' ) ); + + $this->show_select( + 'update_notice', + __( 'Update notice', 'wp-piwik' ), + array( + 'enabled' => __( 'Show always if WP-Matomo is updated', 'wp-piwik' ), + 'script' => __( 'Show only if WP-Matomo is updated and settings were changed', 'wp-piwik' ), + 'disabled' => __( 'Disabled', 'wp-piwik' ), + ), + esc_html__( 'Choose if you want to get an update notice if WP-Matomo is updated.', 'wp-piwik' ) + ); + + $this->show_input( 'set_download_extensions', __( 'Define all file types for download tracking', 'wp-piwik' ), __( 'Replace Matomo\'s default file extensions for download tracking, divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo documentation%2$s.', 'wp-piwik' ), '', '' ) ); + + $this->show_input( 'set_download_classes', __( 'Set classes to be treated as downloads', 'wp-piwik' ), __( 'Set classes to be treated as downloads (in addition to piwik_download), divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo JavaScript Tracking Client reference%2$s.', 'wp-piwik' ), '', '' ) ); + + $this->show_input( 'set_link_classes', __( 'Set classes to be treated as outlinks', 'wp-piwik' ), __( 'Set classes to be treated as outlinks (in addition to piwik_link), divided by a vertical bar (|). Leave blank to keep Matomo\'s default settings.', 'wp-piwik' ) . ' ' . sprintf( __( 'See %1$sMatomo JavaScript Tracking Client reference%2$s.', 'wp-piwik' ), '', '' ) ); + + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $submit_button; + ?> + + + + + + + + + + + + + +
+
+ show_input_wrapper( + $id, + $name, + $description, + $is_hidden, + $group_name, + $hide_description, + function () use ( $id, $on_change ) { + ?> + get_global_option( $id ) ? ' checked="checked"' : '' ); ?> onchange="jQuery().val(this.checked?1:0); + + " /> + + + + + + + + +

+ +

+ + + + show_input_wrapper( + $id, + $name, + $description, + $is_hidden, + $group_name, + $hide_description, + function () use ( $id, $on_change, $rows, $is_readonly, $is_global ) { + ?> + +

%s

', esc_html( $text ) ); + } + + /** + * Show an input option + * + * @param string $id option id + * @param string $name descriptive option name + * @param string|false $description option description + * @param boolean $is_hidden set to true to initially hide the option (default: false) + * @param string $group_name define a class name to access a group of option rows by javascript (default: empty) + * @param string|false $row_name define a class name to access the specific option row by javascript (default: empty) + * @param boolean $hide_description $hideDescription set to false to show description initially (default: true) + * @param boolean $wide Create a wide box (default: false) + */ + private function show_input( $id, $name, $description, $is_hidden = false, $group_name = '', $row_name = false, $hide_description = true, $wide = false, $type = false ) { + $this->show_input_wrapper( + $id, + $name, + $description, + $is_hidden, + $group_name, + $hide_description, + function () use ( $id, $type ) { + ?> + + descriptive name) + * @param string $description option description + * @param string $on_change javascript for onchange event (default: empty) + * @param boolean $is_hidden set to true to initially hide the option (default: false) + * @param string $group_name define a class name to access a group of option rows by javascript (default: empty) + * @param boolean $hide_description $hideDescription set to false to show description initially (default: true) + * @param boolean $is_global set to false if the textarea shows a site-specific option (default: true) + */ + private function show_select( $id, $name, $options = array(), $description = '', $on_change = '', $is_hidden = false, $group_name = '', $hide_description = true, $is_global = true ) { + $default = $is_global ? self::$settings->get_global_option( $id ) : self::$settings->get_option( $id ); + + $this->show_input_wrapper( + $id, + $name, + $description, + $is_hidden, + $group_name, + $hide_description, + function () use ( $id, $on_change, $options, $default ) { + ?> + +

%s

', esc_attr( $type ), esc_attr( $icon ), $content ); + } + + /** + * Show headline + * + * @param int $order headline order (h?-tag), set to 0 to avoid headline-tagging + * @param string $icon headline icon, see https://developer.wordpress.org/resource/dashicons/ + * @param string $headline headline text + * @param string|bool $add_plugin_name set to true to add the plugin name to the headline (default: false) + */ + private function show_headline( $order, $icon, $headline, $add_plugin_name = false ) { + $this->get_headline( $order, $icon, $headline, $add_plugin_name ); + } + + /** + * Get headline HTML + * + * @param int $order headline order (h?-tag), set to 0 to avoid headline-tagging + * @param string $icon headline icon, see https://developer.wordpress.org/resource/dashicons/ + * @param string $headline headline text + * @param bool $add_plugin_name set to true to add the plugin name to the headline (default: false) + */ + private function get_headline( $order, $icon, $headline, $add_plugin_name = false ) { + echo ( $order > 0 ? '' : '' ) + . sprintf( + ' %s%s', + esc_attr( $icon ), + esc_html( $add_plugin_name ? self::$settings->get_not_empty_global_option( 'plugin_display_name' ) . ' ' : '' ), + esc_html( $headline ) + ) + . ( $order > 0 ? '' : '' ); + } + + /** + * Register admin scripts + * + * @see \WP_Piwik\Admin::print_admin_scripts() + */ + public function print_admin_scripts() { + wp_enqueue_script( 'jquery' ); + } + + /** + * Extend admin header + * + * @see \WP_Piwik\Admin::extend_admin_header() + */ + public function extend_admin_header() { + } + + /** + * Show credits + */ + public function show_credits() { + ?> +

+

+ ', + '' + ); + ?> +

+

, !

+

+

+ ', + '' + ); + ?> +

+ +

+ +
+ +
+ + + +

+

+ ', + '' + ); + ?> +

+
    +
  1. + ' . ( function_exists( 'curl_init' ) ? '' : esc_html__( 'not', 'wp-piwik' ) ) . ' '; + esc_html_e( 'available', 'wp-piwik' ); + ?> + .
  2. +
  3. + ' . ( ini_get( 'allow_url_fopen' ) ? '' : esc_html__( 'not', 'wp-piwik' ) ) . ' '; + esc_html_e( 'enabled', 'wp-piwik' ); + ?> + .
  4. +
  5. get_global_option( 'http_connection' ) ) || ( function_exists( 'curl_init' ) && ! ini_get( 'allow_url_fopen' ) ) ) ? esc_html__( 'cURL', 'wp-piwik' ) : esc_html__( 'fopen', 'wp-piwik' ) ) . ' (' . ( 'post' === self::$settings->get_global_option( 'http_method' ) ? esc_html__( 'POST', 'wp-piwik' ) : esc_html__( 'GET', 'wp-piwik' ) ) . ') ' . esc_html__( 'is used.', 'wp-piwik' ); ?>
  6. + get_global_option( 'piwik_mode' ) ) { + ?> +
  7. + ' . esc_html( self::$settings->get_global_option( 'proxy_url' ) ) . ''; + ?> +
  8. +
+

:

+
    + +
  1. +
  2. +
  3. +
  4. +
+

+ read_rss_feed( 'http://wordpress.org/support/rss/plugin/wp-piwik' ); + if ( ! empty( $support_threads ) ) { + echo '
    '; + foreach ( $support_threads as $support_thread ) { + echo '
  1. ' . esc_html( $support_thread['title'] ) . '
  2. '; + } + echo '
'; + } + } + + /** + * Read RSS feed + * + * @param string $feed + * feed URL + * @param int $cnt + * item limit + * @return array feed items array[](title, url) + * + */ + private function read_rss_feed( $feed, $cnt = 5 ) { + $result = array(); + if ( function_exists( 'simplexml_load_file' ) && ! empty( $feed ) ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $xml = @simplexml_load_file( $feed ); + if ( ! $xml || ! isset( $xml->channel [0]->item ) ) { + return array( + array( + 'title' => 'Can\'t read RSS feed.', + 'url' => $xml, + ), + ); + } + foreach ( $xml->channel [0]->item as $item ) { + if ( 0 === $cnt-- ) { + break; + } + $result [] = array( + 'title' => $item->title [0], + 'url' => $item->link [0], + ); + } + } + return $result; + } + + /** + * Clear cache and reset settings + * + * @param boolean $clear_settings set to true to reset settings (default: false) + */ + private function clear( $clear_settings = false ) { + if ( $clear_settings ) { + self::$settings->reset_settings(); + $this->show_box( 'updated', 'yes', esc_html__( 'Settings cleared (except connection settings).', 'wp-piwik' ) ); + } + global $wpdb; + if ( self::$settings->check_network_activation() ) { + $ary_blogs = \WP_Piwik\Settings::get_blog_list(); + if ( is_array( $ary_blogs ) ) { + foreach ( $ary_blogs as $ary_blog ) { + switch_to_blog( $ary_blog['blog_id'] ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wp-piwik_%'" ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_wp-piwik_%'" ); + restore_current_blog(); + } + } + } else { + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_wp-piwik_%'" ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_wp-piwik_%'" ); + } + $this->show_box( 'updated', 'yes', esc_html__( 'Cache cleared.', 'wp-piwik' ) ); + } + + /** + * Execute test script and display results + */ + private function run_testscript() { + ?> +
+

Testscript Result

+ is_configured() ) { + if ( ! empty( $_GET['testscript_id'] ) ) { + switch_to_blog( intval( wp_unslash( $_GET['testscript_id'] ) ) ); + } + ?> + + +

Please configure WP-Matomo first.

+ +
+ wpPiwik = $wpPiwik; - if( isset($_POST['s']) ){ - $cnt = $this->prepare_items ($_POST['s']); - } else { - $cnt = $this->prepare_items (); - } - global $status, $page; - $this->showSearchForm(); - parent::__construct ( array ( - 'singular' => __ ( 'site', 'wp-piwik' ), - 'plural' => __ ( 'sites', 'wp-piwik' ), - 'ajax' => false - ) ); - if ($cnt > 0) - $this->display (); - else - echo '

' . __ ( 'No site configured yet.', 'wp-piwik' ) . '

'; - } - - public function get_columns() { - $columns = array ( - 'id' => __ ( 'Blog ID', 'wp-piwik' ), - 'name' => __ ( 'Title', 'wp-piwik' ), - 'siteurl' => __ ( 'URL', 'wp-piwik' ), - 'piwikid' => __ ( 'Site ID (Piwik)', 'wp-piwik' ) - ); - return $columns; - } - - public function prepare_items($search = '') { - $current_page = $this->get_pagenum (); - $per_page = 10; - global $blog_id; - global $wpdb; - global $pagenow; - if (is_plugin_active_for_network ( 'wp-piwik/wp-piwik.php' )) { - $total_items = $wpdb->get_var ( $wpdb->prepare('SELECT COUNT(*) FROM ' . $wpdb->blogs . ' WHERE CONCAT(domain, path) LIKE "%%%s%%" AND spam = 0 AND deleted = 0', $search)); - $blogs = \WP_Piwik\Settings::getBlogList($per_page, $current_page, $search); - foreach ( $blogs as $blog ) { - $blogDetails = get_blog_details ( $blog['blog_id'], true ); - $this->data [] = array ( - 'name' => $blogDetails->blogname, - 'id' => $blogDetails->blog_id, - 'siteurl' => $blogDetails->siteurl, - 'piwikid' => $this->wpPiwik->getPiwikSiteId ( $blogDetails->blog_id ) - ); - } - } else { - $blogDetails = get_bloginfo (); - $this->data [] = array ( - 'name' => get_bloginfo ( 'name' ), - 'id' => '-', - 'siteurl' => get_bloginfo ( 'url' ), - 'piwikid' => $this->wpPiwik->getPiwikSiteId () - ); - $total_items = 1; - } - $columns = $this->get_columns (); - $hidden = array (); - $sortable = array (); - $this->_column_headers = array ( - $columns, - $hidden, - $sortable - ); - $this->set_pagination_args ( array ( - 'total_items' => $total_items, - 'per_page' => $per_page - ) ); - foreach ( $this->data as $key => $dataset ) { - if (empty ( $dataset ['piwikid'] ) || $dataset ['piwikid'] == 'n/a') - $this->data [$key] ['piwikid'] = __ ( 'Site not created yet.', 'wp-piwik' ); - if ($this->wpPiwik->isNetworkMode ()) - $this->data [$key] ['name'] = '' . $dataset ['name'] . ''; - } - $this->items = $this->data; - return count ( $this->items ); - } - - public function column_default($item, $column_name) { - switch ($column_name) { - case 'id' : - case 'name' : - case 'siteurl' : - case 'piwikid' : - return $item [$column_name]; - default : - return print_r ( $item, true ); - } - } - - private function showSearchForm() { - ?> -
- - search_box('Search domain and path', 'wpPiwikSiteSearch'); ?> -
- wp_piwik = $wp_piwik; + if ( isset( $_REQUEST['s'] ) ) { + $cnt = $this->prepare_items( sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) ); + } else { + $cnt = $this->prepare_items(); + } + $this->show_search_form(); + parent::__construct( + array( + 'singular' => __( 'site', 'wp-piwik' ), + 'plural' => __( 'sites', 'wp-piwik' ), + 'ajax' => false, + ) + ); + if ( $cnt > 0 ) { + $this->display(); + } else { + echo '

' . esc_html__( 'No site configured yet.', 'wp-piwik' ) . '

'; + } + } + + public function get_columns() { + $columns = array( + 'id' => __( 'Blog ID', 'wp-piwik' ), + 'name' => __( 'Title', 'wp-piwik' ), + 'siteurl' => __( 'URL', 'wp-piwik' ), + 'piwikid' => __( 'Site ID (Piwik)', 'wp-piwik' ), + ); + return $columns; + } + + public function prepare_items( $search = '' ) { + global $blog_id; + global $wpdb; + global $pagenow; + + $current_page = $this->get_pagenum(); + $per_page = 10; + + if ( is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ) ) { + $search = '%' . $wpdb->esc_like( $search ) . '%'; + $total_items = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %s WHERE CONCAT(domain, path) LIKE %s AND spam = 0 AND deleted = 0', $wpdb->blogs, $search ) ); + $blogs = \WP_Piwik\Settings::get_blog_list( $per_page, $current_page, $search ); + foreach ( $blogs as $blog ) { + $blog_details = get_blog_details( $blog['blog_id'], true ); + $this->data [] = array( + 'name' => $blog_details->blogname, + 'id' => $blog_details->blog_id, + 'siteurl' => $blog_details->siteurl, + 'piwikid' => $this->wp_piwik->get_piwik_site_id( $blog_details->blog_id ), + ); + } + } else { + $blog_details = get_bloginfo(); + $this->data [] = array( + 'name' => get_bloginfo( 'name' ), + 'id' => '-', + 'siteurl' => get_bloginfo( 'url' ), + 'piwikid' => $this->wp_piwik->get_piwik_site_id(), + ); + $total_items = 1; + } + $columns = $this->get_columns(); + $hidden = array(); + $sortable = array(); + $this->_column_headers = array( + $columns, + $hidden, + $sortable, + ); + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + ) + ); + foreach ( $this->data as $key => $dataset ) { + if ( empty( $dataset['piwikid'] ) || 'n/a' === $dataset['piwikid'] ) { + $this->data [ $key ] ['piwikid'] = __( 'Site not created yet.', 'wp-piwik' ); + } + if ( $this->wp_piwik->is_network_mode() ) { + $this->data [ $key ] ['name'] = '' . esc_html( $dataset['name'] ) . ''; + } + } + $this->items = $this->data; + return count( $this->items ); + } + + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'id': + case 'name': + case 'siteurl': + case 'piwikid': + return $item [ $column_name ]; + default: + return print_r( $item, true ); + } + } + + private function show_search_form() { + $page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : ''; + ?> +
+ + search_box( 'Search domain and path', 'wpPiwikSiteSearch' ); ?> +
+ getGlobalOption('disable_timelimit')) set_time_limit(0); - echo '
'; - echo '

'.(self::$settings->getGlobalOption('plugin_display_name') == 'WP-Piwik'?'Piwik '.__('Statistics', 'wp-piwik'):self::$settings->getGlobalOption('plugin_display_name')).'

'; - if (self::$settings->checkNetworkActivation() && function_exists('is_super_admin') && is_super_admin()) { +class Statistics extends \WP_Piwik\Admin { - if (isset($_GET['wpmu_show_stats'])) { - switch_to_blog((int) $_GET['wpmu_show_stats']); - } elseif ((isset($_GET['overview']) && $_GET['overview']) || (function_exists('is_network_admin') && is_network_admin())) { - new \WP_Piwik\Admin\Sitebrowser(self::$wpPiwik); - return; - } - echo '

'.__('Currently shown stats:').' '.get_bloginfo('name').'.'.' Show site overview.

'; - } - echo '
'; - wp_nonce_field('wp-piwik_stats-general'); - wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false); - wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false); - $columns = array('normal', 'side', 'column3'); - for ($i = 0; $i < 3; $i++) { - echo '
'; - do_meta_boxes(self::$wpPiwik->statsPageId, $columns[$i], null); - echo '
'; - } - echo '
'; - echo ''."\n"; - if (self::$settings->checkNetworkActivation() && function_exists('is_super_admin') && is_super_admin()) { - restore_current_blog(); - } + /** + * @return void + * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound + */ + public function show() { + global $screen_layout_columns; + if ( empty( $screen_layout_columns ) ) { + $screen_layout_columns = 2; } - - public function printAdminScripts() { - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); + if ( self::$settings->get_global_option( 'disable_timelimit' ) ) { + set_time_limit( 0 ); } + echo '
'; + echo '

' . esc_html( 'WP-Piwik' === self::$settings->get_global_option( 'plugin_display_name' ) ? 'Piwik ' . esc_html__( 'Statistics', 'wp-piwik' ) : self::$settings->get_global_option( 'plugin_display_name' ) ) . '

'; + if ( self::$settings->check_network_activation() && function_exists( 'is_super_admin' ) && is_super_admin() ) { + if ( isset( $_GET['wpmu_show_stats'] ) ) { + switch_to_blog( (int) $_GET['wpmu_show_stats'] ); + } elseif ( ! empty( $_GET['overview'] ) || ( function_exists( 'is_network_admin' ) && is_network_admin() ) ) { + new \WP_Piwik\Admin\Sitebrowser( self::$wp_piwik ); + return; + } + echo '

' . esc_html__( 'Currently shown stats:', 'wp-piwik' ) . ' ' . esc_html( get_bloginfo( 'name' ) ) . '. Show site overview.

'; + } + echo '
'; + wp_nonce_field( 'wp-piwik_stats-general' ); + wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); + wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); + $columns = array( 'normal', 'side', 'column3' ); + for ( $i = 0; $i < 3; $i++ ) { + // @phpstan-ignore-next-line + echo '
'; + do_meta_boxes( self::$wp_piwik->stats_page_id, $columns[ $i ], null ); + echo '
'; + } + echo '
'; + echo '' . "\n"; + if ( self::$settings->check_network_activation() && function_exists( 'is_super_admin' ) && is_super_admin() ) { + restore_current_blog(); + } + } - } \ No newline at end of file + public function print_admin_scripts() { + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/AjaxTracker.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/AjaxTracker.php new file mode 100644 index 00000000..f6db750a --- /dev/null +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/AjaxTracker.php @@ -0,0 +1,175 @@ +logger = $logger; + + $idsite = $settings->get_option( 'site_id' ); + if ( ! $idsite ) { + return; + } + + $api_endpoint = rtrim( $settings->get_matomo_url(), '/' ) . '/matomo.php'; + + parent::__construct( (int) $idsite, $api_endpoint ); + + $this->ip = false; + + if ( ! $settings->get_global_option( 'disable_cookies' ) ) { + $cookie_domain = $this->get_tracking_cookie_domain( $settings ); + $this->enableCookies( $cookie_domain ); + } else { + $this->disableCookieSupport(); + } + + if ( $this->loadVisitorIdCookie() ) { + if ( ! empty( $this->cookieVisitorId ) ) { + $this->has_cookie = true; + $this->set_visitor_id_safe( $this->cookieVisitorId ); + } + } + } + + public function set_visitor_id_safe( $visitor_id ) { + try { + $this->setVisitorId( $visitor_id ); + } catch ( \Exception $ex ) { + // do not fatal if the visitor ID is invalid for some reason + if ( ! $this->is_invalid_visitor_id_error( $ex ) ) { + throw $ex; + } + } + } + + protected function setCookie( $cookieName, $cookieValue, $cookieTTL ) { + if ( ! $this->has_cookie ) { + // we only set / overwrite cookies if it is a visitor that has eg no JS enabled or ad blocker enabled etc. + // this way we will track all cart updates and orders into the same visitor on following requests. + // If we recognized the visitor before via cookie we want in our case to make sure to not overwrite + // any cookie + return parent::setCookie( $cookieName, $cookieValue, $cookieTTL ); + } + return $this; + } + + protected function sendRequest( string $url, string $method = 'GET', $data = null, bool $force = false ): string { + if ( ! $this->idSite ) { + $this->logger->log( 'ecommerce tracking could not find idSite, cannot send request' ); + return ''; // not installed or synced yet + } + + if ( $this->is_prerender() ) { + // do not track if for some reason we are prerendering + return ''; + } + + $args = array( + 'method' => $method, + 'headers' => array( + 'User-Agent' => $this->userAgent, + ), + 'blocking' => false, + ); + if ( ! empty( $data ) ) { + $args['body'] = $data; + } + + $url = $url . '&bots=1'; + + try { + $response = $this->wp_remote_request( $url, $args ); + } catch ( \Exception $ex ) { + $this->logger->log( 'ajax_tracker: ' . $ex->getMessage() ); + return ''; + } + + if ( is_wp_error( $response ) ) { + $this->logger->log( 'ajax_tracker: ' . new \Exception( $response->get_error_message() ) ); + return ''; + } + + return $response['body']; + } + + private function is_invalid_visitor_id_error( \Exception $ex ) { + return strpos( $ex->getMessage(), 'setVisitorId() expects' ) === 0; + } + + /** + * See https://developer.chrome.com/docs/web-platform/prerender-pages + * + * @return bool + */ + private function is_prerender() { + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $purpose = strtolower( isset( $_SERVER['HTTP_SEC_PURPOSE'] ) ? wp_unslash( $_SERVER['HTTP_SEC_PURPOSE'] ) : '' ); + return strpos( $purpose, 'prefetch' ) !== false + || strpos( $purpose, 'prerender' ) !== false; + } + + /** + * for tests to override + * + * @param string $url + * @param array $args + * @return array|\WP_Error + */ + protected function wp_remote_request( $url, $args ) { + return wp_remote_request( $url, $args ); + } + + /** + * In Connect Matomo we want to rely entirely on JavaScript tracker + * for creating cookies. + * + * @return self + */ + protected function setFirstPartyCookies() { + // disabled + return $this; + } + + public static function getCurrentUrl(): string { + return parent::getCurrentUrl(); + } + + public function get_tracking_cookie_domain( Settings $settings ) { + if ( + $settings->get_global_option( 'track_across' ) + || $settings->get_global_option( 'track_crossdomain_linking' ) + ) { + $host = wp_parse_url( home_url(), PHP_URL_HOST ); + if ( ! empty( $host ) ) { + return '*.' . $host; + } + } + + return ''; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger.php index d016661f..7221469b 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger.php @@ -1,47 +1,57 @@ setName($loggerName); - $this->setStartMicrotime(microtime(true)); - $this->log('Logging started -------------------------------'); + private $logger_name = 'unnamed'; + private $start_microtime = null; + + abstract public function logger_output( $logger_time, $logger_message ); + + public function __construct( $logger_name ) { + $this->set_name( $logger_name ); + $this->set_start_microtime( microtime( true ) ); + $this->log( 'Logging started -------------------------------' ); + } + + public static function make_logger() { + $logger = defined( 'WP_PIWIK_ACTIVATE_LOGGER' ) ? WP_PIWIK_ACTIVATE_LOGGER : 0; + switch ( $logger ) { + case 1: + return new \WP_Piwik\Logger\Screen( __CLASS__ ); + case 2: + return new \WP_Piwik\Logger\File( __CLASS__ ); + default: + return new \WP_Piwik\Logger\Dummy( __CLASS__ ); } - - public function __destruct() { - $this->log('Logging finished ------------------------------'); - } - - public function log($loggerMessage) { - $this->loggerOutput($this->getElapsedMicrotime(), $loggerMessage); - } - - private function setName($loggerName) { - $this->loggerName = $loggerName; - } - - public function getName() { - return $this->loggerName; - } - - private function setStartMicrotime($startMicrotime) { - $this->startMicrotime = $startMicrotime; - } - - public function getStartMicrotime() { - return $this->startMicrotime; - } - - public function getElapsedMicrotime() { - return microtime(true) - $this->getStartMicrotime(); - } - - } \ No newline at end of file + } + + public function __destruct() { + $this->log( 'Logging finished ------------------------------' ); + } + + public function log( $logger_message ) { + $this->logger_output( $this->get_elapsed_microtime(), $logger_message ); + } + + private function set_name( $logger_name ) { + $this->logger_name = $logger_name; + } + + public function get_name() { + return $this->logger_name; + } + + private function set_start_microtime( $start_microtime ) { + $this->start_microtime = $start_microtime; + } + + public function get_start_microtime() { + return $this->start_microtime; + } + + public function get_elapsed_microtime() { + return microtime( true ) - $this->get_start_microtime(); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Dummy.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Dummy.php index 211c871d..20af7b40 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Dummy.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Dummy.php @@ -1,9 +1,10 @@ logger_file = WP_PIWIK_PATH . 'logs' . DIRECTORY_SEPARATOR . + gmdate( 'Ymd' ) . '_' . $this->encode_filename( $this->get_name() ) . '.log'; + } + + private function get_filename() { + return $this->logger_file; + } + + private function open_file() { + if ( ! $this->logger_file ) { + $this->set_filename(); } - - private function setFilename() { - $this->loggerFile = WP_PIWIK_PATH.'logs'.DIRECTORY_SEPARATOR. - date('Ymd').'_'.$this->encodeFilename($this->getName()).'.log'; + return fopen( $this->get_filename(), 'a' ); + } + + private function close_file( $file_handle ) { + fclose( $file_handle ); + } + + private function write_file( $file_handle, $file_content ) { + fwrite( $file_handle, $file_content . "\n" ); + } + + private function format_microtime( $logger_time ) { + return sprintf( '[%6s sec]', number_format( $logger_time, 3 ) ); + } + + public function logger_output( $logger_time, $logger_message ) { + $file_handle = $this->open_file(); + if ( $file_handle ) { + $this->write_file( $file_handle, $this->format_microtime( $logger_time ) . ' ' . $logger_message ); + $this->close_file( $file_handle ); } - - private function getFilename() { - return $this->loggerFile; - } - - private function openFile() { - if (!$this->loggerFile) - $this->setFilename(); - return fopen($this->getFilename(), 'a'); - } - - private function closeFile($fileHandle) { - fclose($fileHandle); - } - - private function writeFile($fileHandle, $fileContent) { - fwrite($fileHandle, $fileContent."\n"); - } - - private function formatMicrotime($loggerTime) { - return sprintf('[%6s sec]',number_format($loggerTime,3)); - } - - public function loggerOutput($loggerTime, $loggerMessage) { - if ($fileHandle = $this->openFile()) { - $this->writeFile($fileHandle, $this->formatMicrotime($loggerTime).' '.$loggerMessage); - $this->closeFile($fileHandle); - } - } - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Screen.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Screen.php index 88afa23d..7ec7fd8a 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Screen.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Logger/Screen.php @@ -1,27 +1,27 @@ logs[] = $this->formatMicrotime($loggerTime).' '.$loggerMessage; - } - - public function echoResults() { - echo '
';
-			print_r($this->logs);
-			echo '
'; - } - } \ No newline at end of file +namespace WP_Piwik\Logger; + +class Screen extends \WP_Piwik\Logger { + + private $logs = array(); + + private function format_microtime( $logger_time ) { + return sprintf( '[%6s sec]', number_format( $logger_time, 3 ) ); + } + + public function __construct( $logger_name ) { + add_action( is_admin() ? 'admin_footer' : 'wp_footer', array( $this, 'echo_results' ) ); + parent::__construct( $logger_name ); + } + + public function logger_output( $logger_time, $logger_message ) { + $this->logs[] = $this->format_microtime( $logger_time ) . ' ' . $logger_message; + } + + public function echo_results() { + echo '
';
+		print_r( $this->logs );
+		echo '
'; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request.php index c26e2291..8c449598 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request.php @@ -1,95 +1,132 @@ $method, 'parameter' => $parameter); - return $id; - } +abstract class Request { - private static function parameterToString($parameter) { - $return = ''; - if (is_array($parameter)) - foreach ($parameter as $key => $value) - $return .= '&'.$key.'='.$value; - return $return; - } + /** + * @var \WP_Piwik + */ + protected static $wp_piwik; - public function perform($id) { - if ( self::$settings->getGlobalOption('cache') && false !== ( $cached = get_transient( 'wp-piwik_c_'.md5(self::$isCacheable[$id] ) ) ) ) { - if (!empty ( $cached ) && !(! empty ( $cached['result'] ) && $cached['result'] == 'error') ) { - self::$wpPiwik->log("Deliver cached data: ".$id); - return $cached; - } + /** + * @var Settings + */ + protected static $settings; + protected static $debug; + protected static $last_error = ''; + protected static $requests = array(); + protected static $results = array(); + protected static $is_cacheable = array(); + protected static $piwik_version; + + public function __construct( $wp_piwik, $settings ) { + self::$wp_piwik = $wp_piwik; + self::$settings = $settings; + self::register( 'API.getPiwikVersion', array() ); + } + + public function reset() { + self::$debug = null; + self::$requests = array(); + self::$results = array(); + self::$is_cacheable = array(); + self::$piwik_version = null; + } + + public static function register( $method, $parameter ) { + if ( 'API.getPiwikVersion' === $method ) { + $id = 'global.getPiwikVersion'; + } else { + $id = 'method=' . $method . self::parameter_to_string( $parameter ); + } + if ( + in_array( $method, array( 'API.getPiwikVersion', 'SitesManager.getJavascriptTag', 'SitesManager.getSitesWithAtLeastViewAccess', 'SitesManager.getSitesIdFromSiteUrl', 'SitesManager.addSite', 'SitesManager.updateSite', 'SitesManager.getSitesWithAtLeastViewAccess' ), true ) || + ! isset( $parameter['date'] ) || + ! isset( $parameter['period'] ) || + 'last' === substr( $parameter['date'], 0, 4 ) || + 'today' === $parameter['date'] || + ( 'day' === $parameter['period'] && gmdate( 'Ymd' ) === $parameter['date'] ) || + ( 'month' === $parameter['period'] && gmdate( 'Ym' ) === $parameter['date'] ) || + ( 'week' === $parameter['period'] && gmdate( 'Ymd', strtotime( 'last Monday' ) ) === $parameter['date'] ) + ) { + self::$is_cacheable[ $id ] = false; + } else { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + self::$is_cacheable[ $id ] = $method . '-' . serialize( $parameter ); + } + if ( ! isset( self::$requests[ $id ] ) ) { + self::$requests[ $id ] = array( + 'method' => $method, + 'parameter' => $parameter, + ); + } + return $id; + } + + private static function parameter_to_string( $parameter ) { + $return = ''; + if ( is_array( $parameter ) ) { + foreach ( $parameter as $key => $value ) { + $return .= '&' . $key . '=' . $value; } - self::$wpPiwik->log("Perform request: ".$id); - if (!isset(self::$requests[$id])) - return array('result' => 'error', 'message' => 'Request '.$id.' was not registered.'); - elseif (!isset(self::$results[$id])) { - $this->request($id); - } - if ( isset ( self::$results[$id] ) ) { - if ( self::$settings->getGlobalOption('cache') && self::$isCacheable[$id] ) { - set_transient( 'wp-piwik_c_'.md5(self::$isCacheable[$id]) , self::$results[$id], WEEK_IN_SECONDS ); - } - return self::$results[$id]; - } else return false; - } - - public function getDebug($id) { - return isset( self::$debug[$id] )? self::$debug[$id] : false; - } - - protected function buildURL($config, $urlDecode = false) { - $url = 'method='.($config['method']).'&idSite='.self::$settings->getOption('site_id'); - foreach ($config['parameter'] as $key => $value) - $url .= '&'.$key.'='.($urlDecode?urldecode($value):$value); - return $url; - } - - protected function unserialize($str) { - self::$wpPiwik->log("Result string: ".$str); - return ($str == json_decode(false, true) || @json_decode($str, true) !== false)?json_decode($str, true):array(); } + return $return; + } - public static function getLastError() { - return self::$lastError; - } - - abstract protected function request($id); - - } \ No newline at end of file + public function perform( $id ) { + if ( self::$settings->get_global_option( 'cache' ) ) { + $cached = get_transient( 'wp-piwik_c_' . md5( self::$is_cacheable[ $id ] ) ); + if ( ! empty( $cached ) && ! ( ! empty( $cached['result'] ) && 'error' === $cached['result'] ) ) { + self::$wp_piwik->log( 'Deliver cached data: ' . $id ); + return $cached; + } + } + self::$wp_piwik->log( 'Perform request: ' . $id ); + if ( ! isset( self::$requests[ $id ] ) ) { + return array( + 'result' => 'error', + 'message' => 'Request ' . $id . ' was not registered.', + ); + } elseif ( ! isset( self::$results[ $id ] ) ) { + $this->request( $id ); + } + if ( isset( self::$results[ $id ] ) ) { + if ( self::$settings->get_global_option( 'cache' ) && self::$is_cacheable[ $id ] ) { + set_transient( 'wp-piwik_c_' . md5( self::$is_cacheable[ $id ] ), self::$results[ $id ], WEEK_IN_SECONDS ); + } + return self::$results[ $id ]; + } else { + return false; + } + } + + public function get_debug( $id ) { + return isset( self::$debug[ $id ] ) ? self::$debug[ $id ] : false; + } + + protected function build_url( $config ) { + return http_build_query( $this->get_url_params( $config ), '', '&' ); + } + + protected function get_url_params( $config ) { + $params = [ + 'method' => $config['method'], + 'idSite' => self::$settings->get_option( 'site_id' ), + ]; + $params = array_merge( $params, $config['parameter'] ); + return $params; + } + + protected function unserialize( $str ) { + self::$wp_piwik->log( 'Result string: ' . $str ); + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + return ( json_decode( '', true ) === $str || false !== @json_decode( $str, true ) ) ? json_decode( $str, true ) : array(); + } + + public static function get_last_error() { + return self::$last_error; + } + + abstract protected function request( $id ); +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php index 02856557..93534a27 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php @@ -1,64 +1,94 @@ getGlobalOption('piwik_url'); - foreach (self::$requests as $requestID => $config) { - if (!isset(self::$results[$requestID])) { - if (self::$settings->getGlobalOption('filter_limit') != "" && self::$settings->getGlobalOption('filter_limit') == (int) self::$settings->getGlobalOption('filter_limit')) - $config['parameter']['filter_limit'] = self::$settings->getGlobalOption('filter_limit'); - $params = 'module=API&format=json&'.$this->buildURL($config, true); - $map[$count] = $requestID; - $result = $this->call($id, $url, $params); - self::$results[$map[$count]] = $result; - $count++; + protected function request( $id ) { + $count = 0; + $url = self::$settings->get_global_option( 'piwik_url' ); + foreach ( self::$requests as $request_id => $config ) { + if ( ! isset( self::$results[ $request_id ] ) ) { + if ( '' !== self::$settings->get_global_option( 'filter_limit' ) && is_numeric( self::$settings->get_global_option( 'filter_limit' ) ) ) { + $config['parameter']['filter_limit'] = self::$settings->get_global_option( 'filter_limit' ); } + $params = $this->get_url_params( $config ); + $params['module'] = 'API'; + $params['format'] = 'json'; + $map[ $count ] = $request_id; + $result = $this->call( $id, $url, $params ); + self::$results[ $map[ $count ] ] = $result; + ++$count; } } + } - private function call($id, $url, $params) { - if (!defined('PIWIK_INCLUDE_PATH')) - return false; - if (PIWIK_INCLUDE_PATH === FALSE) - return array('result' => 'error', 'message' => __('Could not resolve','wp-piwik').' "'.htmlentities(self::$settings->getGlobalOption('piwik_path')).'": '.__('realpath() returns false','wp-piwik').'.'); - if (file_exists(PIWIK_INCLUDE_PATH . "/index.php")) - require_once PIWIK_INCLUDE_PATH . "/index.php"; - if (file_exists(PIWIK_INCLUDE_PATH . "/core/API/Request.php")) - require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php"; - if (class_exists('\Piwik\Application\Environment') && !self::$piwikEnvironment) { - // Piwik 2.14.* compatibility fix - self::$piwikEnvironment = new \Piwik\Application\Environment(null); - self::$piwikEnvironment->init(); - } - if (class_exists('Piwik\FrontController')) - \Piwik\FrontController::getInstance()->init(); - else return array('result' => 'error', 'message' => __('Class Piwik\FrontController does not exists.','wp-piwik')); - if (class_exists('Piwik\API\Request')) - $request = new \Piwik\API\Request($params.'&token_auth='.self::$settings->getGlobalOption('piwik_token')); - else return array('result' => 'error', 'message' => __('Class Piwik\API\Request does not exists.','wp-piwik')); - if (isset($request)) - $result = $request->process(); - else $result = null; - if (!headers_sent()) - header("Content-Type: text/html", true); - $result = $this->unserialize($result); - if ($GLOBALS ['wp-piwik_debug']) - self::$debug[$id] = array ( $params.'&token_auth=...' ); - return $result; + private function call( $id, $url, $params ) { + if ( ! defined( 'PIWIK_INCLUDE_PATH' ) ) { + return false; + } + if ( PIWIK_INCLUDE_PATH === false ) { + return array( + 'result' => 'error', + 'message' => __( 'Could not resolve', 'wp-piwik' ) . ' "' . htmlentities( self::$settings->get_global_option( 'piwik_path' ) ) . '": ' . __( 'realpath() returns false', 'wp-piwik' ) . '.', + ); + } + if ( file_exists( PIWIK_INCLUDE_PATH . '/index.php' ) ) { + require_once PIWIK_INCLUDE_PATH . '/index.php'; + } + if ( file_exists( PIWIK_INCLUDE_PATH . '/core/API/Request.php' ) ) { + require_once PIWIK_INCLUDE_PATH . '/core/API/Request.php'; + } + if ( class_exists( '\Piwik\Application\Environment' ) && ! self::$piwik_environment ) { + // Piwik 2.14.* compatibility fix + self::$piwik_environment = new \Piwik\Application\Environment( null ); + self::$piwik_environment->init(); + } + if ( class_exists( 'Piwik\FrontController' ) ) { + \Piwik\FrontController::getInstance()->init(); + } else { + return array( + 'result' => 'error', + 'message' => __( 'Class Piwik\FrontController does not exists.', 'wp-piwik' ), + ); + } + if ( class_exists( 'Piwik\API\Request' ) ) { + $params['token_auth'] = self::$settings->get_global_option( 'piwik_token' ); + $request = new \Piwik\API\Request( $params ); + } else { + return array( + 'result' => 'error', + 'message' => __( 'Class Piwik\API\Request does not exists.', 'wp-piwik' ), + ); } - public function reset() { - if (class_exists('\Piwik\Application\Environment') && !self::$piwikEnvironment) { - self::$piwikEnvironment->destroy(); - } - if (class_exists('Piwik\FrontController')) - \Piwik\FrontController::unsetInstance(); - parent::reset(); - } - } \ No newline at end of file + $result = $request->process(); + + if ( ! headers_sent() ) { + header( 'Content-Type: text/html', true ); + } + $result = $this->unserialize( $result ); + if ( $GLOBALS ['wp-piwik_debug'] ) { + self::$debug[ $id ] = array( $params . '&token_auth=...' ); + } + return $result; + } + + public function reset() { + if ( + class_exists( '\Piwik\Application\Environment' ) + && self::$piwik_environment instanceof \Piwik\Application\Environment + ) { + self::$piwik_environment->destroy(); + } + if ( class_exists( 'Piwik\FrontController' ) ) { + \Piwik\FrontController::unsetInstance(); + } + parent::reset(); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Rest.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Rest.php index 198ad844..80843536 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Rest.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Rest.php @@ -1,81 +1,124 @@ getGlobalOption('piwik_mode') == 'http') - $url = self::$settings->getGlobalOption('piwik_url'); - else if (self::$settings->getGlobalOption('piwik_mode') == 'cloud') - $url = 'https://'.self::$settings->getGlobalOption('piwik_user').'.innocraft.cloud/'; - else $url = 'https://'.self::$settings->getGlobalOption('matomo_user').'.matomo.cloud/'; - $params = 'module=API&method=API.getBulkRequest&format=json'; - if (self::$settings->getGlobalOption('filter_limit') != "" && self::$settings->getGlobalOption('filter_limit') == (int) self::$settings->getGlobalOption('filter_limit')) - $params .= '&filter_limit='.self::$settings->getGlobalOption('filter_limit'); - foreach (self::$requests as $requestID => $config) { - if (!isset(self::$results[$requestID])) { - $params .= '&urls['.$count.']='.urlencode($this->buildURL($config)); - $map[$count] = $requestID; - $count++; +/** + * TODO: switch to wp_remote_get + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_getinfo + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_error + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt + * phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec + * phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + */ +class Rest extends \WP_Piwik\Request { + + protected function request( $id ) { + $count = 0; + $url = self::$settings->get_matomo_url(); + $params = 'module=API&method=API.getBulkRequest&format=json'; + + $filter_limit = self::$settings->get_global_option( 'filter_limit' ); + if ( + $filter_limit > 0 + && is_numeric( self::$settings->get_global_option( 'filter_limit' ) ) + ) { + $params .= '&filter_limit=' . self::$settings->get_global_option( 'filter_limit' ); + } + foreach ( self::$requests as $request_id => $config ) { + if ( ! isset( self::$results[ $request_id ] ) ) { + $params .= '&urls[' . $count . ']=' . rawurlencode( $this->build_url( $config ) ); + $map[ $count ] = $request_id; + ++$count; + } + } + $use_curl = ( + function_exists( 'curl_init' ) + && ini_get( 'allow_url_fopen' ) + && 'curl' === self::$settings->get_global_option( 'http_connection' ) + ) || ( + function_exists( 'curl_init' ) + && ! ini_get( 'allow_url_fopen' ) + ); + $results = $use_curl ? $this->curl( $id, $url, $params ) : $this->fopen( $id, $url, $params ); + if ( is_array( $results ) ) { + foreach ( $results as $num => $result ) { + if ( isset( $map[ $num ] ) ) { + self::$results[ $map[ $num ] ] = $result; } } - $results = ((function_exists('curl_init') && ini_get('allow_url_fopen') && self::$settings->getGlobalOption('http_connection') == 'curl') || (function_exists('curl_init') && !ini_get('allow_url_fopen')))?$this->curl($id, $url, $params):$this->fopen($id, $url, $params); - if (is_array($results)) - foreach ($results as $num => $result) - if (isset($map[$num])) - self::$results[$map[$num]] = $result; - } - - private function curl($id, $url, $params) { - if (self::$settings->getGlobalOption('http_method')=='post') { - $c = curl_init($url); - curl_setopt($c, CURLOPT_POST, 1); - curl_setopt($c, CURLOPT_POSTFIELDS, $params.'&token_auth='.self::$settings->getGlobalOption('piwik_token')); - } else $c = curl_init($url.'?'.$params.'&token_auth='.self::$settings->getGlobalOption('piwik_token')); - curl_setopt($c, CURLOPT_SSL_VERIFYPEER, !self::$settings->getGlobalOption('disable_ssl_verify')); - curl_setopt($c, CURLOPT_SSL_VERIFYHOST, !self::$settings->getGlobalOption('disable_ssl_verify_host')?2:0); - curl_setopt($c, CURLOPT_USERAGENT, self::$settings->getGlobalOption('piwik_useragent')=='php'?ini_get('user_agent'):self::$settings->getGlobalOption('piwik_useragent_string')); - curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($c, CURLOPT_HEADER, $GLOBALS ['wp-piwik_debug'] ); - curl_setopt($c, CURLOPT_TIMEOUT, self::$settings->getGlobalOption('connection_timeout')); - $httpProxyClass = new \WP_HTTP_Proxy(); - if ($httpProxyClass->is_enabled() && $httpProxyClass->send_through_proxy($url)) { - curl_setopt($c, CURLOPT_PROXY, $httpProxyClass->host()); - curl_setopt($c, CURLOPT_PROXYPORT, $httpProxyClass->port()); - if ($httpProxyClass->use_authentication()) - curl_setopt($c, CURLOPT_PROXYUSERPWD, $httpProxyClass->username().':'.$httpProxyClass->password()); - } - $result = curl_exec($c); - self::$lastError = curl_error($c); - if ($GLOBALS ['wp-piwik_debug']) { - $header_size = curl_getinfo($c, CURLINFO_HEADER_SIZE); - $header = substr($result, 0, $header_size); - $body = substr($result, $header_size); - $result = $this->unserialize($body); - self::$debug[$id] = array ( $header, $url.'?'.$params.'&token_auth=...' ); - } else $result = $this->unserialize($result); - curl_close($c); - return $result; } + } - private function fopen($id, $url, $params) { - $contextDefinition = array('http'=>array('timeout' => self::$settings->getGlobalOption('connection_timeout'), 'header' => "Content-type: application/x-www-form-urlencoded\r\n") ); - $contextDefinition['ssl'] = array(); - if (self::$settings->getGlobalOption('disable_ssl_verify')) - $contextDefinition['ssl'] = array('allow_self_signed' => true, 'verify_peer' => false ); - if (self::$settings->getGlobalOption('disable_ssl_verify_host')) - $contextDefinition['ssl']['verify_peer_name'] = false; - if (self::$settings->getGlobalOption('http_method')=='post') { - $fullUrl = $url; - $contextDefinition['http']['method'] = 'POST'; - $contextDefinition['http']['content'] = $params.'&token_auth='.self::$settings->getGlobalOption('piwik_token'); - } else $fullUrl = $url.'?'.$params.'&token_auth='.self::$settings->getGlobalOption('piwik_token'); - $context = stream_context_create($contextDefinition); - $result = $this->unserialize(@file_get_contents($fullUrl, false, $context)); - if ($GLOBALS ['wp-piwik_debug']) - self::$debug[$id] = array ( get_headers($fullUrl, 1), $url.'?'.$params.'&token_auth=...' ); - return $result; + private function curl( $id, $url, $params ) { + if ( 'post' === self::$settings->get_global_option( 'http_method' ) ) { + $c = curl_init( $url ); + curl_setopt( $c, CURLOPT_POST, 1 ); + curl_setopt( $c, CURLOPT_POSTFIELDS, $params . '&token_auth=' . self::$settings->get_global_option( 'piwik_token' ) ); + } else { + $c = curl_init( $url . '?' . $params . '&token_auth=' . self::$settings->get_global_option( 'piwik_token' ) ); } - } \ No newline at end of file + curl_setopt( $c, CURLOPT_SSL_VERIFYPEER, ! self::$settings->get_global_option( 'disable_ssl_verify' ) ); + curl_setopt( $c, CURLOPT_SSL_VERIFYHOST, ! self::$settings->get_global_option( 'disable_ssl_verify_host' ) ? 2 : 0 ); + curl_setopt( $c, CURLOPT_USERAGENT, 'php' === self::$settings->get_global_option( 'piwik_useragent' ) ? ini_get( 'user_agent' ) : self::$settings->get_global_option( 'piwik_useragent_string' ) ); + curl_setopt( $c, CURLOPT_RETURNTRANSFER, 1 ); + curl_setopt( $c, CURLOPT_HEADER, $GLOBALS ['wp-piwik_debug'] ); + curl_setopt( $c, CURLOPT_TIMEOUT, self::$settings->get_global_option( 'connection_timeout' ) ); + $http_proxy_class = new \WP_HTTP_Proxy(); + if ( $http_proxy_class->is_enabled() && $http_proxy_class->send_through_proxy( $url ) ) { + curl_setopt( $c, CURLOPT_PROXY, $http_proxy_class->host() ); + curl_setopt( $c, CURLOPT_PROXYPORT, $http_proxy_class->port() ); + if ( $http_proxy_class->use_authentication() ) { + curl_setopt( $c, CURLOPT_PROXYUSERPWD, $http_proxy_class->username() . ':' . $http_proxy_class->password() ); + } + } + $result = curl_exec( $c ); + self::$last_error = curl_error( $c ); + if ( $GLOBALS ['wp-piwik_debug'] ) { + $header_size = curl_getinfo( $c, CURLINFO_HEADER_SIZE ); + $header = substr( $result, 0, $header_size ); + $body = substr( $result, $header_size ); + $result = $this->unserialize( $body ); + self::$debug[ $id ] = array( $header, $url . '?' . $params . '&token_auth=...' ); + } else { + $result = $this->unserialize( $result ); + } + curl_close( $c ); + return $result; + } + + private function fopen( $id, $url, $params ) { + $context_definition = array( + 'http' => array( + 'timeout' => self::$settings->get_global_option( 'connection_timeout' ), + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + ), + ); + $context_definition['ssl'] = array(); + if ( self::$settings->get_global_option( 'disable_ssl_verify' ) ) { + $context_definition['ssl'] = array( + 'allow_self_signed' => true, + 'verify_peer' => false, + ); + } + if ( self::$settings->get_global_option( 'disable_ssl_verify_host' ) ) { + $context_definition['ssl']['verify_peer_name'] = false; + } + if ( self::$settings->get_global_option( 'http_method' ) === 'post' ) { + $full_url = $url; + $context_definition['http']['method'] = 'POST'; + $context_definition['http']['content'] = $params . '&token_auth=' . self::$settings->get_global_option( 'piwik_token' ); + } else { + $full_url = $url . '?' . $params . '&token_auth=' . self::$settings->get_global_option( 'piwik_token' ); + } + $context = stream_context_create( $context_definition ); + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + $result = $this->unserialize( @file_get_contents( $full_url, false, $context ) ); + if ( $GLOBALS ['wp-piwik_debug'] ) { + self::$debug[ $id ] = array( get_headers( $full_url, 1 ), $url . '?' . $params . '&token_auth=...' ); + } + return $result; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Settings.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Settings.php index 04356e58..9a844af3 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Settings.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Settings.php @@ -1,427 +1,486 @@ - 'checkPiwikUrl', - 'piwik_token' => 'checkPiwikToken', - 'site_id' => 'requestPiwikSiteID', - 'tracking_code' => 'prepareTrackingCode', - 'noscript_code' => 'prepareNocscriptCode' - ); - - /** - * - * @var Register default configuration set - */ - private $globalSettings = array ( - // Plugin settings - 'revision' => 0, - 'last_settings_update' => 0, - // User settings: Piwik configuration - 'piwik_mode' => 'http', - 'piwik_url' => '', - 'piwik_path' => '', - 'piwik_user' => '', - 'matomo_user' => '', - 'piwik_token' => '', - 'auto_site_config' => true, - // User settings: Stats configuration - 'default_date' => 'yesterday', - 'stats_seo' => false, - 'stats_ecommerce' => false, - 'dashboard_widget' => false, - 'dashboard_ecommerce' => false, - 'dashboard_chart' => false, - 'dashboard_seo' => false, - 'toolbar' => false, - 'capability_read_stats' => array ( - 'administrator' => true - ), - 'perpost_stats' => "disabled", - 'plugin_display_name' => 'Connect Matomo', - 'piwik_shortcut' => false, - 'shortcodes' => false, - // User settings: Tracking configuration - 'track_mode' => 'disabled', - 'track_codeposition' => 'footer', - 'track_noscript' => false, - 'track_nojavascript' => false, - 'proxy_url' => '', - 'track_content' => 'disabled', - 'track_search' => false, - 'track_404' => false, - 'add_post_annotations' => array(), - 'add_customvars_box' => false, - 'add_download_extensions' => '', - 'set_download_extensions' => '', - 'set_link_classes' => '', - 'set_download_classes' => '', - 'require_consent' => 'disabled', - 'disable_cookies' => false, - 'limit_cookies' => false, - 'limit_cookies_visitor' => 34186669, // Piwik default 13 months - 'limit_cookies_session' => 1800, // Piwik default 30 minutes - 'limit_cookies_referral' => 15778463, // Piwik default 6 months - 'track_admin' => false, - 'capability_stealth' => array (), - 'track_across' => false, - 'track_across_alias' => false, - 'track_crossdomain_linking' => false, - 'track_feed' => false, - 'track_feed_addcampaign' => false, - 'track_feed_campaign' => 'feed', - 'track_heartbeat' => 0, - 'track_user_id' => 'disabled', - // User settings: Expert configuration - 'cache' => true, - 'http_connection' => 'curl', - 'http_method' => 'post', - 'disable_timelimit' => false, - 'filter_limit' => '', - 'connection_timeout' => 5, - 'disable_ssl_verify' => false, - 'disable_ssl_verify_host' => false, - 'piwik_useragent' => 'php', - 'piwik_useragent_string' => 'WP-Piwik', - 'dnsprefetch' => false, - 'track_datacfasync' => false, - 'track_cdnurl' => '', - 'track_cdnurlssl' => '', - 'force_protocol' => 'disabled', - 'remove_type_attribute' => false, - 'update_notice' => 'enabled' - ), $settings = array ( - 'name' => '', - 'site_id' => NULL, - 'noscript_code' => '', - 'tracking_code' => '', - 'last_tracking_code_update' => 0, - 'dashboard_revision' => 0 - ), $settingsChanged = false; - - /** - * Constructor class to prepare settings manager - * - * @param WP_Piwik $wpPiwik - * active WP-Piwik instance - */ - public function __construct($wpPiwik) { - self::$wpPiwik = $wpPiwik; - self::$wpPiwik->log ( 'Store default settings' ); - self::$defaultSettings = array ( - 'globalSettings' => $this->globalSettings, - 'settings' => $this->settings - ); - self::$wpPiwik->log ( 'Load settings' ); - foreach ( $this->globalSettings as $key => $default ) { - $this->globalSettings [$key] = ($this->checkNetworkActivation () ? get_site_option ( 'wp-piwik_global-' . $key, $default ) : get_option ( 'wp-piwik_global-' . $key, $default )); - } - foreach ( $this->settings as $key => $default ) - $this->settings [$key] = get_option ( 'wp-piwik-' . $key, $default ); - } - - /** - * Save all settings as WordPress options - */ - public function save() { - if (! $this->settingsChanged) { - self::$wpPiwik->log ( 'No settings changed yet' ); - return; - } - self::$wpPiwik->log ( 'Save settings' ); - $this->globalSettings['plugin_display_name'] = htmlspecialchars($this->globalSettings['plugin_display_name'], ENT_QUOTES, 'utf-8'); - foreach ( $this->globalSettings as $key => $value ) { - if ( $this->checkNetworkActivation() ) - update_site_option ( 'wp-piwik_global-' . $key, $value ); - else - update_option ( 'wp-piwik_global-' . $key, $value ); - } - foreach ( $this->settings as $key => $value ) { - update_option ( 'wp-piwik-' . $key, $value ); - } - global $wp_roles; - if (! is_object ( $wp_roles )) - $wp_roles = new \WP_Roles (); - if (! is_object ( $wp_roles )) - die ( "STILL NO OBJECT" ); - foreach ( $wp_roles->role_names as $strKey => $strName ) { - $objRole = get_role ( $strKey ); - foreach ( array ( - 'stealth', - 'read_stats' - ) as $strCap ) { - $aryCaps = $this->getGlobalOption ( 'capability_' . $strCap ); - if (isset ( $aryCaps [$strKey] ) && $aryCaps [$strKey]) - $wp_roles->add_cap ( $strKey, 'wp-piwik_' . $strCap ); - else $wp_roles->remove_cap ( $strKey, 'wp-piwik_' . $strCap ); - } - } - $this->settingsChanged = false; - } - - /** - * Get a global option's value which should not be empty - * - * @param string $key - * option key - * @return string option value - */ - public function getNotEmptyGlobalOption($key) { - return isset ( $this->globalSettings [$key] ) && !empty($this->globalSettings [$key]) ? $this->globalSettings [$key] : self::$defaultSettings ['globalSettings'] [$key]; - } - - /** - * Get a global option's value - * - * @param string $key - * option key - * @return string option value - */ - public function getGlobalOption($key) { - return isset ( $this->globalSettings [$key] ) ? $this->globalSettings [$key] : self::$defaultSettings ['globalSettings'] [$key]; - } - - /** - * Get an option's value related to a specific blog - * - * @param string $key - * option key - * @param int $blogID - * blog ID (default: current blog) - * @return \WP_Piwik\Register - */ - public function getOption($key, $blogID = null) { - if ($this->checkNetworkActivation () && ! empty ( $blogID )) { - return get_blog_option ( $blogID, 'wp-piwik-'.$key ); - } - return isset ( $this->settings [$key] ) ? $this->settings [$key] : self::$defaultSettings ['settings'] [$key]; - } - - /** - * Set a global option's value - * - * @param string $key - * option key - * @param string $value - * new option value - */ - public function setGlobalOption($key, $value) { - $this->settingsChanged = true; - self::$wpPiwik->log ( 'Changed global option ' . $key . ': ' . (is_array ( $value ) ? serialize ( $value ) : $value) ); - $this->globalSettings [$key] = $value; - } - - /** - * Set an option's value related to a specific blog - * - * @param string $key - * option key - * @param string $value - * new option value - * @param int $blogID - * blog ID (default: current blog) - */ - public function setOption($key, $value, $blogID = null) { - if (empty( $blogID )) { - $blogID = get_current_blog_id(); - } - $this->settingsChanged = true; - self::$wpPiwik->log ( 'Changed option ' . $key . ': ' . $value ); - if ($this->checkNetworkActivation ()) { - update_blog_option ( $blogID, 'wp-piwik-'.$key, $value ); - } - if ($blogID == get_current_blog_id()) { - $this->settings [$key] = $value; - } - } - - /** - * Reset settings to default - */ - public function resetSettings() { - self::$wpPiwik->log ( 'Reset WP-Piwik settings' ); - global $wpdb; - if ( $this->checkNetworkActivation() ) { - $aryBlogs = self::getBlogList(); - if (is_array($aryBlogs)) - foreach ($aryBlogs as $aryBlog) { - switch_to_blog($aryBlog['blog_id']); - $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'wp-piwik-%'"); - restore_current_blog(); - } - $wpdb->query("DELETE FROM $wpdb->sitemeta WHERE meta_key LIKE 'wp-piwik_global-%'"); - } - else $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'wp-piwik_global-%'"); - } - - /** - * Get blog list - */ - public static function getBlogList($limit = null, $page = null, $search = '') { - if ($limit && $page) - $queryLimit = ' LIMIT '.(int) (($page - 1) * $limit).','.(int) $limit; - global $wpdb; - return $wpdb->get_results($wpdb->prepare('SELECT blog_id FROM '.$wpdb->blogs.' WHERE CONCAT(domain, path) LIKE "%%%s%%" AND spam = 0 AND deleted = 0 ORDER BY blog_id'.$queryLimit, $search), ARRAY_A); - } - - /** - * Check if plugin is network activated - * - * @return boolean Is network activated? - */ - public function checkNetworkActivation() { - if (! function_exists ( "is_plugin_active_for_network" )) - require_once (ABSPATH . 'wp-admin/includes/plugin.php'); - return is_plugin_active_for_network ( 'wp-piwik/wp-piwik.php' ); - } - - /** - * Apply new configuration - * - * @param array $in - * new configuration set - */ - public function applyChanges($in) { - if (!self::$wpPiwik->isValidOptionsPost()) - die("Invalid config changes."); - $in = $this->checkSettings ( $in ); - self::$wpPiwik->log ( 'Apply changed settings:' ); - foreach ( self::$defaultSettings ['globalSettings'] as $key => $val ) - $this->setGlobalOption ( $key, isset ( $in [$key] ) ? $in [$key] : $val ); - foreach ( self::$defaultSettings ['settings'] as $key => $val ) - $this->setOption ( $key, isset ( $in [$key] ) ? $in [$key] : $val ); - $this->setGlobalOption ( 'last_settings_update', time () ); - $this->save (); - } - - /** - * Apply callback function on new settings - * - * @param array $in - * new configuration set - * @return array configuration set after callback functions were applied - */ - private function checkSettings($in) { - foreach ( $this->checkSettings as $key => $value ) - if (isset ( $in [$key] )) - $in [$key] = call_user_func_array ( array ( - $this, - $value - ), array ( - $in [$key], - $in - ) ); - return $in; - } - - /** - * Add slash to Piwik URL if necessary - * - * @param string $value - * Piwik URL - * @param array $in - * configuration set - * @return string Piwik URL - */ - private function checkPiwikUrl($value, $in) { - return substr ( $value, - 1, 1 ) != '/' ? $value . '/' : $value; - } - - /** - * Remove &token_auth= from auth token - * - * @param string $value - * Piwik auth token - * @param array $in - * configuration set - * @return string Piwik auth token - */ - private function checkPiwikToken($value, $in) { - return str_replace ( '&token_auth=', '', $value ); - } - - /** - * Request the site ID (if not set before) - * - * @param string $value - * tracking code - * @param array $in - * configuration set - * @return int Piwik site ID - */ - private function requestPiwikSiteID($value, $in) { - if ($in ['auto_site_config'] && ! $value) - return self::$wpPiwik->getPiwikSiteId(); - return $value; - } - - /** - * Prepare the tracking code - * - * @param string $value - * tracking code - * @param array $in - * configuration set - * @return string tracking code - */ - private function prepareTrackingCode($value, $in) { - if ($in ['track_mode'] == 'manually' || $in ['track_mode'] == 'disabled') { - $value = stripslashes ( $value ); - if ($this->checkNetworkActivation ()) - update_site_option ( 'wp-piwik-manually', $value ); - return $value; - } - /*$result = self::$wpPiwik->updateTrackingCode (); - echo '
'; print_r($result); echo '
'; - $this->setOption ( 'noscript_code', $result ['noscript'] );*/ - return; // $result ['script']; - } - - /** - * Prepare the nocscript code - * - * @param string $value - * noscript code - * @param array $in - * configuration set - * @return string noscript code - */ - private function prepareNocscriptCode($value, $in) { - if ($in ['track_mode'] == 'manually') - return stripslashes ( $value ); - return $this->getOption ( 'noscript_code' ); - } - - /** - * Get debug data - * - * @return array WP-Piwik settings for debug output - */ - public function getDebugData() { - $debug = array( - 'global_settings' => $this->globalSettings, - 'settings' => $this->settings - ); - $debug['global_settings']['piwik_token'] = !empty($debug['global_settings']['piwik_token'])?'set':'not set'; - return $debug; - } -} + 'check_piwik_url', + 'piwik_token' => 'check_piwik_token', + 'site_id' => 'request_piwik_site_id', + 'tracking_code' => 'prepare_tracking_code', + 'noscript_code' => 'prepare_nocscript_code', + ); + + /** + * @var array default configuration set + */ + private $global_settings = array( + // Plugin settings + 'revision' => 0, + 'last_settings_update' => 0, + // User settings: Piwik configuration + 'piwik_mode' => 'http', + 'piwik_url' => '', + 'piwik_path' => '', + 'piwik_user' => '', + 'matomo_user' => '', + 'piwik_token' => '', + 'auto_site_config' => true, + // User settings: Stats configuration + 'default_date' => 'yesterday', + 'stats_seo' => false, + 'stats_ecommerce' => false, + 'dashboard_widget' => false, + 'dashboard_ecommerce' => false, + 'dashboard_chart' => false, + 'dashboard_seo' => false, + 'toolbar' => false, + 'capability_read_stats' => array( + 'administrator' => true, + ), + 'perpost_stats' => 'disabled', + 'plugin_display_name' => 'Connect Matomo', + 'piwik_shortcut' => false, + 'shortcodes' => false, + // User settings: Tracking configuration + 'track_mode' => 'disabled', + 'track_codeposition' => 'footer', + 'track_noscript' => false, + 'track_nojavascript' => false, + 'proxy_url' => '', + 'track_content' => 'disabled', + 'track_search' => false, + 'track_404' => false, + 'add_post_annotations' => array(), + 'add_customvars_box' => false, + 'add_download_extensions' => '', + 'set_download_extensions' => '', + 'set_link_classes' => '', + 'set_download_classes' => '', + 'require_consent' => 'disabled', + 'disable_cookies' => false, + 'limit_cookies' => false, + 'limit_cookies_visitor' => 34186669, // Piwik default 13 months + 'limit_cookies_session' => 1800, // Piwik default 30 minutes + 'limit_cookies_referral' => 15778463, // Piwik default 6 months + 'track_admin' => false, + 'capability_stealth' => array(), + 'track_across' => false, + 'track_across_alias' => false, + 'track_crossdomain_linking' => false, + 'track_feed' => false, + 'track_feed_addcampaign' => false, + 'track_feed_campaign' => 'feed', + 'track_heartbeat' => 0, + 'track_user_id' => 'disabled', + // User settings: Expert configuration + 'cache' => true, + 'http_connection' => 'curl', + 'http_method' => 'post', + 'disable_timelimit' => false, + 'filter_limit' => '', + 'connection_timeout' => 5, + 'disable_ssl_verify' => false, + 'disable_ssl_verify_host' => false, + 'piwik_useragent' => 'php', + 'piwik_useragent_string' => 'WP-Piwik', + 'dnsprefetch' => false, + 'track_datacfasync' => false, + 'track_cdnurl' => '', + 'track_cdnurlssl' => '', + 'force_protocol' => 'disabled', + 'remove_type_attribute' => false, + 'update_notice' => 'enabled', + + self::TRACK_AI_BOTS => false, + self::TRACK_AI_BOTS_USING_ESI => false, + ); + + private $settings = array( + 'name' => '', + 'site_id' => null, + 'noscript_code' => '', + 'tracking_code' => '', + 'last_tracking_code_update' => 0, + 'dashboard_revision' => 0, + ); + + private $settings_changed = false; + + /** + * Constructor class to prepare settings manager + * + * @param \WP_Piwik $wp_piwik + * active WP-Piwik instance + */ + public function __construct( $wp_piwik ) { + self::$wp_piwik = $wp_piwik; + self::$wp_piwik->log( 'Store default settings' ); + self::$default_settings = array( + 'globalSettings' => $this->global_settings, + 'settings' => $this->settings, + ); + self::$wp_piwik->log( 'Load settings' ); + foreach ( $this->global_settings as $key => $default ) { + $this->global_settings [ $key ] = ( $this->check_network_activation() ? get_site_option( 'wp-piwik_global-' . $key, $default ) : get_option( 'wp-piwik_global-' . $key, $default ) ); + } + foreach ( $this->settings as $key => $default ) { + $this->settings [ $key ] = get_option( 'wp-piwik-' . $key, $default ); + } + } + + /** + * Save all settings as WordPress options + */ + public function save() { + global $wp_roles; + + if ( ! $this->settings_changed ) { + self::$wp_piwik->log( 'No settings changed yet' ); + return; + } + self::$wp_piwik->log( 'Save settings' ); + $this->global_settings['plugin_display_name'] = htmlspecialchars( $this->global_settings['plugin_display_name'], ENT_QUOTES, 'utf-8' ); + foreach ( $this->global_settings as $key => $value ) { + if ( $this->check_network_activation() ) { + update_site_option( 'wp-piwik_global-' . $key, $value ); + } else { + update_option( 'wp-piwik_global-' . $key, $value ); + } + } + foreach ( $this->settings as $key => $value ) { + update_option( 'wp-piwik-' . $key, $value ); + } + foreach ( $wp_roles->role_names as $str_key => $str_name ) { + $obj_role = get_role( $str_key ); + $caps = array( 'stealth', 'read_stats' ); + foreach ( $caps as $str_cap ) { + $ary_caps = $this->get_global_option( 'capability_' . $str_cap ); + if ( isset( $ary_caps [ $str_key ] ) && $ary_caps [ $str_key ] ) { + $wp_roles->add_cap( $str_key, 'wp-piwik_' . $str_cap ); + } else { + $wp_roles->remove_cap( $str_key, 'wp-piwik_' . $str_cap ); + } + } + } + $this->settings_changed = false; + } + + /** + * Get a global option's value which should not be empty + * + * @param string $key + * option key + * @return string option value + */ + public function get_not_empty_global_option( $key ) { + return isset( $this->global_settings [ $key ] ) && ! empty( $this->global_settings [ $key ] ) ? $this->global_settings [ $key ] : self::$default_settings ['globalSettings'] [ $key ]; + } + + /** + * Get a global option's value + * + * @param string $key + * option key + * @return mixed option value + */ + public function get_global_option( $key ) { + return isset( $this->global_settings [ $key ] ) ? $this->global_settings [ $key ] : self::$default_settings ['globalSettings'] [ $key ]; + } + + /** + * Get an option's value related to a specific blog + * + * @param string $key + * option key + * @param int $blog_id + * blog ID (default: current blog) + * @return mixed + */ + public function get_option( $key, $blog_id = null ) { + if ( $this->check_network_activation() && ! empty( $blog_id ) ) { + return get_blog_option( $blog_id, 'wp-piwik-' . $key ); + } + return isset( $this->settings [ $key ] ) ? $this->settings [ $key ] : self::$default_settings ['settings'] [ $key ]; + } + + /** + * Set a global option's value + * + * @param string $key + * option key + * @param mixed $value + * new option value + */ + public function set_global_option( $key, $value ) { + $this->settings_changed = true; + self::$wp_piwik->log( 'Changed global option ' . $key . ': ' . ( is_array( $value ) ? wp_json_encode( $value ) : $value ) ); + $this->global_settings [ $key ] = $value; + } + + /** + * Set an option's value related to a specific blog + * + * @param string $key + * option key + * @param string $value + * new option value + * @param int $blog_id + * blog ID (default: current blog) + */ + public function set_option( $key, $value, $blog_id = null ) { + if ( empty( $blog_id ) ) { + $blog_id = get_current_blog_id(); + } + $this->settings_changed = true; + self::$wp_piwik->log( 'Changed option ' . $key . ': ' . $value ); + if ( $this->check_network_activation() ) { + update_blog_option( $blog_id, 'wp-piwik-' . $key, $value ); + } + if ( get_current_blog_id() === $blog_id ) { + $this->settings [ $key ] = $value; + } + } + + /** + * Reset settings to default + */ + public function reset_settings() { + self::$wp_piwik->log( 'Reset WP-Piwik settings' ); + global $wpdb; + if ( $this->check_network_activation() ) { + $ary_blogs = self::get_blog_list(); + if ( is_array( $ary_blogs ) ) { + foreach ( $ary_blogs as $ary_blog ) { + switch_to_blog( $ary_blog['blog_id'] ); + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'wp-piwik-%'" ); + restore_current_blog(); + } + } + $wpdb->query( "DELETE FROM $wpdb->sitemeta WHERE meta_key LIKE 'wp-piwik_global-%'" ); + } else { + $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'wp-piwik_global-%'" ); + } + } + + /** + * Get blog list + */ + public static function get_blog_list( $limit = null, $page = null, $search = '' ) { + global $wpdb; + + $query_limit = ''; + if ( $limit && $page ) { + $query_limit = ' LIMIT ' . (int) ( ( $page - 1 ) * $limit ) . ',' . (int) $limit; + } + + $like = '%' . $wpdb->esc_like( $search ) . '%'; + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + return $wpdb->get_results( $wpdb->prepare( 'SELECT blog_id FROM %s WHERE CONCAT(domain, path) LIKE %s AND spam = 0 AND deleted = 0 ORDER BY blog_id' . $query_limit, $wpdb->blogs, $like ), ARRAY_A ); + } + + /** + * Check if plugin is network activated + * + * @return boolean Is network activated? + */ + public function check_network_activation() { + if ( ! function_exists( 'is_plugin_active_for_network' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + return is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ); + } + + /** + * Apply new configuration + * + * @param array $in + * new configuration set + */ + public function apply_changes( $in ) { + if ( ! self::$wp_piwik->is_valid_options_post() ) { + die( 'Invalid config changes.' ); + } + $in = $this->check_settings( $in ); + self::$wp_piwik->log( 'Apply changed settings:' ); + foreach ( self::$default_settings ['globalSettings'] as $key => $val ) { + $this->set_global_option( $key, isset( $in [ $key ] ) ? $in [ $key ] : $val ); + } + foreach ( self::$default_settings ['settings'] as $key => $val ) { + $this->set_option( $key, isset( $in [ $key ] ) ? $in [ $key ] : $val ); + } + $this->set_global_option( 'last_settings_update', (string) time() ); + $this->save(); + } + + /** + * Apply callback function on new settings + * + * @param array $in new configuration set + * @return array configuration set after callback functions were applied + */ + private function check_settings( $in ) { + foreach ( $this->check_settings as $key => $value ) { + if ( isset( $in [ $key ] ) ) { + $in [ $key ] = call_user_func_array( + array( + $this, + $value, + ), + array( + $in [ $key ], + $in, + ) + ); + } + } + return $in; + } + + /** + * Add slash to Piwik URL if necessary + * + * @param string $value + * Piwik URL + * @return string Piwik URL + * @phpstan-ignore method.unused + */ + private function check_piwik_url( $value ) { + return substr( $value, - 1, 1 ) !== '/' ? $value . '/' : $value; + } + + /** + * Remove &token_auth= from auth token + * + * @param string $value + * Piwik auth token + * @return string Piwik auth token + * @phpstan-ignore method.unused + */ + private function check_piwik_token( $value ) { + return str_replace( '&token_auth=', '', $value ); + } + + /** + * Request the site ID (if not set before) + * + * @param string|int $value + * site ID setting value + * @param array $in + * configuration set + * @return int Piwik site ID + * @phpstan-ignore method.unused + */ + private function request_piwik_site_id( $value, $in ) { + if ( $in ['auto_site_config'] && ! $value ) { + return self::$wp_piwik->get_piwik_site_id(); + } + return intval( $value ); + } + + /** + * Prepare the tracking code + * + * @param string $value + * tracking code + * @param array $in + * configuration set + * @return string tracking code + * @phpstan-ignore method.unused + */ + private function prepare_tracking_code( $value, $in ) { + if ( 'manually' === $in['track_mode'] || 'disabled' === $in['track_mode'] ) { + $value = stripslashes( $value ); + if ( $this->check_network_activation() ) { + update_site_option( 'wp-piwik-manually', $value ); + } + return $value; + } + + return ''; + } + + /** + * Prepare the nocscript code + * + * @param string $value + * noscript code + * @param array $in + * configuration set + * @return string noscript code + * @phpstan-ignore method.unused + */ + private function prepare_nocscript_code( $value, $in ) { + if ( 'manually' === $in['track_mode'] ) { + return stripslashes( $value ); + } + return $this->get_option( 'noscript_code' ); + } + + /** + * Get debug data + * + * @return array WP-Piwik settings for debug output + */ + public function get_debug_data() { + $debug = array( + 'global_settings' => $this->global_settings, + 'settings' => $this->settings, + ); + $debug['global_settings']['piwik_token'] = ! empty( $debug['global_settings']['piwik_token'] ) ? 'set' : 'not set'; + return $debug; + } + + public function is_ai_bot_tracking_enabled() { + return (bool) $this->get_global_option( self::TRACK_AI_BOTS ); + } + + public function is_ai_bot_tracking_enabled_via_esi_includes() { + return (bool) $this->get_global_option( self::TRACK_AI_BOTS_USING_ESI ); + } + + public function is_track_via_esi_enabled() { + return true === (bool) $this->get_global_option( 'track_ai_bots_using_esi' ); + } + + public function get_matomo_url() { + if ( 'http' === $this->get_global_option( 'piwik_mode' ) ) { + return $this->get_global_option( 'piwik_url' ); + } + + if ( 'cloud' === $this->get_global_option( 'piwik_mode' ) ) { + return 'https://' . $this->get_global_option( 'piwik_user' ) . '.innocraft.cloud/'; + } + + return 'https://' . $this->get_global_option( 'matomo_user' ) . '.matomo.cloud/'; + } + + public function is_tracking_enabled() { + return 'disabled' !== $this->get_global_option( 'track_mode' ); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Shortcode.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Shortcode.php index c2c9ee52..c90f79ab 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Shortcode.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Shortcode.php @@ -1,28 +1,34 @@ 'OptOut', - 'post' => 'Post', - 'overview' => 'Overview' - ), $content; - - public function __construct($attributes, $wpPiwik, $settings) { - $wpPiwik->log('Check requested shortcode widget '.$attributes['module']); - if (isset($attributes['module']) && isset($this->available[$attributes['module']])) { - $wpPiwik->log('Add shortcode widget '.$this->available[$attributes['module']]); - $class = '\\WP_Piwik\\Widget\\'.$this->available[$attributes['module']]; - $widget = new $class($wpPiwik, $settings, null, null, null, $attributes, true); - $widget->show(); - $this->content = $widget->get(); - } + +namespace WP_Piwik; + +class Shortcode { + + private $available = array( + 'opt-out' => 'OptOut', + 'post' => 'Post', + 'overview' => 'Overview', + ); + + private $content; + + /** + * @param array $attributes + * @param \WP_Piwik $wp_piwik + * @param Settings $settings + */ + public function __construct( $attributes, $wp_piwik, $settings ) { + $wp_piwik->log( 'Check requested shortcode widget ' . $attributes['module'] ); + if ( isset( $attributes['module'] ) && isset( $this->available[ $attributes['module'] ] ) ) { + $wp_piwik->log( 'Add shortcode widget ' . $this->available[ $attributes['module'] ] ); + $class = '\\WP_Piwik\\Widget\\' . $this->available[ $attributes['module'] ]; + $widget = new $class( $wp_piwik, $settings, null, null, null, $attributes, true ); + $widget->show(); + $this->content = $widget->get(); } - - public function get() { - return $this->content; - } - - } \ No newline at end of file + } + + public function get() { + return $this->content; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template.php index 8342a108..d0113b71 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template.php @@ -1,31 +1,45 @@ '.$name.''.$value.''; - } - - public function getRangeLast30() { - $diff = (self::$settings->getGlobalOption('default_date') == 'yesterday') ? -86400 : 0; - $end = time() + $diff; - $start = time() - 2592000 + $diff; - return date('Y-m-d', $start).','.date('Y-m-d', $end); - } - } \ No newline at end of file + } + + public function tab_row( $name, $value ) { + echo '' . esc_html( $name ) . '' . esc_html( $value ) . ''; + } + + public function get_range_last30() { + $diff = ( self::$settings->get_global_option( 'default_date' ) === 'yesterday' ) ? -86400 : 0; + $end = time() + $diff; + $start = time() - 2592000 + $diff; + return gmdate( 'Y-m-d', $start ) . ',' . gmdate( 'Y-m-d', $end ); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template/MetaBoxCustomVars.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template/MetaBoxCustomVars.php index 9f8b5cea..7eb25b2d 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template/MetaBoxCustomVars.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Template/MetaBoxCustomVars.php @@ -1,63 +1,74 @@ + public function addMetabox() { + add_meta_box( + 'wp-piwik_post_customvars', + __( 'Piwik Custom Variables', 'wp-piwik' ), + array( &$this, 'showCustomvars' ), + array( 'post', 'page', 'custom_post_type' ), + 'side', + 'default' + ); + } + + public function showCustomvars( $obj_post, $obj_box ) { + wp_nonce_field( basename( __FILE__ ), 'wp-piwik_post_customvars_nonce' ); ?> - - - - - - - - -
-

. (.)

- + + + + + + + + +

. (.)

+ post_type); - // Check if the current user has permission to edit the post. - if (!current_user_can($objPostType->cap->edit_post, $intID)) - return $intID; - $aryNames = array('cat', 'val'); - for ($i = 1; $i <= 5; $i++) - for ($j = 0; $j <= 1; $j++) { - // Get data - $strMetaVal = (isset($_POST['wp-piwik_custom_'.$aryNames[$j].$i])?htmlentities($_POST['wp-piwik_custom_'.$aryNames[$j].$i]):''); - // Create key - $strMetaKey = 'wp-piwik_custom_'.$aryNames[$j].$i; - // Get the meta value of the custom field key - $strCurVal = get_post_meta($intID, $strMetaKey, true); + // Get post type object + $obj_post_type = get_post_type_object( $obj_post->post_type ); + // Check if the current user has permission to edit the post. + if ( ! current_user_can( $obj_post_type->cap->edit_post, $int_id ) ) { + return $int_id; + } + $ary_names = array( 'cat', 'val' ); + for ( $i = 1; $i <= 5; $i++ ) { + for ( $j = 0; $j <= 1; $j++ ) { + // Create key + $str_meta_key = 'wp-piwik_custom_' . $ary_names[ $j ] . $i; + // Get data + $str_meta_val = isset( $_POST[ $str_meta_key ] ) ? esc_html( $str_meta_key ) : ''; + // Get the meta value of the custom field key + $str_cur_val = get_post_meta( $int_id, $str_meta_key, true ); + if ( $str_meta_val && '' === $str_cur_val ) { // Add meta val: - if ($strMetaVal && '' == $strCurVal) - add_post_meta($intID, $strMetaKey, $strMetaVal, true); + add_post_meta( $int_id, $str_meta_key, $str_meta_val, true ); + } elseif ( $str_meta_val && $str_meta_val !== $str_cur_val ) { // Update meta val: - elseif ($strMetaVal && $strMetaVal != $strCurVal) - update_post_meta($intID, $strMetaKey, $strMetaVal); + update_post_meta( $int_id, $str_meta_key, $str_meta_val ); + } elseif ( '' === $str_meta_val && $str_cur_val ) { // Delete meta val: - elseif (''==$strMetaVal && $strCurVal) - delete_post_meta($intID, $strMetaKey, $strCurVal); + delete_post_meta( $int_id, $str_meta_key, $str_cur_val ); } - } - } \ No newline at end of file + } + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/TrackingCode.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/TrackingCode.php index 94efcc7e..0e575e84 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/TrackingCode.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/TrackingCode.php @@ -1,167 +1,264 @@ -isCurrentTrackingCode () || ! self::$wpPiwik->getOption ( 'tracking_code' ) || strpos( self::$wpPiwik->getOption ( 'tracking_code' ), '{"result":"error",' ) !== false ) - self::$wpPiwik->updateTrackingCode (); - $this->trackingCode = (self::$wpPiwik->isNetworkMode () && self::$wpPiwik->getGlobalOption ( 'track_mode' ) == 'manually') ? get_site_option ( 'wp-piwik-manually' ) : self::$wpPiwik->getOption ( 'tracking_code' ); - } - - public function getTrackingCode() { - if ($this->isUsertracking) - $this->applyUserTracking (); - if ($this->is404) - $this->apply404Changes (); - if ($this->isSearch) - $this->applySearchChanges (); - if (is_single () || is_page()) - $this->addCustomValues (); - $this->trackingCode = apply_filters('wp-piwik_tracking_code', $this->trackingCode); - return $this->trackingCode; - } - - public static function prepareTrackingCode($code, $settings, $logger) { - global $current_user; - $logger->log ( 'Apply tracking code changes:' ); - $settings->setOption ( 'last_tracking_code_update', time () ); - if (preg_match ( '/var u="([^"]*)";/', $code, $hits )) { - $fetchedProxyUrl = $hits [1]; - } else $fetchedProxyUrl = ''; - if ($settings->getGlobalOption ( 'remove_type_attribute')) { - $code = str_replace ( - array( ' type="text/javascript"', " type='text/javascript'" ), - '', - $code - ); - } - if ($settings->getGlobalOption ( 'track_mode' ) == 'js') - $code = str_replace ( array ( - 'piwik.js', - 'piwik.php', - 'matomo.js', - 'matomo.php' - ), 'js/index.php', $code ); - elseif ($settings->getGlobalOption ( 'track_mode' ) == 'proxy') { - $code = str_replace ( 'piwik.js', 'matomo.php', $code ); - $code = str_replace ( 'matomo.js', 'matomo.php', $code ); - $code = str_replace ( 'piwik.php', 'matomo.php', $code ); - $proxy = str_replace ( array ( - 'https://', - 'http://' - ), '//', plugins_url ( 'wp-piwik' ) . '/proxy' ) . '/'; - $code = preg_replace ( '/var u="([^"]*)";/', 'var u="' . $proxy . '"', $code ); - $code = preg_replace ( '/img src="([^"]*)piwik.php/', 'img src="' . $proxy . 'matomo.php', $code ); - $code = preg_replace ( '/img src="([^"]*)matomo.php/', 'img src="' . $proxy . 'matomo.php', $code ); - } - if ($settings->getGlobalOption ( 'track_cdnurl' ) || $settings->getGlobalOption ( 'track_cdnurlssl' )) - $code = str_replace ( array ( - "var d=doc", - "g.src=u+" - ), array ( - "var ucdn=(('https:' == document.location.protocol) ? 'https://" . ($settings->getGlobalOption ( 'track_cdnurlssl' ) ? $settings->getGlobalOption ( 'track_cdnurlssl' ) : $settings->getGlobalOption ( 'track_cdnurl' )) . "/' : 'http://" . ($settings->getGlobalOption ( 'track_cdnurl' ) ? $settings->getGlobalOption ( 'track_cdnurl' ) : $settings->getGlobalOption ( 'track_cdnurlssl' )) . "/');\nvar d=doc", - "g.src=ucdn+" - ), $code ); - - if ($settings->getGlobalOption ( 'track_datacfasync' )) - $code = str_replace ( ' - page_id = $page_id; + $this->context = $context; + $this->priority = $priority; + if ( self::$settings->check_network_activation() && function_exists( 'is_super_admin' ) && is_super_admin() && isset( $_GET ['wpmu_show_stats'] ) ) { + switch_to_blog( (int) $_GET ['wpmu_show_stats'] ); + $this->blog_id = get_current_blog_id(); + restore_current_blog(); + } + $this->is_shortcode = $is_shortcode; + $prefix = ( 'dashboard' === $this->page_id ? self::$settings->get_global_option( 'plugin_display_name' ) . ' - ' : '' ); + $this->configure( $prefix, $params ); + if ( is_array( $this->method ) ) { + foreach ( $this->method as $method ) { + $this->api_id [ $method ] = Request::register( $method, $this->parameter ); + self::$wp_piwik->log( 'Register request: ' . $this->api_id [ $method ] ); + } + } else { + $this->api_id [ $this->method ] = Request::register( $this->method, $this->parameter ); + self::$wp_piwik->log( 'Register request: ' . $this->api_id [ $this->method ] ); + } + if ( $this->is_shortcode ) { + return; + } + add_meta_box( + $this->get_name(), + $this->title, + array( + $this, + 'show', + ), + $page_id, + $this->context, + $this->priority + ); + } + + /** + * Conifguration dummy method + * + * @param string $prefix + * metabox title prefix (default: empty) + * @param array $params + * widget parameters (default: empty array) + */ + protected function configure( $prefix = '', $params = array() ) { + } + + /** + * Default show widget method, handles default Piwik output + */ + public function show() { + $response = self::$wp_piwik->request( $this->api_id [ $this->method ] ); + if ( ! empty( $response ['result'] ) && 'error' === $response['result'] ) { + $this->out( '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ) ); + } else { + if ( isset( $response [0] ['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; + } + $table_head = array( + 'label' => $this->name, + ); + $table_head [ $unique ] = __( 'Unique', 'wp-piwik' ); + if ( isset( $response [0] ['nb_visits'] ) ) { + $table_head ['nb_visits'] = __( 'Visits', 'wp-piwik' ); + } + if ( isset( $response [0] ['nb_hits'] ) ) { + $table_head ['nb_hits'] = __( 'Hits', 'wp-piwik' ); + } + if ( isset( $response [0] ['nb_actions'] ) ) { + $table_head ['nb_actions'] = __( 'Actions', 'wp-piwik' ); + } + $table_body = array(); + $count = 0; + if ( is_array( $response ) ) { + foreach ( $response as $row_key => $row ) { + ++$count; + $table_body[ $row_key ] = array(); + foreach ( $table_head as $key => $value ) { + $table_body[ $row_key ] [] = isset( $row[ $key ] ) ? $row[ $key ] : '-'; + } + if ( 10 === $count ) { + break; + } + } + } + $this->table( $table_head, $table_body, null ); + } + } + + /** + * Display or store shortcode output + */ + protected function out( $output ) { + if ( $this->is_shortcode ) { + $this->output .= $output; + } else { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $output; + } + } + + /** + * Return shortcode output + */ + public function get() { + return $this->output; + } + + /** + * Display a HTML table + * + * @param array|null $thead + * table header content (array of cells) + * @param array $tbody + * table body content (array of rows) + * @param array|null $tfoot + * table footer content (array of cells) + * @param string|false $css_class + * CSS class name to apply on table sections + * @param array $java_script + * array of javascript code to apply on body rows + * @param array $css_classes + * array mapping keys in $tbody to css classes to apply on table rows. + */ + protected function table( $thead, $tbody = array(), $tfoot = array(), $css_class = false, $java_script = array(), $css_classes = array() ) { + $this->out( '
' ); + if ( $this->is_shortcode && $this->title ) { + $colspan = ! empty( $tbody ) ? count( $tbody[0] ) : 2; + $this->out( '' ); + } + if ( ! empty( $thead ) ) { + $this->tab_head( $thead, $css_class ); + } + if ( ! empty( $tbody ) ) { + $this->tab_body( $tbody, $css_class, $java_script, $css_classes ); + } else { + $this->out( '' ); + } + if ( ! empty( $tfoot ) ) { + $this->tab_foot( $tfoot, $css_class ); + } + $this->out( '
' . esc_html( $this->title ) . '
' . esc_html__( 'No data available.', 'wp-piwik' ) . '
' ); + } + + /** + * Display a HTML table header + * + * @param array $thead + * array of cells. + * @param string|false $css_class + * CSS class to apply + */ + private function tab_head( $thead, $css_class = false ) { + $this->out( '' ); + $count = 0; + foreach ( $thead as $value ) { + $this->out( '' . esc_html( $value ) . '' ); + } + $this->out( '' ); + } + + /** + * Display a HTML table body + * + * @param array $tbody + * array of rows, each row containing an array of cells + * @param string $css_class + * CSS class to apply + * @param array $java_script + * array of javascript code to apply (one item per row) + */ + private function tab_body( $tbody, $css_class = '', $java_script = array(), $css_classes = array() ) { + $this->out( '' ); + foreach ( $tbody as $key => $trow ) { + $this->tab_row( $trow, isset( $java_script [ $key ] ) ? $java_script [ $key ] : '', isset( $css_classes [ $key ] ) ? $css_classes [ $key ] : '' ); + } + $this->out( '' ); + } + + /** + * Display a HTML table footer + * + * @param array $tfoot + * array of cells + * @param string|false $css_class + * CSS class to apply + */ + private function tab_foot( $tfoot, $css_class = false ) { + $this->out( '' ); + $count = 0; + foreach ( $tfoot as $value ) { + // $value is allowed to contain html + $this->out( '' . $value . '' ); + } + $this->out( '' ); + } + + /** + * Display a HTML table row + * + * @param array $trow + * array of cells + * @param string $java_script + * javascript code to apply + */ + private function tab_row( $trow, $java_script = '', $css_class = '' ) { + $this->out( '' ); + $count = 0; + foreach ( $trow as $tcell ) { + $this->out( '' . esc_html( $tcell ) . '' ); + } + $this->out( '' ); + } + + /** + * Get the current request's Piwik time settings + * + * @return array time settings: period => Piwik period, date => requested date, description => time description to show in widget title + */ + protected function get_time_settings() { + switch ( self::$settings->get_global_option( 'default_date' ) ) { + case 'today': + $period = 'day'; + $date = 'today'; + $description = __( 'today', 'wp-piwik' ); + break; + case 'current_month': + $period = 'month'; + $date = 'today'; + $description = __( 'current month', 'wp-piwik' ); + break; + case 'last_month': + $period = 'month'; + $date = gmdate( 'Y-m-d', strtotime( 'last day of previous month' ) ); + $description = __( 'last month', 'wp-piwik' ); + break; + case 'current_week': + $period = 'week'; + $date = 'today'; + $description = __( 'current week', 'wp-piwik' ); + break; + case 'last_week': + $period = 'week'; + $date = gmdate( 'Y-m-d', strtotime( '-1 week' ) ); + $description = __( 'last week', 'wp-piwik' ); + break; + case 'yesterday': + default: + $period = 'day'; + $date = 'yesterday'; + $description = __( 'yesterday', 'wp-piwik' ); + break; + } + + if ( isset( $_GET['date'] ) ) { + $date = intval( wp_unslash( $_GET['date'] ) ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $description = $this->date_format( wp_unslash( $_GET['date'] ), $period ); + } + + return array( + 'period' => $period, + 'date' => $date, + 'description' => $description, + ); + } + + /** + * Format a date to show in widget + * + * @param string $date + * date string + * @param string $period + * Piwik period + * @return string formatted date + */ + protected function date_format( $date, $period = 'day' ) { + $prefix = ''; + switch ( $period ) { + case 'week': + $prefix = __( 'week', 'wp-piwik' ) . ' '; + $format = 'W/Y'; + break; + case 'short_week': + $format = 'W'; + break; + case 'month': + $format = 'F Y'; + $date = gmdate( 'Y-m-d', strtotime( $date ) ); + break; + default: + $format = get_option( 'date_format' ); + } + return $prefix . date_i18n( $format, strtotime( $date ) ); + } + + /** + * Format time to show in widget + * + * @param int $time + * time in seconds + * @return string formatted time + */ + protected function time_format( $time ) { + return floor( $time / 3600 ) . 'h ' . floor( ( $time % 3600 ) / 60 ) . 'm ' . floor( ( $time % 3600 ) % 60 ) . 's'; + } + + /** + * Convert Piwik range into meaningful text + * + * @return string range description + */ + public function range_name() { + switch ( $this->parameter ['date'] ) { + case 'last90': + return __( 'last 90 days', 'wp-piwik' ); + case 'last60': + return __( 'last 60 days', 'wp-piwik' ); + case 'last30': + return __( 'last 30 days', 'wp-piwik' ); + case 'last12': + switch ( $this->parameter['period'] ) { + case 'day': + return __( 'last 12 days', 'wp-piwik' ); + case 'week': + return __( 'last 12 weeks', 'wp-piwik' ); + case 'month': + return __( 'last 12 months', 'wp-piwik' ); + case 'year': + return __( 'last 12 years', 'wp-piwik' ); + default: + return __( 'last 12', 'wp-piwik' ) . $this->parameter['period']; + } + default: + return $this->parameter ['date']; + } + } + + /** + * Get the widget name + * + * @return string widget name + */ + public function get_name() { + return str_replace( '\\', '-', get_called_class() ); + } + + /** + * Display a pie chart + * + * @param array $data chart data array(array(0 => name, 1 => value)) + */ + public function pie_chart( $data ) { + $labels = array(); + $values = array(); + foreach ( $data as $key => $data_set ) { + $labels[] = $data_set[0]; + $values[] = $data_set[1]; + if ( 'Others' === $key ) { + break; + } + } + ?> +
+ +
+ + getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Browser Details', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicesDetection.getBrowserVersions'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Browser', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); + public $class_name = __CLASS__; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Browser Details', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getBrowserVersions'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Browser', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Browsers.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Browsers.php index 46f8f832..67586ae1 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Browsers.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Browsers.php @@ -1,73 +1,83 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Browsers', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicesDetection.getBrowsers'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} + public $class_name = __CLASS__; - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Browser', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); - elseif ($count == $this->limit) { - $class['Others'] = $js['Others'] = ''; - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Browsers', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getBrowsers'; + $this->context = 'normal'; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Browser', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } elseif ( $count === $this->limit ) { + $css_class['Others'] = ''; + $js['Others'] = ''; + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Chart.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Chart.php index 946ba4de..ddca703d 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Chart.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Chart.php @@ -4,85 +4,97 @@ namespace WP_Piwik\Widget; use WP_Piwik\Widget; -class Chart extends Visitors -{ +class Chart extends Visitors { - public $className = __CLASS__; + public $class_name = __CLASS__; - public function show() - { - $result = $this->requestData(); - $response = $result["response"]; - if (!$result["success"]) { - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response[$method]['message'], ENT_QUOTES, 'utf-8'); - } else { - $values = $labels = $bounced = $unique = ''; - $count = $uniqueSum = 0; - if (is_array($response['VisitsSummary.getVisits'])) - foreach ($response['VisitsSummary.getVisits'] as $date => $value) { - $count++; - $values .= $value . ','; - $unique .= $response['VisitsSummary.getUniqueVisitors'][$date] . ','; - $bounced .= $response['VisitsSummary.getBounceCount'][$date] . ','; - if ($this->parameter['period'] == 'week') { - preg_match("/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/", $date, $dateList); - $textKey = $this->dateFormat($dateList[0], 'short_week'); - } else $textKey = substr($date, -2); - $labels .= '["' . $textKey . '"],'; - $uniqueSum += $response['VisitsSummary.getActions'][$date]; - } - else { - $values = '0,'; - $labels = '[0,"-"],'; - $unique = '0,'; - $bounced = '0,'; - } - $average = round($uniqueSum / 30, 0); - $values = substr($values, 0, -1); - $unique = substr($unique, 0, -1); - $labels = substr($labels, 0, -1); - $bounced = substr($bounced, 0, -1); - ?> -
- -
- - request_data(); + $response = $result['response']; + if ( ! $result['success'] ) { + $message = ''; + if ( is_array( $this->method ) ) { + foreach ( $this->method as $m ) { + if ( empty( $response[ $m ]['message'] ) ) { + continue; + } + $message .= ' ' . $m . ' - ' . $response[ $m ]['message']; + } + } else { + $message = $response[ $this->method ]['message']; + } + + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $message ); + } else { + $values = array(); + $labels = array(); + $bounced = array(); + $unique = array(); + $count = 0; + $unique_sum = 0; + if ( is_array( $response['VisitsSummary.getVisits'] ) ) { + foreach ( $response['VisitsSummary.getVisits'] as $date => $value ) { + ++$count; + $values [] = $value; + $unique [] = $response['VisitsSummary.getUniqueVisitors'][ $date ]; + $bounced [] = $response['VisitsSummary.getBounceCount'][ $date ]; + if ( 'week' === $this->parameter['period'] ) { + preg_match( '/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $date, $date_list ); + $text_key = $this->date_format( $date_list[0], 'short_week' ); + } else { + $text_key = substr( $date, -2 ); + } + $labels [] = array( $text_key ); + $unique_sum += $response['VisitsSummary.getActions'][ $date ]; + } + } else { + $values = array( 0 ); + $labels = array( array( 0, '-' ) ); + $unique = array( 0 ); + $bounced = array( 0 ); + } + $average = round( $unique_sum / 30, 0 ); + ?> +
+ +
+ + getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Cities', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'UserCountry.getCity'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('City', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); - elseif ($count == $this->limit) { - $class['Others'] = $js['Others'] = ''; - } + public $class_name = __CLASS__; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Cities', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'UserCountry.getCity'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'City', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } elseif ( $count === $this->limit ) { + $css_class['Others'] = ''; + $js['Others'] = ''; + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Country.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Country.php index 19d65288..40d381a9 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Country.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Country.php @@ -4,70 +4,80 @@ use WP_Piwik\Widget; - class Country extends Widget { - - public $className = __CLASS__; +class Country extends Widget { - protected function configure($prefix = '', $params = array()) { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Countries', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'UserCountry.getCountry '; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Country', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); - elseif ($count == $this->limit) { - $class['Others'] = $js['Others'] = ''; - } + public $class_name = __CLASS__; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Countries', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'UserCountry.getCountry '; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Country', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } elseif ( $count === $this->limit ) { + $css_class['Others'] = ''; + $js['Others'] = ''; + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Ecommerce.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Ecommerce.php index 2251315b..31bee8a1 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Ecommerce.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Ecommerce.php @@ -2,50 +2,52 @@ namespace WP_Piwik\Widget; -class Ecommerce extends \WP_Piwik\Widget -{ +class Ecommerce extends \WP_Piwik\Widget { - public $className = __CLASS__; - protected function configure($prefix = '', $params = array()) - { - $timeSettings = $this->getTimeSettings(); - $this->title = $prefix . __('E-Commerce', 'wp-piwik'); - $this->method = 'Goals.get'; - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - } + public $class_name = __CLASS__; - public function show() - { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] = 'error') - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = null; - $revenue = is_float($this->value($response, 'revenue')) ? number_format($this->value($response, 'revenue'), 2) : ""; - $revenue_new = is_float($this->value($response, 'revenue_new_visit')) ? number_format($this->value($response, 'revenue_new_visit'), 2) : ""; - $revenue_return = is_float($this->value($response, 'revenue_returning_visit')) ? number_format($this->value($response, 'revenue_returning_visit'), 2) : ""; - $tableBody = array( - array(__('Conversions', 'wp-piwik') . ':', $this->value($response, 'nb_conversions')), - array(__('Visits converted', 'wp-piwik') . ':', $this->value($response, 'nb_visits_converted')), - array(__('Revenue', 'wp-piwik') . ':', $revenue), - array(__('Conversion rate', 'wp-piwik') . ':', $this->value($response, 'conversion_rate')), - array(__('Conversions (new visitor)', 'wp-piwik') . ':', $this->value($response, 'nb_conversions_new_visit')), - array(__('Visits converted (new visitor)', 'wp-piwik') . ':', $this->value($response, 'nb_visits_converted_new_visit')), - array(__('Revenue (new visitor)', 'wp-piwik') . ':', $revenue_new), - array(__('Conversion rate (new visitor)', 'wp-piwik') . ':', $this->value($response, 'conversion_rate_new_visit')), - array(__('Conversions (returning visitor)', 'wp-piwik') . ':', $this->value($response, 'nb_conversions_returning_visit')), - array(__('Visits converted (returning visitor)', 'wp-piwik') . ':', $this->value($response, 'nb_visits_converted_returning_visit')), - array(__('Revenue (returning visitor)', 'wp-piwik') . ':', $revenue_return), - array(__('Conversion rate (returning visitor)', 'wp-piwik') . ':', $this->value($response, 'conversion_rate_returning_visit')), - ); - $tableFoot = (self::$settings->getGlobalOption('piwik_shortcut') ? array(__('Shortcut', 'wp-piwik') . ':', 'Piwik' . (isset($aryConf['inline']) && $aryConf['inline'] ? ' - WP-Piwik' : '')) : null); - $this->table($tableHead, $tableBody, $tableFoot); - } - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->title = $prefix . __( 'E-Commerce', 'wp-piwik' ); + $this->method = 'Goals.get'; + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + } -} \ No newline at end of file + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = null; + $revenue = is_numeric( $this->value( $response, 'revenue' ) ) ? number_format( (float) $this->value( $response, 'revenue' ), 2 ) : ''; + $revenue_new = is_numeric( $this->value( $response, 'revenue_new_visit' ) ) ? number_format( (float) $this->value( $response, 'revenue_new_visit' ), 2 ) : ''; + $revenue_return = is_numeric( $this->value( $response, 'revenue_returning_visit' ) ) ? number_format( (float) $this->value( $response, 'revenue_returning_visit' ), 2 ) : ''; + $table_body = array( + array( __( 'Conversions', 'wp-piwik' ) . ':', $this->value( $response, 'nb_conversions' ) ), + array( __( 'Visits converted', 'wp-piwik' ) . ':', $this->value( $response, 'nb_visits_converted' ) ), + array( __( 'Revenue', 'wp-piwik' ) . ':', $revenue ), + array( __( 'Conversion rate', 'wp-piwik' ) . ':', $this->value( $response, 'conversion_rate' ) ), + array( __( 'Conversions (new visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'nb_conversions_new_visit' ) ), + array( __( 'Visits converted (new visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'nb_visits_converted_new_visit' ) ), + array( __( 'Revenue (new visitor)', 'wp-piwik' ) . ':', $revenue_new ), + array( __( 'Conversion rate (new visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'conversion_rate_new_visit' ) ), + array( __( 'Conversions (returning visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'nb_conversions_returning_visit' ) ), + array( __( 'Visits converted (returning visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'nb_visits_converted_returning_visit' ) ), + array( __( 'Revenue (returning visitor)', 'wp-piwik' ) . ':', $revenue_return ), + array( __( 'Conversion rate (returning visitor)', 'wp-piwik' ) . ':', $this->value( $response, 'conversion_rate_returning_visit' ) ), + ); + $table_foot = self::$settings->get_global_option( 'piwik_shortcut' ) + ? array( + esc_html__( 'Shortcut', 'wp-piwik' ) . ':', + 'Piwik', + ) + : null; + $this->table( $table_head, $table_body, $table_foot ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Items.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Items.php index e339cacc..ab2357ac 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Items.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Items.php @@ -1,52 +1,55 @@ getTimeSettings(); - $this->title = $prefix.__('E-Commerce Items', 'wp-piwik'); - $this->method = 'Goals.getItemsName'; - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array( - __('Label', 'wp-piwik'), - __('Revenue', 'wp-piwik'), - __('Quantity', 'wp-piwik'), - __('Orders', 'wp-piwik'), - __('Avg. price', 'wp-piwik'), - __('Avg. quantity', 'wp-piwik'), - __('Conversion rate', 'wp-piwik'), - ); - $tableBody = array(); - if (is_array($response)) - foreach ($response as $data) { - array_push($tableBody, array( - $data['label'], - number_format($data['revenue'],2), - $data['quantity'], - $data['orders'], - number_format($data['avg_price'],2), - $data['avg_quantity'], - $data['conversion_rate'] - )); - } - $tableFoot = array(); - $this->table($tableHead, $tableBody, $tableFoot); + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->title = $prefix . __( 'E-Commerce Items', 'wp-piwik' ); + $this->method = 'Goals.getItemsName'; + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( + __( 'Label', 'wp-piwik' ), + __( 'Revenue', 'wp-piwik' ), + __( 'Quantity', 'wp-piwik' ), + __( 'Orders', 'wp-piwik' ), + __( 'Avg. price', 'wp-piwik' ), + __( 'Avg. quantity', 'wp-piwik' ), + __( 'Conversion rate', 'wp-piwik' ), + ); + $table_body = array(); + if ( is_array( $response ) ) { + foreach ( $response as $data ) { + array_push( + $table_body, + array( + $data['label'], + number_format( $data['revenue'], 2 ), + $data['quantity'], + $data['orders'], + number_format( $data['avg_price'], 2 ), + $data['avg_quantity'], + $data['conversion_rate'], + ) + ); + } } + $table_foot = array(); + $this->table( $table_head, $table_body, $table_foot ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/ItemsCategory.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/ItemsCategory.php index 2ed58ae4..77ad976b 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/ItemsCategory.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/ItemsCategory.php @@ -4,49 +4,52 @@ namespace WP_Piwik\Widget; class ItemsCategory extends \WP_Piwik\Widget { - public $className = __CLASS__; + public $class_name = __CLASS__; - protected function configure($prefix = '', $params = array()) { - $timeSettings = $this->getTimeSettings(); - $this->title = $prefix.__('E-Commerce Item Categories', 'wp-piwik'); - $this->method = 'Goals.getItemsCategory'; - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->title = $prefix . __( 'E-Commerce Item Categories', 'wp-piwik' ); + $this->method = 'Goals.getItemsCategory'; + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + } - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array( - __('Label', 'wp-piwik'), - __('Revenue', 'wp-piwik'), - __('Quantity', 'wp-piwik'), - __('Orders', 'wp-piwik'), - __('Avg. price', 'wp-piwik'), - __('Avg. quantity', 'wp-piwik'), - __('Conversion rate', 'wp-piwik'), - ); - $tableBody = array(); - if (is_array($response)) - foreach ($response as $data) { - array_push($tableBody, array( - $data['label'], - isset($data['revenue'])?number_format($data['revenue'],2):"-.--", - isset($data['quantity'])?$data['quantity']:'-', - isset($data['orders'])?$data['orders']:'-', - number_format($data['avg_price'],2), - $data['avg_quantity'], - $data['conversion_rate'] - )); - } - $tableFoot = array(); - $this->table($tableHead, $tableBody, $tableFoot); - } - } - -} \ No newline at end of file + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( + __( 'Label', 'wp-piwik' ), + __( 'Revenue', 'wp-piwik' ), + __( 'Quantity', 'wp-piwik' ), + __( 'Orders', 'wp-piwik' ), + __( 'Avg. price', 'wp-piwik' ), + __( 'Avg. quantity', 'wp-piwik' ), + __( 'Conversion rate', 'wp-piwik' ), + ); + $table_body = array(); + if ( is_array( $response ) ) { + foreach ( $response as $data ) { + array_push( + $table_body, + array( + $data['label'], + isset( $data['revenue'] ) ? number_format( $data['revenue'], 2 ) : '-.--', + isset( $data['quantity'] ) ? $data['quantity'] : '-', + isset( $data['orders'] ) ? $data['orders'] : '-', + number_format( $data['avg_price'], 2 ), + $data['avg_quantity'], + $data['conversion_rate'], + ) + ); + } + } + $table_foot = array(); + $this->table( $table_head, $table_body, $table_foot ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Keywords.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Keywords.php index bb50c0c1..beff7afa 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Keywords.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Keywords.php @@ -1,21 +1,20 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Keywords', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Referrers.getKeywords'; - $this->name = 'Keyword'; - } + public $class_name = __CLASS__; - } \ No newline at end of file + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Keywords', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Referrers.getKeywords'; + $this->name = 'Keyword'; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Models.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Models.php index 1b9d988f..66c2a021 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Models.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Models.php @@ -1,73 +1,83 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Models', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicesDetection.getModel'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Model', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array(htmlentities($row['label']), $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); - elseif ($count == $this->limit) { - $class['Others'] = $js['Others'] = ''; - } + public $class_name = __CLASS__; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Models', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getModel'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Model', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( htmlentities( $row['label'] ), $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } elseif ( $count === $this->limit ) { + $css_class['Others'] = ''; + $js['Others'] = ''; + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Noresult.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Noresult.php index 11b7c31d..24a99264 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Noresult.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Noresult.php @@ -2,37 +2,42 @@ namespace WP_Piwik\Widget; - class Noresult extends \WP_Piwik\Widget { - - public $className = __CLASS__; +class Noresult extends \WP_Piwik\Widget { - protected function configure($prefix = '', $params = array()) { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Site Search', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Actions.getSiteSearchNoResultKeywords'; - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Keyword', 'wp-piwik'), __('Requests', 'wp-piwik'), __('Bounced', 'wp-piwik')); - $tableBody = array(); - $count = 0; - if (is_array($response)) - foreach ($response as $row) { - $count++; - $tableBody[] = array($row['label'], $row['nb_visits'], $row['bounce_rate']); - if ($count == 10) break; - } - $this->table($tableHead, $tableBody, null); + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Site Search', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Actions.getSiteSearchNoResultKeywords'; + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( + ! empty( $response['result'] ) + && 'error' === $response['result'] + ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Keyword', 'wp-piwik' ), __( 'Requests', 'wp-piwik' ), __( 'Bounced', 'wp-piwik' ) ); + $table_body = array(); + $count = 0; + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $table_body[] = array( $row['label'], $row['nb_visits'], $row['bounce_rate'] ); + if ( 10 === $count ) { + break; + } + } } + $this->table( $table_head, $table_body, null ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/OptOut.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/OptOut.php index c67b1cbe..c518ef19 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/OptOut.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/OptOut.php @@ -2,37 +2,34 @@ namespace WP_Piwik\Widget; -class OptOut extends \WP_Piwik\Widget -{ +class OptOut extends \WP_Piwik\Widget { - public $className = __CLASS__; + public $class_name = __CLASS__; - protected function configure($prefix = '', $params = array()) - { - $this->parameter = $params; - } + protected function configure( $prefix = '', $params = array() ) { + $this->parameter = $params; + } - public function show() - { - $protocol = (isset ($_SERVER ['HTTPS']) && $_SERVER ['HTTPS'] != 'off') ? 'https' : 'http'; - switch (self::$settings->getGlobalOption('piwik_mode')) { - case 'php' : - $PIWIK_URL = $protocol . ':' . self::$settings->getGlobalOption('proxy_url'); - break; - case 'cloud' : - $PIWIK_URL = 'https://' . self::$settings->getGlobalOption('piwik_user') . '.innocraft.cloud/'; - break; - case 'cloud-matomo': - $PIWIK_URL = 'https://' . self::$settings->getGlobalOption('matomo_user') . '.matomo.cloud/'; - break; - default : - $PIWIK_URL = self::$settings->getGlobalOption('piwik_url'); - } - $width = (isset($this->parameter['width']) ? esc_attr($this->parameter['width']) : ''); - $height = (isset($this->parameter['height']) ? esc_attr($this->parameter['height']) : ''); - $idSite = (isset($this->parameter['idsite']) ? 'idsite=' . (int)$this->parameter['idsite'] . '&' : ''); - $language = (isset($this->parameter['language']) ? esc_attr($this->parameter['language']) : 'en'); - $this->out(''); - } - -} \ No newline at end of file + public function show() { + $protocol = ( isset( $_SERVER ['HTTPS'] ) && 'off' !== $_SERVER ['HTTPS'] ) ? 'https' : 'http'; + switch ( self::$settings->get_global_option( 'piwik_mode' ) ) { + case 'php': + $piwik_url = $protocol . ':' . self::$settings->get_global_option( 'proxy_url' ); + break; + case 'cloud': + $piwik_url = 'https://' . self::$settings->get_global_option( 'piwik_user' ) . '.innocraft.cloud/'; + break; + case 'cloud-matomo': + $piwik_url = 'https://' . self::$settings->get_global_option( 'matomo_user' ) . '.matomo.cloud/'; + break; + default: + $piwik_url = self::$settings->get_global_option( 'piwik_url' ); + break; + } + $width = ( isset( $this->parameter['width'] ) ? rawurlencode( $this->parameter['width'] ) : '' ); + $height = ( isset( $this->parameter['height'] ) ? rawurlencode( $this->parameter['height'] ) : '' ); + $idsite = ( isset( $this->parameter['idsite'] ) ? 'idsite=' . (int) $this->parameter['idsite'] . '&' : '' ); + $language = ( isset( $this->parameter['language'] ) ? rawurlencode( $this->parameter['language'] ) : 'en' ); + $this->out( '' ); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Overview.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Overview.php index 115bdd0a..c893dc1d 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Overview.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Overview.php @@ -2,65 +2,76 @@ namespace WP_Piwik\Widget; -class Overview extends \WP_Piwik\Widget -{ +class Overview extends \WP_Piwik\Widget { - public $className = __CLASS__; + public $class_name = __CLASS__; - protected function configure($prefix = '', $params = array()) - { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => isset($params['period']) ? $params['period'] : $timeSettings['period'], - 'date' => isset($params['date']) ? $params['date'] : $timeSettings['date'], - 'description' => $timeSettings['description'] - ); - $this->title = !$this->isShortcode ? $prefix . __('Overview', 'wp-piwik') . ' (' . __($this->pageId == 'dashboard' ? $this->rangeName() : $timeSettings['description'], 'wp-piwik') . ')' : ($params['title'] ? $params['title'] : ''); - $this->method = 'VisitsSummary.get'; - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => isset( $params['period'] ) ? $params['period'] : $time_settings['period'], + 'date' => isset( $params['date'] ) ? $params['date'] : $time_settings['date'], + 'description' => $time_settings['description'], + ); + $this->title = ! $this->is_shortcode ? $prefix . __( 'Overview', 'wp-piwik' ) . ' (' . ( 'dashboard' === $this->page_id ? $this->range_name() : $time_settings['description'] ) . ')' : ( $params['title'] ? $params['title'] : '' ); + $this->method = 'VisitsSummary.get'; + } - public function show() - { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] = 'error') - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - if (in_array($this->parameter['date'], array('last30', 'last60', 'last90'))) { - $result = array(); - if (is_array($response)) { - foreach ($response as $data) - foreach ($data as $key => $value) - if (isset($result[$key]) && is_numeric($value)) - $result[$key] += $value; - elseif (is_numeric($value)) - $result[$key] = $value; - else - $result[$key] = 0; - if (isset($result['nb_visits']) && $result['nb_visits'] > 0) { - $result['nb_actions_per_visit'] = round($result['nb_actions'] / $result['nb_visits'], 1); - $result['bounce_rate'] = round($result['bounce_count'] / $result['nb_visits'] * 100, 1) . '%'; - $result['avg_time_on_site'] = round($result['sum_visit_length'] / $result['nb_visits'], 0); - } else $result['nb_actions_per_visit'] = $result['bounce_rate'] = $result['avg_time_on_site'] = 0; - } - $response = $result; - } - $time = isset($response['sum_visit_length']) ? $this->timeFormat($response['sum_visit_length']) : '-'; - $avgTime = isset($response['avg_time_on_site']) ? $this->timeFormat($response['avg_time_on_site']) : '-'; - $tableHead = null; - $tableBody = array(array(__('Visitors', 'wp-piwik') . ':', $this->value($response, 'nb_visits'))); - if ($this->value($response, 'nb_uniq_visitors') != '-') - array_push($tableBody, array(__('Unique visitors', 'wp-piwik') . ':', $this->value($response, 'nb_uniq_visitors'))); - array_push($tableBody, - array(__('Page views', 'wp-piwik') . ':', $this->value($response, 'nb_actions') . ' (Ø ' . $this->value($response, 'nb_actions_per_visit') . ')'), - array(__('Total time spent', 'wp-piwik') . ':', $time . ' (Ø ' . $avgTime . ')'), - array(__('Bounce count', 'wp-piwik') . ':', $this->value($response, 'bounce_count') . ' (' . $this->value($response, 'bounce_rate') . ')') - ); - if (!in_array($this->parameter['date'], array('last30', 'last60', 'last90'))) - array_push($tableBody, array(__('Time/visit', 'wp-piwik') . ':', $avgTime), array(__('Max. page views in one visit', 'wp-piwik') . ':', $this->value($response, 'max_actions'))); - $tableFoot = (self::$settings->getGlobalOption('piwik_shortcut') ? array(__('Shortcut', 'wp-piwik') . ':', 'Matomo' . (isset($aryConf['inline']) && $aryConf['inline'] ? ' - WP-Piwik' : '')) : null); - $this->table($tableHead, $tableBody, $tableFoot); - } - } - -} \ No newline at end of file + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + if ( in_array( $this->parameter['date'], array( 'last30', 'last60', 'last90' ), true ) ) { + $result = array(); + if ( is_array( $response ) ) { + foreach ( $response as $data ) { + foreach ( $data as $key => $value ) { + if ( isset( $result[ $key ] ) && is_numeric( $value ) ) { + $result[ $key ] += $value; + } elseif ( is_numeric( $value ) ) { + $result[ $key ] = $value; + } else { + $result[ $key ] = 0; + } + } + } + if ( isset( $result['nb_visits'] ) && $result['nb_visits'] > 0 ) { + $result['nb_actions_per_visit'] = round( $result['nb_actions'] / $result['nb_visits'], 1 ); + $result['bounce_rate'] = round( $result['bounce_count'] / $result['nb_visits'] * 100, 1 ) . '%'; + $result['avg_time_on_site'] = round( $result['sum_visit_length'] / $result['nb_visits'], 0 ); + } else { + $result['nb_actions_per_visit'] = 0; + $result['bounce_rate'] = 0; + $result['avg_time_on_site'] = 0; + } + } + $response = $result; + } + $time = isset( $response['sum_visit_length'] ) ? $this->time_format( $response['sum_visit_length'] ) : '-'; + $avg_time = isset( $response['avg_time_on_site'] ) ? $this->time_format( $response['avg_time_on_site'] ) : '-'; + $table_head = null; + $table_body = array( array( __( 'Visitors', 'wp-piwik' ) . ':', $this->value( $response, 'nb_visits' ) ) ); + if ( '-' !== $this->value( $response, 'nb_uniq_visitors' ) ) { + array_push( $table_body, array( __( 'Unique visitors', 'wp-piwik' ) . ':', $this->value( $response, 'nb_uniq_visitors' ) ) ); + } + array_push( + $table_body, + array( __( 'Page views', 'wp-piwik' ) . ':', $this->value( $response, 'nb_actions' ) . ' (Ø ' . $this->value( $response, 'nb_actions_per_visit' ) . ')' ), + array( __( 'Total time spent', 'wp-piwik' ) . ':', $time . ' (Ø ' . $avg_time . ')' ), + array( __( 'Bounce count', 'wp-piwik' ) . ':', $this->value( $response, 'bounce_count' ) . ' (' . $this->value( $response, 'bounce_rate' ) . ')' ) + ); + if ( ! in_array( $this->parameter['date'], array( 'last30', 'last60', 'last90' ), true ) ) { + array_push( $table_body, array( __( 'Time/visit', 'wp-piwik' ) . ':', $avg_time ), array( __( 'Max. page views in one visit', 'wp-piwik' ) . ':', $this->value( $response, 'max_actions' ) ) ); + } + $table_foot = self::$settings->get_global_option( 'piwik_shortcut' ) + ? array( + esc_html__( 'Shortcut', 'wp-piwik' ) . ':', + 'Matomo', + ) + : null; + $this->table( $table_head, $table_body, $table_foot ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Pages.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Pages.php index 0b16ea5f..d5745e2f 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Pages.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Pages.php @@ -1,21 +1,20 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Pages', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Actions.getPageTitles'; - $this->name = __('Page', 'wp-piwik' ); - } - - } \ No newline at end of file + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Pages', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Actions.getPageTitles'; + $this->name = __( 'Page', 'wp-piwik' ); + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Plugins.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Plugins.php index a5c7b92e..0f6193a0 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Plugins.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Plugins.php @@ -1,38 +1,40 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Plugins', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicePlugins.getPlugin'; - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Plugin', 'wp-piwik'), __('Visits', 'wp-piwik'), __('Percent', 'wp-piwik')); - $tableBody = array(); - $count = 0; - if (is_array($response)) - foreach ($response as $row) { - $count++; - $tableBody[] = array($row['label'], $row['nb_visits'], $row['nb_visits_percentage']); - if ($count == 10) break; - } - $this->table($tableHead, $tableBody, null); + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Plugins', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicePlugins.getPlugin'; + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Plugin', 'wp-piwik' ), __( 'Visits', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + $table_body = array(); + $count = 0; + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $table_body[] = array( $row['label'], $row['nb_visits'], $row['nb_visits_percentage'] ); + if ( 10 === $count ) { + break; + } + } } + $this->table( $table_head, $table_body, null ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Post.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Post.php index 82238101..7f59cd48 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Post.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Post.php @@ -1,82 +1,95 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => isset($params['period']) ? $params['period'] : $timeSettings['period'], - 'date' => isset($params['date']) ? $params['date'] : $timeSettings['date'], - 'key' => isset($params['key'])?$params['key']:null, - 'pageUrl' => isset($params['url'])?$params['url']:urlencode(get_permalink($post->ID)), - 'description' => $timeSettings['description'] - ); - $this->title = $prefix.__('Overview', 'wp-piwik').' ('.__($this->parameter['date'],'wp-piwik').')'; - $this->method = 'Actions.getPageUrl'; - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] = 'error') - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - if (in_array($this->parameter['date'], array('last30', 'last60', 'last90'))) { - $result = array(); - if (is_array($response)) { - foreach ($response as $data) { - if (isset($data[0])) { - foreach ($data[0] as $key => $value) - if (isset($result[$key]) && is_numeric($value)) - $result[$key] += $value; - elseif (is_numeric($value)) - $result[$key] = $value; - else - $result[$key] = 0; - } - } - if (isset($result['nb_visits']) && $result['nb_visits'] > 0) { - $result['nb_actions_per_visit'] = round((isset( $result['nb_actions'] ) ? $result['nb_actions'] : 0) / $result['nb_visits'], 1); - $result['bounce_rate'] = round((isset($result['bounce_count']) ? $result['bounce_count'] : 0) / $result['nb_visits'] * 100, 1) . '%'; - $result['avg_time_on_site'] = round((isset($result['sum_visit_length']) ? $result['sum_visit_length'] : 0) / $result['nb_visits'], 0); - } else $result['nb_actions_per_visit'] = $result['bounce_rate'] = $result['avg_time_on_site'] = 0; - } - $response = $result; - } else { - if (isset($response[0])) - $response = $response[0]; - if ($this->parameter['key']) { - $this->out(isset($response[$this->parameter['key']])?$response[$this->parameter['key']]:'not defined'); - return; - } - } - $time = isset($response['sum_visit_length']) ? $this->timeFormat($response['sum_visit_length']) : '-'; - $avgTime = isset($response['avg_time_on_site']) ? $this->timeFormat($response['avg_time_on_site']) : '-'; - $tableHead = null; - $tableBody = array(array(__('Visitors', 'wp-piwik') . ':', $this->value($response, 'nb_visits'))); - if ($this->value($response, 'nb_uniq_visitors') != '-') - array_push($tableBody, array(__('Unique visitors', 'wp-piwik') . ':', $this->value($response, 'nb_uniq_visitors'))); - elseif ($this->value($response, 'sum_daily_nb_uniq_visitors') != '-') { - array_push($tableBody, __('Unique visitors', 'wp-piwik') . ':', $this->value($response, 'sum_daily_nb_uniq_visitors')); - } - array_push($tableBody, - array(__('Page views', 'wp-piwik').':', $this->value($response, 'nb_hits').' (Ø '.$this->value($response, 'entry_nb_actions').')'), - array(__('Total time spent', 'wp-piwik').':', $time), - array(__('Bounce count', 'wp-piwik').':', $this->value($response, 'entry_bounce_count').' ('.$this->value($response, 'bounce_rate').')'), - array(__('Time/visit', 'wp-piwik').':', $avgTime), - array(__('Min. generation time', 'wp-piwik').':', $this->value($response, 'min_time_generation')), - array(__('Max. generation time', 'wp-piwik').':', $this->value($response, 'max_time_generation')) - ); - if (!in_array($this->parameter['date'], array('last30', 'last60', 'last90'))) - array_push($tableBody, array(__('Time/visit', 'wp-piwik') . ':', $avgTime), array(__('Max. page views in one visit', 'wp-piwik') . ':', $this->value($response, 'max_actions'))); - $tableFoot = (self::$settings->getGlobalOption('piwik_shortcut') ? array(__('Shortcut', 'wp-piwik') . ':', 'Piwik' . (isset($aryConf['inline']) && $aryConf['inline'] ? ' - WP-Piwik' : '')) : null); - $this->table($tableHead, $tableBody, $tableFoot); - } - } - + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + global $post; + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => isset( $params['period'] ) ? $params['period'] : $time_settings['period'], + 'date' => isset( $params['date'] ) ? $params['date'] : $time_settings['date'], + 'key' => isset( $params['key'] ) ? $params['key'] : null, + 'pageUrl' => isset( $params['url'] ) ? $params['url'] : get_permalink( $post->ID ), + 'description' => $time_settings['description'], + ); + $this->title = $prefix . esc_html__( 'Overview', 'wp-piwik' ) . ' (' . $this->parameter['date'] . ')'; + $this->method = 'Actions.getPageUrl'; } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + if ( in_array( $this->parameter['date'], array( 'last30', 'last60', 'last90' ), true ) ) { + $result = array(); + if ( is_array( $response ) ) { + foreach ( $response as $data ) { + if ( isset( $data[0] ) ) { + foreach ( $data[0] as $key => $value ) { + if ( isset( $result[ $key ] ) && is_numeric( $value ) ) { + $result[ $key ] += $value; + } elseif ( is_numeric( $value ) ) { + $result[ $key ] = $value; + } else { + $result[ $key ] = 0; + } + } + } + } + if ( isset( $result['nb_visits'] ) && $result['nb_visits'] > 0 ) { + $result['nb_actions_per_visit'] = round( ( isset( $result['nb_actions'] ) ? $result['nb_actions'] : 0 ) / $result['nb_visits'], 1 ); + $result['bounce_rate'] = round( ( isset( $result['bounce_count'] ) ? $result['bounce_count'] : 0 ) / $result['nb_visits'] * 100, 1 ) . '%'; + $result['avg_time_on_site'] = round( ( isset( $result['sum_visit_length'] ) ? $result['sum_visit_length'] : 0 ) / $result['nb_visits'], 0 ); + } else { + $result['nb_actions_per_visit'] = 0; + $result['bounce_rate'] = 0; + $result['avg_time_on_site'] = 0; + } + } + $response = $result; + } else { + if ( isset( $response[0] ) ) { + $response = $response[0]; + } + if ( $this->parameter['key'] ) { + $this->out( isset( $response[ $this->parameter['key'] ] ) ? esc_html( $response[ $this->parameter['key'] ] ) : 'not defined' ); + return; + } + } + $time = isset( $response['sum_visit_length'] ) ? $this->time_format( $response['sum_visit_length'] ) : '-'; + $avg_time = isset( $response['avg_time_on_site'] ) ? $this->time_format( $response['avg_time_on_site'] ) : '-'; + $table_head = null; + $table_body = array( array( __( 'Visitors', 'wp-piwik' ) . ':', $this->value( $response, 'nb_visits' ) ) ); + if ( '-' !== $this->value( $response, 'nb_uniq_visitors' ) ) { + array_push( $table_body, array( __( 'Unique visitors', 'wp-piwik' ) . ':', $this->value( $response, 'nb_uniq_visitors' ) ) ); + } elseif ( '-' !== $this->value( $response, 'sum_daily_nb_uniq_visitors' ) ) { + array_push( $table_body, __( 'Unique visitors', 'wp-piwik' ) . ':', $this->value( $response, 'sum_daily_nb_uniq_visitors' ) ); + } + array_push( + $table_body, + array( __( 'Page views', 'wp-piwik' ) . ':', $this->value( $response, 'nb_hits' ) . ' (Ø ' . $this->value( $response, 'entry_nb_actions' ) . ')' ), + array( __( 'Total time spent', 'wp-piwik' ) . ':', $time ), + array( __( 'Bounce count', 'wp-piwik' ) . ':', $this->value( $response, 'entry_bounce_count' ) . ' (' . $this->value( $response, 'bounce_rate' ) . ')' ), + array( __( 'Time/visit', 'wp-piwik' ) . ':', $avg_time ), + array( __( 'Min. generation time', 'wp-piwik' ) . ':', $this->value( $response, 'min_time_generation' ) ), + array( __( 'Max. generation time', 'wp-piwik' ) . ':', $this->value( $response, 'max_time_generation' ) ) + ); + if ( ! in_array( $this->parameter['date'], array( 'last30', 'last60', 'last90' ), true ) ) { + array_push( $table_body, array( __( 'Time/visit', 'wp-piwik' ) . ':', $avg_time ), array( __( 'Max. page views in one visit', 'wp-piwik' ) . ':', $this->value( $response, 'max_actions' ) ) ); + } + $table_foot = self::$settings->get_global_option( 'piwik_shortcut' ) + ? array( + esc_html__( 'Shortcut', 'wp-piwik' ) . ':', + 'Piwik', + ) + : null; + $this->table( $table_head, $table_body, $table_foot ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Referrers.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Referrers.php index 51c88824..148c6d35 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Referrers.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Referrers.php @@ -2,20 +2,19 @@ namespace WP_Piwik\Widget; - class Referrers extends \WP_Piwik\Widget { - - public $className = __CLASS__; +class Referrers extends \WP_Piwik\Widget { - protected function configure($prefix = '', $params = array()) { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Referrers', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Referrers.getWebsites'; - $this->name = 'Referrer'; - } - - } \ No newline at end of file + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Referrers', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Referrers.getWebsites'; + $this->name = 'Referrer'; + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Screens.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Screens.php index 3aae02eb..1762ad8a 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Screens.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Screens.php @@ -1,68 +1,77 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Resolutions', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Resolution.getResolution'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Resolution', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); + public $class_name = __CLASS__; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Resolutions', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Resolution.getResolution'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Resolution', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Search.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Search.php index 2cddf7ee..b6f0bbdf 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Search.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Search.php @@ -1,38 +1,40 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Site Search', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'Actions.getSiteSearchKeywords'; - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Keyword', 'wp-piwik'), __('Requests', 'wp-piwik'), __('Bounced', 'wp-piwik')); - $tableBody = array(); - $count = 0; - if (is_array($response)) - foreach ($response as $row) { - $count++; - $tableBody[] = array(htmlentities($row['label']), $row['nb_visits'], $row['bounce_rate']); - if ($count == 10) break; - } - $this->table($tableHead, $tableBody, null); - } - } - + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Site Search', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'Actions.getSiteSearchKeywords'; } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Keyword', 'wp-piwik' ), __( 'Requests', 'wp-piwik' ), __( 'Bounced', 'wp-piwik' ) ); + $table_body = array(); + $count = 0; + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $table_body[] = array( $row['label'], $row['nb_visits'], $row['bounce_rate'] ); + if ( 10 === $count ) { + break; + } + } + } + $this->table( $table_head, $table_body, null ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Seo.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Seo.php index a5522253..c2709290 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Seo.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Seo.php @@ -1,31 +1,33 @@ parameter = array( - 'url' => get_bloginfo('url') - ); - $this->title = $prefix.__('SEO', 'wp-piwik'); - $this->method = 'SEO.getRank'; - } - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - echo '
'; - if (is_array($response)) - foreach ($response as $val) - echo ''; - else echo ''; - echo '
'.(isset($val['logo_link']) && !empty($val['logo_link'])?''.$val['label'].'':$val['label']).''.$val['rank'].'
SEO module currently not available.
'; + public $class_name = __CLASS__; + + protected function configure( $prefix = '', $params = array() ) { + $this->parameter = array( + 'url' => get_bloginfo( 'url' ), + ); + $this->title = $prefix . __( 'SEO', 'wp-piwik' ); + $this->method = 'SEO.getRank'; + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + echo '
'; + if ( is_array( $response ) ) { + foreach ( $response as $val ) { + echo ''; + } + } else { + echo ''; } + echo '
' . ( isset( $val['logo_link'] ) && ! empty( $val['logo_link'] ) ? '' . esc_html( $val['label'] ) . '' : esc_html( $val['label'] ) ) . '' . esc_html( $val['rank'] ) . '
SEO module currently not available.
'; } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/SystemDetails.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/SystemDetails.php index 7403bf68..15f62d42 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/SystemDetails.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/SystemDetails.php @@ -1,66 +1,76 @@ getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Operation System Details', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicesDetection.getOsVersions'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Operation System', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); +class SystemDetails extends \WP_Piwik\Widget { - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Operation System Details', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getOsVersions'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Operation System', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Systems.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Systems.php index 5b952a90..00dbe8d0 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Systems.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Systems.php @@ -2,65 +2,75 @@ namespace WP_Piwik\Widget; - class Systems extends \WP_Piwik\Widget { - - protected function configure($prefix = '', $params = array()) { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix.__('Operation Systems', 'wp-piwik').' ('.__($timeSettings['description'],'wp-piwik').')'; - $this->method = 'DevicesDetection.getOsFamilies'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL().'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script ( 'wp-piwik-chartjs', self::$wpPiwik->getPluginURL () . 'js/chartjs/chart.min.js', "3.4.1" ); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL().'css/wp-piwik.css',array(),self::$wpPiwik->getPluginVersion()); -} - - public function show() { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] ='error') - echo ''.__('Piwik error', 'wp-piwik').': '.htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Operation System', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) $unique = 'nb_uniq_visitors'; - else $unique = 'sum_daily_nb_uniq_visitors'; - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $count++; - $sum += isset($row[$unique])?$row[$unique]:0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$unique], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$unique]; - $tableBody[$row['label']] = array($row['label'], $row[$unique], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j'."( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) - $tableBody['Others'][0] = __('Others', 'wp-piwik'); +class Systems extends \WP_Piwik\Widget { - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1]/$sum*100, 2).'%'; - - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Operation Systems', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getOsFamilies'; + $this->context = 'normal'; + + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } + + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Operation System', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + ++$count; + $sum += isset( $row[ $unique ] ) ? $row[ $unique ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $unique ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $unique ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $unique ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } + + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + + $this->table( $table_head, $table_body, null, false, $js, $css_class ); } - - } \ No newline at end of file + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Types.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Types.php index 9a16f0ba..cbe56f51 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Types.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Types.php @@ -4,77 +4,81 @@ namespace WP_Piwik\Widget; use WP_Piwik\Widget; -class Types extends Widget -{ +class Types extends Widget { - public $className = __CLASS__; - protected function configure($prefix = '', $params = array()) - { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => $timeSettings['period'], - 'date' => $timeSettings['date'] - ); - $this->title = $prefix . __('Types', 'wp-piwik') . ' (' . __($timeSettings['description'], 'wp-piwik') . ')'; - $this->method = 'DevicesDetection.getType'; - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL() . 'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script('wp-piwik-chartjs', self::$wpPiwik->getPluginURL() . 'js/chartjs/chart.min.js', "3.4.1"); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL() . 'css/wp-piwik.css', array(), self::$wpPiwik->getPluginVersion()); - } + public $class_name = __CLASS__; - public function show() - { - $response = self::$wpPiwik->request($this->apiID[$this->method]); - $tableBody = array(); - if (!empty($response['result']) && $response['result'] = 'error') - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response['message'], ENT_QUOTES, 'utf-8'); - else { - $tableHead = array(__('Type', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Percent', 'wp-piwik')); - if (isset($response[0]['nb_uniq_visitors'])) { - $unique = 'nb_uniq_visitors'; - } else { - $unique = 'sum_daily_nb_uniq_visitors'; - } - $count = 0; - $sum = 0; - $js = array(); - $class = array(); - if (is_array($response)) - foreach ($response as $row) { - $key = isset($row[$unique]) ? $unique : "nb_visits"; - $count++; - $sum += isset($row[$key]) ? $row[$key] : 0; - if ($count < $this->limit) - $tableBody[$row['label']] = array($row['label'], $row[$key], 0); - elseif (!isset($tableBody['Others'])) { - $tableBody['Others'] = array($row['label'], $row[$key], 0); - $class['Others'] = 'wp-piwik-hideDetails'; - $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - $tableBody[$row['label']] = array($row['label'], $row[$key], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } else { - $tableBody['Others'][1] += $row[$key]; - $tableBody[$row['label']] = array($row['label'], $row[$key], 0); - $class[$row['label']] = 'wp-piwik-hideDetails hidden'; - $js[$row['label']] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; - } - } - if ($count > $this->limit) { - $tableBody['Others'][0] = __('Others', 'wp-piwik'); - } elseif ($count == $this->limit) { - $class['Others'] = $js['Others'] = ''; - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => $time_settings['period'], + 'date' => $time_settings['date'], + ); + $this->title = $prefix . __( 'Types', 'wp-piwik' ) . ' (' . $time_settings['description'] . ')'; + $this->method = 'DevicesDetection.getType'; + $this->context = 'normal'; - foreach ($tableBody as $key => $row) - $tableBody[$key][2] = number_format($row[1] / $sum * 100, 2) . '%'; + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } - if (!empty($tableBody)) $this->pieChart($tableBody); - $this->table($tableHead, $tableBody, null, false, $js, $class); - } - } + public function show() { + $response = self::$wp_piwik->request( $this->api_id[ $this->method ] ); + $table_body = array(); + if ( ! empty( $response['result'] ) && 'error' === $response['result'] ) { + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $response['message'] ); + } else { + $table_head = array( __( 'Type', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Percent', 'wp-piwik' ) ); + if ( isset( $response[0]['nb_uniq_visitors'] ) ) { + $unique = 'nb_uniq_visitors'; + } else { + $unique = 'sum_daily_nb_uniq_visitors'; + } + $count = 0; + $sum = 0; + $js = array(); + $css_class = array(); + if ( is_array( $response ) ) { + foreach ( $response as $row ) { + $key = isset( $row[ $unique ] ) ? $unique : 'nb_visits'; + ++$count; + $sum += isset( $row[ $key ] ) ? $row[ $key ] : 0; + if ( $count < $this->limit ) { + $table_body[ $row['label'] ] = array( $row['label'], $row[ $key ], 0 ); + } elseif ( ! isset( $table_body['Others'] ) ) { + $table_body['Others'] = array( $row['label'], $row[ $key ], 0 ); + $css_class['Others'] = 'wp-piwik-hideDetails'; + $js['Others'] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $key ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } else { + $table_body['Others'][1] += $row[ $key ]; + $table_body[ $row['label'] ] = array( $row['label'], $row[ $key ], 0 ); + $css_class[ $row['label'] ] = 'wp-piwik-hideDetails hidden'; + $js[ $row['label'] ] = '$j' . "( '.wp-piwik-hideDetails' ).toggle( 'hidden' );"; + } + } + } + if ( $count > $this->limit ) { + $table_body['Others'][0] = __( 'Others', 'wp-piwik' ); + } elseif ( $count === $this->limit ) { + $css_class['Others'] = ''; + $js['Others'] = ''; + } -} \ No newline at end of file + foreach ( $table_body as $key => $row ) { + $table_body[ $key ][2] = number_format( $row[1] / $sum * 100, 2 ) . '%'; + } + + if ( ! empty( $table_body ) ) { + $this->pie_chart( $table_body ); + } + $this->table( $table_head, $table_body, null, false, $js, $css_class ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Visitors.php b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Visitors.php index 26cbd117..0c320f1c 100644 --- a/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Visitors.php +++ b/wp-content/plugins/wp-piwik/classes/WP_Piwik/Widget/Visitors.php @@ -4,76 +4,97 @@ namespace WP_Piwik\Widget; use WP_Piwik\Widget; -class Visitors extends Widget -{ +class Visitors extends Widget { - public $className = __CLASS__; + public $class_name = __CLASS__; - protected function configure($prefix = '', $params = array()) - { - $timeSettings = $this->getTimeSettings(); - $this->parameter = array( - 'idSite' => self::$wpPiwik->getPiwikSiteId($this->blogId), - 'period' => isset($params['period']) ? $params['period'] : $timeSettings['period'], - 'date' => 'last' . ($timeSettings['period'] == 'day' ? '30' : '12'), - 'limit' => null - ); - $this->title = $prefix . __('Visitors', 'wp-piwik') . ' (' . __($this->rangeName(), 'wp-piwik') . ')'; - $this->method = array('VisitsSummary.getVisits', 'VisitsSummary.getUniqueVisitors', 'VisitsSummary.getBounceCount', 'VisitsSummary.getActions'); - $this->context = 'normal'; - wp_enqueue_script('wp-piwik', self::$wpPiwik->getPluginURL() . 'js/wp-piwik.js', array(), self::$wpPiwik->getPluginVersion(), true); - wp_enqueue_script('wp-piwik-chartjs', self::$wpPiwik->getPluginURL() . 'js/chartjs/chart.min.js', "3.4.1"); - wp_enqueue_style('wp-piwik', self::$wpPiwik->getPluginURL() . 'css/wp-piwik.css', array(), self::$wpPiwik->getPluginVersion()); - } + protected function configure( $prefix = '', $params = array() ) { + $time_settings = $this->get_time_settings(); + $this->parameter = array( + 'idSite' => self::$wp_piwik->get_piwik_site_id( $this->blog_id ), + 'period' => isset( $params['period'] ) ? $params['period'] : $time_settings['period'], + 'date' => 'last' . ( 'day' === $time_settings['period'] ? '30' : '12' ), + 'limit' => null, + ); + $this->title = $prefix . __( 'Visitors', 'wp-piwik' ) . ' (' . $this->range_name() . ')'; + $this->method = array( 'VisitsSummary.getVisits', 'VisitsSummary.getUniqueVisitors', 'VisitsSummary.getBounceCount', 'VisitsSummary.getActions' ); + $this->context = 'normal'; - public function requestData() - { - $response = array(); - $success = true; - foreach ($this->method as $method) { - $response[$method] = self::$wpPiwik->request($this->apiID[$method]); - if (!empty($response[$method]['result']) && $response[$method]['result'] = 'error') - $success = false; - } - return array("response" => $response, "success" => $success); - } + $version = self::$wp_piwik->get_plugin_version(); + wp_enqueue_script( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'js/wp-piwik.js', array(), $version, true ); + wp_enqueue_script( 'wp-piwik-chartjs', self::$wp_piwik->get_plugin_url() . 'js/chartjs/chart.min.js', array(), $version, false ); + wp_enqueue_style( 'wp-piwik', self::$wp_piwik->get_plugin_url() . 'css/wp-piwik.css', array(), $version ); + } - public function show() - { - $result = $this->requestData(); - $response = $result["response"]; - if (!$result["success"]) { - echo '' . __('Piwik error', 'wp-piwik') . ': ' . htmlentities($response[$method]['message'], ENT_QUOTES, 'utf-8'); - } else { - $data = array(); - if (is_array($response) && is_array($response['VisitsSummary.getVisits'])) - foreach ($response['VisitsSummary.getVisits'] as $key => $value) { - if ($this->parameter['period'] == 'week') { - preg_match("/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/", $key, $dateList); - $jsKey = $dateList[0]; - $textKey = $this->dateFormat($jsKey, 'week'); - } elseif ($this->parameter['period'] == 'month') { - $jsKey = $key . '-01'; - $textKey = $key; - } else $jsKey = $textKey = $key; - $data[] = array( - $textKey, - $value, - $response['VisitsSummary.getUniqueVisitors'][$key] ? $response['VisitsSummary.getUniqueVisitors'][$key] : '-', - $response['VisitsSummary.getBounceCount'][$key] ? $response['VisitsSummary.getBounceCount'][$key] : '-', - $response['VisitsSummary.getActions'][$key] ? $response['VisitsSummary.getActions'][$key] : '-' - ); - $javaScript[] = 'javascript:wp_piwik_datelink(\'' . urlencode('wp-piwik_stats') . '\',\'' . str_replace('-', '', $jsKey) . '\',\'' . (isset($_GET['wpmu_show_stats']) ? (int)$_GET['wpmu_show_stats'] : '') . '\');'; - } - $this->table( - array(__('Date', 'wp-piwik'), __('Visits', 'wp-piwik'), __('Unique', 'wp-piwik'), __('Bounced', 'wp-piwik'), __('Page Views', 'wp-piwik')), - array_reverse($data), - array(), - 'clickable', - array_reverse(isset($javaScript) ? $javaScript : []) - ); - } + public function request_data() { + $response = array(); + $success = true; + foreach ( $this->method as $method ) { + $response[ $method ] = self::$wp_piwik->request( $this->api_id[ $method ] ); + if ( ! empty( $response[ $method ]['result'] ) && 'error' === $response[ $method ]['result'] ) { + $success = false; + } + } + return array( + 'response' => $response, + 'success' => $success, + ); + } - } + public function show() { + $result = $this->request_data(); + $response = $result['response']; + if ( ! $result['success'] ) { + $message = ''; + if ( is_array( $this->method ) ) { + foreach ( $this->method as $m ) { + if ( empty( $response[ $m ]['message'] ) ) { + continue; + } -} \ No newline at end of file + $message .= ' ' . $m . ' - ' . $response[ $m ]['message']; + } + } else { + $message = $response[ $this->method ]['message']; + } + + echo '' . esc_html__( 'Piwik error', 'wp-piwik' ) . ': ' . esc_html( $message ); + } else { + $data = array(); + if ( is_array( $response ) && is_array( $response['VisitsSummary.getVisits'] ) ) { + foreach ( $response['VisitsSummary.getVisits'] as $key => $value ) { + if ( 'week' === $this->parameter['period'] ) { + preg_match( '/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $key, $date_list ); + $js_key = $date_list[0]; + $text_key = $this->date_format( $js_key, 'week' ); + } elseif ( 'month' === $this->parameter['period'] ) { + $js_key = $key . '-01'; + $text_key = $key; + } else { + $js_key = $key; + $text_key = $key; + } + $data[] = array( + $text_key, + $value, + $response['VisitsSummary.getUniqueVisitors'][ $key ] ? $response['VisitsSummary.getUniqueVisitors'][ $key ] : '-', + $response['VisitsSummary.getBounceCount'][ $key ] ? $response['VisitsSummary.getBounceCount'][ $key ] : '-', + $response['VisitsSummary.getActions'][ $key ] ? $response['VisitsSummary.getActions'][ $key ] : '-', + ); + $java_script[] = 'javascript:wp_piwik_datelink(' + . wp_json_encode( rawurlencode( 'wp-piwik_stats' ) ) . ',' + . wp_json_encode( str_replace( '-', '', $js_key ) ) . ',' + . wp_json_encode( isset( $_GET['wpmu_show_stats'] ) ? (int) $_GET['wpmu_show_stats'] : '' ) + . ');'; + } + } + $this->table( + array( __( 'Date', 'wp-piwik' ), __( 'Visits', 'wp-piwik' ), __( 'Unique', 'wp-piwik' ), __( 'Bounced', 'wp-piwik' ), __( 'Page Views', 'wp-piwik' ) ), + array_reverse( $data ), + array(), + 'clickable', + array_reverse( isset( $java_script ) ? $java_script : array() ) + ); + } + } +} diff --git a/wp-content/plugins/wp-piwik/composer.json b/wp-content/plugins/wp-piwik/composer.json new file mode 100644 index 00000000..c2d808fc --- /dev/null +++ b/wp-content/plugins/wp-piwik/composer.json @@ -0,0 +1,20 @@ +{ + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.2", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "squizlabs/php_codesniffer": "^3.13", + "wp-coding-standards/wpcs": "^3.3", + "friendsofphp/php-cs-fixer": "^3.94" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "szepeviktor/phpstan-wordpress": "^2.0", + "phpstan/extension-installer": "^1.4" + } +} diff --git a/wp-content/plugins/wp-piwik/composer.lock b/wp-content/plugins/wp-piwik/composer.lock new file mode 100644 index 00000000..3c2dc066 --- /dev/null +++ b/wp-content/plugins/wp-piwik/composer.lock @@ -0,0 +1,3529 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "78837cf0ee62370f920471ab6c92f1da", + "packages": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1", + "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.2", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "^2.2", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "opensource@frenck.dev", + "homepage": "https://frenck.dev", + "role": "Open source developer" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-11T04:32:07+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.94.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7.1", + "infection/infection": "^0.32.3", + "justinrainbow/json-schema": "^6.6.4", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz RumiÅ„ski", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-02-20T16:13:53+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "9.3.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2019-12-27T09:44:58+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-paragonie", + "version": "1.3.4", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "paragonie/random_compat": "dev-master", + "paragonie/sodium_compat": "dev-master" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "paragonie", + "phpcs", + "polyfill", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" + }, + { + "name": "phpcompatibility/phpcompatibility-wp", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "shasum": "" + }, + "require": { + "phpcompatibility/php-compatibility": "^9.0", + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "lead" + } + ], + "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.", + "homepage": "http://phpcompatibility.com/", + "keywords": [ + "compatibility", + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "b598aa890815b8df16363271b659d73280129101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101", + "reference": "b598aa890815b8df16363271b659d73280129101", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.2.0", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-12T23:06:57+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcsstandards/phpcsdevcs": "^1.2.0", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "phpcs4", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-12-08T14:27:58+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "49257c96304c508223815ee965c251e7c79e614e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/49257c96304c508223815ee965c251e7c79e614e", + "reference": "49257c96304c508223815ee965c251e7c79e614e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:31:08+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "99d7e101826e6610606b9433248f80c1997cd20b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99d7e101826e6610606b9433248f80c1997cd20b", + "reference": "99d7e101826e6610606b9433248f80c1997cd20b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T11:13:48+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/01ffe0411b842f93c571e5c391f289c3fdd498c3", + "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-24T17:51:06+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9590e86be1d1c57bfbb16d0dd040345378c20896", + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-28T15:16:37+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.30", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "reference": "eeaa8cabe54c7b3516938c72a4a161c0cc80a34f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.4.30" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-12T13:06:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.33", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e", + "reference": "c46e854e79b52d07666e43924a20cb6dc546644e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.33" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-23T16:02:12+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.4.24", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b67e94e06a05d9572c2fa354483b3e13e3cb1898", + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v6.4.24" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:14:14+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.4.34" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-08T20:44:54+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", + "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=7.2", + "phpcsstandards/phpcsextra": "^1.5.0", + "phpcsstandards/phpcsutils": "^1.1.0", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2025-11-25T12:08:04+00:00" + } + ], + "packages-dev": [ + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.9.1", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/f12220f303e0d7c0844c0e5e957b0c3cee48d2f7", + "reference": "f12220f303e0d7c0844c0e5e957b0c3cee48d2f7", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.3", + "phpdocumentor/reflection-docblock": "^6.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "symfony/polyfill-php80": "*", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.1" + }, + "time": "2026-02-03T19:29:21+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.42", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-03-17T14:58:32+00:00" + }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/aa722f037b2d034828cd6c55ebe9e5c74961927e", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "php-stubs/wordpress-stubs": "^6.6.2", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "composer/semver": "^3.4", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.3" + }, + "time": "2025-09-14T02:58:22+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/wp-content/plugins/wp-piwik/config.php b/wp-content/plugins/wp-piwik/config.php index 7e460314..461ef615 100644 --- a/wp-content/plugins/wp-piwik/config.php +++ b/wp-content/plugins/wp-piwik/config.php @@ -1,9 +1,11 @@ idSite = $idSite; + $this->urlReferrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; + $this->pageUrl = self::getCurrentUrl(); + $this->ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; + $this->acceptLanguage = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : false; + $this->userAgent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : false; + $this->setClientHints( + !empty($_SERVER['HTTP_SEC_CH_UA_MODEL']) ? $_SERVER['HTTP_SEC_CH_UA_MODEL'] : '', + !empty($_SERVER['HTTP_SEC_CH_UA_PLATFORM']) ? $_SERVER['HTTP_SEC_CH_UA_PLATFORM'] : '', + !empty($_SERVER['HTTP_SEC_CH_UA_PLATFORM_VERSION']) ? $_SERVER['HTTP_SEC_CH_UA_PLATFORM_VERSION'] : '', + !empty($_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST']) ? $_SERVER['HTTP_SEC_CH_UA_FULL_VERSION_LIST'] : '', + !empty($_SERVER['HTTP_SEC_CH_UA_FULL_VERSION']) ? $_SERVER['HTTP_SEC_CH_UA_FULL_VERSION'] : '' + ); + if (!empty($apiUrl)) { + self::$URL = $apiUrl; + } + + $this->setNewVisitorId(); + + $this->currentTs = time(); + $this->createTs = $this->currentTs; + + $this->visitorCustomVar = $this->getCustomVariablesFromCookie(); + } + + public function setApiUrl(string $url): void + { + self::$URL = $url; + } + + /** + * By default, Matomo expects utf-8 encoded values, for example + * for the page URL parameter values, Page Title, etc. + * It is recommended to only send UTF-8 data to Matomo. + * If required though, you can also specify another charset using this function. + * + * @return $this + */ + public function setPageCharset(string $charset = '') + { + $this->pageCharset = $charset; + + return $this; + } + + /** + * Sets the current URL being tracked + * + * @param string $url Raw URL (not URL encoded) + * @return $this + */ + public function setUrl(string $url) + { + $this->pageUrl = $url; + + return $this; + } + + /** + * Sets the URL referrer used to track Referrers details for new visits. + * + * @param string $url Raw URL (not URL encoded) + * @return $this + */ + public function setUrlReferrer(string $url) + { + $this->urlReferrer = $url; + + return $this; + } + + /** + * This method is deprecated and does nothing. It used to set the time that it took to generate the document on the server side. + * + * @param int $timeMs Generation time in ms + * @return $this + * + * @deprecated this metric is deprecated please use performance timings instead + * @see setPerformanceTimings + */ + public function setGenerationTime(int $timeMs) + { + return $this; + } + + /** + * Sets timings for various browser performance metrics. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming + * + * @param null|int $network Network time in ms (connectEnd – fetchStart) + * @param null|int $server Server time in ms (responseStart – requestStart) + * @param null|int $transfer Transfer time in ms (responseEnd – responseStart) + * @param null|int $domProcessing DOM Processing to Interactive time in ms (domInteractive – domLoading) + * @param null|int $domCompletion DOM Interactive to Complete time in ms (domComplete – domInteractive) + * @param null|int $onload Onload time in ms (loadEventEnd – loadEventStart) + * @return $this + */ + public function setPerformanceTimings( + ?int $network = null, + ?int $server = null, + ?int $transfer = null, + ?int $domProcessing = null, + ?int $domCompletion = null, + ?int $onload = null + ) { + $this->networkTime = $network; + $this->serverTime = $server; + $this->transferTime = $transfer; + $this->domProcessingTime = $domProcessing; + $this->domCompletionTime = $domCompletion; + $this->onLoadTime = $onload; + + return $this; + } + + /** + * Clear / reset all previously set performance metrics. + */ + public function clearPerformanceTimings(): void + { + $this->networkTime = false; + $this->serverTime = false; + $this->transferTime = false; + $this->domProcessingTime = false; + $this->domCompletionTime = false; + $this->onLoadTime = false; + } + + /** + * @deprecated + * @ignore + */ + public function setUrlReferer(string $url) + { + $this->setUrlReferrer($url); + + return $this; + } + + /** + * Sets the attribution information to the visit, so that subsequent Goal conversions are + * properly attributed to the right Referrer URL, timestamp, Campaign Name & Keyword. + * + * This must be a JSON encoded string that would typically be fetched from the JS API: + * matomoTracker.getAttributionInfo() and that you have JSON encoded via JSON2.stringify() + * + * If you call enableCookies() then these referral attribution values will be set + * to the 'ref' first party cookie storing referral information. + * + * @param string $jsonEncoded JSON encoded array containing Attribution info + * @return $this + * @throws Exception + * @see function getAttributionInfo() in https://github.com/matomo-org/matomo/blob/master/js/matomo.js + */ + public function setAttributionInfo(string $jsonEncoded) + { + $decoded = json_decode($jsonEncoded, $assoc = true); + if (!is_array($decoded)) { + throw new Exception("setAttributionInfo() is expecting a JSON encoded string, $jsonEncoded given"); + } + $this->attributionInfo = $decoded; + + return $this; + } + + /** + * Sets Visit Custom Variable. + * See https://matomo.org/docs/custom-variables/ + * + * @param int $id Custom variable slot ID from 1-5 + * @param string $name Custom variable name + * @param string $value Custom variable value + * @param string $scope Custom variable scope. Possible values: visit, page, event + * @return $this + * @throws Exception + */ + public function setCustomVariable( + int $id, + string $name, + string $value, + string $scope = 'visit' + ) { + if ($scope === 'page') { + $this->pageCustomVar[$id] = array($name, $value); + } elseif ($scope === 'event') { + $this->eventCustomVar[$id] = array($name, $value); + } elseif ($scope === 'visit') { + $this->visitorCustomVar[$id] = array($name, $value); + } else { + throw new Exception("Invalid 'scope' parameter value"); + } + return $this; + } + + /** + * Returns the currently assigned Custom Variable. + * + * If scope is 'visit', it will attempt to read the value set in the first party cookie created by Matomo Tracker + * ($_COOKIE array). + * + * @param int $id Custom Variable integer index to fetch from cookie. Should be a value from 1 to 5 + * @param string $scope Custom variable scope. Possible values: visit, page, event + * + * @throws Exception + * @return mixed An array with this format: array( 0 => CustomVariableName, 1 => CustomVariableValue ) or false + * @see matomo.js getCustomVariable() + */ + public function getCustomVariable(int $id, string $scope = 'visit') + { + if ($scope === 'page') { + return $this->pageCustomVar[$id] ?? false; + } + + if ($scope === 'event') { + return $this->eventCustomVar[$id] ?? false; + } + + if ($scope !== 'visit') { + throw new Exception("Invalid 'scope' parameter value"); + } + + if (!empty($this->visitorCustomVar[$id])) { + return $this->visitorCustomVar[$id]; + } + $cookieDecoded = $this->getCustomVariablesFromCookie(); + + if (!is_array($cookieDecoded) + || !isset($cookieDecoded[$id]) + || !is_array($cookieDecoded[$id]) + || count($cookieDecoded[$id]) !== 2 + ) { + return false; + } + + return $cookieDecoded[$id]; + } + + /** + * Clears any Custom Variable that may be have been set. + * + * This can be useful when you have enabled bulk requests, + * and you wish to clear Custom Variables of 'visit' scope. + */ + public function clearCustomVariables(): void + { + $this->visitorCustomVar = []; + $this->pageCustomVar = []; + $this->eventCustomVar = []; + } + + /** + * Sets a specific custom dimension + * + * @param int $id id of custom dimension + * @param string $value value for custom dimension + * @return $this + */ + public function setCustomDimension(int $id, string $value) + { + $this->customDimensions['dimension'.$id] = $value; + + return $this; + } + + /** + * Clears all previously set custom dimensions + */ + public function clearCustomDimensions(): void + { + $this->customDimensions = []; + } + + /** + * Returns the value of the custom dimension with the given id + * + * @param int $id id of custom dimension + * @return string|null + */ + public function getCustomDimension(int $id): ?string + { + return $this->customDimensions['dimension'.$id] ?? null; + } + + /** + * Sets a custom tracking parameter. This is useful if you need to send any tracking parameters for a 3rd party + * plugin that is not shipped with Matomo itself. Please note that custom parameters are cleared after each + * tracking request. + * + * @param string $trackingApiParameter The name of the tracking API parameter, eg 'bw_bytes' + * @param string $value Tracking parameter value that shall be sent for this tracking parameter. + * @return $this + * @throws Exception + */ + public function setCustomTrackingParameter(string $trackingApiParameter, string $value) + { + $matches = []; + + if (preg_match('/^dimension([0-9]+)$/', $trackingApiParameter, $matches)) { + $this->setCustomDimension($matches[1], $value); + + return $this; + } + + $this->customParameters[$trackingApiParameter] = $value; + + return $this; + } + + /** + * Clear / reset all previously set custom tracking parameters. + */ + public function clearCustomTrackingParameters(): void + { + $this->customParameters = []; + } + + /** + * Sets the current visitor ID to a random new one. + * @return $this + */ + public function setNewVisitorId() + { + $this->randomVisitorId = substr(md5(uniqid(rand(), true)), 0, self::LENGTH_VISITOR_ID); + $this->forcedVisitorId = false; + $this->cookieVisitorId = false; + + return $this; + } + + /** + * Sets the current site ID. + * + * @return $this + */ + public function setIdSite(int $idSite) + { + $this->idSite = $idSite; + + return $this; + } + + /** + * Sets the Browser language. Used to guess visitor countries when GeoIP is not enabled + * + * @param string $acceptLanguage For example "fr-fr" + * @return $this + */ + public function setBrowserLanguage(string $acceptLanguage) + { + $this->acceptLanguage = $acceptLanguage; + + return $this; + } + + /** + * Sets the user agent, used to detect OS and browser. + * If this function is not called, the User Agent will default to the current user agent. + * + * @param string $userAgent + * @return $this + */ + public function setUserAgent(string $userAgent) + { + $this->userAgent = $userAgent; + + return $this; + } + + /** + * Sets the client hints, used to detect OS and browser. + * If this function is not called, the client hints sent with the current request will be used. + * + * Supported as of Matomo 4.12.0 + * + * @param string $model Value of the header 'HTTP_SEC_CH_UA_MODEL' + * @param string $platform Value of the header 'HTTP_SEC_CH_UA_PLATFORM' + * @param string $platformVersion Value of the header 'HTTP_SEC_CH_UA_PLATFORM_VERSION' + * @param string|array $fullVersionList Value of header 'HTTP_SEC_CH_UA_FULL_VERSION_LIST' + * or an array containing all brands with the structure + * [['brand' => 'Chrome', 'version' => '10.0.2'], ['brand' => '...] + * @param string $uaFullVersion Value of the header 'HTTP_SEC_CH_UA_FULL_VERSION' + * + * @return $this + */ + public function setClientHints( + string $model = '', + string $platform = '', + string $platformVersion = '', + $fullVersionList = '', + string $uaFullVersion = '' + ) { + if (is_string($fullVersionList)) { + $reg = '/^"([^"]+)"; ?v="([^"]+)"(?:, )?/'; + $list = []; + + while (\preg_match($reg, $fullVersionList, $matches)) { + $list[] = ['brand' => $matches[1], 'version' => $matches[2]]; + $fullVersionList = \substr($fullVersionList, \strlen($matches[0])); + } + + $fullVersionList = $list; + } elseif (!is_array($fullVersionList)) { + $fullVersionList = []; + } + + $this->clientHints = array_filter([ + 'model' => $model, + 'platform' => $platform, + 'platformVersion' => $platformVersion, + 'uaFullVersion' => $uaFullVersion, + 'fullVersionList' => $fullVersionList, + ]); + + return $this; + } + + /** + * Sets the country of the visitor. If not used, Matomo will try to find the country + * using either the visitor's IP address or language. + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth(). + * + * @return $this + */ + public function setCountry(string $country) + { + $this->country = $country; + + return $this; + } + + /** + * Sets the region of the visitor. If not used, Matomo may try to find the region + * using the visitor's IP address (if configured to do so). + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth(). + * + * @return $this + */ + public function setRegion(string $region) + { + $this->region = $region; + + return $this; + } + + /** + * Sets the city of the visitor. If not used, Matomo may try to find the city + * using the visitor's IP address (if configured to do so). + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth(). + * + * @return $this + */ + public function setCity(string $city) + { + $this->city = $city; + + return $this; + } + + /** + * Sets the latitude of the visitor. If not used, Matomo may try to find the visitor's + * latitude using the visitor's IP address (if configured to do so). + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth(). + * + * @return $this + */ + public function setLatitude(float $lat) + { + $this->lat = $lat; + + return $this; + } + + /** + * Sets the longitude of the visitor. If not used, Matomo may try to find the visitor's + * longitude using the visitor's IP address (if configured to do so). + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth(). + * + * @return $this + */ + public function setLongitude(float $long) + { + $this->long = $long; + + return $this; + } + + /** + * Enables the bulk request feature. When used, each tracking action is stored until the + * doBulkTrack method is called. This method will send all tracking data at once. + */ + public function enableBulkTracking(): void + { + $this->doBulkRequests = true; + } + + /** + * Disables the bulk request feature. Make sure to call `doBulkTrack()` before disabling it if you have stored + * tracking actions previously as this method won't be sending any previously stored actions before disabling it. + */ + public function disableBulkTracking(): void + { + $this->doBulkRequests = false; + } + + /** + * Enable Cookie Creation - this will cause a first party VisitorId cookie to be set when the VisitorId is set or reset + * + * @param string $domain (optional) Set first-party cookie domain. + * Accepted values: example.com, *.example.com (same as .example.com) or subdomain.example.com + * @param string $path (optional) Set first-party cookie path + * @param bool $secure (optional) Set secure flag for cookies + * @param bool $httpOnly (optional) Set HTTPOnly flag for cookies + * @param string $sameSite (optional) Set SameSite flag for cookies + */ + public function enableCookies( + string $domain = '', + string $path = '/', + bool $secure = false, + bool $httpOnly = false, + string $sameSite = '' + ): void { + $this->configCookiesDisabled = false; + $this->configCookieDomain = self::domainFixup($domain); + $this->configCookiePath = $path; + $this->configCookieSecure = $secure; + $this->configCookieHTTPOnly = $httpOnly; + $this->configCookieSameSite = $sameSite; + } + + /** + * If image response is disabled Matomo will respond with a HTTP 204 header instead of responding with a gif. + */ + public function disableSendImageResponse(): void + { + $this->sendImageResponse = false; + } + + /** + * Fix-up domain + */ + protected static function domainFixup($domain) + { + if (strlen($domain) > 0) { + $dl = strlen($domain) - 1; + // remove trailing '.' + if ($domain[$dl] === '.') { + $domain = substr($domain, 0, $dl); + } + // remove leading '*' + if (substr($domain, 0, 2) === '*.') { + $domain = substr($domain, 1); + } + } + + return $domain; + } + + /** + * Get cookie name with prefix and domain hash + */ + protected function getCookieName(string $cookieName): string + { + // NOTE: If the cookie name is changed, we must also update the method in matomo.js with the same name. + $hash = substr( + sha1( + ($this->configCookieDomain === '' + ? self::getCurrentHost() + : $this->configCookieDomain + ) . $this->configCookiePath + ), + 0, + 4 + ); + + return self::FIRST_PARTY_COOKIES_PREFIX . $cookieName . '.' . $this->idSite . '.' . $hash; + } + + /** + * Tracks a page view + * + * @param string $documentTitle Page title as it will appear in the Actions > Page titles report + * @return mixed Response string or true if using bulk requests. + */ + public function doTrackPageView(string $documentTitle) + { + if (!$this->idPageviewSetManually) { + $this->generateNewPageviewId(); + } + + $url = $this->getUrlTrackPageView($documentTitle); + + return $this->sendRequest($url); + } + + /** + * If the current user agent belongs to an AI agent bot, tracks a pageview action. + * + * This method should be used server side to track AI bots that do not execute + * JavaScript. + * + * @return mixed Response string or true if using bulk requests. + */ + public function doTrackPageViewIfAIBot(?int $httpStatus = null, ?int $responseSizeBytes = null, ?int $serverTimeMs = null, ?string $source = null) + { + if (!self::isUserAgentAIBot($this->userAgent)) { + return null; + } + + $url = $this->getUrlTrackAIBot($httpStatus, $responseSizeBytes, $serverTimeMs, $source); + return $this->sendRequest($url); + } + + /** + * Override PageView id for every use of `doTrackPageView()`. Do not use this if you call `doTrackPageView()` + * multiple times during tracking (if, for example, you are tracking a single page application). + */ + public function setPageviewId(string $idPageview): void + { + $this->idPageview = $idPageview; + $this->idPageviewSetManually = true; + } + + /** + * Returns the PageView id. If the id was manually set using `setPageViewId()`, that id will be returned. + * If the id was not set manually, the id that was automatically generated in last `doTrackPageView()` will + * be returned. If there was no last page view, this will be false. + * + * @return string|false The PageView id as string or false if there is none yet. + */ + public function getPageviewId() + { + return $this->idPageview; + } + + private function generateNewPageviewId(): void + { + $this->idPageview = substr(md5(uniqid(rand(), true)), 0, 6); + } + + /** + * Tracks an event + * + * @param string $category The Event Category (Videos, Music, Games...) + * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) + * @param string|bool $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...) + * @param float|bool $value (optional) The Event's value + * @return mixed Response string or true if using bulk requests. + */ + public function doTrackEvent( + string $category, + string $action, + $name = false, + $value = false + ) { + $url = $this->getUrlTrackEvent($category, $action, $name, $value); + + return $this->sendRequest($url); + } + + /** + * Tracks a content impression + * + * @param string $contentName The name of the content. For instance 'Ad Foo Bar' + * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text + * @param string|bool $contentTarget (optional) The target of the content. For instance the URL of a landing page. + * @return mixed Response string or true if using bulk requests. + */ + public function doTrackContentImpression( + string $contentName, + string $contentPiece = 'Unknown', + $contentTarget = false + ) { + $url = $this->getUrlTrackContentImpression($contentName, $contentPiece, $contentTarget); + + return $this->sendRequest($url); + } + + /** + * Tracks a content interaction. Make sure you have tracked a content impression using the same content name and + * content piece, otherwise it will not count. To do so you should call the method doTrackContentImpression(); + * + * @param string $interaction The name of the interaction with the content. For instance a 'click' + * @param string $contentName The name of the content. For instance 'Ad Foo Bar' + * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text + * @param string|bool $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page. + * @return mixed Response string or true if using bulk requests. + */ + public function doTrackContentInteraction( + string $interaction, + string $contentName, + string $contentPiece = 'Unknown', + $contentTarget = false + ) { + $url = $this->getUrlTrackContentInteraction($interaction, $contentName, $contentPiece, $contentTarget); + + return $this->sendRequest($url); + } + + /** + * Tracks an internal Site Search query, and optionally tracks the Search Category, and Search results Count. + * These are used to populate reports in Actions > Site Search. + * + * @param string $keyword Searched query on the site + * @param string $category (optional) Search engine category if applicable + * @param bool|int $countResults (optional) results displayed on the search result page. Used to track "zero result" keywords. + * + * @return mixed Response or true if using bulk requests. + */ + public function doTrackSiteSearch( + string $keyword, + string $category = '', + $countResults = false + ) { + $url = $this->getUrlTrackSiteSearch($keyword, $category, $countResults); + + return $this->sendRequest($url); + } + + /** + * Records a Goal conversion + * + * @param int $idGoal Id Goal to record a conversion + * @param float $revenue Revenue for this conversion + * @return mixed Response or true if using bulk request + */ + public function doTrackGoal(int $idGoal, float $revenue = 0.0) + { + $url = $this->getUrlTrackGoal($idGoal, $revenue); + + return $this->sendRequest($url); + } + + /** + * Tracks a download or outlink + * + * @param string $actionUrl URL of the download or outlink + * @param string $actionType Type of the action: 'download' or 'link' + * @return mixed Response or true if using bulk request + */ + public function doTrackAction(string $actionUrl, string $actionType) + { + // Referrer could be udpated to be the current URL temporarily (to mimic JS behavior) + $url = $this->getUrlTrackAction($actionUrl, $actionType); + + return $this->sendRequest($url); + } + + /** + * Adds an item in the Ecommerce order. + * + * This should be called before doTrackEcommerceOrder(), or before doTrackEcommerceCartUpdate(). + * This function can be called for all individual products in the cart (or order). + * SKU parameter is mandatory. Other parameters are optional (set to false if value not known). + * Ecommerce items added via this function are automatically cleared when doTrackEcommerceOrder() or getUrlTrackEcommerceOrder() is called. + * + * @param string $sku (required) SKU, Product identifier + * @param string $name (optional) Product name + * @param string|array $category (optional) Product category, or array of product categories (up to 5 categories can be specified for a given product) + * @param float|int $price (optional) Individual product price (supports integer and decimal prices) + * @param int $quantity (optional) Product quantity. If not specified, will default to 1 in the Reports + * @throws Exception + * @return $this + */ + public function addEcommerceItem( + string $sku, + string $name = '', + $category = '', + $price = 0.0, + int $quantity = 1 + ) { + if (empty($sku)) { + throw new Exception("You must specify a SKU for the Ecommerce item"); + } + + $price = $this->forceDotAsSeparatorForDecimalPoint($price); + + $this->ecommerceItems[] = array($sku, $name, $category, $price, $quantity); + + return $this; + } + + /** + * Tracks a Cart Update (add item, remove item, update item). + * + * On every Cart update, you must call addEcommerceItem() for each item (product) in the cart, + * including the items that haven't been updated since the last cart update. + * Items which were in the previous cart and are not sent in later Cart updates will be deleted from the cart (in the database). + * + * @param float $grandTotal Cart grandTotal (typically the sum of all items' prices) + * @return mixed Response or true if using bulk request + */ + public function doTrackEcommerceCartUpdate(float $grandTotal) + { + $url = $this->getUrlTrackEcommerceCartUpdate($grandTotal); + + return $this->sendRequest($url); + } + + /** + * Sends all stored tracking actions at once. Only has an effect if bulk tracking is enabled. + * + * To enable bulk tracking, call enableBulkTracking(). + * + * @throws Exception + * @return string Response + */ + public function doBulkTrack() + { + if (empty($this->storedTrackingActions)) { + throw new Exception( + "Error: you must call the function doTrackPageView or doTrackGoal from this class, + before calling this method doBulkTrack()" + ); + } + + $data = ['requests' => $this->storedTrackingActions]; + + // token_auth is not required by default, except if bulk_requests_require_authentication=1 + if (!empty($this->token_auth)) { + $data['token_auth'] = $this->token_auth; + } + + $postData = json_encode($data); + $response = $this->sendRequest($this->getBaseUrl(), 'POST', $postData, $force = true); + + $this->storedTrackingActions = []; + + return $response; + } + + /** + * Tracks an Ecommerce order. + * + * If the Ecommerce order contains items (products), you must call first the addEcommerceItem() for each item in the order. + * All revenues (grandTotal, subTotal, tax, shipping, discount) will be individually summed and reported in Matomo reports. + * Only the parameters $orderId and $grandTotal are required. + * + * @param string|int $orderId (required) Unique Order ID. + * This will be used to count this order only once in the event the order page is reloaded several times. + * orderId must be unique for each transaction, even on different days, or the transaction will not be recorded by Matomo. + * @param float $grandTotal (required) Grand Total revenue of the transaction (including tax, shipping, etc.) + * @param float $subTotal (optional) Sub total amount, typically the sum of items prices for all items in this order (before Tax and Shipping costs are applied) + * @param float $tax (optional) Tax amount for this order + * @param float $shipping (optional) Shipping amount for this order + * @param float $discount (optional) Discounted amount in this order + * @return mixed Response or true if using bulk request + */ + public function doTrackEcommerceOrder( + $orderId, + float $grandTotal, + float $subTotal = 0.0, + float $tax = 0.0, + float $shipping = 0.0, + float $discount = 0.0 + ) { + $url = $this->getUrlTrackEcommerceOrder($orderId, $grandTotal, $subTotal, $tax, $shipping, $discount); + + return $this->sendRequest($url); + } + + /** + * Tracks a PHP Throwable a crash (requires CrashAnalytics to be enabled in the target Matomo) + * + * @param Throwable $throwable (required) the throwable to track. The message, stack trace, file location and line number + * of the crash are deduced from this parameter. The crash type is set to the class name of + * the Throwable. + * @param string|null $category (optional) a category value for this crash. This can be any information you want + * to attach to the crash. + * @return mixed Response or true if using bulk request + */ + public function doTrackPhpThrowable(Throwable $throwable, ?string $category = null) + { + $message = $throwable->getMessage(); + $stack = $throwable->getTraceAsString(); + $type = get_class($throwable); + $location = $throwable->getFile(); + $line = $throwable->getLine(); + + return $this->doTrackCrash($message, $type, $category, $stack, $location, $line); + } + + /** + * Track a crash (requires CrashAnalytics to be enabled in the target Matomo) + * + * @param string $message (required) the error message. + * @param string|null $type (optional) the error type, such as the class name of an Exception. + * @param string|null $category (optional) a category value for this crash. This can be any information you want + * to attach to the crash. + * @param string|null $stack (optional) the stack trace of the crash. + * @param string|null $location (optional) the source file URI where the crash originated. + * @param int|null $line (optional) the source file line where the crash originated. + * @param int|null $column (optional) the source file column where the crash originated. + * @return mixed Response or true if using bulk request + */ + public function doTrackCrash( + string $message, + ?string $type = null, + ?string $category = null, + ?string $stack = null, + ?string $location = null, + ?int $line = null, + ?int $column = null + ) { + $url = $this->getUrlTrackCrash($message, $type, $category, $stack, $location, $line, $column); + + return $this->sendRequest($url); + } + + /** + * Sends a ping request. + * + * Ping requests do not track new actions. If they are sent within the standard visit length (see global.ini.php), + * they will extend the existing visit and the current last action for the visit. If after the standard visit length, + * ping requests will create a new visit using the last action in the last known visit. + * + * @return mixed Response or true if using bulk request + */ + public function doPing() + { + $url = $this->getRequest($this->idSite); + $url .= '&ping=1'; + + return $this->sendRequest($url); + } + + /** + * Sets the current page view as an item (product) page view, or an Ecommerce Category page view. + * + * This must be called before doTrackPageView() on this product/category page. + * + * On a category page, you may set the parameter $category only and set the other parameters to false. + * + * Tracking Product/Category page views will allow Matomo to report on Product & Categories + * conversion rates (Conversion rate = Ecommerce orders containing this product or category / Visits to the product or category) + * + * @param string $sku Product SKU being viewed + * @param string $name Product Name being viewed + * @param string|array $category Category being viewed. On a Product page, this is the product's category. + * You can also specify an array of up to 5 categories for a given page view. + * @param float $price Specify the price at which the item was displayed + * @return $this + */ + public function setEcommerceView( + string $sku = '', + string $name = '', + $category = '', + float $price = 0.0 + ) { + $this->ecommerceView = []; + + if (!empty($category)) { + if (is_array($category)) { + $category = json_encode($category); + } + } else { + $category = ""; + } + $this->ecommerceView['_pkc'] = $category; + + if (!empty($price)) { + $price = $this->forceDotAsSeparatorForDecimalPoint($price); + $this->ecommerceView['_pkp'] = $price; + } + + // On a category page, do not record "Product name not defined" + if (empty($sku) && empty($name)) { + return $this; + } + if (!empty($sku)) { + $this->ecommerceView['_pks'] = $sku; + } + if (empty($name)) { + $name = ''; + } + $this->ecommerceView['_pkn'] = $name; + + return $this; + } + + /** + * Force the separator for decimal point to be a dot. See https://github.com/matomo-org/matomo/issues/6435 + * If for instance a German locale is used it would be a comma otherwise. + * + * @param float|string $value + */ + private function forceDotAsSeparatorForDecimalPoint($value): string + { + if (null === $value || false === $value) { + return $value; + } + + return str_replace(',', '.', $value); + } + + /** + * TODO: tests + * TODO: docs + * + * @return string + */ + public function getUrlTrackAIBot(?int $httpStatus = null, ?int $responseSizeBytes = null, ?int $serverTimeMs = null, ?string $source = null): string + { + $url = $this->getRequest($this->idSite); + + $url .= '&recMode=1'; + + if (!empty($httpStatus)) { + $url .= '&http_status=' . $httpStatus; + } + + if (!empty($responseSizeBytes)) { + $url .= '&bw_bytes=' . $responseSizeBytes; + } + + if (!empty($serverTimeMs)) { + $url .= '&pf_srv=' . $serverTimeMs; + } + + if (!empty($source)) { + $url .= '&source=' . rawurlencode($source); + } + + return $url; + } + + /** + * Returns URL used to track Ecommerce Cart updates + * Calling this function will reinitializes the property ecommerceItems to empty array + * so items will have to be added again via addEcommerceItem() + * @ignore + */ + public function getUrlTrackEcommerceCartUpdate($grandTotal) + { + return $this->getUrlTrackEcommerce($grandTotal); + } + + /** + * Returns URL used to track Ecommerce Orders + * Calling this function will reinitializes the property ecommerceItems to empty array + * so items will have to be added again via addEcommerceItem() + * @ignore + */ + public function getUrlTrackEcommerceOrder( + $orderId, + $grandTotal, + $subTotal = 0.0, + $tax = 0.0, + $shipping = 0.0, + $discount = 0.0 + ) { + if (empty($orderId)) { + throw new Exception("You must specifiy an orderId for the Ecommerce order"); + } + $url = $this->getUrlTrackEcommerce($grandTotal, $subTotal, $tax, $shipping, $discount); + $url .= '&ec_id=' . urlencode($orderId); + + return $url; + } + + /** + * Returns URL used to track Ecommerce orders + * + * Calling this function will reinitializes the property ecommerceItems to empty array + * so items will have to be added again via addEcommerceItem() + * + * @ignore + */ + protected function getUrlTrackEcommerce($grandTotal, $subTotal = 0.0, $tax = 0.0, $shipping = 0.0, $discount = 0.0) + { + if (!is_numeric($grandTotal)) { + throw new Exception("You must specifiy a grandTotal for the Ecommerce order (or Cart update)"); + } + + $url = $this->getRequest($this->idSite); + $url .= '&idgoal=0'; + if (!empty($grandTotal)) { + $grandTotal = $this->forceDotAsSeparatorForDecimalPoint($grandTotal); + $url .= '&revenue=' . $grandTotal; + } + if (!empty($subTotal)) { + $subTotal = $this->forceDotAsSeparatorForDecimalPoint($subTotal); + $url .= '&ec_st=' . $subTotal; + } + if (!empty($tax)) { + $tax = $this->forceDotAsSeparatorForDecimalPoint($tax); + $url .= '&ec_tx=' . $tax; + } + if (!empty($shipping)) { + $shipping = $this->forceDotAsSeparatorForDecimalPoint($shipping); + $url .= '&ec_sh=' . $shipping; + } + if (!empty($discount)) { + $discount = $this->forceDotAsSeparatorForDecimalPoint($discount); + $url .= '&ec_dt=' . $discount; + } + if (!empty($this->ecommerceItems)) { + $url .= '&ec_items=' . urlencode(json_encode($this->ecommerceItems)); + } + $this->ecommerceItems = array(); + + return $url; + } + + /** + * Builds URL to track a page view. + * + * @see doTrackPageView() + * @param string $documentTitle Page view name as it will appear in Matomo reports + * @return string URL to matomo.php with all parameters set to track the pageview + */ + public function getUrlTrackPageView(string $documentTitle = ''): string + { + $url = $this->getRequest($this->idSite); + if (strlen($documentTitle) > 0) { + $url .= '&action_name=' . urlencode($documentTitle); + } + + return $url; + } + + /** + * Builds URL to track a custom event. + * + * @see doTrackEvent() + * @param string $category The Event Category (Videos, Music, Games...) + * @param string $action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) + * @param string|bool $name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...) + * @param float|bool $value (optional) The Event's value + * @return string URL to matomo.php with all parameters set to track the pageview + * @throws + */ + public function getUrlTrackEvent( + string $category, + string $action, + $name = false, + $value = false + ): string { + $url = $this->getRequest($this->idSite); + if (strlen($category) === 0) { + throw new Exception("You must specify an Event Category name (Music, Videos, Games...)."); + } + if (strlen($action) === 0) { + throw new Exception("You must specify an Event action (click, view, add...)."); + } + + $url .= '&e_c=' . urlencode($category); + $url .= '&e_a=' . urlencode($action); + + if (strlen($name) > 0) { + $url .= '&e_n=' . urlencode($name); + } + if (strlen($value) > 0) { + $value = $this->forceDotAsSeparatorForDecimalPoint($value); + $url .= '&e_v=' . $value; + } + + return $url; + } + + /** + * Builds URL to track a content impression. + * + * @see doTrackContentImpression() + * @param string $contentName The name of the content. For instance 'Ad Foo Bar' + * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text + * @param string|false $contentTarget (optional) The target of the content. For instance the URL of a landing page. + * @throws Exception In case $contentName is empty + * @return string URL to matomo.php with all parameters set to track the pageview + */ + public function getUrlTrackContentImpression( + string $contentName, + string $contentPiece, + $contentTarget + ): string { + $url = $this->getRequest($this->idSite); + + if (strlen($contentName) === 0) { + throw new Exception("You must specify a content name"); + } + + $url .= '&c_n=' . urlencode($contentName); + + if (!empty($contentPiece) && strlen($contentPiece) > 0) { + $url .= '&c_p=' . urlencode($contentPiece); + } + if (!empty($contentTarget) && strlen($contentTarget) > 0) { + $url .= '&c_t=' . urlencode($contentTarget); + } + + return $url; + } + + /** + * Builds URL to track a content interaction. + * + * @see doTrackContentInteraction() + * @param string $interaction The name of the interaction with the content. For instance a 'click' + * @param string $contentName The name of the content. For instance 'Ad Foo Bar' + * @param string $contentPiece The actual content. For instance the path to an image, video, audio, any text + * @param string|false $contentTarget (optional) The target the content leading to when an interaction occurs. For instance the URL of a landing page. + * @throws Exception In case $interaction or $contentName is empty + * @return string URL to matomo.php with all parameters set to track the pageview + */ + public function getUrlTrackContentInteraction( + string $interaction, + string $contentName, + string $contentPiece, + $contentTarget + ): string { + $url = $this->getRequest($this->idSite); + + if (strlen($interaction) === 0) { + throw new Exception("You must specify a name for the interaction"); + } + + if (strlen($contentName) === 0) { + throw new Exception("You must specify a content name"); + } + + $url .= '&c_i=' . urlencode($interaction); + $url .= '&c_n=' . urlencode($contentName); + + if (!empty($contentPiece) && strlen($contentPiece) > 0) { + $url .= '&c_p=' . urlencode($contentPiece); + } + if (!empty($contentTarget) && strlen($contentTarget) > 0) { + $url .= '&c_t=' . urlencode($contentTarget); + } + + return $url; + } + + /** + * Builds URL to track a site search. + * + * @see doTrackSiteSearch() + */ + public function getUrlTrackSiteSearch(string $keyword, string $category, int $countResults): string + { + $url = $this->getRequest($this->idSite); + $url .= '&search=' . urlencode($keyword); + if (strlen($category) > 0) { + $url .= '&search_cat=' . urlencode($category); + } + if (!empty($countResults) || $countResults === 0) { + $url .= '&search_count=' . (int)$countResults; + } + + return $url; + } + + /** + * Builds URL to track a goal with idGoal and revenue. + * + * @see doTrackGoal() + * @param int $idGoal Id Goal to record a conversion + * @param float $revenue Revenue for this conversion + * @return string URL to matomo.php with all parameters set to track the goal conversion + */ + public function getUrlTrackGoal(int $idGoal, float $revenue = 0.0): string + { + $url = $this->getRequest($this->idSite); + $url .= '&idgoal=' . $idGoal; + if (!empty($revenue)) { + $revenue = $this->forceDotAsSeparatorForDecimalPoint($revenue); + $url .= '&revenue=' . $revenue; + } + + return $url; + } + + /** + * Builds URL to track a new action. + * + * @see doTrackAction() + * @param string $actionUrl URL of the download or outlink + * @param string $actionType Type of the action: 'download' or 'link' + * @return string URL to matomo.php with all parameters set to track an action + */ + public function getUrlTrackAction(string $actionUrl, string $actionType): string + { + $url = $this->getRequest($this->idSite); + $url .= '&' . $actionType . '=' . urlencode($actionUrl); + + return $url; + } + + /** + * Builds URL to track a crash. + * + * @see doTrackCrash() + * @param string $message (required) the error message. + * @param string|null $type (optional) the error type, such as the class name of an Exception. + * @param string|null $category (optional) a category value for this crash. This can be any information you want + * to attach to the crash. + * @param string|null $stack (optional) the stack trace of the crash. + * @param string|null $location (optional) the source file URI where the crash originated. + * @param int|null $line (optional) the source file line where the crash originated. + * @param int|null $column (optional) the source file column where the crash originated. + * @return string URL to matomo.php with all parameters set to track an action + */ + public function getUrlTrackCrash( + string $message, + ?string $type = null, + ?string $category = null, + ?string $stack = null, + ?string $location = null, + ?int $line = null, + ?int $column = null + ): string { + $url = $this->getRequest($this->idSite); + $url .= '&ca=1&cra=' . urlencode($message); + if ($type) { + $url .= '&cra_tp=' . urlencode($type); + } + if ($category) { + $url .= '&cra_ct=' . urlencode($category); + } + if ($stack) { + $url .= '&cra_st=' . urlencode($stack); + } + if ($location) { + $url .= '&cra_ru=' . urlencode($location); + } + if ($line) { + $url .= '&cra_rl=' . urlencode($line); + } + if ($column) { + $url .= '&cra_rc=' . urlencode($column); + } + + return $url; + } + + /** + * Overrides server date and time for the tracking requests. + * By default Matomo will track requests for the "current datetime" but this function allows you + * to track visits in the past. All times are in UTC. + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth() + * @see setTokenAuth() + * @param string $dateTime Date with the format 'Y-m-d H:i:s', or a UNIX timestamp. + * If the datetime is older than one day (default value for tracking_requests_require_authentication_when_custom_timestamp_newer_than), then you must call setTokenAuth() with a valid Admin/Super user token. + * @return $this + */ + public function setForceVisitDateTime(string $dateTime) + { + $this->forcedDatetime = $dateTime; + + return $this; + } + + /** + * Forces Matomo to create a new visit for the tracking request. + * + * By default, Matomo will create a new visit if the last request by this user was more than 30 minutes ago. + * If you call setForceNewVisit() before calling doTrack*, then a new visit will be created for this request. + * @return $this + */ + public function setForceNewVisit() + { + $this->forcedNewVisit = true; + + return $this; + } + + /** + * Overrides IP address + * + * Allowed only for Admin/Super User, must be used along with setTokenAuth() + * @see setTokenAuth() + * @param string $ip IP string, eg. 130.54.2.1 + * @return $this + */ + public function setIp(string $ip) + { + $this->ip = $ip; + + return $this; + } + + /** + * Force the action to be recorded for a specific User. The User ID is a string representing a given user in your system. + * + * A User ID can be a username, UUID or an email address, or any number or string that uniquely identifies a user or client. + * + * @param string $userId Any user ID string (eg. email address, ID, username). Must be non empty. Set to false to de-assign a user id previously set. + * @return $this + * @throws Exception + */ + public function setUserId(string $userId) + { + if ($userId === '') { + throw new Exception("User ID cannot be empty."); + } + $this->userId = $userId; + + return $this; + } + + /** + * Hash function used internally by Matomo to hash a User ID into the Visitor ID. + * + * Note: matches implementation of Tracker\Request->getUserIdHashed() + * + * @return string + */ + public static function getUserIdHashed($id): string + { + return substr(sha1($id), 0, 16); + } + + /** + * Forces the requests to be recorded for the specified Visitor ID. + * + * Rather than letting Matomo attribute the user with a heuristic based on IP and other user fingeprinting attributes, + * force the action to be recorded for a particular visitor. + * + * If not set, the visitor ID will be fetched from the 1st party cookie, or will be set to a random UUID. + * + * @param string $visitorId 16 hexadecimal characters visitor ID, eg. "33c31e01394bdc63" + * @return $this + * @throws Exception + */ + public function setVisitorId(string $visitorId) + { + $hexChars = '01234567890abcdefABCDEF'; + if (strlen($visitorId) !== self::LENGTH_VISITOR_ID + || strspn($visitorId, $hexChars) !== strlen($visitorId) + ) { + throw new Exception( + "setVisitorId() expects a " + . self::LENGTH_VISITOR_ID + . " characters hexadecimal string (containing only the following: " + . $hexChars + . ")" + ); + } + $this->forcedVisitorId = $visitorId; + + return $this; + } + + /** + * If the user initiating the request has the Matomo first party cookie, + * this function will try and return the ID parsed from this first party cookie (found in $_COOKIE). + * + * If you call this function from a server, where the call is triggered by a cron or script + * not initiated by the actual visitor being tracked, then it will return + * the random Visitor ID that was assigned to this visit object. + * + * This can be used if you wish to record more visits, actions or goals for this visitor ID later on. + * + * @return string 16 hex chars visitor ID string + */ + public function getVisitorId() + { + if (!empty($this->forcedVisitorId)) { + return $this->forcedVisitorId; + } + if ($this->loadVisitorIdCookie()) { + return $this->cookieVisitorId; + } + + return $this->randomVisitorId; + } + + /** + * Returns the currently set user agent. + * @return string + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * Returns the currently set IP address. + * @return string + */ + public function getIp() + { + return $this->ip; + } + + /** + * Returns the User ID string, which may have been set via: + * $v->setUserId('username@example.org'); + * + * @return bool + */ + public function getUserId() + { + return $this->userId; + } + + /** + * Loads values from the VisitorId Cookie + * + * @return bool True if cookie exists and is valid, False otherwise + */ + protected function loadVisitorIdCookie(): bool + { + $idCookie = $this->getCookieMatchingName('id'); + if ($idCookie === false) { + return false; + } + $parts = explode('.', $idCookie); + if (strlen($parts[0]) !== self::LENGTH_VISITOR_ID) { + return false; + } + + /* $this->cookieVisitorId provides backward compatibility since getVisitorId() +didn't change any existing VisitorId value */ + $this->cookieVisitorId = $parts[0]; + $this->createTs = $parts[1]; + + return true; + } + + /** + * Deletes all first party cookies from the client + */ + public function deleteCookies(): void + { + $cookies = array('id', 'ses', 'cvar', 'ref'); + foreach ($cookies as $cookie) { + $this->setCookie($cookie, '', -86400); + } + } + + /** + * Returns the currently assigned Attribution Information stored in a first party cookie. + * + * This function will only work if the user is initiating the current request, and his cookies + * can be read by PHP from the $_COOKIE array. + * + * @return string JSON Encoded string containing the Referrer information for Goal conversion attribution. + * Will return false if the cookie could not be found + * @see matomo.js getAttributionInfo() + */ + public function getAttributionInfo() + { + if (!empty($this->attributionInfo)) { + return json_encode($this->attributionInfo); + } + } + + /** + * Some Tracking API functionality requires express authentication, using either the + * Super User token_auth, or a user with 'admin' access to the website. + * + * The following features require access: + * - force the visitor IP + * - force the date & time of the tracking requests rather than track for the current datetime + * + * @param string $token_auth token_auth 32 chars token_auth string + * @return $this + */ + public function setTokenAuth(string $token_auth) + { + $this->token_auth = $token_auth; + + return $this; + } + + /** + * Sets local visitor time + * + * @param string $time HH:MM:SS format + * @return $this + */ + public function setLocalTime(string $time) + { + [$hour, $minute, $second] = explode(':', $time); + $this->localHour = (int)$hour; + $this->localMinute = (int)$minute; + $this->localSecond = (int)$second; + + return $this; + } + + /** + * Sets user resolution width and height. + * + * @param int $width + * @param int $height + * @return $this + */ + public function setResolution(int $width, int $height) + { + $this->width = $width; + $this->height = $height; + + return $this; + } + + /** + * Sets if the browser supports cookies + * This is reported in "List of plugins" report in Matomo. + * + * @return $this + */ + public function setBrowserHasCookies(bool $hasCookies) + { + $this->hasCookies = $hasCookies; + + return $this; + } + + /** + * Will append a custom string at the end of the Tracking request. + * + * @return $this + */ + public function setDebugStringAppend(string $debugString) + { + $this->DEBUG_APPEND_URL = '&' . $debugString; + + return $this; + } + + /** + * Sets visitor browser supported plugins + * + * @return $this + */ + public function setPlugins( + bool $flash = false, + bool $java = false, + bool $quickTime = false, + bool $realPlayer = false, + bool $pdf = false, + bool $windowsMedia = false, + bool $silverlight = false + ) { + $this->plugins = + '&fla=' . (int)$flash . + '&java=' . (int)$java . + '&qt=' . (int)$quickTime . + '&realp=' . (int)$realPlayer . + '&pdf=' . (int)$pdf . + '&wma=' . (int)$windowsMedia . + '&ag=' . (int)$silverlight; + + return $this; + } + + /** + * By default, MatomoTracker will read first party cookies + * from the request and write updated cookies in the response (using setrawcookie). + * This can be disabled by calling this function. + */ + public function disableCookieSupport(): void + { + $this->configCookiesDisabled = true; + } + + /** + * Returns the maximum number of seconds the tracker will spend waiting for a response + * from Matomo. Defaults to 600 seconds. + */ + public function getRequestTimeout(): int + { + return $this->requestTimeout; + } + + /** + * Sets the maximum number of seconds that the tracker will spend waiting for a response + * from Matomo. + * + * @return $this + * @throws Exception + */ + public function setRequestTimeout(int $timeout) + { + if ($timeout < 0) { + throw new Exception("Invalid value supplied for request timeout: $timeout"); + } + + $this->requestTimeout = $timeout; + + return $this; + } + + /** + * Returns the maximum number of seconds the tracker will spend trying to connect to Matomo. + * Defaults to 300 seconds. + */ + public function getRequestConnectTimeout(): int + { + return $this->requestConnectTimeout; + } + + /** + * Sets the maximum number of seconds that the tracker will spend tryint to connect to Matomo. + * + * @param int $timeout + * @return $this + * @throws Exception + */ + public function setRequestConnectTimeout(int $timeout) + { + if ($timeout < 0) { + throw new Exception("Invalid value supplied for request connect timeout: $timeout"); + } + + $this->requestConnectTimeout = $timeout; + + return $this; + } + + /** + * Sets the request method to POST, which is recommended when using setTokenAuth() + * to prevent the token from being recorded in server logs. Avoid using redirects + * when using POST to prevent the loss of POST values. When using Log Analytics, + * be aware that POST requests are not parseable/replayable. + * + * @param string $method Either 'POST' or 'GET' + * @return $this + */ + public function setRequestMethodNonBulk(string $method) + { + $this->requestMethod = strtoupper($method) === 'POST' ? 'POST' : 'GET'; + + return $this; + } + + /** + * If a proxy is needed to look up the address of the Matomo site, set it with this + * @param string $proxy IP as string, for example "173.234.92.107" + */ + public function setProxy(string $proxy, int $proxyPort = 80): void + { + $this->proxy = $proxy; + $this->proxyPort = $proxyPort; + } + + /** + * If the proxy IP and the proxy port have been set, with the setProxy() function + * returns a string, like "173.234.92.107:80" + */ + private function getProxy(): ?string + { + if (isset($this->proxy) && isset($this->proxyPort)) { + return $this->proxy.":".$this->proxyPort; + } + return null; + } + + /** + * Used in tests to output useful error messages. + * + * @ignore + */ + static public $DEBUG_LAST_REQUESTED_URL = false; + + /** + * Returns array of curl options for request + * + * @return array + */ + protected function prepareCurlOptions( + string $url, + string $method, + $data, + bool $forcePostUrlEncoded + ): array { + $options = [ + CURLOPT_URL => $url, + CURLOPT_USERAGENT => $this->userAgent, + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => $this->requestTimeout, + CURLOPT_CONNECTTIMEOUT => $this->requestConnectTimeout, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Accept-Language: ' . $this->acceptLanguage, + ], + ]; + + if ($method === 'GET') { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + + if (defined('PATH_TO_CERTIFICATES_FILE')) { + $options[CURLOPT_CAINFO] = PATH_TO_CERTIFICATES_FILE; + } + + $proxy = $this->getProxy(); + if (isset($proxy)) { + $options[CURLOPT_PROXY] = $proxy; + } + + switch ($method) { + case 'POST': + $options[CURLOPT_POST] = true; + break; + default: + break; + } + + // only supports JSON data + if (!empty($data) && $forcePostUrlEncoded) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded'; + $options[CURLOPT_POSTFIELDS] = $data; + $options[CURLOPT_POST] = true; + if (defined('CURL_REDIR_POST_ALL')) { + $options[CURLOPT_POSTREDIR] = CURL_REDIR_POST_ALL; + $options[CURLOPT_FOLLOWLOCATION] = true; + } + } elseif (!empty($data)) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json'; + $options[CURLOPT_HTTPHEADER][] = 'Expect:'; + $options[CURLOPT_POSTFIELDS] = $data; + } + + if (!empty($this->outgoingTrackerCookies)) { + $options[CURLOPT_COOKIE] = http_build_query($this->outgoingTrackerCookies); + $this->outgoingTrackerCookies = array(); + } + + return $options; + } + + /** + * Returns array of stream options for request + * + * @return array{http: array} + */ + protected function prepareStreamOptions(string $method, $data, bool $forcePostUrlEncoded): array + { + $stream_options = [ + 'http' => [ + 'method' => $method, + 'user_agent' => $this->userAgent, + 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n", + 'timeout' => $this->requestTimeout, + ], + ]; + + $proxy = $this->getProxy(); + if (isset($proxy)) { + $stream_options['http']['proxy'] = $proxy; + } + + // only supports JSON data + if (!empty($data) && $forcePostUrlEncoded) { + $stream_options['http']['header'] .= "Content-Type: application/x-www-form-urlencoded \r\n"; + $stream_options['http']['content'] = $data; + } elseif (!empty($data)) { + $stream_options['http']['header'] .= "Content-Type: application/json \r\n"; + $stream_options['http']['content'] = $data; + } + + if (!empty($this->outgoingTrackerCookies)) { + $stream_options['http']['header'] .= 'Cookie: ' . http_build_query($this->outgoingTrackerCookies) . "\r\n"; + $this->outgoingTrackerCookies = array(); + } + + return $stream_options; + } + + /** + * @ignore + */ + protected function sendRequest(string $url, string $method = 'GET', $data = null, bool $force = false): string + { + self::$DEBUG_LAST_REQUESTED_URL = $url; + + // if doing a bulk request, store the url + if ($this->doBulkRequests && !$force) { + $this->storedTrackingActions[] + = $url + . (!empty($this->userAgent) ? ('&ua=' . urlencode($this->userAgent)) : '') + . (!empty($this->acceptLanguage) ? ('&lang=' . urlencode($this->acceptLanguage)) : ''); + + // Clear custom variables & dimensions so they don't get copied over to other users in the bulk request + $this->clearCustomVariables(); + $this->clearCustomDimensions(); + $this->clearCustomTrackingParameters(); + $this->userAgent = false; + $this->clientHints = false; + $this->acceptLanguage = false; + + return true; + } + + $forcePostUrlEncoded = false; + if (!$this->doBulkRequests) { + if (!empty($this->requestMethod) && strtoupper($this->requestMethod) === 'POST') { + // POST ALL parameters and have no GET parameters + $urlParts = explode('?', $url); + + $url = $urlParts[0]; + $data = $urlParts[1]; + $forcePostUrlEncoded = true; + + $method = 'POST'; + } + + if (!empty($this->token_auth)) { + $appendTokenString = '&token_auth=' . urlencode($this->token_auth); + + if (empty($this->requestMethod) || $method === 'POST') { + // Only post token_auth but use GET URL parameters for everything else + $forcePostUrlEncoded = true; + if (empty($data)) { + $data = ''; + } + $data .= $appendTokenString; + $data = ltrim($data, '&'); // when no request method set we don't want it to start with '&' + } elseif (!empty($this->token_auth)) { + // Use GET for all URL parameters + $url .= $appendTokenString; + } + } + } + + $content = ''; + + if (function_exists('curl_init') && function_exists('curl_exec')) { + $options = $this->prepareCurlOptions($url, $method, $data, $forcePostUrlEncoded); + + $ch = curl_init(); + curl_setopt_array($ch, $options); + ob_start(); + $response = @curl_exec($ch); + + try { + $header = ''; + + if ($response === false) { + $curlError = curl_error($ch); + if (!empty($curlError)) { + throw new \RuntimeException($curlError); + } + } + + if (!empty($response)) { + // extract header + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $headerSize); + + // extract content + $content = substr($response, $headerSize); + } + + $this->parseIncomingCookies(explode("\r\n", $header)); + } finally { + curl_close($ch); + ob_end_clean(); + } + } elseif (function_exists('stream_context_create')) { + $stream_options = $this->prepareStreamOptions($method, $data, $forcePostUrlEncoded); + + $ctx = stream_context_create($stream_options); + $response = file_get_contents($url, 0, $ctx); + $content = $response; + + $this->parseIncomingCookies($http_response_header); + } + + return $content; + } + + /** + * Returns current timestamp, or forced timestamp/datetime if it was set + * @return string|int + */ + protected function getTimestamp() + { + return !empty($this->forcedDatetime) + ? strtotime($this->forcedDatetime) + : time(); + } + + /** + * Returns the base URL for the Matomo server. + */ + protected function getBaseUrl(): string + { + if (empty(self::$URL)) { + throw new Exception( + 'You must first set the Matomo Tracker URL by calling + MatomoTracker::$URL = \'http://your-website.org/matomo/\';' + ); + } + if (strpos(self::$URL, '/matomo.php') === false + && strpos(self::$URL, '/proxy-matomo.php') === false + ) { + self::$URL = rtrim(self::$URL, '/'); + self::$URL .= '/matomo.php'; + } + + return self::$URL; + } + + /** + * @ignore + */ + protected function getRequest(int $idSite): string + { + $this->setFirstPartyCookies(); + + $customFields = ''; + if (!empty($this->customParameters)) { + $customFields = '&' . http_build_query($this->customParameters, '', '&'); + } + + $customDimensions = ''; + if (!empty($this->customDimensions)) { + $customDimensions = '&' . http_build_query($this->customDimensions, '', '&'); + } + + $baseUrl = $this->getBaseUrl(); + $start = '?'; + if (strpos($baseUrl, '?') !== false) { + $start = '&'; + } + + $url = $baseUrl . $start . + 'idsite=' . $idSite . + '&rec=1' . + '&apiv=' . self::VERSION . + '&r=' . substr(strval(mt_rand()), 2, 6) . + + // XDEBUG_SESSIONS_START and KEY are related to the PHP Debugger, this can be ignored in other languages + (!empty($_GET['XDEBUG_SESSION_START']) ? + '&XDEBUG_SESSION_START=' . @urlencode($_GET['XDEBUG_SESSION_START']) : '') . + (!empty($_GET['KEY']) ? '&KEY=' . @urlencode($_GET['KEY']) : '') . + + // Only allowed for Admin/Super User, token_auth required, + ((!empty($this->ip) && !empty($this->token_auth)) ? '&cip=' . $this->ip : '') . + (!empty($this->userId) ? '&uid=' . urlencode($this->userId) : '') . + (!empty($this->forcedDatetime) ? '&cdt=' . urlencode($this->forcedDatetime) : '') . + (!empty($this->forcedNewVisit) ? '&new_visit=1' : '') . + + // Values collected from cookie + '&_idts=' . $this->createTs . + + // These parameters are set by the JS, but optional when using API + (!empty($this->plugins) ? $this->plugins : '') . + (($this->localHour !== false && $this->localMinute !== false && $this->localSecond !== false) ? + '&h=' . $this->localHour . '&m=' . $this->localMinute . '&s=' . $this->localSecond : '') . + (!empty($this->width) && !empty($this->height) ? '&res=' . $this->width . 'x' . $this->height : '') . + (!empty($this->hasCookies) ? '&cookie=' . $this->hasCookies : '') . + + // Various important attributes + (!empty($this->customData) ? '&data=' . $this->customData : '') . + (!empty($this->visitorCustomVar) ? '&_cvar=' . urlencode(json_encode($this->visitorCustomVar)) : '') . + (!empty($this->pageCustomVar) ? '&cvar=' . urlencode(json_encode($this->pageCustomVar)) : '') . + (!empty($this->eventCustomVar) ? '&e_cvar=' . urlencode(json_encode($this->eventCustomVar)) : '') . + (!empty($this->forcedVisitorId) ? '&cid=' . $this->forcedVisitorId : '&_id=' . $this->getVisitorId()) . + + // URL parameters + '&url=' . urlencode($this->pageUrl ?? '') . + '&urlref=' . urlencode($this->urlReferrer ?? '') . + ((!empty($this->pageCharset) && $this->pageCharset != self::DEFAULT_CHARSET_PARAMETER_VALUES) ? + '&cs=' . $this->pageCharset : '') . + + // unique pageview id + (!empty($this->idPageview) ? '&pv_id=' . urlencode($this->idPageview) : '') . + + // Attribution information, so that Goal conversions are attributed to the right referrer or campaign + // Campaign name + (!empty($this->attributionInfo[0]) ? '&_rcn=' . urlencode($this->attributionInfo[0]) : '') . + // Campaign keyword + (!empty($this->attributionInfo[1]) ? '&_rck=' . urlencode($this->attributionInfo[1]) : '') . + // Timestamp at which the referrer was set + (!empty($this->attributionInfo[2]) ? '&_refts=' . $this->attributionInfo[2] : '') . + // Referrer URL + (!empty($this->attributionInfo[3]) ? '&_ref=' . urlencode($this->attributionInfo[3]) : '') . + + // custom location info + (!empty($this->country) ? '&country=' . urlencode($this->country) : '') . + (!empty($this->region) ? '®ion=' . urlencode($this->region) : '') . + (!empty($this->city) ? '&city=' . urlencode($this->city) : '') . + (!empty($this->lat) ? '&lat=' . urlencode($this->lat) : '') . + (!empty($this->long) ? '&long=' . urlencode($this->long) : '') . + $customFields . $customDimensions . + (!$this->sendImageResponse ? '&send_image=0' : '') . + + // client hints + (!empty($this->clientHints) ? ('&uadata=' . urlencode(json_encode($this->clientHints))) : '') . + + // DEBUG + $this->DEBUG_APPEND_URL; + + if (!empty($this->idPageview)) { + $url .= + ($this->networkTime !== false ? '&pf_net=' . ((int)$this->networkTime) : '') . + ($this->serverTime !== false ? '&pf_srv=' . ((int)$this->serverTime) : '') . + ($this->transferTime !== false ? '&pf_tfr=' . ((int)$this->transferTime) : '') . + ($this->domProcessingTime !== false ? '&pf_dm1=' . ((int)$this->domProcessingTime) : '') . + ($this->domCompletionTime !== false ? '&pf_dm2=' . ((int)$this->domCompletionTime) : '') . + ($this->onLoadTime !== false ? '&pf_onl=' . ((int)$this->onLoadTime) : ''); + $this->clearPerformanceTimings(); + } + + foreach ($this->ecommerceView as $param => $value) { + $url .= '&' . $param . '=' . urlencode($value); + } + + // Reset page level custom variables after this page view + $this->ecommerceView = []; + $this->pageCustomVar = []; + $this->eventCustomVar = []; + $this->clearCustomDimensions(); + $this->clearCustomTrackingParameters(); + + // force new visit only once, user must call again setForceNewVisit() + $this->forcedNewVisit = false; + + return $url; + } + + + /** + * Returns a first party cookie which name contains $name + * + * @return string String value of cookie, or false if not found + * @ignore + */ + protected function getCookieMatchingName(string $name) + { + if ($this->configCookiesDisabled) { + return false; + } + if (!is_array($_COOKIE)) { + return false; + } + $name = $this->getCookieName($name); + + // Matomo cookie names use dots separators in matomo.js, + // but PHP Replaces . with _ http://www.php.net/manual/en/language.variables.predefined.php#72571 + $name = str_replace('.', '_', $name); + foreach ($_COOKIE as $cookieName => $cookieValue) { + if (strpos($cookieName, $name) !== false) { + return $cookieValue; + } + } + + return false; + } + + /** + * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" + * will return "/dir1/dir2/index.php" + * + * @ignore + */ + protected static function getCurrentScriptName(): string + { + $url = ''; + if (!empty($_SERVER['PATH_INFO'])) { + $url = $_SERVER['PATH_INFO']; + } else { + if (!empty($_SERVER['REQUEST_URI'])) { + if (($pos = strpos($_SERVER['REQUEST_URI'], '?')) !== false) { + $url = substr($_SERVER['REQUEST_URI'], 0, $pos); + } else { + $url = $_SERVER['REQUEST_URI']; + } + } + } + if (empty($url) && isset($_SERVER['SCRIPT_NAME'])) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (empty($url)) { + $url = '/'; + } + + if (!empty($url) && $url[0] !== '/') { + $url = '/' . $url; + } + + return $url; + } + + /** + * If the current URL is 'http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" + * will return 'http' + * + * @return string 'https' or 'http' + * @ignore + */ + protected static function getCurrentScheme(): string + { + if (isset($_SERVER['HTTPS']) + && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] === true) + ) { + return 'https'; + } + + return 'http'; + } + + /** + * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" + * will return "http://example.org" + * + * @ignore + */ + protected static function getCurrentHost(): string + { + if (isset($_SERVER['HTTP_HOST'])) { + return $_SERVER['HTTP_HOST']; + } + + return 'unknown'; + } + + /** + * If current URL is "http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2" + * will return "?param1=value1¶m2=value2" + * + * @ignore + */ + protected static function getCurrentQueryString(): string + { + $url = ''; + if (isset($_SERVER['QUERY_STRING']) + && !empty($_SERVER['QUERY_STRING']) + ) { + $url .= '?' . $_SERVER['QUERY_STRING']; + } + + return $url; + } + + /** + * Returns the current full URL (scheme, host, path and query string. + * + * @ignore + */ + protected static function getCurrentUrl(): string + { + return self::getCurrentScheme() . '://' + . self::getCurrentHost() + . self::getCurrentScriptName() + . self::getCurrentQueryString(); + } + + /** + * Sets the first party cookies as would the matomo.js + * All cookies are supported: 'id' and 'ses' and 'ref' and 'cvar' cookies. + * @return $this + */ + protected function setFirstPartyCookies() + { + if ($this->configCookiesDisabled) { + return $this; + } + + if (empty($this->cookieVisitorId)) { + $this->loadVisitorIdCookie(); + } + + // Set the 'ref' cookie + $attributionInfo = $this->getAttributionInfo(); + if (!empty($attributionInfo)) { + $this->setCookie('ref', $attributionInfo, $this->configReferralCookieTimeout); + } + + // Set the 'ses' cookie + $this->setCookie('ses', '*', $this->configSessionCookieTimeout); + + // Set the 'id' cookie + $cookieValue = $this->getVisitorId() . '.' . $this->createTs; + $this->setCookie('id', $cookieValue, $this->configVisitorCookieTimeout); + + // Set the 'cvar' cookie + $this->setCookie('cvar', json_encode($this->visitorCustomVar), $this->configSessionCookieTimeout); + return $this; + } + + /** + * Sets a first party cookie to the client to improve dual JS-PHP tracking. + * + * This replicates the matomo.js tracker algorithms for consistency and better accuracy. + * + * @return $this + */ + protected function setCookie(string $cookieName, $cookieValue, int $cookieTTL) + { + $cookieExpire = $this->currentTs + $cookieTTL; + if (!headers_sent()) { + $header = 'Set-Cookie: ' . rawurlencode($this->getCookieName($cookieName)) . '=' . rawurlencode($cookieValue) + . (empty($cookieExpire) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', $cookieExpire) . ' GMT') + . (empty($this->configCookiePath) ? '' : '; path=' . $this->configCookiePath) + . (empty($this->configCookieDomain) ? '' : '; domain=' . rawurlencode($this->configCookieDomain)) + . (!$this->configCookieSecure ? '' : '; secure') + . (!$this->configCookieHTTPOnly ? '' : '; HttpOnly') + . (!$this->configCookieSameSite ? '' : '; SameSite=' . rawurlencode($this->configCookieSameSite)); + + header($header, false); + } + return $this; + } + + /** + * @return array + */ + protected function getCustomVariablesFromCookie() + { + $cookie = $this->getCookieMatchingName('cvar'); + if (!$cookie) { + return []; + } + + return json_decode($cookie, true); + } + + /** + * Sets a cookie to be sent to the tracking server. + * + * @param $name + * @param $value + */ + public function setOutgoingTrackerCookie($name, $value) + { + if ($value === null) { + unset($this->outgoingTrackerCookies[$name]); + } + else { + $this->outgoingTrackerCookies[$name] = $value; + } + } + + /** + * Gets a cookie which was set by the tracking server. + * + * @param $name + * + * @return bool|string + */ + public function getIncomingTrackerCookie($name) + { + if (isset($this->incomingTrackerCookies[$name])) { + return $this->incomingTrackerCookies[$name]; + } + + return false; + } + + /** + * Reads incoming tracking server cookies. + * + * @param array $headers Array with HTTP response headers as values + */ + protected function parseIncomingCookies(array $headers): void + { + $this->incomingTrackerCookies = []; + + if (!empty($headers)) { + $headerName = 'set-cookie:'; + $headerNameLength = strlen($headerName); + + foreach($headers as $header) { + if (strpos(strtolower($header), $headerName) !== 0) { + continue; + } + $cookies = trim(substr($header, $headerNameLength)); + $posEnd = strpos($cookies, ';'); + if ($posEnd !== false) { + $cookies = substr($cookies, 0, $posEnd); + } + parse_str($cookies, $this->incomingTrackerCookies); + } + } + } + + /** + * Returns true if the given user agent belongs to a known AI bot. + * + * @param string $userAgent + * @return bool + */ + public static function isUserAgentAIBot(string $userAgent): bool + { + if (empty($userAgent)) { + return false; + } + + foreach (self::AI_BOT_USER_AGENT_SUBSTRINGS as $substring) { + if (stripos($userAgent, $substring) !== false) { + return true; + } + } + return false; + } +} + +/** + * Helper function to quickly generate the URL to track a page view. + * + * @param $idSite + * @param string $documentTitle + * @return string + */ +function Matomo_getUrlTrackPageView($idSite, $documentTitle = '') +{ + $tracker = new MatomoTracker($idSite); + + return $tracker->getUrlTrackPageView($documentTitle); +} + +/** + * Helper function to quickly generate the URL to track a goal. + * + * @param $idSite + * @param $idGoal + * @param float $revenue + * @return string + */ +function Matomo_getUrlTrackGoal($idSite, $idGoal, $revenue = 0.0) +{ + $tracker = new MatomoTracker($idSite); + + return $tracker->getUrlTrackGoal($idGoal, $revenue); +} + +/** + * Ensure PiwikTracker class is available as well + * + * @deprecated + */ +if (!class_exists('\WP_Piwik\PiwikTracker')) { + include_once('PiwikTracker.php'); +} diff --git a/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/PiwikTracker.php b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/PiwikTracker.php new file mode 100644 index 00000000..c48c9026 --- /dev/null +++ b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/PiwikTracker.php @@ -0,0 +1,51 @@ +setTokenAuth($matomoToken); + +// Track page view +$matomoTracker->doTrackPageView($matomoPageTitle); +``` + +## Requirements: +* JSON extension (json_decode, json_encode) +* cURL or stream extension (to issue the HTTPS request to Matomo) + +## Installation + +### Composer + +``` +composer require matomo/matomo-php-tracker +``` + +### Manually + +Alternatively, you can download the files and require the Matomo tracker manually: + +``` +require_once("MatomoTracker.php"); +``` + +## License + +Released under the [BSD License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/composer.json b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/composer.json new file mode 100644 index 00000000..389aaeeb --- /dev/null +++ b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/composer.json @@ -0,0 +1,44 @@ +{ + "name": "matomo\/matomo-php-tracker", + "description": "PHP Client for Matomo Analytics Tracking API", + "keywords": [ + "matomo", + "piwik", + "tracker", + "analytics" + ], + "homepage": "https:\/\/matomo.org", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "The Matomo Team", + "email": "hello@matomo.org", + "homepage": "https:\/\/matomo.org\/team\/" + } + ], + "support": { + "forum": "https:\/\/forum.matomo.org\/", + "issues": "https:\/\/github.com\/matomo-org\/matomo-php-tracker\/issues", + "source": "https:\/\/github.com\/matomo-org\/matomo-php-tracker" + }, + "require": { + "php": "^7.2 || ^8.0", + "ext-json": "*" + }, + "suggest": { + "ext-curl": "Using this extension to issue the HTTPS request to Matomo" + }, + "autoload": { + "classmap": [ + "." + ] + }, + "autoload-dev": { + "psr-4": { + "\\": "tests\/" + } + }, + "require-dev": { + "phpunit\/phpunit": "^8.5 || ^9.3 || ^10.1" + } +} \ No newline at end of file diff --git a/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/phpunit.xml.dist b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/phpunit.xml.dist new file mode 100644 index 00000000..cb8caf5e --- /dev/null +++ b/wp-content/plugins/wp-piwik/libs/matomo-php-tracker/phpunit.xml.dist @@ -0,0 +1,12 @@ + + + + + + ./tests/Unit + + + diff --git a/wp-content/plugins/wp-piwik/misc/track_ai_bot.php b/wp-content/plugins/wp-piwik/misc/track_ai_bot.php new file mode 100644 index 00000000..67986c4c --- /dev/null +++ b/wp-content/plugins/wp-piwik/misc/track_ai_bot.php @@ -0,0 +1,108 @@ + directive. It should have as + * few dependencies as possible, and load as few PHP files as possible. + * + * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + */ +function wp_piwik_track_if_ai_bot() { + global $wpdb; + + if ( + ( ! defined( 'WP_CACHE' ) || ! WP_CACHE ) + && empty( $_GET['mtm_esi'] ) + ) { // advanced-cache.php not in use and we are not tracking via esi:include + return; + } + + if ( isset( $_GET['mtm_esi'] ) ) { // executing via esi:include directive + $GLOBALS['WP_PIWIK_IN_ESI'] = true; + } + + require_once __DIR__ . '/../libs/matomo-php-tracker/MatomoTracker.php'; + + // check user agent is AI bot first thing, so if it is a normal request we do + // as little extra work as possible + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $user_agent = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? stripslashes( $_SERVER['HTTP_USER_AGENT'] ) : ''; + if ( ! \WP_Piwik\MatomoTracker::isUserAgentAIBot( $user_agent ) ) { + return; + } + + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound + $GLOBALS['wp_plugin_paths'] = array(); + + if ( ! defined( 'ABSPATH' ) ) { + // being called from a esi:include directive + define( 'SHORTINIT', true ); + + $wp_config_file = dirname( dirname( dirname( dirname( __DIR__ ) ) ) ) . '/wp-config.php'; + if ( ! is_file( $wp_config_file ) && ! empty( $_SERVER['SCRIPT_FILENAME'] ) ) { + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $script_filename = stripslashes( $_SERVER['SCRIPT_FILENAME'] ); + $wp_config_file = dirname( dirname( dirname( dirname( dirname( $script_filename ) ) ) ) ) . '/wp-config.php'; + } + + require_once $wp_config_file; + } else { + // being called from request that uses advanced-cache.php + require_once ABSPATH . WPINC . '/class-wp-list-util.php'; + require_once ABSPATH . WPINC . '/class-wp-token-map.php'; + require_once ABSPATH . WPINC . '/formatting.php'; + require_once ABSPATH . WPINC . '/functions.php'; + } + + require_once ABSPATH . WPINC . '/link-template.php'; + require_once ABSPATH . WPINC . '/general-template.php'; + require_once ABSPATH . WPINC . '/http.php'; + require_once ABSPATH . WPINC . '/class-wp-http.php'; + require_once ABSPATH . WPINC . '/class-wp-http-streams.php'; + require_once ABSPATH . WPINC . '/class-wp-http-curl.php'; + require_once ABSPATH . WPINC . '/class-wp-http-proxy.php'; + require_once ABSPATH . WPINC . '/class-wp-http-cookie.php'; + require_once ABSPATH . WPINC . '/class-wp-http-encoding.php'; + require_once ABSPATH . WPINC . '/class-wp-http-response.php'; + require_once ABSPATH . WPINC . '/class-wp-http-requests-response.php'; + require_once ABSPATH . WPINC . '/class-wp-http-requests-hooks.php'; + + require_once __DIR__ . '/../wp-piwik.php'; + + if ( empty( $wpdb ) ) { + require_wp_db(); + wp_set_wpdb_vars(); + } + + wp_start_object_cache(); + + if ( ! defined( 'WPMU_PLUGIN_DIR' ) ) { + wp_plugin_directory_constants(); + } + + // url is passed to tracker so we don't want to modify it + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $url = ! empty( $_REQUEST['mtm_url'] ) ? wp_unslash( $_REQUEST['mtm_url'] ) : null; + + $wp_piwik = new \WP_Piwik(); + $settings = new \WP_Piwik\Settings( $wp_piwik ); + $ai_bot_tracking = new \WP_Piwik\AIBotTracking( $settings, \WP_Piwik::get_logger() ); + $ai_bot_tracking->do_ai_bot_tracking( $url ); +} + +register_shutdown_function( 'wp_piwik_track_if_ai_bot' ); diff --git a/wp-content/plugins/wp-piwik/package-lock.json b/wp-content/plugins/wp-piwik/package-lock.json new file mode 100644 index 00000000..c8be79b6 --- /dev/null +++ b/wp-content/plugins/wp-piwik/package-lock.json @@ -0,0 +1,131 @@ +{ + "name": "wp-matomo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@fastify/pre-commit": "^2.2.1" + } + }, + "node_modules/@fastify/pre-commit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@fastify/pre-commit/-/pre-commit-2.2.1.tgz", + "integrity": "sha512-EluAZU4mFnCJfb6RyWFpWvEIAwdchipoiWMSRkQEaQ6ubbf6UVzYuXKSSZJR36SgtgZmKV5oRMxxwMNta5hskg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "which": "^5.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + } + } +} diff --git a/wp-content/plugins/wp-piwik/package.json b/wp-content/plugins/wp-piwik/package.json new file mode 100644 index 00000000..25b299f6 --- /dev/null +++ b/wp-content/plugins/wp-piwik/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "phpcs": "./vendor/bin/phpcs", + "phpcbf": "./vendor/bin/phpcbf" + }, + "devDependencies": { + "@fastify/pre-commit": "^2.2.1" + }, + "pre-commit": [ + "phpcs" + ] +} diff --git a/wp-content/plugins/wp-piwik/proxy/config.php b/wp-content/plugins/wp-piwik/proxy/config.php index aedcd3f4..77132a05 100644 --- a/wp-content/plugins/wp-piwik/proxy/config.php +++ b/wp-content/plugins/wp-piwik/proxy/config.php @@ -1,5 +1,8 @@ getGlobalOption ( 'piwik_mode' )) { +switch ($settings->get_global_option ( 'piwik_mode' )) { case 'php' : - $PIWIK_URL = $settings->getGlobalOption ( 'proxy_url' ); + $PIWIK_URL = $settings->get_global_option ( 'proxy_url' ); break; case 'cloud' : - $PIWIK_URL = 'https://' . $settings->getGlobalOption ( 'piwik_user' ) . '.innocraft.cloud/'; + $PIWIK_URL = 'https://' . $settings->get_global_option ( 'piwik_user' ) . '.innocraft.cloud/'; break; case 'cloud-matomo' : - $PIWIK_URL = 'https://' . $settings->getGlobalOption ( 'matomo_user' ) . '.matomo.cloud/'; + $PIWIK_URL = 'https://' . $settings->get_global_option ( 'matomo_user' ) . '.matomo.cloud/'; break; default : - $PIWIK_URL = $settings->getGlobalOption ( 'piwik_url' ); + $PIWIK_URL = $settings->get_global_option ( 'piwik_url' ); + break; } -if (substr ( $PIWIK_URL, 0, 2 ) == '//') +if ( substr ( $PIWIK_URL, 0, 2 ) == '//' ) { $PIWIK_URL = $protocol . ':' . $PIWIK_URL; +} -$TOKEN_AUTH = $settings->getGlobalOption ( 'piwik_token' ); -$timeout = $settings->getGlobalOption ( 'connection_timeout' ); +$TOKEN_AUTH = $settings->get_global_option ( 'piwik_token' ); +$timeout = $settings->get_global_option ( 'connection_timeout' ); $useCurl = ( - (function_exists('curl_init') && ini_get('allow_url_fopen') && $settings->getGlobalOption('http_connection') == 'curl') || (function_exists('curl_init') && !ini_get('allow_url_fopen')) + (function_exists('curl_init') && ini_get('allow_url_fopen') && $settings->get_global_option('http_connection') == 'curl') || (function_exists('curl_init') && !ini_get('allow_url_fopen')) ); -$settings->getGlobalOption ( 'http_connection' ); +$settings->get_global_option ( 'http_connection' ); -ini_set ( 'display_errors', 0 ); \ No newline at end of file +ini_set ( 'display_errors', 0 ); diff --git a/wp-content/plugins/wp-piwik/proxy/proxy.php b/wp-content/plugins/wp-piwik/proxy/proxy.php index e1090446..81227e19 100644 --- a/wp-content/plugins/wp-piwik/proxy/proxy.php +++ b/wp-content/plugins/wp-piwik/proxy/proxy.php @@ -126,6 +126,10 @@ if (strpos($path, 'piwik.php') === 0 || strpos($path, 'matomo.php') === 0) { 'cip' => getVisitIp(), 'token_auth' => $TOKEN_AUTH, ); + + if (!isset($_GET['token_auth']) && !isset($_POST['token_auth'])) { + sanitizeTrackingOverrideParams($_GET); + } } $url = $MATOMO_URL . $path; @@ -293,8 +297,14 @@ function getHttpContentAndStatus($url, $timeout, $user_agent) // if there's POST data, send our proxy request as a POST if (!empty($_POST)) { $postBody = file_get_contents("php://input"); + if (!isset($_GET['token_auth']) && !isset($_POST['token_auth'])) { + $didSanitizePostParams = sanitizeTrackingOverrideParams($_POST); + if ($didSanitizePostParams) { + $postBody = http_build_query($_POST); + } + } - $stream_options['http']['method'] = 'POST'; + $stream_options['http']['method'] = 'POST'; $stream_options['http']['header'][] = "Content-type: application/x-www-form-urlencoded"; $stream_options['http']['header'][] = "Content-Length: " . strlen($postBody); $stream_options['http']['content'] = $postBody; @@ -365,6 +375,20 @@ function getHttpContentAndStatus($url, $timeout, $user_agent) } +function sanitizeTrackingOverrideParams(&$params) +{ + $didSanitizeParams = false; + $queryParamsToUnset = ['cdt', 'country', 'region', 'city', 'lat', 'long', 'cip']; + foreach ($queryParamsToUnset as $queryParamToUnset) { + if (isset($params[$queryParamToUnset])) { + unset($params[$queryParamToUnset]); + $didSanitizeParams = true; + } + } + + return $didSanitizeParams; +} + function sendHeader($header, $replace = true) { headers_sent() || header($header, $replace); diff --git a/wp-content/plugins/wp-piwik/readme.txt b/wp-content/plugins/wp-piwik/readme.txt index ccdcfa2c..af08dbc1 100644 --- a/wp-content/plugins/wp-piwik/readme.txt +++ b/wp-content/plugins/wp-piwik/readme.txt @@ -1,9 +1,9 @@ -=== Connect Matomo (WP-Matomo, WP-Piwik) === +=== Connect Matomo - Analytics Dashboard for WordPress === Contributors: Braekling Requires at least: 5.0 -Tested up to: 6.3 -Stable tag: 1.0.30 +Tested up to: 6.9.4 +Stable tag: 1.1.5 Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6046779 Tags: matomo, tracking, statistics, stats, analytics @@ -11,14 +11,16 @@ Adds Matomo (former Piwik) statistics to your WordPress dashboard and is also ab == Description == -If you are not yet using Matomo On-Premise, Matomo Cloud or hosting your own instance of Matomo, please use the [Matomo for WordPress plugin](https://wordpress.org/plugins/matomo/). +**Version 1.1.4 includes several important security related fixes, it is highly recommended to update to this version.** + +If you are not yet using Matomo On-Premise, Matomo Cloud or hosting your own instance of Matomo, please use the [Matomo for WordPress plugin](https://wordpress.org/plugins/matomo/). This plugin uses the Matomo API to show your Matomo statistics in your WordPress dashboard. It's also able to add the Matomo tracking code to your blog and to do some modifications to the tracking code. Additionally, WP-Matomo supports WordPress networks and manages multiple sites and their tracking codes. To use this plugin the Matomo web analytics application is required. If you do not already have a Matomo setup (e.g., provided by your web hosting service), you have two simple options: use either a [self-hosted Matomo](http://matomo.org/) or a [cloud-hosted Matomo by InnoCraft](https://www.innocraft.cloud/?pk_campaign=WP-Piwik). **Requirements:** PHP 7.0 (or higher), WordPress 5.0 (or higher), Matomo 4.0 (or higher) - + **Languages:** English, Albanian, Chinese, Dutch, French, German, Greek, Hungarian, Italian, Polish, Portuguese (Brazil). Partially supported: Azerbaijani, Belarusian, Hindi, Lithuanian, Luxembourgish, Norwegian, Persian, Romanian, Russian, Spanish, Swedish, Turkish, Ukrainian = What is Matomo? = @@ -122,7 +124,7 @@ Thank you very much! :-) There are two differents methods to use WP-Matomo in a multisite environment: -* As a Site Specific Plugin it behaves like a plugin installed on a simple WordPress blog. Each user can enable, configure and use WP-Matomo on his own. Users can even use their own Matomo instances (and accordingly they have to). +* As a Site Specific Plugin it behaves like a plugin installed on a simple WordPress blog. Each user can enable, configure and use WP-Matomo on his own. Users can even use their own Matomo instances (and accordingly they have to). * Using WP-Matomo as a Network Plugin equates to a central approach. A single Matomo instance is used and the site admin configures the plugin completely. Users are just allowed to see their own statistics, site admins can see each blog's stats. *Site Specific Plugin* @@ -145,6 +147,33 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi == Changelog == += 1.1.5 = +* Update Matomo logo and screenshots. +* Update tracker proxy code with latest changes. +* Ensure query strings are correctly created when arg_separator.input is not '&'. + += 1.1.4 = +* Bug fix: fix URL to settings displayed upon installation. +* Remove donation form. +* Several assorted security related fixes. + += 1.1.3 = +* Replaced wp_unslash with stripslashes to address cases where wp_unslash may be undefined. + += 1.1.2 = +* Allow $wpRootDir variable in the proxy config.php file to be defined by the WP_MATOMO_WP_ROOT_DIR environment variable if present. +* Using phpcs and phpstan fix several issues including several vulnerabilities. + += 1.1.1 = +* Security bug fix: convert custom variable name and values to JSON before using in tracking code. + += 1.1.0 = +* Support for tracking AI bots to Matomo. + += 1.0.31 = +* Do not display value of persisted Matomo token in settings page. +* Fix notice when persisted notifications value is for some reason not an array. + = 1.0.30 = * Fix settings behavior * Fix auto configuration in PHP API mode @@ -168,7 +197,7 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi * Fix JavaScript typos on settings page which broke some interface functionality * Fix proxy path on multisite networks (thanks to caveman99, [details](https://github.com/braekling/WP-Matomo/pull/98)) * Fix array key warnings (thanks to goaround, [details](https://github.com/braekling/WP-Matomo/pull/102)) -* Fixed a bug in proxy config.php to avoid adding the protocol twice to the Matomo URL +* Fixed a bug in proxy config.php to avoid adding the protocol twice to the Matomo URL * Proxy script will run proxy/config.local.php before proxy/config.php to set an individual WordPress root directory via $wpRootDir = 1.0.26 = @@ -358,7 +387,7 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi * Bugfix: Keep sure the revision ID is stored and avoid re-installing the plugin again and again * Bugfix: http/pro - after configuration the settings page had to be reloaded once to start working * Typo fixes - + = 0.10.0.6 = * Bugfix: Option storage bug if WP-Matomo is used as single site plugin on blog networks * Bugfix: WP-Matomo will work without Matomo superuser access, again @@ -413,7 +442,7 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi * Improvement: Only activate/ load admin components if an admin page is actually loaded. Thanks to Michael! * Bugfix: Proxy tracking will work again. Matomo 2.7 or higher is recommended. * Bugfix: Avoid a PHP notice in dashboard -* NOTE: If you update Matomo and use the "add tracking code" feature, please also update your WP-Matomo tracking code: Just open the WP-Matomo tracking code settings and save them again. +* NOTE: If you update Matomo and use the "add tracking code" feature, please also update your WP-Matomo tracking code: Just open the WP-Matomo tracking code settings and save them again. = 0.9.9.12 = * Bugfix: Avoid forced relogin on site change (WP network) @@ -470,7 +499,7 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi * Use Transients API (one week caching) * Option: Track visitors without JavaScript, see http://piwik.org/faq/how-to/#faq_176 -= 0.9.9.3 = += 0.9.9.3 = * Sparkline script update (IE 10 compatibility) * Syntax error fixes @@ -486,7 +515,7 @@ Add WP-Matomo to your /wp-content/plugins folder and enable it as [Network Plugi * Made