From cf81e8f2aaa708e15bc62bae446afa08076c248f Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 20:25:38 +0200 Subject: [PATCH 01/14] * add default config in config.ini.example * add i18n resources * add first draft of widget --- config/config.ini.example | 5 + lang/de_DE.ini | 184 +++++++++++++++--------------- lang/en_US.ini | 6 +- system/admin/views/config-widget.html.php | 23 ++++ 4 files changed, 127 insertions(+), 91 deletions(-) diff --git a/config/config.ini.example b/config/config.ini.example index 31ba57a..53bfac7 100644 --- a/config/config.ini.example +++ b/config/config.ini.example @@ -134,3 +134,8 @@ views.root = "themes/twentyfifteen" ; Framework config. No need to edit. views.layout = "layout" + +; Matomo Config +matomo.url = "" +matomo.token = "" +matomo.id = "" \ No newline at end of file diff --git a/lang/de_DE.ini b/lang/de_DE.ini index d1dfd3e..f6b889e 100644 --- a/lang/de_DE.ini +++ b/lang/de_DE.ini @@ -26,7 +26,7 @@ Filename = "Datei Name" Follow = "Folgen" Image_post = "Bild-Beitrag" Image_post_comment = "Erstelle einen Beitrag mit hervorgehobenem Bild" -Import = "Importieren" +Import = "Import" Link_post = "Link-Beitrag" Link_post_comment = "Erstelle einen Beitrag mit hervorgehobenem Link" Login = "Anmelden" @@ -69,12 +69,12 @@ Search_for = "Suche nach" Static_page = "Statische Seite" Static_page_comment = "Erstelle eine statische Seite" Static_pages = "Statische Seiten" -Tag = "Etikett" -Tags = "Stichworte" +Tag = "Tag" +Tags = "Tags" Title = "Titel" Uncategorized = "Sonstiges" Uncategorized_comment = "Themen die keine Kategorie brauchen oder nicht in andere existierende Kategorien passen." -Update = "Aktualisieren" +Update = "Update" Update_draft = "Draft aktualisieren" Update_post = "Beitrag aktualisieren" Video_post = "Video-Beitrag" @@ -84,36 +84,36 @@ Views = "Ansichten" Your_recent_posts = "Deine letzten Beiträge" by = "von" read_more = "weiterlesen" -Dashboard="Instrumententafel" -Posts_list="Beitragsliste" -Posts_draft="Beiträge Entwurf" -Menus="Menü-Editor" -Settings="die Einstellungen" -Tools="Werkzeuge" -Check_update="Aktualisierung überprüfen" -Import_RSS="RSS importieren" -User="Benutzer" -Proudly_powered_by="Stolz angetrieben von" -Home="Zuhause" -Type_to_search="Tippe um zu suchen" -Admin_panel_style_based_on="Admin-Panel-Stil basierend auf" -Sign_in_to_start_your_session="Melden Sie sich an, um Ihre Sitzung zu starten" -Back_to="Zurück zu" -Login="Anmeldung" -User="Benutzer" -Password="Passwort" -Comma_separated_values="Komma-getrennte Werte" -If_leave_empty_we_will_excerpt_it_from_the_content_below="Wenn Sie das Feld leer lassen, wird es aus dem folgenden Inhalt extrahiert" +Dashboard="Dashboard" +Posts_list="Posts list" +Posts_draft="Posts draft" +Menus="Menu Editor" +Settings="Settings" +Tools="Tools" +Check_update="Check update" +Import_RSS="Import RSS" +User="User" +Proudly_powered_by="Proudly powered by" +Home="Home" +Type_to_search="Type to search" +Admin_panel_style_based_on="Admin panel style based on" +Sign_in_to_start_your_session="Sign in to start your session" +Back_to="Back to" +Login="Login" +User="User" +Password="Password" +Comma_separated_values="Comma separated values" +If_leave_empty_we_will_excerpt_it_from_the_content_below="If leave empty we will excerpt it from the content below" optional="optional" -If_the_url_leave_empty_we_will_use_the_post_title="Wenn die URL leer bleibt, verwenden wir den Post-Titel" -If_the_url_leave_empty_we_will_use_the_page_title="Wenn die URL leer bleibt, verwenden wir den Seitentitel" -Only="Nur" -Featured_Audio="Empfohlenes Audio" -Featured_Video="Empfohlenes Video" -Featured_Image="Ausgewähltes Bild" -Featured_Quote="Ausgewähltes Zitat" -Featured_Link="Ausgewählter Link" -Content="Inhalt" +If_the_url_leave_empty_we_will_use_the_post_title="If the url leave empty we will use the post title" +If_the_url_leave_empty_we_will_use_the_page_title="If the url leave empty we will use the page title" +Only="Only" +Featured_Audio="Featured Audio" +Featured_Video="Featured Video" +Featured_Image="Featured Image" +Featured_Quote="Featured Quote" +Featured_Link="Featured Link" +Content="Content" Preview="Preview" Enter_image_URL="Enter image URL" Upload="Upload" @@ -122,71 +122,71 @@ This_page_doesnt_exist="This page doesn't exist !" Would_you_like_to_try_our="Would you like to try our " homepage="homepage" instead="instead" -Your_backups="Ihre Backups" -Create_backup="Ein Backup erstellen" -All_cache_has_been_deleted="Der gesamte Cache wurde gelöscht!" -Edit_category="Kategorie bearbeiten" +Your_backups="Your backups" +Create_backup="Create backup" +All_cache_has_been_deleted="All cache has been deleted !" +Edit_category="Edit category" Date="Date" -Time="Zeit" -No_available_backup="Derzeit ist kein Backup verfügbar." -You_dont_have_permission_to_access_this_page="Sie haben keine Berechtigung, auf diese Seite zuzugreifen." -Save_category="Kategorie speichern" -Import_RSS_Feed_2.0="RSS-Feed 2.0 importieren" -By_using_this_importer_you_are_agree_if_the_feed_is_yours_or_at_least_you_have_the_authority_to_publish_it="Durch die Verwendung dieses Importeurs stimmen Sie zu, ob der Feed Ihnen gehört oder zumindest die Berechtigung hat, ihn zu veröffentlichen." +Time="Time" +No_available_backup="No available backup at this time." +You_dont_have_permission_to_access_this_page="You don't have permission to access this page" +Save_category="Save category" +Import_RSS_Feed_2.0="Import RSS Feed 2.0" +By_using_this_importer_you_are_agree_if_the_feed_is_yours_or_at_least_you_have_the_authority_to_publish_it="By using this importer you are agree if the feed is yours or at least you have the authority to publish it." Feed_Url="Feed URL" -Add_source_link_optional="Quelllink hinzufügen (optional)" -Import_Feed="Importieren Feed starten" -At_the_moment_you_are_using_auto_generated_menu="Im Moment verwenden Sie ein automatisch generiertes Menü. " -Add_menu="Menü hinzufügen" +Add_source_link_optional="Add source link (optional)" +Import_Feed="Start Import Feed" +At_the_moment_you_are_using_auto_generated_menu="At the moment you are using auto generated menu." +Add_menu="Add menu" Name="Name" -Link_name="Linkname" +Link_name="Link name" Slug="Slug" -item_slug="Link URL einfügen" -CSS_Class_Optional="CSS-Klasse (optional) " -item_class="CSS Klasse einfügen" -Add_link="Link hinzufügen" -Save_Edit="Speichern Bearbeiten" -Save_Menu="Menü speichern" -Add_new_post="Neuen Beitrag hinzufügen" -Add_new_page="Neue Seite hinzufügen" -Update_Available="Update verfügbar" -Congrats_You_have_the_latest_version_of_HTMLy="Herzlichen Glückwunsch! Sie haben die neueste Version von HTMLy." -Update_to="Aktualisieren zu" -now="jetzt" -Performance_Settings="Leistungseinstellungen" -General="Allgemeines" -Reading="Lesen" +item_slug="Insert Link URL" +CSS_Class_Optional="CSS Class (optional)" +item_class="Insert CSS class" +Add_link="Add link" +Save_Edit="Save Edit" +Save_Menu="Save menu" +Add_new_post="Add new post" +Add_new_page="Add new page" +Update_Available="Update Available" +Congrats_You_have_the_latest_version_of_HTMLy="Congrats! You have the latest version of HTMLy." +Update_to="Update to" +now="now" +Performance_Settings="Performance Settings" +General="General" +Reading="Reading" Widget="Widget" Metatags="Metatags" Performance="Performance" -Custom="Benutzerdefiniert" -General_Settings="Allgemeine Einstellungen" -Reading_Settings="Leseeinstellungen" -Widget_Settings="Widget Einstellungen" -Metatags_Settings="Metatags Einstellungen" -Custom_Settings="Benutzerdefinierte Einstellungen" -Address_URL="Adresse (URL)" -Blog_Title="Blog Titel" -Blog_Title_Placeholder="Mein HTMLy Blog " +Custom="Custom" +General_Settings="General Settings" +Reading_Settings="Reading Settings" +Widget_Settings="Widget Settings" +Metatags_Settings="Metatags Settings" +Custom_Settings="Custom Settings" +Address_URL="Address (URL)" +Blog_Title="Blog title" +Blog_Title_Placeholder="My HTMLy Blog" Tagline="Tagline" -Tagline_Placeholder="Datenbanklose PHP Blogging Plattform" -Tagline_description="Erklären Sie in wenigen Worten, worum es in diesem Blog geht." -Blog_Description="Erzählen Sie uns in einem Absatz mehr über Ihr Blog." -Language="Systemsprache" -Timezone="Zeitzone" -Date_Format="Datumsformat" -Blog_Theme="Blog Thema" -Copyright_Line="Copyright Zeile" -Copyright_Line_Placeholder="(c) Dein Name." -Save_Config="Konfiguration speichern" -Front_page_displays="Titelseite wird angezeigt" -Your_latest_blog_posts="Deine neuesten Blog-Beiträge" -Enable_blog_URL="URL aktivieren / bloggen" -Enable="Aktivieren" -Disable="Deaktivieren" -Posts_in_front_page_show_at_most="Beiträge auf der Titelseite werden höchstens angezeigt" -Blog_posts_displayed_as="Blog-Beiträge angezeigt als" -Full_post="Vollständiger Beitrag" +Tagline_Placeholder="Databaseless PHP Blogging Platform" +Tagline_description="In a few words, explain what this blog is about." +Blog_Description="In one paragraph, tell us more about your blog." +Language="System Language" +Timezone="Timezone" +Date_Format="Date Format" +Blog_Theme="Blog Theme" +Copyright_Line="Copyright line" +Copyright_Line_Placeholder="(c) Your name." +Save_Config="Save config" +Front_page_displays="Front page displays" +Your_latest_blog_posts="Your latest blog posts" +Enable_blog_URL="Enable /blog URL" +Enable="Enable" +Disable="Disable" +Posts_in_front_page_show_at_most="Posts in front page show at most" +Blog_posts_displayed_as="Blog posts displayed as" +Full_post="Full post" Summary="Summary" Summary_character="Summary character" Read_more_text="Read more text" @@ -248,3 +248,7 @@ Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" \ No newline at end of file diff --git a/lang/en_US.ini b/lang/en_US.ini index e377334..836a615 100644 --- a/lang/en_US.ini +++ b/lang/en_US.ini @@ -247,4 +247,8 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" \ No newline at end of file diff --git a/system/admin/views/config-widget.html.php b/system/admin/views/config-widget.html.php index 8365ea2..a5c98b2 100644 --- a/system/admin/views/config-widget.html.php +++ b/system/admin/views/config-widget.html.php @@ -150,6 +150,28 @@ +
+

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


@@ -170,4 +192,5 @@ + From bcbc1421705417fead210b7e2c95a251a4d13e8f Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 20:35:20 +0200 Subject: [PATCH 02/14] * add tracking config --- config/config.ini.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.ini.example b/config/config.ini.example index 53bfac7..15348e5 100644 --- a/config/config.ini.example +++ b/config/config.ini.example @@ -138,4 +138,5 @@ views.layout = "layout" ; Matomo Config matomo.url = "" matomo.token = "" -matomo.id = "" \ No newline at end of file +matomo.id = "" +matomo.tracking = "disable" \ No newline at end of file From 4eb2b26feb849c7741e3dd6f97769bb4e0bc2e74 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 20:36:24 +0200 Subject: [PATCH 03/14] * add selection for tracking type (disable/javascript/php) --- system/admin/views/config-widget.html.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/system/admin/views/config-widget.html.php b/system/admin/views/config-widget.html.php index a5c98b2..72256f0 100644 --- a/system/admin/views/config-widget.html.php +++ b/system/admin/views/config-widget.html.php @@ -171,6 +171,31 @@ +
+ +
+
+
+ checked> + +
+
+ checked> + +
+
+ checked> + +
+
+
+

From c0b87e2e61e6cd9a84afbe1f5886dfbbbaf9c5b9 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 20:41:41 +0200 Subject: [PATCH 04/14] * add englisch resources for (de/en/es/fr) --- lang/de_DE.ini | 3 ++- lang/en_US.ini | 3 ++- lang/es_ES.ini | 7 ++++++- lang/fr_FR.ini | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lang/de_DE.ini b/lang/de_DE.ini index f6b889e..6fcabeb 100644 --- a/lang/de_DE.ini +++ b/lang/de_DE.ini @@ -251,4 +251,5 @@ Nope="Nope" Matomo="Matomo" Matomo_URL="Server" Matomo_Token="Token" -Matomo_ID="Page ID" \ No newline at end of file +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/en_US.ini b/lang/en_US.ini index 836a615..2a9948d 100644 --- a/lang/en_US.ini +++ b/lang/en_US.ini @@ -251,4 +251,5 @@ Nope="Nope" Matomo="Matomo" Matomo_URL="Server" Matomo_Token="Token" -Matomo_ID="Page ID" \ No newline at end of file +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/es_ES.ini b/lang/es_ES.ini index 77e7a7c..1a7d554 100644 --- a/lang/es_ES.ini +++ b/lang/es_ES.ini @@ -246,4 +246,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/fr_FR.ini b/lang/fr_FR.ini index 41a5c9e..140bc07 100644 --- a/lang/fr_FR.ini +++ b/lang/fr_FR.ini @@ -248,3 +248,8 @@ Github_pre_release="Pré-release Github" Pre_release="Pré-release" Yes_Im_in="Oui, je suis prêt" Nope="Non !" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file From 790ebd327ce07042a80f00ab565cb8f53c04bd92 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 21:44:59 +0200 Subject: [PATCH 05/14] * add matomotracker.php * add function to track via matomo (js/php) --- system/plugins/matomo/MatomoTracker.php | 2201 +++++++++++++++++++++++++++++++ 1 file changed, 2201 insertions(+) create mode 100644 system/plugins/matomo/MatomoTracker.php diff --git a/system/plugins/matomo/MatomoTracker.php b/system/plugins/matomo/MatomoTracker.php new file mode 100644 index 0000000..5894692 --- /dev/null +++ b/system/plugins/matomo/MatomoTracker.php @@ -0,0 +1,2201 @@ +ecommerceItems = array(); + $this->attributionInfo = false; + $this->eventCustomVar = false; + $this->forcedDatetime = false; + $this->forcedNewVisit = false; + $this->networkTime = false; + $this->serverTime = false; + $this->transferTime = false; + $this->domProcessingTime = false; + $this->domCompletionTime = false; + $this->onLoadTime = false; + $this->pageCustomVar = false; + $this->ecommerceView = array(); + $this->customParameters = array(); + $this->customDimensions = array(); + $this->customData = false; + $this->hasCookies = false; + $this->token_auth = false; + $this->userAgent = false; + $this->country = false; + $this->region = false; + $this->city = false; + $this->lat = false; + $this->long = false; + $this->width = false; + $this->height = false; + $this->plugins = false; + $this->localHour = false; + $this->localMinute = false; + $this->localSecond = false; + $this->idPageview = false; + + $this->idSite = $idSite; + $this->urlReferrer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false; + $this->pageCharset = self::DEFAULT_CHARSET_PARAMETER_VALUES; + $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; + if (!empty($apiUrl)) { + self::$URL = $apiUrl; + } + + // Life of the visitor cookie (in sec) + $this->configVisitorCookieTimeout = 33955200; // 13 months (365 + 28 days) + // Life of the session cookie (in sec) + $this->configSessionCookieTimeout = 1800; // 30 minutes + // Life of the session cookie (in sec) + $this->configReferralCookieTimeout = 15768000; // 6 months + + // Visitor Ids in order + $this->userId = false; + $this->forcedVisitorId = false; + $this->cookieVisitorId = false; + $this->randomVisitorId = false; + + $this->setNewVisitorId(); + + $this->configCookiesDisabled = false; + $this->configCookiePath = self::DEFAULT_COOKIE_PATH; + $this->configCookieDomain = ''; + $this->configCookieSameSite = ''; + $this->configCookieSecure = false; + $this->configCookieHTTPOnly = false; + + $this->currentTs = time(); + $this->createTs = $this->currentTs; + + // Allow debug while blocking the request + $this->requestTimeout = 600; + $this->doBulkRequests = false; + $this->storedTrackingActions = array(); + + $this->sendImageResponse = true; + + $this->visitorCustomVar = $this->getCustomVariablesFromCookie(); + + $this->outgoingTrackerCookies = array(); + $this->incomingTrackerCookies = array(); + } + + /** + * 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. + * + * @param string $charset + * @return $this + */ + public function setPageCharset($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($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($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($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($network = null, $server = null, $transfer = null, $domProcessing = null, $domCompletion = null, $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() + { + $this->networkTime = false; + $this->serverTime = false; + $this->transferTime = false; + $this->domProcessingTime = false; + $this->domCompletionTime = false; + $this->onLoadTime = false; + } + + /** + * @deprecated + * @ignore + */ + public function setUrlReferer($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($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($id, $name, $value, $scope = 'visit') + { + if (!is_int($id)) { + throw new Exception("Parameter id to setCustomVariable should be an integer"); + } + 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($id, $scope = 'visit') + { + if ($scope == 'page') { + return isset($this->pageCustomVar[$id]) ? $this->pageCustomVar[$id] : false; + } elseif ($scope == 'event') { + return isset($this->eventCustomVar[$id]) ? $this->eventCustomVar[$id] : false; + } else { + if ($scope != 'visit') { + throw new Exception("Invalid 'scope' parameter value"); + } + } + if (!empty($this->visitorCustomVar[$id])) { + return $this->visitorCustomVar[$id]; + } + $cookieDecoded = $this->getCustomVariablesFromCookie(); + if (!is_int($id)) { + throw new Exception("Parameter to getCustomVariable should be an integer"); + } + 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() + { + $this->visitorCustomVar = array(); + $this->pageCustomVar = array(); + $this->eventCustomVar = array(); + } + + /** + * Sets a specific custom dimension + * + * @param int $id id of custom dimension + * @param string $value value for custom dimension + * @return $this + */ + public function setCustomDimension($id, $value) + { + $this->customDimensions['dimension'.(int)$id] = $value; + return $this; + } + + /** + * Clears all previously set custom dimensions + */ + public function clearCustomDimensions() + { + $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($id) + { + return $this->customDimensions['dimension'.(int)$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($trackingApiParameter, $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() + { + $this->customParameters = array(); + } + + /** + * 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. + * + * @param int $idSite + * @return $this + */ + public function setIdSite($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($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($userAgent) + { + $this->userAgent = $userAgent; + 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(). + * @param string $country + * @return $this + */ + public function setCountry($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(). + * @param string $region + * @return $this + */ + public function setRegion($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(). + * @param string $city + * @return $this + */ + public function setCity($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(). + * @param float $lat + * @return $this + */ + public function setLatitude($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(). + * @param float $long + * @return $this + */ + public function setLongitude($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() + { + $this->doBulkRequests = true; + } + + /** + * 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($domain = '', $path = '/', $secure = false, $httpOnly = false, $sameSite = '') + { + $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() + { + $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 + * @param string $cookieName + * @return string + */ + protected function getCookieName($cookieName) + { + // 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($documentTitle) + { + $this->generateNewPageviewId(); + + $url = $this->getUrlTrackPageView($documentTitle); + + return $this->sendRequest($url); + } + + private function generateNewPageviewId() + { + $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($category, $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($contentName, $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( + $interaction, + $contentName, + $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($keyword, $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($idGoal, $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($actionUrl, $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($sku, $name = '', $category = '', $price = 0.0, $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($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 = array('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 = array(); + + 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, + $grandTotal, + $subTotal = 0.0, + $tax = 0.0, + $shipping = 0.0, + $discount = 0.0 + ) + { + $url = $this->getUrlTrackEcommerceOrder($orderId, $grandTotal, $subTotal, $tax, $shipping, $discount); + + 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($sku = '', $name = '', $category = '', $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 = (float)$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 + * @return string + */ + private function forceDotAsSeparatorForDecimalPoint($value) + { + if (null === $value || false === $value) { + return $value; + } + + return str_replace(',', '.', $value); + } + + /** + * 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) + { + $url = $this->getUrlTrackEcommerce($grandTotal); + + 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 + */ + 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($documentTitle = '') + { + $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($category, $action, $name = false, $value = false) + { + $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($contentName, $contentPiece, $contentTarget) + { + $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 impression. + * + * @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($interaction, $contentName, $contentPiece, $contentTarget) + { + $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() + * @param string $keyword + * @param string $category + * @param int $countResults + * @return string + */ + public function getUrlTrackSiteSearch($keyword, $category, $countResults) + { + $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($idGoal, $revenue = 0.0) + { + $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($actionUrl, $actionType) + { + $url = $this->getRequest($this->idSite); + $url .= '&' . $actionType . '=' . urlencode($actionUrl); + + 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($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($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($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() + * + * @param $id + * @return string + */ + public static function getUserIdHashed($id) + { + 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($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() + { + $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() + { + $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); + } + + return $this->getCookieMatchingName('ref'); + } + + /** + * 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($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($time) + { + list($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($width, $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. + * + * @param bool $bool + * @return $this + */ + public function setBrowserHasCookies($bool) + { + $this->hasCookies = $bool; + return $this; + } + + /** + * Will append a custom string at the end of the Tracking request. + * @param string $string + * @return $this + */ + public function setDebugStringAppend($string) + { + $this->DEBUG_APPEND_URL = '&' . $string; + return $this; + } + + /** + * Sets visitor browser supported plugins + * + * @param bool $flash + * @param bool $java + * @param bool $quickTime + * @param bool $realPlayer + * @param bool $pdf + * @param bool $windowsMedia + * @param bool $silverlight + * @return $this + */ + public function setPlugins( + $flash = false, + $java = false, + $quickTime = false, + $realPlayer = false, + $pdf = false, + $windowsMedia = false, + $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() + { + $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() + { + return $this->requestTimeout; + } + + /** + * Sets the maximum number of seconds that the tracker will spend waiting for a response + * from Matomo. + * + * @param int $timeout + * @return $this + * @throws Exception + */ + public function setRequestTimeout($timeout) + { + if (!is_int($timeout) || $timeout < 0) { + throw new Exception("Invalid value supplied for request timeout: $timeout"); + } + + $this->requestTimeout = $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($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" + * @param int $proxyPort + */ + public function setProxy($proxy, $proxyPort = 80) + { + $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() + { + 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; + + /** + * @ignore + */ + protected function sendRequest($url, $method = 'GET', $data = null, $force = false) + { + 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->acceptLanguage = false; + + return true; + } + + $forcePostUrlEncoded = false; + if (!$this->doBulkRequests) { + if (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; + } + } + } + + $proxy = $this->getProxy(); + + if (function_exists('curl_init') && function_exists('curl_exec')) { + $options = array( + CURLOPT_URL => $url, + CURLOPT_USERAGENT => $this->userAgent, + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => $this->requestTimeout, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array( + 'Accept-Language: ' . $this->acceptLanguage, + ), + ); + + if ($method === 'GET') { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + + if (defined('PATH_TO_CERTIFICATES_FILE')) { + $options[CURLOPT_CAINFO] = PATH_TO_CERTIFICATES_FILE; + } + + 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(); + } + + $ch = curl_init(); + curl_setopt_array($ch, $options); + ob_start(); + $response = @curl_exec($ch); + ob_end_clean(); + $header = ''; + $content = ''; + + if ($response === false) { + throw new \RuntimeException(curl_error($ch)); + } + + if (!empty($response)) { + list($header, $content) = explode("\r\n\r\n", $response, $limitCount = 2); + } + + $this->parseIncomingCookies(explode("\r\n", $header)); + + } elseif (function_exists('stream_context_create')) { + $stream_options = array( + 'http' => array( + 'method' => $method, + 'user_agent' => $this->userAgent, + 'header' => "Accept-Language: " . $this->acceptLanguage . "\r\n", + 'timeout' => $this->requestTimeout, + ), + ); + + 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(); + } + + $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() + { + 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($idSite) + { + $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' : '') . + + // 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 = array(); + $this->pageCustomVar = array(); + $this->eventCustomVar = array(); + $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 + * + * @param string $name + * @return string String value of cookie, or false if not found + * @ignore + */ + protected function getCookieMatchingName($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" + * + * @return string + * @ignore + */ + protected static function getCurrentScriptName() + { + $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() + { + 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" + * + * @return string + * @ignore + */ + protected static function getCurrentHost() + { + 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" + * + * @return string + * @ignore + */ + protected static function getCurrentQueryString() + { + $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. + * + * @return string + * @ignore + */ + protected static function getCurrentUrl() + { + 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. + * + * @param $cookieName + * @param $cookieValue + * @param $cookieTTL + * @return $this + */ + protected function setCookie($cookieName, $cookieValue, $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 bool|mixed + */ + protected function getCustomVariablesFromCookie() + { + $cookie = $this->getCookieMatchingName('cvar'); + if (!$cookie) { + return false; + } + + return json_decode($cookie, $assoc = 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($headers) + { + $this->incomingTrackerCookies = array(); + + 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); + } + } + } +} + +/** + * 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); +} \ No newline at end of file From b647e8ac8ca12855cc41557591d5ab2dd0681f09 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 21:46:28 +0200 Subject: [PATCH 06/14] * add matomo function * add usage in twentyfifteen template --- system/includes/functions.php | 57 ++++++++++++++++++++++++++++++++++++ themes/twentyfifteen/layout.html.php | 1 + 2 files changed, 58 insertions(+) diff --git a/system/includes/functions.php b/system/includes/functions.php index 26f3613..911cec3 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -5,6 +5,7 @@ use \Michelf\MarkdownExtra; use \Suin\RSSWriter\Feed; use \Suin\RSSWriter\Channel; use \Suin\RSSWriter\Item; +use function Composer\Autoload\includeFile; // Get blog post path. Unsorted. Mostly used on widget. function get_post_unsorted() @@ -2021,6 +2022,62 @@ EOF; } } +// Matomo +function matomo($title) +{ + $matomoURL = config('matomo.url'); + $matomoToken = config('matomo.token'); + $matomoID = config('matomo.id'); + $matomoTracking = config('matomo.tracking'); + + if(empty($matomoURL) || empty($matomoTracking) || empty($matomoID) || empty($matomoToken)) + { + error_log("Please set all Matomo configuration values", 0); + return; + } + + $script = << + var _paq = window._paq = window._paq || []; + /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + (function() { + var u="${matomoURL}"; + _paq.push(['setTrackerUrl', u+'matomo.php']); + _paq.push(['setSiteId', '{$matomoID}']); + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); + })(); + +EOF; + + if($matomoTracking == 'javascript') + { + return $script; + } + else if($matomoTracking == 'php') + { + $url = 'system/plugins/matomo/MatomoTracker.php'; + require_once ($url); + + MatomoTracker::$URL = $matomoURL; + + // Matomo object + $matomoTracker = new MatomoTracker((int) $matomoID, $matomoURL); + + // Set authentication token + $matomoTracker->setTokenAuth($matomoToken); + + // Track page view + $matomoTracker->doTrackPageView($title); + } + else + { + return "nothing"; + } +} + function slashUrl($url) { return rtrim($url, '/') . '/'; } diff --git a/themes/twentyfifteen/layout.html.php b/themes/twentyfifteen/layout.html.php index 9caa0fa..5dda851 100644 --- a/themes/twentyfifteen/layout.html.php +++ b/themes/twentyfifteen/layout.html.php @@ -132,6 +132,7 @@ + From 12c6d24df2e296909b7b000fa8519effa9a7e8f1 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Thu, 26 Aug 2021 21:49:44 +0200 Subject: [PATCH 07/14] * check at first the tracking method --- system/includes/functions.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/system/includes/functions.php b/system/includes/functions.php index 911cec3..64e775e 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2030,6 +2030,11 @@ function matomo($title) $matomoID = config('matomo.id'); $matomoTracking = config('matomo.tracking'); + if($matomoTracking == 'disable') + { + return; + } + if(empty($matomoURL) || empty($matomoTracking) || empty($matomoID) || empty($matomoToken)) { error_log("Please set all Matomo configuration values", 0); @@ -2072,10 +2077,6 @@ EOF; // Track page view $matomoTracker->doTrackPageView($title); } - else - { - return "nothing"; - } } function slashUrl($url) { From e2dd11585efbfb4147e390b0ac6e53b5c0066e97 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Fri, 27 Aug 2021 09:43:23 +0200 Subject: [PATCH 08/14] * add noscript tag --- system/includes/functions.php | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/system/includes/functions.php b/system/includes/functions.php index 64e775e..f3e7bbd 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2042,19 +2042,20 @@ function matomo($title) } $script = << - var _paq = window._paq = window._paq || []; - /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ - _paq.push(['trackPageView']); - _paq.push(['enableLinkTracking']); - (function() { - var u="${matomoURL}"; - _paq.push(['setTrackerUrl', u+'matomo.php']); - _paq.push(['setSiteId', '{$matomoID}']); - var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; - g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); - })(); - + + EOF; if($matomoTracking == 'javascript') From 2f9e3ddf531136383d536e5d739a75283bfe9b3f Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Fri, 27 Aug 2021 09:45:07 +0200 Subject: [PATCH 09/14] * add token check only if tacking method php --- system/includes/functions.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/system/includes/functions.php b/system/includes/functions.php index f3e7bbd..e4973aa 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2035,7 +2035,7 @@ function matomo($title) return; } - if(empty($matomoURL) || empty($matomoTracking) || empty($matomoID) || empty($matomoToken)) + if(empty($matomoURL) || empty($matomoTracking) || empty($matomoID)) { error_log("Please set all Matomo configuration values", 0); return; @@ -2064,6 +2064,12 @@ EOF; } else if($matomoTracking == 'php') { + if(empty($matomoToken)) + { + error_log("Please set Matomo Token for PHP Tracking Method", 0); + return; + } + $url = 'system/plugins/matomo/MatomoTracker.php'; require_once ($url); From ee013b009a7bb4859f3c87c9e8ccb950e932c9d9 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Fri, 27 Aug 2021 19:33:12 +0200 Subject: [PATCH 10/14] * disable wait in curl request (php method) --- system/includes/functions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/includes/functions.php b/system/includes/functions.php index e4973aa..fa36288 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2078,6 +2078,9 @@ EOF; // Matomo object $matomoTracker = new MatomoTracker((int) $matomoID, $matomoURL); + // do not wait + $matomoTracker->setRequestTimeout(1); + // Set authentication token $matomoTracker->setTokenAuth($matomoToken); From 809b8524a3c95d39e21be31d73b5da246c2a4d9b Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Fri, 27 Aug 2021 20:23:53 +0200 Subject: [PATCH 11/14] * remove http/https from matomo url programmatically --- system/includes/functions.php | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/system/includes/functions.php b/system/includes/functions.php index fa36288..fd623bb 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2041,14 +2041,29 @@ function matomo($title) return; } - $script = << var _paq = window._paq || []; _paq.push(["setDocumentTitle", document.domain + "/" + document.title]); _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { - var u="{$matomoURL}"; + var u="{$url}"; _paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setSiteId', '{$matomoID}']); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; @@ -2057,9 +2072,7 @@ function matomo($title) EOF; - - if($matomoTracking == 'javascript') - { + return $script; } else if($matomoTracking == 'php') @@ -2079,7 +2092,7 @@ EOF; $matomoTracker = new MatomoTracker((int) $matomoID, $matomoURL); // do not wait - $matomoTracker->setRequestTimeout(1); + $matomoTracker->setRequestTimeout(1); // Set authentication token $matomoTracker->setTokenAuth($matomoToken); @@ -2089,6 +2102,17 @@ EOF; } } +function startsWith($haystack, $needle) +{ + $length = strlen( $needle ); + return substr( $haystack, 0, $length ) === $needle; +} + +function replace_first_str($search_str, $replacement_str, $src_str) +{ + return (false !== ($pos = strpos($src_str, $search_str))) ? substr_replace($src_str, $replacement_str, $pos, strlen($search_str)) : $src_str; +} + function slashUrl($url) { return rtrim($url, '/') . '/'; } From eff501a2089540ec37edd3700483a291c8ffe8ea Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Sat, 28 Aug 2021 12:56:09 +0200 Subject: [PATCH 12/14] * fix bug in image URL --- system/includes/functions.php | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/system/includes/functions.php b/system/includes/functions.php index fd623bb..dc56c95 100644 --- a/system/includes/functions.php +++ b/system/includes/functions.php @@ -2044,16 +2044,23 @@ function matomo($title) if($matomoTracking == 'javascript') { - $url = $matomoURL; + $jsURL = $matomoURL; + $imageURL = $matomoURL; // remove http | https if (startsWith($matomoURL, 'https')) { - $url = replace_first_str('https', '', $matomoURL); + $jsURL = replace_first_str('https', '', $matomoURL); } else if (startsWith($matomoURL, 'http')) { - $url = replace_first_str('http', '', $matomoURL); + $jsURL = replace_first_str('http', '', $matomoURL); + } + + if(!endsWith($matomoURL, '/')) + { + $jsURL = $jsURL . '/'; + $imageURL = $imageURL . "/"; } $script = << - + EOF; return $script; @@ -2083,8 +2090,8 @@ EOF; return; } - $url = 'system/plugins/matomo/MatomoTracker.php'; - require_once ($url); + $jsURL = 'system/plugins/matomo/MatomoTracker.php'; + require_once ($jsURL); MatomoTracker::$URL = $matomoURL; @@ -2102,10 +2109,25 @@ EOF; } } -function startsWith($haystack, $needle) + +function startsWith($haystack, $needle, $case = true) { - $length = strlen( $needle ); - return substr( $haystack, 0, $length ) === $needle; + if ($case) + { + return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0); + } + + return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0); +} + +function endsWith($haystack, $needle, $case = true) +{ + if ($case) + { + return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0); + } + + return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0); } function replace_first_str($search_str, $replacement_str, $src_str) From 91cf58f2891d141cf3a4247cf39814d78dae6d9d Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Sat, 28 Aug 2021 13:02:42 +0200 Subject: [PATCH 13/14] * add english resources in all *ini Files * add matomo function in all default templates --- lang/de_DE.ini | 4 ++-- lang/hu_HU.ini | 7 ++++++- lang/id_ID.ini | 5 +++++ lang/it_IT.ini | 7 ++++++- lang/ko_KO.ini | 5 +++++ lang/ms_MY.ini | 5 +++++ lang/nl_NL.ini | 9 +++++++-- lang/pl_PL.ini | 7 ++++++- lang/pt_BR.ini | 7 ++++++- lang/ru_RU.ini | 7 ++++++- lang/sv_SE.ini | 7 ++++++- lang/tr_TR.ini | 7 ++++++- lang/zh_TW.ini | 7 ++++++- themes/blog/layout.html.php | 1 + themes/clean/layout.html.php | 1 + themes/logs/layout.html.php | 1 + themes/readable/layout.html.php | 1 + themes/twentysixteen/layout.html.php | 1 + 18 files changed, 77 insertions(+), 12 deletions(-) diff --git a/lang/de_DE.ini b/lang/de_DE.ini index 6fcabeb..61bd714 100644 --- a/lang/de_DE.ini +++ b/lang/de_DE.ini @@ -251,5 +251,5 @@ Nope="Nope" Matomo="Matomo" Matomo_URL="Server" Matomo_Token="Token" -Matomo_ID="Page ID" -Matomo_Tracking="Tracking Method" \ No newline at end of file +Matomo_ID="Seiten-ID" +Matomo_Tracking="Tracking Methode" \ No newline at end of file diff --git a/lang/hu_HU.ini b/lang/hu_HU.ini index af86782..52c80da 100644 --- a/lang/hu_HU.ini +++ b/lang/hu_HU.ini @@ -247,4 +247,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/id_ID.ini b/lang/id_ID.ini index fed3640..0160c2d 100644 --- a/lang/id_ID.ini +++ b/lang/id_ID.ini @@ -248,3 +248,8 @@ Github_pre_release="Pra-rilis Github" Pre_release="Pra-rilis" Yes_Im_in="Ya, saya ikut" Nope="Nggak" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/it_IT.ini b/lang/it_IT.ini index 5e6275a..a4dd987 100644 --- a/lang/it_IT.ini +++ b/lang/it_IT.ini @@ -247,4 +247,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/ko_KO.ini b/lang/ko_KO.ini index feacf4c..abde346 100644 --- a/lang/ko_KO.ini +++ b/lang/ko_KO.ini @@ -248,3 +248,8 @@ Github_pre_release = "Github에서의 시험판" Pre_release = "사전 출시" Yes_Im_in = "그래, 난에있어" Nope = "아니" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/ms_MY.ini b/lang/ms_MY.ini index 5cc9f0d..3cea591 100644 --- a/lang/ms_MY.ini +++ b/lang/ms_MY.ini @@ -248,3 +248,8 @@ Github_pre_release = "Github pra keluaran" Pre_release = "Pra-release" Yes_Im_in = "Ya saya setuju" Nope = "Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/nl_NL.ini b/lang/nl_NL.ini index 996dbd1..144fdeb 100644 --- a/lang/nl_NL.ini +++ b/lang/nl_NL.ini @@ -207,7 +207,7 @@ post_your_post_slug="/bericht/uw-bericht-slug" Meta_description_character="Meta beschrijving" Breadcrumb_home_text="Breadcrumb home tekst" Sitemap="Sitemap" -Valid_values_range_from_0_to_1.0._See="Geldige waarden variren van 0,0 tot 1,0. Zie" +Valid_values_range_from_0_to_1.0._See="Geldige waarden vari�ren van 0,0 tot 1,0. Zie" hint_Use_CtrlCMDF_to_search_for_your_config_key_or_value="hint: Use Ctrl/CMD + F to search for your config key or value." pro_tips_You_can_creating_custom_config_key_and_print_out_your_config_key_value_anywhere_in_your_template="pro tips: You can creating custom config key and print out your config key value anywhere in your template." your_key="your.key" @@ -247,4 +247,9 @@ Page_generation_time="Generatietijd pagina" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Ja, ik doe mee" -Nope="Nietes" \ No newline at end of file +Nope="Nietes" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/pl_PL.ini b/lang/pl_PL.ini index 1791c0a..9189360 100644 --- a/lang/pl_PL.ini +++ b/lang/pl_PL.ini @@ -248,4 +248,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/pt_BR.ini b/lang/pt_BR.ini index 91d7a8d..1e0ee3c 100644 --- a/lang/pt_BR.ini +++ b/lang/pt_BR.ini @@ -247,4 +247,9 @@ Page_generation_time="Tempo de geração da página" Github_pre_release="Pré-lançamento do Github" Pre_release="Pré-lançamento" Yes_Im_in="Sim, estou dentro" -Nope="Não" \ No newline at end of file +Nope="Não" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/ru_RU.ini b/lang/ru_RU.ini index 9569d35..26beca5 100644 --- a/lang/ru_RU.ini +++ b/lang/ru_RU.ini @@ -248,4 +248,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/sv_SE.ini b/lang/sv_SE.ini index b189b31..9fc9625 100644 --- a/lang/sv_SE.ini +++ b/lang/sv_SE.ini @@ -265,4 +265,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/tr_TR.ini b/lang/tr_TR.ini index 618bca3..f87a1a8 100644 --- a/lang/tr_TR.ini +++ b/lang/tr_TR.ini @@ -247,4 +247,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/lang/zh_TW.ini b/lang/zh_TW.ini index 31705f7..e35b63a 100644 --- a/lang/zh_TW.ini +++ b/lang/zh_TW.ini @@ -247,4 +247,9 @@ Page_generation_time="Page generation time" Github_pre_release="Github pre-release" Pre_release="Pre-release" Yes_Im_in="Yes I'm in" -Nope="Nope" \ No newline at end of file +Nope="Nope" +Matomo="Matomo" +Matomo_URL="Server" +Matomo_Token="Token" +Matomo_ID="Page ID" +Matomo_Tracking="Tracking Method" \ No newline at end of file diff --git a/themes/blog/layout.html.php b/themes/blog/layout.html.php index 77a6340..b531f94 100644 --- a/themes/blog/layout.html.php +++ b/themes/blog/layout.html.php @@ -192,5 +192,6 @@ + \ No newline at end of file diff --git a/themes/clean/layout.html.php b/themes/clean/layout.html.php index d66ed7a..01a4488 100644 --- a/themes/clean/layout.html.php +++ b/themes/clean/layout.html.php @@ -45,5 +45,6 @@ + \ No newline at end of file diff --git a/themes/logs/layout.html.php b/themes/logs/layout.html.php index f8bcd39..76bd1ea 100644 --- a/themes/logs/layout.html.php +++ b/themes/logs/layout.html.php @@ -93,5 +93,6 @@ + \ No newline at end of file diff --git a/themes/readable/layout.html.php b/themes/readable/layout.html.php index 111e556..4d5521c 100644 --- a/themes/readable/layout.html.php +++ b/themes/readable/layout.html.php @@ -58,5 +58,6 @@ + \ No newline at end of file diff --git a/themes/twentysixteen/layout.html.php b/themes/twentysixteen/layout.html.php index 52d9441..3496dbe 100644 --- a/themes/twentysixteen/layout.html.php +++ b/themes/twentysixteen/layout.html.php @@ -157,5 +157,6 @@ + \ No newline at end of file From 482b0f7f9b84a60f07a66bf6865090aadcd05fb0 Mon Sep 17 00:00:00 2001 From: "maenz.torsten" Date: Sat, 28 Aug 2021 13:10:34 +0200 Subject: [PATCH 14/14] * fix encoding error --- lang/nl_NL.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/nl_NL.ini b/lang/nl_NL.ini index 144fdeb..9dc2de3 100644 --- a/lang/nl_NL.ini +++ b/lang/nl_NL.ini @@ -207,7 +207,7 @@ post_your_post_slug="/bericht/uw-bericht-slug" Meta_description_character="Meta beschrijving" Breadcrumb_home_text="Breadcrumb home tekst" Sitemap="Sitemap" -Valid_values_range_from_0_to_1.0._See="Geldige waarden vari�ren van 0,0 tot 1,0. Zie" +Valid_values_range_from_0_to_1.0._See="Geldige waarden variëren van 0,0 tot 1,0. Zie" hint_Use_CtrlCMDF_to_search_for_your_config_key_or_value="hint: Use Ctrl/CMD + F to search for your config key or value." pro_tips_You_can_creating_custom_config_key_and_print_out_your_config_key_value_anywhere_in_your_template="pro tips: You can creating custom config key and print out your config key value anywhere in your template." your_key="your.key"