").concat(t):e},getDimensionsHelpTextExtended:function(){if(this.isCloud)return"";var e=Object(ot["externalLink"])("https://matomo.org/faq/custom-reports/faq_25655/");return Object(ot["translate"])("CustomReports_ReportDimensionsHelpExtended",e,"")}}});_t.render=tt;var Mt=_t,Dt={class:"reportSearchFilter"},At={class:"index"},It={class:"name"},Ut={class:"description"},Bt={class:"reportType"},Tt={class:"reportCategory"},Lt={class:"action"},xt={colspan:"7"},Pt={class:"loadingPiwik"},Ht=Object(n["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),Gt={colspan:"7"},qt=["id"],Ft={class:"index"},Wt={class:"name"},$t=["title"],Yt=["title"],zt=["title"],Qt=["title"],Xt=["title"],Jt={class:"reportType"},Kt=["title"],Zt={key:0},eo={class:"action"},to=["title","onClick"],oo=["title","onClick"],ro=["title","onClick"],io=["title","href"],no=["title","onClick"],so={class:"tableActionBar"},ao=Object(n["createElementVNode"])("span",{class:"icon-add"},null,-1),lo={class:"ui-confirm",ref:"confirmDeleteReport"},co=["value"],uo=["value"],po={class:"ui-confirm",ref:"confirmPauseReport"},mo=["value"],ho=["value"],bo={class:"ui-confirm",ref:"confirmResumeReport"},fo=["value"],vo=["value"];function go(e,t,o,r,i,s){var a=Object(n["resolveComponent"])("Field"),l=Object(n["resolveComponent"])("ContentBlock"),c=Object(n["resolveDirective"])("content-table");return Object(n["openBlock"])(),Object(n["createElementBlock"])("div",null,[Object(n["createVNode"])(l,{"content-title":e.translate("CustomReports_ManageReports"),feature:e.translate("CustomReports_ManageReports")},{default:Object(n["withCtx"])((function(){return[Object(n["createElementVNode"])("p",null,Object(n["toDisplayString"])(e.translate("CustomReports_CustomReportIntroduction")),1),Object(n["createElementVNode"])("div",Dt,[Object(n["withDirectives"])(Object(n["createVNode"])(a,{uicontrol:"text",name:"reportSearch",title:e.translate("General_Search"),modelValue:e.searchFilter,"onUpdate:modelValue":t[0]||(t[0]=function(t){return e.searchFilter=t})},null,8,["title","modelValue"]),[[n["vShow"],e.reports.length>0]])]),Object(n["withDirectives"])(Object(n["createElementVNode"])("table",null,[Object(n["createElementVNode"])("thead",null,[Object(n["createElementVNode"])("tr",null,[Object(n["createElementVNode"])("th",At,Object(n["toDisplayString"])(e.translate("General_Id")),1),Object(n["createElementVNode"])("th",It,Object(n["toDisplayString"])(e.translate("General_Name")),1),Object(n["createElementVNode"])("th",Ut,Object(n["toDisplayString"])(e.translate("General_Description")),1),Object(n["createElementVNode"])("th",Bt,Object(n["toDisplayString"])(e.translate("CustomReports_Type")),1),Object(n["createElementVNode"])("th",Tt,Object(n["toDisplayString"])(e.translate("CustomReports_Category")),1),Object(n["createElementVNode"])("th",Lt,Object(n["toDisplayString"])(e.translate("General_Actions")),1)])]),Object(n["createElementVNode"])("tbody",null,[Object(n["withDirectives"])(Object(n["createElementVNode"])("tr",null,[Object(n["createElementVNode"])("td",xt,[Object(n["createElementVNode"])("span",Pt,[Ht,Object(n["createTextVNode"])(" "+Object(n["toDisplayString"])(e.translate("General_LoadingData")),1)])])],512),[[n["vShow"],e.isLoading||e.isUpdating]]),Object(n["withDirectives"])(Object(n["createElementVNode"])("tr",null,[Object(n["createElementVNode"])("td",Gt,Object(n["toDisplayString"])(e.translate("CustomReports_NoCustomReportsFound")),1)],512),[[n["vShow"],!e.isLoading&&0==e.reports.length]]),(Object(n["openBlock"])(!0),Object(n["createElementBlock"])(n["Fragment"],null,Object(n["renderList"])(e.sortedReports,(function(t){var o;return Object(n["openBlock"])(),Object(n["createElementBlock"])("tr",{id:"report".concat(t.idcustomreport),class:"customReports",key:t.idcustomreport},[Object(n["createElementVNode"])("td",Ft,Object(n["toDisplayString"])(t.idcustomreport),1),Object(n["createElementVNode"])("td",Wt,[Object(n["createTextVNode"])(Object(n["toDisplayString"])(t.name)+" ",1),Object(n["withDirectives"])(Object(n["createElementVNode"])("span",{class:"icon-locked",title:e.translate("CustomReports_ReportEditNotAllowedAllWebsitesUpdated")},null,8,$t),[[n["vShow"],!t.idsite&&!e.isSuperUser]]),Object(n["withDirectives"])(Object(n["createElementVNode"])("span",{class:"icon-info2",title:e.translate("CustomReports_ReportAvailableToAllWebsites")},null,8,Yt),[[n["vShow"],!t.idsite&&e.isSuperUser]]),Object(n["withDirectives"])(Object(n["createElementVNode"])("span",{class:"icon-locked",title:e.translate("CustomReports_ReportEditNotAllowedMultipleWebsitesAccessIssue")},null,8,zt),[[n["vShow"],!t.allowedToEdit&&e.isMultiSiteReport(t)]]),Object(n["withDirectives"])(Object(n["createElementVNode"])("span",{class:"icon-info2",title:e.translate("CustomReports_ReportAvailableToMultipleWebsites")},null,8,Qt),[[n["vShow"],t.allowedToEdit&&e.isMultiSiteReport(t)]])]),Object(n["createElementVNode"])("td",{class:"description",title:e.htmlEntities(t.description)},Object(n["toDisplayString"])(e.truncate(t.description.trim(),60)),9,Xt),Object(n["createElementVNode"])("td",Jt,Object(n["toDisplayString"])(e.reportTypesReadable[t.report_type]),1),Object(n["createElementVNode"])("td",{class:"reportCategory",title:e.htmlEntities(t.category.name)},[Object(n["createTextVNode"])(Object(n["toDisplayString"])(e.truncate(t.category.name.trim(),60))+" ",1),null!==(o=t.subcategory)&&void 0!==o&&o.name?(Object(n["openBlock"])(),Object(n["createElementBlock"])("span",Zt," - "+Object(n["toDisplayString"])(e.truncate(t.subcategory.name.trim(),60)),1)):Object(n["createCommentVNode"])("",!0)],8,Kt),Object(n["createElementVNode"])("td",eo,[Object(n["withDirectives"])(Object(n["createElementVNode"])("a",{class:"table-action icon-pause",title:e.translate("CustomReports_PauseReportInfo"),onClick:function(o){return e.pauseReport(t)}},null,8,to),[[n["vShow"],(t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit)&&"active"===t.status]]),Object(n["withDirectives"])(Object(n["createElementVNode"])("a",{class:"table-action icon-play",title:e.translate("CustomReports_ResumeReportInfo"),onClick:function(o){return e.resumeReport(t)}},null,8,oo),[[n["vShow"],(t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit)&&"paused"===t.status]]),Object(n["createElementVNode"])("a",{class:"table-action icon-edit",title:e.translate("CustomReports_EditReport"),onClick:function(o){return e.editReport(t.idcustomreport)}},null,8,ro),Object(n["createElementVNode"])("a",{target:"_blank",class:"table-action icon-show",title:e.translate("CustomReports_ViewReportInfo"),href:e.getViewReportLink(t)},null,8,io),Object(n["withDirectives"])(Object(n["createElementVNode"])("a",{class:"table-action icon-delete",title:e.translate("CustomReports_DeleteReportInfo"),onClick:function(o){return e.deleteReport(t)}},null,8,no),[[n["vShow"],t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit]])])],8,qt)})),128))])],512),[[c]]),Object(n["createElementVNode"])("div",so,[Object(n["createElementVNode"])("a",{class:"createNewReport",onClick:t[1]||(t[1]=function(t){return e.createReport()})},[ao,Object(n["createTextVNode"])(" "+Object(n["toDisplayString"])(e.translate("CustomReports_CreateNewReport")),1)])])]})),_:1},8,["content-title","feature"]),Object(n["createElementVNode"])("div",lo,[Object(n["createElementVNode"])("h2",null,Object(n["toDisplayString"])(e.translate("CustomReports_DeleteReportConfirm")),1),Object(n["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,co),Object(n["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,uo)],512),Object(n["createElementVNode"])("div",po,[Object(n["createElementVNode"])("h2",null,Object(n["toDisplayString"])(e.translate("CustomReports_PauseReportConfirm")),1),Object(n["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,mo),Object(n["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,ho)],512),Object(n["createElementVNode"])("div",bo,[Object(n["createElementVNode"])("h2",null,Object(n["toDisplayString"])(e.translate("CustomReports_ResumeReportConfirm")),1),Object(n["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,fo),Object(n["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,vo)],512)])}
-/**
- * Copyright (C) InnoCraft Ltd - All rights reserved.
- *
- * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
- * The intellectual and technical concepts contained herein are protected by trade secret
- * or copyright law. Redistribution of this information or reproduction of this material is
- * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
- *
- * You shall use this code only in accordance with the license agreement obtained from
- * InnoCraft Ltd.
- *
- * @link https://www.innocraft.com/
- * @license For license details see https://www.innocraft.com/license
- */function jo(e,t){return e&&e.length>t?"".concat(e.substr(0,t-3),"..."):e}function Oo(e){return Co(e)||So(e)||Ro(e)||yo()}function yo(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function Ro(e,t){if(e){if("string"===typeof e)return No(e,t);var o=Object.prototype.toString.call(e).slice(8,-1);return"Object"===o&&e.constructor&&(o=e.constructor.name),"Map"===o||"Set"===o?Array.from(e):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?No(e,t):void 0}}function So(e){if("undefined"!==typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function Co(e){if(Array.isArray(e))return No(e)}function No(e,t){(null==t||t>e.length)&&(t=e.length);for(var o=0,r=new Array(t);o2&&void 0!==arguments[2]?arguments[2]:null,r=ot["NotificationsStore"].show({message:e,context:t,id:Eo,type:null!==o?o:"toast"});setTimeout((function(){ot["NotificationsStore"].scrollToNotification(r)}),200)},deleteReport:function(e){ot["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteReport,{yes:function(){jt.deleteReport(e.idcustomreport,e.idsite).then((function(){jt.reload(),ot["Matomo"].postEvent("updateReportingMenu")}))}})},getViewReportLink:function(e){return"?".concat(ot["MatomoUrl"].stringify({module:"CoreHome",action:"index",idSite:e.linkIdSite,period:"day",date:"yesterday"}),"#?").concat(ot["MatomoUrl"].stringify({category:e.category.id,idSite:e.linkIdSite,date:ot["MatomoUrl"].parsed.value.date,period:ot["MatomoUrl"].parsed.value.period,segment:ot["MatomoUrl"].parsed.value.segment,subcategory:e.subcategoryLink}))},truncate:jo,htmlEntities:function(e){return ot["Matomo"].helper.htmlEntities(e)},isMultiSiteReport:function(e){return e.multiple_idsites&&e.multiple_idsites.split(",")}},computed:{isSuperUser:function(){return ot["Matomo"].hasSuperUserAccess},reports:function(){return jt.state.value.reports},sortedReports:function(){var e=this.searchFilter.toLowerCase(),t=Oo(this.reports).filter((function(t){return Object.keys(t).some((function(o){var r=t;return"string"===typeof r[o]&&-1!==r[o].toLowerCase().indexOf(e)}))}));return t.sort((function(e,t){var o=parseInt("".concat(e.idcustomreport),10),r=parseInt("".concat(t.idcustomreport),10);return o-r})),t},isLoading:function(){return jt.state.value.isLoading},isUpdating:function(){return jt.state.value.isUpdating},reportTypesReadable:function(){return jt.state.value.reportTypesReadable}}});ko.render=go;var Vo=ko,wo={class:"manageReports"},_o={key:0},Mo={key:1};function Do(e,t,o,r,i,s){var a=Object(n["resolveComponent"])("CustomReportsList"),l=Object(n["resolveComponent"])("CustomReportsEdit");return Object(n["openBlock"])(),Object(n["createElementBlock"])("div",wo,[e.editMode?Object(n["createCommentVNode"])("",!0):(Object(n["openBlock"])(),Object(n["createElementBlock"])("div",_o,[Object(n["createVNode"])(a)])),e.editMode?(Object(n["openBlock"])(),Object(n["createElementBlock"])("div",Mo,[Object(n["createVNode"])(l,{"id-custom-report":e.idCustomReport,"browser-archiving-disabled":e.browserArchivingDisabled,"re-archive-last-n":e.reArchiveLastN,"max-dimensions":e.maxDimensions,"is-cloud":e.isCloud},null,8,["id-custom-report","browser-archiving-disabled","re-archive-last-n","max-dimensions","is-cloud"])])):Object(n["createCommentVNode"])("",!0)])}var Ao=Object(n["defineComponent"])({props:{browserArchivingDisabled:Boolean,reArchiveLastN:Number,maxDimensions:Number,isCloud:Boolean},components:{CustomReportsList:Vo,CustomReportsEdit:Mt},data:function(){return{editMode:!1,idCustomReport:null}},watch:{editMode:function(){$(".ui-tooltip").remove()}},created:function(){var e=this;Object(n["watch"])((function(){return ot["MatomoUrl"].hashParsed.value.idCustomReport}),(function(t){e.initState(t)})),this.initState(ot["MatomoUrl"].hashParsed.value.idCustomReport)},methods:{removeAnyReportNotification:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];ot["NotificationsStore"].remove("reportsmanagement"),e&&ot["NotificationsStore"].remove("reportsmanagementProductMetric")},initState:function(e){if(e){if("0"===e){var t={isAllowed:!0};if(ot["Matomo"].postEvent("CustomReports.initAddReport",t),t&&!t.isAllowed)return this.editMode=!1,void(this.idCustomReport=null)}this.editMode=!0,this.idCustomReport=parseInt(e,10)}else this.editMode=!1,this.idCustomReport=null;this.removeAnyReportNotification(!e)}}});Ao.render=Do;var Io=Ao;
-/**
- * Copyright (C) InnoCraft Ltd - All rights reserved.
- *
- * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
- * The intellectual and technical concepts contained herein are protected by trade secret
- * or copyright law. Redistribution of this information or reproduction of this material is
- * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
- *
- * You shall use this code only in accordance with the license agreement obtained from
- * InnoCraft Ltd.
- *
- * @link https://www.innocraft.com/
- * @license For license details see https://www.innocraft.com/license
- */}})}));
-//# sourceMappingURL=CustomReports.umd.min.js.map
\ No newline at end of file
diff --git a/files/plugin-CustomReports-5.4.5/API.php b/files/plugin-CustomReports-5.4.5/API.php
new file mode 100644
index 0000000..a5dc7e7
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/API.php
@@ -0,0 +1,2033 @@
+Custom Reports API lets you 1) create custom
+ * reports within Matomo and 2) view the created reports in the Matomo Reporting UI or consume them via the API.
+ *
+ * You can choose between different visualizations (eg table or evolution graph) and combine hundreds of dimensions
+ * and metrics to get the data you need.
+ *
+ * @method static \Piwik\Plugins\CustomReports\API getInstance()
+ *
+ * @OA\Tag(name="CustomReports")
+ */
+class API extends \Piwik\Plugin\API
+{
+ /**
+ * @var MetricsList
+ */
+ private $metricsList;
+
+ /**
+ * @var DimensionsProvider
+ */
+ private $columnsProvider;
+
+ /**
+ * @var CustomReportsModel
+ */
+ private $model;
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * @var LogTablesProvider
+ */
+ private $logTablesProvider;
+
+ /**
+ * @var Configuration
+ */
+ private $configuration;
+
+ /**
+ * @var ArchiveInvalidator
+ */
+ private $archiveInvalidator;
+
+ public function __construct(
+ CustomReportsModel $model,
+ Validator $validator,
+ DimensionsProvider $columnsProvider,
+ LogTablesProvider $logTablesProvider,
+ Configuration $configuration,
+ ArchiveInvalidator $archiveInvalidator
+ ) {
+ $this->metricsList = MetricsList::get();
+ $this->columnsProvider = $columnsProvider;
+ $this->model = $model;
+ $this->validator = $validator;
+ $this->logTablesProvider = $logTablesProvider;
+ $this->configuration = $configuration;
+ $this->archiveInvalidator = $archiveInvalidator;
+ }
+
+ /**
+ * Copies a specified custom report to one or more sites. If a custom report with the same name already exists, the new custom report
+ * will have an automatically adjusted name to make it unique to the assigned site.
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report to duplicate.
+ * @param int[] $idDestinationSites Optional array of IDs identifying which site(s) the new custom report is to be
+ * assigned to. The default is [idSite] when nothing is provided.
+ *
+ * @return array Response indicating success and containing the ID of the newly created report.
+ * @throws Exception
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.duplicateCustomReport",
+ * operationId="CustomReports.duplicateCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to duplicate.",
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="idDestinationSites",
+ * in="query",
+ * required=false,
+ * description="Optional array of IDs identifying which site(s) the new custom report is to be assigned to. The default is [idSite] when nothing is provided.",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="integer"),
+ * default={}
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Response indicating success and containing the ID of the newly created report.",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"success":"1","message":"The custom report has been successfully copied.","additionalData":{"idSite":"1","idDestinationSites":"","idCustomReport":"1","newIds":"9"}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="additionalData",
+ * type="object"
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={"success":true,"message":"The custom report has been successfully copied.","additionalData":{"idSite":1,"idDestinationSites":{},"idCustomReport":1,"newIds":9}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Property(property="success", type="boolean"),
+ * @OA\Property(property="message", type="string"),
+ * @OA\Property(
+ * property="additionalData",
+ * type="object",
+ * @OA\Property(property="idSite", type="integer"),
+ * @OA\Property(
+ * property="idDestinationSites",
+ * type="array",
+ * @OA\Items()
+ * ),
+ * @OA\Property(property="idCustomReport", type="integer"),
+ * @OA\Property(property="newIds", type="integer")
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function duplicateCustomReport(int $idSite, int $idCustomReport, array $idDestinationSites = []): array
+ {
+ if (!class_exists('\Piwik\Plugins\CoreHome\EntityDuplicator\DuplicateRequestResponse')) {
+ throw new BadRequestException('This endpoint is not available until Matomo 5.4.0');
+ }
+
+ // Define data array before any alterations to the variables
+ $additionalData = [
+ 'idSite' => $idSite,
+ 'idDestinationSites' => $idDestinationSites,
+ 'idCustomReport' => $idCustomReport,
+ ];
+
+ $idDestinationSites = count($idDestinationSites) > 0 ? $idDestinationSites : [$idSite];
+ $idSitesToCheck = array_unique(array_merge([$idSite], $idDestinationSites));
+ $this->validator->checkSitesDuplicationPermission($idSitesToCheck);
+
+ // Initialise the common response values
+ $duplicateRequestResponse = new DuplicateRequestResponse();
+
+ $customReport = null;
+ try {
+ $customReport = $this->getConfiguredReport($idSite, $idCustomReport);
+ } catch (\Throwable $e) {
+ // Log the error, but continue for the proper response to be built later
+ $this->logError('Uncaught exception looking up custom report to duplicate: {exception}', $e);
+ }
+ // Name and atleast 1 dimension and 1 metric is needed to create a custom report
+ if (empty($customReport['name'])) {
+ $duplicateRequestResponse->setSuccess(false);
+ $duplicateRequestResponse->setMessage(Piwik::translate('CustomReports_SourceCustomReportLookupError'));
+ $duplicateRequestResponse->setAdditionalData($additionalData);
+
+ return $duplicateRequestResponse->getResponseArray();
+ }
+
+ $newName = $customReport['name'];
+ $customReportNames = [];
+ foreach ($idDestinationSites as $idDestinationSite) {
+ $customReports = $this->model->getAllCustomReportsForSite($idDestinationSite, true, true);
+ // It can only be a duplicate name if some custom reports were found for the site.
+ if (is_array($customReports) && count($customReports) > 0) {
+ $customReportNames = array_merge($customReportNames, array_column($customReports, 'name'));
+ }
+ }
+ // It can only be a duplicate name if some custom reports were found for the site.
+ if (count($customReportNames) > 0) {
+ $newName = EntityDuplicatorHelper::getUniqueNameComparedToList($newName, $customReportNames, 50);
+ }
+ $category = '';
+ $subCategory = '';
+ if ($idDestinationSites[0] === $idSite) {
+ $category = !empty($customReport['category']['id']) ? $customReport['category']['id'] : '';
+ $subCategory = !empty($customReport['subcategory']['id']) ? $customReport['subcategory']['id'] : '';
+ }
+ try {
+ $response = $this->addCustomReport(
+ $idDestinationSites[0],
+ $newName,
+ $customReport['report_type'],
+ $customReport['metrics'],
+ $category,
+ $customReport['dimensions'],
+ $subCategory,
+ $customReport['description'],
+ $customReport['segment_filter'],
+ (count($idDestinationSites) > 1 ? $idDestinationSites : []) // Add only if more than 1 destination site is passed
+ );
+ } catch (\Exception $e) {
+ $response = false;
+ $this->logError('Uncaught exception duplicating custom report: {exception}', $e);
+ }
+
+ if (!is_int($response) || $response < 1) {
+ $duplicateRequestResponse->setSuccess(false);
+ $duplicateRequestResponse->setMessage(Piwik::translate('CustomReports_CustomReportDuplicationError'));
+ } else {
+ // Set the values for success response
+ $duplicateRequestResponse->setSuccess(true);
+ $duplicateRequestResponse->setMessage(Piwik::translate('CustomReports_CustomReportCopied'));
+ $additionalData['newIds'] = $response;
+ $duplicateRequestResponse->setAdditionalData($additionalData);
+ }
+
+ // Make sure to record the activity for the report being copied
+ if (class_exists('\Piwik\Plugins\ActivityLog\ActivityParamObject\EntityDuplicatedData')) {
+ // TODO - Remove this if/else and always use the setRequestDataForActivity method for Matomo 6.x
+ if (method_exists($duplicateRequestResponse, 'setRequestDataForEvent')) {
+ $duplicateRequestResponse->setRequestDataForEvent(
+ 'CustomReports_CustomReport',
+ $customReport['name'],
+ $idCustomReport,
+ $idSite,
+ $idDestinationSites,
+ $additionalData
+ );
+ } else {
+ (
+ new \Piwik\Plugins\ActivityLog\ActivityParamObject\EntityDuplicatedData(
+ 'CustomReports_CustomReport',
+ $customReport['name'],
+ $idCustomReport,
+ $idSite,
+ $idDestinationSites,
+ $additionalData
+ )
+ )->postActivityEvent();
+ }
+ }
+
+ return $duplicateRequestResponse->getResponseArray();
+ }
+
+ private function logError(string $message, \Throwable $e): void
+ {
+ StaticContainer::get(\Piwik\Log\LoggerInterface::class)->error(
+ $message,
+ [
+ 'exception' => $e,
+ 'ignoreInScreenWriter' => true,
+ ]
+ );
+ }
+
+ /**
+ * Adds a new custom report
+ *
+ * @param int $idSite
+ * @param string $name The name of the report.
+ * @param string $reportType The type of report you want to create, for example 'table' or 'evolution'.
+ * For a list of available reports call 'CustomReports.getAvailableReportTypes'
+ * @param string[] $metricIds A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics'
+ * @param string $categoryId By default, the report will be put into a custom report category unless a specific
+ * categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'.
+ * @param string[] $dimensionIds A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions'
+ * @param string $subcategoryId By default, a new reporting page will be created for this report unless you
+ * specifiy a specific name or subcategoryID. For a list of available subcategories
+ * call 'CustomReports.getAvailableCategories'.
+ * @param string $description An optional description for the report, will be shown in the title help icon of the report.
+ * @param string $segmentFilter An optional segment to filter the report data. Needs to be sent urlencoded.
+ * @param string[] $multipleIdSites An optional list of idsites for which we need to execute the report
+ *
+ * @return int
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.addCustomReport",
+ * operationId="CustomReports.addCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="name",
+ * in="query",
+ * required=true,
+ * description="The name of the report.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="reportType",
+ * in="query",
+ * required=true,
+ * description="The type of report you want to create, for example 'table' or 'evolution'. For a list of available reports call 'CustomReports.getAvailableReportTypes'",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="metricIds",
+ * in="query",
+ * required=true,
+ * description="A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics'",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string")
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="categoryId",
+ * in="query",
+ * required=false,
+ * description="By default, the report will be put into a custom report category unless a specific categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="dimensionIds",
+ * in="query",
+ * required=false,
+ * description="A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions'",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string"),
+ * default={}
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="subcategoryId",
+ * in="query",
+ * required=false,
+ * description="By default, a new reporting page will be created for this report unless you specifiy a specific name or subcategoryID. For a list of available subcategories call 'CustomReports.getAvailableCategories'.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="description",
+ * in="query",
+ * required=false,
+ * description="An optional description for the report, will be shown in the title help icon of the report.",
+ * @OA\Schema(
+ * type="string",
+ * default=""
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="segmentFilter",
+ * in="query",
+ * required=false,
+ * description="An optional segment to filter the report data. Needs to be sent urlencoded.",
+ * @OA\Schema(
+ * type="string",
+ * default=""
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="multipleIdSites",
+ * in="query",
+ * required=false,
+ * description="An optional list of idsites for which we need to execute the report",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string"),
+ * default={}
+ * )
+ * ),
+ * @OA\Response(response=200, ref="#/components/responses/GenericInteger"),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function addCustomReport($idSite, $name, $reportType, $metricIds, $categoryId = false, $dimensionIds = array(), $subcategoryId = false, $description = '', $segmentFilter = '', $multipleIdSites = [])
+ {
+ if (!empty($multipleIdSites) && $idSite != 'all' && $idSite != '0') {
+ $multipleIdSites = array_unique($multipleIdSites);
+ foreach ($multipleIdSites as $multipleIdSite) {
+ $this->validator->checkWritePermission($multipleIdSite);
+ }
+ if (!in_array($idSite, $multipleIdSites)) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorInvalidMultipleIdSite', [$idSite]));
+ }
+ } else {
+ $this->validator->checkWritePermission($idSite);
+ // prevent creating reports for sites that do not yet exist but might in the future
+ $this->validator->checkSiteExists($idSite);
+ }
+
+ if (empty($categoryId)) {
+ $categoryId = CustomReportsDao::DEFAULT_CATEGORY;
+ }
+
+ $createdDate = Date::now()->getDatetime();
+ if (!empty($segmentFilter)) {
+ $segmentFilter = Common::unsanitizeInputValue($segmentFilter);
+ $segmentFilter = urldecode($segmentFilter);
+ }
+
+ // If there's a Product Revenue metric without a Product Quantity metric, throw an exception
+ if (
+ (in_array('sum_product_revenue', $metricIds) || in_array('avg_product_revenue', $metricIds))
+ && !in_array('sum_ecommerce_productquantity', $metricIds)
+ && !in_array('avg_ecommerce_productquantity', $metricIds)
+ ) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorProductRevenueMetricDependency'));
+ }
+
+ $idReport = $this->model->createCustomReport($idSite, $name, $description, $reportType, $dimensionIds, $metricIds, $segmentFilter, $categoryId, $subcategoryId, $createdDate, $multipleIdSites);
+ $report = $this->model->getCustomReportById($idReport, $idSite);
+
+ $config = StaticContainer::get(Configuration::class);
+ $startDate = null;
+ $subMonth = $config->getReArchiveReportsInPastLastNMonths();
+ if (!empty($subMonth)) {
+ $startDate = Date::yesterday()->subMonth($subMonth)->setDay(1);
+ }
+
+ $this->scheduleReArchiving($idSite, $multipleIdSites, $report, $startDate);
+
+ $this->clearCache();
+ return $idReport;
+ }
+
+ private function clearCache()
+ {
+ // we need to delete possibly cached values. especially ReportsProvider
+ try {
+ Cache::getLazyCache()->flushAll();
+ } catch (\Exception $e) {
+ }
+ // we need to delete possibly cached values. especially ReportsProvider
+ try {
+ Cache::getEagerCache()->flushAll();
+ } catch (\Exception $e) {
+ }
+ // we need to delete possibly cached values. especially ReportsProvider
+ try {
+ Cache::getTransientCache()->flushAll();
+ } catch (\Exception $e) {
+ }
+ }
+
+ /**
+ * Updates an existing custom report. Be aware that if you change metrics, dimensions, the report type or the segment filter,
+ * previously processed/archived reports may become unavailable and would need to be re-processed.
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report to update.
+ * @param string $name The name of the report.
+ * @param string $reportType The type of report you want to create, for example 'table' or 'evolution'.
+ * For a list of available reports call 'CustomReports.getAvailableReportTypes'
+ * @param string[] $metricIds A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics'
+ * @param string $categoryId By default, the report will be put into a custom report category unless a specific
+ * categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'.
+ * @param string[] $dimensionIds A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions'
+ * @param string $subcategoryId By default, a new reporting page will be created for this report unless you
+ * specify a specific name or subcategoryID. For a list of available subcategories
+ * call 'CustomReports.getAvailableCategories'.
+ * @param string $description An optional description for the report, will be shown in the title help icon of the report.
+ * @param string $segmentFilter An optional segment to filter the report data. Needs to be sent urlencoded.
+ * @param int[] $subCategoryReportIds List of sub report ids mapped to this report
+ * @param string[] $multipleIdSites An optional list of idSites for which we need to execute the report
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.updateCustomReport",
+ * operationId="CustomReports.updateCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to update.",
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="name",
+ * in="query",
+ * required=true,
+ * description="The name of the report.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="reportType",
+ * in="query",
+ * required=true,
+ * description="The type of report you want to create, for example 'table' or 'evolution'. For a list of available reports call 'CustomReports.getAvailableReportTypes'",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="metricIds",
+ * in="query",
+ * required=true,
+ * description="A list of metric IDs. For a list of available metrics call 'CustomReports.getAvailableMetrics'",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string")
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="categoryId",
+ * in="query",
+ * required=false,
+ * description="By default, the report will be put into a custom report category unless a specific categoryId is provided. For a list of available categories call 'CustomReports.getAvailableCategories'.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="dimensionIds",
+ * in="query",
+ * required=false,
+ * description="A list of dimension IDs. For a list of available metrics call 'CustomReports.getAvailableDimensions'",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string"),
+ * default={}
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="subcategoryId",
+ * in="query",
+ * required=false,
+ * description="By default, a new reporting page will be created for this report unless you specifiy a specific name or subcategoryID. For a list of available subcategories call 'CustomReports.getAvailableCategories'.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="description",
+ * in="query",
+ * required=false,
+ * description="An optional description for the report, will be shown in the title help icon of the report.",
+ * @OA\Schema(
+ * type="string",
+ * default=""
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="segmentFilter",
+ * in="query",
+ * required=false,
+ * description="An optional segment to filter the report data. Needs to be sent urlencoded.",
+ * @OA\Schema(
+ * type="string",
+ * default=""
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="subCategoryReportIds",
+ * in="query",
+ * required=false,
+ * description="List of sub report ids mapped to this report",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="integer"),
+ * default={}
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="multipleIdSites",
+ * in="query",
+ * required=false,
+ * description="An optional list of idsites for which we need to execute the report",
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(type="string"),
+ * default={}
+ * )
+ * ),
+ * @OA\Response(response=200, ref="#/components/responses/GenericSuccess"),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function updateCustomReport(
+ $idSite,
+ $idCustomReport,
+ $name,
+ $reportType,
+ $metricIds,
+ $categoryId = false,
+ $dimensionIds = array(),
+ $subcategoryId = false,
+ $description = '',
+ $segmentFilter = '',
+ $subCategoryReportIds = [],
+ $multipleIdSites = []
+ ): void {
+ if (!empty($multipleIdSites) && $idSite != 'all' && $idSite != '0') {
+ $multipleIdSites = array_unique($multipleIdSites);
+ foreach ($multipleIdSites as $multipleIdSite) {
+ $this->validator->checkWritePermission($multipleIdSite);
+ }
+ if (!in_array($idSite, $multipleIdSites)) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorInvalidMultipleIdSite', [$idSite]));
+ }
+ } else {
+ $this->validator->checkWritePermission($idSite);
+ // prevent creating reports for sites that do not yet exist but might in the future
+ $this->validator->checkSiteExists($idSite);
+ }
+
+ // we cannot get report by idSite, idCustomReport since the idSite may change!
+ $report = $this->model->getCustomReportById($idCustomReport, $idSite);
+
+ if (empty($report)) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorReportDoesNotExist'));
+ }
+
+ if ($report['idsite'] != $idSite && empty($multipleIdSites)) {
+ // if the site changes for a report, make sure the user write permission for the old and the new site
+ $this->validator->checkWritePermission($report['idsite']);
+ }
+
+ if (empty($categoryId)) {
+ $categoryId = CustomReportsDao::DEFAULT_CATEGORY;
+ }
+
+ if (!empty($segmentFilter)) {
+ $segmentFilter = Common::unsanitizeInputValue($segmentFilter);
+ $segmentFilter = urldecode($segmentFilter);
+ }
+
+ $updatedDate = Date::now()->getDatetime();
+
+ $shouldReArchive = false;
+ if (
+ (
+ isset($report['report_type']) &&
+ $report['report_type'] != $reportType
+ ) ||
+ (
+ isset($report['dimensions']) &&
+ $report['dimensions'] != $dimensionIds
+ ) ||
+ (
+ isset($report['metrics']) &&
+ $report['metrics'] != $metricIds
+ ) ||
+ (
+ isset($report['segment_filter']) &&
+ $report['segment_filter'] != $segmentFilter
+ )
+ ) {
+ $shouldReArchive = true;
+ }
+
+ // If there's a Product Revenue metric without a Product Quantity metric, throw an exception
+ if (
+ (in_array('sum_product_revenue', $metricIds) || in_array('avg_product_revenue', $metricIds))
+ && !in_array('sum_ecommerce_productquantity', $metricIds)
+ && !in_array('avg_ecommerce_productquantity', $metricIds)
+ ) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorProductRevenueMetricDependency'));
+ }
+
+ $this->model->updateCustomReport($idSite, $idCustomReport, $name, $description, $reportType, $dimensionIds, $metricIds, $segmentFilter, $categoryId, $subcategoryId, $updatedDate, $subCategoryReportIds, $multipleIdSites);
+
+ if ($shouldReArchive) {
+ $updatedReport = $this->model->getCustomReportById($idCustomReport, $idSite);
+ $config = StaticContainer::get(Configuration::class);
+ $startDate = null;
+ $subMonth = $config->getReArchiveReportsInPastLastNMonths();
+ if (!empty($subMonth)) {
+ $startDate = Date::yesterday()->subMonth($subMonth)->setDay(1);
+ }
+
+ $this->scheduleReArchiving($idSite, $multipleIdSites, $updatedReport, $startDate);
+ }
+
+ $this->clearCache();
+ }
+
+ private function scheduleReArchiving($idSite, $multipleIdSites, array $report, ?Date $startDate): void
+ {
+ $idSites = $idSite === 0 || $idSite === '0' || $idSite == 'all' ? 'all' : (!empty($multipleIdSites) ? $multipleIdSites : [$idSite]);
+
+ $this->archiveInvalidator->scheduleReArchiving(
+ $idSites,
+ 'CustomReports',
+ Archiver::makeRecordName($report['idcustomreport'], $report['revision'] ?? 0),
+ $startDate
+ );
+
+ if ($report['report_type'] === Evolution::ID) {
+ foreach ($this->model->getArchivableMetricsInReport($report) as $metric) {
+ $this->archiveInvalidator->scheduleReArchiving(
+ $idSites,
+ 'CustomReports',
+ Archiver::makeEvolutionRecordName($report['idcustomreport'], $report['revision'], $metric->getName()),
+ $startDate
+ );
+ }
+ }
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get all custom report configurations for a specific site.
+ *
+ * @param int $idSite
+ * @param bool $skipCategoryMetadata Optional flag indicating whether to omit metadata for the category.
+ * @return array The list of configured custom reports.
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getConfiguredReports",
+ * operationId="CustomReports.getConfiguredReports",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="skipCategoryMetadata",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="boolean",
+ * default=false
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getConfiguredReports&idSite=1&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getConfiguredReports&idSite=1&format=JSON&token_auth=anonymous), TSV (N/A)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"idcustomreport":"1","idsite":"1","revision":"0","report_type":"table","name":"Pages by New\/Returning visitor","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":"65","icon":"icon-business"},"subcategory":"","subcategory_order":"9999999","dimensions":{"row":{"CoreHome.VisitorReturning","Actions.PageTitle"}},"metrics":{"row":{"nb_uniq_visitors","nb_visits","pageviews"}},"segment_filter":"","created_date":"2017-10-20 02:31:50","updated_date":"2017-10-20 02:31:50","status":"active","multiple_idsites":"","site":{"id":"1","name":"Demo Site"},"allowedToEdit":"0"},{"idcustomreport":"2","idsite":"1","revision":"0","report_type":"table","name":"Bali pages, breakdown new\/returning","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":"65","icon":"icon-business"},"subcategory":"","subcategory_order":"9999999","dimensions":{"row":{"Actions.PageTitle","CoreHome.VisitorReturning"}},"metrics":{"row":{"nb_uniq_visitors","pageviews","nb_visits"}},"segment_filter":"pageTitle=@bali","created_date":"2017-10-20 02:41:08","updated_date":"2017-10-20 02:41:08","status":"active","multiple_idsites":"","site":{"id":"1","name":"Demo Site"},"allowedToEdit":"0"},{"idcustomreport":"5","idsite":"1","revision":"0","report_type":"table","name":"Country by New\/returning with a filter","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":"65","icon":"icon-business"},"subcategory":"","subcategory_order":"9999999","dimensions":{"row":{"UserCountry.Country","CoreHome.VisitorReturning"}},"metrics":{"row":{"nb_uniq_visitors","goal_7_conversion","goal_7_conversion_uniq_visitors_rate"}},"segment_filter":"countryCode!=pl","created_date":"2018-01-26 03:51:11","updated_date":"2018-01-26 03:51:11","status":"active","multiple_idsites":"","site":{"id":"1","name":"Demo Site"},"allowedToEdit":"0"},{"idcustomreport":"8","idsite":"1","revision":"0","report_type":"evolution","name":"Evolution KPIs","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":"65","icon":"icon-business"},"subcategory":"","subcategory_order":"9999999","dimensions":"","metrics":{"row":{"nb_uniq_visitors","goal_7_conversion","goal_7_conversion_uniq_visitors_rate","goal_4_conversion"}},"segment_filter":"","created_date":"2018-04-03 02:55:32","updated_date":"2018-04-03 02:55:32","status":"active","multiple_idsites":"","site":{"id":"1","name":"Demo Site"},"allowedToEdit":"0"}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true,
+ * @OA\Property(
+ * property="category",
+ * type="object"
+ * ),
+ * @OA\Property(
+ * property="dimensions",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="string"
+ * )
+ * )
+ * ),
+ * @OA\Property(
+ * property="metrics",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="string"
+ * )
+ * )
+ * ),
+ * @OA\Property(
+ * property="site",
+ * type="object"
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"idcustomreport":1,"idsite":1,"revision":0,"report_type":"table","name":"Pages by New\/Returning visitor","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":65,"icon":"icon-business"},"subcategory":null,"subcategory_order":9999999,"dimensions":{"CoreHome.VisitorReturning","Actions.PageTitle"},"metrics":{"nb_uniq_visitors","nb_visits","pageviews"},"segment_filter":"","created_date":"2017-10-20 02:31:50","updated_date":"2017-10-20 02:31:50","status":"active","multiple_idsites":null,"site":{"id":1,"name":"Demo Site"},"allowedToEdit":false},{"idcustomreport":2,"idsite":1,"revision":0,"report_type":"table","name":"Bali pages, breakdown new\/returning","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":65,"icon":"icon-business"},"subcategory":null,"subcategory_order":9999999,"dimensions":{"Actions.PageTitle","CoreHome.VisitorReturning"},"metrics":{"nb_uniq_visitors","pageviews","nb_visits"},"segment_filter":"pageTitle=@bali","created_date":"2017-10-20 02:41:08","updated_date":"2017-10-20 02:41:08","status":"active","multiple_idsites":null,"site":{"id":1,"name":"Demo Site"},"allowedToEdit":false},{"idcustomreport":5,"idsite":1,"revision":0,"report_type":"table","name":"Country by New\/returning with a filter","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":65,"icon":"icon-business"},"subcategory":null,"subcategory_order":9999999,"dimensions":{"UserCountry.Country","CoreHome.VisitorReturning"},"metrics":{"nb_uniq_visitors","goal_7_conversion","goal_7_conversion_uniq_visitors_rate"},"segment_filter":"countryCode!=pl","created_date":"2018-01-26 03:51:11","updated_date":"2018-01-26 03:51:11","status":"active","multiple_idsites":null,"site":{"id":1,"name":"Demo Site"},"allowedToEdit":false},{"idcustomreport":8,"idsite":1,"revision":0,"report_type":"evolution","name":"Evolution KPIs","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":65,"icon":"icon-business"},"subcategory":null,"subcategory_order":9999999,"dimensions":{},"metrics":{"nb_uniq_visitors","goal_7_conversion","goal_7_conversion_uniq_visitors_rate","goal_4_conversion"},"segment_filter":"","created_date":"2018-04-03 02:55:32","updated_date":"2018-04-03 02:55:32","status":"active","multiple_idsites":null,"site":{"id":1,"name":"Demo Site"},"allowedToEdit":false}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="idcustomreport", type="integer"),
+ * @OA\Property(property="idsite", type="integer"),
+ * @OA\Property(property="revision", type="integer"),
+ * @OA\Property(property="report_type", type="string"),
+ * @OA\Property(property="name", type="string"),
+ * @OA\Property(property="description", type="string"),
+ * @OA\Property(
+ * property="site",
+ * type="object",
+ * @OA\Property(property="id", type="integer"),
+ * @OA\Property(property="name", type="string")
+ * ),
+ * @OA\Property(property="subcategory", type={"string", "number", "integer", "boolean", "array", "object", "null"}),
+ * @OA\Property(property="subcategory_order", type="integer"),
+ * @OA\Property(property="segment_filter", type="string"),
+ * @OA\Property(property="created_date", type="string"),
+ * @OA\Property(property="updated_date", type="string"),
+ * @OA\Property(property="status", type="string"),
+ * @OA\Property(property="multiple_idsites", type={"string", "number", "integer", "boolean", "array", "object", "null"}),
+ * @OA\Property(property="allowedToEdit", type="boolean")
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getConfiguredReports($idSite, $skipCategoryMetadata = false)
+ {
+ $this->validator->checkReportViewPermission($idSite);
+ $this->validator->checkSiteExists($idSite);
+
+ if ($idSite === 'all') {
+ $idSite = 0;
+ }
+
+ $reports = $this->model->getAllCustomReportsForSite($idSite, $skipCategoryMetadata == '1');
+ usort($reports, function ($a, $b) {
+ if ($a['idcustomreport'] > $b['idcustomreport']) {
+ return 1; // no need to check for === because two reports won't have same ID
+ }
+ return -1;
+ });
+
+ foreach ($reports as &$report) {
+ $this->addAllowedToEditStatus($report);
+ }
+
+ return $reports;
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get a specific custom report configuration.
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report. [@example=1]
+ * @return array The details of the configured custom report.
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getConfiguredReport",
+ * operationId="CustomReports.getConfiguredReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report.",
+ * @OA\Schema(
+ * type="integer",
+ * example=1
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getConfiguredReport&idSite=1&idCustomReport=1&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getConfiguredReport&idSite=1&idCustomReport=1&format=JSON&token_auth=anonymous), TSV (N/A)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"idcustomreport":"1","idsite":"1","revision":"0","report_type":"table","name":"Pages by New\/Returning visitor","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":"65","icon":"icon-business"},"subcategory":"","subcategory_order":"9999999","dimensions":{"row":{"CoreHome.VisitorReturning","Actions.PageTitle"}},"metrics":{"row":{"nb_uniq_visitors","nb_visits","pageviews"}},"segment_filter":"","created_date":"2017-10-20 02:31:50","updated_date":"2017-10-20 02:31:50","status":"active","multiple_idsites":"","site":{"id":"1","name":"Demo Site"},"child_reports":"","multipleIdSites":"","allowedToEdit":"0"},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="category",
+ * type="object"
+ * ),
+ * @OA\Property(
+ * property="dimensions",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * ),
+ * @OA\Property(
+ * property="metrics",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * ),
+ * @OA\Property(
+ * property="site",
+ * type="object"
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={"idcustomreport":1,"idsite":1,"revision":0,"report_type":"table","name":"Pages by New\/Returning visitor","description":"","category":{"id":"CustomReports_CustomReports","name":"Custom Reports","order":65,"icon":"icon-business"},"subcategory":null,"subcategory_order":9999999,"dimensions":{"CoreHome.VisitorReturning","Actions.PageTitle"},"metrics":{"nb_uniq_visitors","nb_visits","pageviews"},"segment_filter":"","created_date":"2017-10-20 02:31:50","updated_date":"2017-10-20 02:31:50","status":"active","multiple_idsites":null,"site":{"id":1,"name":"Demo Site"},"child_reports":{},"multipleIdSites":{},"allowedToEdit":false},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Property(property="idcustomreport", type="integer"),
+ * @OA\Property(property="idsite", type="integer"),
+ * @OA\Property(property="revision", type="integer"),
+ * @OA\Property(property="report_type", type="string"),
+ * @OA\Property(property="name", type="string"),
+ * @OA\Property(property="description", type="string"),
+ * @OA\Property(
+ * property="multipleIdSites",
+ * type="array",
+ * @OA\Items()
+ * ),
+ * @OA\Property(property="subcategory", type={"string", "number", "integer", "boolean", "array", "object", "null"}),
+ * @OA\Property(property="subcategory_order", type="integer"),
+ * @OA\Property(property="segment_filter", type="string"),
+ * @OA\Property(property="created_date", type="string"),
+ * @OA\Property(property="updated_date", type="string"),
+ * @OA\Property(property="status", type="string"),
+ * @OA\Property(property="multiple_idsites", type={"string", "number", "integer", "boolean", "array", "object", "null"}),
+ * @OA\Property(property="allowedToEdit", type="boolean")
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getConfiguredReport($idSite, $idCustomReport)
+ {
+ $this->validator->checkReportViewPermission($idSite);
+ $this->validator->checkSiteExists($idSite);
+
+ if ($idSite === 'all') {
+ $idSite = 0;
+ }
+
+ $this->model->checkReportExists($idSite, $idCustomReport);
+
+ $report = $this->model->getCustomReport($idSite, $idCustomReport);
+ $this->addAllowedToEditStatus($report);
+
+ return $report;
+ }
+
+ /**
+ * Deletes the given custom report.
+ *
+ * When a custom report is deleted, its report will be no longer available in the API and tracked data for this
+ * report might be removed at some point by the system.
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report to duplicate.
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.deleteCustomReport",
+ * operationId="CustomReports.deleteCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to duplicate.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"success":""},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result")
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={"result":"success","message":"ok"},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Property(property="result", type="string"),
+ * @OA\Property(property="message", type="string")
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function deleteCustomReport($idSite, $idCustomReport): void
+ {
+ $this->validator->checkWritePermission($idSite);
+
+ if ($idSite === 'all') {
+ $idSite = 0;
+ }
+
+ $report = $this->getCustomReportInfo($idSite, $idCustomReport, 'Delete');
+
+ $multipleIDSites = $report['multiple_idsites'] ? explode(',', $report['multiple_idsites']) : [];
+ if ($multipleIDSites) {
+ foreach ($multipleIDSites as $multipleIdSite) {
+ $this->validator->checkWritePermission($multipleIdSite);
+ }
+ }
+ $this->archiveInvalidator->removeInvalidationsSafely(
+ $multipleIDSites ? $multipleIDSites : [$idSite],
+ 'CustomReports',
+ Archiver::makeRecordName($idCustomReport, $report['revision'])
+ );
+
+ $this->model->deactivateReport($multipleIDSites ? -1 : $idSite, $idCustomReport, $report['name']);
+ Piwik::postEvent('CustomReports.deleteCustomReport.end', array($idSite, $idCustomReport));
+ $this->clearCache();
+ }
+
+ /**
+ * Pauses the given custom report.
+ *
+ * When a custom report is paused, its report will be no longer be archived
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report to pause.
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.pauseCustomReport",
+ * operationId="CustomReports.pauseCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to pause.",
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Response(response=200, ref="#/components/responses/GenericSuccess"),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function pauseCustomReport($idSite, $idCustomReport): void
+ {
+ $this->validator->checkWritePermission($idSite);
+
+ if ($idSite === 'all') {
+ $idSite = 0;
+ }
+
+ $report = $this->getCustomReportInfo($idSite, $idCustomReport, 'Pause');
+
+ $multipleIDSites = $report['multiple_idsites'] ? explode(',', $report['multiple_idsites']) : [];
+ if ($multipleIDSites) {
+ foreach ($multipleIDSites as $multipleIdSite) {
+ $this->validator->checkWritePermission($multipleIdSite);
+ }
+ }
+ $this->archiveInvalidator->removeInvalidationsSafely(
+ $multipleIDSites ? $multipleIDSites : [$idSite],
+ 'CustomReports',
+ Archiver::makeRecordName($idCustomReport, $report['revision'])
+ );
+
+ $this->model->pauseReport($multipleIDSites ? -1 : $idSite, $idCustomReport, $report['name']);
+ $this->clearCache();
+ }
+
+ /**
+ * Resumes the given custom report.
+ *
+ * When a custom report is resumed, its report will start archiving again
+ *
+ * @param int $idSite
+ * @param int $idCustomReport The ID of the custom report to resume.
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.resumeCustomReport",
+ * operationId="CustomReports.resumeCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to resume.",
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Response(response=200, ref="#/components/responses/GenericSuccess"),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ public function resumeCustomReport($idSite, $idCustomReport): void
+ {
+ $this->validator->checkWritePermission($idSite);
+
+ if ($idSite === 'all') {
+ $idSite = 0;
+ }
+
+ $report = $this->getCustomReportInfo($idSite, $idCustomReport, 'Resume');
+
+ $multipleIDSites = $report['multiple_idsites'] ? explode(',', $report['multiple_idsites']) : [];
+ if ($multipleIDSites) {
+ foreach ($multipleIDSites as $multipleIdSite) {
+ $this->validator->checkWritePermission($multipleIdSite);
+ }
+ }
+
+ $this->model->resumeReport($multipleIDSites ? -1 : $idSite, $idCustomReport, $report['name']);
+ $this->clearCache();
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get a list of available categories that can be used in custom reports.
+ *
+ * @param int $idSite
+ * @return array
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getAvailableCategories",
+ * operationId="CustomReports.getAvailableCategories",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableCategories&idSite=1&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableCategories&idSite=1&format=JSON&token_auth=anonymous), TSV (N/A)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"uniqueId":"General_Actions","name":"Behaviour","subcategories":{"row":{{"uniqueId":"customdimension2","name":"Page Author"},{"uniqueId":"customdimension4","name":"Page Location"},{"uniqueId":"customdimension5","name":"Page Type"},{"uniqueId":"VisitorInterest_Engagement","name":"Engagement"},{"uniqueId":"Transitions_Transitions","name":"Transitions"},{"uniqueId":"General_Downloads","name":"Downloads"},{"uniqueId":"Actions_SubmenuPagesEntry","name":"Entry pages"},{"uniqueId":"Actions_SubmenuPagesExit","name":"Exit pages"},{"uniqueId":"General_Outlinks","name":"Outlinks"},{"uniqueId":"Actions_SubmenuPageTitles","name":"Page titles"},{"uniqueId":"General_Pages","name":"Pages"},{"uniqueId":"Actions_SubmenuSitesearch","name":"Site Search"},{"uniqueId":"Events_Events","name":"Events"},{"uniqueId":"Contents_Contents","name":"Contents"},{"uniqueId":"PagePerformance_Performance","name":"Performance"},{"uniqueId":"UsersFlow_TopPaths","name":"Top Paths"},{"uniqueId":"UsersFlow_UsersFlow","name":"Users Flow"},{"uniqueId":"SearchEngineKeywordsPerformance_CrawlingErrors","name":"Crawling errors"}}}},{"uniqueId":"General_Visitors","name":"Visitors","subcategories":{"row":{{"uniqueId":"customdimension1","name":"User Type"},{"uniqueId":"DevicesDetection_Devices","name":"Devices"},{"uniqueId":"DevicesDetection_Software","name":"Software"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"UserCountry_SubmenuLocations","name":"Locations"},{"uniqueId":"VisitTime_SubmenuTimes","name":"Times"},{"uniqueId":"UserCountryMap_RealTimeMap","name":"Real-time Map"},{"uniqueId":"General_RealTime","name":"Real-time"},{"uniqueId":"Live_VisitorLog","name":"Visits Log"},{"uniqueId":"UserId_UserReportTitle","name":"User IDs"},{"uniqueId":"CustomVariables_CustomVariables","name":"Custom Variables"}}}},{"uniqueId":"Referrers_Referrers","name":"Acquisition","subcategories":{"row":{{"uniqueId":"Referrers_AIAssistants","name":"AI Assistants"},{"uniqueId":"Referrers_WidgetGetAll","name":"All Channels"},{"uniqueId":"Referrers_URLCampaignBuilder","name":"Campaign URL Builder"},{"uniqueId":"Referrers_Campaigns","name":"Campaigns"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"Referrers_SubmenuSearchEngines","name":"Search Engines & Keywords"},{"uniqueId":"Referrers_Socials","name":"Social Networks"},{"uniqueId":"Referrers_SubmenuWebsitesOnly","name":"Websites"},{"uniqueId":"SearchEngineKeywordsPerformance_CrawlingStats","name":"Crawling overview"}}}},{"uniqueId":"Goals_Goals","name":"Goals","subcategories":{"row":{{"uniqueId":"8","name":"Agoda click"},{"uniqueId":"7","name":"Liveaboard.com click"},{"uniqueId":"4","name":"New Job Application"},{"uniqueId":"6","name":"New Resume"},{"uniqueId":"10","name":"Newsletter Signup"},{"uniqueId":"9","name":"User Comments"},{"uniqueId":"5","name":"View Submit Job"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"MultiChannelConversionAttribution_MultiAttribution","name":"Multi Attribution"}}}}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true,
+ * @OA\Property(
+ * property="subcategories",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"uniqueId":"General_Actions","name":"Behaviour","subcategories":{{"uniqueId":"customdimension2","name":"Page Author"},{"uniqueId":"customdimension4","name":"Page Location"},{"uniqueId":"customdimension5","name":"Page Type"},{"uniqueId":"VisitorInterest_Engagement","name":"Engagement"},{"uniqueId":"Transitions_Transitions","name":"Transitions"},{"uniqueId":"General_Downloads","name":"Downloads"},{"uniqueId":"Actions_SubmenuPagesEntry","name":"Entry pages"},{"uniqueId":"Actions_SubmenuPagesExit","name":"Exit pages"},{"uniqueId":"General_Outlinks","name":"Outlinks"},{"uniqueId":"Actions_SubmenuPageTitles","name":"Page titles"},{"uniqueId":"General_Pages","name":"Pages"},{"uniqueId":"Actions_SubmenuSitesearch","name":"Site Search"},{"uniqueId":"Events_Events","name":"Events"},{"uniqueId":"Contents_Contents","name":"Contents"},{"uniqueId":"PagePerformance_Performance","name":"Performance"},{"uniqueId":"UsersFlow_TopPaths","name":"Top Paths"},{"uniqueId":"UsersFlow_UsersFlow","name":"Users Flow"},{"uniqueId":"SearchEngineKeywordsPerformance_CrawlingErrors","name":"Crawling errors"}}},{"uniqueId":"General_Visitors","name":"Visitors","subcategories":{{"uniqueId":"customdimension1","name":"User Type"},{"uniqueId":"DevicesDetection_Devices","name":"Devices"},{"uniqueId":"DevicesDetection_Software","name":"Software"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"UserCountry_SubmenuLocations","name":"Locations"},{"uniqueId":"VisitTime_SubmenuTimes","name":"Times"},{"uniqueId":"UserCountryMap_RealTimeMap","name":"Real-time Map"},{"uniqueId":"General_RealTime","name":"Real-time"},{"uniqueId":"Live_VisitorLog","name":"Visits Log"},{"uniqueId":"UserId_UserReportTitle","name":"User IDs"},{"uniqueId":"CustomVariables_CustomVariables","name":"Custom Variables"}}},{"uniqueId":"Referrers_Referrers","name":"Acquisition","subcategories":{{"uniqueId":"Referrers_AIAssistants","name":"AI Assistants"},{"uniqueId":"Referrers_WidgetGetAll","name":"All Channels"},{"uniqueId":"Referrers_URLCampaignBuilder","name":"Campaign URL Builder"},{"uniqueId":"Referrers_Campaigns","name":"Campaigns"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"Referrers_SubmenuSearchEngines","name":"Search Engines & Keywords"},{"uniqueId":"Referrers_Socials","name":"Social Networks"},{"uniqueId":"Referrers_SubmenuWebsitesOnly","name":"Websites"},{"uniqueId":"SearchEngineKeywordsPerformance_CrawlingStats","name":"Crawling overview"}}},{"uniqueId":"Goals_Goals","name":"Goals","subcategories":{{"uniqueId":"8","name":"Agoda click"},{"uniqueId":"7","name":"Liveaboard.com click"},{"uniqueId":"4","name":"New Job Application"},{"uniqueId":"6","name":"New Resume"},{"uniqueId":"10","name":"Newsletter Signup"},{"uniqueId":"9","name":"User Comments"},{"uniqueId":"5","name":"View Submit Job"},{"uniqueId":"General_Overview","name":"Overview"},{"uniqueId":"MultiChannelConversionAttribution_MultiAttribution","name":"Multi Attribution"}}}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="uniqueId", type="string"),
+ * @OA\Property(property="name", type="string"),
+ * @OA\Property(
+ * property="subcategories",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="uniqueId", type="string"),
+ * @OA\Property(property="name", type="string")
+ * )
+ * )
+ * )
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getAvailableCategories($idSite)
+ {
+ $this->validator->checkReportViewPermission($idSite);
+
+ $reportPages = Request::processRequest('API.getReportPagesMetadata', array('idSite' => $idSite, 'filter_limit' => -1));
+
+ $categories = array();
+ foreach ($reportPages as $reportPage) {
+ if (!empty($reportPage['category']['id'])) {
+ $categoryId = $reportPage['category']['id'];
+
+ if ($categoryId === 'Dashboard_Dashboard') {
+ continue;
+ }
+
+ $subcategoryId = $reportPage['subcategory']['id'];
+ if (strpos($subcategoryId, '_Manage') !== false) {
+ continue; // we do not want to be able to add reports to manage pages
+ }
+
+ if (isset($categories[$categoryId])) {
+ $categories[$categoryId]['subcategories'][] = array(
+ 'uniqueId' => $reportPage['subcategory']['id'],
+ 'name' => $reportPage['subcategory']['name']
+ );
+ } else {
+ $categories[$categoryId] = array(
+ 'uniqueId' => $categoryId,
+ 'name' => $reportPage['category']['name'],
+ 'subcategories' => array(
+ array(
+ 'uniqueId' => $reportPage['subcategory']['id'],
+ 'name' => $reportPage['subcategory']['name']
+ )
+ ),
+ );
+ }
+ }
+ }
+
+ if (!isset($categories['CustomReports_CustomReports'])) {
+ $categories['CustomReports_CustomReports'] = array(
+ 'uniqueId' => 'CustomReports_CustomReports',
+ 'name' => Piwik::translate('CustomReports_CustomReports'),
+ 'subcategories' => array()
+ );
+ }
+
+ return array_values($categories);
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get a list of available report types that can be used in custom reports.
+ *
+ * @return array
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getAvailableReportTypes",
+ * operationId="CustomReports.getAvailableReportTypes",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableReportTypes&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableReportTypes&format=JSON&token_auth=anonymous), [TSV (Excel)](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableReportTypes&format=Tsv&token_auth=anonymous)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"key":"table","value":"Table"},{"key":"evolution","value":"Evolution"}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"key":"table","value":"Table"},{"key":"evolution","value":"Evolution"}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="key", type="string"),
+ * @OA\Property(property="value", type="string")
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/vnd.ms-excel",
+ * example="key value
+ * table Table
+ * evolution Evolution"
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getAvailableReportTypes()
+ {
+ $this->validator->checkHasSomeWritePermission();
+
+ $rows = array();
+ foreach (ReportType::getAll() as $reportType) {
+ $rows[] = array('key' => $reportType::ID, 'value' => $reportType->getName());
+ }
+
+ return $rows;
+ }
+
+ private function isTableJoinable($tableName)
+ {
+ $logTable = $this->logTablesProvider->getLogTable($tableName);
+ if ($logTable && ($logTable->getColumnToJoinOnIdAction() || $logTable->getColumnToJoinOnIdVisit())) {
+ if ($logTable->getPrimaryKey()) {
+ // without primary key we would not group the data correctly
+ return true;
+ }
+ } elseif ($logTable && $logTable->getWaysToJoinToOtherLogTables()) {
+ $tables = new JoinTables($this->logTablesProvider, [$tableName]);
+ return $tables->isTableJoinableOnVisit($tableName) || $tables->isTableJoinableOnAction($tableName);
+ }
+
+ return false;
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get a list of available dimensions that can be used in custom reports.
+ *
+ * @param int $idSite
+ * @return array
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getAvailableDimensions",
+ * operationId="CustomReports.getAvailableDimensions",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableDimensions&idSite=1&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableDimensions&idSite=1&format=JSON&token_auth=anonymous), TSV (N/A)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"category":"Visitor location","dimensions":{"row":{{"uniqueId":"UserCountry.City","name":"City","sqlSegment":"log_visit.location_city"},{"uniqueId":"UserCountry.Continent","name":"Continent","sqlSegment":"log_visit.location_country"},{"uniqueId":"UserCountry.Country","name":"Country","sqlSegment":"log_visit.location_country"},{"uniqueId":"UserLanguage.Language","name":"Language","sqlSegment":"log_visit.location_browser_lang"},{"uniqueId":"UserCountry.Latitude","name":"Latitude","sqlSegment":"log_visit.location_latitude"},{"uniqueId":"UserCountry.Longitude","name":"Longitude","sqlSegment":"log_visit.location_longitude"},{"uniqueId":"UserCountry.Region","name":"Region","sqlSegment":"log_visit.location_region"}}},"orderId":"7"},{"category":"Events","dimensions":{"row":{{"uniqueId":"Events.EventAction","name":"Event Action","sqlSegment":"log_link_visit_action.idaction_event_action"},{"uniqueId":"Events.EventCategory","name":"Event Category","sqlSegment":"log_link_visit_action.idaction_event_category"},{"uniqueId":"Events.EventName","name":"Event Name","sqlSegment":"log_link_visit_action.idaction_name"},{"uniqueId":"Events.EventUrl","name":"Event URL","sqlSegment":"log_link_visit_action.idaction_url"},{"uniqueId":"Events.EventValue","name":"Event Value","sqlSegment":"log_link_visit_action.custom_float"}}},"orderId":"12"},{"category":"Acquisition","dimensions":{"row":{{"uniqueId":"AdvertisingConversionExport.AdClickId","name":"Ad Click ID","sqlSegment":"log_clickid.adclickid"},{"uniqueId":"AdvertisingConversionExport.AdProvider","name":"Ad Provider","sqlSegment":"log_clickid.adprovider"},{"uniqueId":"MarketingCampaignsReporting.CampaignContent","name":"Campaign Content","sqlSegment":"log_visit.campaign_content"},{"uniqueId":"MarketingCampaignsReporting.CampaignGroup","name":"Campaign Group","sqlSegment":"log_visit.campaign_group"},{"uniqueId":"MarketingCampaignsReporting.CampaignId","name":"Campaign Id","sqlSegment":"log_visit.campaign_id"},{"uniqueId":"MarketingCampaignsReporting.CampaignKeyword","name":"Campaign Keyword","sqlSegment":"log_visit.campaign_keyword"},{"uniqueId":"MarketingCampaignsReporting.CampaignMedium","name":"Campaign Medium","sqlSegment":"log_visit.campaign_medium"},{"uniqueId":"MarketingCampaignsReporting.CampaignName","name":"Campaign Name","sqlSegment":"log_visit.campaign_name"},{"uniqueId":"MarketingCampaignsReporting.CampaignPlacement","name":"Campaign Placement","sqlSegment":"log_visit.campaign_placement"},{"uniqueId":"MarketingCampaignsReporting.CampaignSource","name":"Campaign Source","sqlSegment":"log_visit.campaign_source"},{"uniqueId":"Referrers.ReferrerType","name":"Channel Type","sqlSegment":"log_visit.referer_type"},{"uniqueId":"Referrers.Keyword","name":"Keyword","sqlSegment":"log_visit.referer_keyword"},{"uniqueId":"Referrers.ReferrerName","name":"Referrer Name","sqlSegment":"log_visit.referer_name"},{"uniqueId":"Referrers.ReferrerUrl","name":"Referrer URL","sqlSegment":"log_visit.referer_url"}}},"orderId":"15"}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true,
+ * @OA\Property(
+ * property="dimensions",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"category":"Visitor location","dimensions":{{"uniqueId":"UserCountry.City","name":"City","sqlSegment":"log_visit.location_city"},{"uniqueId":"UserCountry.Continent","name":"Continent","sqlSegment":"log_visit.location_country"},{"uniqueId":"UserCountry.Country","name":"Country","sqlSegment":"log_visit.location_country"},{"uniqueId":"UserLanguage.Language","name":"Language","sqlSegment":"log_visit.location_browser_lang"},{"uniqueId":"UserCountry.Latitude","name":"Latitude","sqlSegment":"log_visit.location_latitude"},{"uniqueId":"UserCountry.Longitude","name":"Longitude","sqlSegment":"log_visit.location_longitude"},{"uniqueId":"UserCountry.Region","name":"Region","sqlSegment":"log_visit.location_region"}},"orderId":7},{"category":"Events","dimensions":{{"uniqueId":"Events.EventAction","name":"Event Action","sqlSegment":"log_link_visit_action.idaction_event_action"},{"uniqueId":"Events.EventCategory","name":"Event Category","sqlSegment":"log_link_visit_action.idaction_event_category"},{"uniqueId":"Events.EventName","name":"Event Name","sqlSegment":"log_link_visit_action.idaction_name"},{"uniqueId":"Events.EventUrl","name":"Event URL","sqlSegment":"log_link_visit_action.idaction_url"},{"uniqueId":"Events.EventValue","name":"Event Value","sqlSegment":"log_link_visit_action.custom_float"}},"orderId":12},{"category":"Acquisition","dimensions":{{"uniqueId":"AdvertisingConversionExport.AdClickId","name":"Ad Click ID","sqlSegment":"log_clickid.adclickid"},{"uniqueId":"AdvertisingConversionExport.AdProvider","name":"Ad Provider","sqlSegment":"log_clickid.adprovider"},{"uniqueId":"MarketingCampaignsReporting.CampaignContent","name":"Campaign Content","sqlSegment":"log_visit.campaign_content"},{"uniqueId":"MarketingCampaignsReporting.CampaignGroup","name":"Campaign Group","sqlSegment":"log_visit.campaign_group"},{"uniqueId":"MarketingCampaignsReporting.CampaignId","name":"Campaign Id","sqlSegment":"log_visit.campaign_id"},{"uniqueId":"MarketingCampaignsReporting.CampaignKeyword","name":"Campaign Keyword","sqlSegment":"log_visit.campaign_keyword"},{"uniqueId":"MarketingCampaignsReporting.CampaignMedium","name":"Campaign Medium","sqlSegment":"log_visit.campaign_medium"},{"uniqueId":"MarketingCampaignsReporting.CampaignName","name":"Campaign Name","sqlSegment":"log_visit.campaign_name"},{"uniqueId":"MarketingCampaignsReporting.CampaignPlacement","name":"Campaign Placement","sqlSegment":"log_visit.campaign_placement"},{"uniqueId":"MarketingCampaignsReporting.CampaignSource","name":"Campaign Source","sqlSegment":"log_visit.campaign_source"},{"uniqueId":"Referrers.ReferrerType","name":"Channel Type","sqlSegment":"log_visit.referer_type"},{"uniqueId":"Referrers.Keyword","name":"Keyword","sqlSegment":"log_visit.referer_keyword"},{"uniqueId":"Referrers.ReferrerName","name":"Referrer Name","sqlSegment":"log_visit.referer_name"},{"uniqueId":"Referrers.ReferrerUrl","name":"Referrer URL","sqlSegment":"log_visit.referer_url"}},"orderId":15}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="category", type="string"),
+ * @OA\Property(
+ * property="dimensions",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="uniqueId", type="string"),
+ * @OA\Property(property="name", type="string"),
+ * @OA\Property(property="sqlSegment", type="string")
+ * )
+ * )
+ * ),
+ * @OA\Property(property="orderId", type="integer")
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getAvailableDimensions($idSite)
+ {
+ Piwik::checkUserIsNotAnonymous();
+ Piwik::checkUserHasSomeViewAccess();
+
+ $dimensions = $this->columnsProvider->getAllDimensions();
+
+ $rows = array();
+
+ $dimensionsToIgnore = array(
+ 'Actions.IdPageview', 'CoreHome.VisitId',
+ 'DevicesDetection.OsVersion', // only makes sense in combination with Os Family
+ 'CoreHome.LinkVisitActionId', 'CoreHome.LinkVisitActionIdPages', 'UserCountry.Provider'
+ );
+
+ $dimensionsToRename = [
+ 'CoreHome.IdSite' => Piwik::translate('CustomReports_WebsiteName')
+ ];
+
+ $config = StaticContainer::get(Configuration::class);
+ foreach ($config->getDisabledDimensions() as $dimensionDisabled) {
+ $dimensionsToIgnore[] = $dimensionDisabled;
+ }
+
+ /**
+ * Adds the possibility to other plugins to ignore more dimensions
+ */
+ Piwik::postEvent('CustomReports.addDimensionsToIgnore', array(&$dimensionsToIgnore));
+
+ $categoryList = CategoryList::get();
+
+ foreach ($dimensions as $dimension) {
+ $categoryId = $dimension->getCategoryId();
+ $dimensionName = $dimension->getName();
+ $table = $dimension->getDbTableName();
+ $dimensionId = $dimension->getId();
+
+ if (!$table) {
+ // without table we cannot join it
+ continue;
+ }
+
+ if (!$this->isTableJoinable($table)) {
+ // archiving this dimension would not work
+ continue;
+ }
+
+ if (in_array($dimensionId, $dimensionsToIgnore)) {
+ continue;
+ }
+
+ if (key_exists($dimensionId, $dimensionsToRename)) {
+ $dimensionName = $dimensionsToRename[$dimensionId];
+ }
+
+ if ($dimension->getColumnName() && $dimensionName) {
+ if (!isset($rows[$categoryId])) {
+ $category = $categoryList->getCategory($categoryId);
+ $orderId = 999;
+ if (!empty($category)) {
+ $orderId = $category->getOrder();
+ }
+
+ $categoryName = Piwik::translate($categoryId);
+ if (!is_null($category) && method_exists($category, 'getDisplayName')) {
+ $categoryName = $category->getDisplayName();
+ }
+
+ $rows[$categoryId] = array(
+ 'category' => $categoryName,
+ 'dimensions' => array(),
+ 'orderId' => $orderId
+ );
+ }
+ $rows[$categoryId]['dimensions'][] = array(
+ 'uniqueId' => $dimension->getId(),
+ 'name' => ucwords($dimensionName),
+ 'sqlSegment' => $dimension->getSqlSegment(),
+ );
+ }
+ }
+
+ usort($rows, function ($rowA, $rowB) {
+ if ((int)$rowA['orderId'] > (int)$rowB['orderId']) {
+ return 1;
+ }
+ if ((int)$rowA['orderId'] === (int)$rowB['orderId']) {
+ return 0;
+ }
+ return -1;
+ });
+
+ foreach ($rows as $categoryId => $row) {
+ $dimensions = $row['dimensions'];
+ usort($dimensions, function ($dimA, $dimB) {
+ return strcmp($dimA['name'], $dimB['name']);
+ });
+ $rows[$categoryId]['dimensions'] = $dimensions;
+ }
+
+ return array_values($rows);
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get a list of available metrics that can be used in custom reports.
+ *
+ * @param int $idSite
+ * @return array
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getAvailableMetrics",
+ * operationId="CustomReports.getAvailableMetrics",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableMetrics&idSite=1&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/?module=API&method=CustomReports.getAvailableMetrics&idSite=1&format=JSON&token_auth=anonymous), TSV (N/A)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"category":"Visitor location","metrics":{"row":{{"uniqueId":"nb_uniq_usercountry_city","name":"Unique Cities","description":"The unique number of Cities. When viewing a period that is not day, then this metric will become ""Sum of Unique Cities"". In such case, if the same Cities appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_continent","name":"Unique Continents","description":"The unique number of Continents. When viewing a period that is not day, then this metric will become ""Sum of Unique Continents"". In such case, if the same Continents appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_country","name":"Unique Countries","description":"The unique number of Countries. When viewing a period that is not day, then this metric will become ""Sum of Unique Countries"". In such case, if the same Countries appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_userlanguage_language","name":"Unique Languages","description":"The unique number of Languages. When viewing a period that is not day, then this metric will become ""Sum of Unique Languages"". In such case, if the same Languages appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_latitude","name":"Unique Latitudes","description":"The unique number of Latitudes. When viewing a period that is not day, then this metric will become ""Sum of Unique Latitudes"". In such case, if the same Latitudes appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_longitude","name":"Unique Longitudes","description":"The unique number of Longitudes. When viewing a period that is not day, then this metric will become ""Sum of Unique Longitudes"". In such case, if the same Longitudes appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_region","name":"Unique Regions","description":"The unique number of Regions. When viewing a period that is not day, then this metric will become ""Sum of Unique Regions"". In such case, if the same Regions appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."}}},"orderId":"7"}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true,
+ * @OA\Property(
+ * property="metrics",
+ * type="object",
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"category":"Visitor location","metrics":{{"uniqueId":"nb_uniq_usercountry_city","name":"Unique Cities","description":"The unique number of Cities. When viewing a period that is not day, then this metric will become ""Sum of Unique Cities"". In such case, if the same Cities appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_continent","name":"Unique Continents","description":"The unique number of Continents. When viewing a period that is not day, then this metric will become ""Sum of Unique Continents"". In such case, if the same Continents appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_country","name":"Unique Countries","description":"The unique number of Countries. When viewing a period that is not day, then this metric will become ""Sum of Unique Countries"". In such case, if the same Countries appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_userlanguage_language","name":"Unique Languages","description":"The unique number of Languages. When viewing a period that is not day, then this metric will become ""Sum of Unique Languages"". In such case, if the same Languages appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_latitude","name":"Unique Latitudes","description":"The unique number of Latitudes. When viewing a period that is not day, then this metric will become ""Sum of Unique Latitudes"". In such case, if the same Latitudes appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_longitude","name":"Unique Longitudes","description":"The unique number of Longitudes. When viewing a period that is not day, then this metric will become ""Sum of Unique Longitudes"". In such case, if the same Longitudes appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."},{"uniqueId":"nb_uniq_usercountry_region","name":"Unique Regions","description":"The unique number of Regions. When viewing a period that is not day, then this metric will become ""Sum of Unique Regions"". In such case, if the same Regions appear in 2 or more days within the selected period, then it will be counted as 2 or the total number of days it has appeared and not 1."}},"orderId":7}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="category", type="string"),
+ * @OA\Property(
+ * property="metrics",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="uniqueId", type="string"),
+ * @OA\Property(property="name", type="string"),
+ * @OA\Property(property="description", type="string")
+ * )
+ * )
+ * ),
+ * @OA\Property(property="orderId", type="integer")
+ * )
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getAvailableMetrics($idSite)
+ {
+ Piwik::checkUserIsNotAnonymous();
+ Piwik::checkUserHasSomeViewAccess();
+
+ $metrics = MetricsList::get();
+ $categoryList = CategoryList::get();
+
+ $rows = array();
+ $period = Common::getRequestVar('period', '', 'string');
+ foreach ($metrics->getMetrics() as $metric) {
+ if (!$metric) {
+ continue;
+ }
+ if ($metric instanceof ProcessedMetric && !$this->canGenerateMetricAutomatically($metric)) {
+ // we do not have all the dependent metrics to generate this processed metric automatically
+ continue;
+ }
+
+ $categoryId = $metric->getCategoryId();
+ $name = $metric->getName();
+ $translatedName = $metric->getTranslatedName();
+
+ if (($metric instanceof ProcessedMetric || $metric instanceof ArchivedMetric) && $name && $translatedName) {
+ if (method_exists($metric, 'getQuery') && !$metric->getQuery()) {
+ // archiving this metric would not work!
+ continue;
+ }
+
+ if (method_exists($metric, 'getDbTableName') && $metric->getDbTableName() && !$this->isTableJoinable($metric->getDbTableName())) {
+ // archiving this metric would not work!
+ continue;
+ }
+
+ if (method_exists($metric, 'getDimension') && $metric->getDimension()) {
+ $dimension = $metric->getDimension();
+ $dbDiscriminator = $dimension->getDbDiscriminator();
+ if ($dbDiscriminator) {
+ $dbDiscriminatorValue = $dbDiscriminator->getValue();
+ if (!isset($dbDiscriminatorValue) || !is_numeric($dbDiscriminatorValue)) {
+ continue;
+ }
+ }
+ }
+
+ if (!isset($rows[$categoryId])) {
+ $category = $categoryList->getCategory($categoryId);
+ $orderId = 999;
+ if (!empty($category)) {
+ $orderId = $category->getOrder();
+ }
+
+ $categoryName = Piwik::translate($categoryId);
+ if (!is_null($category) && method_exists($category, 'getDisplayName')) {
+ $categoryName = $category->getDisplayName();
+ }
+
+ $rows[$categoryId] = array(
+ 'category' => $categoryName,
+ 'metrics' => array(),
+ 'orderId' => $orderId
+ );
+ }
+
+ $description = $metric->getDocumentation();
+ if (stripos($translatedName, 'unique') === 0) {
+ $description = Piwik::translate('CustomReports_CommonUniqueMetricDescription', array($description, ucwords($translatedName), str_replace('Unique ', '', ucwords($translatedName))));
+ }
+
+ $rows[$categoryId]['metrics'][] = array(
+ 'uniqueId' => $name, 'name' => ucwords($translatedName), 'description' => $description
+ );
+ }
+ }
+
+ usort($rows, function ($rowA, $rowB) {
+ if ((int)$rowA['orderId'] > (int)$rowB['orderId']) {
+ return 1;
+ }
+ if ((int)$rowA['orderId'] === (int)$rowB['orderId']) {
+ return 0;
+ }
+ return -1;
+ });
+
+ foreach ($rows as $category => $row) {
+ $dimensions = $row['metrics'];
+ usort($dimensions, function ($dimA, $dimB) {
+ return strcasecmp($dimA['name'], $dimB['name']);
+ });
+ $rows[$category]['metrics'] = $dimensions;
+ }
+
+ return array_values($rows);
+ }
+
+ private function canGenerateMetricAutomatically(ProcessedMetric $metric)
+ {
+ foreach ($metric->getDependentMetrics() as $dependentMetric) {
+ $depMetric = $this->metricsList->getMetric($dependentMetric);
+ if (!$depMetric) {
+ // we cannot generate this metric directly
+ return false;
+ }
+
+ if ($depMetric instanceof ProcessedMetric && !$this->canGenerateMetricAutomatically($depMetric)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // phpcs:disable Generic.Files.LineLength
+ /**
+ * Get report data for a previously created custom report.
+ *
+ * @param int $idSite
+ * @param string $period
+ * @param string $date
+ * @param int $idCustomReport The ID of the custom report to look up data for.
+ * @param string $segment
+ * @param bool $expanded
+ * @param bool $flat
+ * @param int $idSubtable
+ * @param string $columns
+ *
+ * @return DataTable\DataTableInterface
+ *
+ * @OA\Get(
+ * path="/index.php?module=API&method=CustomReports.getCustomReport",
+ * operationId="CustomReports.getCustomReport",
+ * tags={"CustomReports"},
+ * @OA\Parameter(ref="#/components/parameters/formatOptional"),
+ * @OA\Parameter(ref="#/components/parameters/idSiteRequired"),
+ * @OA\Parameter(
+ * name="period",
+ * in="query",
+ * required=true,
+ * description="The ID of the custom report to look up data for.",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="date",
+ * in="query",
+ * required=true,
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="idCustomReport",
+ * in="query",
+ * required=true,
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="segment",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="expanded",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="boolean",
+ * default=false
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="flat",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="boolean",
+ * default=false
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="idSubtable",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="integer"
+ * )
+ * ),
+ * @OA\Parameter(
+ * name="columns",
+ * in="query",
+ * required=false,
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Example links: [XML](https://demo.matomo.cloud/index.php?module=API&method=CustomReports.getCustomReport&idSite=1&idCustomReport=5&period=day&date=today&format=xml&token_auth=anonymous), [JSON](https://demo.matomo.cloud/index.php?module=API&method=CustomReports.getCustomReport&idSite=1&idCustomReport=5&period=day&date=today&format=JSON&token_auth=anonymous), [TSV (Excel)](https://demo.matomo.cloud/index.php?module=API&method=CustomReports.getCustomReport&idSite=1&idCustomReport=5&period=day&date=today&format=Tsv&token_auth=anonymous)",
+ * @OA\MediaType(
+ * mediaType="text/xml",
+ * example={"row":{{"label":"Australia","nb_uniq_visitors":"1","goal_7_conversion":"0","level":"1","goal_7_conversion_uniq_visitors_rate":"0","segment":"countryCode==au;countryCode!=pl"},{"label":"United States","nb_uniq_visitors":"1","goal_7_conversion":"0","level":"1","goal_7_conversion_uniq_visitors_rate":"0","segment":"countryCode==us;countryCode!=pl"}}},
+ * @OA\Schema(
+ * type="object",
+ * @OA\Xml(name="result"),
+ * @OA\Property(
+ * property="row",
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * @OA\Xml(name="row"),
+ * additionalProperties=true
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * example={{"label":"Australia","nb_uniq_visitors":1,"goal_7_conversion":0,"level":1,"goal_7_conversion_uniq_visitors_rate":0,"segment":"countryCode==au;countryCode!=pl"},{"label":"United States","nb_uniq_visitors":1,"goal_7_conversion":0,"level":1,"goal_7_conversion_uniq_visitors_rate":0,"segment":"countryCode==us;countryCode!=pl"}},
+ * @OA\Schema(
+ * type="array",
+ * @OA\Items(
+ * type="object",
+ * additionalProperties=true,
+ * @OA\Property(
+ * type="object",
+ * @OA\Property(property="label", type="string"),
+ * @OA\Property(property="nb_uniq_visitors", type="integer"),
+ * @OA\Property(property="goal_7_conversion", type="integer"),
+ * @OA\Property(property="level", type="integer"),
+ * @OA\Property(property="goal_7_conversion_uniq_visitors_rate", type="integer"),
+ * @OA\Property(property="segment", type="string")
+ * )
+ * )
+ * )
+ * ),
+ * @OA\MediaType(
+ * mediaType="application/vnd.ms-excel",
+ * example="label nb_uniq_visitors goal_7_conversion level goal_7_conversion_uniq_visitors_rate metadata_segment
+ * Australia 1 0 1 0 ""countryCode==au;countryCode!=pl""
+ * United States 1 0 1 0 ""countryCode==us;countryCode!=pl"""
+ * )
+ * ),
+ * @OA\Response(response=400, ref="#/components/responses/BadRequest"),
+ * @OA\Response(response=401, ref="#/components/responses/Unauthorized"),
+ * @OA\Response(response=403, ref="#/components/responses/Forbidden"),
+ * @OA\Response(response=404, ref="#/components/responses/NotFound"),
+ * @OA\Response(response=500, ref="#/components/responses/ServerError"),
+ * @OA\Response(response="default", ref="#/components/responses/DefaultError")
+ * )
+ */
+ // phpcs:enable Generic.Files.LineLength
+ public function getCustomReport($idSite, $period, $date, $idCustomReport, $segment = false, $expanded = false, $flat = false, $idSubtable = false, $columns = false)
+ {
+ $this->validator->checkReportViewPermission($idSite);
+ $this->validator->checkSiteExists($idSite); // lets not return any reports from eg deleted sites if for some reason report still exists
+ $this->model->checkReportExists($idSite, $idCustomReport);
+
+ $report = $this->model->getCustomReport($idSite, $idCustomReport);
+
+ $reportType = ReportType::factory($report['report_type']);
+
+ $table = $reportType->fetchApi($idSite, $idCustomReport, $period, $date, $segment, $expanded, $flat, $idSubtable, $columns);
+
+ return $table;
+ }
+
+ private function addAllowedToEditStatus(&$report)
+ {
+ $idSite = $report['idsite'];
+ $multipleIdSites = $report['multiple_idsites'] ? explode(',', $report['multiple_idsites']) : array();
+ if (Piwik::hasUserSuperUserAccess()) {
+ $allowedToEdit = true;
+ } elseif ($idSite == 'all' || $idSite == '0') {
+ $allowedToEdit = false;
+ } else {
+ $allowedToEdit = $multipleIdSites ? Piwik::isUserHasWriteAccess($multipleIdSites) : Piwik::isUserHasWriteAccess($idSite);
+ }
+
+ $report['allowedToEdit'] = $allowedToEdit;
+ }
+
+ private function getCustomReportInfo($idSite, $idCustomReport, $action)
+ {
+ $report = $this->model->getCustomReport($idSite, $idCustomReport);
+
+ if (empty($report)) {
+ throw new \Exception(Piwik::translate('CustomReports_ErrorCustomReportDoesNotExist'));
+ } elseif ($report['idsite'] != $idSite) {
+ // prevent a possible hack that someone passes a different site than the report has and then we accidentally
+ // still delete the report because we match with `idsite = 0 or idsite = ?`. We don't do this here right now
+ // and wouldn't need this code but it is to prevent any possible future security bugs.
+ throw new \Exception(Piwik::translate('CustomReports_' . $action . 'ExceptionMessage'));
+ }
+
+ return $report;
+ }
+}
diff --git a/files/plugin-CustomReports-5.4.3/Activity/BaseActivity.php b/files/plugin-CustomReports-5.4.5/Activity/BaseActivity.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/BaseActivity.php
rename to files/plugin-CustomReports-5.4.5/Activity/BaseActivity.php
diff --git a/files/plugin-CustomReports-5.4.3/Activity/ReportAdded.php b/files/plugin-CustomReports-5.4.5/Activity/ReportAdded.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/ReportAdded.php
rename to files/plugin-CustomReports-5.4.5/Activity/ReportAdded.php
diff --git a/files/plugin-CustomReports-5.4.3/Activity/ReportDeleted.php b/files/plugin-CustomReports-5.4.5/Activity/ReportDeleted.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/ReportDeleted.php
rename to files/plugin-CustomReports-5.4.5/Activity/ReportDeleted.php
diff --git a/files/plugin-CustomReports-5.4.3/Activity/ReportPaused.php b/files/plugin-CustomReports-5.4.5/Activity/ReportPaused.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/ReportPaused.php
rename to files/plugin-CustomReports-5.4.5/Activity/ReportPaused.php
diff --git a/files/plugin-CustomReports-5.4.3/Activity/ReportResumed.php b/files/plugin-CustomReports-5.4.5/Activity/ReportResumed.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/ReportResumed.php
rename to files/plugin-CustomReports-5.4.5/Activity/ReportResumed.php
diff --git a/files/plugin-CustomReports-5.4.3/Activity/ReportUpdated.php b/files/plugin-CustomReports-5.4.5/Activity/ReportUpdated.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Activity/ReportUpdated.php
rename to files/plugin-CustomReports-5.4.5/Activity/ReportUpdated.php
diff --git a/files/plugin-CustomReports-5.4.3/Archiver.php b/files/plugin-CustomReports-5.4.5/Archiver.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Archiver.php
rename to files/plugin-CustomReports-5.4.5/Archiver.php
diff --git a/files/plugin-CustomReports-5.4.3/Archiver/ExecutionPlan.php b/files/plugin-CustomReports-5.4.5/Archiver/ExecutionPlan.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Archiver/ExecutionPlan.php
rename to files/plugin-CustomReports-5.4.5/Archiver/ExecutionPlan.php
diff --git a/files/plugin-CustomReports-5.4.3/Archiver/NotJoinableException.php b/files/plugin-CustomReports-5.4.5/Archiver/NotJoinableException.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Archiver/NotJoinableException.php
rename to files/plugin-CustomReports-5.4.5/Archiver/NotJoinableException.php
diff --git a/files/plugin-CustomReports-5.4.3/Archiver/QueryBuilder.php b/files/plugin-CustomReports-5.4.5/Archiver/QueryBuilder.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Archiver/QueryBuilder.php
rename to files/plugin-CustomReports-5.4.5/Archiver/QueryBuilder.php
diff --git a/files/plugin-CustomReports-5.4.3/Archiver/ReportQuery.php b/files/plugin-CustomReports-5.4.5/Archiver/ReportQuery.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Archiver/ReportQuery.php
rename to files/plugin-CustomReports-5.4.5/Archiver/ReportQuery.php
diff --git a/files/plugin-CustomReports-5.4.3/CHANGELOG.md b/files/plugin-CustomReports-5.4.5/CHANGELOG.md
similarity index 96%
rename from files/plugin-CustomReports-5.4.3/CHANGELOG.md
rename to files/plugin-CustomReports-5.4.5/CHANGELOG.md
index c88875b..428795f 100644
--- a/files/plugin-CustomReports-5.4.3/CHANGELOG.md
+++ b/files/plugin-CustomReports-5.4.5/CHANGELOG.md
@@ -1,5 +1,12 @@
## Changelog
+5.4.5 - 2025-10-13
+- Added triggering of event when reports are copied
+
+5.4.4 - 2025-09-15
+- Added ability to copy a custom report when Matomo 5.4.0-b4 or later is installed
+- Fixed bug when creating an evolution report which prevented historical archiving of weekly/monthly/yearly data
+
5.4.3 - 2025-07-21
- Stopped filtering empty values for regionCode dimension
diff --git a/files/plugin-CustomReports-5.4.3/Categories/CustomReportsCategory.php b/files/plugin-CustomReports-5.4.5/Categories/CustomReportsCategory.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Categories/CustomReportsCategory.php
rename to files/plugin-CustomReports-5.4.5/Categories/CustomReportsCategory.php
diff --git a/files/plugin-CustomReports-5.4.3/Categories/ManageReportsSubcategory.php b/files/plugin-CustomReports-5.4.5/Categories/ManageReportsSubcategory.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Categories/ManageReportsSubcategory.php
rename to files/plugin-CustomReports-5.4.5/Categories/ManageReportsSubcategory.php
diff --git a/files/plugin-CustomReports-5.4.3/Columns/CustomMetricHelper.php b/files/plugin-CustomReports-5.4.5/Columns/CustomMetricHelper.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Columns/CustomMetricHelper.php
rename to files/plugin-CustomReports-5.4.5/Columns/CustomMetricHelper.php
diff --git a/files/plugin-CustomReports-5.4.3/Columns/ProductCategory.php b/files/plugin-CustomReports-5.4.5/Columns/ProductCategory.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Columns/ProductCategory.php
rename to files/plugin-CustomReports-5.4.5/Columns/ProductCategory.php
diff --git a/files/plugin-CustomReports-5.4.3/Commands/ArchiveReports.php b/files/plugin-CustomReports-5.4.5/Commands/ArchiveReports.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Commands/ArchiveReports.php
rename to files/plugin-CustomReports-5.4.5/Commands/ArchiveReports.php
diff --git a/files/plugin-CustomReports-5.4.3/Commands/GenerateReports.php b/files/plugin-CustomReports-5.4.5/Commands/GenerateReports.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Commands/GenerateReports.php
rename to files/plugin-CustomReports-5.4.5/Commands/GenerateReports.php
diff --git a/files/plugin-CustomReports-5.4.3/Configuration.php b/files/plugin-CustomReports-5.4.5/Configuration.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Configuration.php
rename to files/plugin-CustomReports-5.4.5/Configuration.php
diff --git a/files/plugin-CustomReports-5.4.3/Controller.php b/files/plugin-CustomReports-5.4.5/Controller.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Controller.php
rename to files/plugin-CustomReports-5.4.5/Controller.php
diff --git a/files/plugin-CustomReports-5.4.3/CustomLogAggregator.php b/files/plugin-CustomReports-5.4.5/CustomLogAggregator.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/CustomLogAggregator.php
rename to files/plugin-CustomReports-5.4.5/CustomLogAggregator.php
diff --git a/files/plugin-CustomReports-5.4.3/CustomReports.php b/files/plugin-CustomReports-5.4.5/CustomReports.php
similarity index 99%
rename from files/plugin-CustomReports-5.4.3/CustomReports.php
rename to files/plugin-CustomReports-5.4.5/CustomReports.php
index 84aa45d..52c4467 100644
--- a/files/plugin-CustomReports-5.4.3/CustomReports.php
+++ b/files/plugin-CustomReports-5.4.5/CustomReports.php
@@ -5,7 +5,7 @@
* Description: Pull out the information you need in order to be successful. Develop your custom strategy to meet your individualized goals while saving money & time.
* Author: InnoCraft
* Author URI: https://www.innocraft.com
- * Version: 5.4.3
+ * Version: 5.4.5
*/
?> 0) {
+ throw new \Exception(Piwik::translate(
+ 'CustomReports_CustomReportDuplicationSiteTypeError',
+ ['\'' . implode('\', \'', $rollupSiteNames) . '\'']
+ ));
+ }
+ }
+
public static function isAllWebsitesRequest($idSite)
{
return $idSite === 0 || $idSite === '0' || $idSite === 'all' || $idSite === false;
diff --git a/files/plugin-CustomReports-5.4.3/LICENSE b/files/plugin-CustomReports-5.4.5/LICENSE
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/LICENSE
rename to files/plugin-CustomReports-5.4.5/LICENSE
diff --git a/files/plugin-CustomReports-5.4.3/Menu.php b/files/plugin-CustomReports-5.4.5/Menu.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Menu.php
rename to files/plugin-CustomReports-5.4.5/Menu.php
diff --git a/files/plugin-CustomReports-5.4.3/Model/CustomReportsModel.php b/files/plugin-CustomReports-5.4.5/Model/CustomReportsModel.php
similarity index 93%
rename from files/plugin-CustomReports-5.4.3/Model/CustomReportsModel.php
rename to files/plugin-CustomReports-5.4.5/Model/CustomReportsModel.php
index 2b12c39..a2dd827 100644
--- a/files/plugin-CustomReports-5.4.3/Model/CustomReportsModel.php
+++ b/files/plugin-CustomReports-5.4.5/Model/CustomReportsModel.php
@@ -19,12 +19,16 @@
use Piwik\API\Request;
use Piwik\ArchiveProcessor;
use Piwik\Category\CategoryList;
+use Piwik\Columns\MetricsList;
use Piwik\Container\StaticContainer;
use Piwik\DataAccess\ArchiveWriter;
use Piwik\DataAccess\LogAggregator;
use Piwik\Date;
use Piwik\Period;
use Piwik\Piwik;
+use Piwik\Plugin\ArchivedMetric;
+use Piwik\Plugin\Metric;
+use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugins\CustomReports\Configuration;
use Piwik\Plugins\CustomReports\Dao\CustomReportsDao;
use Piwik\Plugins\CustomReports\Input\Category;
@@ -176,9 +180,13 @@ public function getCustomReportById($idCustomReport, $idSite)
/**
* @return array
*/
- public function getAllCustomReportsForSite($idSite, $skipCategoryMetadata = false)
+ public function getAllCustomReportsForSite($idSite, $skipCategoryMetadata = false, $skipEnrich = false)
{
$reports = $this->dao->getCustomReports($idSite);
+ if ($skipEnrich) {
+ return $reports;
+ }
+
return $this->enrichReports($reports, $skipCategoryMetadata);
}
@@ -488,4 +496,42 @@ private function makeArchiveProcessor($idSite, $date = 'today', $period = 'day',
return $processor;
}
+
+ /**
+ * @return Metric[]
+ */
+ public function getArchivableMetricsInReport(array $report): array
+ {
+ $metrics = array();
+
+ if (!empty($report['metrics'])) {
+ foreach ($report['metrics'] as $metric) {
+ $metrics = $this->getMetrics($metrics, $metric);
+ }
+ }
+
+ return $metrics;
+ }
+
+ /**
+ * @return Metric[]
+ */
+ private function getMetrics(array $metricInstances, string $metricName): array
+ {
+ $metricsList = MetricsList::get();
+ $metricInstance = $metricsList->getMetric($metricName);
+
+ if ($metricInstance instanceof ArchivedMetric) {
+ if (!in_array($metricInstance, $metricInstances, $strict = true)) {
+ $metricInstances[] = $metricInstance;
+ }
+ } elseif ($metricInstance instanceof ProcessedMetric) {
+ $depMetrics = $metricInstance->getDependentMetrics();
+ foreach ($depMetrics as $depMetric) {
+ $metricInstances = $this->getMetrics($metricInstances, $depMetric);
+ }
+ }
+
+ return $metricInstances;
+ }
}
diff --git a/files/plugin-CustomReports-5.4.3/README.md b/files/plugin-CustomReports-5.4.5/README.md
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/README.md
rename to files/plugin-CustomReports-5.4.5/README.md
diff --git a/files/plugin-CustomReports-5.4.3/RecordBuilders/CustomReport.php b/files/plugin-CustomReports-5.4.5/RecordBuilders/CustomReport.php
similarity index 95%
rename from files/plugin-CustomReports-5.4.3/RecordBuilders/CustomReport.php
rename to files/plugin-CustomReports-5.4.5/RecordBuilders/CustomReport.php
index 1ce71e2..2f8032f 100644
--- a/files/plugin-CustomReports-5.4.3/RecordBuilders/CustomReport.php
+++ b/files/plugin-CustomReports-5.4.5/RecordBuilders/CustomReport.php
@@ -28,7 +28,6 @@
use Piwik\Log;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
-use Piwik\Plugin\ProcessedMetric;
use Piwik\Plugins\CustomDimensions\CustomDimension;
use Piwik\Plugins\CustomReports\Configuration;
use Piwik\Plugins\CustomReports\Archiver;
@@ -384,38 +383,13 @@ private function findNotFoundCustomDimensionManually(string $dimensionId, int $i
}
/**
+ * @deprecated Moved to the model
* @return ArchivedMetric[]
*/
public function getArchivableMetricsInReport(): array
{
- $metrics = array();
-
- $report = $this->report;
- if (!empty($report['metrics'])) {
- foreach ($report['metrics'] as $metric) {
- $metrics = $this->getMetrics($metrics, $metric);
- }
- }
-
- return $metrics;
- }
-
- private function getMetrics(array $metricInstances, string $metricName): array
- {
- $metricInstance = $this->metricsList->getMetric($metricName);
-
- if ($metricInstance && $metricInstance instanceof ArchivedMetric) {
- if (!in_array($metricInstance, $metricInstances, $strict = true)) {
- $metricInstances[] = $metricInstance;
- }
- } elseif ($metricInstance && $metricInstance instanceof ProcessedMetric) {
- $depMetrics = $metricInstance->getDependentMetrics();
- foreach ($depMetrics as $depMetric) {
- $metricInstances = $this->getMetrics($metricInstances, $depMetric);
- }
- }
-
- return $metricInstances;
+ $customReportsModel = StaticContainer::get('Piwik\Plugins\CustomReports\Model\CustomReportsModel');
+ return $customReportsModel->getArchivableMetricsInReport($this->report);
}
private function usesSqlFunction(string $function, string $select): bool
diff --git a/files/plugin-CustomReports-5.4.3/ReportType/Evolution.php b/files/plugin-CustomReports-5.4.5/ReportType/Evolution.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/ReportType/Evolution.php
rename to files/plugin-CustomReports-5.4.5/ReportType/Evolution.php
diff --git a/files/plugin-CustomReports-5.4.3/ReportType/ReportType.php b/files/plugin-CustomReports-5.4.5/ReportType/ReportType.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/ReportType/ReportType.php
rename to files/plugin-CustomReports-5.4.5/ReportType/ReportType.php
diff --git a/files/plugin-CustomReports-5.4.3/ReportType/Table.php b/files/plugin-CustomReports-5.4.5/ReportType/Table.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/ReportType/Table.php
rename to files/plugin-CustomReports-5.4.5/ReportType/Table.php
diff --git a/files/plugin-CustomReports-5.4.3/Updates/5.0.18.php b/files/plugin-CustomReports-5.4.5/Updates/5.0.18.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Updates/5.0.18.php
rename to files/plugin-CustomReports-5.4.5/Updates/5.0.18.php
diff --git a/files/plugin-CustomReports-5.4.3/Updates/5.1.0.php b/files/plugin-CustomReports-5.4.5/Updates/5.1.0.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Updates/5.1.0.php
rename to files/plugin-CustomReports-5.4.5/Updates/5.1.0.php
diff --git a/files/plugin-CustomReports-5.4.3/Updates/5.2.0.php b/files/plugin-CustomReports-5.4.5/Updates/5.2.0.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Updates/5.2.0.php
rename to files/plugin-CustomReports-5.4.5/Updates/5.2.0.php
diff --git a/files/plugin-CustomReports-5.4.3/Updates/5.4.0.php b/files/plugin-CustomReports-5.4.5/Updates/5.4.0.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Updates/5.4.0.php
rename to files/plugin-CustomReports-5.4.5/Updates/5.4.0.php
diff --git a/files/plugin-CustomReports-5.4.3/Updates/5.4.2.php b/files/plugin-CustomReports-5.4.5/Updates/5.4.2.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Updates/5.4.2.php
rename to files/plugin-CustomReports-5.4.5/Updates/5.4.2.php
diff --git a/files/plugin-CustomReports-5.4.3/Widgets/BaseWidget.php b/files/plugin-CustomReports-5.4.5/Widgets/BaseWidget.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Widgets/BaseWidget.php
rename to files/plugin-CustomReports-5.4.5/Widgets/BaseWidget.php
diff --git a/files/plugin-CustomReports-5.4.3/Widgets/GetManageReports.php b/files/plugin-CustomReports-5.4.5/Widgets/GetManageReports.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/Widgets/GetManageReports.php
rename to files/plugin-CustomReports-5.4.5/Widgets/GetManageReports.php
diff --git a/files/plugin-CustomReports-5.4.3/config/config.php b/files/plugin-CustomReports-5.4.5/config/config.php
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/config/config.php
rename to files/plugin-CustomReports-5.4.5/config/config.php
diff --git a/files/plugin-CustomReports-5.4.3/docs/index.md b/files/plugin-CustomReports-5.4.5/docs/index.md
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/docs/index.md
rename to files/plugin-CustomReports-5.4.5/docs/index.md
diff --git a/files/plugin-CustomReports-5.4.3/lang/da.json b/files/plugin-CustomReports-5.4.5/lang/am.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/da.json
rename to files/plugin-CustomReports-5.4.5/lang/am.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/fi.json b/files/plugin-CustomReports-5.4.5/lang/ar.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/fi.json
rename to files/plugin-CustomReports-5.4.5/lang/ar.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/hi.json b/files/plugin-CustomReports-5.4.5/lang/az.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/hi.json
rename to files/plugin-CustomReports-5.4.5/lang/az.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/ja.json b/files/plugin-CustomReports-5.4.5/lang/be.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/ja.json
rename to files/plugin-CustomReports-5.4.5/lang/be.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/bg.json b/files/plugin-CustomReports-5.4.5/lang/bg.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/bg.json
rename to files/plugin-CustomReports-5.4.5/lang/bg.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/nb.json b/files/plugin-CustomReports-5.4.5/lang/bn.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/nb.json
rename to files/plugin-CustomReports-5.4.5/lang/bn.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/ru.json b/files/plugin-CustomReports-5.4.5/lang/bs.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/ru.json
rename to files/plugin-CustomReports-5.4.5/lang/bs.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/ca.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/zh-cn.json
rename to files/plugin-CustomReports-5.4.5/lang/ca.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/cs.json b/files/plugin-CustomReports-5.4.5/lang/cs.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/cs.json
rename to files/plugin-CustomReports-5.4.5/lang/cs.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json b/files/plugin-CustomReports-5.4.5/lang/cy.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json
rename to files/plugin-CustomReports-5.4.5/lang/cy.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/da.json b/files/plugin-CustomReports-5.4.5/lang/da.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/da.json
rename to files/plugin-CustomReports-5.4.5/lang/da.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/de.json b/files/plugin-CustomReports-5.4.5/lang/de.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/de.json
rename to files/plugin-CustomReports-5.4.5/lang/de.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json b/files/plugin-CustomReports-5.4.5/lang/dv.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json
rename to files/plugin-CustomReports-5.4.5/lang/dv.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json b/files/plugin-CustomReports-5.4.5/lang/el.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json
rename to files/plugin-CustomReports-5.4.5/lang/el.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/en.json b/files/plugin-CustomReports-5.4.5/lang/en.json
similarity index 94%
rename from files/plugin-CustomReports-5.4.3/lang/en.json
rename to files/plugin-CustomReports-5.4.5/lang/en.json
index 431c2a4..05e6e1f 100644
--- a/files/plugin-CustomReports-5.4.3/lang/en.json
+++ b/files/plugin-CustomReports-5.4.5/lang/en.json
@@ -118,6 +118,11 @@
"NoDataMessagePausedStateAdminUser": "This report is paused, no new data will be available until the report is resumed. To resume the report, go to %1$sManage Reports%2$s and click Play for the selected report.",
"NoSegmentationJoinExceptionMessage": "The current order or combination of dimensions is not compatible. %1$sLearn more%2$s.",
"PreviewReportInvalidTimeFrameValues": "Invalid timeframe values set for preview report, allowed values are seconds, minutes, hours and days.",
- "PreviewReportExceedsMaximumTimeFrameValueAllowed": "The maximum time frame for preview report cannot be > 24 hours."
+ "PreviewReportExceedsMaximumTimeFrameValueAllowed": "The maximum time frame for preview report cannot be > 24 hours.",
+ "QuotaReachedForX": "You’ve reached your quota limit. Unable to copy the %1$s due to exceeded usage. Consider deleting unused or less critical %2$s to manage your quota.",
+ "SourceCustomReportLookupError": "There was an unexpected error looking up the source custom report. Please try again. Contact your administrator or support if the problem persists.",
+ "CustomReportDuplicationError": "Process incomplete. The custom report could not be copied to all selected sites.",
+ "CustomReportCopied": "The custom report has been successfully copied.",
+ "CustomReportDuplicationSiteTypeError": "Copy feature is not supported for sites: %1$s."
}
}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json b/files/plugin-CustomReports-5.4.5/lang/eo.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json
rename to files/plugin-CustomReports-5.4.5/lang/eo.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json b/files/plugin-CustomReports-5.4.5/lang/es-ar.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json
rename to files/plugin-CustomReports-5.4.5/lang/es-ar.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/es.json b/files/plugin-CustomReports-5.4.5/lang/es.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/es.json
rename to files/plugin-CustomReports-5.4.5/lang/es.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/et.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json
rename to files/plugin-CustomReports-5.4.5/lang/et.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/fi.json b/files/plugin-CustomReports-5.4.5/lang/eu.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/fi.json
rename to files/plugin-CustomReports-5.4.5/lang/eu.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/hi.json b/files/plugin-CustomReports-5.4.5/lang/fa.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/hi.json
rename to files/plugin-CustomReports-5.4.5/lang/fa.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/fi.json b/files/plugin-CustomReports-5.4.5/lang/fi.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/fi.json
rename to files/plugin-CustomReports-5.4.5/lang/fi.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/fr.json b/files/plugin-CustomReports-5.4.5/lang/fr.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/fr.json
rename to files/plugin-CustomReports-5.4.5/lang/fr.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/ja.json b/files/plugin-CustomReports-5.4.5/lang/ga.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/ja.json
rename to files/plugin-CustomReports-5.4.5/lang/ga.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/ru.json b/files/plugin-CustomReports-5.4.5/lang/gl.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/ru.json
rename to files/plugin-CustomReports-5.4.5/lang/gl.json
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/gu.json
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.6/lang/zh-cn.json
rename to files/plugin-CustomReports-5.4.5/lang/gu.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/cs.json b/files/plugin-CustomReports-5.4.5/lang/he.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/cs.json
rename to files/plugin-CustomReports-5.4.5/lang/he.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/hi.json b/files/plugin-CustomReports-5.4.5/lang/hi.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/hi.json
rename to files/plugin-CustomReports-5.4.5/lang/hi.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/da.json b/files/plugin-CustomReports-5.4.5/lang/hr.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/da.json
rename to files/plugin-CustomReports-5.4.5/lang/hr.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ja.json b/files/plugin-CustomReports-5.4.5/lang/hu.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ja.json
rename to files/plugin-CustomReports-5.4.5/lang/hu.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/nb.json b/files/plugin-CustomReports-5.4.5/lang/hy.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/nb.json
rename to files/plugin-CustomReports-5.4.5/lang/hy.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ro.json b/files/plugin-CustomReports-5.4.5/lang/id.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ro.json
rename to files/plugin-CustomReports-5.4.5/lang/id.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/uk.json b/files/plugin-CustomReports-5.4.5/lang/is.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/uk.json
rename to files/plugin-CustomReports-5.4.5/lang/is.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/it.json b/files/plugin-CustomReports-5.4.5/lang/it.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/it.json
rename to files/plugin-CustomReports-5.4.5/lang/it.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/ja.json b/files/plugin-CustomReports-5.4.5/lang/ja.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/ja.json
rename to files/plugin-CustomReports-5.4.5/lang/ja.json
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/ka.json
similarity index 100%
rename from files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/zh-cn.json
rename to files/plugin-CustomReports-5.4.5/lang/ka.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/da.json b/files/plugin-CustomReports-5.4.5/lang/ko.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/da.json
rename to files/plugin-CustomReports-5.4.5/lang/ko.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/fi.json b/files/plugin-CustomReports-5.4.5/lang/ku.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/fi.json
rename to files/plugin-CustomReports-5.4.5/lang/ku.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/hi.json b/files/plugin-CustomReports-5.4.5/lang/lb.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/hi.json
rename to files/plugin-CustomReports-5.4.5/lang/lb.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/nb.json b/files/plugin-CustomReports-5.4.5/lang/lt.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/nb.json
rename to files/plugin-CustomReports-5.4.5/lang/lt.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/ru.json b/files/plugin-CustomReports-5.4.5/lang/lv.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/ru.json
rename to files/plugin-CustomReports-5.4.5/lang/lv.json
diff --git a/files/plugin-UsersFlow-5.0.5/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/ms.json
similarity index 100%
rename from files/plugin-UsersFlow-5.0.5/lang/zh-cn.json
rename to files/plugin-CustomReports-5.4.5/lang/ms.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/nb.json b/files/plugin-CustomReports-5.4.5/lang/nb.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/nb.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/nl.json b/files/plugin-CustomReports-5.4.5/lang/nl.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/nl.json
rename to files/plugin-CustomReports-5.4.5/lang/nl.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/nn.json b/files/plugin-CustomReports-5.4.5/lang/nn.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/nn.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/pl.json b/files/plugin-CustomReports-5.4.5/lang/pl.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/pl.json
rename to files/plugin-CustomReports-5.4.5/lang/pl.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/pt-br.json b/files/plugin-CustomReports-5.4.5/lang/pt-br.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/pt-br.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/pt.json b/files/plugin-CustomReports-5.4.5/lang/pt.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/pt.json
rename to files/plugin-CustomReports-5.4.5/lang/pt.json
diff --git a/files/plugin-CustomReports-5.4.3/lang/ro.json b/files/plugin-CustomReports-5.4.5/lang/ro.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/ro.json
rename to files/plugin-CustomReports-5.4.5/lang/ro.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/ru.json b/files/plugin-CustomReports-5.4.5/lang/ru.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/ru.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/si.json b/files/plugin-CustomReports-5.4.5/lang/si.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/si.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/sk.json b/files/plugin-CustomReports-5.4.5/lang/sk.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/sk.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/sl.json b/files/plugin-CustomReports-5.4.5/lang/sl.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/sl.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/sq.json b/files/plugin-CustomReports-5.4.5/lang/sq.json
similarity index 95%
rename from files/plugin-CustomReports-5.4.3/lang/sq.json
rename to files/plugin-CustomReports-5.4.5/lang/sq.json
index aa65248..d732b34 100644
--- a/files/plugin-CustomReports-5.4.3/lang/sq.json
+++ b/files/plugin-CustomReports-5.4.5/lang/sq.json
@@ -17,6 +17,9 @@
"ContentImpressions": "Përshtypje Nga Lënda",
"CreateNewReport": "Krijoni raport të ri",
"CustomReport": "Raport Vetjak",
+ "CustomReportCopied": "Raporti vetjak u kopjua me sukses.",
+ "CustomReportDuplicationError": "Proces i paplotësuar. Raporti vetjak s’u kopjua dot te krejt sajtet e përzgjedhur.",
+ "CustomReportDuplicationSiteTypeError": "Veçoria e kopjimit nuk mbulohet për sajtet: %1$s.",
"CustomReportIntroduction": "Raportet Vetjakë ju lejojnë të krijoni raporte të rinj për të ndjekur prirje të reja që s’janë të mundshme me raportet standarde. Përmasat (të tilla si, “Lloj Pajisjeje” dhe “Shfletues”), matjet (të tilla si, “Numër Vizitorësh” dhe “Nivel Kthimesh”) dhe se si duhet të shfaqen për të pasur të dhënat unike të përdorshme dhe të dobishme që ju nevojiten, i zgjidhni ju.",
"CustomReports": "Raporte Vetjakë",
"DeleteExceptionMessage": "S’fshihet dot raporti, sajti s’përputhet",
@@ -77,6 +80,7 @@
"PreviewReportInvalidTimeFrameValues": "Për paraparje raporti janë vënë vlera të pavlefshme intervalesh kohorë, vlerat e lejuara janë sekonda, minuta, orë dhe ditë.",
"PreviewSupportsDimension": "Hëpërhë paraparja mbulon deri në %s përmasa.",
"ProductsWithX": "Produkte me %s",
+ "QuotaReachedForX": "Keni mbërritur në kufirin e kuotës tuaj. S’arrihet të kopjohet %1$s, për shkak tejkalimi përdorimi. Që të administroni kuotën tuaj, shihni mundësinë e fshirjes së %2$s të papërdorur, ose më pak kritike.",
"RemoveDimension": "Hiqe përmasën",
"RemoveMetric": "Hiqe matjen",
"RemovedMetrics": "Këto vlera u hoqën, ngaqë s’mund të përllogariten saktë për këtë periudhë: %s.",
@@ -108,7 +112,7 @@
"ResumeReportConfirm": "Kur rinisni këtë raport, arkivimi do të rinisë nga sot. Jeni i sigurt se doni të riniset ky raport vetjak?",
"ResumeReportInfo": "Kur rinisni këtë raport, arkivimi do të rifillojë nga sot.",
"ResumedReport": "Raporti u rinis me sukses.",
- "SelectMeasurablesMatchingSearch": "ose shtoni krejt gjërat e matshme që përmbajnë termin vijues të kërkimit",
+ "SourceCustomReportLookupError": "Pati një gabim të papritur me kërkimin e raportit vetjak burim. Ju lutemi, riprovoni. Nëse problemi vazhdon, lidhuni me përgjegjësin tuaj, ose me asistencën.",
"Type": "Lloj",
"Unlock": "Shkyçe",
"UpdatingData": "Po përditësohen të dhëna…",
diff --git a/files/plugin-CustomReports-5.4.5/lang/sr.json b/files/plugin-CustomReports-5.4.5/lang/sr.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/sr.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/sv.json b/files/plugin-CustomReports-5.4.5/lang/sv.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/sv.json
rename to files/plugin-CustomReports-5.4.5/lang/sv.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/ta.json b/files/plugin-CustomReports-5.4.5/lang/ta.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/ta.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/te.json b/files/plugin-CustomReports-5.4.5/lang/te.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/te.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/th.json b/files/plugin-CustomReports-5.4.5/lang/th.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/th.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/tl.json b/files/plugin-CustomReports-5.4.5/lang/tl.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/tl.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/tr.json b/files/plugin-CustomReports-5.4.5/lang/tr.json
similarity index 95%
rename from files/plugin-CustomReports-5.4.3/lang/tr.json
rename to files/plugin-CustomReports-5.4.5/lang/tr.json
index bd40f4b..2a7b952 100644
--- a/files/plugin-CustomReports-5.4.3/lang/tr.json
+++ b/files/plugin-CustomReports-5.4.5/lang/tr.json
@@ -17,6 +17,9 @@
"ContentImpressions": "İçerik gösterimleri",
"CreateNewReport": "Yeni rapor ekle",
"CustomReport": "Özel rapor",
+ "CustomReportCopied": "Özel rapor kopyalandı.",
+ "CustomReportDuplicationError": "İşlem tamamlanmadı. Özel rapor seçilmiş tüm sitelere kopyalanamadı.",
+ "CustomReportDuplicationSiteTypeError": "Kopyalama özelliği sitelerde desteklenmiyor: %1$s.",
"CustomReportIntroduction": "Özel raporlar, standart raporlarda bulunmayan yeni bakış açıları oluşturmanızı sağlar. Boyutları (\"Aygıt türü\" ve \"Tarayıcı\" gibi) ve ölçümleri (\"Ziyaretçi sayısı\" ve \"Geri dönüş oranı\" gibi) ve nasıl görüneceğini seçerek eşsiz ve işinize yarayacak veriler elde edebilirsiniz.",
"CustomReports": "Özel raporlar",
"DeleteExceptionMessage": "Rapor silinemedi. Site eşleşmiyor",
@@ -77,6 +80,7 @@
"PreviewReportInvalidTimeFrameValues": "Rapor ön izlemesi için ayarlanmış zaman aralığı değerleri geçersiz. Saniye, dakika, saat ve gün değerlerine izin verilir.",
"PreviewSupportsDimension": "Ön izlemede en çok %s boyut görüntülenebilir.",
"ProductsWithX": "%s ile ürünler",
+ "QuotaReachedForX": "Kota sınırınıza ulaştınız. Kullanım aşımı nedeniyle %1$s kopyalanamıyor. Kotanızı yönetmek için kullanılmayan veya daha az önemli %2$s ögesini silmeyi değerlendirin.",
"RemoveDimension": "Boyutu kaldır",
"RemoveMetric": "Ölçümü kaldır",
"RemovedMetrics": "%s aralığı için doğru hesaplanamadığından bu ölçümler çıkarıldı.",
@@ -109,6 +113,7 @@
"ResumeReportInfo": "Bu raporu sürdürdüğünüzde, arşivleme bugünden itibaren yeniden başlar.",
"ResumedReport": "Rapor sürdürüldü.",
"SelectMeasurablesMatchingSearch": "ya da şu ifadeyi içeren tüm ölçülebilirleri ekleyin",
+ "SourceCustomReportLookupError": "Kaynak özel raporuna bakılırken beklenmeyen bir sorun çıktı. Lütfen yeniden deneyin. Sorun sürerse yöneticinizle veya destek ekibinizle görüşün.",
"Type": "Tür",
"Unlock": "Kilidi aç",
"UpdatingData": "Veriler güncelleniyor…",
diff --git a/files/plugin-CustomReports-5.4.5/lang/tzm.json b/files/plugin-CustomReports-5.4.5/lang/tzm.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/tzm.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/uk.json b/files/plugin-CustomReports-5.4.5/lang/uk.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/uk.json
rename to files/plugin-CustomReports-5.4.5/lang/uk.json
diff --git a/files/plugin-CustomReports-5.4.5/lang/ur.json b/files/plugin-CustomReports-5.4.5/lang/ur.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/ur.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/vi.json b/files/plugin-CustomReports-5.4.5/lang/vi.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/vi.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.5/lang/zh-cn.json b/files/plugin-CustomReports-5.4.5/lang/zh-cn.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/lang/zh-cn.json
@@ -0,0 +1 @@
+{}
diff --git a/files/plugin-CustomReports-5.4.3/lang/zh-tw.json b/files/plugin-CustomReports-5.4.5/lang/zh-tw.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/lang/zh-tw.json
rename to files/plugin-CustomReports-5.4.5/lang/zh-tw.json
diff --git a/files/plugin-CustomReports-5.4.3/phpcs.xml b/files/plugin-CustomReports-5.4.5/phpcs.xml
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/phpcs.xml
rename to files/plugin-CustomReports-5.4.5/phpcs.xml
diff --git a/files/plugin-CustomReports-5.4.3/phpstan.neon b/files/plugin-CustomReports-5.4.5/phpstan.neon
similarity index 54%
rename from files/plugin-CustomReports-5.4.3/phpstan.neon
rename to files/plugin-CustomReports-5.4.5/phpstan.neon
index c3fa656..8ec3d36 100644
--- a/files/plugin-CustomReports-5.4.3/phpstan.neon
+++ b/files/plugin-CustomReports-5.4.5/phpstan.neon
@@ -1,6 +1,7 @@
parameters:
level: 0
phpVersion: 70200
+ tmpDir: /tmp/phpstan/CustomReports/main
paths:
- .
excludePaths:
@@ -12,4 +13,7 @@ parameters:
- Piwik\View
- Piwik\ViewDataTable\Config
scanDirectories:
- - ../../plugins/
+ # ../../ does not actually seem to give us anything
+ # that ../plugins/ does not, but including it for
+ # completeness. It does not seem to slow down performance.
+ - ../../
diff --git a/files/plugin-CustomReports-5.4.5/phpstan/phpstan.created.neon b/files/plugin-CustomReports-5.4.5/phpstan/phpstan.created.neon
new file mode 100644
index 0000000..084eb8d
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/phpstan/phpstan.created.neon
@@ -0,0 +1,5 @@
+includes:
+ - ../phpstan.neon
+parameters:
+ level: 9
+ tmpDir: /tmp/phpstan/CustomReports/created
diff --git a/files/plugin-CustomReports-5.4.5/phpstan/phpstan.modified.neon b/files/plugin-CustomReports-5.4.5/phpstan/phpstan.modified.neon
new file mode 100644
index 0000000..ac4783d
--- /dev/null
+++ b/files/plugin-CustomReports-5.4.5/phpstan/phpstan.modified.neon
@@ -0,0 +1,5 @@
+includes:
+ - ../phpstan.neon
+parameters:
+ level: 1
+ tmpDir: /tmp/phpstan/CustomReports/modified
diff --git a/files/plugin-CustomReports-5.4.3/plugin.json b/files/plugin-CustomReports-5.4.5/plugin.json
similarity index 97%
rename from files/plugin-CustomReports-5.4.3/plugin.json
rename to files/plugin-CustomReports-5.4.5/plugin.json
index 910ca83..1383377 100644
--- a/files/plugin-CustomReports-5.4.3/plugin.json
+++ b/files/plugin-CustomReports-5.4.5/plugin.json
@@ -1,7 +1,7 @@
{
"name": "CustomReports",
"description": "Pull out the information you need in order to be successful. Develop your custom strategy to meet your individualized goals while saving money & time.",
- "version": "5.4.3",
+ "version": "5.4.5",
"theme": false,
"require": {
"matomo": ">=5.0.0-rc5,<6.0.0-b1"
diff --git a/files/plugin-CustomReports-5.4.3/pull_request_template.md b/files/plugin-CustomReports-5.4.5/pull_request_template.md
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/pull_request_template.md
rename to files/plugin-CustomReports-5.4.5/pull_request_template.md
diff --git a/files/plugin-CustomReports-5.4.3/templates/manage.twig b/files/plugin-CustomReports-5.4.5/templates/manage.twig
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/templates/manage.twig
rename to files/plugin-CustomReports-5.4.5/templates/manage.twig
diff --git a/files/plugin-CustomReports-5.4.3/vue/dist/CustomReports.umd.js b/files/plugin-CustomReports-5.4.5/vue/dist/CustomReports.umd.js
similarity index 56%
rename from files/plugin-CustomReports-5.4.3/vue/dist/CustomReports.umd.js
rename to files/plugin-CustomReports-5.4.5/vue/dist/CustomReports.umd.js
index 04a71bb..640ceae 100644
--- a/files/plugin-CustomReports-5.4.3/vue/dist/CustomReports.umd.js
+++ b/files/plugin-CustomReports-5.4.5/vue/dist/CustomReports.umd.js
@@ -155,338 +155,309 @@ if (typeof window !== 'undefined') {
// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"}
var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf");
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CustomReports/vue/src/Reports/Edit.vue?vue&type=template&id=769922f2
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/CustomReports/vue/src/Reports/Edit.vue?vue&type=template&id=769922f2
-var _hoisted_1 = {
+const _hoisted_1 = {
class: "loadingPiwik"
};
-
-var _hoisted_2 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
+const _hoisted_2 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
src: "plugins/Morpheus/images/loading-blue.gif"
}, null, -1);
-
-var _hoisted_3 = {
+const _hoisted_3 = {
class: "loadingPiwik"
};
-
-var _hoisted_4 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
+const _hoisted_4 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
src: "plugins/Morpheus/images/loading-blue.gif"
}, null, -1);
-
-var _hoisted_5 = {
+const _hoisted_5 = {
class: "alert alert-warning"
};
-var _hoisted_6 = {
+const _hoisted_6 = {
key: 0
};
-var _hoisted_7 = {
+const _hoisted_7 = {
key: 1
};
-var _hoisted_8 = {
+const _hoisted_8 = {
key: 0,
class: "alert alert-warning"
};
-var _hoisted_9 = ["innerHTML"];
-var _hoisted_10 = {
+const _hoisted_9 = ["innerHTML"];
+const _hoisted_10 = {
key: 1
};
-var _hoisted_11 = {
+const _hoisted_11 = {
name: "name"
};
-var _hoisted_12 = {
+const _hoisted_12 = {
name: "description"
};
-var _hoisted_13 = {
+const _hoisted_13 = {
class: "form-group row"
};
-var _hoisted_14 = {
+const _hoisted_14 = {
class: "col s12"
};
-var _hoisted_15 = {
+const _hoisted_15 = {
class: "col s12 m6"
};
-var _hoisted_16 = {
+const _hoisted_16 = {
for: "all_websites",
class: "siteSelectorLabel"
};
-var _hoisted_17 = {
+const _hoisted_17 = {
class: "sites_autocomplete"
};
-var _hoisted_18 = {
+const _hoisted_18 = {
class: "col s12 m6"
};
-var _hoisted_19 = {
+const _hoisted_19 = {
class: "form-help"
};
-var _hoisted_20 = {
+const _hoisted_20 = {
key: 0,
class: "inline-help"
};
-var _hoisted_21 = {
+const _hoisted_21 = {
key: 1,
class: "inline-help"
};
-var _hoisted_22 = {
+const _hoisted_22 = {
key: 0,
class: "col s12 m6"
};
-var _hoisted_23 = {
+const _hoisted_23 = {
key: 0
};
-var _hoisted_24 = {
+const _hoisted_24 = {
for: "websitecontains"
};
-
-var _hoisted_25 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_26 = ["placeholder"];
-var _hoisted_27 = ["disabled", "value"];
-var _hoisted_28 = {
+const _hoisted_25 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_26 = ["placeholder"];
+const _hoisted_27 = ["disabled", "value"];
+const _hoisted_28 = {
key: 1
};
-
-var _hoisted_29 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_30 = {
+const _hoisted_29 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_30 = {
class: "entityTable"
};
-var _hoisted_31 = {
+const _hoisted_31 = {
class: "siteId"
};
-var _hoisted_32 = {
+const _hoisted_32 = {
class: "siteName"
};
-var _hoisted_33 = {
+const _hoisted_33 = {
key: 0,
class: "siteAction"
};
-var _hoisted_34 = {
+const _hoisted_34 = {
colspan: "3"
};
-var _hoisted_35 = {
+const _hoisted_35 = {
key: 0,
class: "siteAction"
};
-var _hoisted_36 = ["onClick"];
-var _hoisted_37 = {
+const _hoisted_36 = ["onClick"];
+const _hoisted_37 = {
class: "form-group row"
};
-var _hoisted_38 = {
+const _hoisted_38 = {
class: "col s12"
};
-var _hoisted_39 = {
+const _hoisted_39 = {
class: "unlockAlert alert alert-info"
};
-var _hoisted_40 = {
+const _hoisted_40 = {
key: 0
};
-var _hoisted_41 = {
+const _hoisted_41 = {
key: 1
};
-
-var _hoisted_42 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_43 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_44 = ["value"];
-var _hoisted_45 = {
+const _hoisted_42 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_43 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_44 = ["value"];
+const _hoisted_45 = {
class: "alertUnlocked alert alert-warning"
};
-var _hoisted_46 = {
+const _hoisted_46 = {
key: 0
};
-var _hoisted_47 = {
+const _hoisted_47 = {
key: 1
};
-var _hoisted_48 = {
+const _hoisted_48 = {
name: "reportType"
};
-var _hoisted_49 = {
+const _hoisted_49 = {
class: "form-group row"
};
-var _hoisted_50 = {
+const _hoisted_50 = {
class: "col s12 m6 dimensionsGroup"
};
-
-var _hoisted_51 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_52 = {
+const _hoisted_51 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_52 = {
class: "groupValueSelect",
name: "dimensions"
};
-var _hoisted_53 = ["title", "onClick"];
-var _hoisted_54 = {
+const _hoisted_53 = ["title", "onClick"];
+const _hoisted_54 = {
class: "groupValueSelect addDimension",
name: "dimensions"
};
-var _hoisted_55 = {
+const _hoisted_55 = {
class: "col s12 m6"
};
-var _hoisted_56 = {
+const _hoisted_56 = {
class: "form-help"
};
-var _hoisted_57 = ["innerHTML"];
-var _hoisted_58 = {
+const _hoisted_57 = ["innerHTML"];
+const _hoisted_58 = {
class: "form-group row"
};
-var _hoisted_59 = {
+const _hoisted_59 = {
class: "col s12 m6 metricsGroup"
};
-
-var _hoisted_60 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_61 = {
+const _hoisted_60 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_61 = {
class: "groupValueSelect",
name: "metrics"
};
-var _hoisted_62 = ["title", "onClick"];
-var _hoisted_63 = {
+const _hoisted_62 = ["title", "onClick"];
+const _hoisted_63 = {
class: "groupValueSelect addMetric",
name: "metrics"
};
-var _hoisted_64 = {
+const _hoisted_64 = {
class: "col s12 m6"
};
-var _hoisted_65 = {
+const _hoisted_65 = {
class: "form-help"
};
-var _hoisted_66 = {
+const _hoisted_66 = {
class: "inline-help"
};
-var _hoisted_67 = ["innerHTML"];
-var _hoisted_68 = {
+const _hoisted_67 = ["innerHTML"];
+const _hoisted_68 = {
class: "form-group row segmentFilterGroup"
};
-var _hoisted_69 = {
+const _hoisted_69 = {
class: "col s12"
};
-var _hoisted_70 = {
+const _hoisted_70 = {
style: {
"margin": "8px 0",
"display": "inline-block"
}
};
-var _hoisted_71 = {
+const _hoisted_71 = {
class: "form-group row"
};
-var _hoisted_72 = {
+const _hoisted_72 = {
class: "col s12"
};
-
-var _hoisted_73 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_74 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-
-var _hoisted_75 = {
+const _hoisted_73 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_74 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
+const _hoisted_75 = {
name: "reportCategories"
};
-var _hoisted_76 = {
+const _hoisted_76 = {
name: "reportSubcategories"
};
-var _hoisted_77 = {
+const _hoisted_77 = {
class: "alert alert-warning"
};
-var _hoisted_78 = {
+const _hoisted_78 = {
key: 0
};
-var _hoisted_79 = {
+const _hoisted_79 = {
key: 1
};
-var _hoisted_80 = {
+const _hoisted_80 = {
class: "alert alert-warning"
};
-var _hoisted_81 = {
+const _hoisted_81 = {
key: 0
};
-var _hoisted_82 = {
+const _hoisted_82 = {
key: 1
};
-var _hoisted_83 = {
+const _hoisted_83 = {
key: 0,
class: "form-group row"
};
-var _hoisted_84 = ["textContent"];
-var _hoisted_85 = {
+const _hoisted_84 = ["textContent"];
+const _hoisted_85 = {
class: "col s12 m6"
};
-var _hoisted_86 = {
+const _hoisted_86 = {
id: "childReports",
class: "col s12 m6"
};
-var _hoisted_87 = ["data-id"];
-
-var _hoisted_88 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
+const _hoisted_87 = ["data-id"];
+const _hoisted_88 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
class: "ui-icon ui-icon-arrowthick-2-n-s"
}, null, -1);
-
-var _hoisted_89 = {
+const _hoisted_89 = {
class: "col s12 m6"
};
-var _hoisted_90 = {
+const _hoisted_90 = {
class: "form-help"
};
-var _hoisted_91 = ["textContent"];
-var _hoisted_92 = {
+const _hoisted_91 = ["textContent"];
+const _hoisted_92 = {
class: "entityCancel"
};
-var _hoisted_93 = {
+const _hoisted_93 = {
class: "ui-confirm",
id: "confirmUnlockReport",
ref: "confirmUnlockReport"
};
-var _hoisted_94 = {
+const _hoisted_94 = {
key: 0
};
-var _hoisted_95 = {
+const _hoisted_95 = {
key: 1
};
-var _hoisted_96 = ["value"];
-var _hoisted_97 = ["value"];
-var _hoisted_98 = {
+const _hoisted_96 = ["value"];
+const _hoisted_97 = ["value"];
+const _hoisted_98 = {
class: "ui-confirm",
id: "infoReportIsLocked",
ref: "infoReportIsLocked"
};
-var _hoisted_99 = {
+const _hoisted_99 = {
key: 0
};
-var _hoisted_100 = {
+const _hoisted_100 = {
key: 1
};
-var _hoisted_101 = ["value"];
-var _hoisted_102 = ["value"];
+const _hoisted_101 = ["value"];
+const _hoisted_102 = ["value"];
function render(_ctx, _cache, $props, $setup, $data, $options) {
- var _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field");
-
- var _component_SiteSelector = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SiteSelector");
-
- var _component_SegmentGenerator = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SegmentGenerator");
-
- var _component_SaveButton = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SaveButton");
-
- var _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock");
-
+ const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field");
+ const _component_SiteSelector = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SiteSelector");
+ const _component_SegmentGenerator = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SegmentGenerator");
+ const _component_SaveButton = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SaveButton");
+ const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock");
return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createBlock"])(_component_ContentBlock, {
class: "editReport",
"content-title": _ctx.contentTitle
}, {
- default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(function () {
+ default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => {
var _ctx$report$metrics, _ctx$report$dimension, _ctx$report$subcatego;
-
return [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_1, [_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_3, [_hoisted_4, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_UpdatingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isUpdating]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_5, [_ctx.multipleSites.length && !_ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportEditNotAllowedMultipleWebsitesAccessIssue')), 1)) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_7, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportEditNotAllowedAllWebsitesUpdated')), 1))], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.canEdit]]), _ctx.report.status === 'paused' ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_8, [_ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", {
key: 0,
innerHTML: _ctx.getPausedStateAdminMessage
}, null, 8, _hoisted_9)) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_10, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_NoDataMessagePausedStateNonAdminUser')), 1))])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("form", {
- onSubmit: _cache[15] || (_cache[15] = function ($event) {
- return _ctx.edit ? _ctx.updateReport() : _ctx.createReport();
- })
+ onSubmit: _cache[15] || (_cache[15] = $event => _ctx.edit ? _ctx.updateReport() : _ctx.createReport())
}, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_11, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "text",
name: "name",
"model-value": _ctx.report.name,
- "onUpdate:modelValue": _cache[0] || (_cache[0] = function ($event) {
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => {
_ctx.report.name = $event;
-
_ctx.setValueHasChanged();
}),
title: _ctx.translate('General_Name'),
@@ -498,12 +469,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
uicontrol: "textarea",
name: "description",
"model-value": _ctx.report.description,
- "onUpdate:modelValue": _cache[1] || (_cache[1] = function ($event) {
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => {
_ctx.report.description = $event;
-
_ctx.setValueHasChanged();
}),
- title: "".concat(_ctx.translate('General_Description'), " (optional)"),
+ title: `${_ctx.translate('General_Description')} (optional)`,
maxlength: 1000,
disabled: !_ctx.canEdit,
rows: 3,
@@ -512,9 +482,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
}, null, 8, ["model-value", "title", "disabled", "placeholder", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_13, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", _hoisted_14, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ApplyTo')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_15, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", _hoisted_16, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Website')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_17, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SiteSelector, {
id: "all_websites",
"model-value": _ctx.report.site,
- "onUpdate:modelValue": _cache[2] || (_cache[2] = function ($event) {
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => {
_ctx.report.site = $event;
-
_ctx.setWebsiteChanged($event);
}),
"show-all-sites-item": _ctx.isSuperUser,
@@ -524,9 +493,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
class: "control_text customReportSearchMeasurablesField",
type: "text",
id: "websitecontains",
- "onUpdate:modelValue": _cache[3] || (_cache[3] = function ($event) {
- return _ctx.containsText = $event;
- }),
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => _ctx.containsText = $event),
placeholder: _ctx.translate('General_Search')
}, null, 8, _hoisted_26), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vModelText"], _ctx.containsText]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", {
style: {
@@ -535,62 +502,50 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
disabled: !_ctx.containsText,
class: "btn customReportSearchFindMeasurables",
type: "button",
- onClick: _cache[4] || (_cache[4] = function ($event) {
- return _ctx.addSitesContaining(_ctx.containsText);
- }),
+ onClick: _cache[4] || (_cache[4] = $event => _ctx.addSitesContaining(_ctx.containsText)),
value: _ctx.translate('CustomReports_FindMeasurables')
- }, null, 8, _hoisted_27)])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.report.allowedToEdit || _ctx.multipleSites.length ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_28, [_hoisted_29, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("table", _hoisted_30, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("thead", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", _hoisted_31, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Id')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", _hoisted_32, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Name')), 1), _ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("th", _hoisted_33, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Remove')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tbody", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", _hoisted_34, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_NoMeasurableAssignedYet')), 1)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.multipleSites.length]]), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.multipleSites, function (site, index) {
+ }, null, 8, _hoisted_27)])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.report.allowedToEdit || _ctx.multipleSites.length ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_28, [_hoisted_29, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("table", _hoisted_30, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("thead", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", _hoisted_31, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Id')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", _hoisted_32, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Name')), 1), _ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("th", _hoisted_33, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Remove')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tbody", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", _hoisted_34, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_NoMeasurableAssignedYet')), 1)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.multipleSites.length]]), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.multipleSites, (site, index) => {
return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("tr", {
key: index
}, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(site.idsite), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(site.name), 1), _ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("td", _hoisted_35, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
class: "icon-minus table-action",
- onClick: function onClick($event) {
- return _ctx.removeSite(site);
- }
- }, null, 8, _hoisted_36)])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.multipleSites.length > 0]]);
+ onClick: $event => _ctx.removeSite(site)
+ }, null, 8, _hoisted_36)])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)])), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.multipleSites.length > 0]]);
}), 128))])])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_37, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", _hoisted_38, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportContent')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_39, [_ctx.browserArchivingDisabled && _ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_40, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningRequiresUnlockBrowserArchivingDisabled', _ctx.reArchiveLastN)), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), !_ctx.browserArchivingDisabled || !_ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_41, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningRequiresUnlock')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _hoisted_42, _hoisted_43, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", {
type: "button",
class: "btn unlockReport",
- onClick: _cache[5] || (_cache[5] = function ($event) {
- return _ctx.unlockReport();
- }),
+ onClick: _cache[5] || (_cache[5] = $event => _ctx.unlockReport()),
value: _ctx.translate('CustomReports_Unlock')
}, null, 8, _hoisted_44)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLocked]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_45, [_ctx.browserArchivingDisabled && _ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_46, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningOnUpdateReportMightGetLostBrowserArchivingDisabled', _ctx.reArchiveLastN)), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), !_ctx.browserArchivingDisabled || !_ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_47, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningOnUpdateReportMightGetLost')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isUnlocked]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_48, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "radio",
name: "reportType",
"model-value": _ctx.report.report_type,
- "onUpdate:modelValue": _cache[6] || (_cache[6] = function ($event) {
- return _ctx.setReportTypeHasChanged($event);
- }),
+ "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => _ctx.setReportTypeHasChanged($event)),
title: _ctx.translate('CustomReports_ReportType'),
disabled: !_ctx.canEdit,
options: _ctx.reportTypes
- }, null, 8, ["model-value", "title", "disabled", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_49, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_50, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_Dimensions')), 1), _hoisted_51, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.report.dimensions, function (dimension, dimIndex) {
+ }, null, 8, ["model-value", "title", "disabled", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_49, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_50, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_Dimensions')), 1), _hoisted_51, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.report.dimensions, (dimension, dimIndex) => {
return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])("selectedDimension selectedDimension".concat(dimIndex)),
+ class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(`selectedDimension selectedDimension${dimIndex}`),
key: dimIndex
}, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_52, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "expandable-select",
name: "dimensions",
"model-value": dimension,
- "onUpdate:modelValue": function onUpdateModelValue($event) {
- return _ctx.changeDimension($event, dimIndex);
- },
+ "onUpdate:modelValue": $event => _ctx.changeDimension($event, dimIndex),
title: _ctx.dimensionsReadable[dimension] || dimension,
"full-width": true,
options: _ctx.dimensions
}, null, 8, ["model-value", "onUpdate:modelValue", "title", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
class: "icon-minus",
title: _ctx.translate('CustomReports_RemoveDimension'),
- onClick: function onClick($event) {
- return _ctx.removeDimension(dimIndex);
- }
+ onClick: $event => _ctx.removeDimension(dimIndex)
}, null, 8, _hoisted_53)], 2);
}), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_54, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "expandable-select",
name: "dimensions",
"model-value": '',
- "onUpdate:modelValue": _cache[7] || (_cache[7] = function ($event) {
+ "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => {
_ctx.addDimension($event);
}),
title: _ctx.translate('CustomReports_AddDimension'),
@@ -599,32 +554,28 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
}, null, 8, ["title", "options"])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.report.dimensions.length < _ctx.maxDimensions]])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_55, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_56, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
class: "inline-help",
innerHTML: _ctx.$sanitize(_ctx.getDimensionsHelpText)
- }, null, 8, _hoisted_57)])])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.report.report_type !== 'evolution']]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_58, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_59, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Metrics')), 1), _hoisted_60, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.report.metrics, function (metric, metricIndex) {
+ }, null, 8, _hoisted_57)])])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.report.report_type !== 'evolution']]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_58, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_59, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Metrics')), 1), _hoisted_60, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.report.metrics, (metric, metricIndex) => {
return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])("selectedMetric selectedMetric".concat(metricIndex)),
+ class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(`selectedMetric selectedMetric${metricIndex}`),
key: metricIndex
}, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_61, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "expandable-select",
name: "metrics",
"model-value": metric,
- "onUpdate:modelValue": function onUpdateModelValue($event) {
- return _ctx.changeMetric($event, metricIndex);
- },
+ "onUpdate:modelValue": $event => _ctx.changeMetric($event, metricIndex),
title: _ctx.metricsReadable[metric] || metric,
"full-width": true,
options: _ctx.metrics
}, null, 8, ["model-value", "onUpdate:modelValue", "title", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
class: "icon-minus",
title: _ctx.translate('CustomReports_RemoveMetric'),
- onClick: function onClick($event) {
- return _ctx.removeMetric(metricIndex);
- }
+ onClick: $event => _ctx.removeMetric(metricIndex)
}, null, 8, _hoisted_62)], 2);
}), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_63, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "expandable-select",
name: "metrics",
"model-value": '',
- "onUpdate:modelValue": _cache[8] || (_cache[8] = function ($event) {
+ "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => {
_ctx.addMetric($event);
}),
title: _ctx.translate('CustomReports_AddMetric'),
@@ -635,24 +586,19 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
innerHTML: _ctx.getProductRevenueDependencyMessage
}, null, 8, _hoisted_67), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.dependencyAdded]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_68, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_69, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", _hoisted_70, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_Filter')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportSegmentHelp')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SegmentGenerator, {
"model-value": _ctx.report.segment_filter,
- "onUpdate:modelValue": _cache[9] || (_cache[9] = function ($event) {
- return _ctx.setSegmentFilterHasChanged($event);
- }),
+ "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => _ctx.setSegmentFilterHasChanged($event)),
idsite: _ctx.report.site.id
}, null, 8, ["model-value", "idsite"])])])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_71, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_72, [_hoisted_73, _hoisted_74, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, {
class: "showPreviewButton",
disabled: !((_ctx$report$metrics = _ctx.report.metrics) !== null && _ctx$report$metrics !== void 0 && _ctx$report$metrics.length) || !((_ctx$report$dimension = _ctx.report.dimensions) !== null && _ctx$report$dimension !== void 0 && _ctx$report$dimension.length),
- onConfirm: _cache[10] || (_cache[10] = function ($event) {
- return _ctx.showPreview();
- }),
+ onConfirm: _cache[10] || (_cache[10] = $event => _ctx.showPreview()),
value: _ctx.translate('CustomReports_PreviewReport')
}, null, 8, ["disabled", "value"])])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.report.report_type === 'table']]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_75, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
uicontrol: "select",
name: "reportCategories",
"model-value": _ctx.report.category.id,
- "onUpdate:modelValue": _cache[11] || (_cache[11] = function ($event) {
+ "onUpdate:modelValue": _cache[11] || (_cache[11] = $event => {
_ctx.report.category.id = $event;
-
_ctx.setValueHasChanged();
}),
title: _ctx.translate('CustomReports_ReportCategory'),
@@ -664,9 +610,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
uicontrol: "select",
name: "reportSubcategories",
"model-value": (_ctx$report$subcatego = _ctx.report.subcategory) === null || _ctx$report$subcatego === void 0 ? void 0 : _ctx$report$subcatego.id,
- "onUpdate:modelValue": _cache[12] || (_cache[12] = function ($event) {
+ "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => {
_ctx.setSubcategory($event);
-
_ctx.setValueHasChanged();
}),
title: _ctx.translate('CustomReports_ReportSubcategory'),
@@ -676,7 +621,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
}, null, 8, ["model-value", "title", "disabled", "options", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_77, [_ctx.browserArchivingDisabled && _ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_78, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningOnUpdateReportMightGetLostBrowserArchivingDisabled', _ctx.reArchiveLastN)), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), !_ctx.browserArchivingDisabled || !_ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_79, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_WarningOnUpdateReportMightGetLost')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isUnlocked]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_80, [_ctx.multipleSites.length && !_ctx.report.allowedToEdit ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_81, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportEditNotAllowedMultipleWebsitesAccessIssue')), 1)) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", _hoisted_82, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ReportEditNotAllowedAllWebsitesUpdated')), 1))], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.canEdit]]), _ctx.childReports.length ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_83, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", {
class: "col s12",
textContent: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_OrderSubCategoryReports'))
- }, null, 8, _hoisted_84), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_85, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("ul", _hoisted_86, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.childReports, function (childReport) {
+ }, null, 8, _hoisted_84), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_85, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("ul", _hoisted_86, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.childReports, childReport => {
return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("li", {
key: childReport.idcustomreport,
"data-id": childReport.idcustomreport
@@ -686,16 +631,12 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
textContent: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_OrderSubCategoryReportsDescription'))
}, null, 8, _hoisted_91)])])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, {
class: "createButton",
- onConfirm: _cache[13] || (_cache[13] = function ($event) {
- return _ctx.edit ? _ctx.updateReport() : _ctx.createReport();
- }),
+ onConfirm: _cache[13] || (_cache[13] = $event => _ctx.edit ? _ctx.updateReport() : _ctx.createReport()),
disabled: _ctx.isUpdating || !_ctx.isDirty,
saving: _ctx.isUpdating,
value: _ctx.saveButtonText
}, null, 8, ["disabled", "saving", "value"]), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.canEdit]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_92, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
- onClick: _cache[14] || (_cache[14] = function ($event) {
- return _ctx.cancel();
- })
+ onClick: _cache[14] || (_cache[14] = $event => _ctx.cancel())
}, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Cancel')), 1)])])], 32), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_93, [_ctx.browserArchivingDisabled && _ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", _hoisted_94, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ConfirmUnlockReportBrowserArchivingDisabled', _ctx.reArchiveLastN)), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), !_ctx.browserArchivingDisabled || !_ctx.reArchiveLastN ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", _hoisted_95, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CustomReports_ConfirmUnlockReport')), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", {
role: "yes",
type: "button",
@@ -729,26 +670,7 @@ var external_CorePluginsAdmin_ = __webpack_require__("a5a2");
var external_SegmentEditor_ = __webpack_require__("f06f");
// CONCATENATED MODULE: ./plugins/CustomReports/vue/src/CustomReports.store.ts
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
-function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
-
-function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
-
-function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
-
-function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
-
-function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
-
-function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
-
-function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
-
/**
* Copyright (C) InnoCraft Ltd - All rights reserved.
*
@@ -765,39 +687,31 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
*/
-
function arrayFilterAndRemoveDuplicates(values) {
- return _toConsumableArray(new Set(values)).filter(function (v) {
- return !!v;
- });
+ return [...new Set(values)].filter(v => !!v);
}
-
function formatExpandableList(listByCategories, subcategoryField, extraField) {
- var list = [];
- listByCategories.forEach(function (category) {
- category[subcategoryField].forEach(function (value) {
+ const list = [];
+ listByCategories.forEach(category => {
+ category[subcategoryField].forEach(value => {
list.push(Object.assign({
group: category.category,
key: value.uniqueId,
value: value.name,
tooltip: value.description || undefined
- }, extraField ? _defineProperty({}, extraField, value[extraField]) : {}));
+ }, extraField ? {
+ [extraField]: value[extraField]
+ } : {}));
});
});
return list;
}
-
-var EMPTY_CAT = {
+const EMPTY_CAT = {
key: '',
value: ''
};
-
-var CustomReports_store_CustomReportsStore = /*#__PURE__*/function () {
- function CustomReportsStore() {
- var _this = this;
-
- _classCallCheck(this, CustomReportsStore);
-
+class CustomReports_store_CustomReportsStore {
+ constructor() {
_defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({
reports: [],
reportTypesReadable: {},
@@ -810,366 +724,254 @@ var CustomReports_store_CustomReportsStore = /*#__PURE__*/function () {
allMetrics: [],
allDimensions: []
}));
-
- _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(_this.privateState);
- }));
-
+ _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(this.privateState)));
_defineProperty(this, "fetchPromise", null);
-
_defineProperty(this, "availableReportTypesPromise", null);
-
_defineProperty(this, "dimensionsPromise", null);
-
_defineProperty(this, "dimensionsIdsiteLoaded", 0);
-
_defineProperty(this, "metricsPromise", null);
-
_defineProperty(this, "metricsIdsiteLoaded", 0);
-
_defineProperty(this, "categoriesPromise", null);
-
_defineProperty(this, "categoriesIdsiteLoaded", null);
}
-
- _createClass(CustomReportsStore, [{
- key: "reload",
- value: function reload() {
- this.privateState.reports = [];
- this.fetchPromise = null;
- return this.fetchReports();
- }
- }, {
- key: "cleanupSegmentDefinition",
- value: function cleanupSegmentDefinition(definition) {
- var result = definition;
- result = result.replace('\'', '%27');
- result = result.replace('&', '%26');
- return result;
- }
- }, {
- key: "getAvailableReportTypes",
- value: function getAvailableReportTypes() {
- var _this2 = this;
-
- if (!this.availableReportTypesPromise) {
- this.availableReportTypesPromise = external_CoreHome_["AjaxHelper"].fetch({
- method: 'CustomReports.getAvailableReportTypes',
- filter_limit: '-1'
- }).then(function (reportTypes) {
- var reportTypeMap = {};
- reportTypes.forEach(function (rt) {
- reportTypeMap[rt.key] = rt.value;
- });
- _this2.privateState.reportTypesReadable = reportTypeMap;
+ reload() {
+ this.privateState.reports = [];
+ this.fetchPromise = null;
+ return this.fetchReports();
+ }
+ cleanupSegmentDefinition(definition) {
+ let result = definition;
+ result = result.replace('\'', '%27');
+ result = result.replace('&', '%26');
+ return result;
+ }
+ getAvailableReportTypes() {
+ if (!this.availableReportTypesPromise) {
+ this.availableReportTypesPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'CustomReports.getAvailableReportTypes',
+ filter_limit: '-1'
+ }).then(reportTypes => {
+ const reportTypeMap = {};
+ reportTypes.forEach(rt => {
+ reportTypeMap[rt.key] = rt.value;
});
- }
-
- return this.availableReportTypesPromise.then(function () {
- return _this2.state.value.reportTypesReadable;
+ this.privateState.reportTypesReadable = reportTypeMap;
});
}
- }, {
- key: "getAvailableDimensions",
- value: function getAvailableDimensions(idSite) {
- var _this3 = this;
-
- if (!this.dimensionsPromise || this.dimensionsIdsiteLoaded !== idSite) {
- this.dimensionsIdsiteLoaded = idSite;
- this.dimensionsPromise = external_CoreHome_["AjaxHelper"].fetch({
- method: 'CustomReports.getAvailableDimensions',
- filter_limit: '-1',
- idSite: idSite
- }).then(function (dimensions) {
- var dimensionMap = {};
- dimensions.forEach(function (category) {
- category.dimensions.forEach(function (dimension) {
- dimensionMap[dimension.uniqueId] = dimension.name;
- });
+ return this.availableReportTypesPromise.then(() => this.state.value.reportTypesReadable);
+ }
+ getAvailableDimensions(idSite) {
+ if (!this.dimensionsPromise || this.dimensionsIdsiteLoaded !== idSite) {
+ this.dimensionsIdsiteLoaded = idSite;
+ this.dimensionsPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'CustomReports.getAvailableDimensions',
+ filter_limit: '-1',
+ idSite
+ }).then(dimensions => {
+ const dimensionMap = {};
+ dimensions.forEach(category => {
+ category.dimensions.forEach(dimension => {
+ dimensionMap[dimension.uniqueId] = dimension.name;
});
- _this3.privateState.dimensionsReadable = dimensionMap;
- _this3.privateState.allDimensions = formatExpandableList(dimensions, 'dimensions', 'sqlSegment');
});
- }
-
- return this.dimensionsPromise.then(function () {
- return _this3.state.value.dimensionsReadable;
+ this.privateState.dimensionsReadable = dimensionMap;
+ this.privateState.allDimensions = formatExpandableList(dimensions, 'dimensions', 'sqlSegment');
});
}
- }, {
- key: "getAvailableMetrics",
- value: function getAvailableMetrics(idSite) {
- var _this4 = this;
-
- if (!this.metricsPromise || this.metricsIdsiteLoaded !== idSite) {
- this.metricsIdsiteLoaded = idSite;
- this.metricsPromise = external_CoreHome_["AjaxHelper"].fetch({
- method: 'CustomReports.getAvailableMetrics',
- filter_limit: '-1',
- idSite: idSite
- }).then(function (metrics) {
- var metricsMap = {};
- metrics.forEach(function (metricsCategory) {
- metricsCategory.metrics.forEach(function (metric) {
- metricsMap[metric.uniqueId] = metric.name;
- });
+ return this.dimensionsPromise.then(() => this.state.value.dimensionsReadable);
+ }
+ getAvailableMetrics(idSite) {
+ if (!this.metricsPromise || this.metricsIdsiteLoaded !== idSite) {
+ this.metricsIdsiteLoaded = idSite;
+ this.metricsPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'CustomReports.getAvailableMetrics',
+ filter_limit: '-1',
+ idSite
+ }).then(metrics => {
+ const metricsMap = {};
+ metrics.forEach(metricsCategory => {
+ metricsCategory.metrics.forEach(metric => {
+ metricsMap[metric.uniqueId] = metric.name;
});
- _this4.privateState.metricsReadable = metricsMap;
- _this4.privateState.allMetrics = formatExpandableList(metrics, 'metrics');
});
- }
-
- return this.metricsPromise.then(function () {
- return _this4.state.value.metricsReadable;
+ this.privateState.metricsReadable = metricsMap;
+ this.privateState.allMetrics = formatExpandableList(metrics, 'metrics');
});
}
- }, {
- key: "getAvailableCategories",
- value: function getAvailableCategories(idSite) {
- var _this5 = this;
-
- var idSiteToUse = !idSite || idSite === 'all' ? external_CoreHome_["Matomo"].idSite : idSite;
-
- if (!this.categoriesPromise || this.categoriesIdsiteLoaded !== idSite) {
- this.categoriesPromise = external_CoreHome_["AjaxHelper"].fetch({
- method: 'CustomReports.getAvailableCategories',
- filter_limit: '-1',
- idSite: idSiteToUse
- }).then(function (response) {
- var categories = [];
- var subcategories = {};
- response.forEach(function (category) {
- categories.push({
- key: category.uniqueId,
- value: category.name
- });
- category.subcategories.forEach(function (subcat) {
- subcategories[category.uniqueId] = subcategories[category.uniqueId] || [EMPTY_CAT];
- subcategories[category.uniqueId].push({
- key: subcat.uniqueId,
- value: subcat.name
- });
- });
+ return this.metricsPromise.then(() => this.state.value.metricsReadable);
+ }
+ getAvailableCategories(idSite) {
+ const idSiteToUse = !idSite || idSite === 'all' ? external_CoreHome_["Matomo"].idSite : idSite;
+ if (!this.categoriesPromise || this.categoriesIdsiteLoaded !== idSite) {
+ this.categoriesPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'CustomReports.getAvailableCategories',
+ filter_limit: '-1',
+ idSite: idSiteToUse
+ }).then(response => {
+ const categories = [];
+ const subcategories = {};
+ response.forEach(category => {
+ categories.push({
+ key: category.uniqueId,
+ value: category.name
});
- _this5.privateState.categories = categories;
- _this5.privateState.subcategories = subcategories;
- });
- }
-
- return this.categoriesPromise;
- }
- }, {
- key: "fetchReports",
- value: function fetchReports() {
- var _this6 = this;
-
- if (!this.fetchPromise) {
- this.fetchPromise = external_CoreHome_["AjaxHelper"].fetch({
- method: 'CustomReports.getConfiguredReports',
- filter_limit: '-1'
- });
- }
-
- this.privateState.isLoading = true;
- this.privateState.reports = [];
- return this.fetchPromise.then(function (reports) {
- _this6.privateState.reports = reports.map(function (report) {
- var _report$subcategory, _report$category;
-
- var subcategoryLink = undefined;
-
- if (report !== null && report !== void 0 && (_report$subcategory = report.subcategory) !== null && _report$subcategory !== void 0 && _report$subcategory.id) {
- subcategoryLink = report.subcategory.id;
- } else if ((report === null || report === void 0 ? void 0 : (_report$category = report.category) === null || _report$category === void 0 ? void 0 : _report$category.id) === 'CustomReports_CustomReports') {
- subcategoryLink = report.idcustomreport;
- } else {
- subcategoryLink = report.name;
- }
-
- return Object.assign(Object.assign({}, report), {}, {
- // report.idsite is falsey when report is set for all sites
- linkIdSite: report.idsite ? report.idsite : external_CoreHome_["Matomo"].idSite,
- subcategoryLink: subcategoryLink
+ category.subcategories.forEach(subcat => {
+ subcategories[category.uniqueId] = subcategories[category.uniqueId] || [EMPTY_CAT];
+ subcategories[category.uniqueId].push({
+ key: subcat.uniqueId,
+ value: subcat.name
+ });
});
});
- return _this6.state.value.reports;
- }).finally(function () {
- _this6.privateState.isLoading = false;
+ this.privateState.categories = categories;
+ this.privateState.subcategories = subcategories;
});
}
- }, {
- key: "findReport",
- value: function findReport(idCustomReport, isReload) {
- var _this7 = this;
-
- // before going through an API request we first try to find it in loaded reports
- var found = this.state.value.reports.find(function (r) {
- return parseInt("".concat(r.idcustomreport), 10) === idCustomReport;
- });
-
- if (found && !isReload) {
- return Promise.resolve(found);
- } // otherwise we fetch it via API
-
-
- this.privateState.isLoading = true;
- return external_CoreHome_["AjaxHelper"].fetch({
- idCustomReport: idCustomReport,
- method: 'CustomReports.getConfiguredReport'
- }).finally(function () {
- _this7.privateState.isLoading = false;
- });
- }
- }, {
- key: "deleteReport",
- value: function deleteReport(idCustomReport, idSite) {
- var _this8 = this;
-
- this.privateState.isUpdating = true;
- this.privateState.reports = [];
- return external_CoreHome_["AjaxHelper"].fetch({
- idCustomReport: idCustomReport,
- idSite: "".concat(idSite),
- method: 'CustomReports.deleteCustomReport'
- }, {
- withTokenInUrl: true
- }).then(function () {
- return {
- type: 'success'
- };
- }).catch(function (e) {
- return {
- type: 'error',
- message: e.message || e
- };
- }).finally(function () {
- _this8.privateState.isUpdating = false;
- });
- }
- }, {
- key: "pauseReport",
- value: function pauseReport(idCustomReport, idSite) {
- var _this9 = this;
-
- this.privateState.isUpdating = true;
- this.privateState.reports = [];
- return external_CoreHome_["AjaxHelper"].fetch({
- idCustomReport: idCustomReport,
- idSite: "".concat(idSite),
- method: 'CustomReports.pauseCustomReport'
- }, {
- withTokenInUrl: true
- }).then(function () {
- return {
- type: 'success'
- };
- }).catch(function (e) {
- return {
- type: 'error',
- message: e.message || e
- };
- }).finally(function () {
- _this9.privateState.isUpdating = false;
- });
- }
- }, {
- key: "resumeReport",
- value: function resumeReport(idCustomReport, idSite) {
- var _this10 = this;
-
- this.privateState.isUpdating = true;
- this.privateState.reports = [];
- return external_CoreHome_["AjaxHelper"].fetch({
- idCustomReport: idCustomReport,
- idSite: "".concat(idSite),
- method: 'CustomReports.resumeCustomReport'
- }, {
- withTokenInUrl: true
- }).then(function () {
- return {
- type: 'success'
- };
- }).catch(function (e) {
- return {
- type: 'error',
- message: e.message || e
- };
- }).finally(function () {
- _this10.privateState.isUpdating = false;
+ return this.categoriesPromise;
+ }
+ fetchReports() {
+ if (!this.fetchPromise) {
+ this.fetchPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'CustomReports.getConfiguredReports',
+ filter_limit: '-1'
});
}
- }, {
- key: "createOrUpdateReport",
- value: function createOrUpdateReport(report, method, childReportIds, multipleIdSites) {
- var _report$category2,
- _report$subcategory2,
- _this11 = this;
-
- this.privateState.isUpdating = true;
- return external_CoreHome_["AjaxHelper"].post({
- method: method,
- idCustomReport: report.idcustomreport,
- reportType: report.report_type,
- name: report.name.trim(),
- description: report.description.trim(),
- segmentFilter: encodeURIComponent(report.segment_filter),
- categoryId: (_report$category2 = report.category) === null || _report$category2 === void 0 ? void 0 : _report$category2.id,
- subcategoryId: (_report$subcategory2 = report.subcategory) === null || _report$subcategory2 === void 0 ? void 0 : _report$subcategory2.id,
- idSite: report.site.id,
- subCategoryReportIds: childReportIds,
- multipleIdSites: multipleIdSites
- }, {
- dimensionIds: arrayFilterAndRemoveDuplicates(report.dimensions),
- metricIds: arrayFilterAndRemoveDuplicates(report.metrics)
- }, {
- withTokenInUrl: true
- }).then(function (response) {
- return {
- type: 'success',
- response: response
- };
- }).catch(function (error) {
- return {
- type: 'error',
- message: error.message || error
- };
- }).finally(function () {
- _this11.privateState.isUpdating = false;
+ this.privateState.isLoading = true;
+ this.privateState.reports = [];
+ return this.fetchPromise.then(reports => {
+ this.privateState.reports = reports.map(report => {
+ var _report$subcategory, _report$category;
+ let subcategoryLink = undefined;
+ if (report !== null && report !== void 0 && (_report$subcategory = report.subcategory) !== null && _report$subcategory !== void 0 && _report$subcategory.id) {
+ subcategoryLink = report.subcategory.id;
+ } else if ((report === null || report === void 0 || (_report$category = report.category) === null || _report$category === void 0 ? void 0 : _report$category.id) === 'CustomReports_CustomReports') {
+ subcategoryLink = report.idcustomreport;
+ } else {
+ subcategoryLink = report.name;
+ }
+ return Object.assign(Object.assign({}, report), {}, {
+ // report.idsite is falsey when report is set for all sites
+ linkIdSite: report.idsite ? report.idsite : external_CoreHome_["Matomo"].idSite,
+ subcategoryLink
+ });
});
+ return this.state.value.reports;
+ }).finally(() => {
+ this.privateState.isLoading = false;
+ });
+ }
+ findReport(idCustomReport, isReload) {
+ // before going through an API request we first try to find it in loaded reports
+ const found = this.state.value.reports.find(r => parseInt(`${r.idcustomreport}`, 10) === idCustomReport);
+ if (found && !isReload) {
+ return Promise.resolve(found);
}
- }]);
-
- return CustomReportsStore;
-}();
-
+ // otherwise we fetch it via API
+ this.privateState.isLoading = true;
+ return external_CoreHome_["AjaxHelper"].fetch({
+ idCustomReport,
+ method: 'CustomReports.getConfiguredReport'
+ }).finally(() => {
+ this.privateState.isLoading = false;
+ });
+ }
+ deleteReport(idCustomReport, idSite) {
+ this.privateState.isUpdating = true;
+ this.privateState.reports = [];
+ return external_CoreHome_["AjaxHelper"].fetch({
+ idCustomReport,
+ idSite: `${idSite}`,
+ method: 'CustomReports.deleteCustomReport'
+ }, {
+ withTokenInUrl: true
+ }).then(() => ({
+ type: 'success'
+ })).catch(e => ({
+ type: 'error',
+ message: e.message || e
+ })).finally(() => {
+ this.privateState.isUpdating = false;
+ });
+ }
+ pauseReport(idCustomReport, idSite) {
+ this.privateState.isUpdating = true;
+ this.privateState.reports = [];
+ return external_CoreHome_["AjaxHelper"].fetch({
+ idCustomReport,
+ idSite: `${idSite}`,
+ method: 'CustomReports.pauseCustomReport'
+ }, {
+ withTokenInUrl: true
+ }).then(() => ({
+ type: 'success'
+ })).catch(e => ({
+ type: 'error',
+ message: e.message || e
+ })).finally(() => {
+ this.privateState.isUpdating = false;
+ });
+ }
+ resumeReport(idCustomReport, idSite) {
+ this.privateState.isUpdating = true;
+ this.privateState.reports = [];
+ return external_CoreHome_["AjaxHelper"].fetch({
+ idCustomReport,
+ idSite: `${idSite}`,
+ method: 'CustomReports.resumeCustomReport'
+ }, {
+ withTokenInUrl: true
+ }).then(() => ({
+ type: 'success'
+ })).catch(e => ({
+ type: 'error',
+ message: e.message || e
+ })).finally(() => {
+ this.privateState.isUpdating = false;
+ });
+ }
+ createOrUpdateReport(report, method, childReportIds, multipleIdSites) {
+ var _report$category2, _report$subcategory2;
+ this.privateState.isUpdating = true;
+ return external_CoreHome_["AjaxHelper"].post({
+ method,
+ idCustomReport: report.idcustomreport,
+ reportType: report.report_type,
+ name: report.name.trim(),
+ description: report.description.trim(),
+ segmentFilter: encodeURIComponent(report.segment_filter),
+ categoryId: (_report$category2 = report.category) === null || _report$category2 === void 0 ? void 0 : _report$category2.id,
+ subcategoryId: (_report$subcategory2 = report.subcategory) === null || _report$subcategory2 === void 0 ? void 0 : _report$subcategory2.id,
+ idSite: report.site.id,
+ subCategoryReportIds: childReportIds,
+ multipleIdSites
+ }, {
+ dimensionIds: arrayFilterAndRemoveDuplicates(report.dimensions),
+ metricIds: arrayFilterAndRemoveDuplicates(report.metrics)
+ }, {
+ withTokenInUrl: true
+ }).then(response => ({
+ type: 'success',
+ response
+ })).catch(error => ({
+ type: 'error',
+ message: error.message || error
+ })).finally(() => {
+ this.privateState.isUpdating = false;
+ });
+ }
+}
/* harmony default export */ var CustomReports_store = (new CustomReports_store_CustomReportsStore());
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CustomReports/vue/src/Reports/Edit.vue?vue&type=script&lang=ts
-function Editvue_type_script_lang_ts_toConsumableArray(arr) { return Editvue_type_script_lang_ts_arrayWithoutHoles(arr) || Editvue_type_script_lang_ts_iterableToArray(arr) || Editvue_type_script_lang_ts_unsupportedIterableToArray(arr) || Editvue_type_script_lang_ts_nonIterableSpread(); }
-
-function Editvue_type_script_lang_ts_nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
-
-function Editvue_type_script_lang_ts_unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return Editvue_type_script_lang_ts_arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return Editvue_type_script_lang_ts_arrayLikeToArray(o, minLen); }
-
-function Editvue_type_script_lang_ts_iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
-
-function Editvue_type_script_lang_ts_arrayWithoutHoles(arr) { if (Array.isArray(arr)) return Editvue_type_script_lang_ts_arrayLikeToArray(arr); }
-
-function Editvue_type_script_lang_ts_arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
-
-
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/CustomReports/vue/src/Reports/Edit.vue?vue&type=script&lang=ts
-var notificationId = 'reportsmanagement';
-var productMetricNotificationId = 'reportsmanagementProductMetric';
+const notificationId = 'reportsmanagement';
+const productMetricNotificationId = 'reportsmanagementProductMetric';
function Editvue_type_script_lang_ts_arrayFilterAndRemoveDuplicates(values) {
- return Editvue_type_script_lang_ts_toConsumableArray(new Set(values)).filter(function (v) {
- return !!v;
- });
+ return [...new Set(values)].filter(v => !!v);
}
-
function makeDefaultReport() {
return {
dimensions: [],
@@ -1180,7 +982,6 @@ function makeDefaultReport() {
category: {}
};
}
-
/* harmony default export */ var Editvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
props: {
idCustomReport: Number,
@@ -1196,7 +997,7 @@ function makeDefaultReport() {
SegmentGenerator: external_SegmentEditor_["SegmentGenerator"],
SaveButton: external_CorePluginsAdmin_["SaveButton"]
},
- data: function data() {
+ data() {
return {
isDirty: false,
report: makeDefaultReport(),
@@ -1211,168 +1012,149 @@ function makeDefaultReport() {
multipleIdSites: []
};
},
- created: function created() {
+ created() {
CustomReports_store.getAvailableReportTypes();
this.init();
},
watch: {
- idCustomReport: function idCustomReport(newValue) {
+ idCustomReport(newValue) {
if (newValue === null) {
return;
}
-
this.init();
}
},
methods: {
- initReportOptions: function initReportOptions() {
- var idsite = parseInt("".concat(this.report.site.id), 10) || 'all';
+ initReportOptions() {
+ const idsite = parseInt(`${this.report.site.id}`, 10) || 'all';
CustomReports_store.getAvailableDimensions(idsite);
CustomReports_store.getAvailableMetrics(idsite);
CustomReports_store.getAvailableCategories(idsite);
},
- doUnlock: function doUnlock() {
+ doUnlock() {
this.isLocked = false;
this.isUnlocked = true;
},
- confirmReportIsLocked: function confirmReportIsLocked(callback) {
- var _this = this;
-
+ confirmReportIsLocked(callback) {
external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.infoReportIsLocked, {
- unlock: function unlock() {
- _this.doUnlock();
-
+ unlock: () => {
+ this.doUnlock();
if (callback) {
callback();
}
}
});
},
- removeAnyReportNotification: function removeAnyReportNotification() {
+ removeAnyReportNotification() {
external_CoreHome_["NotificationsStore"].remove(notificationId);
external_CoreHome_["NotificationsStore"].remove(productMetricNotificationId);
external_CoreHome_["NotificationsStore"].remove('ajaxHelper');
},
- showApiErrorMessage: function showApiErrorMessage(errorMessage, responseType) {
+ showApiErrorMessage(errorMessage, responseType) {
if (errorMessage && responseType) {
this.removeAnyReportNotification();
- var elem = document.createElement('textarea');
+ const elem = document.createElement('textarea');
elem.innerHTML = errorMessage;
this.showNotification(elem.value, responseType, 'toast');
}
},
- showNotification: function showNotification(message, context) {
- var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
- var instanceId = external_CoreHome_["NotificationsStore"].show({
- message: message,
- context: context,
+ showNotification(message, context, type = null) {
+ const instanceId = external_CoreHome_["NotificationsStore"].show({
+ message,
+ context,
id: notificationId,
type: type !== null && type !== void 0 ? type : 'transient',
prepend: true
});
- setTimeout(function () {
+ setTimeout(() => {
external_CoreHome_["NotificationsStore"].scrollToNotification(instanceId);
}, 100);
},
- showProductMetricNotification: function showProductMetricNotification(message, shouldScrollToNotification) {
- var instanceId = external_CoreHome_["NotificationsStore"].show({
- message: message,
+ showProductMetricNotification(message, shouldScrollToNotification) {
+ const instanceId = external_CoreHome_["NotificationsStore"].show({
+ message,
context: 'warning',
id: productMetricNotificationId,
type: 'transient'
});
-
if (!shouldScrollToNotification) {
return;
}
-
- setTimeout(function () {
+ setTimeout(() => {
external_CoreHome_["NotificationsStore"].scrollToNotification(instanceId);
}, 100);
},
- showErrorFieldNotProvidedNotification: function showErrorFieldNotProvidedNotification(title) {
- var message = Object(external_CoreHome_["translate"])('CustomReports_ErrorXNotProvided', [title]);
+ showErrorFieldNotProvidedNotification(title) {
+ const message = Object(external_CoreHome_["translate"])('CustomReports_ErrorXNotProvided', [title]);
this.showNotification(message, 'error');
},
- init: function init() {
- var _this2 = this;
-
- var idCustomReport = this.idCustomReport;
+ init() {
+ const {
+ idCustomReport
+ } = this;
this.canEdit = true;
this.report = makeDefaultReport();
external_CoreHome_["Matomo"].helper.lazyScrollToContent();
-
if (this.edit && idCustomReport) {
- CustomReports_store.findReport(idCustomReport, true).then(function (report) {
- var _this2$report$child_r;
-
+ CustomReports_store.findReport(idCustomReport, true).then(report => {
+ var _this$report$child_re;
if (!report) {
return;
}
-
- _this2.report = Object(external_CoreHome_["clone"])(report);
- _this2.isLocked = true;
- _this2.isUnlocked = false;
- _this2.canEdit = true;
- _this2.childReports = (_this2$report$child_r = _this2.report.child_reports) !== null && _this2$report$child_r !== void 0 ? _this2$report$child_r : [];
-
- if (_this2.report.multipleIdSites && _this2.report.multipleIdSites.length && _this2.report.site.id !== 'all' && _this2.report.site.id !== '0' && _this2.report.site.id !== 0) {
- _this2.multipleSites = _this2.report.multipleIdSites;
-
- if (!_this2.report.allowedToEdit) {
- _this2.canEdit = false;
- _this2.isLocked = false;
+ this.report = Object(external_CoreHome_["clone"])(report);
+ this.isLocked = true;
+ this.isUnlocked = false;
+ this.canEdit = true;
+ this.childReports = (_this$report$child_re = this.report.child_reports) !== null && _this$report$child_re !== void 0 ? _this$report$child_re : [];
+ if (this.report.multipleIdSites && this.report.multipleIdSites.length && this.report.site.id !== 'all' && this.report.site.id !== '0' && this.report.site.id !== 0) {
+ this.multipleSites = this.report.multipleIdSites;
+ if (!this.report.allowedToEdit) {
+ this.canEdit = false;
+ this.isLocked = false;
}
}
-
- if (_this2.childReports.length) {
- Object.values(_this2.childReports).forEach(function (value) {
- _this2.childReportIds.push(value.idcustomreport);
+ if (this.childReports.length) {
+ Object.values(this.childReports).forEach(value => {
+ this.childReportIds.push(value.idcustomreport);
});
}
-
- $(document).ready(function () {
+ $(document).ready(() => {
$('#childReports').sortable({
connectWith: '#childReports',
- update: function update() {
- _this2.isDirty = true;
- var childReportsListItems = $('#childReports li');
- _this2.childReportIds = [];
- childReportsListItems.each(function (idx, li) {
+ update: () => {
+ this.isDirty = true;
+ const childReportsListItems = $('#childReports li');
+ this.childReportIds = [];
+ childReportsListItems.each((idx, li) => {
if (li.dataset.id) {
- _this2.childReportIds.push(li.dataset.id);
+ this.childReportIds.push(li.dataset.id);
}
});
}
});
});
- var idSite = _this2.report.idsite;
-
+ let idSite = this.report.idsite;
if (idSite === 0 || idSite === '0' || idSite === 'all') {
// we need to make sure to send 'all' and not '0' as otherwise piwikApi would
// consider 0 as no value set and replace it with the current idsite. Also the
// site selector expects us to set 'all' instead of 0
idSite = 'all';
-
- if (!_this2.isSuperUser) {
+ if (!this.isSuperUser) {
// a lock does not make sense because report cannot be changed anyway. we do not want
// to show a warning related to this in such a case
- _this2.canEdit = false;
- _this2.isLocked = false;
+ this.canEdit = false;
+ this.isLocked = false;
}
}
-
- _this2.report.site = {
+ this.report.site = {
id: idSite,
- name: _this2.report.site.name
+ name: this.report.site.name
};
- _this2.isDirty = false;
-
- _this2.initReportOptions();
+ this.isDirty = false;
+ this.initReportOptions();
});
return;
}
-
if (this.create) {
this.report = {
idsite: external_CoreHome_["Matomo"].idSite,
@@ -1399,289 +1181,233 @@ function makeDefaultReport() {
this.initReportOptions();
}
},
- cancel: function cancel() {
- var newParams = Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value);
+ cancel() {
+ const newParams = Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value);
delete newParams.idCustomReport;
external_CoreHome_["MatomoUrl"].updateHash(newParams);
},
- unlockReport: function unlockReport() {
- var _this3 = this;
-
+ unlockReport() {
if (!this.report) {
return;
}
-
if (this.isLocked) {
external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmUnlockReport, {
- yes: function yes() {
- _this3.doUnlock();
+ yes: () => {
+ this.doUnlock();
}
});
}
},
- createReport: function createReport() {
- var _this4 = this;
-
- var method = 'CustomReports.addCustomReport';
+ createReport() {
+ const method = 'CustomReports.addCustomReport';
this.removeAnyReportNotification();
-
if (!this.checkRequiredFieldsAreSet()) {
return;
}
-
this.multipleIdSites = [];
-
if (this.multipleSites && this.multipleSites.length && this.report.site.id !== 'all' && this.report.site.id !== '0' && this.report.site.id !== 0) {
this.multipleIdSites.push(external_CoreHome_["Matomo"].idSite);
- this.multipleSites.forEach(function (item) {
- var idSite = item.idsite;
-
- if (!_this4.multipleIdSites.includes(idSite)) {
- _this4.multipleIdSites.push(idSite);
+ this.multipleSites.forEach(item => {
+ const idSite = item.idsite;
+ if (!this.multipleIdSites.includes(idSite)) {
+ this.multipleIdSites.push(idSite);
}
});
}
-
if (this.multipleIdSites && this.multipleIdSites.length) {
// need to update this else this creates an issue after save
this.report.site.id = external_CoreHome_["Matomo"].idSite;
}
-
- CustomReports_store.createOrUpdateReport(this.report, method, this.childReportIds, this.multipleIdSites).then(function (response) {
+ CustomReports_store.createOrUpdateReport(this.report, method, this.childReportIds, this.multipleIdSites).then(response => {
if (!response || response.type === 'error' || !response.response) {
var _response$message, _response$type;
-
- _this4.showApiErrorMessage((_response$message = response.message) !== null && _response$message !== void 0 ? _response$message : '', (_response$type = response.type) !== null && _response$type !== void 0 ? _response$type : 'error');
-
+ this.showApiErrorMessage((_response$message = response.message) !== null && _response$message !== void 0 ? _response$message : '', (_response$type = response.type) !== null && _response$type !== void 0 ? _response$type : 'error');
return;
}
-
- _this4.isDirty = false;
- var idCustomReport = response.response.value;
-
- if (_this4.report.site) {
- var idSite = _this4.report.site.id;
-
- if (idSite && idSite !== 'all' && "".concat(idSite) !== "".concat(external_CoreHome_["Matomo"].idSite)) {
+ this.isDirty = false;
+ const idCustomReport = response.response.value;
+ if (this.report.site) {
+ const idSite = this.report.site.id;
+ if (idSite && idSite !== 'all' && `${idSite}` !== `${external_CoreHome_["Matomo"].idSite}`) {
// when creating a report for a different site...
// we need to reload this page for a different idsite, otherwise the report won't
// be found
external_CoreHome_["MatomoUrl"].updateUrl(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].urlParsed.value), {}, {
- idSite: idSite
+ idSite
}), Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, {
- idCustomReport: idCustomReport
+ idCustomReport
}));
return;
}
}
-
- CustomReports_store.reload().then(function () {
+ CustomReports_store.reload().then(() => {
if (external_CoreHome_["Matomo"].helper.isReportingPage()) {
external_CoreHome_["Matomo"].postEvent('updateReportingMenu');
}
-
external_CoreHome_["MatomoUrl"].updateHash(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, {
- idCustomReport: idCustomReport
+ idCustomReport
}));
- setTimeout(function () {
- _this4.showNotification(Object(external_CoreHome_["translate"])('CustomReports_ReportCreated'), response.type);
+ setTimeout(() => {
+ this.showNotification(Object(external_CoreHome_["translate"])('CustomReports_ReportCreated'), response.type);
}, 200);
});
});
},
- showPreview: function showPreview() {
+ showPreview() {
var _this$report$site, _this$report$dimensio, _this$report$metrics;
-
if (!this.isProductRevenueDependencyMet(true)) {
return;
}
-
- var idSite = (_this$report$site = this.report.site) !== null && _this$report$site !== void 0 && _this$report$site.id && this.report.site.id !== 'all' ? this.report.site.id : external_CoreHome_["Matomo"].idSite;
- var hasDimensions = ((_this$report$dimensio = this.report.dimensions) === null || _this$report$dimensio === void 0 ? void 0 : _this$report$dimensio.length) && this.report.report_type && this.report.report_type !== 'evolution';
- var dimensions = hasDimensions ? this.report.dimensions.join(',') : undefined;
- var hasMetrics = !!((_this$report$metrics = this.report.metrics) !== null && _this$report$metrics !== void 0 && _this$report$metrics.length);
- var metrics = hasMetrics ? this.report.metrics.join(',') : undefined;
- var url = external_CoreHome_["MatomoUrl"].stringify({
+ const idSite = (_this$report$site = this.report.site) !== null && _this$report$site !== void 0 && _this$report$site.id && this.report.site.id !== 'all' ? this.report.site.id : external_CoreHome_["Matomo"].idSite;
+ const hasDimensions = ((_this$report$dimensio = this.report.dimensions) === null || _this$report$dimensio === void 0 ? void 0 : _this$report$dimensio.length) && this.report.report_type && this.report.report_type !== 'evolution';
+ const dimensions = hasDimensions ? this.report.dimensions.join(',') : undefined;
+ const hasMetrics = !!((_this$report$metrics = this.report.metrics) !== null && _this$report$metrics !== void 0 && _this$report$metrics.length);
+ const metrics = hasMetrics ? this.report.metrics.join(',') : undefined;
+ const url = external_CoreHome_["MatomoUrl"].stringify({
module: 'CustomReports',
action: 'previewReport',
period: 'day',
date: 'today',
- idSite: idSite,
+ idSite,
report_type: this.report.report_type,
- dimensions: dimensions,
- metrics: metrics,
+ dimensions,
+ metrics,
segment: this.report.segment_filter || undefined
});
- var title = Object(external_CoreHome_["translate"])('CustomReports_Preview');
+ const title = Object(external_CoreHome_["translate"])('CustomReports_Preview');
window.Piwik_Popover.createPopupAndLoadUrl(url, title, 'customReportPreview');
},
- setValueHasChanged: function setValueHasChanged() {
+ setValueHasChanged() {
this.isDirty = true;
},
- addDimension: function addDimension(dimension) {
- var _this5 = this;
-
+ addDimension(dimension) {
if (!this.report || !dimension) {
return;
}
-
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this5.addDimension(dimension);
+ this.confirmReportIsLocked(() => {
+ this.addDimension(dimension);
});
return;
}
-
if (!this.report.dimensions) {
this.report.dimensions = [];
}
-
- this.report.dimensions = [].concat(Editvue_type_script_lang_ts_toConsumableArray(this.report.dimensions), [dimension]);
+ this.report.dimensions = [...this.report.dimensions, dimension];
this.setValueHasChanged();
},
- changeDimension: function changeDimension(dimension, index) {
- var _this6 = this,
- _this$report$dimensio2;
-
+ changeDimension(dimension, index) {
+ var _this$report$dimensio2;
if (!this.report || !dimension) {
return;
}
-
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this6.changeDimension(dimension, index);
+ this.confirmReportIsLocked(() => {
+ this.changeDimension(dimension, index);
});
return;
}
-
if (!((_this$report$dimensio2 = this.report.dimensions) !== null && _this$report$dimensio2 !== void 0 && _this$report$dimensio2[index])) {
return;
}
-
- this.report.dimensions = Editvue_type_script_lang_ts_toConsumableArray(this.report.dimensions);
+ this.report.dimensions = [...this.report.dimensions];
this.report.dimensions[index] = dimension;
this.setValueHasChanged();
},
- changeMetric: function changeMetric(metric, index) {
- var _this7 = this,
- _this$report$metrics2;
-
+ changeMetric(metric, index) {
+ var _this$report$metrics2;
this.dependencyAdded = false;
-
if (!this.report || !metric) {
return;
}
-
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this7.changeMetric(metric, index);
+ this.confirmReportIsLocked(() => {
+ this.changeMetric(metric, index);
});
return;
}
-
if (!((_this$report$metrics2 = this.report.metrics) !== null && _this$report$metrics2 !== void 0 && _this$report$metrics2[index])) {
return;
}
-
- this.report.metrics = Editvue_type_script_lang_ts_toConsumableArray(this.report.metrics);
+ this.report.metrics = [...this.report.metrics];
this.report.metrics[index] = metric;
this.setValueHasChanged();
this.addMetricIfMissingDependency(metric);
},
- setWebsiteChanged: function setWebsiteChanged(newValue) {
+ setWebsiteChanged(newValue) {
this.setValueHasChanged();
this.initReportOptions();
-
if (this.report.site.id === 'all' || this.report.site.id === '0' || this.report.site.id === 0) {
this.multipleSites = [];
- } else if (this.report.allowedToEdit && !this.isSiteIncludedAlready("".concat(newValue.id)) && this.multipleSites) {
+ } else if (this.report.allowedToEdit && !this.isSiteIncludedAlready(`${newValue.id}`) && this.multipleSites) {
this.multipleSites.push({
idsite: newValue.id,
name: newValue.name
});
}
},
- removeDimension: function removeDimension(index) {
- var _this8 = this;
-
+ removeDimension(index) {
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this8.removeDimension(index);
+ this.confirmReportIsLocked(() => {
+ this.removeDimension(index);
});
return;
}
-
window.$('div.ui-tooltip[role="tooltip"]:not([style*="display: none"])').remove();
-
if (index > -1) {
- this.report.dimensions = Editvue_type_script_lang_ts_toConsumableArray(this.report.dimensions);
+ this.report.dimensions = [...this.report.dimensions];
this.report.dimensions.splice(index, 1);
this.setValueHasChanged();
}
},
- addMetric: function addMetric(metric) {
- var _this9 = this;
-
+ addMetric(metric) {
this.dependencyAdded = false;
-
if (!this.report || !metric) {
return;
}
-
if (!this.report.metrics) {
this.report.metrics = [];
}
-
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this9.addMetric(metric);
+ this.confirmReportIsLocked(() => {
+ this.addMetric(metric);
});
return;
}
-
- this.report.metrics = [].concat(Editvue_type_script_lang_ts_toConsumableArray(this.report.metrics), [metric]);
+ this.report.metrics = [...this.report.metrics, metric];
this.setValueHasChanged();
this.addMetricIfMissingDependency(metric);
},
- addMetricIfMissingDependency: function addMetricIfMissingDependency(metric) {
+ addMetricIfMissingDependency(metric) {
// If the metric isn't Product Revenue or the dependency is already met, return
if (!['sum_product_revenue', 'avg_product_revenue'].includes(metric) || this.doesReportIncludeProductQuantityMetric()) {
return;
}
-
- var dependency = metric === 'avg_product_revenue' ? 'avg_ecommerce_productquantity' : 'sum_ecommerce_productquantity';
+ const dependency = metric === 'avg_product_revenue' ? 'avg_ecommerce_productquantity' : 'sum_ecommerce_productquantity';
this.addMetric(dependency);
this.dependencyAdded = true;
},
- removeMetric: function removeMetric(index) {
- var _this10 = this;
-
+ removeMetric(index) {
this.dependencyAdded = false;
-
if (this.isLocked) {
- this.confirmReportIsLocked(function () {
- _this10.removeMetric(index);
+ this.confirmReportIsLocked(() => {
+ this.removeMetric(index);
});
return;
}
-
window.$('div.ui-tooltip[role="tooltip"]:not([style*="display: none"])').remove();
-
if (index > -1) {
- this.report.metrics = Editvue_type_script_lang_ts_toConsumableArray(this.report.metrics);
+ this.report.metrics = [...this.report.metrics];
this.report.metrics.splice(index, 1);
this.setValueHasChanged();
}
},
- setReportTypeHasChanged: function setReportTypeHasChanged(newReportType) {
- var _this11 = this;
-
+ setReportTypeHasChanged(newReportType) {
if (this.report && this.isLocked) {
if (newReportType !== this.report.report_type) {
- this.confirmReportIsLocked(function () {
- _this11.report.report_type = newReportType;
-
- _this11.setValueHasChanged();
+ this.confirmReportIsLocked(() => {
+ this.report.report_type = newReportType;
+ this.setValueHasChanged();
});
}
} else {
@@ -1689,15 +1415,12 @@ function makeDefaultReport() {
this.setValueHasChanged();
}
},
- setSegmentFilterHasChanged: function setSegmentFilterHasChanged(newSegmentFilter) {
- var _this12 = this;
-
+ setSegmentFilterHasChanged(newSegmentFilter) {
if (this.report && this.isLocked) {
if (newSegmentFilter !== this.report.segment_filter) {
- this.confirmReportIsLocked(function () {
- _this12.report.segment_filter = newSegmentFilter;
-
- _this12.setValueHasChanged();
+ this.confirmReportIsLocked(() => {
+ this.report.segment_filter = newSegmentFilter;
+ this.setValueHasChanged();
});
}
} else {
@@ -1705,293 +1428,243 @@ function makeDefaultReport() {
this.setValueHasChanged();
}
},
- updateReport: function updateReport() {
- var _this13 = this;
-
+ updateReport() {
this.removeAnyReportNotification();
-
if (!this.checkRequiredFieldsAreSet()) {
return;
}
-
this.multipleIdSites = [];
-
if (this.multipleSites && this.multipleSites.length && this.report.site.id !== 'all' && this.report.site.id !== '0' && this.report.site.id !== 0) {
this.multipleIdSites.push(external_CoreHome_["Matomo"].idSite);
- this.multipleSites.forEach(function (item) {
- var idSite = item.idsite;
-
- if (!_this13.multipleIdSites.includes(idSite)) {
- _this13.multipleIdSites.push(idSite);
+ this.multipleSites.forEach(item => {
+ const idSite = item.idsite;
+ if (!this.multipleIdSites.includes(idSite)) {
+ this.multipleIdSites.push(idSite);
}
});
}
-
- var method = 'CustomReports.updateCustomReport';
-
+ const method = 'CustomReports.updateCustomReport';
if (this.multipleIdSites && this.multipleIdSites.length) {
// need to update this else this creates an issue after save
this.report.site.id = external_CoreHome_["Matomo"].idSite;
}
-
- CustomReports_store.createOrUpdateReport(this.report, method, this.childReportIds, this.multipleIdSites).then(function (response) {
+ CustomReports_store.createOrUpdateReport(this.report, method, this.childReportIds, this.multipleIdSites).then(response => {
if (!response || response.type === 'error') {
var _response$message2, _response$type2;
-
- _this13.showApiErrorMessage((_response$message2 = response.message) !== null && _response$message2 !== void 0 ? _response$message2 : '', (_response$type2 = response.type) !== null && _response$type2 !== void 0 ? _response$type2 : 'error');
-
+ this.showApiErrorMessage((_response$message2 = response.message) !== null && _response$message2 !== void 0 ? _response$message2 : '', (_response$type2 = response.type) !== null && _response$type2 !== void 0 ? _response$type2 : 'error');
return;
}
-
- var idSite = _this13.report.site.id;
- _this13.isDirty = false;
- _this13.canEdit = true;
-
- if (idSite && idSite !== 'all' && "".concat(idSite) !== "".concat(external_CoreHome_["Matomo"].idSite)) {
+ const idSite = this.report.site.id;
+ this.isDirty = false;
+ this.canEdit = true;
+ if (idSite && idSite !== 'all' && `${idSite}` !== `${external_CoreHome_["Matomo"].idSite}`) {
// when moving a report from one site to another...
// we need to reload this page for a different idsite, otherwise the report won't be found
external_CoreHome_["MatomoUrl"].updateUrl(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].urlParsed.value), {}, {
- idSite: idSite
+ idSite
}), Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value));
return;
}
-
- CustomReports_store.reload().then(function () {
- _this13.init();
+ CustomReports_store.reload().then(() => {
+ this.init();
});
-
- _this13.showNotification(Object(external_CoreHome_["translate"])('CustomReports_ReportUpdated'), response.type);
+ this.showNotification(Object(external_CoreHome_["translate"])('CustomReports_ReportUpdated'), response.type);
});
},
- checkRequiredFieldsAreSet: function checkRequiredFieldsAreSet() {
+ checkRequiredFieldsAreSet() {
var _this$report$metrics3;
-
if (!this.report.name) {
- var title = Object(external_CoreHome_["translate"])('General_Name');
+ const title = Object(external_CoreHome_["translate"])('General_Name');
this.showErrorFieldNotProvidedNotification(title);
return false;
}
-
if (this.report.report_type !== 'evolution') {
var _this$report$dimensio3;
-
if (!((_this$report$dimensio3 = this.report.dimensions) !== null && _this$report$dimensio3 !== void 0 && _this$report$dimensio3.length) || !Editvue_type_script_lang_ts_arrayFilterAndRemoveDuplicates(this.report.dimensions).length) {
- var _title = Object(external_CoreHome_["translate"])('CustomReports_ErrorMissingDimension');
-
- this.showNotification(_title, 'error');
+ const title = Object(external_CoreHome_["translate"])('CustomReports_ErrorMissingDimension');
+ this.showNotification(title, 'error');
return false;
}
}
-
if (!((_this$report$metrics3 = this.report.metrics) !== null && _this$report$metrics3 !== void 0 && _this$report$metrics3.length) || !Editvue_type_script_lang_ts_arrayFilterAndRemoveDuplicates(this.report.metrics).length) {
- var _title2 = Object(external_CoreHome_["translate"])('CustomReports_ErrorMissingMetric');
-
- this.showNotification(_title2, 'error');
+ const title = Object(external_CoreHome_["translate"])('CustomReports_ErrorMissingMetric');
+ this.showNotification(title, 'error');
return false;
- } // Don't fail validation since we automatically add the dependency
-
-
+ }
+ // Don't fail validation since we automatically add the dependency
this.isProductRevenueDependencyMet(false);
return true;
},
- setSubcategory: function setSubcategory(subcategoryId) {
+ setSubcategory(subcategoryId) {
this.report.subcategory = this.report.subcategory || {
id: ''
};
this.report.subcategory.id = subcategoryId;
},
- isProductRevenueDependencyMet: function isProductRevenueDependencyMet(shouldScrollToNotification) {
- var linkString = Object(external_CoreHome_["externalLink"])('https://matomo.org/faq/custom-reports/why-is-there-an-error-when-i-try-to-run-a-custom-report-with-the-product-revenue-metric/');
- var notificationText = Object(external_CoreHome_["translate"])('CustomReports_WarningProductRevenueMetricDependency', linkString, '');
-
+ isProductRevenueDependencyMet(shouldScrollToNotification) {
+ const linkString = Object(external_CoreHome_["externalLink"])('https://matomo.org/faq/custom-reports/why-is-there-an-error-when-i-try-to-run-a-custom-report-with-the-product-revenue-metric/');
+ const notificationText = Object(external_CoreHome_["translate"])('CustomReports_WarningProductRevenueMetricDependency', linkString, '');
if (this.report.metrics.includes('sum_product_revenue') && !this.doesReportIncludeProductQuantityMetric()) {
this.addMetric('sum_ecommerce_productquantity');
this.showProductMetricNotification(notificationText, shouldScrollToNotification);
return false;
}
-
if (this.report.metrics.includes('avg_product_revenue') && !this.doesReportIncludeProductQuantityMetric()) {
this.addMetric('avg_ecommerce_productquantity');
this.showProductMetricNotification(notificationText, shouldScrollToNotification);
return false;
}
-
return true;
},
- doesReportIncludeProductQuantityMetric: function doesReportIncludeProductQuantityMetric() {
+ doesReportIncludeProductQuantityMetric() {
return this.report.metrics.includes('sum_ecommerce_productquantity') || this.report.metrics.includes('avg_ecommerce_productquantity');
},
- isSiteIncludedAlready: function isSiteIncludedAlready(idSite) {
+ isSiteIncludedAlready(idSite) {
if (this.multipleSites && this.multipleSites.length) {
- return this.multipleSites.some(function (item) {
- return "".concat(item.idsite) === "".concat(idSite);
- });
+ return this.multipleSites.some(item => `${item.idsite}` === `${idSite}`);
}
-
return false;
},
- removeSite: function removeSite(site) {
+ removeSite(site) {
if (this.multipleSites) {
this.isDirty = true;
- this.multipleSites = this.multipleSites.filter(function (item) {
- return item.idsite !== site.idsite;
- });
+ this.multipleSites = this.multipleSites.filter(item => item.idsite !== site.idsite);
}
},
- addSitesContaining: function addSitesContaining(searchTerm) {
- var _this14 = this;
-
+ addSitesContaining(searchTerm) {
if (!searchTerm) {
return;
}
-
- var displaySearchTerm = "\"".concat(external_CoreHome_["Matomo"].helper.escape(external_CoreHome_["Matomo"].helper.htmlEntities(searchTerm)), "\"");
+ const displaySearchTerm = `"${external_CoreHome_["Matomo"].helper.escape(external_CoreHome_["Matomo"].helper.htmlEntities(searchTerm))}"`;
external_CoreHome_["AjaxHelper"].fetch({
method: 'SitesManager.getSitesWithAdminAccess',
pattern: searchTerm,
filter_limit: -1
- }).then(function (sites) {
+ }).then(sites => {
if (!sites || !sites.length) {
- var _sitesToAdd = "
${t}`:e},getDimensionsHelpTextExtended(){if(this.isCloud)return"";const e=Object(ot["externalLink"])("https://matomo.org/faq/custom-reports/faq_25655/");return Object(ot["translate"])("CustomReports_ReportDimensionsHelpExtended",e,"")}}});bt.render=tt;var vt=bt;const Ot={class:"reportSearchFilter"},jt={class:"index"},gt={class:"name"},Rt={class:"description"},yt={class:"reportType"},Ct={class:"reportCategory"},ft={class:"action"},St={colspan:"7"},Et={class:"loadingPiwik"},Nt=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),kt={colspan:"7"},Vt=["id"],wt={class:"index"},Dt={class:"name"},_t=["title"],Mt=["title"],At=["title"],Bt=["title"],Ut=["title"],It={class:"reportType"},Lt=["title"],Tt={key:0},xt=["title","onClick"],Pt=["title","onClick"],Ht=["title","onClick"],Gt=["title","href"],Ft={key:0},qt=["title","onClick"],$t={class:"tableActionBar"},Wt=Object(s["createElementVNode"])("span",{class:"icon-add"},null,-1),zt={class:"ui-confirm",ref:"confirmDeleteReport"},Yt=["value"],Qt=["value"],Xt={class:"ui-confirm",ref:"confirmPauseReport"},Jt=["value"],Kt=["value"],Zt={class:"ui-confirm",ref:"confirmResumeReport"},eo=["value"],to=["value"];function oo(e,t,o,i,r,n){const a=Object(s["resolveComponent"])("Field"),l=Object(s["resolveComponent"])("EntityDuplicatorAction"),c=Object(s["resolveComponent"])("ContentBlock"),d=Object(s["resolveComponent"])("EntityDuplicatorModal"),p=Object(s["resolveDirective"])("content-table");return Object(s["openBlock"])(),Object(s["createElementBlock"])(s["Fragment"],null,[Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(c,{"content-title":e.translate("CustomReports_ManageReports"),feature:e.translate("CustomReports_ManageReports")},{default:Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("p",null,Object(s["toDisplayString"])(e.translate("CustomReports_CustomReportIntroduction")),1),Object(s["createElementVNode"])("div",Ot,[Object(s["withDirectives"])(Object(s["createVNode"])(a,{uicontrol:"text",name:"reportSearch",title:e.translate("General_Search"),modelValue:e.searchFilter,"onUpdate:modelValue":t[0]||(t[0]=t=>e.searchFilter=t)},null,8,["title","modelValue"]),[[s["vShow"],e.reports.length>0]])]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("table",null,[Object(s["createElementVNode"])("thead",null,[Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("th",jt,Object(s["toDisplayString"])(e.translate("General_Id")),1),Object(s["createElementVNode"])("th",gt,Object(s["toDisplayString"])(e.translate("General_Name")),1),Object(s["createElementVNode"])("th",Rt,Object(s["toDisplayString"])(e.translate("General_Description")),1),Object(s["createElementVNode"])("th",yt,Object(s["toDisplayString"])(e.translate("CustomReports_Type")),1),Object(s["createElementVNode"])("th",Ct,Object(s["toDisplayString"])(e.translate("CustomReports_Category")),1),Object(s["createElementVNode"])("th",ft,Object(s["toDisplayString"])(e.translate("General_Actions")),1)])]),Object(s["createElementVNode"])("tbody",null,[Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",St,[Object(s["createElementVNode"])("span",Et,[Nt,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)])])],512),[[s["vShow"],e.isLoading||e.isUpdating]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",kt,Object(s["toDisplayString"])(e.translate("CustomReports_NoCustomReportsFound")),1)],512),[[s["vShow"],!e.isLoading&&0==e.reports.length]]),(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.sortedReports,t=>{var o;return Object(s["openBlock"])(),Object(s["createElementBlock"])("tr",{id:"report"+t.idcustomreport,class:"customReports",key:t.idcustomreport},[Object(s["createElementVNode"])("td",wt,Object(s["toDisplayString"])(t.idcustomreport),1),Object(s["createElementVNode"])("td",Dt,[Object(s["createTextVNode"])(Object(s["toDisplayString"])(t.name)+" ",1),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-locked",title:e.translate("CustomReports_ReportEditNotAllowedAllWebsitesUpdated")},null,8,_t),[[s["vShow"],!t.idsite&&!e.isSuperUser]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-info2",title:e.translate("CustomReports_ReportAvailableToAllWebsites")},null,8,Mt),[[s["vShow"],!t.idsite&&e.isSuperUser]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-locked",title:e.translate("CustomReports_ReportEditNotAllowedMultipleWebsitesAccessIssue")},null,8,At),[[s["vShow"],!t.allowedToEdit&&e.isMultiSiteReport(t)]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-info2",title:e.translate("CustomReports_ReportAvailableToMultipleWebsites")},null,8,Bt),[[s["vShow"],t.allowedToEdit&&e.isMultiSiteReport(t)]])]),Object(s["createElementVNode"])("td",{class:"description",title:e.htmlEntities(t.description)},Object(s["toDisplayString"])(e.truncate(t.description.trim(),60)),9,Ut),Object(s["createElementVNode"])("td",It,Object(s["toDisplayString"])(e.reportTypesReadable[t.report_type]),1),Object(s["createElementVNode"])("td",{class:"reportCategory",title:e.htmlEntities(t.category.name)},[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.truncate(t.category.name.trim(),60))+" ",1),null!==(o=t.subcategory)&&void 0!==o&&o.name?(Object(s["openBlock"])(),Object(s["createElementBlock"])("span",Tt," - "+Object(s["toDisplayString"])(e.truncate(t.subcategory.name.trim(),60)),1)):Object(s["createCommentVNode"])("",!0)],8,Lt),Object(s["createElementVNode"])("td",{class:Object(s["normalizeClass"])({action:!0,"duplicate-available":e.isEntityDuplicatorAvailable})},[Object(s["withDirectives"])(Object(s["createElementVNode"])("a",{class:"table-action icon-pause",title:e.translate("CustomReports_PauseReportInfo"),onClick:o=>e.pauseReport(t)},null,8,xt),[[s["vShow"],(t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit)&&"active"===t.status]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("a",{class:"table-action icon-play",title:e.translate("CustomReports_ResumeReportInfo"),onClick:o=>e.resumeReport(t)},null,8,Pt),[[s["vShow"],(t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit)&&"paused"===t.status]]),Object(s["createElementVNode"])("a",{class:"table-action icon-edit",title:e.translate("CustomReports_EditReport"),onClick:o=>e.editReport(t.idcustomreport)},null,8,Ht),Object(s["createElementVNode"])("a",{target:"_blank",class:"table-action icon-show",title:e.translate("CustomReports_ViewReportInfo"),href:e.getViewReportLink(t)},null,8,Gt),e.isEntityDuplicatorAvailable&&(t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit)?(Object(s["openBlock"])(),Object(s["createElementBlock"])("span",Ft,[Object(s["createVNode"])(l,{actionFormData:{idCustomReport:t.idcustomreport},modalStore:e.entityDuplicatorStore,isActionVisible:e.showDuplicatorAction,isActionEnabled:e.enableDuplicatorAction,tooltipTextOverrideDisabled:e.translate("CustomReports_QuotaReachedForX",e.translate("CustomReports_CustomReport"),e.translate("CustomReports_CustomReports")),extraClasses:["customreport-duplicate-action","customreport-"+t.idcustomreport]},null,8,["actionFormData","modalStore","isActionVisible","isActionEnabled","tooltipTextOverrideDisabled","extraClasses"])])):Object(s["createCommentVNode"])("",!0),Object(s["withDirectives"])(Object(s["createElementVNode"])("a",{class:"table-action icon-delete",title:e.translate("CustomReports_DeleteReportInfo"),onClick:o=>e.deleteReport(t)},null,8,qt),[[s["vShow"],t.idsite&&!e.isMultiSiteReport(t)||t.allowedToEdit]])],2)],8,Vt)}),128))])])),[[p]]),Object(s["createElementVNode"])("div",$t,[Object(s["createElementVNode"])("a",{class:"createNewReport",onClick:t[1]||(t[1]=t=>e.createReport())},[Wt,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("CustomReports_CreateNewReport")),1)])])]),_:1},8,["content-title","feature"]),Object(s["createElementVNode"])("div",zt,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("CustomReports_DeleteReportConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,Yt),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,Qt)],512),Object(s["createElementVNode"])("div",Xt,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("CustomReports_PauseReportConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,Jt),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,Kt)],512),Object(s["createElementVNode"])("div",Zt,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("CustomReports_ResumeReportConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,eo),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,to)],512)]),Object(s["createVNode"])(d,{modalStore:e.entityDuplicatorStore},null,8,["modalStore"])],64)}
+/**
+ * Copyright (C) InnoCraft Ltd - All rights reserved.
+ *
+ * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
+ * The intellectual and technical concepts contained herein are protected by trade secret
+ * or copyright law. Redistribution of this information or reproduction of this material is
+ * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
+ *
+ * You shall use this code only in accordance with the license agreement obtained from
+ * InnoCraft Ltd.
+ *
+ * @link https://www.innocraft.com/
+ * @license For license details see https://www.innocraft.com/license
+ */function io(e,t){return e&&e.length>t?e.substr(0,t-3)+"...":e}const ro=Object(ot["useExternalPluginComponent"])("CoreHome","EntityDuplicatorModal"),so=Object(ot["useExternalPluginComponent"])("CoreHome","EntityDuplicatorAction");let no=void 0;Object(ot["importPluginUmd"])("CoreHome").then(e=>{no=null===e||void 0===e?void 0:e.EntityDuplicatorStore});const ao="customreportmanagementlist";var lo=Object(s["defineComponent"])({props:{},components:{ContentBlock:ot["ContentBlock"],Field:it["Field"],EntityDuplicatorModal:ro,EntityDuplicatorAction:so},directives:{ContentTable:ot["ContentTable"]},data(){return{searchFilter:"",showDuplicatorAction:!0,enableDuplicatorAction:!1,entityDuplicatorStore:"undefined"!==typeof no?no.buildStoreInstance("CustomReports_CustomReport",{method:"CustomReports.duplicateCustomReport",requiredFields:["idSite","idDestinationSites","idCustomReport"]}):void 0}},created(){dt.getAvailableReportTypes(),dt.fetchReports()},mounted(){if(this.checkIsAddingCustomReportsAllowed(),this.entityDuplicatorStore){const e=this.entityDuplicatorStore.adapter;e.onSuccessCallback=e=>new Promise(t=>{var o,i,r;const s=null===(o=e.additionalData)||void 0===o?void 0:o.idSite;return null!==(i=e.additionalData)&&void 0!==i&&i.idSite&&Array.isArray(null===(r=e.additionalData)||void 0===r?void 0:r.idDestinationSites)&&!e.additionalData.idDestinationSites.some(e=>+e===+s)?t():dt.reload().then(()=>t())})}},methods:{createReport(){this.editReport(0)},checkIsAddingCustomReportsAllowed(){const e={isAllowed:!0};return ot["Matomo"].postEvent("CustomReports.initAddCustomReport",e),this.enableDuplicatorAction=e&&!0===e.isAllowed,!this.enableDuplicatorAction},editReport(e){ot["MatomoUrl"].updateHash(Object.assign(Object.assign({},ot["MatomoUrl"].hashParsed.value),{},{idCustomReport:e}))},pauseReport(e){ot["Matomo"].helper.modalConfirm(this.$refs.confirmPauseReport,{yes:()=>{dt.pauseReport(e.idcustomreport,e.idsite).then(e=>{e&&"error"!==e.type?(dt.reload().then(()=>{this.showNotification(this.translate("CustomReports_PausedReport"),"success")}),ot["Matomo"].postEvent("updateReportingMenu")):dt.reload()})}})},resumeReport(e){ot["Matomo"].helper.modalConfirm(this.$refs.confirmResumeReport,{yes:()=>{dt.resumeReport(e.idcustomreport,e.idsite).then(e=>{e&&"error"!==e.type?(dt.reload().then(()=>{this.showNotification(this.translate("CustomReports_ResumedReport"),"success")}),ot["Matomo"].postEvent("updateReportingMenu")):dt.reload()})}})},showNotification(e,t,o=null){const i=ot["NotificationsStore"].show({message:e,context:t,id:ao,type:null!==o?o:"toast"});setTimeout(()=>{ot["NotificationsStore"].scrollToNotification(i)},200)},deleteReport(e){ot["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteReport,{yes:()=>{dt.deleteReport(e.idcustomreport,e.idsite).then(()=>{dt.reload(),ot["Matomo"].postEvent("updateReportingMenu")})}})},getViewReportLink(e){return`?${ot["MatomoUrl"].stringify({module:"CoreHome",action:"index",idSite:e.linkIdSite,period:"day",date:"yesterday"})}#?${ot["MatomoUrl"].stringify({category:e.category.id,idSite:e.linkIdSite,date:ot["MatomoUrl"].parsed.value.date,period:ot["MatomoUrl"].parsed.value.period,segment:ot["MatomoUrl"].parsed.value.segment,subcategory:e.subcategoryLink})}`},truncate:io,htmlEntities(e){return ot["Matomo"].helper.htmlEntities(e)},isMultiSiteReport(e){return e.multiple_idsites&&e.multiple_idsites.split(",")}},computed:{isSuperUser(){return ot["Matomo"].hasSuperUserAccess},reports(){return dt.state.value.reports},sortedReports(){const e=this.searchFilter.toLowerCase(),t=[...this.reports].filter(t=>Object.keys(t).some(o=>{const i=t;return"string"===typeof i[o]&&-1!==i[o].toLowerCase().indexOf(e)}));return t.sort((e,t)=>{const o=parseInt(""+e.idcustomreport,10),i=parseInt(""+t.idcustomreport,10);return o-i}),t},isLoading(){return dt.state.value.isLoading},isUpdating(){return dt.state.value.isUpdating},reportTypesReadable(){return dt.state.value.reportTypesReadable},isEntityDuplicatorAvailable(){return"undefined"!==typeof no}}});lo.render=oo;var co=lo;const po={class:"manageReports"},mo={key:0},uo={key:1};function ho(e,t,o,i,r,n){const a=Object(s["resolveComponent"])("CustomReportsList"),l=Object(s["resolveComponent"])("CustomReportsEdit");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",po,[e.editMode?Object(s["createCommentVNode"])("",!0):(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",mo,[Object(s["createVNode"])(a)])),e.editMode?(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",uo,[Object(s["createVNode"])(l,{"id-custom-report":e.idCustomReport,"browser-archiving-disabled":e.browserArchivingDisabled,"re-archive-last-n":e.reArchiveLastN,"max-dimensions":e.maxDimensions,"is-cloud":e.isCloud},null,8,["id-custom-report","browser-archiving-disabled","re-archive-last-n","max-dimensions","is-cloud"])])):Object(s["createCommentVNode"])("",!0)])}var bo=Object(s["defineComponent"])({props:{browserArchivingDisabled:Boolean,reArchiveLastN:Number,maxDimensions:Number,isCloud:Boolean},components:{CustomReportsList:co,CustomReportsEdit:vt},data(){return{editMode:!1,idCustomReport:null}},watch:{editMode(){$(".ui-tooltip").remove()}},created(){Object(s["watch"])(()=>ot["MatomoUrl"].hashParsed.value.idCustomReport,e=>{this.initState(e)}),this.initState(ot["MatomoUrl"].hashParsed.value.idCustomReport)},methods:{removeAnyReportNotification(e=!0){ot["NotificationsStore"].remove("reportsmanagement"),e&&ot["NotificationsStore"].remove("reportsmanagementProductMetric")},initState(e){if(e){if("0"===e){const e={isAllowed:!0};if(ot["Matomo"].postEvent("CustomReports.initAddReport",e),e&&!e.isAllowed)return this.editMode=!1,void(this.idCustomReport=null)}this.editMode=!0,this.idCustomReport=parseInt(e,10)}else this.editMode=!1,this.idCustomReport=null;this.removeAnyReportNotification(!e)}}});bo.render=ho;var vo=bo;
+/**
+ * Copyright (C) InnoCraft Ltd - All rights reserved.
+ *
+ * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
+ * The intellectual and technical concepts contained herein are protected by trade secret
+ * or copyright law. Redistribution of this information or reproduction of this material is
+ * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
+ *
+ * You shall use this code only in accordance with the license agreement obtained from
+ * InnoCraft Ltd.
+ *
+ * @link https://www.innocraft.com/
+ * @license For license details see https://www.innocraft.com/license
+ */}})}));
+//# sourceMappingURL=CustomReports.umd.min.js.map
\ No newline at end of file
diff --git a/files/plugin-CustomReports-5.4.3/vue/dist/umd.metadata.json b/files/plugin-CustomReports-5.4.5/vue/dist/umd.metadata.json
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/dist/umd.metadata.json
rename to files/plugin-CustomReports-5.4.5/vue/dist/umd.metadata.json
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/CustomReports.store.ts b/files/plugin-CustomReports-5.4.5/vue/src/CustomReports.store.ts
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/CustomReports.store.ts
rename to files/plugin-CustomReports-5.4.5/vue/src/CustomReports.store.ts
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/Reports/Edit.less b/files/plugin-CustomReports-5.4.5/vue/src/Reports/Edit.less
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/Reports/Edit.less
rename to files/plugin-CustomReports-5.4.5/vue/src/Reports/Edit.less
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/Reports/Edit.vue b/files/plugin-CustomReports-5.4.5/vue/src/Reports/Edit.vue
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/Reports/Edit.vue
rename to files/plugin-CustomReports-5.4.5/vue/src/Reports/Edit.vue
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/Reports/List.less b/files/plugin-CustomReports-5.4.5/vue/src/Reports/List.less
similarity index 90%
rename from files/plugin-CustomReports-5.4.3/vue/src/Reports/List.less
rename to files/plugin-CustomReports-5.4.5/vue/src/Reports/List.less
index f32f753..0b28ef0 100644
--- a/files/plugin-CustomReports-5.4.3/vue/src/Reports/List.less
+++ b/files/plugin-CustomReports-5.4.5/vue/src/Reports/List.less
@@ -37,6 +37,10 @@
}
}
+ th.action.duplicate-available, td.action.duplicate-available {
+ width: 280px;
+ }
+
.index {
width: 60px;
}
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/Reports/List.vue b/files/plugin-CustomReports-5.4.5/vue/src/Reports/List.vue
similarity index 73%
rename from files/plugin-CustomReports-5.4.3/vue/src/Reports/List.vue
rename to files/plugin-CustomReports-5.4.5/vue/src/Reports/List.vue
index 61cacf4..910c7a9 100644
--- a/files/plugin-CustomReports-5.4.3/vue/src/Reports/List.vue
+++ b/files/plugin-CustomReports-5.4.5/vue/src/Reports/List.vue
@@ -93,7 +93,10 @@
- {{ truncate(report.subcategory.name.trim(), 60) }}
-
+
+
+
+
+
+
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/Reports/Manage.vue b/files/plugin-CustomReports-5.4.5/vue/src/Reports/Manage.vue
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/Reports/Manage.vue
rename to files/plugin-CustomReports-5.4.5/vue/src/Reports/Manage.vue
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/index.ts b/files/plugin-CustomReports-5.4.5/vue/src/index.ts
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/index.ts
rename to files/plugin-CustomReports-5.4.5/vue/src/index.ts
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/truncateText2.ts b/files/plugin-CustomReports-5.4.5/vue/src/truncateText2.ts
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/truncateText2.ts
rename to files/plugin-CustomReports-5.4.5/vue/src/truncateText2.ts
diff --git a/files/plugin-CustomReports-5.4.3/vue/src/types.ts b/files/plugin-CustomReports-5.4.5/vue/src/types.ts
similarity index 100%
rename from files/plugin-CustomReports-5.4.3/vue/src/types.ts
rename to files/plugin-CustomReports-5.4.5/vue/src/types.ts
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/API.php b/files/plugin-HeatmapSessionRecording-5.2.4/API.php
deleted file mode 100644
index 8dd9ecc..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/API.php
+++ /dev/null
@@ -1,999 +0,0 @@
-validator = $validator;
- $this->aggregator = $aggregator;
- $this->siteHsr = $siteHsr;
- $this->logHsr = $logHsr;
- $this->logEvent = $logEvent;
- $this->logHsrSite = $logHsrSite;
- $this->systemSettings = $settings;
- $this->configuration = $configuration;
-
- $dir = Plugin\Manager::getPluginDirectory('UserCountry');
- require_once $dir . '/functions.php';
- }
-
- /**
- * Adds a new heatmap.
- *
- * Once added, the system will start recording activities for this heatmap.
- *
- * @param int $idSite
- * @param string $name The name of heatmap which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1".
- * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc.
- * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken.
- * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive.
- * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive.
- * @return int
- */
- public function addHeatmap($idSite, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
-
- if ($breakpointMobile === false || $breakpointMobile === null) {
- $breakpointMobile = $this->systemSettings->breakpointMobile->getValue();
- }
-
- if ($breakpointTablet === false || $breakpointTablet === null) {
- $breakpointTablet = $this->systemSettings->breakpointTablet->getValue();
- }
-
- $createdDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
- $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl);
-
- return $this->siteHsr->addHeatmap($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $createdDate);
- }
-
- private function unsanitizeScreenshotUrl($screenshotUrl)
- {
- if (!empty($screenshotUrl) && is_string($screenshotUrl)) {
- $screenshotUrl = Common::unsanitizeInputValue($screenshotUrl);
- }
-
- return $screenshotUrl;
- }
-
- private function unsanitizePageRules($matchPageRules)
- {
- if (!empty($matchPageRules) && is_array($matchPageRules)) {
- foreach ($matchPageRules as $index => $matchPageRule) {
- if (is_array($matchPageRule) && !empty($matchPageRule['value'])) {
- $matchPageRules[$index]['value'] = Common::unsanitizeInputValue($matchPageRule['value']);
- }
- }
- }
- return $matchPageRules;
- }
-
- /**
- * Updates an existing heatmap.
- *
- * All fields need to be set in order to update a heatmap. Easiest way is to get all values for a heatmap via
- * "HeatmapSessionRecording.getHeatmap", make the needed changes on the heatmap, and send all values back to
- * "HeatmapSessionRecording.updateHeatmap".
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap you want to update.
- * @param string $name The name of heatmap which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1".
- * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc.
- * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken.
- * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive.
- * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive.
- */
- public function updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- if ($breakpointMobile === false || $breakpointMobile === null) {
- $breakpointMobile = $this->systemSettings->breakpointMobile->getValue();
- }
-
- if ($breakpointTablet === false || $breakpointTablet === null) {
- $breakpointTablet = $this->systemSettings->breakpointTablet->getValue();
- }
-
- $updatedDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
- $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl);
-
- $this->siteHsr->updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $updatedDate);
- }
-
- /**
- * Deletes / removes the screenshot from a heatmap
- * @param int $idSite
- * @param int $idSiteHsr
- * @return bool
- * @throws Exception
- */
- public function deleteHeatmapScreenshot($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr);
- if (!empty($heatmap['status']) && $heatmap['status'] === SiteHsrDao::STATUS_ACTIVE) {
- $this->siteHsr->setPageTreeMirror($idSite, $idSiteHsr, null, null);
-
- if (!empty($heatmap['page_treemirror'])) {
- // only needed when a screenshot existed before that
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
- return true;
- } elseif (!empty($heatmap['status'])) {
- throw new Exception('The screenshot can be only removed from active heatmaps');
- }
- }
-
- /**
- * Adds a new session recording.
- *
- * Once added, the system will start recording sessions.
- *
- * @param int $idSite
- * @param string $name The name of session recording which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1". Leave it empty to record any page.
- * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules.
- * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page.
- * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once.
- * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded.
- * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute.
- * @return int
- */
- public function addSessionRecording($idSite, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
-
- $createdDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- return $this->siteHsr->addSessionRecording($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $createdDate);
- }
-
- /**
- * Updates an existing session recording.
- *
- * All fields need to be set in order to update a session recording. Easiest way is to get all values for a
- * session recording via "HeatmapSessionRecording.getSessionRecording", make the needed changes on the recording,
- * and send all values back to "HeatmapSessionRecording.updateSessionRecording".
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to update.
- * @param string $name The name of session recording which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1". Leave it empty to record any page.
- * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules.
- * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page.
- * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once.
- * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded.
- * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute.
- */
- public function updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $updatedDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- $this->siteHsr->updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $updatedDate);
- }
-
- /**
- * Get a specific heatmap by its ID.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- * @return array|false
- */
- public function getHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr);
-
- return $heatmap;
- }
-
- /**
- * Get a specific session recording by its ID.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- * @return array|false
- */
- public function getSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- return $this->siteHsr->getSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Pauses the given heatmap.
- *
- * When a heatmap is paused, all the tracking will be paused until its resumed again.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function pauseHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->pauseHeatmap($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Resumes the given heatmap.
- *
- * When a heatmap is resumed, all the tracking will be enabled.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function resumeHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->resumeHeatmap($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Deletes the given heatmap.
- *
- * When a heatmap is deleted, the report will be no longer available in the API and tracked data for this
- * heatmap might be removed.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function deleteHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
-
- $this->siteHsr->deactivateHeatmap($idSite, $idSiteHsr);
- }
-
- /**
- * Ends / finishes the given heatmap.
- *
- * When you end a heatmap, the heatmap reports will be still available via API and UI but no new heatmap activity
- * will be recorded for this heatmap.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- */
- public function endHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->endHeatmap($idSite, $idSiteHsr);
- }
-
- /**
- * Pauses the given session recording.
- *
- * When a session recording is paused, all the tracking will be paused until its resumed again.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function pauseSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->pauseSessionRecording($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Resumes the given session recording.
- *
- * When a session recording is resumed, all the tracking will be enabled.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function resumeSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->resumeSessionRecording($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Deletes the given session recording.
- *
- * When a session recording is deleted, any related recordings be no longer available in the API and tracked data
- * for this session recording might be removed.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording.
- */
- public function deleteSessionRecording($idSite, $idSiteHsr)
- {
-
- $this->validator->checkSessionReportWritePermission($idSite);
-
- $this->siteHsr->deactivateSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Ends / finishes the given session recording.
- *
- * When you end a session recording, the session recording reports will be still available via API and UI but no new
- * session will be recorded anymore.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording.
- */
- public function endSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->endSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Get all available heatmaps for a specific website or app.
- *
- * It will return active as well as ended heatmaps but not any deleted heatmaps.
- *
- * @param int $idSite
- * @param bool|int $includePageTreeMirror set to 0 if you don't need the page tree mirror for heatmaps (improves performance)
- * @return array
- */
- public function getHeatmaps($idSite, $includePageTreeMirror = true)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
-
- return $this->siteHsr->getHeatmaps($idSite, !empty($includePageTreeMirror));
- }
-
- /**
- * Get all available session recordings for a specific website or app.
- *
- * It will return active as well as ended session recordings but not any deleted session recordings.
- *
- * @param int $idSite
- * @return array
- */
- public function getSessionRecordings($idSite)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
-
- return $this->siteHsr->getSessionRecordings($idSite);
- }
-
- /**
- * Returns all page views that were recorded during a particular session / visit. We do not apply segments as it is
- * used for video player when replaying sessions etc.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of a session recording
- * @param int $idVisit The visit / session id
- * @return array
- */
- private function getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date)
- {
- $timezone = Site::getTimezoneFor($idSite);
-
- // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons
- $pageviews = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment = false);
-
- $isAnonymous = Piwik::isUserIsAnonymous();
-
- foreach ($pageviews as &$pageview) {
- $pageview['server_time_pretty'] = Date::factory($pageview['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT);
-
- if ($isAnonymous) {
- unset($pageview['idvisitor']);
- } else {
- $pageview['idvisitor'] = bin2hex($pageview['idvisitor']);
- }
-
- $formatter = new Formatter();
- $pageview['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($pageview['time_on_page'] / 1000), $asSentence = true);
- }
-
- return $pageviews;
- }
-
- /**
- * Returns all recorded sessions for a specific session recording.
- *
- * To get the actual recorded data for any of the recorded sessions, call {@link getRecordedSession()}.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the session recording you want to retrieve all the recorded sessions for.
- * @param bool $segment
- * @param int $idSubtable Optional visit id if you want to get all recorded pageviews of a specific visitor
- * @return DataTable
- */
- public function getRecordedSessions($idSite, $period, $date, $idSiteHsr, $segment = false, $idSubtable = false)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $idVisit = $idSubtable;
-
- try {
- PeriodFactory::checkPeriodIsEnabled($period);
- } catch (\Exception $e) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_PeriodDisabledErrorMessage', $period));
- }
-
- if (!empty($idVisit)) {
- $recordings = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment);
- } else {
- $recordings = $this->aggregator->getRecordedSessions($idSite, $idSiteHsr, $period, $date, $segment);
- }
-
- $table = new DataTable();
- $table->disableFilter('AddColumnsProcessedMetrics');
- $table->setMetadata('idSiteHsr', $idSiteHsr);
-
- if (!empty($recordings)) {
- $table->addRowsFromSimpleArray($recordings);
- }
-
- if (empty($idVisit)) {
- $table->queueFilter(function (DataTable $table) {
- foreach ($table->getRowsWithoutSummaryRow() as $row) {
- if ($idVisit = $row->getColumn('idvisit')) {
- $row->setNonLoadedSubtableId($idVisit);
- }
- }
- });
- } else {
- $table->disableFilter('Sort');
- }
-
- if (!method_exists(SettingsServer::class, 'isMatomoForWordPress') || !SettingsServer::isMatomoForWordPress()) {
- $table->queueFilter(function (DataTable $table) use ($idSite, $idSiteHsr, $period, $date) {
- foreach ($table->getRowsWithoutSummaryRow() as $row) {
- $idLogHsr = $row->getColumn('idloghsr');
- $row->setMetadata('sessionReplayUrl', SiteHsrModel::completeWidgetUrl('replayRecording', 'idSiteHsr=' . (int) $idSiteHsr . '&idLogHsr=' . (int) $idLogHsr, $idSite, $period, $date));
- }
- });
- }
-
- $table->filter('Piwik\Plugins\HeatmapSessionRecording\DataTable\Filter\EnrichRecordedSessions');
-
- return $table;
- }
-
- /**
- * Get all activities of a specific recorded session.
- *
- * This includes events such as clicks, mouse moves, scrolls, resizes, page / HTML DOM changes, form changed.
- * It is recommended to call this API method with filter_limit = -1 to retrieve all results. It also returns
- * metadata like the viewport size the user had when it was recorded, the browser, operating system, and more.
- *
- * To see what each event type in the events property means, call {@link getEventTypes()}.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to retrieve the data for.
- * @param int $idLogHsr The id of the recorded session you want to retrieve the data for.
- * @return array
- * @throws Exception
- */
- public function getRecordedSession($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons
- $session = $this->aggregator->getRecordedSession($idLogHsr);
-
- if (empty($session['idsite']) || empty($idSite)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
-
- if ($session['idsite'] != $idSite) {
- // important otherwise can fetch any log entry!
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
-
- $session['idvisitor'] = !empty($session['idvisitor']) ? bin2hex($session['idvisitor']) : '';
-
- if (Piwik::isUserIsAnonymous()) {
- foreach (EnrichRecordedSessions::getBlockedFields() as $blockedField) {
- if (isset($session[$blockedField])) {
- $session[$blockedField] = null;
- }
- }
- }
-
-
- $configBrowserName = !empty($session['config_browser_name']) ? $session['config_browser_name'] : '';
- $session['browser_name'] = \Piwik\Plugins\DevicesDetection\getBrowserName($configBrowserName);
- $session['browser_logo'] = \Piwik\Plugins\DevicesDetection\getBrowserLogo($configBrowserName);
- $configOs = !empty($session['config_os']) ? $session['config_os'] : '';
- $session['os_name'] = \Piwik\Plugins\DevicesDetection\getOsFullName($configOs);
- $session['os_logo'] = \Piwik\Plugins\DevicesDetection\getOsLogo($configOs);
- $session['device_name'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($session['config_device_type']);
- $session['device_logo'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLogo($session['config_device_type']);
-
- if (!empty($session['config_device_model'])) {
- $session['device_name'] .= ', ' . $session['config_device_model'];
- }
-
- $session['location_name'] = '';
- $session['location_logo'] = '';
-
- if (!empty($session['location_country'])) {
- $session['location_name'] = \Piwik\Plugins\UserCountry\countryTranslate($session['location_country']);
- $session['location_logo'] = \Piwik\Plugins\UserCountry\getFlagFromCode($session['location_country']);
-
- if (!empty($session['location_region']) && $session['location_region'] != Visit::UNKNOWN_CODE) {
- $session['location_name'] .= ', ' . \Piwik\Plugins\UserCountry\getRegionNameFromCodes($session['location_country'], $session['location_region']);
- }
-
- if (!empty($session['location_city'])) {
- $session['location_name'] .= ', ' . $session['location_city'];
- }
- }
-
- $timezone = Site::getTimezoneFor($idSite);
- $session['server_time_pretty'] = Date::factory($session['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT);
-
- $formatter = new Formatter();
- $session['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($session['time_on_page'] / 1000), $asSentence = true);
-
- // we make sure to get all recorded pageviews in this session
- $serverTime = Date::factory($session['server_time']);
- $from = $serverTime->subDay(1)->toString();
- $to = $serverTime->addDay(1)->toString();
-
- $period = 'range';
- $dateRange = $from . ',' . $to;
-
- $session['events'] = $this->logEvent->getEventsForPageview($idLogHsr);
- $session['pageviews'] = $this->getRecordedPageViewsInSession($idSite, $idSiteHsr, $session['idvisit'], $period, $dateRange);
- $session['numPageviews'] = count($session['pageviews']);
-
- return $session;
- }
-
- /**
- * Deletes all recorded page views within a recorded session.
- *
- * Once a recorded session has been deleted, the replay video will no longer be available in the UI and no data
- * can be retrieved anymore via the API.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to delete the data.
- * @param int $idVisit The visitId of the recorded session you want to delete.
- */
- public function deleteRecordedSession($idSite, $idSiteHsr, $idVisit)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- // we also need to make sure the visit actually belongs to that site
- $idLogHsrs = $this->logHsr->findLogHsrIdsInVisit($idSite, $idVisit);
-
- foreach ($idLogHsrs as $idLogHsr) {
- $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr);
- }
- }
-
- /**
- * Deletes an individual page view within a recorded session.
- *
- * It only deletes one recorded session of one page view, not all recorded sessions.
- * Once a recorded page view has been deleted, the replay video will no longer be available in the UI and no data
- * can be retrieved anymore via the API for this page view.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to delete the data.
- * @param int $idLogHsr The id of the recorded session you want to delete.
- */
- public function deleteRecordedPageview($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkWritePermission($idSite);
- // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr);
- }
-
- /**
- * Get metadata for a specific heatmap like the number of samples / pageviews that were recorded or the
- * average above the fold per device type.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the heatmap you want to retrieve the meta data for.
- * @param bool|string $segment
- * @return array
- */
- public function getRecordedHeatmapMetadata($idSite, $period, $date, $idSiteHsr, $segment = false)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $samples = $this->aggregator->getRecordedHeatmapMetadata($idSiteHsr, $idSite, $period, $date, $segment);
-
- $result = array('nb_samples_device_all' => 0);
-
- foreach ($samples as $sample) {
- $result['nb_samples_device_' . $sample['device_type']] = $sample['value'];
- $result['avg_fold_device_' . $sample['device_type']] = round(($sample['avg_fold'] / LogHsr::SCROLL_ACCURACY) * 100, 1);
- $result['nb_samples_device_all'] += $sample['value'];
- }
-
- return $result;
- }
-
- /**
- * Get all activities of a heatmap.
- *
- * For example retrieve all mouse movements made by desktop visitors, or all clicks made my tablet visitors, or
- * all scrolls by mobile users. It is recommended to call this method with filter_limit = -1 to retrieve all
- * results. As there can be many results, you may want to call this method several times using filter_limit and
- * filter_offset.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the heatmap you want to retrieve the data for.
- * @param int $heatmapType To see which heatmap types can be used, call {@link getAvailableHeatmapTypes()}
- * @param int $deviceType To see which device types can be used, call {@link getAvailableDeviceTypes()}
- * @param bool|string $segment
- * @return array
- */
- public function getRecordedHeatmap($idSite, $period, $date, $idSiteHsr, $heatmapType, $deviceType, $segment = false)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- if ($heatmapType == RequestProcessor::EVENT_TYPE_SCROLL) {
- $heatmap = $this->aggregator->aggregateScrollHeatmap($idSiteHsr, $deviceType, $idSite, $period, $date, $segment);
- } else {
- $heatmap = $this->aggregator->aggregateHeatmap($idSiteHsr, $heatmapType, $deviceType, $idSite, $period, $date, $segment);
- }
-
- // we do not return dataTable here as it doubles the time it takes to call this method (eg 4s vs 7s when heaps of data)
- // datatable is not really needed here as we don't want to sort it or so
- return $heatmap;
- }
-
- /**
- * @param $idSite
- * @param $idSiteHsr
- * @param $idLogHsr
- * @return array
- * @hide
- */
- public function getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
-
- $aggregator = new Aggregator();
- return $aggregator->getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr);
- }
-
- /**
- * Tests, checks whether the given URL matches the given page rules.
- *
- * This can be used before configuring a heatmap or session recording to make sure the configured target page(s)
- * will match a specific URL.
- *
- * @param string $url
- * @param array $matchPageRules
- * @return array
- * @throws Exception
- */
- public function testUrlMatchPages($url, $matchPageRules = array())
- {
- $this->validator->checkHasSomeWritePermission();
-
- if ($url === '' || $url === false || $url === null) {
- return array('url' => '', 'matches' => false);
- }
-
- if (!empty($matchPageRules) && !is_array($matchPageRules)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorNotAnArray', 'matchPageRules'));
- }
-
- $url = Common::unsanitizeInputValue($url);
-
- if (!empty($matchPageRules)) {
- $pageRules = new PageRules($matchPageRules, '', $needsOneEntry = false);
- $pageRules->check();
- }
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- $allMatch = HsrMatcher::matchesAllPageRules($matchPageRules, $url);
-
- return array('url' => $url, 'matches' => $allMatch);
- }
-
- /**
- * Get a list of valid heatmap and session recording statuses (eg "active", "ended")
- *
- * @return array
- */
- public function getAvailableStatuses()
- {
- $this->validator->checkHasSomeWritePermission();
-
- return array(
- array('value' => SiteHsrDao::STATUS_ACTIVE, 'name' => Piwik::translate('HeatmapSessionRecording_StatusActive')),
- array('value' => SiteHsrDao::STATUS_ENDED, 'name' => Piwik::translate('HeatmapSessionRecording_StatusEnded')),
- );
- }
-
- /**
- * Get a list of all available target attributes and target types for "pageTargets" / "page rules".
- *
- * For example URL, URL Parameter, Path, simple comparison, contains, starts with, and more.
- *
- * @return array
- */
- public function getAvailableTargetPageRules()
- {
- $this->validator->checkHasSomeWritePermission();
-
- return PageRuleMatcher::getAvailableTargetTypes();
- }
-
- /**
- * Get a list of available device types that can be used when fetching a heatmap report.
- *
- * For example desktop, tablet, mobile.
- *
- * @return array
- */
- public function getAvailableDeviceTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array('name' => Piwik::translate('General_Desktop'),
- 'key' => LogHsr::DEVICE_TYPE_DESKTOP,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/desktop.png'),
- array('name' => Piwik::translate('DevicesDetection_Tablet'),
- 'key' => LogHsr::DEVICE_TYPE_TABLET,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/tablet.png'),
- array('name' => Piwik::translate('General_Mobile'),
- 'key' => LogHsr::DEVICE_TYPE_MOBILE,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/smartphone.png'),
- );
- }
-
- /**
- * Get a list of available heatmap types that can be used when fetching a heatmap report.
- *
- * For example click, mouse move, scroll.
- *
- * @return array
- */
- public function getAvailableHeatmapTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'),
- 'key' => RequestProcessor::EVENT_TYPE_CLICK),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'),
- 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL),
- );
- }
-
- /**
- * Get a list of available session recording sample limits.
- *
- * Note: This is only a suggested list of sample limits that should be shown in the UI when creating or editing a
- * session recording. When you configure a session recording via the API directly, any limit can be used.
- *
- * For example 50, 100, 200, 500
- *
- * @return array
- */
- public function getAvailableSessionRecordingSampleLimits()
- {
- $this->validator->checkHasSomeWritePermission();
- $this->validator->checkSessionRecordingEnabled();
-
- return $this->configuration->getSessionRecordingSampleLimits();
- }
-
- /**
- * Get a list of available event types that may be returned eg when fetching a recorded session.
- *
- * @return array
- */
- public function getEventTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'),
- 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'),
- 'key' => RequestProcessor::EVENT_TYPE_CLICK),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityResize'),
- 'key' => RequestProcessor::EVENT_TYPE_RESIZE),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityInitialDom'),
- 'key' => RequestProcessor::EVENT_TYPE_INITIAL_DOM),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityPageChange'),
- 'key' => RequestProcessor::EVENT_TYPE_MUTATION),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormText'),
- 'key' => RequestProcessor::EVENT_TYPE_FORM_TEXT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormValue'),
- 'key' => RequestProcessor::EVENT_TYPE_FORM_VALUE),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScrollElement'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL_ELEMENT),
- );
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md b/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md
deleted file mode 100644
index a32f012..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md
+++ /dev/null
@@ -1,452 +0,0 @@
-## Changelog
-
-5.2.4 - 2025-06-09
-- Started showing the troubleshooting link even when no heatmap sample has been recorded
-- Do not crash when displaying a heatmap for a page with invalid HTML
-
-5.2.3 - 2025-01-20
-- Added an activity for pause and resume action
-- Added a troubleshooting FAQ link for heatmaps
-
-5.2.2 - 2024-12-16
-- Fixes PHP deprecation warnings
-
-5.2.1 - 2024-12-02
-- Added activities to track deleting recorded sessions and page views
-
-5.2.0 - 2024-11-04
-- Implemented a tooltip which displays click count and rate
-
-5.1.8 - 2024-10-17
-- Fixes excluded_elements not working for escaped values for a heatmap
-
-5.1.7 - 2024-10-11
-- Fixes classes with word script being removed due to xss filtering
-
-5.1.6 - 2024-08-26
-- Pricing updated
-
-5.1.5
-- Added cover image for marketplace
-
-5.1.4
-- Fixes captureInitialDom not working for single heatmap
-
-5.1.3
-- Added code to disable matomo.js file writable check code for Matomo Cloud
-
-5.1.2
-- Added code to alert if matomo.js is not writable
-
-5.1.1
-- Fixed applying segment returns error for SessionRecording
-
-5.1.0
-- Added an option to capture Heatmap DOM on demand
-
-5.0.10
-- Added total actions column in Session Recording listing page
-
-5.0.9
-- Added code to keep playing on resize event
-- Added code to update Translation keys via event
-
-5.0.8
-- Changes for README.md
-- Fixed an error that occurs when viewing posts that have heatmaps associated in WordPress.
-
-5.0.7
-- Fixed issue where form fields that were supposed to be unmasked weren't
-- Added code to pause/resume heatmap for Matomo Cloud
-
-5.0.6
-- Fixed input[type="button"] background being ignored
-- Added code to display AdBlocker banner when detected
-
-5.0.5
-- Fixed regression where good configs were disabled
-
-5.0.4
-- Fixed location provider not loading for cloud customers
-
-5.0.3
-- Fixed error when location provider is null
-
-5.0.2
-- Added option to fire heatmap/session recording only for certain geographies
-
-5.0.1
-- Compatibility with Matomo 5.0.0-b4
-
-5.0.0
-- Compatibility with Matomo 5
-
-4.5.10
-- Started skipping deletion of heatmap and session recordings for proxysite
-
-4.5.9
-- Started hiding period selector when viewing heatmaps
-
-4.5.8
-- Fixed scroll data not displaying correctly due to sort missing
-
-4.5.7
-- Fixed deprecation warnings for PHP 8.1
-
-4.5.6
-- Changed time_on_page column to BIGINT for new installation for log_hsr and log_hsr_event table
-
-4.5.5
-- Fixed session recording not masking image with `data-matomo-mask` attribute set on parent node
-
-4.5.4
-- Fixed unmasking issue for text-node elements
-- Fixed recording to not end on tabs switch
-
-4.5.3
-- Added support to pass media attribute if present for external stylesheets
-
-4.5.2
-- Made regex to work consistently, #PG-373
-- Added examples of possible xss from portswigger.net
-
-4.5.1
-- Fixed mutation id bug to load css from DB
-
-4.5.0
-- Starting migrating AngularJS to Vue.
-- Migrated view code to VueJs
-- Updated code to respect max execution time during Archiving
-
-4.4.3
-- Added code to remove attributes with possible XSS values
-
-4.4.2
-- Added support for lazy loaded images
-
-4.4.1
-- Fixed masking issue for dynamically added DOM elements
-
-4.4.0
-- Added option to disable heatmap independently
-- Stopped showing visitor profile icon in session recording when visitor profile is disabled
-
-4.3.1
-- Fixed recorded session link not working for segmented logs in visit action
-
-4.3.0
-- Started storing CSS content in DB
-- Fixed range error when range is disabled
-
-4.2.1
-- Fixed double encoded segments
-
-4.2.0
-- Fixed heatmap not triggering when tracker configured directly.
-- Added masking for images with height and width
-- Added masking for [input type="image"]
-- Fixed non-masking bug for child elements with data-matomo-unmask
-
-4.1.2
-- Fix to record inputs with data-matomo-unmask
-
-4.1.1
-- Removed masking for input type button, submit and reset
-
-4.1.0
-- Added option to disable session recording independently
-
-4.0.14
-- Support Matomo's new content security policy header
-
-4.0.13
-- Fix sharing a session might not work anymore with latest Matomo version
-
-4.0.12
-- Ensure configs.php is loaded correctly with multiple trackers
-- Translation updates
-
-4.0.11
-- Improve handling of attribute changes
-- Add translations for Czech, Dutch & Portuguese
-
-4.0.10
-- Further improvements for loading for iframes
-
-4.0.9
-- Improve loading for iframes
-
-4.0.8
-- Improve tracking react pages
-
-4.0.7
-- Add category help texts
-- Increase possible sample limit
-- jQuery 3 compatibility for WP
-
-4.0.6
-- Performance improvements
-
-4.0.4
-- Compatibility with Matomo 4.X
-
-4.0.3
-- Compatibility with Matomo 4.X
-
-4.0.2
-- Compatibility with Matomo 4.X
-
-4.0.1
-- Handle base URLs better
-
-4.0.0
-- Compatibility with Matomo 4.X
-
-3.2.39
-- Better handling for base URL
-
-3.2.38
-- Improve SPA tracking
-
-3.2.37
-- Improve sorting of server time
-
-3.2.36
-- Fix number of recorded pages may be wrong when a segment is applied
-
-3.2.35
-- Improve widgetize feature when embedded as iframe
-
-3.2.34
-- Further improvements for WordPress
-
-3.2.33
-- Improve compatibilty with WordPress
-
-3.2.32
-- Improve checking for number of previously recorded sessions
-
-3.2.31
-- Matomo for WordPress support
-
-3.2.30
-- Send less tracking requests by queueing more requests together
-
-3.2.29
-- Use DB reader in Aggregator for better compatibility with Matomo 3.12
-
-3.2.28
-- Improvements for Matomo 3.12 to support faster segment archiving
-- Better support for single page applications
-
-3.2.27
- - Show search box for entities
- - Support usage of a reader DB when configured
-
-3.2.26
- - Tracker improvements
-
-3.2.25
- - Tracker improvements
-
-3.2.24
- - Generate correct session recording link when a visitor matches multiple recordings in the visitor log
-
-3.2.23
- - Internal tracker performance improvements
-
-3.2.22
- - Add more translations
- - Tracker improvements
- - Internal changes
-
-3.2.21
- - title-text of JavaScript Tracking option help box shows HTML
- - Add primary key to log_event table for new installs (existing users should receive the update with Matomo 4)
-
-3.2.20
- - Fix tracker may under circumstances not enable tracking after disabling it manually
-
-3.2.19
- - Add possibility to delete an already taken heatmap screenshot so it can be re-taken
-
-3.2.18
- - Performance improvements for high traffic websites
-
-3.2.17
- - Add possibility to define alternative CSS file through `data-matomo-href`
- - Added new API method `HeatmapSessionRecording.deleteHeatmapScreenshot` to delete an already taken heatmap screenshot
- - Add possibility to delete an already taken heatmap screenshot so it can be re-taken
-
-3.2.16
- - Add useDateUrl=0 to default Heatmap export URL so it can be used easier
-
-3.2.15
- - Support a URL parameter &useDateUrl=1 in exported heatmaps to fetch heatmaps only for a specific date range
-
-3.2.14
- - Improve compatibility with tag manager
- - Fix possible notice when matching url array parameters
- - Add command to remove a stored heatmap
-
-3.2.13
- - Fix some coordinate cannot be calculated for SVG elements
- - Added more languages
- - Use new brand colors
- - If time on page is too high, abort the tracking request
-
-3.2.12
- - Update tracker file
-
-3.2.11
- - Add possibility to mask images
-
-3.2.10
- - Make sure to replay scrolling in element correctly
-
-3.2.9
- - Change min height of heatmaps to 400 pixels.
-
-3.2.8
- - When widgetizing the session player it bursts out of the iframe
- - Log more debug information in tracker
- - Use API calls instead of model
-
-3.2.7
- - Support new "Write" role
-
-3.2.6
- - Improve compatibility with styled-components and similar projects
- - Add possibility to not record mouse and touch movements.
-
-3.2.5
- - Compatibility with SiteUrlTrackingID plugin
- - Ensure selectors are generated correctly
-
-3.2.4
- - Allow users to pass sample limit of zero for unlimited recordings
- - Show which page view within a session is currently being replayed
-
-3.2.3
- - In configs.php return a 403 if Matomo is not installed yet
-
-3.2.2
- - Validate an entered regular expression when configuring a heatmap or session recording
- - Improve heatmap rendering of sharepoint sites
-
-3.2.1
- - Improve the rendering of heatmaps and session recordings
-
-3.2.0
- - Optimize tracker cache file
- - Prevent recording injected CSS resources that only work on a visitors' computer such as Kaspersky Antivirus CSS.
- - For better GDPR compliance disable capture keystroke in sessions by default.
- - Added logic to support Matomo GDPR features
- - Only specifically whitelisted form fields can now be recorded in plain text
- - Some form fields that could potentially include personal information such as an address will be always masked and anonymized
- - Trim any whitespace when configuring target pages
-
-3.1.9
- - Support new attribute `data-matomo-mask` which works similar to `data-piwik-mask` but additionally allows to mask content of elements.
-
-3.1.8
- - Support new CSS rendering classes matomoHsr, matomoHeatmap and matomoSessionRecording
- - For input text fields prefer a set value on the element directly
- - Differentiate between scrolling of the window and scrolling within an element (part of the window)
- - Replay in the recorded session when a user is scrolling within an element
-
-3.1.7
- - Make sure validating URL works correctly with HTML entities
- - Prevent possible fatal error when opening manage screen for all websites
-
-3.1.6
- - Renamed Piwik to Matomo
-
-3.1.5
- - Fix requested stylesheet URLs were requested lowercase when using a relative base href in the recorded page
- - Show more accurate time on page and record pageviews for a longer period in case a user is not active right away.
-
-3.1.4
- - Prevent target rules in heatmap or session recording to visually disappear under circumstances when not using the cancel or back button.
- - Respect URL prefix (eg www.) when replaying a session recording, may fix some displaying issues if website does not work without www.
- - Improved look of widgetized session recording
-
-3.1.3
- - Make Heatmap & Session Recording compatible with canvas and webgl libraries like threejs and earcut
- - Better detected of the embedded heatmap height
- - Fix scroll heatmap did not paint the last scroll section correctly
- - It is now possible to configure the sample limits in the config via `[HeatmapSessionRecording] session_recording_sample_limits = 50,100,...`
-
-3.1.2
- - Added URL to view heatmap and to replay a session recording to the API response
- - Fix widgetized URL for heatmaps and sessions redirected to another page when authenticated via token_auth
-
-3.1.1
- - Better error code when a site does not exist
- - Fix configs.php may fail if plugins directory is a symlink
- - Available sessions are now also displayed in the visitor profile
-
-3.1.0
- - Added autoplay feature for page views within a visit
- - Added possibility to change replay speed
- - Added possibility to skip long pauses in a session recording automatically
- - Better base URL detection in case a relative base URL is used
-
-3.0.15
- - Fix only max 100 heatmaps or session recordings were shown when managing them for a specific site.
- - Mask closing body in embedded page so it won't be replaced by some server logic
-
-3.0.14
- - Make sure to find all matches for a root folder when "equals simple" is used
-
-3.0.13
- - Fix a custom set based URL was ignored.
-
-3.0.12
- - Fix session recording stops when a user changes a file form field because form value is not allowed to be changed.
-
-3.0.11
- - Improve the performance of a DB query of a daily task when cleaning up blob entries.
-
-3.0.10
- - Improve the performance of a DB query of a daily task
- - Respect the new config setting `enable_internet_features` in the system check
-
-3.0.9
- - Make sure page rules work fine when using HTML entities
-
-3.0.8
- - Fix possible notice when tracking
- - Avoid some logs in chrome when viewing a heatmaps or session recordings
- - Always prefer same protocol when replaying sessions as currently used
-
-3.0.7
- - When using an "equals exactly" comparison, ignore a trailing slash when there is no path set
- - Let users customize if the tracking code should be included only when active records are configured
-
-3.0.6
- - Fix link to replay session in visitor log may not work under circumstances
-
-3.0.5
- - More detailed "no data message" when nothing has been recorded yet
- - Fix select fields were not recorded
-
-3.0.4
- - Only add tracker code when heatmap or sessions are actually active in any site
- - Added index on site_hsr table
- - Add custom stylesheets for custom styling
-
-3.0.3
- - Add system check for configs.php
- - On install, if .htaccess was not created, create the file manually
-
-3.0.2
- - Enrich system summary widget
- - Show an arrow instead of a dash between entry and exit url
- - Added some German translations
-
-3.0.1
- - Updated translations
-
-3.0.0
- - Heatmap & Session Recording for Piwik 3
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php b/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php
deleted file mode 100644
index f9f7059..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php
+++ /dev/null
@@ -1,886 +0,0 @@
-register_route('HeatmapSessionRecording', 'getHeatmap');
- $api->register_route('HeatmapSessionRecording', 'getHeatmaps');
- $api->register_route('HeatmapSessionRecording', 'getRecordedHeatmapMetadata');
- $api->register_route('HeatmapSessionRecording', 'getRecordedHeatmap');
-
- $api->register_route('HeatmapSessionRecording', 'getSessionRecording');
- $api->register_route('HeatmapSessionRecording', 'getSessionRecordings');
- $api->register_route('HeatmapSessionRecording', 'getRecordedSessions');
- $api->register_route('HeatmapSessionRecording', 'getRecordedSession');
- });
-
- /**
- * @param array $actions
- * @param \WP_Post $post
- *
- * @return mixed
- */
- function add_new_heat_map_link($actions, $post)
- {
- if (
- !$post
- || !is_plugin_active('matomo/matomo.php')
- || !current_user_can('write_matomo')
- ) {
- return $actions;
- }
-
- if ($post->post_status !== 'publish') {
- // the permalink url wouldn't be correct yet for unpublished post
- return $actions;
- }
-
- $postUrl = get_permalink($post);
- $rules = array(array(
- 'attribute' => 'url',
- 'type' => 'equals_simple',
- 'inverted' => 0,
- 'value' => $postUrl
- ));
-
- $hsrParams = array(
- 'idSite' => 1,
- 'idSiteHsr' => 0,
- 'name' => $post->post_title,
- // Encoded to avoid pitfalls of decoding multi-dimensional array URL params in JavaScript
- 'matchPageRules' => json_encode($rules)
- );
-
- $url = Menu::get_matomo_reporting_url(
- 'HeatmapSessionRecording_Heatmaps',
- 'HeatmapSessionRecording_ManageHeatmaps',
- $hsrParams
- );
-
- $actions['create_heatmap'] = 'Create Heatmap';
- return $actions;
- }
-
- function get_matomo_heatmaps()
- {
- static $heatmaps_cached;
-
- global $wpdb;
-
- if (!isset($heatmaps_cached)) {
- $site = new Site();
- $idsite = $site->get_current_matomo_site_id();
-
- if (!$idsite) {
- $heatmaps_cached = array(); // prevent it not being executed again
- } else {
- $wpDbSettings = new \WpMatomo\Db\Settings();
- $tableName = $wpDbSettings->prefix_table_name('site_hsr');
- $idsite = (int) $idsite;// needed cause we don't bind parameters below
-
- $heatmaps_cached = $wpdb->get_results(
- "select * from $tableName WHERE record_type = 1 AND idsite = $idsite AND status != 'deleted'",
- ARRAY_A
- );
- }
- }
- return $heatmaps_cached;
- }
-
- /**
- * @param array $actions
- * @param \WP_Post $post
- *
- * @return mixed
- */
- function add_view_heat_map_link($actions, $post)
- {
- if (
- !$post
- || !is_plugin_active('matomo/matomo.php')
- || !current_user_can('write_matomo')
- ) {
- return $actions;
- }
-
- $heatmaps = get_matomo_heatmaps();
-
- if (empty($heatmaps)) {
- return $actions;
- }
-
- $postUrl = get_permalink($post);
-
- if (!$postUrl) {
- return $actions;
- }
-
- if (class_exists(Bootstrap::class)) {
- Bootstrap::do_bootstrap();
- }
-
- require_once('Tracker/PageRuleMatcher.php');
- require_once('Tracker/HsrMatcher.php');
-
- $heatmaps = array_values(array_filter($heatmaps, function ($heatmap) use ($postUrl) {
- $systemSettings = StaticContainer::get(SystemSettings::class);
- $includedCountries = $systemSettings->getIncludedCountries();
- return HsrMatcher::matchesAllPageRules(json_decode($heatmap['match_page_rules'], true), $postUrl) && HsrMatcher::isIncludedCountry($includedCountries);
- }));
-
- $numMatches = count($heatmaps);
- foreach ($heatmaps as $i => $heatmap) {
- $url = Menu::get_matomo_reporting_url(
- 'HeatmapSessionRecording_Heatmaps',
- $heatmap['idsitehsr'],
- array()
- );
- $linkText = 'View Heatmap';
- if ($numMatches > 1) {
- $linkText .= ' #' . ($i + 1);
- }
- $actions['view_heatmap_' . $i] =
- '' . esc_html($linkText) . '';
- }
-
- return $actions;
- }
-}
-
-class HeatmapSessionRecording extends \Piwik\Plugin
-{
- public const EMBED_SESSION_TIME = 43200; // half day in seconds
- public const ULR_PARAM_FORCE_SAMPLE = 'pk_hsr_forcesample';
- public const ULR_PARAM_FORCE_CAPTURE_SCREEN = 'pk_hsr_capturescreen';
- public const EMBED_SESSION_NAME = 'HSR_EMBED_SESSID';
-
- public const TRACKER_READY_HOOK_NAME = '/*!! hsrTrackerReadyHook */';
- public const TRACKER_READY_HOOK_NAME_WHEN_MINIFIED = '/*!!! hsrTrackerReadyHook */';
-
- public function registerEvents()
- {
- return array(
- 'Db.getActionReferenceColumnsByTable' => 'addActionReferenceColumnsByTable',
- 'Tracker.Cache.getSiteAttributes' => 'addSiteTrackerCache',
- 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
- 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
- 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
- 'Template.jsGlobalVariables' => 'addJsGlobalVariables',
- 'Category.addSubcategories' => 'addSubcategories',
- 'SitesManager.deleteSite.end' => 'onDeleteSite',
- 'Tracker.PageUrl.getQueryParametersToExclude' => 'getQueryParametersToExclude',
- 'Widget.addWidgetConfigs' => 'addWidgetConfigs',
- 'System.addSystemSummaryItems' => 'addSystemSummaryItems',
- 'API.HeatmapSessionRecording.addHeatmap.end' => 'updatePiwikTracker',
- 'API.HeatmapSessionRecording.addSessionRecording.end' => 'updatePiwikTracker',
- 'CustomJsTracker.shouldAddTrackerFile' => 'shouldAddTrackerFile',
- 'Updater.componentUpdated' => 'installHtAccess',
- 'Live.visitorLogViewBeforeActionsInfo' => 'visitorLogViewBeforeActionsInfo',
- 'Widgetize.shouldEmbedIframeEmpty' => 'shouldEmbedIframeEmpty',
- 'Session.beforeSessionStart' => 'changeSessionLengthIfEmbedPage',
- 'TwoFactorAuth.requiresTwoFactorAuthentication' => 'requiresTwoFactorAuthentication',
- 'API.getPagesComparisonsDisabledFor' => 'getPagesComparisonsDisabledFor',
- 'CustomJsTracker.manipulateJsTracker' => 'disableHeatmapsDefaultIfNeeded',
- 'AssetManager.addStylesheets' => [
- 'function' => 'addStylesheets',
- 'after' => true,
- ],
- 'Db.getTablesInstalled' => 'getTablesInstalled'
- );
- }
-
- public function disableHeatmapsDefaultIfNeeded(&$content)
- {
- $settings = StaticContainer::get(SystemSettings::class);
- if ($settings->disableTrackingByDefault->getValue()) {
- $replace = 'Matomo.HeatmapSessionRecording._setDisabled();';
- } else {
- $replace = '';
- }
-
- $content = str_replace(array(self::TRACKER_READY_HOOK_NAME_WHEN_MINIFIED, self::TRACKER_READY_HOOK_NAME), $replace, $content);
- }
-
- /**
- * Register the new tables, so Matomo knows about them.
- *
- * @param array $allTablesInstalled
- */
- public function getTablesInstalled(&$allTablesInstalled)
- {
- $allTablesInstalled[] = Common::prefixTable('log_hsr');
- $allTablesInstalled[] = Common::prefixTable('log_hsr_blob');
- $allTablesInstalled[] = Common::prefixTable('log_hsr_event');
- $allTablesInstalled[] = Common::prefixTable('log_hsr_site');
- $allTablesInstalled[] = Common::prefixTable('site_hsr');
- }
-
- public static function getPathPrefix()
- {
- $webRootDirs = Manager::getInstance()->getWebRootDirectoriesForCustomPluginDirs();
- if (!empty($webRootDirs['HeatmapSessionRecording'])) {
- $baseUrl = trim($webRootDirs['HeatmapSessionRecording'], '/');
- } else {
- $baseUrl = 'plugins';
- }
- return $baseUrl;
- }
-
- public static function isMatomoForWordPress()
- {
- return defined('ABSPATH') && function_exists('add_action');
- }
-
- public function addStylesheets(&$mergedContent)
- {
- if (self::isMatomoForWordPress()) {
- // we hide this icon since it uses the widgetize feature which is disabled in WordPress
- $mergedContent .= '.manageHsr .action .icon-show { display: none; }';
- }
- }
- public function getPagesComparisonsDisabledFor(&$pages)
- {
- $pages[] = 'HeatmapSessionRecording_Heatmaps.*';
- $pages[] = 'HeatmapSessionRecording_SessionRecordings.*';
- }
-
- public function addJsGlobalVariables()
- {
- $idSite = Common::getRequestVar('idSite', 0, 'int');
-
- if ($idSite > 0 && Piwik::isUserHasWriteAccess($idSite)) {
- echo 'piwik.heatmapWriteAccess = true;';
- } else {
- echo 'piwik.heatmapWriteAccess = false;';
- }
- }
-
- public function requiresTwoFactorAuthentication(&$requiresAuth, $module, $action, $parameters)
- {
- if ($module == 'HeatmapSessionRecording' && $action === 'embedPage') {
- $requiresAuth = false;
- }
- }
-
- public function shouldEmbedIframeEmpty(&$shouldEmbedEmpty, $controllerName, $actionName)
- {
- if ($controllerName == 'HeatmapSessionRecording' && ($actionName == 'replayRecording' || $actionName == 'embedPage')) {
- $shouldEmbedEmpty = true;
- }
- }
-
- /**
- * Fallback to add play session link for Matomo < 3.1.0
- *
- * NOTE: TO BE REMOVED EG FROM FEBRUARY OR MARCH 2018
- *
- * @param string $out
- * @param Row $visitor
- */
- public function visitorLogViewBeforeActionsInfo(&$out, $visitor)
- {
- if (class_exists('\\Piwik\\Plugins\\Live\\VisitorDetailsAbstract')) {
- return;
- }
-
- $idVisit = $visitor->getColumn('idVisit');
- $idSite = (int) $visitor->getColumn('idSite');
-
- if (empty($idSite) || empty($idVisit) || !$this->getValidator()->canViewSessionReport($idSite)) {
- return;
- }
-
- $aggregator = new Aggregator();
- $recording = $aggregator->findRecording($idVisit);
- if (!empty($recording['idsitehsr'])) {
- $title = Piwik::translate('HeatmapSessionRecording_ReplayRecordedSession');
- $out .= ' ' . $title . ' ';
- }
- }
-
- public function shouldAddTrackerFile(&$shouldAdd, $pluginName)
- {
- if ($pluginName === 'HeatmapSessionRecording') {
- $config = new Configuration();
-
- $siteHsrDao = $this->getSiteHsrDao();
- if ($config->shouldOptimizeTrackingCode() && !$siteHsrDao->hasActiveRecordsAcrossSites()) {
- // saves requests to configs.php while no heatmap or session recording configured.
- $shouldAdd = false;
- }
- }
- }
-
- public function updatePiwikTracker()
- {
- if (Plugin\Manager::getInstance()->isPluginActivated('CustomJsTracker')) {
- $trackerUpdater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater');
- if (!empty($trackerUpdater)) {
- $trackerUpdater->update();
- }
- }
- }
-
- public function addSystemSummaryItems(&$systemSummary)
- {
- $dao = $this->getSiteHsrDao();
- $numHeatmaps = $dao->getNumRecordsTotal(SiteHsrDao::RECORD_TYPE_HEATMAP);
- $numSessions = $dao->getNumRecordsTotal(SiteHsrDao::RECORD_TYPE_SESSION);
-
- $systemSummary[] = new SystemSummary\Item(
- $key = 'heatmaps',
- Piwik::translate('HeatmapSessionRecording_NHeatmaps', $numHeatmaps),
- $value = null,
- array('module' => 'HeatmapSessionRecording', 'action' => 'manageHeatmap'),
- $icon = 'icon-drop',
- $order = 6
- );
- $systemSummary[] = new SystemSummary\Item(
- $key = 'sessionrecordings',
- Piwik::translate('HeatmapSessionRecording_NSessionRecordings', $numSessions),
- $value = null,
- array('module' => 'HeatmapSessionRecording', 'action' => 'manageSessions'),
- $icon = 'icon-play',
- $order = 7
- );
- }
-
- public function getQueryParametersToExclude(&$parametersToExclude)
- {
- // these are used by the tracker
- $parametersToExclude[] = self::ULR_PARAM_FORCE_CAPTURE_SCREEN;
- $parametersToExclude[] = self::ULR_PARAM_FORCE_SAMPLE;
- }
-
- public function onDeleteSite($idSite)
- {
- $model = $this->getSiteHsrModel();
- $model->deactivateRecordsForSite($idSite);
- }
-
- private function getSiteHsrModel()
- {
- return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel');
- }
-
- private function getValidator()
- {
- return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator');
- }
-
- public function addWidgetConfigs(&$configs)
- {
- $idSite = Common::getRequestVar('idSite', 0, 'int');
-
- if (!$this->getValidator()->canViewHeatmapReport($idSite)) {
- return;
- }
-
- $heatmaps = $this->getHeatmaps($idSite);
-
- foreach ($heatmaps as $heatmap) {
- $widget = new WidgetConfig();
- $widget->setCategoryId('HeatmapSessionRecording_Heatmaps');
- $widget->setSubcategoryId($heatmap['idsitehsr']);
- $widget->setModule('HeatmapSessionRecording');
- $widget->setAction('showHeatmap');
- $widget->setParameters(array('idSiteHsr' => $heatmap['idsitehsr']));
- $widget->setIsNotWidgetizable();
- $configs[] = $widget;
- }
- }
-
- public function addSubcategories(&$subcategories)
- {
- $idSite = Common::getRequestVar('idSite', 0, 'int');
-
- if (empty($idSite)) {
- // fallback for eg API.getReportMetadata which uses idSites
- $idSite = Common::getRequestVar('idSites', 0, 'int');
-
- if (empty($idSite)) {
- return;
- }
- }
-
- if ($this->getValidator()->canViewHeatmapReport($idSite)) {
- $heatmaps = $this->getHeatmaps($idSite);
-
- // we list recently created heatmaps first
- $order = 20;
- foreach ($heatmaps as $heatmap) {
- $subcategory = new Subcategory();
- $subcategory->setName($heatmap['name']);
- $subcategory->setCategoryId('HeatmapSessionRecording_Heatmaps');
- $subcategory->setId($heatmap['idsitehsr']);
- $subcategory->setOrder($order++);
- $subcategories[] = $subcategory;
- }
- }
-
- if ($this->getValidator()->canViewSessionReport($idSite)) {
- $recordings = $this->getSessionRecordings($idSite);
-
- // we list recently created recordings first
- $order = 20;
- foreach ($recordings as $recording) {
- $subcategory = new Subcategory();
- $subcategory->setName($recording['name']);
- $subcategory->setCategoryId('HeatmapSessionRecording_SessionRecordings');
- $subcategory->setId($recording['idsitehsr']);
- $subcategory->setOrder($order++);
- $subcategories[] = $subcategory;
- }
- }
- }
-
- public function getClientSideTranslationKeys(&$result)
- {
- $result[] = 'General_Save';
- $result[] = 'General_Done';
- $result[] = 'General_Actions';
- $result[] = 'General_Yes';
- $result[] = 'General_No';
- $result[] = 'General_Add';
- $result[] = 'General_Remove';
- $result[] = 'General_Id';
- $result[] = 'General_Ok';
- $result[] = 'General_Cancel';
- $result[] = 'General_Name';
- $result[] = 'General_Loading';
- $result[] = 'General_LoadingData';
- $result[] = 'General_Mobile';
- $result[] = 'General_All';
- $result[] = 'General_Search';
- $result[] = 'CorePluginsAdmin_Status';
- $result[] = 'DevicesDetection_Tablet';
- $result[] = 'CoreUpdater_UpdateTitle';
- $result[] = 'DevicesDetection_Device';
- $result[] = 'Installation_Legend';
- $result[] = 'HeatmapSessionRecording_DeleteScreenshot';
- $result[] = 'HeatmapSessionRecording_DeleteHeatmapScreenshotConfirm';
- $result[] = 'HeatmapSessionRecording_enable';
- $result[] = 'HeatmapSessionRecording_disable';
- $result[] = 'HeatmapSessionRecording_ChangeReplaySpeed';
- $result[] = 'HeatmapSessionRecording_ClickToSkipPauses';
- $result[] = 'HeatmapSessionRecording_AutoPlayNextPageview';
- $result[] = 'HeatmapSessionRecording_XSamples';
- $result[] = 'HeatmapSessionRecording_StatusActive';
- $result[] = 'HeatmapSessionRecording_StatusEnded';
- $result[] = 'HeatmapSessionRecording_StatusPaused';
- $result[] = 'HeatmapSessionRecording_RequiresActivity';
- $result[] = 'HeatmapSessionRecording_RequiresActivityHelp';
- $result[] = 'HeatmapSessionRecording_CaptureKeystrokes';
- $result[] = 'HeatmapSessionRecording_CaptureKeystrokesHelp';
- $result[] = 'HeatmapSessionRecording_SessionRecording';
- $result[] = 'HeatmapSessionRecording_Heatmap';
- $result[] = 'HeatmapSessionRecording_ActivityClick';
- $result[] = 'HeatmapSessionRecording_ActivityMove';
- $result[] = 'HeatmapSessionRecording_ActivityScroll';
- $result[] = 'HeatmapSessionRecording_ActivityResize';
- $result[] = 'HeatmapSessionRecording_ActivityFormChange';
- $result[] = 'HeatmapSessionRecording_ActivityPageChange';
- $result[] = 'HeatmapSessionRecording_HeatmapWidth';
- $result[] = 'HeatmapSessionRecording_Width';
- $result[] = 'HeatmapSessionRecording_Action';
- $result[] = 'HeatmapSessionRecording_DeviceType';
- $result[] = 'HeatmapSessionRecording_PlayerDurationXofY';
- $result[] = 'HeatmapSessionRecording_PlayerPlay';
- $result[] = 'HeatmapSessionRecording_PlayerPause';
- $result[] = 'HeatmapSessionRecording_PlayerRewindFast';
- $result[] = 'HeatmapSessionRecording_PlayerForwardFast';
- $result[] = 'HeatmapSessionRecording_PlayerReplay';
- $result[] = 'HeatmapSessionRecording_PlayerPageViewPrevious';
- $result[] = 'HeatmapSessionRecording_PlayerPageViewNext';
- $result[] = 'HeatmapSessionRecording_SessionRecordingsUsageBenefits';
- $result[] = 'HeatmapSessionRecording_ManageSessionRecordings';
- $result[] = 'HeatmapSessionRecording_ManageHeatmaps';
- $result[] = 'HeatmapSessionRecording_NoSessionRecordingsFound';
- $result[] = 'HeatmapSessionRecording_FieldIncludedTargetsHelpSessions';
- $result[] = 'HeatmapSessionRecording_NoHeatmapsFound';
- $result[] = 'HeatmapSessionRecording_AvgAboveFoldTitle';
- $result[] = 'HeatmapSessionRecording_AvgAboveFoldDescription';
- $result[] = 'HeatmapSessionRecording_TargetPage';
- $result[] = 'HeatmapSessionRecording_TargetPages';
- $result[] = 'HeatmapSessionRecording_ViewReport';
- $result[] = 'HeatmapSessionRecording_SampleLimit';
- $result[] = 'HeatmapSessionRecording_SessionNameHelp';
- $result[] = 'HeatmapSessionRecording_HeatmapSampleLimit';
- $result[] = 'HeatmapSessionRecording_SessionSampleLimit';
- $result[] = 'HeatmapSessionRecording_HeatmapSampleLimitHelp';
- $result[] = 'HeatmapSessionRecording_SessionSampleLimitHelp';
- $result[] = 'HeatmapSessionRecording_MinSessionTime';
- $result[] = 'HeatmapSessionRecording_MinSessionTimeHelp';
- $result[] = 'HeatmapSessionRecording_EditX';
- $result[] = 'HeatmapSessionRecording_StopX';
- $result[] = 'HeatmapSessionRecording_HeatmapUsageBenefits';
- $result[] = 'HeatmapSessionRecording_AdvancedOptions';
- $result[] = 'HeatmapSessionRecording_SampleRate';
- $result[] = 'HeatmapSessionRecording_HeatmapSampleRateHelp';
- $result[] = 'HeatmapSessionRecording_SessionSampleRateHelp';
- $result[] = 'HeatmapSessionRecording_ExcludedElements';
- $result[] = 'HeatmapSessionRecording_ExcludedElementsHelp';
- $result[] = 'HeatmapSessionRecording_ScreenshotUrl';
- $result[] = 'HeatmapSessionRecording_ScreenshotUrlHelp';
- $result[] = 'HeatmapSessionRecording_BreakpointX';
- $result[] = 'HeatmapSessionRecording_BreakpointGeneralHelp';
- $result[] = 'HeatmapSessionRecording_Rule';
- $result[] = 'HeatmapSessionRecording_UrlParameterValueToMatchPlaceholder';
- $result[] = 'HeatmapSessionRecording_EditHeatmapX';
- $result[] = 'HeatmapSessionRecording_TargetTypeIsAny';
- $result[] = 'HeatmapSessionRecording_TargetTypeIsNot';
- $result[] = 'HeatmapSessionRecording_PersonalInformationNote';
- $result[] = 'HeatmapSessionRecording_UpdatingData';
- $result[] = 'HeatmapSessionRecording_FieldIncludedTargetsHelp';
- $result[] = 'HeatmapSessionRecording_DeleteX';
- $result[] = 'HeatmapSessionRecording_DeleteHeatmapConfirm';
- $result[] = 'HeatmapSessionRecording_BreakpointGeneralHelpManage';
- $result[] = 'HeatmapSessionRecording_TargetPageTestTitle';
- $result[] = 'HeatmapSessionRecording_TargetPageTestErrorInvalidUrl';
- $result[] = 'HeatmapSessionRecording_TargetPageTestUrlMatches';
- $result[] = 'HeatmapSessionRecording_TargetPageTestUrlNotMatches';
- $result[] = 'HeatmapSessionRecording_TargetPageTestLabel';
- $result[] = 'HeatmapSessionRecording_ErrorXNotProvided';
- $result[] = 'HeatmapSessionRecording_ErrorPageRuleRequired';
- $result[] = 'HeatmapSessionRecording_CreationDate';
- $result[] = 'HeatmapSessionRecording_HeatmapCreated';
- $result[] = 'HeatmapSessionRecording_HeatmapUpdated';
- $result[] = 'HeatmapSessionRecording_FieldNamePlaceholder';
- $result[] = 'HeatmapSessionRecording_HeatmapNameHelp';
- $result[] = 'HeatmapSessionRecording_CreateNewHeatmap';
- $result[] = 'HeatmapSessionRecording_CreateNewSessionRecording';
- $result[] = 'HeatmapSessionRecording_EditSessionRecordingX';
- $result[] = 'HeatmapSessionRecording_DeleteSessionRecordingConfirm';
- $result[] = 'HeatmapSessionRecording_EndHeatmapConfirm';
- $result[] = 'HeatmapSessionRecording_EndSessionRecordingConfirm';
- $result[] = 'HeatmapSessionRecording_SessionRecordingCreated';
- $result[] = 'HeatmapSessionRecording_SessionRecordingUpdated';
- $result[] = 'HeatmapSessionRecording_Filter';
- $result[] = 'HeatmapSessionRecording_PlayRecordedSession';
- $result[] = 'HeatmapSessionRecording_DeleteRecordedSession';
- $result[] = 'HeatmapSessionRecording_DeleteRecordedPageview';
- $result[] = 'Live_ViewVisitorProfile';
- $result[] = 'HeatmapSessionRecording_HeatmapXRecordedSamplesSince';
- $result[] = 'HeatmapSessionRecording_PageviewsInVisit';
- $result[] = 'HeatmapSessionRecording_ColumnTime';
- $result[] = 'General_TimeOnPage';
- $result[] = 'Goals_URL';
- $result[] = 'General_Close';
- $result[] = 'HeatmapSessionRecording_HeatmapX';
- $result[] = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYet';
- $result[] = 'HeatmapSessionRecording_NoHeatmapScreenshotRecordedYet';
- $result[] = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYetWithoutSystemConfiguration';
- $result[] = 'HeatmapSessionRecording_NoHeatmapScreenshotRecordedYetWithoutSystemConfiguration';
- $result[] = 'HeatmapSessionRecording_HeatmapInfoTrackVisitsFromCountries';
- $result[] = 'HeatmapSessionRecording_SessionRecordingInfoTrackVisitsFromCountries';
- $result[] = 'HeatmapSessionRecording_AdBlockerDetected';
- $result[] = 'HeatmapSessionRecording_CaptureDomTitle';
- $result[] = 'HeatmapSessionRecording_CaptureDomInlineHelp';
- $result[] = 'HeatmapSessionRecording_MatomoJSNotWritableErrorMessage';
- $result[] = 'HeatmapSessionRecording_SessionRecordings';
- $result[] = 'HeatmapSessionRecording_Heatmaps';
- $result[] = 'HeatmapSessionRecording_Clicks';
- $result[] = 'HeatmapSessionRecording_ClickRate';
- $result[] = 'HeatmapSessionRecording_Moves';
- $result[] = 'HeatmapSessionRecording_MoveRate';
- $result[] = 'HeatmapSessionRecording_HeatmapTroubleshoot';
- }
-
- public function getJsFiles(&$jsFiles)
- {
- $jsFiles[] = "plugins/HeatmapSessionRecording/javascripts/rowaction.js";
- }
-
- public function getStylesheetFiles(&$stylesheets)
- {
- $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/list-entities.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/edit-entities.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/recordings.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.less";
- $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.less";
- }
-
- public function activate()
- {
- $this->installHtAccess();
- }
-
- public function install()
- {
- $siteHsr = new SiteHsrDao();
- $siteHsr->install();
-
- $hsrSite = new LogHsrSite();
- $hsrSite->install();
-
- $hsr = new LogHsr($hsrSite);
- $hsr->install();
-
- $blobHsr = new LogHsrBlob();
- $blobHsr->install();
-
- $event = new LogHsrEvent($blobHsr);
- $event->install();
-
- $this->installHtAccess();
-
- $configuration = new Configuration();
- $configuration->install();
- }
-
- public function installHtAccess()
- {
- $htaccess = new HtAccess();
- $htaccess->install();
- }
-
- public function uninstall()
- {
- $siteHsr = new SiteHsrDao();
- $siteHsr->uninstall();
-
- $hsrSite = new LogHsrSite();
- $hsrSite->uninstall();
-
- $hsr = new LogHsr($hsrSite);
- $hsr->uninstall();
-
- $blobHsr = new LogHsrBlob();
- $blobHsr->uninstall();
-
- $event = new LogHsrEvent($blobHsr);
- $event->uninstall();
-
- $configuration = new Configuration();
- $configuration->uninstall();
- }
-
- public function isTrackerPlugin()
- {
- return true;
- }
-
- private function getSiteHsrDao()
- {
- return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao');
- }
-
- public function addSiteTrackerCache(&$content, $idSite)
- {
- $hsr = $this->getSiteHsrDao();
- $hsrs = $hsr->getActiveRecords($idSite);
-
- foreach ($hsrs as $index => $hsr) {
- // we make sure to keep the cache file small as this is not needed in the cache
- $hsrs[$index]['page_treemirror'] = !empty($hsr['page_treemirror']) ? '1' : null;
- }
-
- $content['hsr'] = $hsrs;
- }
-
- public function addActionReferenceColumnsByTable(&$result)
- {
- $result['log_hsr'] = array('idaction_url');
- $result['log_hsr_event'] = array('idselector');
- }
-
- public function changeSessionLengthIfEmbedPage()
- {
- if (
- SettingsServer::isTrackerApiRequest()
- || Common::isPhpCliMode()
- ) {
- return;
- }
-
- // if there's no token_auth=... in the URL and there's no existing HSR session, then
- // we don't change the session options and try to use the normal matomo session.
- if (
- Common::getRequestVar('token_auth', false) === false
- && empty($_COOKIE[self::EMBED_SESSION_NAME])
- ) {
- return;
- }
-
- $module = Common::getRequestVar('module', '', 'string');
- $action = Common::getRequestVar('action', '', 'string');
- if (
- $module == 'HeatmapSessionRecording'
- && $action == 'embedPage'
- ) {
- Config::getInstance()->General['login_cookie_expire'] = self::EMBED_SESSION_TIME;
-
- Session::$sessionName = self::EMBED_SESSION_NAME;
- Session::rememberMe(Config::getInstance()->General['login_cookie_expire']);
- }
- }
-
- public static function getTranslationKey($type)
- {
- $key = '';
- switch ($type) {
- case 'pause':
- $key = 'HeatmapSessionRecording_PauseReason';
- break;
- case 'noDataSession':
- $key = 'HeatmapSessionRecording_NoSessionRecordedYetWithoutSystemConfiguration';
- break;
- case 'noDataHeatmap':
- $key = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYetWithoutSystemConfiguration';
- break;
- }
-
- if (!$key) {
- return null;
- }
-
- Piwik::postEvent('HeatmapSessionRecording.updateTranslationKey', [&$key]);
- return $key;
- }
-
- public static function isMatomoJsWritable($checkSpecificFile = '')
- {
- if (Manager::getInstance()->isPluginActivated('Cloud')) {
- return true;
- }
-
- $updater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater');
- $filePath = $updater->getToFile()->getPath();
- $filesToCheck = array($filePath);
- $jsCodeGenerator = new TrackerCodeGenerator();
- if (SettingsPiwik::isMatomoInstalled() && $jsCodeGenerator->shouldPreferPiwikEndpoint()) {
- // if matomo is not installed yet, we definitely prefer matomo.js... check for isMatomoInstalled is needed
- // cause otherwise it would perform a db query before matomo DB is configured
- $filesToCheck[] = str_replace('matomo.js', 'piwik.js', $filePath);
- }
-
- if (!empty($checkSpecificFile)) {
- $filesToCheck = [$checkSpecificFile]; // mostly used for testing isMatomoJsWritable functionality
- }
-
- if (!Manager::getInstance()->isPluginActivated('CustomJsTracker')) {
- return false;
- }
-
- foreach ($filesToCheck as $fileToCheck) {
- $file = new File($fileToCheck);
-
- if (!$file->hasWriteAccess()) {
- return false;
- }
- }
-
- return true;
- }
-
- private function getHeatmaps($idSite)
- {
- return Request::processRequest('HeatmapSessionRecording.getHeatmaps', [
- 'idSite' => $idSite, 'filter_limit' => -1,
- 'includePageTreeMirror' => 0 // IMPORTANT for performance and IO. If you need page tree mirror please add another method and don't remove this parameter
- ], $default = []);
- }
-
- private function getSessionRecordings($idSite)
- {
- return Request::processRequest('HeatmapSessionRecording.getSessionRecordings', [
- 'idSite' => $idSite, 'filter_limit' => -1
- ], $default = []);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php
deleted file mode 100644
index e1b99c3..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php
+++ /dev/null
@@ -1,176 +0,0 @@
-configuration = new Configuration();
- $this->systemSettings = $systemSettings;
- }
-
- private function supportsMethod($method)
- {
- return method_exists('Piwik\Piwik', $method);
- }
-
- public function checkHasSomeWritePermission()
- {
- if ($this->supportsMethod('checkUserHasSomeWriteAccess')) {
- // since Matomo 3.6.0
- Piwik::checkUserHasSomeWriteAccess();
- return;
- }
-
- Piwik::checkUserHasSomeAdminAccess();
- }
-
- public function checkWritePermission($idSite)
- {
- $this->checkSiteExists($idSite);
- Piwik::checkUserIsNotAnonymous();
-
- if ($this->supportsMethod('checkUserHasWriteAccess')) {
- // since Matomo 3.6.0
- Piwik::checkUserHasWriteAccess($idSite);
- return;
- }
-
- Piwik::checkUserHasAdminAccess($idSite);
- }
-
- public function checkHeatmapReportViewPermission($idSite)
- {
- $this->checkSiteExists($idSite);
- Piwik::checkUserHasViewAccess($idSite);
- $this->checkHeatmapRecordingEnabled();
- }
-
- public function checkSessionReportViewPermission($idSite)
- {
- $this->checkSiteExists($idSite);
- $this->checkUserIsNotAnonymousForView($idSite);
- Piwik::checkUserHasViewAccess($idSite);
- $this->checkSessionRecordingEnabled();
- }
-
- public function checkSessionReportWritePermission($idSite)
- {
- $this->checkWritePermission($idSite);
- $this->checkSessionRecordingEnabled();
- }
-
- public function checkHeatmapReportWritePermission($idSite)
- {
- $this->checkWritePermission($idSite);
- $this->checkHeatmapRecordingEnabled();
- }
-
- public function checkSessionRecordingEnabled()
- {
- if ($this->isSessionRecordingDisabled()) {
- throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDisabled'));
- }
- }
-
- public function checkHeatmapRecordingEnabled()
- {
- if ($this->isHeatmapRecordingDisabled()) {
- throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapRecordingDisabled'));
- }
- }
-
- private function checkUserIsNotAnonymousForView($idSite)
- {
- if ($this->configuration->isAnonymousSessionRecordingAccessEnabled($idSite)) {
- Piwik::checkUserHasViewAccess($idSite);
- return;
- }
-
- Piwik::checkUserIsNotAnonymous();
- }
-
- private function checkSiteExists($idSite)
- {
- new Site($idSite);
- }
-
- public function canViewSessionReport($idSite)
- {
- if (empty($idSite) || $this->isSessionRecordingDisabled()) {
- return false;
- }
-
- if (
- !$this->configuration->isAnonymousSessionRecordingAccessEnabled($idSite)
- && Piwik::isUserIsAnonymous()
- ) {
- return false;
- }
-
- return Piwik::isUserHasViewAccess($idSite);
- }
-
- public function canViewHeatmapReport($idSite)
- {
- if (empty($idSite) || $this->isHeatmapRecordingDisabled()) {
- return false;
- }
-
- return Piwik::isUserHasViewAccess($idSite);
- }
-
- public function canWrite($idSite)
- {
- if (empty($idSite)) {
- return false;
- }
-
- if ($this->supportsMethod('isUserHasWriteAccess')) {
- // since Matomo 3.6.0
- return Piwik::isUserHasWriteAccess($idSite);
- }
-
- return Piwik::isUserHasAdminAccess($idSite);
- }
-
- public function isSessionRecordingDisabled()
- {
- return $this->systemSettings->disableSessionRecording->getValue();
- }
-
- public function isHeatmapRecordingDisabled()
- {
- return $this->systemSettings->disableHeatmapRecording->getValue();
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php b/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php
deleted file mode 100644
index f95767c..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php
+++ /dev/null
@@ -1,489 +0,0 @@
-dao = $dao;
- $this->logHsrSite = $logHsrSite;
- }
-
- public function addHeatmap($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $createdDate)
- {
- $this->checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet);
-
- $status = SiteHsrDao::STATUS_ACTIVE;
-
- $idSiteHsr = $this->dao->createHeatmapRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $status, $captureDomManually, $createdDate);
- $this->clearTrackerCache($idSite);
-
- return (int) $idSiteHsr;
- }
-
- public function updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $updatedDate)
- {
- $this->checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet);
-
- $columns = array(
- 'name' => $name,
- 'sample_limit' => $sampleLimit,
- 'match_page_rules' => $matchPageRules,
- 'sample_rate' => $sampleRate,
- 'excluded_elements' => $excludedElements,
- 'screenshot_url' => $screenshotUrl,
- 'breakpoint_mobile' => $breakpointMobile,
- 'breakpoint_tablet' => $breakpointTablet,
- 'updated_date' => $updatedDate,
- );
-
- if (!empty($captureDomManually)) {
- $columns['capture_manually'] = 1;
- $columns['page_treemirror'] = null;
- } else {
- $columns['capture_manually'] = 0;
- }
-
- $this->updateHsrColumns($idSite, $idSiteHsr, $columns);
- $this->clearTrackerCache($idSite);
- }
-
- private function checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet)
- {
- $name = new Name($name);
- $name->check();
-
- $pageRules = new PageRules($matchPageRules, 'matchPageRules', $needsOneEntry = true);
- $pageRules->check();
-
- $sampleLimit = new SampleLimit($sampleLimit);
- $sampleLimit->check();
-
- $sampleRate = new SampleRate($sampleRate);
- $sampleRate->check();
-
- $screenshotUrl = new ScreenshotUrl($screenshotUrl);
- $screenshotUrl->check();
-
- $excludedElements = new ExcludedElements($excludedElements);
- $excludedElements->check();
-
- $breakpointMobile = new Breakpoint($breakpointMobile, 'Mobile');
- $breakpointMobile->check();
-
- $breakpointTablet = new Breakpoint($breakpointTablet, 'Tablet');
- $breakpointTablet->check();
- }
-
- public function addSessionRecording($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $createdDate)
- {
- $this->checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes);
- $status = SiteHsrDao::STATUS_ACTIVE;
-
- $idSiteHsr = $this->dao->createSessionRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $minSessionTime, $requiresActivity, $captureKeystrokes, $status, $createdDate);
-
- $this->clearTrackerCache($idSite);
- return (int) $idSiteHsr;
- }
-
- public function updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $updatedDate)
- {
- $this->checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes);
-
- $columns = array(
- 'name' => $name,
- 'sample_limit' => $sampleLimit,
- 'match_page_rules' => $matchPageRules,
- 'sample_rate' => $sampleRate,
- 'min_session_time' => $minSessionTime,
- 'requires_activity' => $requiresActivity,
- 'capture_keystrokes' => $captureKeystrokes,
- 'updated_date' => $updatedDate,
- );
-
- $this->updateHsrColumns($idSite, $idSiteHsr, $columns);
- $this->clearTrackerCache($idSite);
- }
-
- private function checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes)
- {
- $name = new Name($name);
- $name->check();
-
- $pageRules = new PageRules($matchPageRules, 'matchPageRules', $needsOneEntry = false);
- $pageRules->check();
-
- $sampleLimit = new SampleLimit($sampleLimit);
- $sampleLimit->check();
-
- $sampleRate = new SampleRate($sampleRate);
- $sampleRate->check();
-
- $minSessionTime = new MinSessionTime($minSessionTime);
- $minSessionTime->check();
-
- $requiresActivity = new RequiresActivity($requiresActivity);
- $requiresActivity->check();
-
- $captureKeystrokes = new CaptureKeystrokes($captureKeystrokes);
- $captureKeystrokes->check();
- }
-
- public function getHeatmap($idSite, $idSiteHsr)
- {
- $record = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP);
-
- return $this->enrichHeatmap($record);
- }
-
- public function getSessionRecording($idSite, $idSiteHsr)
- {
- $record = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION);
- return $this->enrichSessionRecording($record);
- }
-
- public function pauseHeatmap($idSite, $idSiteHsr)
- {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_PAUSED));
- }
-
- public function resumeHeatmap($idSite, $idSiteHsr)
- {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ACTIVE));
- }
-
- public function deactivateHeatmap($idSite, $idSiteHsr)
- {
- $heatmap = $this->getHeatmap($idSite, $idSiteHsr);
-
- if (!empty($heatmap)) {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_DELETED));
-
- // the actual recorded heatmap data will still exist but we remove the "links" which is quick. a task will later remove all entries
- $this->logHsrSite->unlinkSiteRecords($idSiteHsr);
- }
- }
-
- public function checkHeatmapExists($idSite, $idSiteHsr)
- {
- $hsr = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP);
-
- if (empty($hsr)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapDoesNotExist'));
- }
- }
-
- public function checkSessionRecordingExists($idSite, $idSiteHsr)
- {
- $hsr = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION);
-
- if (empty($hsr)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
- }
-
- public function pauseSessionRecording($idSite, $idSiteHsr)
- {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_PAUSED));
- }
-
- public function resumeSessionRecording($idSite, $idSiteHsr)
- {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ACTIVE));
- }
-
- public function deactivateSessionRecording($idSite, $idSiteHsr)
- {
- $session = $this->getSessionRecording($idSite, $idSiteHsr);
-
- if (!empty($session)) {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_DELETED));
-
- // the actual recording will still exist but we remove the "links" which is quick. a task will later remove all entries
- $this->logHsrSite->unlinkSiteRecords($idSiteHsr);
- }
- }
-
- public function deactivateRecordsForSite($idSite)
- {
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) {
- $this->deactivateHeatmap($idSite, $heatmap['idsitehsr']);
- }
-
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) {
- $this->deactivateSessionRecording($idSite, $session['idsitehsr']);
- }
- }
-
- public function pauseRecordsForSite($idSite)
- {
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) {
- $this->pauseHeatmap($idSite, $heatmap['idsitehsr']);
- }
-
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) {
- $this->pauseSessionRecording($idSite, $session['idsitehsr']);
- }
- }
-
- public function resumeRecordsForSite($idSite)
- {
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) {
- $this->resumeHeatmap($idSite, $heatmap['idsitehsr']);
- }
-
- foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) {
- $this->resumeSessionRecording($idSite, $session['idsitehsr']);
- }
- }
-
- public function endHeatmap($idSite, $idSiteHsr)
- {
- $heatmap = $this->getHeatmap($idSite, $idSiteHsr);
- if (!empty($heatmap)) {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ENDED));
-
- Piwik::postEvent('HeatmapSessionRecording.endHeatmap', array($idSite, $idSiteHsr));
- }
- }
-
- public function endSessionRecording($idSite, $idSiteHsr)
- {
- $session = $this->getSessionRecording($idSite, $idSiteHsr);
- if (!empty($session)) {
- $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ENDED));
-
- Piwik::postEvent('HeatmapSessionRecording.endSessionRecording', array($idSite, $idSiteHsr));
- }
- }
-
- /**
- * @param $idSite
- * @param bool $includePageTreeMirror performance and IO tweak has some heatmaps might have a 16MB or more treemirror and it would be loaded on every request causing a lot of IO etc.
- * @return array
- */
- public function getHeatmaps($idSite, $includePageTreeMirror)
- {
- $heatmaps = $this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, $includePageTreeMirror);
-
- return $this->enrichHeatmaps($heatmaps);
- }
-
- public function getSessionRecordings($idSite)
- {
- $sessionRecordings = $this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, $includePageTreeMirror = false);
-
- return $this->enrichSessionRecordings($sessionRecordings);
- }
-
- public function hasSessionRecordings($idSite)
- {
- $hasSession = $this->dao->hasRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION);
-
- return !empty($hasSession);
- }
-
- public function hasHeatmaps($idSite)
- {
- $hasHeatmap = $this->dao->hasRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP);
-
- return !empty($hasHeatmap);
- }
-
- public function setPageTreeMirror($idSite, $idSiteHsr, $treeMirror, $screenshotUrl)
- {
- $heatmap = $this->getHeatmap($idSite, $idSiteHsr);
- if (!empty($heatmap)) {
- // only supported by heatmaps
- $columns = array(
- 'page_treemirror' => $treeMirror,
- 'screenshot_url' => $screenshotUrl
- );
- if (!empty($heatmap['capture_manually']) && !empty($treeMirror)) {
- $columns['capture_manually'] = 0;
- }
- $this->updateHsrColumns($idSite, $idSiteHsr, $columns);
- }
- }
-
- public function getPiwikRequestDate($hsr)
- {
- // we sub one day to make sure to include them all
- $from = Date::factory($hsr['created_date'])->subDay(1)->toString();
- $to = Date::now()->addDay(1)->toString();
-
- if ($from === $to) {
- $dateRange = $from;
- $period = 'year';
- } else {
- $period = 'range';
- $dateRange = $from . ',' . $to;
- }
-
- return array('period' => $period, 'date' => $dateRange);
- }
-
- private function enrichHeatmaps($heatmaps)
- {
- if (empty($heatmaps)) {
- return array();
- }
-
- foreach ($heatmaps as $index => $heatmap) {
- $heatmaps[$index] = $this->enrichHeatmap($heatmap);
- }
-
- return $heatmaps;
- }
-
- private function enrichHeatmap($heatmap)
- {
- if (empty($heatmap)) {
- return $heatmap;
- }
-
- unset($heatmap['record_type']);
- unset($heatmap['min_session_time']);
- unset($heatmap['requires_activity']);
- unset($heatmap['capture_keystrokes']);
- $heatmap['created_date_pretty'] = Date::factory($heatmap['created_date'])->getLocalized(DateTimeFormatProvider::DATE_FORMAT_SHORT);
-
- if ((!method_exists(SettingsServer::class, 'isMatomoForWordPress') || !SettingsServer::isMatomoForWordPress()) && !SettingsServer::isTrackerApiRequest()) {
- $heatmap['heatmapViewUrl'] = self::completeWidgetUrl('showHeatmap', 'idSiteHsr=' . (int) $heatmap['idsitehsr'] . '&useDateUrl=0', (int) $heatmap['idsite']);
- }
-
- return $heatmap;
- }
-
- public static function completeWidgetUrl($action, $params, $idSite, $period = null, $date = null)
- {
- if (!isset($date)) {
- if (empty(self::$defaultDate)) {
- $userPreferences = new UserPreferences();
- self::$defaultDate = $userPreferences->getDefaultDate();
- if (empty(self::$defaultDate)) {
- self:: $defaultDate = 'today';
- }
- }
- $date = self::$defaultDate;
- }
-
- if (!isset($period)) {
- if (!isset(self::$defaultPeriod)) {
- $userPreferences = new UserPreferences();
- self::$defaultPeriod = $userPreferences->getDefaultPeriod(false);
- if (empty(self::$defaultPeriod)) {
- self::$defaultPeriod = 'day';
- }
- }
- $period = self::$defaultPeriod;
- }
-
- $token = Access::getInstance()->getTokenAuth();
-
- $url = 'index.php?module=Widgetize&action=iframe&moduleToWidgetize=HeatmapSessionRecording&actionToWidgetize=' . urlencode($action) . '&' . $params . '&idSite=' . (int) $idSite . '&period=' . urlencode($period) . '&date=' . urlencode($date);
- if (!empty($token)) {
- $url .= '&token_auth=' . urlencode($token);
- }
- return $url;
- }
-
- private function enrichSessionRecordings($sessionRecordings)
- {
- if (empty($sessionRecordings)) {
- return array();
- }
-
- foreach ($sessionRecordings as $index => $sessionRecording) {
- $sessionRecordings[$index] = $this->enrichSessionRecording($sessionRecording);
- }
-
- return $sessionRecordings;
- }
-
- private function enrichSessionRecording($session)
- {
- if (empty($session)) {
- return $session;
- }
-
- unset($session['record_type']);
- unset($session['screenshot_url']);
- unset($session['page_treemirror']);
- unset($session['excluded_elements']);
- unset($session['breakpoint_mobile']);
- unset($session['breakpoint_tablet']);
- $session['created_date_pretty'] = Date::factory($session['created_date'])->getLocalized(DateTimeFormatProvider::DATE_FORMAT_SHORT);
-
- return $session;
- }
-
- protected function getCurrentDateTime()
- {
- return Date::now()->getDatetime();
- }
-
- private function updateHsrColumns($idSite, $idSiteHsr, $columns)
- {
- if (!isset($columns['updated_date'])) {
- $columns['updated_date'] = $this->getCurrentDateTime();
- }
-
- $this->dao->updateHsrColumns($idSite, $idSiteHsr, $columns);
- $this->clearTrackerCache($idSite);
- }
-
- private function clearTrackerCache($idSite)
- {
- Tracker\Cache::deleteCacheWebsiteAttributes($idSite);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php b/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php
deleted file mode 100644
index 3c16e9c..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php
+++ /dev/null
@@ -1,296 +0,0 @@
-breakpointMobile = $this->createBreakpointMobileSetting();
- $this->breakpointTablet = $this->createBreakpointTabletSetting();
- $this->disableTrackingByDefault = $this->createDisableTrackingByDefaultSetting();
- $this->disableSessionRecording = $this->createDisableSessionRecordingSetting();
- $this->disableHeatmapRecording = $this->createDisableHeatmapRecordingSetting();
- $this->includeCountries = $this->createIncludeCountriesSetting();
-
- if (Plugin\Manager::getInstance()->isPluginActivated('CustomJsTracker')) {
- $trackerUpdater = StaticContainer::get(TrackerUpdater::class);
- if (!$trackerUpdater || !$trackerUpdater->getToFile() || !$trackerUpdater->getToFile()->hasWriteAccess()) {
- // only works if matomo file can be updated
- $this->disableTrackingByDefault->setIsWritableByCurrentUser(false);
- }
- } else {
- $this->disableTrackingByDefault->setIsWritableByCurrentUser(false);
- }
- }
-
- private function createDisableTrackingByDefaultSetting()
- {
- $setting = new TrackingDisableDefault('trackingDisabledDefault', false, FieldConfig::TYPE_BOOL, $this->pluginName);
- $setting->setConfigureCallback(function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_TrackingDisabledDefaultSettingTitle');
- $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
- $field->description = Piwik::translate('HeatmapSessionRecording_TrackingDisabledDefaultSettingDescription');
- });
- $this->addSetting($setting);
- return $setting;
- }
-
- private function createBreakpointMobileSetting()
- {
- return $this->makeSetting('breakpointMobile', Breakpoint::DEFAULT_MOBILE, FieldConfig::TYPE_INT, function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_BreakpointX', Piwik::translate('General_Mobile'));
- $field->uiControl = FieldConfig::UI_CONTROL_TEXT;
- $field->description = Piwik::translate('HeatmapSessionRecording_BreakpointGeneralHelp');
- $field->validate = function ($value) {
- $breakpoint = new Breakpoint($value, Piwik::translate('General_Mobile'));
- $breakpoint->check();
- };
- });
- }
-
- private function createBreakpointTabletSetting()
- {
- return $this->makeSetting('breakpointTablet', Breakpoint::DEFAULT_TABLET, FieldConfig::TYPE_INT, function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_BreakpointX', Piwik::translate('DevicesDetection_Tablet'));
- $field->uiControl = FieldConfig::UI_CONTROL_TEXT;
- $field->description = Piwik::translate('HeatmapSessionRecording_BreakpointGeneralHelp');
- $field->validate = function ($value) {
- $breakpoint = new Breakpoint($value, Piwik::translate('DevicesDetection_Tablet'));
- $breakpoint->check();
- };
- });
- }
-
- private function createDisableSessionRecordingSetting()
- {
- $setting = new TrackingDisableDefault('disableSessionRecording', false, FieldConfig::TYPE_BOOL, $this->pluginName);
- $setting->setConfigureCallback(function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingTitle', Piwik::translate('General_Mobile'));
- $field->description = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingDescription');
- $field->inlineHelp = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingInlineHelp', array('',''));
- });
- $this->addSetting($setting);
-
- return $setting;
- }
-
- private function createDisableHeatmapRecordingSetting()
- {
- $setting = new TrackingDisableDefault('disableHeatmapRecording', false, FieldConfig::TYPE_BOOL, $this->pluginName);
- $setting->setConfigureCallback(function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingTitle', Piwik::translate('General_Mobile'));
- $field->description = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingDescription');
- $field->inlineHelp = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingInlineHelp', array('',''));
- });
- $this->addSetting($setting);
-
- return $setting;
- }
-
- public function createIncludeCountriesSetting()
- {
- return $this->makeSetting('included_countries', [], FieldConfig::TYPE_ARRAY, function (FieldConfig $field) {
- $field->title = Piwik::translate('HeatmapSessionRecording_EnableIncludeCountriesTitle');
- $field->description = Piwik::translate('HeatmapSessionRecording_EnableIncludeCountriesDescription');
- $field->uiControl = FieldConfig::UI_CONTROL_MULTI_TUPLE;
- $field1 = new FieldConfig\MultiPair(Piwik::translate('HeatmapSessionRecording_Country'), 'country', FieldConfig::UI_CONTROL_SINGLE_SELECT);
- $field1->availableValues = $this->getAvailableCountries();
- $field->uiControlAttributes['field1'] = $field1->toArray();
-
- $self = $this;
- $field->transform = function ($value) use ($self) {
- return $self->transformCountryList($value);
- };
- $field->validate = function ($value) use ($field1) {
- foreach ($value as $country) {
- if (empty($country['country'])) {
- continue;
- }
- if ($country['country'] === 'xx') {
- continue; // valid, country not detected
- }
- if (!isset($field1->availableValues[$country['country']])) {
- throw new \Exception('Invalid country code');
- }
- }
- };
- });
- }
-
- public function transformCountryList($value)
- {
- if (!empty($value) && is_array($value)) {
- $newVal = [];
- foreach ($value as $index => $val) {
- if (empty($val['country'])) {
- continue;
- }
- $newVal[] = ['country' => $val['country']];
- }
- return $newVal;
- }
- return $value;
- }
-
- public function save()
- {
- $this->endSessionRecordings();
- $this->endHeatmapRecordings();
- parent::save();
-
- if (!empty($this->disableTrackingByDefault)) {
- $oldValue = $this->disableTrackingByDefault->getOldValue();
- $newValue = $this->disableTrackingByDefault->getValue();
- if ($oldValue != $newValue) {
- $plugin = Plugin\Manager::getInstance()->getLoadedPlugin($this->pluginName);
- if (!empty($plugin) && $plugin instanceof HeatmapSessionRecording) {
- $plugin->updatePiwikTracker();
- }
- }
- }
- }
-
- public function getIncludedCountries($applyTransformation = true)
- {
- $includedCountries = $this->includeCountries->getValue();
- $transformedList = [];
-
- if (!empty($includedCountries)) {
- foreach ($includedCountries as $value) {
- $transformedList[] = $value['country'];
- }
- }
-
- if (!empty($transformedList) && $applyTransformation) {
- $transformedList = $this->getAvailableCountries($transformedList);
- }
-
- return $transformedList;
- }
-
- private function getAvailableCountries($countryCodesToFilter = [])
- {
- $regionDataProvider = StaticContainer::get(RegionDataProvider::class);
- $countryList = $regionDataProvider->getCountryList();
- array_walk($countryList, function (&$item, $key) {
- $item = Piwik::translate('Intl_Country_' . strtoupper($key));
- });
- asort($countryList); //order by localized name
-
- if (!empty($countryCodesToFilter)) {
- $filteredList = [];
- foreach ($countryList as $countryCode => $countryName) {
- if (in_array($countryCode, $countryCodesToFilter)) {
- $filteredList[$countryCode] = $countryName;
- }
- }
-
- return $filteredList;
- }
-
- return $countryList;
- }
-
- private function endSessionRecordings()
- {
- if (
- !empty($this->disableSessionRecording->getValue()) &&
- $this->disableSessionRecording->getOldValue() != $this->disableSessionRecording->getValue()
- ) {
- $sites = SitesManagerAPI::getInstance()->getAllSitesId();
- $this->disableSessionRecording->setValue(false); //added this to fetch results, else it throws an exception
- foreach ($sites as $idSite) {
- if (Site::getTypeFor($idSite) === 'proxysite') {
- continue; //do not delete session recording for proxySite as it will throw an exception due to read only behaviour
- }
- $recordings = Request::processRequest('HeatmapSessionRecording.getSessionRecordings', [
- 'idSite' => $idSite, 'filter_limit' => -1
- ]);
- foreach ($recordings as $recording) {
- Request::processRequest('HeatmapSessionRecording.deleteSessionRecording', [
- 'idSite' => $idSite,
- 'idSiteHsr' => $recording['idsitehsr']
- ]);
- }
- }
-
- $this->disableSessionRecording->setValue(true);
- Cache::deleteTrackerCache();
- }
- }
-
- private function endHeatmapRecordings()
- {
- if (
- !empty($this->disableHeatmapRecording->getValue()) &&
- $this->disableHeatmapRecording->getOldValue() != $this->disableHeatmapRecording->getValue()
- ) {
- $sites = SitesManagerAPI::getInstance()->getAllSitesId();
- $this->disableHeatmapRecording->setValue(false); //added this to fetch results, else it throws an exception
- foreach ($sites as $idSite) {
- if (Site::getTypeFor($idSite) === 'proxysite') {
- continue; //do not delete heatmap for proxySite as it will throw an exception due to read only behaviour
- }
- $recordings = Request::processRequest('HeatmapSessionRecording.getHeatmaps', [
- 'idSite' => $idSite, 'filter_limit' => -1
- ]);
- foreach ($recordings as $recording) {
- Request::processRequest('HeatmapSessionRecording.deleteHeatmap', [
- 'idSite' => $idSite,
- 'idSiteHsr' => $recording['idsitehsr']
- ]);
- }
- }
-
- $this->disableHeatmapRecording->setValue(true);
- Cache::deleteTrackerCache();
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json
deleted file mode 100644
index 9f39115..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json
+++ /dev/null
@@ -1,237 +0,0 @@
-{
- "HeatmapSessionRecording": {
- "Recordings": "Recordings",
- "Heatmaps": "Heatmaps",
- "Heatmap": "Heatmap",
- "HeatmapX": "Heatmap %s",
- "NHeatmaps": "%s heatmaps",
- "NSessionRecordings": "%s session recordings",
- "NoHeatmapsFound": "No heatmaps found",
- "NoSessionRecordingsFound": "No session recordings found",
- "HeatmapUsageBenefits": "Heatmaps let you record all clicks, mouse moves, and scroll activities of your visitors on a certain page. This helps you to find out where users think something is clickable but is not, whether there are parts of the page that are being barely viewed or interacted with, what your visitors are looking for, how much of the page is visible when users view your page, and more.",
- "HeatmapXRecordedSamplesSince": "%1$s samples have been recorded since %2$s.",
- "HeatmapTroubleshoot": "Having issues? %1$sLearn more%2$s.",
- "SessionRecordingsUsageBenefits": "Session recordings let you record all activities of a real visitor during their session (visit) such as clicks, mouse movements, scrolls, window resizes, page changes, and form interactions. You can then replay these interactions to see exactly how a visitor interacted with your website. This way you understand their expectations, problems they may have, usage patterns, and more.",
- "SessionRecordings": "Session Recordings",
- "SessionRecording": "Session Recording",
- "AutoPlayNextPageview": "Click to %1$s autoplay next page view (shortcut %2$s)",
- "ClickToSkipPauses": "Click to %1$s skip pauses (shortcut %2$s)",
- "ChangeReplaySpeed": "Change replay speed (shortcut %s)",
- "PageviewXofY": "Pageview %1$s of %2$s",
- "enable": "enable",
- "disable": "disable",
- "ReplayX": "Replay %s",
- "DeleteScreenshot": "Delete Screenshot",
- "CreationDate": "Creation Date",
- "SessionRecordingX": "Session Recording %s",
- "ReplayRecordedSession": "Replay recorded session",
- "RecordedSessions": "Recorded Sessions",
- "RecordedSessionsDocStatusActive": "This session recording is active. Up to %1$d sessions will be recorded with a sample rate of %2$s.",
- "RecordedSessionsDocStatusEnded": "This session recording has ended and no new sessions will be recorded.",
- "RecordedHeatmapDocStatusActive": "This heatmap is active. Up to %1$d page views will be recorded with a sample rate of %2$s.",
- "RecordedHeatmapDocStatusEnded": "This heatmap has ended. No new activity will be recorded.",
- "TrackingDisabledDefaultSettingTitle": "Disable tracking by default",
- "TrackingDisabledDefaultSettingDescription": "This feature can be useful if your Matomo has multiple sites and you want to use the feature only in a few specific sites. When disabling tracking by default, the Heatmap and Session Recording feature won't execute on any site unless you specifically enable it as part of your tracking code for example by calling '_paq.push(['HeatmapSessionRecording::enable']);'. This avoids unnecessary network requests to the 'configs.php' file to determine if a heatmap or session recording is configured for the current site. It can also be useful for privacy reasons if you want to make sure the feature will be only used on some sites.",
- "StatusActive": "Active",
- "StatusEnded": "Ended",
- "StatusPaused": "Paused",
- "ExcludedElements": "Excluded Elements",
- "ExcludedElementsHelp": "You can optionally define CSS selectors to exclude certain elements that should not be visible in the heatmap preview. For example a popup that is shown when the target page is opened. You can separate multiple selectors with a comma.",
- "Rule": "Rule",
- "Manage": "Manage",
- "ViewReport": "View report",
- "ErrorXNotANumber": "\"%1$s\" has to be a number.",
- "SampleLimit": "Sample Limit",
- "TimeOnSite": "Time on website",
- "HeatmapSampleLimit": "Number of page views",
- "SessionSampleLimit": "Number of sessions",
- "HeatmapSampleLimitHelp": "Defines the amount of page views you want to record in total.",
- "SessionSampleLimitHelp": "Defines the amount of sessions you want to record in total.",
- "PageviewsInVisit": "Recorded pageviews in this session",
- "PlayRecordedSession": "Play this recorded session",
- "DeleteRecordedSession": "Delete this recorded session (no confirmation asked)",
- "DeleteRecordedPageview": "Delete this recorded page view (no confirmation asked)",
- "ViewportResolution": "Viewport Resolution (width x height)",
- "OnePageview": "1 pageview",
- "TargetPage": "Target Page",
- "TargetPages": "Entry Pages",
- "ActivityClick": "Click",
- "ActivityMove": "Move",
- "ActivityScroll": "Scroll",
- "ActivityScrollElement": "Scroll Element",
- "ActivityResize": "Resize",
- "ActivityFormChange": "Form Change",
- "ActivityFormText": "Form Text Change",
- "ActivityFormValue": "Form Value Change",
- "ActivityInitialDom": "Initial Page",
- "ActivityPageChange": "Change Within Page",
- "PlayerRewindFast": "Rewind %1$s seconds (shortcut %2$s)",
- "PlayerForwardFast": "Skip %1$s seconds (shortcut %2$s)",
- "PlayerPageViewPrevious": "Play previous pageview of this visitor %1$s (shortcut %2$s)",
- "PlayerPageViewNext": "Play next pageview of this visitor (%1$s) (shortcut %2$s)",
- "PlayerPlay": "Play (shortcut %s or space)",
- "PlayerPause": "Pause (shortcut %s or space)",
- "PlayerReplay": "Replay (shortcut %s or space)",
- "PlayerDurationXofY": "%1$s of %2$s",
- "AdvancedOptions": "Advanced options",
- "AvgAboveFoldTitle": "Avg. Above Fold %spx",
- "XSamples": "%s samples",
- "AvgAboveFoldDescription": "On average visitors see the content above this line without scrolling",
- "NoSessionRecordedYet": "No session has been recorded yet. If there are supposed to be recorded sessions by now, maybe the configured entry page does not match any page on your website. It is also recommended to check the \"System Check\" under \"Administration\" (only Super Users) to see if your system is configured to automatically recorded sessions.",
- "NoHeatmapSamplesRecordedYet": "No page view has been recorded yet for this heatmap. If there are supposed to be recordings by now, maybe the page targets configured for this heatmap do not match any page on your website. It is also recommended to check the \"System Check\" under \"Administration\" (only Super Users) to see if your system is configured to automatically track Heatmaps.",
- "NoSessionRecordedYetWithoutSystemConfiguration": "No session has been recorded yet. If there are supposed to be recorded sessions by now, maybe the configured entry page does not match any page on your website.",
- "NoHeatmapSamplesRecordedYetWithoutSystemConfiguration": "No page view has been recorded yet for this heatmap. If there are supposed to be recordings by now, maybe the page targets configured for this heatmap do not match any page on your website.",
- "NoHeatmapScreenshotRecordedYet": "There have been %1$s samples recorded so far. However, no screenshot has been taken yet. If there is a \"%2$s\" for this heatmap, it may take a while for the screenshot to become available as a user first needs to open this screenshot URL. Depending on the sample rate this may take a while.",
- "ColumnPageviewsDocumentation": "The number of page views that were recorded during this session.",
- "ColumnBrowserDocumentation": "The browser the visitor was using when this session was recorded.",
- "ColumnDeviceDocumentation": "The device the visitor was using when this session was recorded.",
- "ColumnLocationDocumentation": "The location of the visitor when this session was recorded.",
- "ColumnOperatingSystem": "OS",
- "ColumnTime": "Time",
- "ColumnLabelRecordedSessions": "Entry URL → Exit URL",
- "ColumnOperatingSystemDocumentation": "The operating system the visitor was using when this session was recorded.",
- "ColumnActionsDocumentation": "Play the recorded session or remove the recording permanently.",
- "ColumnTimeDocumentation": "The time the recording started in the timezone of the website.",
- "ColumnTimeOnSiteDocumentation": "How much time the visitor spent in total while the session was recorded",
- "ColumnTimeOnPageDocumentation": "How much time the visitor spent on this page while the session was recorded",
- "ColumnResolutionDocumentation": "The number of pixels of the visible viewport in the browser window when the user opened this page.",
- "UrlXDoesNotLookLikeUrl": "The %s does not look like a URL. Make sure it contains a protocol like http:\/\/ or that it starts with \/\/",
- "ReportRecordedSessionsDocumentation": "A list of all sessions that have been recorded. You can replay any recorded session.",
- "NoHeatmapsConfiguredInfo": "There are currently no active heatmaps. To view heatmaps, please ask a user with at least admin access to create a new heatmap.",
- "NoSessionRecordingsConfiguredInfo": "There are currently no active session recordings. To record new sessions, please ask a user with at least admin access to create a new recording.",
- "NotSupportedBrowser": "This browser is not supported, please use a later version or try a different browser.",
- "HeatmapWidth": "Heatmap width",
- "Width": "Width",
- "Action": "Action",
- "DeviceType": "Device Type",
- "BreakpointX": "Breakpoint %s",
- "BreakpointGeneralHelp": "The device type is automatically detected when a visitor views your website. However, sometimes it might not be possible to detect the device type. If your website is responsive and your layout breaks at a certain point, we will put desktop users with a small resolution into this device type for more accurate heatmaps. Any width lower than this value will be put into this device type. Set it to zero if your website is not responsive.",
- "BreakpointGeneralHelpManage": "A user with Super User access can change the default values for these breakpoints in Administration => General Settings.",
- "CaptureKeystrokes": "Capture keystrokes",
- "CaptureKeystrokesHelp": "If enabled, any text that is entered into text form fields are recorded. While the text is recorded, any character a user enters is replaced with a star ('*'). You may whitelist certain fields to be recorded in plain text by specifying a 'data-matomo-unmask' attribute on a form field. However, password fields and many other fields which could potentially include personal information (address, email, credit card information, username, phone number, …) will be always automatically masked if detected by us as such (%1$slearn more%2$s). Please note that when you enable this feature, you may record personal data which may affect GDPR.",
- "PersonalInformationNote": "Please note a %1$s may record personal or sensitive information. If you wish to hide certain content on your website or app, you may tag this content by adding a %2$sdata-matomo-mask%3$s attribute to the HTML markup. %4$sLearn more%5$s",
- "RequiresActivity": "Requires activity",
- "RequiresActivityHelp": "If enabled, only sessions that have a scroll and a click activity in one page view will be recorded. This prevents the recording of sessions with not much activity.",
- "SampleRate": "Sample Rate",
- "HeatmapSampleRateHelp": "Also known as 'traffic'. When you select 100%%, all visitors that visit the selected target page will be recorded. When you select for example 10%%, only every 10th visitor will be recorded. The lower the percentage you select, the longer it will take to reach the selected sample limit.",
- "SessionSampleRateHelp": "Also known as 'traffic'. When you select 100%%, all sessions will be recorded as soon as they have reached the target page. When you select for example 10%%, only every 10th session will be recorded. The lower the percentage you select, the longer it will take to reach the selected sample limit.",
- "MinSessionTime": "Min Session Time",
- "MinSessionTimeHelp": "A session will be only recorded when a visitor has spent at least the specified time on a page.",
- "ScreenshotUrl": "Screenshot URL",
- "ScreenshotUrlHelp": "By default, a screenshot is taken when the first visitor views the target page. If different page URLs match your target page, you can specify a specific URL that should be used to capture the screenshot. The heatmap visualization will be only available once at least one visitor has visited this URL whose activities are recorded. If defined, the URL has to match exactly. To ignore the protocol, start the URL with two double slashes (\/\/example.com). Please note that this URL cannot be changed as soon as a screenshot has been taken.",
- "GettingStarted": "Getting Started",
- "TargetPageTestTitle": "URL validator",
- "TargetPageTestLabel": "Enter a full URL including the protocol to check if activities will be recorded on this URL:",
- "TargetPageTestErrorInvalidUrl": "Enter a URL including a protocol.",
- "TargetPageTestUrlMatches": "Activities will be recorded on this URL",
- "TargetPageTestUrlNotMatches": "Activities will not be recorded on this URL",
- "UrlParameterValueToMatchPlaceholder": "Value to match for URL parameter name",
- "TargetTypeIsAny": "is any",
- "TargetTypeIsNot": "not %s",
- "TargetTypeEqualsExactly": "equals exactly",
- "TargetTypeEqualsExactlyInfo": "The value has to match exactly, including the URL protocol, search query and hash.",
- "TargetTypeEqualsSimple": "equals simple",
- "TargetTypeEqualsSimpleInfo": "The URL will match any protocol (eg http and https) with or without \"www.\" subdomain. Any trailing slash in the path as well as the search query and hash part of the URL will be ignored when matching the URL.",
- "TargetTypeContains": "contains",
- "TargetTypeExists": "exists",
- "TargetTypeStartsWith": "starts with",
- "TargetTypeRegExp": "matches the regular expression",
- "TargetTypeRegExpInfo": "Any regular expression, for example \"^(.*)test(.*)$\".",
- "UpdatingData": "Updating data…",
- "FieldIncludedTargetsHelp": "Targets allow you to define on which pages the user activities should be recorded. You can define one or more conditions. For example, you can define to record activities whenever the URL or path equals a certain value and only if a certain URL parameter is present in the URL. Activities will be recorded only when all conditions are met, not if only one of them is met. All conditions will be evaluated case-insensitive. When you select 'equals simple', the URL protocol, search parameters and a trailing slash will be ignored.",
- "FieldIncludedTargetsHelpSessions": "Targets allow you to configure to only start the recording of a session as soon as a visitor has reached a certain page. This lets you for example only record sessions that go through the check out process. You can define one or more conditions. For example, you can define to record a session only when the URL or path equals a certain value and only if a certain URL parameter is present in the URL. Sessions will be only recorded when all conditions are met on a page view, not if only one of them is met. All conditions will be evaluated case-insensitive. When you select 'equals simple', the URL protocol, search parameters and a trailing slash will be ignored.",
- "DeleteHeatmapScreenshotConfirm": "Are you sure you want to delete this screenshot? It may take a while for the screenshot to be re-taken.",
- "DeleteHeatmapConfirm": "Are you sure you want to delete this heatmap? Reports previously generated heatmaps will no longer be available in the reporting UI.",
- "DeleteSessionRecordingConfirm": "Are you sure you want to delete this session recording? Recordings previously generated will no longer be available in the reporting UI.",
- "EndSessionRecordingConfirm": "Are you sure you want to stop recording any new sessions?",
- "EndHeatmapConfirm": "Are you sure you want to stop capturing any activities for this heatmap?",
- "ErrorInvalidRegExp": "The regular expression \"%1$s\" does not have a valid format.",
- "ErrorHeatmapNameDuplicate": "The heatmap name is already in use by another heatmap.",
- "ErrorHeatmapDoesNotExist": "The requested heatmap does not exist",
- "ErrorSessionRecordingDoesNotExist": "The requested session recording does not exist",
- "ErrorSessionRecordingDisabled": "The session recording feature is disabled. To use this feature it needs to be enabled by a Matomo super user in the general settings.",
- "ErrorHeatmapRecordingDisabled": "The heatmap recording feature is disabled. To use this feature it needs to be enabled by a Matomo super user in the general settings.",
- "ErrorXNotProvided": "Please provide a value for \"%1$s\".",
- "ErrorXTooLow": "\"%1$s\" is too low, minimum allowed value is %2$s.",
- "ErrorXTooHigh": "\"%1$s\" is too high, maximum allowed value is %2$s.",
- "ErrorXTooLong": "\"%1$s\" is too long, max %2$s characters are allowed.",
- "ErrorNotAnArray": "\"%1$s\" has to be an array.",
- "ErrorInnerIsNotAnArray": "Each \"%1$s\" within \"%2$s\" has to be an array.",
- "ErrorArrayMissingKey": "Missing array key \"%1$s\" in \"%2$s\" at position \"%3$s\".",
- "ErrorArrayMissingValue": "Missing value for array key \"%1$s\" in \"%2$s\" at position \"%3$s\".",
- "ErrorXNotWhitelisted": "The value for \"%1$s\" is not allowed, use one of: %2$s.",
- "ErrorXContainsWhitespace": "The \"%1$s\" is not allowed to contain any white space.",
- "ErrorPageRuleRequired": "At least one page rule has to be set.",
- "HeatmapCreated": "The heatmap has been successfully created.",
- "HeatmapUpdated": "The heatmap has been successfully updated.",
- "SessionRecordingCreated": "The session recording has been successfully created.",
- "SessionRecordingUpdated": "The session recording has been successfully updated.",
- "FieldNamePlaceholder": "eg 'Sign Up Page'",
- "PageRule": "Page Rule",
- "HeatmapNameHelp": "Defines the name under which the report for this heatmap will be available.",
- "SessionNameHelp": "Defines the name under which the session recordings will be available.",
- "CreateNewHeatmap": "Create new heatmap",
- "StopX": "Stop recording any new activities for this %s.",
- "EditX": "Edit %s",
- "DeleteX": "Delete %s",
- "EditHeatmapX": "Edit Heatmap %s",
- "Filter": "Filter",
- "EditSessionRecordingX": "Edit Session Recording %s",
- "CreateNewSessionRecording": "Create new session recording",
- "TargetAttributeUrl": "URL",
- "TargetAttributePath": "Path",
- "FilesystemDirectory": "directory",
- "TargetAttributeUrlParameter": "URL Parameter",
- "TargetAttributeUrlParameterExample": "nameOfUrlParameter",
- "ManageSessionRecordings": "Manage Session Recordings",
- "ManageHeatmaps": "Manage Heatmaps",
- "ConfigsPhpSuccess": "'%s' is accessible",
- "ConfigsPhpNotAccessible": "The URL '%s' seems to be not accessible from the Internet or Intranet.",
- "ConfigsPhpErrorResult": "As a result, tracking Heatmaps and Session Recordings may not work. You may need to change your webserver configuration to allow access to this file via the Internet or Intranet.",
- "ConfigsPhpSelfSignedError": "Requesting '%s' resulted in an SSL error. Maybe you are using a self signed certificate?",
- "ConfigsInternetDisabled": "We couldn't check if '%s' is accessible because the internet connection is disabled on this Matomo.",
- "ConfigsPhpUnknown": "We couldn't check if '%s' is accessible over the Internet or Intranet.",
- "ConfigsPhpManualCheck": "Please open the URL manually in a browser to see if the response contains 'Piwik.HeatmapSessionRecording'. If not, you might need to modify your server configuration as this file needs to be accessible via a browser from the Internet or Intranet.",
- "ConfigsPhpUnknownError": "Requesting '%1$s' resulted in an error: %2$s.",
- "ManageHeatmapSubcategoryHelp": "Heatmaps let you record all clicks, mouse moves, and scroll activities of your visitors on a specific page. This section enables you to create and manage Heatmap tracking for your website.",
- "ManageSessionRecordingSubcategoryHelp": "Session recordings let you record all activities of a real visitor during their session (visit) such as clicks, mouse movements, scrolls, window resizes, page changes, and form interactions. This section enables you to create and manage Session Recordings for your site.",
- "DisableSessionRecordingTitle": "Disable Session Recording",
- "DisableSessionRecordingDescription": "By disabling this feature no session recordings can be configured or tracked anymore.",
- "DisableSessionRecordingInlineHelp": "%1$sNote: Disabling this feature will delete all session recordings including previously tracked session recordings. There will be no changes for heatmaps if you enable or disable this feature.%2$s",
- "PeriodDisabledErrorMessage": "The \"%1$s\" period is disabled but required for this feature to work. Select a different period or ask a system administrator to change Matomo's config file 'config\/config.ini.php' to allow the \"%1$s\" period in the \"enabled_periods_API\" setting.",
- "DisableHeatmapRecordingTitle": "Disable Heatmaps",
- "DisableHeatmapRecordingDescription": "By disabling this feature no heatmap can be configured or recorded anymore.",
- "DisableHeatmapRecordingInlineHelp": "%1$sNote: Disabling this feature will delete all heatmaps including previously tracked heatmaps. There will be no changes for session recording if you enable or disable this feature.%2$s",
- "EnableIncludeCountriesTitle": "Only track visitors from specific countries",
- "EnableIncludeCountriesDescription": "By default, all visits are recorded. Enable this section when you want to track visits from specific countries.",
- "Country": "Country",
- "HeatmapInfoTrackVisitsFromCountries": "The heatmap is configured to only track visits from %1$s.",
- "SessionRecordingInfoTrackVisitsFromCountries": "The session recording is configured to only track visits from %1$s.",
- "AdBlockerDetected": "%1$s might not be visible when using an ad blocker. Please disable your ad blocker on this site to make sure Matomo works without any issues.",
- "PauseReason": "The %1$s was paused due to exhaustion of your quota, please contact support for further details.",
- "TotalEvents": "Events",
- "ColumnTotalEventsDocumentation": "The number of events that were recorded during this session. For example: click, move, scroll, resize, page changes and form interaction events.",
- "CaptureDomTitle": "Capture Heatmap Snapshot Manually",
- "CaptureDomInlineHelp": "By default, heatmap snapshots are automatically taken on page load. If your page dynamically loads elements (such as with lazy loading) your snapshot could be incomplete. You can manually capture a complete snapshot by enabling this option and executing the following Javascript code in your browser: %1$s %2$sNote:%3$s This option resets automatically after capturing the snapshot.",
- "MatomoJSNotWritableErrorMessage": "HeatmapSessionRecording: %1$s are currently unable to track visits, because the matomo.js file is read-only. Please see the %2$sdocumentation%3$s about how to make the file writable.",
- "Clicks": "Clicks:",
- "ClickRate": "Click rate:",
- "Moves": "Moves:",
- "MoveRate": "Move rate:",
- "HeatmapAddedActivity": "added a heatmap \"%1$s\" for site \"%2$s\"",
- "HeatmapDeletedActivity": "deleted the heatmap \"%1$s\" for site \"%2$s\"",
- "HeatmapPausedActivity": "paused the heatmap \"%1$s\" for site \"%2$s\"",
- "HeatmapResumedActivity": "resumed the heatmap \"%1$s\" for site \"%2$s\"",
- "HeatmapEndedActivity": "ended the heatmap \"%1$s\" for site \"%2$s\"",
- "HeatmapScreenshotDeletedActivity": "deleted the heatmap screenshot \"%1$s\" for site \"%2$s\"",
- "HeatmapUpdatedActivity": "updated the heatmap \"%1$s\" for site \"%2$s\"",
- "SessionRecordingAddedActivity": "added a session recording \"%1$s\" for site \"%2$s\"",
- "SessionRecordingDeletedActivity": "deleted the session recording \"%1$s\" for site \"%2$s\"",
- "SessionRecordingEndedActivity": "ended the session recording \"%1$s\" for site \"%2$s\"",
- "SessionRecordingUpdatedActivity": "updated the session recording \"%1$s\" for site \"%2$s\"",
- "SessionRecordingPausedActivity": "paused the session recording \"%1$s\" for site \"%2$s\"",
- "SessionRecordingResumedActivity": "resumed the session recording \"%1$s\" for site \"%2$s\"",
- "RecordedSessionDeletedActivity": "deleted a recorded session for session recording \"%1$s\" for site \"%2$s\"",
- "RecordedPageviewDeletedActivity": "deleted a recorded page view for session recording \"%1$s\" for site \"%2$s\""
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less
deleted file mode 100644
index c7c1189..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less
+++ /dev/null
@@ -1,35 +0,0 @@
-.manageHsr {
- .filterStatus, .hsrSearchFilter {
- display: inline-block;
- width:200px;
- }
-
- div.filterStatus {
- margin: 0 -0.75em;
- display: inline-block;
-
- .theWidgetContent & {
- margin: 0;
- }
- }
-
- th.action, td.action {
- width: 250px;
-
- a {
- color: black;
- }
- }
-
- .index {
- width: 60px;
- }
-
- a.table-action {
- margin-right: 3.5px;
- }
-
- a.table-action:last-child {
- margin-right: 0;
- }
-}
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js b/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js
deleted file mode 100644
index 91383a4..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js
+++ /dev/null
@@ -1,125 +0,0 @@
-(function(){var N=1;var aH=9;var o=10;var P=8;var w=3;var ax=["button","submit","reset"];
-/*!!
- * Copyright (C) 2015 Pavel Savshenko
- * Copyright (C) 2011 Google Inc. All rights reserved.
- * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
- * Copyright (C) 2008 Matt Lilek
- * Copyright (C) 2009 Joseph Pecoraro
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- * its contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-;
-var i={};i.cssPath=function(aT,aR){if(aT.nodeType!==N){return""}var aQ=[];var aP=aT;while(aP){var aS=i._cssPathStep(aP,!!aR,aP===aT);if(!aS){break}aQ.push(aS);if(aS.optimized){break}aP=aP.parentNode}aQ.reverse();return aQ.join(" > ")};i._cssPathStep=function(a4,aW,a3){if(a4.nodeType!==N){return null}var a2=a4.getAttribute("id");if(aW){if(a2){return new i.DOMNodePathStep(ba(a2),true)}var aQ=a4.nodeName.toLowerCase();if(aQ==="body"||aQ==="head"||aQ==="html"){return new i.DOMNodePathStep(a4.nodeName.toLowerCase(),true)}}var aP=a4.nodeName.toLowerCase();if(a2){return new i.DOMNodePathStep(aP.toLowerCase()+ba(a2),true)}var aX=a4.parentNode;if(!aX||aX.nodeType===aH){return new i.DOMNodePathStep(aP.toLowerCase(),true)}function bg(bi){var bj=bi.getAttribute("class");if(!bj){return[]}return bj.split(/\s+/g).filter(Boolean).map(function(bk){return"$"+bk})}function ba(bi){return"#"+bf(bi)}function bf(bj){if(bb(bj)){return bj}var bi=/^(?:[0-9]|-[0-9-]?)/.test(bj);var bk=bj.length-1;return bj.replace(/./g,function(bm,bl){return((bi&&bl===0)||!aR(bm))?aT(bm,bl===bk):bm
-})}function aT(bj,bi){return"\\"+a1(bj)+(bi?"":" ")}function a1(bj){var bi=bj.charCodeAt(0).toString(16);if(bi.length===1){bi="0"+bi}return bi}function aR(bi){if(/[a-zA-Z0-9_\-]/.test(bi)){return true}return bi.charCodeAt(0)>=160}function bb(bi){return/^-?[a-zA-Z_][a-zA-Z0-9_\-]*$/.test(bi)}function a6(bi){var bk={},bj;for(bj=0;bj>>0};aP.prototype.nodeId=function(aQ){var aR=aQ[aP.ID_PROP];if(!aR){aR=aQ[aP.ID_PROP]=aP.nextId_++}return aR};aP.prototype.set=function(aQ,aR){var aS=this.nodeId(aQ);this.nodes[aS]=aQ;this.values[aS]=aR};aP.prototype.get=function(aQ){var aR=this.nodeId(aQ);return this.values[aR]};aP.prototype.has=function(aQ){return this.nodeId(aQ) in this.nodes};aP.prototype["delete"]=function(aQ){var aR=this.nodeId(aQ);delete this.nodes[aR];this.values[aR]=undefined};aP.prototype.keys=function(){var aQ=[];for(var aR in this.nodes){if(!this.isIndex(aR)){continue}aQ.push(this.nodes[aR])}return aQ};aP.ID_PROP="__mutation_summary_node_map_id__";aP.nextId_=1;return aP})();var aC;
-(function(aP){aP[aP.STAYED_OUT=0]="STAYED_OUT";aP[aP.ENTERED=1]="ENTERED";aP[aP.STAYED_IN=2]="STAYED_IN";aP[aP.REPARENTED=3]="REPARENTED";aP[aP.REORDERED=4]="REORDERED";aP[aP.EXITED=5]="EXITED"})(aC||(aC={}));function B(aP){return aP===aC.ENTERED||aP===aC.EXITED}var ab=(function(){function aP(aW,aV,aR,aT,aS,aU,aQ,aX){if(aV===void 0){aV=false}if(aR===void 0){aR=false}if(aT===void 0){aT=false}if(aS===void 0){aS=null}if(aU===void 0){aU=false}if(aQ===void 0){aQ=null}if(aX===void 0){aX=null}this.node=aW;this.childList=aV;this.attributes=aR;this.characterData=aT;this.oldParentNode=aS;this.added=aU;this.attributeOldValues=aQ;this.characterDataOldValue=aX;this.isCaseInsensitive=this.node.nodeType===N&&this.node instanceof HTMLElement&&this.node.ownerDocument instanceof HTMLDocument}aP.prototype.getAttributeOldValue=function(aQ){if(!this.attributeOldValues){return undefined}if(this.isCaseInsensitive){aQ=aQ.toLowerCase()}return this.attributeOldValues[aQ]};aP.prototype.getAttributeNamesMutated=function(){var aR=[];
-if(!this.attributeOldValues){return aR}for(var aQ in this.attributeOldValues){aR.push(aQ)}return aR};aP.prototype.attributeMutated=function(aR,aQ){this.attributes=true;this.attributeOldValues=this.attributeOldValues||{};if(aR in this.attributeOldValues){return}this.attributeOldValues[aR]=aQ};aP.prototype.characterDataMutated=function(aQ){if(this.characterData){return}this.characterData=true;this.characterDataOldValue=aQ};aP.prototype.removedFromParent=function(aQ){this.childList=true;if(this.added||this.oldParentNode){this.added=false}else{this.oldParentNode=aQ}};aP.prototype.insertedIntoParent=function(){this.childList=true;this.added=true};aP.prototype.getOldParent=function(){if(this.childList){if(this.oldParentNode){return this.oldParentNode}if(this.added){return null}}return this.node.parentNode};return aP})();var ao=(function(){function aP(){this.added=new J();this.removed=new J();this.maybeMoved=new J();this.oldPrevious=new J();this.moved=undefined}return aP})();var W=(function(aQ){b(aP,aQ);
-function aP(aU,aS){aQ.call(this);this.rootNode=aU;this.reachableCache=undefined;this.wasReachableCache=undefined;this.anyParentsChanged=false;this.anyAttributesChanged=false;this.anyCharacterDataChanged=false;for(var aR=0;aR1){throw Error("Invalid request option. all has no options.")}aU.queries.push({all:true});continue}if("attribute" in aT){var aV={attribute:j(aT.attribute)};aV.elementFilter=aG.parseSelectors("*["+aV.attribute+"]");if(Object.keys(aT).length>1){throw Error("Invalid request option. attribute has no options.")}aU.queries.push(aV);continue}if("element" in aT){var aS=Object.keys(aT).length;var aV={element:aT.element,elementFilter:aG.parseSelectors(aT.element)};if(aT.hasOwnProperty("elementAttributes")){aV.attributeList=az(aT.elementAttributes);aS--}if(aS>1){throw Error("Invalid request option. element only allows elementAttributes option.")}aU.queries.push(aV);continue}if(aT.characterData){if(Object.keys(aT).length>1){throw Error("Invalid request option. characterData has no options.")
-}aU.queries.push({characterData:true});continue}throw Error("Invalid request option. Unknown query request.")}return aU};aP.prototype.createSummaries=function(aR){if(!aR||!aR.length){return[]}var aQ=new K(this.root,aR,this.elementFilter,this.calcReordered,this.options.oldPreviousSibling);var aT=[];for(var aS=0;aS=0){aV={}}else{if(aU.attributes.name){var a3=String(aU.attributes.name.value).toLowerCase();if(a3.indexOf("twitter:")>=0||a3.indexOf("description")>=0||a3.indexOf("keywords")>=0){aV={}}}}}else{if("LINK"===aU.tagName){if(aU.attributes.rel){var a2=String(aU.attributes.rel.value).toLowerCase();
-var a1=["icon","preload","preconnect","dns-prefetch","next","prev","alternate","search"];if(a1.indexOf(a2)>=0){aV={}}}if(aU.attributes.href){var aT=String(aU.attributes.href.value).toLowerCase().indexOf(".scr.kaspersky-labs.com");if(aT>5&&aT<=20){aV={}}}if(aU.href){if(typeof I.URL==="function"){var aS=ak.onCssLoaded(aU.href);var aQ=3;if(!aS){aR(aU.href);function aR(a5){if(aQ>0){setTimeout(function(){aQ--;aS=ak.onCssLoaded(aU.href);if(!aS){aR(aU.href)}},300)}}}}aV.url=aU.href}}}}return aV};aP.prototype.serializeAddedAndMoved=function(aT,aQ,aU){var aW=this;var aS=aT.concat(aQ).concat(aU);var aV=new d.NodeMap();aS.forEach(function(aZ){var aY=aZ.parentNode;var aX=aV.get(aY);if(!aX){aX=new d.NodeMap();aV.set(aY,aX)}aX.set(aZ,true)});var aR=[];aV.keys().forEach(function(aY){var aX=aV.get(aY);var a0=aX.keys();while(a0.length){var aZ=a0[0];while(aZ.previousSibling&&aX.has(aZ.previousSibling)){aZ=aZ.previousSibling}while(aZ&&aX.has(aZ)){var a1=aW.serializeNode(aZ);a1.previousSibling=aW.serializeNode(aZ.previousSibling);
-a1.parentNode=aW.serializeNode(aZ.parentNode);aR.push(a1);aX["delete"](aZ);aZ=aZ.nextSibling}var a0=aX.keys()}});return aR};aP.prototype.serializeAttributeChanges=function(aQ){var aS=this;var aR=new d.NodeMap();Object.keys(aQ).forEach(function(aT){aQ[aT].forEach(function(aW){var aU=aR.get(aW);if(!aU){aU=aS.serializeNode(aW);aU.attributes={};aR.set(aW,aU)}var aV=aN.shouldMaskElementRecursive(aW);var aX=aS.getAttributesFromNode(aW,aV.isIgnoredField,aV.isIgnoredContent);aU.attributes[aT]=aT in aX?aX[aT]:null})});return aR.keys().map(function(aT){return aR.get(aT)})};aP.prototype.applyChanged=function(aT){var aW=this;var aR=aT[0];var aU=aR.removed.map(function(aX){return aW.serializeNode(aX)});var aS=this.serializeAddedAndMoved(aR.added,aR.reparented,aR.reordered);var aQ=this.serializeAttributeChanges(aR.attributeChanged);var aV=aR.characterDataChanged.map(function(aY){var aZ=aW.serializeNode(aY);if(aY.nodeType===w&&aY.parentNode){aY=aY.parentNode}var aX=aN.shouldMaskElementRecursive(aY,false,false);
-aZ.textContent=aN.getMaskedTextContent(aY,aX.isIgnoredField,aX.isIgnoredContent);return aZ});this.mirror.applyChanged(aU,aS,aQ,aV);aR.removed.forEach(function(aX){aW.forgetNode(aX)})};return aP})()}
-/*!!
- * Copyright (C) InnoCraft Ltd - All rights reserved.
- *
- * All information contained herein is, and remains the property of InnoCraft Ltd.
- *
- * @link https://www.innocraft.com/
- * @license For license details see https://www.innocraft.com/license
- */
-;var O=document;var I=window;var F=0;var l=false;var L=!u();var am=true;var n=null;var at=false;var ad="";var T=false;var g=15*60*1000;var aa=30*60*1000;var S=10;var av=(5*60*1000);var aA=2000;var C=1000;var H=100;var Y=500;var G=false;function u(){if("object"!==typeof JSON){return true}if("function"!==typeof Array.prototype.map||"function"!==typeof Array.prototype.filter||"function"!==typeof Array.prototype.indexOf){return true}if("function"!==typeof Element.prototype.getBoundingClientRect){return true}var aP=["cc.bingj.com"];
-if(aP.indexOf(O.domain)!==-1||String(O.domain).indexOf(".googleusercontent.com")!==-1){return true}var aR=/alexa|baidu|bing|bot|crawler|curl|crawling|duckduckgo|facebookexternalhit|feedburner|googlebot|google web preview|linkdex|nagios|postrank|pingdom|robot|slurp|spider|yahoo!|yandex|wget/i.test(navigator.userAgent);if(aR){return true}var aQ=String(O.referrer);if(aQ&&aQ.indexOf("module=Overlay&action=startOverlaySession")>=0){return true}return false}function U(){if(l&&"object"===typeof console){if(typeof console.debug==="function"){console.debug.apply(console,arguments)}else{if(typeof console.log==="function"){console.log.apply(console,arguments)}}}}var D=function(){return true};var s=1;var aJ=2;var h=3;var V=4;var aK=5;var ag=6;var a=7;var k=8;var e=9;var ar=10;var p=11;var ay=12;var aF=13;var aD=0;var af=1;var c=2;var aI=true;var Q=false;var an=false;var aO=true;var M=null;var A=false;var ae={};if("object"===typeof JSON){ae=JSON}var aj=false;var al=[];var aL={hasObserver:function(){if(typeof WebKitMutationObserver!=="undefined"){return true
-}else{if(typeof MutationObserver!=="undefined"){return true}}return false}};var ai=aL.hasObserver();var r={getScrollLeft:function(){return I.document.body.scrollLeft||I.document.documentElement.scrollLeft},getScrollTop:function(){return I.document.body.scrollTop||I.document.documentElement.scrollTop},getDocumentHeight:function(){return au.safeMathMax([O.body.offsetHeight,O.body.scrollHeight,O.documentElement.offsetHeight,O.documentElement.clientHeight,O.documentElement.scrollHeight,1])},getDocumentWidth:function(){return au.safeMathMax([O.body.offsetWidth,O.body.scrollWidth,O.documentElement.offsetWidth,O.documentElement.clientWidth,O.documentElement.scrollWidth,1])},getWindowSize:function(){var aP=I.innerHeight||O.documentElement.clientHeight||O.body.clientHeight;var aQ=I.innerWidth||O.documentElement.clientWidth||O.body.clientWidth;return{width:aQ,height:aP}}};var t={namespace:"hsr",set:function(aR,aV,aT){aV=parseInt(aV,10);aT=parseInt(aT,10);var aU="";var aQ=t.getHsrConfigs(aR);var aS=false;
-for(var aP=0;aP2){return true}}}if(aY){var aX=aP.parentNode?aP.parentNode:null;var aQ=false;while(aX){if(aN.hasAttribute(aX,"data-piwik-mask")||aN.hasAttribute(aX,"data-matomo-mask")){return true}else{if(!aQ&&aX&&aN.hasAttribute(aX,"data-matomo-unmask")){aQ=true}aX=aX.parentNode?aX.parentNode:null}}if(aQ){return false}}if(aN.hasAttribute(aP,"data-matomo-unmask")){return false}if(aT){return false}return true},shouldMaskContent:function(aR,aQ){if(!aR){return false}if(aR.tagName&&aR.tagName!=="FORM"&&aN.hasAttribute(aR,"data-matomo-mask")){return true}if(aR.tagName&&aR.tagName!=="FORM"&&aN.hasAttribute(aR,"data-matomo-unmask")){return false
-}if(aQ){var aP=aR.parentNode?aR.parentNode:null;while(aP){if(aR.nodeName==="#text"&&aN.hasAttribute(aP,"data-matomo-unmask")){return false}else{if(aP.tagName!=="FORM"&&aN.hasAttribute(aP,"data-matomo-mask")){return true}else{aP=aP.parentNode?aP.parentNode:null}}}}return false},isAllowedInputType:function(aP){return(aP.type&&ax.indexOf(aP.type)!==-1&&!aN.hasAttribute(aP,"data-piwik-mask")&&!aN.hasAttribute(aP,"data-matomo-mask"))}};var au={safeMathMax:function(aP){var aQ=[];var aR;for(aR=0;aR0}function x(){return f("pk_hsr_forcesample=1")||f("pk_hsr_capturescreen=1")}function v(){return f("pk_hsr_forcesample=0")}function Z(aP){if(x()){return true}if(v()){return false}if(aP>=100){return true}if(aP<=0){return false}if(aP>=1){return aP>=au.getRandomInt(1,H)}return(aP*10)>=au.getRandomInt(1,H*10)}function q(aP){if("undefined"!==typeof aP.HeatmapSessionRecording){return}aP.HeatmapSessionRecording={myId:au.generateUniqueId(),hasReceivedConfig:false,hasRequestedConfig:false,hasTrackedData:false,hasSentStopTrackingEvent:false,enabled:true,hsrIdsToGetDOM:[],disable:function(){this.enabled=false},enable:function(){this.enabled=true
-},isEnabled:function(){return L&&this.enabled},numSentTrackingRequests:0,Heatmap:{data:[],hsrids:[],configs:[],addConfig:function(aQ){if("object"!==typeof aQ||!aQ.id){return}aQ.id=parseInt(aQ.id,10);this.configs.push(aQ);if("undefined"===typeof aQ.sample_rate){aQ.sample_rate=H}else{aQ.sample_rate=Math.min(parseFloat(aQ.sample_rate),H)}if(aQ.id&&Z(aQ.sample_rate)&&D(aQ)){this.addHsrId(aQ.id);if((aQ.getdom&&!aQ.capture_manually)||f("pk_hsr_capturescreen=1")){aP.HeatmapSessionRecording.hsrIdsToGetDOM.push(aQ.id)}}},addHsrId:function(aQ){this.hsrids.push(aQ);if(aP.HeatmapSessionRecording.hasTrackedData){z.recordData(af,{ty:a,id:aQ})}}},Both:{data:[]},Session:{data:[],hsrids:[],configs:[],addConfig:function(aS){if("object"!==typeof aS||!aS.id){return}aS.id=parseInt(aS.id,10);if("undefined"===typeof aS.sample_rate){aS.sample_rate=H}else{aS.sample_rate=Math.min(parseFloat(aS.sample_rate),H)}aS.conditionsMet=false;this.configs.push(aS);var aR=parseInt(aP.getSiteId(),10);var aT=t.get(aP,aS.id);if(1===aT&&!v()){aS.sample_rate=H;
-aS.activity=false;aS.min_time=0}else{if(x()){}else{if(0===aT||!Z(aS.sample_rate)){t.set(aP,aS.id,0);return}}}this.checkConditionsMet();if(aS.min_time){var aQ=this;Piwik.DOM.onReady(function(){var aU=(aS.min_time*1000)-au.getTimeSincePageReady()+120;if(aU>=0){setTimeout(function(){aQ.checkConditionsMet()},aU)}else{aQ.checkConditionsMet()}})}},checkConditionsMet:function(){var aR;for(var aS=0;aS=au.roundTimeToSeconds(au.getTimeSincePageReady())){aQ=false}if(aR.activity&&!an){an=r.getDocumentHeight()<=r.getWindowSize().height}if(aR.activity&&(!Q||!an)){aQ=false}if(aQ){aR.conditionsMet=true;if(D(aR)){if("undefined"===typeof aR.keystrokes||!aR.keystrokes||aR.keystrokes==="0"){aI=false}this.addHsrId(aR.id)}}}}},addHsrId:function(aQ){this.hsrids.push(aQ);if(aP.HeatmapSessionRecording.hasTrackedData){z.recordData(c,{ty:a,id:aQ})}var aR=parseInt(aP.getSiteId(),10);t.set(aP,aQ,1)}},addConfig:function(aQ){this.hasRequestedConfig=true;
-this.hasReceivedConfig=true;if("undefined"===typeof aQ||!aQ){aM.checkAllConfigsReceived();return}if("object"===typeof aQ.heatmap){this.Heatmap.addConfig(aQ.heatmap)}var aR;if(aQ.heatmaps&&au.isArray(aQ.heatmaps)&&aQ.heatmaps.length){for(aR=0;aR=0;aR--){if(aQ[aR]&&aQ[aR].ty&&aQ[aR].ty===e){aQ.splice(aR,1)}}}}}if(aP.length&&aT.Both.data.length){aQ=aQ.concat(aT.Both.data);aT.Both.data=[]}if("undefined"===typeof aX){aX=this.shouldEndRecording(aW)}if(aX&&aT.hasTrackedData&&!aT.hasSentStopTrackingEvent&&aV){aQ.push({ty:p});aT.hasSentStopTrackingEvent=true}if(!aP||!aP.length||!aQ||!aQ.length){return}if(aW.HeatmapSessionRecording.hsrIdsToGetDOM&&aW.HeatmapSessionRecording.hsrIdsToGetDOM.length){if(!ak.initialDOM&&ai){var aU=new y(O,{initialize:function(aY,aZ){ak.initialDOM=ae.stringify({rootId:aY,children:aZ})}});aU.disconnect()}if(ak.initialDOM&&ai){for(var aS=0;aSM)&&aQ.ty&&aQ.ty!==ag){M=aQ.ti}if(aD===aP){aS.HeatmapSessionRecording.Both.data.push(aQ)}else{if(af===aP){aS.HeatmapSessionRecording.Heatmap.data.push(aQ)}else{if(c===aP){aS.HeatmapSessionRecording.Session.data.push(aQ)}}}}});if(l){U("recorddata",ae.stringify(aQ))}},stopSendingData:function(){var aP=z.getPiwikTrackers();aP.forEach(function(aQ){if(aQ.HeatmapSessionRecording){var aR=aQ.HeatmapSessionRecording;
-if("undefined"!==typeof aR.trackingInterval){clearInterval(aR.trackingInterval);delete aR.trackingInterval}}})},startSendingData:function(){var aP=z.getPiwikTrackers();aP.forEach(function(aQ){if(aQ.HeatmapSessionRecording&&"undefined"===typeof aQ.HeatmapSessionRecording.trackingInterval){var aR=au.getRandomInt(10250,11250);aQ.HeatmapSessionRecording.trackingInterval=setInterval(function(){z.sendQueuedData(aQ)},aR);z.sendQueuedData(aQ)}})}};function aE(){
-/*!!! hsrTrackerReadyHook */
-;if(typeof window==="object"&&"function"===typeof I.piwikHeatmapSessionRecordingAsyncInit){I.piwikHeatmapSessionRecordingAsyncInit()}if(typeof window==="object"&&"function"===typeof I.matomoHeatmapSessionRecordingAsyncInit){I.matomoHeatmapSessionRecordingAsyncInit()}var aQ=al;al=[];aj=true;for(var aP=0;aPY){aY=aY.substr(0,Y)}if(aN.shouldMaskField(aS,!aN.hasAttribute(aS,"data-matomo-unmask"))){aY=aN.maskFormField(aY,aN.getAttribute(aS,"type")==="password")}}else{if(aP===ar&&"undefined"!==typeof aS.value){aY=String(aS.value)}}}var aV={ti:aR,ty:aP,s:aU,te:aY};if(aU){z.recordData(c,aV)}else{U("No selector found for text input ",aX)
-}},onScroll:function(aP){if(!an){an=true;ak.checkTrackersIfConditionsMet()}var aT=au.getTimeSincePageReady();if(aP&&aP.type&&aP.type==="scroll"&&aP.target&&aP.target!==O){var aZ=aP.target;if("undefined"===typeof aZ.scrollTop){return}var aR=aZ.scrollTop;var aU=aZ.scrollLeft;var aS=aN.getWidth(aZ);var aQ=aN.getHeight(aZ);if(aS<=0||aQ<=0||!aS||!aQ){return}var aV=aN.getSelector(aZ);ak.lastElementScroll={time:aT,selector:aV,scrollY:parseInt((C*aR)/aQ,10),scrollX:parseInt((C*aU)/aS,10)};return}var aX=parseInt(r.getScrollTop(),10);var aW=parseInt(r.getScrollLeft(),10);var a1=r.getDocumentHeight();var aY=r.getDocumentWidth();ak.lastScroll={time:aT,scrollY:parseInt((C*aX)/a1,10),scrollX:parseInt((C*aW)/aY,10)};var a0=parseInt((C*(aX+r.getWindowSize().height))/a1,10);if(a0>ak.scrollMaxPercentage){ak.scrollMaxPercentage=a0}},checkTrackersIfConditionsMet:function(){var aQ=z.getPiwikTrackers();for(var aP=0;aPaa){g=aa}},setMaxTextInputLength:function(aQ){Y=aQ},disableCaptureKeystrokes:function(){aI=false},enableCaptureKeystrokes:function(){aI=true},setMatomoTrackers:function(aQ){this.setPiwikTrackers(aQ)},setPiwikTrackers:function(aQ){if(aQ===null){n=null;return}if(!au.isArray(aQ)){aQ=[aQ]
-}n=aQ;n.forEach(q);if(aj){if(A){this.enable()}else{if(L){aM.fetch()}}}},enableDebugMode:function(){l=true}};Piwik.DOM.onReady(function(){F=new Date().getTime()});Piwik.addPlugin("HeatmapSessionRecording",{log:function(aQ){if(aO){if(aQ.tracker&&aQ.tracker.getNumTrackedPageViews&&aQ.tracker.getNumTrackedPageViews()>1){setTimeout(function(){Piwik.HeatmapSessionRecording.setNewPageView(true)},10)}}return""},unload:function(){if(!u()){var aQ=z.getPiwikTrackers();z.stopSendingData();aQ.forEach(function(aS){var aR=false;z.sendQueuedData(aS,aR)})}}});if(I.Piwik.initialized){var aP=Piwik.getAsyncTrackers();aP.forEach(q);Piwik.on("TrackerSetup",q);Piwik.retryMissedPluginCalls();aE();aM.fetch();Piwik.on("TrackerAdded",function(){if(A){Piwik.HeatmapSessionRecording.enable()}else{aM.fetch()}})}else{Piwik.on("TrackerSetup",q);Piwik.on("MatomoInitialized",function(){aE();if(L||A){aM.fetch()}Piwik.on("TrackerAdded",function(){if(L){aM.fetch()}else{if(A){Piwik.HeatmapSessionRecording.enable()}}})})}}ad=au.generateUniqueId();
-if("object"===typeof I.Piwik){ah()}else{if("object"!==typeof I.matomoPluginAsyncInit){I.matomoPluginAsyncInit=[]}I.matomoPluginAsyncInit.push(ah)}})();
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js
deleted file mode 100644
index 5468f48..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js
+++ /dev/null
@@ -1,5480 +0,0 @@
-(function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object')
- module.exports = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
- else if(typeof define === 'function' && define.amd)
- define(["CoreHome", , "CorePluginsAdmin"], factory);
- else if(typeof exports === 'object')
- exports["HeatmapSessionRecording"] = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
- else
- root["HeatmapSessionRecording"] = factory(root["CoreHome"], root["Vue"], root["CorePluginsAdmin"]);
-})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__, __WEBPACK_EXTERNAL_MODULE__8bbf__, __WEBPACK_EXTERNAL_MODULE_a5a2__) {
-return /******/ (function(modules) { // webpackBootstrap
-/******/ // The module cache
-/******/ var installedModules = {};
-/******/
-/******/ // The require function
-/******/ function __webpack_require__(moduleId) {
-/******/
-/******/ // Check if module is in cache
-/******/ if(installedModules[moduleId]) {
-/******/ return installedModules[moduleId].exports;
-/******/ }
-/******/ // Create a new module (and put it into the cache)
-/******/ var module = installedModules[moduleId] = {
-/******/ i: moduleId,
-/******/ l: false,
-/******/ exports: {}
-/******/ };
-/******/
-/******/ // Execute the module function
-/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
-/******/
-/******/ // Flag the module as loaded
-/******/ module.l = true;
-/******/
-/******/ // Return the exports of the module
-/******/ return module.exports;
-/******/ }
-/******/
-/******/
-/******/ // expose the modules object (__webpack_modules__)
-/******/ __webpack_require__.m = modules;
-/******/
-/******/ // expose the module cache
-/******/ __webpack_require__.c = installedModules;
-/******/
-/******/ // define getter function for harmony exports
-/******/ __webpack_require__.d = function(exports, name, getter) {
-/******/ if(!__webpack_require__.o(exports, name)) {
-/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
-/******/ }
-/******/ };
-/******/
-/******/ // define __esModule on exports
-/******/ __webpack_require__.r = function(exports) {
-/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
-/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
-/******/ }
-/******/ Object.defineProperty(exports, '__esModule', { value: true });
-/******/ };
-/******/
-/******/ // create a fake namespace object
-/******/ // mode & 1: value is a module id, require it
-/******/ // mode & 2: merge all properties of value into the ns
-/******/ // mode & 4: return value when already ns object
-/******/ // mode & 8|1: behave like require
-/******/ __webpack_require__.t = function(value, mode) {
-/******/ if(mode & 1) value = __webpack_require__(value);
-/******/ if(mode & 8) return value;
-/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
-/******/ var ns = Object.create(null);
-/******/ __webpack_require__.r(ns);
-/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
-/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
-/******/ return ns;
-/******/ };
-/******/
-/******/ // getDefaultExport function for compatibility with non-harmony modules
-/******/ __webpack_require__.n = function(module) {
-/******/ var getter = module && module.__esModule ?
-/******/ function getDefault() { return module['default']; } :
-/******/ function getModuleExports() { return module; };
-/******/ __webpack_require__.d(getter, 'a', getter);
-/******/ return getter;
-/******/ };
-/******/
-/******/ // Object.prototype.hasOwnProperty.call
-/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
-/******/
-/******/ // __webpack_public_path__
-/******/ __webpack_require__.p = "plugins/HeatmapSessionRecording/vue/dist/";
-/******/
-/******/
-/******/ // Load entry module and return exports
-/******/ return __webpack_require__(__webpack_require__.s = "fae3");
-/******/ })
-/************************************************************************/
-/******/ ({
-
-/***/ "19dc":
-/***/ (function(module, exports) {
-
-module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__;
-
-/***/ }),
-
-/***/ "246e":
-/***/ (function(module, exports, __webpack_require__) {
-
-var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*
- * heatmap.js v2.0.5 | JavaScript Heatmap Library
- *
- * Copyright 2008-2016 Patrick Wied - All rights reserved.
- * Dual licensed under MIT and Beerware license
- *
- * :: 2016-09-05 01:16
- */
-;(function (name, context, factory) {
-
- // Supports UMD. AMD, CommonJS/Node.js and browser context
- if ( true && module.exports) {
- module.exports = factory();
- } else if (true) {
- !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
- __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
- (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
- __WEBPACK_AMD_DEFINE_FACTORY__),
- __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
- } else {}
-
-})("h337", this, function () {
-
-// Heatmap Config stores default values and will be merged with instance config
-var HeatmapConfig = {
- defaultRadius: 40,
- defaultRenderer: 'canvas2d',
- defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"},
- defaultMaxOpacity: 1,
- defaultMinOpacity: 0,
- defaultBlur: .85,
- defaultXField: 'x',
- defaultYField: 'y',
- defaultValueField: 'value',
- plugins: {}
-};
-var Store = (function StoreClosure() {
-
- var Store = function Store(config) {
- this._coordinator = {};
- this._data = [];
- this._radi = [];
- this._min = 10;
- this._max = 1;
- this._xField = config['xField'] || config.defaultXField;
- this._yField = config['yField'] || config.defaultYField;
- this._valueField = config['valueField'] || config.defaultValueField;
-
- if (config["radius"]) {
- this._cfgRadius = config["radius"];
- }
- };
-
- var defaultRadius = HeatmapConfig.defaultRadius;
-
- Store.prototype = {
- // when forceRender = false -> called from setData, omits renderall event
- _organiseData: function(dataPoint, forceRender) {
- var x = dataPoint[this._xField];
- var y = dataPoint[this._yField];
- var radi = this._radi;
- var store = this._data;
- var max = this._max;
- var min = this._min;
- var value = dataPoint[this._valueField] || 1;
- var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
-
- if (!store[x]) {
- store[x] = [];
- radi[x] = [];
- }
-
- if (!store[x][y]) {
- store[x][y] = value;
- radi[x][y] = radius;
- } else {
- store[x][y] += value;
- }
- var storedVal = store[x][y];
-
- if (storedVal > max) {
- if (!forceRender) {
- this._max = storedVal;
- } else {
- this.setDataMax(storedVal);
- }
- return false;
- } else if (storedVal < min) {
- if (!forceRender) {
- this._min = storedVal;
- } else {
- this.setDataMin(storedVal);
- }
- return false;
- } else {
- return {
- x: x,
- y: y,
- value: value,
- radius: radius,
- min: min,
- max: max
- };
- }
- },
- _unOrganizeData: function() {
- var unorganizedData = [];
- var data = this._data;
- var radi = this._radi;
-
- for (var x in data) {
- for (var y in data[x]) {
-
- unorganizedData.push({
- x: x,
- y: y,
- radius: radi[x][y],
- value: data[x][y]
- });
-
- }
- }
- return {
- min: this._min,
- max: this._max,
- data: unorganizedData
- };
- },
- _onExtremaChange: function() {
- this._coordinator.emit('extremachange', {
- min: this._min,
- max: this._max
- });
- },
- addData: function() {
- if (arguments[0].length > 0) {
- var dataArr = arguments[0];
- var dataLen = dataArr.length;
- while (dataLen--) {
- this.addData.call(this, dataArr[dataLen]);
- }
- } else {
- // add to store
- var organisedEntry = this._organiseData(arguments[0], true);
- if (organisedEntry) {
- // if it's the first datapoint initialize the extremas with it
- if (this._data.length === 0) {
- this._min = this._max = organisedEntry.value;
- }
- this._coordinator.emit('renderpartial', {
- min: this._min,
- max: this._max,
- data: [organisedEntry]
- });
- }
- }
- return this;
- },
- setData: function(data) {
- var dataPoints = data.data;
- var pointsLen = dataPoints.length;
-
-
- // reset data arrays
- this._data = [];
- this._radi = [];
-
- for(var i = 0; i < pointsLen; i++) {
- this._organiseData(dataPoints[i], false);
- }
- this._max = data.max;
- this._min = data.min || 0;
-
- this._onExtremaChange();
- this._coordinator.emit('renderall', this._getInternalData());
- return this;
- },
- removeData: function() {
- // TODO: implement
- },
- setDataMax: function(max) {
- this._max = max;
- this._onExtremaChange();
- this._coordinator.emit('renderall', this._getInternalData());
- return this;
- },
- setDataMin: function(min) {
- this._min = min;
- this._onExtremaChange();
- this._coordinator.emit('renderall', this._getInternalData());
- return this;
- },
- setCoordinator: function(coordinator) {
- this._coordinator = coordinator;
- },
- _getInternalData: function() {
- return {
- max: this._max,
- min: this._min,
- data: this._data,
- radi: this._radi
- };
- },
- getData: function() {
- return this._unOrganizeData();
- }/*,
-
- TODO: rethink.
-
- getValueAt: function(point) {
- var value;
- var radius = 100;
- var x = point.x;
- var y = point.y;
- var data = this._data;
-
- if (data[x] && data[x][y]) {
- return data[x][y];
- } else {
- var values = [];
- // radial search for datapoints based on default radius
- for(var distance = 1; distance < radius; distance++) {
- var neighbors = distance * 2 +1;
- var startX = x - distance;
- var startY = y - distance;
-
- for(var i = 0; i < neighbors; i++) {
- for (var o = 0; o < neighbors; o++) {
- if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
- if (data[startY+i] && data[startY+i][startX+o]) {
- values.push(data[startY+i][startX+o]);
- }
- } else {
- continue;
- }
- }
- }
- }
- if (values.length > 0) {
- return Math.max.apply(Math, values);
- }
- }
- return false;
- }*/
- };
-
-
- return Store;
-})();
-
-var Canvas2dRenderer = (function Canvas2dRendererClosure() {
-
- var _getColorPalette = function(config) {
- var gradientConfig = config.gradient || config.defaultGradient;
- var paletteCanvas = document.createElement('canvas');
- var paletteCtx = paletteCanvas.getContext('2d');
-
- paletteCanvas.width = 256;
- paletteCanvas.height = 1;
-
- var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
- for (var key in gradientConfig) {
- gradient.addColorStop(key, gradientConfig[key]);
- }
-
- paletteCtx.fillStyle = gradient;
- paletteCtx.fillRect(0, 0, 256, 1);
-
- return paletteCtx.getImageData(0, 0, 256, 1).data;
- };
-
- var _getPointTemplate = function(radius, blurFactor) {
- var tplCanvas = document.createElement('canvas');
- var tplCtx = tplCanvas.getContext('2d');
- var x = radius;
- var y = radius;
- tplCanvas.width = tplCanvas.height = radius*2;
-
- if (blurFactor == 1) {
- tplCtx.beginPath();
- tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
- tplCtx.fillStyle = 'rgba(0,0,0,1)';
- tplCtx.fill();
- } else {
- var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius);
- gradient.addColorStop(0, 'rgba(0,0,0,1)');
- gradient.addColorStop(1, 'rgba(0,0,0,0)');
- tplCtx.fillStyle = gradient;
- tplCtx.fillRect(0, 0, 2*radius, 2*radius);
- }
-
-
-
- return tplCanvas;
- };
-
- var _prepareData = function(data) {
- var renderData = [];
- var min = data.min;
- var max = data.max;
- var radi = data.radi;
- var data = data.data;
-
- var xValues = Object.keys(data);
- var xValuesLen = xValues.length;
-
- while(xValuesLen--) {
- var xValue = xValues[xValuesLen];
- var yValues = Object.keys(data[xValue]);
- var yValuesLen = yValues.length;
- while(yValuesLen--) {
- var yValue = yValues[yValuesLen];
- var value = data[xValue][yValue];
- var radius = radi[xValue][yValue];
- renderData.push({
- x: xValue,
- y: yValue,
- value: value,
- radius: radius
- });
- }
- }
-
- return {
- min: min,
- max: max,
- data: renderData
- };
- };
-
-
- function Canvas2dRenderer(config) {
- var container = config.container;
- var shadowCanvas = this.shadowCanvas = document.createElement('canvas');
- var canvas = this.canvas = config.canvas || document.createElement('canvas');
- var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0];
-
- var computed = getComputedStyle(config.container) || {};
-
- canvas.className = 'heatmap-canvas';
-
- this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,''));
- this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,''));
-
- this.shadowCtx = shadowCanvas.getContext('2d');
- this.ctx = canvas.getContext('2d');
-
- // @TODO:
- // conditional wrapper
-
- canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;';
-
- container.style.position = 'relative';
- container.appendChild(canvas);
-
- this._palette = _getColorPalette(config);
- this._templates = {};
-
- this._setStyles(config);
- };
-
- Canvas2dRenderer.prototype = {
- renderPartial: function(data) {
- if (data.data.length > 0) {
- this._drawAlpha(data);
- this._colorize();
- }
- },
- renderAll: function(data) {
- // reset render boundaries
- this._clear();
- if (data.data.length > 0) {
- this._drawAlpha(_prepareData(data));
- this._colorize();
- }
- },
- _updateGradient: function(config) {
- this._palette = _getColorPalette(config);
- },
- updateConfig: function(config) {
- if (config['gradient']) {
- this._updateGradient(config);
- }
- this._setStyles(config);
- },
- setDimensions: function(width, height) {
- this._width = width;
- this._height = height;
- this.canvas.width = this.shadowCanvas.width = width;
- this.canvas.height = this.shadowCanvas.height = height;
- },
- _clear: function() {
- this.shadowCtx.clearRect(0, 0, this._width, this._height);
- this.ctx.clearRect(0, 0, this._width, this._height);
- },
- _setStyles: function(config) {
- this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur);
-
- if (config.backgroundColor) {
- this.canvas.style.backgroundColor = config.backgroundColor;
- }
-
- this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width;
- this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height;
-
-
- this._opacity = (config.opacity || 0) * 255;
- this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
- this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
- this._useGradientOpacity = !!config.useGradientOpacity;
- },
- _drawAlpha: function(data) {
- var min = this._min = data.min;
- var max = this._max = data.max;
- var data = data.data || [];
- var dataLen = data.length;
- // on a point basis?
- var blur = 1 - this._blur;
-
- while(dataLen--) {
-
- var point = data[dataLen];
-
- var x = point.x;
- var y = point.y;
- var radius = point.radius;
- // if value is bigger than max
- // use max as value
- var value = Math.min(point.value, max);
- var rectX = x - radius;
- var rectY = y - radius;
- var shadowCtx = this.shadowCtx;
-
-
-
-
- var tpl;
- if (!this._templates[radius]) {
- this._templates[radius] = tpl = _getPointTemplate(radius, blur);
- } else {
- tpl = this._templates[radius];
- }
- // value from minimum / value range
- // => [0, 1]
- var templateAlpha = (value-min)/(max-min);
- // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
- shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha;
-
- shadowCtx.drawImage(tpl, rectX, rectY);
-
- // update renderBoundaries
- if (rectX < this._renderBoundaries[0]) {
- this._renderBoundaries[0] = rectX;
- }
- if (rectY < this._renderBoundaries[1]) {
- this._renderBoundaries[1] = rectY;
- }
- if (rectX + 2*radius > this._renderBoundaries[2]) {
- this._renderBoundaries[2] = rectX + 2*radius;
- }
- if (rectY + 2*radius > this._renderBoundaries[3]) {
- this._renderBoundaries[3] = rectY + 2*radius;
- }
-
- }
- },
- _colorize: function() {
- var x = this._renderBoundaries[0];
- var y = this._renderBoundaries[1];
- var width = this._renderBoundaries[2] - x;
- var height = this._renderBoundaries[3] - y;
- var maxWidth = this._width;
- var maxHeight = this._height;
- var opacity = this._opacity;
- var maxOpacity = this._maxOpacity;
- var minOpacity = this._minOpacity;
- var useGradientOpacity = this._useGradientOpacity;
-
- if (x < 0) {
- x = 0;
- }
- if (y < 0) {
- y = 0;
- }
- if (x + width > maxWidth) {
- width = maxWidth - x;
- }
- if (y + height > maxHeight) {
- height = maxHeight - y;
- }
-
- var img = this.shadowCtx.getImageData(x, y, width, height);
- var imgData = img.data;
- var len = imgData.length;
- var palette = this._palette;
-
-
- for (var i = 3; i < len; i+= 4) {
- var alpha = imgData[i];
- var offset = alpha * 4;
-
-
- if (!offset) {
- continue;
- }
-
- var finalAlpha;
- if (opacity > 0) {
- finalAlpha = opacity;
- } else {
- if (alpha < maxOpacity) {
- if (alpha < minOpacity) {
- finalAlpha = minOpacity;
- } else {
- finalAlpha = alpha;
- }
- } else {
- finalAlpha = maxOpacity;
- }
- }
-
- imgData[i-3] = palette[offset];
- imgData[i-2] = palette[offset + 1];
- imgData[i-1] = palette[offset + 2];
- imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
-
- }
-
- img.data = imgData;
- this.ctx.putImageData(img, x, y);
-
- this._renderBoundaries = [1000, 1000, 0, 0];
-
- },
- getValueAt: function(point) {
- var value;
- var shadowCtx = this.shadowCtx;
- var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
- var data = img.data[3];
- var max = this._max;
- var min = this._min;
-
- value = (Math.abs(max-min) * (data/255)) >> 0;
-
- return value;
- },
- getDataURL: function() {
- return this.canvas.toDataURL();
- }
- };
-
-
- return Canvas2dRenderer;
-})();
-
-
-var Renderer = (function RendererClosure() {
-
- var rendererFn = false;
-
- if (HeatmapConfig['defaultRenderer'] === 'canvas2d') {
- rendererFn = Canvas2dRenderer;
- }
-
- return rendererFn;
-})();
-
-
-var Util = {
- merge: function() {
- var merged = {};
- var argsLen = arguments.length;
- for (var i = 0; i < argsLen; i++) {
- var obj = arguments[i]
- for (var key in obj) {
- merged[key] = obj[key];
- }
- }
- return merged;
- }
-};
-// Heatmap Constructor
-var Heatmap = (function HeatmapClosure() {
-
- var Coordinator = (function CoordinatorClosure() {
-
- function Coordinator() {
- this.cStore = {};
- };
-
- Coordinator.prototype = {
- on: function(evtName, callback, scope) {
- var cStore = this.cStore;
-
- if (!cStore[evtName]) {
- cStore[evtName] = [];
- }
- cStore[evtName].push((function(data) {
- return callback.call(scope, data);
- }));
- },
- emit: function(evtName, data) {
- var cStore = this.cStore;
- if (cStore[evtName]) {
- var len = cStore[evtName].length;
- for (var i=0; i {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["btn-flat", {
- 'visActive': theHeatmapType.key === _ctx.heatmapType,
- [`heatmapType${theHeatmapType.key}`]: true
- }]),
- onClick: $event => _ctx.changeHeatmapType(theHeatmapType.key),
- key: theHeatmapType.key
- }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(theHeatmapType.name), 11, _hoisted_5);
- }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h4", _hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeviceType')), 1), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.deviceTypesWithSamples, theDeviceType => {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["btn-flat", {
- 'visActive': theDeviceType.key === _ctx.deviceType,
- [`deviceType${theDeviceType.key}`]: true
- }]),
- title: theDeviceType.tooltip,
- onClick: $event => _ctx.changeDeviceType(theDeviceType.key),
- key: theDeviceType.key
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
- height: "15",
- src: theDeviceType.logo,
- alt: `${_ctx.translate('DevicesDetection_Device')} ${theDeviceType.name}`
- }, null, 8, _hoisted_8), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_9, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(theDeviceType.numSamples), 1)], 10, _hoisted_7);
- }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_10, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h4", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('Installation_Legend')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_11, [_hoisted_12, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", {
- class: "gradient",
- alt: "gradient",
- src: _ctx.gradientImgData
- }, null, 8, _hoisted_13), _hoisted_14])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_15, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- style: {
- "margin-left": "2.5rem",
- "margin-right": "13.5px"
- },
- textContent: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_Width'))
- }, null, 8, _hoisted_16), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
- uicontrol: "select",
- name: "iframewidth",
- "model-value": _ctx.customIframeWidth,
- "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => {
- _ctx.customIframeWidth = $event;
- _ctx.changeIframeWidth(_ctx.customIframeWidth, true);
- }),
- options: _ctx.iframeWidthOptions
- }, null, 8, ["model-value", "options"])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_17, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_18, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_19, null, 512), _hoisted_20]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "hsrLoadingOuter",
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])([{
- "height": "400px"
- }, {
- width: _ctx.iframeWidth + 'px'
- }])
- }, [_hoisted_21, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_22, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_23, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Loading')), 1)])], 4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "aboveFoldLine",
- title: _ctx.translate('HeatmapSessionRecording_AvgAboveFoldDescription'),
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- width: _ctx.iframeWidth + 'px',
- top: _ctx.avgFold + 'px'
- })
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_AvgAboveFoldTitle', _ctx.avgFold)), 1)], 12, _hoisted_24), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.avgFold]]), _ctx.embedUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("iframe", {
- key: 0,
- id: "recordingPlayer",
- ref: "recordingPlayer",
- sandbox: "allow-scripts allow-same-origin",
- referrerpolicy: "no-referrer",
- onLoad: _cache[1] || (_cache[1] = $event => _ctx.onLoaded()),
- height: "400",
- src: _ctx.embedUrl,
- width: _ctx.iframeWidth
- }, null, 40, _hoisted_25)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_26, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, {
- style: {
- "display": "block !important"
- },
- loading: _ctx.isLoading,
- onClick: _cache[2] || (_cache[2] = $event => _ctx.deleteScreenshot()),
- value: _ctx.translate('HeatmapSessionRecording_DeleteScreenshot')
- }, null, 8, ["loading", "value"])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.showDeleteScreenshot]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_27, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeleteHeatmapScreenshotConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", {
- role: "yes",
- type: "button",
- value: _ctx.translate('General_Yes')
- }, null, 8, _hoisted_28), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", {
- role: "no",
- type: "button",
- value: _ctx.translate('General_No')
- }, null, 8, _hoisted_29)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Tooltip, {
- ref: "tooltip",
- "click-count": _ctx.clickCount,
- "click-rate": _ctx.clickRate,
- "is-moves": _ctx.heatmapType === 1
- }, null, 8, ["click-count", "click-rate", "is-moves"])]);
-}
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=template&id=d1a5d966
-
-// EXTERNAL MODULE: ./plugins/HeatmapSessionRecording/node_modules/heatmap.js/build/heatmap.js
-var heatmap = __webpack_require__("246e");
-var heatmap_default = /*#__PURE__*/__webpack_require__.n(heatmap);
-
-// EXTERNAL MODULE: external "CoreHome"
-var external_CoreHome_ = __webpack_require__("19dc");
-
-// EXTERNAL MODULE: external "CorePluginsAdmin"
-var external_CorePluginsAdmin_ = __webpack_require__("a5a2");
-
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/getIframeWindow.ts
-/**
- * Copyright (C) InnoCraft Ltd - All rights reserved.
- *
- * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
- * The intellectual and technical concepts contained herein are protected by trade secret
- * or copyright law. Redistribution of this information or reproduction of this material is
- * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
- *
- * You shall use this code only in accordance with the license agreement obtained from
- * InnoCraft Ltd.
- *
- * @link https://www.innocraft.com/
- * @license For license details see https://www.innocraft.com/license
- */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getIframeWindow(iframeElement) {
- if (iframeElement && iframeElement.contentWindow) {
- return iframeElement.contentWindow;
- }
- if (iframeElement && iframeElement.contentDocument && iframeElement.contentDocument.defaultView) {
- return iframeElement.contentDocument.defaultView;
- }
- return undefined;
-}
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/oneAtATime.ts
-/**
- * Copyright (C) InnoCraft Ltd - All rights reserved.
- *
- * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd.
- * The intellectual and technical concepts contained herein are protected by trade secret
- * or copyright law. Redistribution of this information or reproduction of this material is
- * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd.
- *
- * You shall use this code only in accordance with the license agreement obtained from
- * InnoCraft Ltd.
- *
- * @link https://www.innocraft.com/
- * @license For license details see https://www.innocraft.com/license
- */
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function oneAtATime(method, options) {
- let abortController = null;
- return (params, postParams) => {
- if (abortController) {
- abortController.abort();
- abortController = null;
- }
- abortController = new AbortController();
- return external_CoreHome_["AjaxHelper"].post(Object.assign(Object.assign({}, params), {}, {
- method
- }), postParams, Object.assign(Object.assign({}, options), {}, {
- abortController
- })).finally(() => {
- abortController = null;
- });
- };
-}
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=template&id=6a6ace20
-
-const Tooltipvue_type_template_id_6a6ace20_hoisted_1 = {
- class: "tooltip-item"
-};
-const Tooltipvue_type_template_id_6a6ace20_hoisted_2 = {
- class: "tooltip-label"
-};
-const Tooltipvue_type_template_id_6a6ace20_hoisted_3 = {
- class: "tooltip-value"
-};
-const Tooltipvue_type_template_id_6a6ace20_hoisted_4 = {
- class: "tooltip-item"
-};
-const Tooltipvue_type_template_id_6a6ace20_hoisted_5 = {
- class: "tooltip-label"
-};
-const Tooltipvue_type_template_id_6a6ace20_hoisted_6 = {
- class: "tooltip-value"
-};
-function Tooltipvue_type_template_id_6a6ace20_render(_ctx, _cache, $props, $setup, $data, $options) {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
- ref: "tooltipRef",
- class: "tooltip",
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])(_ctx.tooltipStyle)
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Tooltipvue_type_template_id_6a6ace20_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickCountTranslation), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickCount), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Tooltipvue_type_template_id_6a6ace20_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_5, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickRateTranslation), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickRate), 1)])], 4)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.visible]]);
-}
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=template&id=6a6ace20
-
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=script&lang=ts
-
-
-/* harmony default export */ var Tooltipvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
- props: {
- clickCount: {
- type: Number,
- required: true
- },
- clickRate: {
- type: Number,
- required: true
- },
- isMoves: {
- type: Boolean,
- required: false,
- default: false
- }
- },
- setup() {
- const state = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({
- visible: false,
- position: {
- top: 0,
- left: 0
- }
- });
- const tooltipRef = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null);
- const tooltipStyle = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => ({
- top: `${state.position.top}px`,
- left: `${state.position.left}px`,
- position: 'absolute',
- zIndex: 1000
- }));
- function show(event) {
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
- const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
- state.position.top = event.clientY + scrollTop + 10;
- state.position.left = event.clientX + scrollLeft + 10;
- state.visible = true;
- Object(external_commonjs_vue_commonjs2_vue_root_Vue_["nextTick"])(() => {
- const tooltipElement = tooltipRef.value;
- if (tooltipElement) {
- const {
- innerWidth,
- innerHeight
- } = window;
- const tooltipRect = tooltipElement.getBoundingClientRect();
- if (tooltipRect.right > innerWidth) {
- state.position.left = event.clientX + scrollLeft - tooltipRect.width - 10;
- }
- if (tooltipRect.bottom > innerHeight) {
- state.position.top = event.clientY + scrollTop - tooltipRect.height - 10;
- }
- const adjustedTooltipRect = tooltipElement.getBoundingClientRect();
- if (adjustedTooltipRect.left < 0) {
- state.position.left = scrollLeft + 10;
- }
- if (adjustedTooltipRect.top < 0) {
- state.position.top = scrollTop + 10;
- }
- }
- });
- }
- function hide() {
- state.visible = false;
- }
- return Object.assign(Object.assign({}, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toRefs"])(state)), {}, {
- tooltipRef,
- show,
- hide,
- tooltipStyle,
- translate: external_CoreHome_["translate"]
- });
- },
- computed: {
- getClickCount() {
- return external_CoreHome_["NumberFormatter"].formatNumber(this.clickCount);
- },
- getClickRate() {
- return external_CoreHome_["NumberFormatter"].formatPercent(this.clickRate);
- },
- getClickCountTranslation() {
- const translation = this.isMoves ? 'HeatmapSessionRecording_Moves' : 'HeatmapSessionRecording_Clicks';
- return Object(external_CoreHome_["translate"])(translation);
- },
- getClickRateTranslation() {
- const translation = this.isMoves ? 'HeatmapSessionRecording_MoveRate' : 'HeatmapSessionRecording_ClickRate';
- return Object(external_CoreHome_["translate"])(translation);
- }
- }
-}));
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=script&lang=ts
-
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue
-
-
-
-Tooltipvue_type_script_lang_ts.render = Tooltipvue_type_template_id_6a6ace20_render
-
-/* harmony default export */ var Tooltip = (Tooltipvue_type_script_lang_ts);
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=script&lang=ts
-
-
-
-
-
-
-
-const {
- $
-} = window;
-const deviceDesktop = 1;
-const deviceTablet = 2;
-const deviceMobile = 3;
-let heightPerHeatmap = 32000;
-const userAgent = String(window.navigator.userAgent).toLowerCase();
-if (userAgent.match(/(iPod|iPhone|iPad|Android|IEMobile|Windows Phone)/i)) {
- heightPerHeatmap = 2000;
-} else if (userAgent.indexOf('msie ') > 0 || userAgent.indexOf('trident/') > 0 || userAgent.indexOf('edge') > 0) {
- heightPerHeatmap = 8000;
-}
-function initHeatmap(recordingPlayer, heatmapContainer,
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-recordingIframe) {
- const $iframe = $(recordingPlayer);
- // we first set the iframe to the initial 400px again so we can for sure detect the current
- // height of the inner iframe body correctly
- $iframe.css('height', '400px');
- const documentHeight = recordingIframe.getIframeHeight();
- $iframe.css('height', `${documentHeight}px`);
- $(heatmapContainer).css('height', `${documentHeight}px`).css('width', `${$iframe.width()}px`).empty();
- const numHeatmaps = Math.ceil(documentHeight / heightPerHeatmap);
- for (let i = 1; i <= numHeatmaps; i += 1) {
- let height = heightPerHeatmap;
- if (i === numHeatmaps) {
- height = documentHeight % heightPerHeatmap;
- }
- $(heatmapContainer).append(``);
- $(heatmapContainer).find(`#heatmap${i}`).css({
- height: `${height}px`
- });
- }
- return numHeatmaps;
-}
-function scrollHeatmap(iframeRecordingContainer, recordingPlayer,
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-recordingIframe, scrollReaches) {
- const $iframe = $(recordingPlayer);
- // we first set the iframe to the initial 400px again so we can for sure detect the current
- // height of the inner iframe body correctly
- $iframe.css('height', '400px');
- const documentHeight = recordingIframe.getIframeHeight();
- $iframe.css('height', `${documentHeight}px`);
- const numIntervals = 1000;
- const heightToIntervalRatio = documentHeight / numIntervals;
- const numViewersTotal = scrollReaches.reduce((pv, cv) => pv + parseInt(cv.value, 10), 0);
- const buckets = [];
- let num_viewers = 0;
- let lastBucket = null;
- let percentage = 100;
- let reachScrolledFromPosition = 0;
- // reachScrolledFromPosition we start from 0, and then always paint to the next bucket. eg when
- // scrollReach is 27 and scrollDepth is 35, then we know that 27 people have scrolled down to
- // 3.5% of the page.
- scrollReaches.forEach(scrollReachObj => {
- // the number of people that reached this point
- const scrollReach = parseInt(scrollReachObj.value, 10);
- // how far down they scrolled
- const scrollDepth = parseInt(scrollReachObj.label, 10);
- const reachScrolledToPosition = Math.round(scrollDepth * heightToIntervalRatio);
- if (lastBucket && lastBucket.position === reachScrolledToPosition) {
- // when page is < 1000 we need to aggregate buckets
- num_viewers += scrollReach;
- } else {
- if (numViewersTotal !== 0) {
- percentage = (numViewersTotal - num_viewers) / numViewersTotal * 100;
- }
- num_viewers += scrollReach;
- // percentage.toFixed(1) * 10 => convert eg 99.8 => 998
- lastBucket = {
- percentageValue: parseFloat(percentage.toFixed(1)) * 10,
- position: reachScrolledFromPosition,
- percent: percentage.toFixed(1)
- };
- buckets.push(lastBucket);
- }
- reachScrolledFromPosition = reachScrolledToPosition;
- });
- function map(value, istart, istop, ostart, ostop) {
- return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
- }
- function mapColorIntensity(intensity, min, max) {
- if (min === max || !min && !max) {
- return [255, 255, 0];
- }
- const cint = map(intensity, min, max, 0, 255);
- const step = (max - min) / 5;
- if (cint > 204) {
- return [255, map(intensity, max - step, max, 255, 0), 0];
- }
- if (cint > 153) {
- return [map(intensity, max - 2 * step, max - step, 0, 255), 255, 0];
- }
- if (cint > 102) {
- return [0, 255, map(intensity, max - 3 * step, max - 2 * step, 255, 0)];
- }
- if (cint > 51) {
- return [0, map(intensity, max - 4 * step, max - 3 * step, 0, 255), 255];
- }
- return [map(intensity, min, max - 4 * step, 255, 0), 0, 255];
- }
- if (buckets.length) {
- // we need to make sure to draw scroll heatmap over full page
- const found = buckets.some(b => b.position === 0);
- if (!found) {
- buckets.unshift({
- percent: '100.0',
- percentageValue: 1000,
- position: 0
- });
- }
- } else {
- // we'll show full page as not scrolled
- buckets.push({
- percent: '0',
- percentageValue: 0,
- position: 0
- });
- }
- let minValue = 0;
- const maxValue = 1000; // max value is always 1000 (=100%)
- if (buckets && buckets.length && buckets[0]) {
- minValue = buckets[buckets.length - 1].percentageValue;
- }
- const iframeWidth = $iframe.width();
- let nextBucket = null;
- for (let index = 0; index < buckets.length; index += 1) {
- const bucket = buckets[index];
- if (buckets[index + 1]) {
- nextBucket = buckets[index + 1];
- } else {
- nextBucket = {
- position: documentHeight
- };
- }
- const top = bucket.position;
- let height = nextBucket.position - bucket.position;
- if (height === 0) {
- height = 1; // make sure to draw at least one px
- }
- const percent = `${bucket.percent} percent reached this point`;
- const colorValues = mapColorIntensity(bucket.percentageValue, minValue, maxValue);
- const color = `rgb(${colorValues.join(',')})`;
- $(iframeRecordingContainer).append(``);
- }
- $('.scrollHeatmapLeaf', iframeRecordingContainer).tooltip({
- track: true,
- items: '*',
- tooltipClass: 'heatmapTooltip',
- show: false,
- hide: false
- });
- $('.legend-area .min').text(`${(minValue / 10).toFixed(1)}%`);
- $('.legend-area .max').text(`${(maxValue / 10).toFixed(1)}%`);
-}
-function actualRenderHeatmap(recordingPlayer, heatmapContainer,
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-recordingIframe, dataPoints) {
- const numHeatmaps = initHeatmap(recordingPlayer, heatmapContainer, recordingIframe);
- const legendCanvas = document.createElement('canvas');
- legendCanvas.width = 100;
- legendCanvas.height = 10;
- const min = document.querySelector('.legend-area .min');
- const max = document.querySelector('.legend-area .max');
- const gradientImg = document.querySelector('.legend-area .gradient');
- const legendCtx = legendCanvas.getContext('2d');
- let gradientCfg = {};
- function updateLegend(data) {
- // the onExtremaChange callback gives us min, max, and the gradientConfig
- // so we can update the legend
- min.innerHTML = `${data.min}`;
- max.innerHTML = `${data.max}`;
- // regenerate gradient image
- if (data.gradient && data.gradient !== gradientCfg) {
- gradientCfg = data.gradient;
- const gradient = legendCtx.createLinearGradient(0, 0, 100, 1);
- Object.keys(gradientCfg).forEach(key => {
- gradient.addColorStop(parseFloat(key), gradientCfg[key]);
- });
- legendCtx.fillStyle = gradient;
- legendCtx.fillRect(0, 0, 100, 10);
- gradientImg.src = legendCanvas.toDataURL();
- }
- }
- const heatmapInstances = [];
- for (let i = 1; i <= numHeatmaps; i += 1) {
- const dpoints = {
- min: dataPoints.min,
- max: dataPoints.max,
- data: []
- };
- const config = {
- container: document.getElementById(`heatmap${i}`),
- radius: 10,
- maxOpacity: 0.5,
- minOpacity: 0,
- blur: 0.75
- };
- if (i === 1) {
- config.onExtremaChange = updateLegend; // typing is wrong here
- }
- if (dataPoints && dataPoints.data && dataPoints.data.length >= 20000) {
- config.radius = 8;
- } else if (dataPoints && dataPoints.data && dataPoints.data.length >= 2000) {
- config.radius = 9;
- }
- if (numHeatmaps === 1) {
- dpoints.data = dataPoints.data;
- } else {
- const lowerLimit = (i - 1) * heightPerHeatmap;
- const upperLimit = lowerLimit + heightPerHeatmap - 1;
- dataPoints.data.forEach(dp => {
- if (dp.y >= lowerLimit && dp.y <= upperLimit) {
- const thePoint = Object.assign(Object.assign({}, dp), {}, {
- y: dp.y - lowerLimit
- });
- dpoints.data.push(thePoint);
- }
- });
- }
- const heatmapInstance = heatmap_default.a.create(config);
- // heatmap type requires value to be number, but matomo sets it as string
- heatmapInstance.setData(dpoints);
- heatmapInstances.push(heatmapInstance);
- }
- return heatmapInstances;
-}
-/* harmony default export */ var HeatmapVisvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
- props: {
- idSiteHsr: {
- type: Number,
- required: true
- },
- deviceTypes: {
- type: Array,
- required: true
- },
- heatmapTypes: {
- type: Array,
- required: true
- },
- breakpointMobile: {
- type: Number,
- required: true
- },
- breakpointTablet: {
- type: Number,
- required: true
- },
- offsetAccuracy: {
- type: Number,
- required: true
- },
- heatmapPeriod: {
- type: String,
- required: true
- },
- heatmapDate: {
- type: String,
- required: true
- },
- url: {
- type: String,
- required: true
- },
- isActive: Boolean,
- numSamples: {
- type: Object,
- required: true
- },
- excludedElements: {
- type: String,
- required: true
- },
- createdDate: {
- type: String,
- required: true
- },
- desktopPreviewSize: {
- type: Number,
- required: true
- },
- iframeResolutionsValues: {
- type: Object,
- required: true
- }
- },
- components: {
- Field: external_CorePluginsAdmin_["Field"],
- SaveButton: external_CorePluginsAdmin_["SaveButton"],
- Tooltip: Tooltip
- },
- data() {
- return {
- isLoading: false,
- iframeWidth: this.desktopPreviewSize,
- customIframeWidth: this.desktopPreviewSize,
- avgFold: 0,
- heatmapType: this.heatmapTypes[0].key,
- deviceType: this.deviceTypes[0].key,
- iframeResolutions: this.iframeResolutionsValues,
- actualNumSamples: this.numSamples,
- dataCoordinates: [],
- currentElement: null,
- totalClicks: 0,
- tooltipShowTimeoutId: null,
- clickCount: 0,
- clickRate: 0
- };
- },
- setup(props) {
- const tooltip = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null);
- let iframeLoadedResolve = null;
- const iframeLoadedPromise = new Promise(resolve => {
- iframeLoadedResolve = resolve;
- });
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- let recordingIframe = null;
- const getRecordingIframe = recordingPlayer => {
- if (!recordingIframe) {
- recordingIframe = getIframeWindow(recordingPlayer).recordingFrame;
- recordingIframe.excludeElements(props.excludedElements);
- recordingIframe.addClass('html', 'piwikHeatmap');
- recordingIframe.addClass('html', 'matomoHeatmap');
- recordingIframe.addWorkaroundForSharepointHeatmaps();
- }
- return recordingIframe;
- };
- const heatmapInstances = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null);
- const renderHeatmap = (recordingPlayer, heatmapContainer,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- theRecordingIframe, dataPoints) => {
- heatmapInstances.value = actualRenderHeatmap(recordingPlayer, heatmapContainer, theRecordingIframe, dataPoints);
- };
- return {
- iframeLoadedPromise,
- onLoaded: iframeLoadedResolve,
- getRecordedHeatmap: oneAtATime('HeatmapSessionRecording.getRecordedHeatmap'),
- getRecordedHeatmapMetadata: oneAtATime('HeatmapSessionRecording.getRecordedHeatmapMetadata'),
- getRecordingIframe,
- heatmapInstances,
- renderHeatmap,
- tooltip
- };
- },
- created() {
- if (this.iframeResolutions.indexOf(this.breakpointMobile) === -1) {
- this.iframeResolutions.push(this.breakpointMobile);
- }
- if (this.iframeResolutions.indexOf(this.breakpointTablet) === -1) {
- this.iframeResolutions.push(this.breakpointTablet);
- }
- this.iframeResolutions = this.iframeResolutions.sort((a, b) => a - b);
- this.fetchHeatmap();
- // Hide the period selector since we don't filter the heatmap by period
- external_CoreHome_["Matomo"].postEvent('hidePeriodSelector');
- },
- watch: {
- isLoading() {
- if (this.isLoading === true) {
- return;
- }
- const heatmapContainer = window.document.getElementById('heatmapContainer');
- if (!heatmapContainer) {
- return;
- }
- heatmapContainer.addEventListener('mouseleave', event => {
- // Stop processing tooltip when moving mouse out of parent element
- if (this.tooltipShowTimeoutId) {
- clearTimeout(this.tooltipShowTimeoutId);
- this.tooltipShowTimeoutId = null;
- }
- // Reset the highlight and tooltip when leaving the container
- this.currentElement = null;
- this.handleTooltip(event, 0, 0, 'hide');
- const highlightDiv = window.document.getElementById('highlightDiv');
- if (!highlightDiv) {
- return;
- }
- highlightDiv.hidden = true;
- });
- heatmapContainer.addEventListener('mousemove', e => {
- this.handleMouseMove(e);
- });
- }
- },
- beforeUnmount() {
- this.removeScrollHeatmap();
- },
- methods: {
- removeScrollHeatmap() {
- const element = this.$refs.iframeRecordingContainer;
- $(element).find('.scrollHeatmapLeaf').remove();
- },
- deleteScreenshot() {
- external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteHeatmapScreenshot, {
- yes: () => {
- this.isLoading = true;
- external_CoreHome_["AjaxHelper"].fetch({
- method: 'HeatmapSessionRecording.deleteHeatmapScreenshot',
- idSiteHsr: this.idSiteHsr
- }).then(() => {
- this.isLoading = false;
- window.location.reload();
- });
- }
- });
- },
- fetchHeatmap() {
- this.removeScrollHeatmap();
- if (this.heatmapInstances) {
- const instances = this.heatmapInstances;
- instances.forEach(heatmapInstance => {
- heatmapInstance.setData({
- max: 1,
- min: 0,
- data: []
- });
- });
- }
- this.isLoading = true;
- this.avgFold = 0;
- const segment = external_CoreHome_["MatomoUrl"].parsed.value.segment ? decodeURIComponent(external_CoreHome_["MatomoUrl"].parsed.value.segment) : undefined;
- const requestParams = {
- idSiteHsr: this.idSiteHsr,
- heatmapType: this.heatmapType,
- deviceType: this.deviceType,
- period: this.heatmapPeriod,
- date: this.heatmapDate,
- filter_limit: -1,
- segment
- };
- const heatmapDataPromise = this.getRecordedHeatmap(requestParams);
- const heatmapMetaDataPromise = this.getRecordedHeatmapMetadata(requestParams);
- Promise.all([heatmapDataPromise, heatmapMetaDataPromise, this.iframeLoadedPromise]).then(response => {
- const iframeElement = this.$refs.recordingPlayer;
- const recordingIframe = this.getRecordingIframe(iframeElement);
- initHeatmap(this.$refs.recordingPlayer, this.$refs.heatmapContainer, recordingIframe);
- this.removeScrollHeatmap();
- const rows = response[0];
- const numSamples = response[1];
- if (Array.isArray(numSamples) && numSamples[0]) {
- [this.actualNumSamples] = numSamples;
- } else {
- this.actualNumSamples = numSamples;
- }
- this.isLoading = false;
- if (this.isScrollHeatmapType) {
- scrollHeatmap(this.$refs.iframeRecordingContainer, iframeElement, recordingIframe, rows);
- } else {
- var _this$actualNumSample;
- const dataPoints = {
- min: 0,
- max: 0,
- data: []
- };
- for (let i = 0; i < rows.length; i += 1) {
- const row = rows[i];
- if (row.selector) {
- const dataPoint = recordingIframe.getCoordinatesInFrame(row.selector, row.offset_x, row.offset_y, this.offsetAccuracy, true);
- if (dataPoint) {
- dataPoint.value = row.value;
- dataPoints.data.push(dataPoint);
- this.dataCoordinates.push(dataPoint);
- this.totalClicks += parseInt(row.value, 10);
- }
- }
- }
- if (this.heatmapType === 2) {
- // click
- let numEntriesHigherThan1 = 0;
- dataPoints.data.forEach(dp => {
- if (dp !== null && dp !== void 0 && dp.value && parseInt(dp.value, 10) > 1) {
- numEntriesHigherThan1 += 1;
- }
- });
- if (numEntriesHigherThan1 / dataPoints.data.length >= 0.10 && dataPoints.data.length > 120) {
- // if at least 10% have .value >= 2, then we set max to 2 to differntiate better
- // between 1 and 2 clicks but only if we also have more than 80 data points
- // ("randomly" chosen that threshold)
- dataPoints.max = 2;
- } else {
- dataPoints.max = 1;
- }
- } else {
- const LIMIT_MAX_DATA_POINT = 10;
- const values = {};
- dataPoints.data.forEach(dp => {
- if (!dp || !dp.value) {
- return;
- }
- let value = parseInt(dp.value, 10);
- if (value > dataPoints.max) {
- dataPoints.max = value;
- }
- if (value > LIMIT_MAX_DATA_POINT) {
- value = LIMIT_MAX_DATA_POINT;
- }
- const valueStr = `${value}`;
- if (valueStr in values) {
- values[valueStr] += 1;
- } else {
- values[valueStr] = 0;
- }
- });
- if (dataPoints.max > LIMIT_MAX_DATA_POINT) {
- // we limit it to 10 otherwise many single points are not visible etc
- // if there is no single entry having value 10, we set it to 9, 8 or 7
- // to make sure there is actually a dataPoint for this max value.
- let sumValuesAboveThreshold = 0;
- for (let k = LIMIT_MAX_DATA_POINT; k > 1; k -= 1) {
- const kStr = `${k}`;
- if (kStr in values) {
- // we need to aggregate the value
- sumValuesAboveThreshold += values[kStr];
- }
- if (sumValuesAboveThreshold / dataPoints.data.length >= 0.2) {
- // we make sure to have at least 20% of entries in that max value
- dataPoints.max = k;
- break;
- }
- // todo ideally in this case also require that akk 2 - (k-1) have a distribution
- // of 0.2 to make sure we have enough values in between, and if not select k-1 or
- // so. Otherwise we have maybe 75% with value 1, 20% with value 10, and only 5% in
- // between... which would be barely visible those 75% maybe
- }
- if (dataPoints.max > LIMIT_MAX_DATA_POINT) {
- // when no entry has more than 15% distribution, we set a default of 5
- dataPoints.max = 5;
- for (let k = 5; k > 0; k -= 1) {
- const kStr = `${k}`;
- if (kStr in values) {
- // we limit it to 10 otherwise many single points are not visible etc
- // also if there is no single entry having value 10, we set it to 9, 8 or 7
- // to make sure there is actually a dataPoint for this max value.
- dataPoints.max = k;
- break;
- }
- }
- }
- }
- }
- this.renderHeatmap(this.$refs.recordingPlayer, this.$refs.heatmapContainer, recordingIframe, dataPoints);
- if ((_this$actualNumSample = this.actualNumSamples) !== null && _this$actualNumSample !== void 0 && _this$actualNumSample[`avg_fold_device_${this.deviceType}`]) {
- const avgFoldPercent = this.actualNumSamples[`avg_fold_device_${this.deviceType}`];
- const height = recordingIframe.getIframeHeight();
- if (height) {
- this.avgFold = parseInt(`${avgFoldPercent / 100 * height}`, 10);
- }
- }
- }
- }).finally(() => {
- this.isLoading = false;
- });
- },
- changeDeviceType(deviceType) {
- this.deviceType = deviceType;
- if (this.deviceType === deviceDesktop) {
- this.changeIframeWidth(this.desktopPreviewSize, false);
- } else if (this.deviceType === deviceTablet) {
- this.changeIframeWidth(this.breakpointTablet || 960, false);
- } else if (this.deviceType === deviceMobile) {
- this.changeIframeWidth(this.breakpointMobile || 600, false);
- }
- },
- changeIframeWidth(iframeWidth, scrollToTop) {
- this.iframeWidth = iframeWidth;
- this.customIframeWidth = this.iframeWidth;
- this.totalClicks = 0;
- this.dataCoordinates = [];
- this.fetchHeatmap();
- if (scrollToTop) {
- external_CoreHome_["Matomo"].helper.lazyScrollToContent();
- }
- },
- changeHeatmapType(heatmapType) {
- this.heatmapType = heatmapType;
- this.totalClicks = 0;
- this.clickCount = 0;
- this.clickRate = 0;
- this.dataCoordinates = [];
- this.fetchHeatmap();
- },
- handleMouseMove(event) {
- const highlightDiv = window.document.getElementById('highlightDiv');
- if (!highlightDiv) {
- return;
- }
- // Keep the tooltip from showing until the cursor has stopped moving
- if (this.tooltipShowTimeoutId) {
- clearTimeout(this.tooltipShowTimeoutId);
- this.tooltipShowTimeoutId = null;
- this.currentElement = null;
- }
- // If the highlight is visible, move the tooltip around with the cursor
- if (!highlightDiv.hidden) {
- this.handleTooltip(event, 0, 0, 'move');
- }
- const element = this.lookUpRecordedElementAtEventLocation(event);
- // If there's no element, don't do anything else
- // If the element hasn't changed, there's no need to do anything else
- if (!element || element === this.currentElement) {
- return;
- }
- this.handleTooltip(event, 0, 0, 'hide');
- highlightDiv.hidden = true;
- const elementRect = element.getBoundingClientRect();
- let elementClicks = 0;
- this.dataCoordinates.forEach(dataPoint => {
- // Return if the dataPoint isn't within the element
- if (dataPoint.y < elementRect.top || dataPoint.y > elementRect.bottom || dataPoint.x < elementRect.left || dataPoint.x > elementRect.right) {
- return;
- }
- elementClicks += parseInt(dataPoint.value, 10);
- });
- // Have a slight delay so that it's not jarring when it displays
- this.tooltipShowTimeoutId = setTimeout(() => {
- this.currentElement = element;
- highlightDiv.hidden = false;
- // Multiplying by 10000 and then dividing by 100 to get 2 decimal points of precision
- const clickRate = this.totalClicks ? Math.round(elementClicks / this.totalClicks * 10000) / 100 : 0;
- const rect = element.getBoundingClientRect();
- highlightDiv.style.top = `${rect.top}px`;
- highlightDiv.style.left = `${rect.left}px`;
- highlightDiv.style.width = `${rect.width}px`;
- highlightDiv.style.height = `${rect.height}px`;
- this.handleTooltip(event, elementClicks, clickRate, 'show');
- this.tooltipShowTimeoutId = null;
- }, 100);
- },
- lookUpRecordedElementAtEventLocation(event) {
- const targetElement = event.target;
- if (!targetElement) {
- return null;
- }
- const frameElement = window.document.getElementById('recordingPlayer');
- if (!frameElement) {
- return null;
- }
- const frameRef = frameElement.contentWindow ? frameElement.contentWindow.document : frameElement.contentDocument;
- if (!frameRef) {
- return null;
- }
- const rect = targetElement.getBoundingClientRect();
- return frameRef.elementFromPoint(event.clientX - rect.left, event.clientY - rect.top);
- },
- handleTooltip(event, clickCount, clickRate, action) {
- if (this.tooltip) {
- if (action === 'show') {
- this.clickCount = clickCount;
- this.clickRate = clickRate;
- this.tooltip.show(event);
- } else if (action === 'move') {
- this.tooltip.show(event);
- } else {
- this.tooltip.hide();
- }
- }
- }
- },
- computed: {
- isScrollHeatmapType() {
- return this.heatmapType === 3;
- },
- tokenAuth() {
- return external_CoreHome_["MatomoUrl"].parsed.value.token_auth;
- },
- embedUrl() {
- return `?${external_CoreHome_["MatomoUrl"].stringify({
- module: 'HeatmapSessionRecording',
- action: 'embedPage',
- idSite: external_CoreHome_["Matomo"].idSite,
- idSiteHsr: this.idSiteHsr,
- token_auth: this.tokenAuth || undefined
- })}`;
- },
- iframeWidthOptions() {
- return this.iframeResolutions.map(width => ({
- key: width,
- value: `${width}px`
- }));
- },
- recordedSamplesSince() {
- const string1 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapXRecordedSamplesSince', `${this.actualNumSamples.nb_samples_device_all}`, this.createdDate);
- const linkString = Object(external_CoreHome_["externalLink"])('https://matomo.org/faq/heatmap-session-recording/troubleshooting-heatmaps/');
- const string2 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapTroubleshoot', linkString, '');
- return `${string1} ${string2}`;
- },
- deviceTypesWithSamples() {
- return this.deviceTypes.map(deviceType => {
- let numSamples;
- if (this.actualNumSamples[`nb_samples_device_${deviceType.key}`]) {
- numSamples = this.actualNumSamples[`nb_samples_device_${deviceType.key}`];
- } else {
- numSamples = 0;
- }
- const tooltip = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_XSamples', `${deviceType.name} - ${numSamples}`);
- return Object.assign(Object.assign({}, deviceType), {}, {
- numSamples,
- tooltip
- });
- });
- },
- hasWriteAccess() {
- return !!(external_CoreHome_["Matomo"] !== null && external_CoreHome_["Matomo"] !== void 0 && external_CoreHome_["Matomo"].heatmapWriteAccess);
- },
- showDeleteScreenshot() {
- return this.isActive && this.hasWriteAccess;
- },
- gradientImgData() {
- return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAAnklEQVRYR+2WQQq' + 'DQBAES5wB/f8/Y05RcMWwSu6JIT0Dm4WlH1DUdHew7/z6WYFhhnGRpnlhAEaQpi/ADbh/np0MiBhGhW+2ymFU+DZ' + 'fg1EhaoB4jCFuMYYcQKZrXwPEVvm5Og0pcYakBvI35G1jNIZ4jCHexxjSpz9ZFUjAynLbpOvqteaODkm9sloz5JF' + '+ZTVmSAWSu9Qb65AvgDwBQoLgVDlWfAQAAAAASUVORK5CYII=';
- }
- }
-}));
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=script&lang=ts
-
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue
-
-
-
-HeatmapVisvue_type_script_lang_ts.render = render
-
-/* harmony default export */ var HeatmapVis = (HeatmapVisvue_type_script_lang_ts);
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=template&id=6f77b61e
-
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_1 = {
- class: "sessionRecordingPlayer"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_2 = {
- class: "controls"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_3 = {
- class: "playerActions"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_4 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_5 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_6 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_7 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_8 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_9 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_10 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_11 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_12 = {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "20",
- height: "20",
- viewBox: "0 0 768 768"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_13 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M480 576.5v-321h-64.5v129h-63v-129h-64.5v192h127.5v129h64.5zM607.5 127.999c34.5 0\n 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z"
-}, null, -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_14 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_13];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_15 = {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "20",
- height: "20",
- viewBox: "0 0 768 768"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_16 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M448.5 576.5v-321h-129v64.5h64.5v256.5h64.5zM607.5 127.999c34.5 0 64.5 30 64.5\n 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5 0-64.5-30-64.5-64.5v-447c0-34.5\n 30-64.5 64.5-64.5h447z"
-}, null, -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_17 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_16];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_18 = {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "20",
- height: "20",
- viewBox: "0 0 768 768"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_19 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M480 384.5v-64.5c0-36-30-64.5-64.5-64.5h-127.5v64.5h127.5v64.5h-63c-34.5 0-64.5\n 27-64.5 63v129h192v-64.5h-127.5v-64.5h63c34.5 0 64.5-27 64.5-63zM607.5 127.999c34.5\n 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z"
-}, null, -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_20 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_19];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_21 = {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "20",
- height: "20",
- viewBox: "0 0 768 768"
-};
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_22 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M480 320v-64.5h-127.5c-34.5 0-64.5 28.5-64.5 64.5v192c0 36 30 64.5 64.5\n 64.5h63c34.5 0 64.5-28.5 64.5-64.5v-64.5c0-36-30-63-64.5-63h-63v-64.5h127.5zM607.5\n 127.999c34.5 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447zM352.5 512v-64.5h63v64.5h-63z"
-}, null, -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_23 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_22];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_24 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_25 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("svg", {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "20",
- height: "20",
- viewBox: "0 0 768 768"
-}, [/*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M223.5 415.5h111l-64.5-63h-46.5v63zM72 72l624 624-42 40.5-88.5-90c-51 36-114\n 57-181.5 57-177 0-319.5-142.5-319.5-319.5 0-67.5 21-130.5 57-181.5l-90-88.5zM544.5\n 352.5h-111l-231-231c51-36 114-57 181.5-57 177 0 319.5 142.5 319.5 319.5 0 67.5-21\n 130.5-57 181.5l-148.5-150h46.5v-63z"
-})], -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_26 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_25];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_27 = ["title"];
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_28 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("svg", {
- version: "1.1",
- xmlns: "http://www.w3.org/2000/svg",
- width: "22",
- height: "22",
- viewBox: "0 0 768 768"
-}, [/*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", {
- d: "M544.5 609v-129h63v192h-384v96l-127.5-127.5 127.5-127.5v96h321zM223.5\n 288v129h-63v-192h384v-96l127.5 127.5-127.5 127.5v-96h-321z"
-})], -1);
-const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_29 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_28];
-const _hoisted_30 = {
- class: "duration"
-};
-const _hoisted_31 = {
- class: "playerHelp"
-};
-const _hoisted_32 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "clickEvent"
-}, null, -1);
-const _hoisted_33 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "moveEvent"
-}, null, -1);
-const _hoisted_34 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "scrollEvent"
-}, null, -1);
-const _hoisted_35 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "resizeEvent"
-}, null, -1);
-const _hoisted_36 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "formChange"
-}, null, -1);
-const _hoisted_37 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "mutationEvent"
-}, null, -1);
-const _hoisted_38 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", {
- style: {
- "clear": "right"
- }
-}, null, -1);
-const _hoisted_39 = ["title"];
-const _hoisted_40 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1);
-const _hoisted_41 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "loadingUnderlay"
-}, null, -1);
-const _hoisted_42 = {
- class: "valign-wrapper loadingInner"
-};
-const _hoisted_43 = {
- class: "loadingContent"
-};
-const _hoisted_44 = ["src", "width", "height"];
-function SessionRecordingVisvue_type_template_id_6f77b61e_render(_ctx, _cache, $props, $setup, $data, $options) {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-skip-previous",
- title: _ctx.skipPreviousButtonTitle,
- onClick: _cache[0] || (_cache[0] = $event => _ctx.loadNewRecording(_ctx.previousRecordingId))
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.previousRecordingId]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-fast-rewind",
- title: _ctx.translate('HeatmapSessionRecording_PlayerRewindFast', 10, 'J'),
- onClick: _cache[1] || (_cache[1] = $event => _ctx.jumpRelative(10, false))
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_5), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-play",
- title: _ctx.translate('HeatmapSessionRecording_PlayerPlay', 'K'),
- onClick: _cache[2] || (_cache[2] = $event => _ctx.play())
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_6), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isPlaying && !_ctx.isFinished]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-replay",
- title: _ctx.translate('HeatmapSessionRecording_PlayerReplay', 'K'),
- onClick: _cache[3] || (_cache[3] = $event => _ctx.replay())
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_7), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isPlaying && _ctx.isFinished]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-pause",
- title: _ctx.translate('HeatmapSessionRecording_PlayerPause', 'K'),
- onClick: _cache[4] || (_cache[4] = $event => _ctx.pause())
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_8), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isPlaying]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-fast-forward",
- title: _ctx.translate('HeatmapSessionRecording_PlayerForwardFast', 10, 'L'),
- onClick: _cache[5] || (_cache[5] = $event => _ctx.jumpRelative(10, true))
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_9), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "playerAction icon-skip-next",
- title: _ctx.translate('HeatmapSessionRecording_PlayerPageViewNext', _ctx.nextRecordingInfo, 'N'),
- onClick: _cache[6] || (_cache[6] = $event => _ctx.loadNewRecording(_ctx.nextRecordingId))
- }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_10), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.nextRecordingId]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: "changeReplaySpeed",
- title: _ctx.translate('HeatmapSessionRecording_ChangeReplaySpeed', 'S'),
- onClick: _cache[7] || (_cache[7] = $event => _ctx.increaseReplaySpeed())
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_12, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_14, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 4]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_15, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_17, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 1]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_18, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_20, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 2]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_21, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_23, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 6]])], 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_11), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["toggleSkipPause", {
- 'active': _ctx.actualSkipPausesEnabled
- }]),
- title: _ctx.translate('HeatmapSessionRecording_ClickToSkipPauses', _ctx.skipPausesEnabledText, 'B'),
- onClick: _cache[8] || (_cache[8] = $event => _ctx.toggleSkipPauses())
- }, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_26, 10, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_24), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["toggleAutoPlay", {
- 'active': _ctx.actualAutoPlayEnabled
- }]),
- title: _ctx.translate('HeatmapSessionRecording_AutoPlayNextPageview', _ctx.autoplayEnabledText, 'A'),
- onClick: _cache[9] || (_cache[9] = $event => _ctx.toggleAutoPlay())
- }, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_29, 10, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_27), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_30, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_PlayerDurationXofY', _ctx.positionPretty, _ctx.durationPretty)), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_31, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("ul", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_32, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityClick')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_33, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityMove')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_34, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityScroll')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_35, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityResize')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_36, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityFormChange')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_37, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityPageChange')), 1)])])]), _hoisted_38]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "timelineOuter",
- onClick: _cache[10] || (_cache[10] = $event => _ctx.seekEvent($event)),
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- width: `${_ctx.replayWidth}px`
- })
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "timelineInner",
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- width: `${_ctx.progress}%`
- })
- }, null, 4), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.clues, (clue, index) => {
- return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
- title: clue.title,
- class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(clue.type),
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- left: `${clue.left}%`
- }),
- key: index
- }, null, 14, _hoisted_39);
- }), 128))], 4), _hoisted_40, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "hsrLoadingOuter",
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- width: `${_ctx.replayWidth}px`,
- height: `${_ctx.replayHeight}px`
- })
- }, [_hoisted_41, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_42, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_43, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Loading')), 1)])], 4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "replayContainerOuter",
- onClick: _cache[12] || (_cache[12] = $event => _ctx.togglePlay()),
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({
- height: `${_ctx.replayHeight}px`,
- width: `${_ctx.replayWidth}px`
- })
- }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", {
- class: "replayContainerInner",
- style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])([{
- "transform-origin": "0 0"
- }, {
- transform: `scale(${_ctx.replayScale})`,
- 'margin-left': `${_ctx.replayMarginLeft}px`
- }])
- }, [_ctx.embedUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("iframe", {
- key: 0,
- id: "recordingPlayer",
- ref: "recordingPlayer",
- onLoad: _cache[11] || (_cache[11] = $event => _ctx.onLoaded()),
- scrolling: "no",
- sandbox: "allow-scripts allow-same-origin",
- referrerpolicy: "no-referrer",
- src: _ctx.embedUrl,
- width: _ctx.recording.viewport_w_px,
- height: _ctx.recording.viewport_h_px
- }, null, 40, _hoisted_44)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 4)], 4)]);
-}
-// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=template&id=6f77b61e
-
-// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=script&lang=ts
-
-
-
-const FRAME_STEP = 20;
-const EVENT_TYPE_MOVEMENT = 1;
-const EVENT_TYPE_CLICK = 2;
-const EVENT_TYPE_SCROLL = 3;
-const EVENT_TYPE_RESIZE = 4;
-const EVENT_TYPE_INITIAL_DOM = 5;
-const EVENT_TYPE_MUTATION = 6;
-const EVENT_TYPE_FORM_TEXT = 9;
-const EVENT_TYPE_FORM_VALUE = 10;
-const EVENT_TYPE_SCROLL_ELEMENT = 12;
-const EVENT_TYPE_TO_NAME = {
- [EVENT_TYPE_CLICK]: 'clickEvent',
- [EVENT_TYPE_MOVEMENT]: 'moveEvent',
- [EVENT_TYPE_SCROLL]: 'scrollEvent',
- [EVENT_TYPE_SCROLL_ELEMENT]: 'scrollEvent',
- [EVENT_TYPE_RESIZE]: 'resizeEvent',
- [EVENT_TYPE_FORM_TEXT]: 'formChange',
- [EVENT_TYPE_FORM_VALUE]: 'formChange',
- [EVENT_TYPE_INITIAL_DOM]: 'mutationEvent',
- [EVENT_TYPE_MUTATION]: 'mutationEvent'
-};
-const EVENT_TYPE_TO_TITLE = {
- [EVENT_TYPE_CLICK]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityClick'),
- [EVENT_TYPE_MOVEMENT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityMove'),
- [EVENT_TYPE_SCROLL]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityScroll'),
- [EVENT_TYPE_SCROLL_ELEMENT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityScroll'),
- [EVENT_TYPE_RESIZE]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityResize'),
- [EVENT_TYPE_FORM_TEXT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityFormChange'),
- [EVENT_TYPE_FORM_VALUE]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityFormChange'),
- [EVENT_TYPE_INITIAL_DOM]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityPageChange'),
- [EVENT_TYPE_MUTATION]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityPageChange')
-};
-const MOUSE_POINTER_HTML = `
-
-
-
-
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/API.php b/files/plugin-HeatmapSessionRecording-5.2.6/API.php
deleted file mode 100644
index 84acc11..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/API.php
+++ /dev/null
@@ -1,1113 +0,0 @@
-validator = $validator;
- $this->aggregator = $aggregator;
- $this->siteHsr = $siteHsr;
- $this->logHsr = $logHsr;
- $this->logEvent = $logEvent;
- $this->logHsrSite = $logHsrSite;
- $this->systemSettings = $settings;
- $this->configuration = $configuration;
-
- $dir = Plugin\Manager::getPluginDirectory('UserCountry');
- require_once $dir . '/functions.php';
- }
-
- /**
- * Adds a new heatmap.
- *
- * Once added, the system will start recording activities for this heatmap.
- *
- * @param int $idSite
- * @param string $name The name of heatmap which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1".
- * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc.
- * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken.
- * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive.
- * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive.
- * @return int
- */
- public function addHeatmap($idSite, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
-
- if ($breakpointMobile === false || $breakpointMobile === null) {
- $breakpointMobile = $this->systemSettings->breakpointMobile->getValue();
- }
-
- if ($breakpointTablet === false || $breakpointTablet === null) {
- $breakpointTablet = $this->systemSettings->breakpointTablet->getValue();
- }
-
- $createdDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
- $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl);
-
- return $this->siteHsr->addHeatmap($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $createdDate);
- }
-
- /**
- * Copies a specified heatmap to one or more sites. If a heatmap with the same name already exists, the new heatmap
- * will have an automatically adjusted name to make it unique to the assigned site.
- *
- * @param int $idSite
- * @param int $idSiteHsr ID of the heatmap to duplicate.
- * @param int[] $idDestinationSites Optional array of IDs identifying which site(s) the new heatmap is to be
- * assigned to. The default is [idSite] when nothing is provided.
- * @return array
- * @throws Exception
- */
- public function duplicateHeatmap(int $idSite, int $idSiteHsr, array $idDestinationSites = []): array
- {
- // Define data array before any alterations to the variables
- $additionalData = [
- 'idSite' => $idSite,
- 'idDestinationSites' => $idDestinationSites,
- 'idSiteHsr' => $idSiteHsr,
- ];
-
- $idDestinationSites = count($idDestinationSites) > 0 ? $idDestinationSites : [$idSite];
- $idSitesToCheck = array_unique(array_merge([$idSite], $idDestinationSites));
- $this->validator->checkSitesDuplicationPermission($idSitesToCheck);
-
- // Initialise the common response values
- $duplicateRequestResponse = new DuplicateRequestResponse();
-
- $heatmap = null;
- try {
- $heatmap = $this->getHeatmap($idSite, $idSiteHsr);
- } catch (\Throwable $e) {
- // Log the error, but continue for the proper response to be built later
- $this->logError('Uncaught exception looking up heatmap to duplicate: {exception}', $e);
- }
- if (empty($heatmap['name']) || empty($heatmap['match_page_rules'])) {
- $duplicateRequestResponse->setSuccess(false);
- $duplicateRequestResponse->setMessage(Piwik::translate('HeatmapSessionRecording_SourceHeatmapLookupError'));
- $duplicateRequestResponse->setAdditionalData($additionalData);
-
- return $duplicateRequestResponse->getResponseArray();
- }
-
- $idSitesFailed = $idHrsNew = [];
- foreach ($idDestinationSites as $idDestinationSite) {
- try {
- // Make sure that the new name is unique.
- $newName = $heatmap['name'];
- $heatmaps = $this->siteHsr->getHeatmaps($idDestinationSite, false, true);
- // It can only be a duplicate name if some heatmaps were found for the site.
- if (is_array($heatmaps) && count($heatmaps) > 0) {
- $heatmapNames = array_column($heatmaps, 'name');
- $newName = EntityDuplicatorHelper::getUniqueNameComparedToList($newName, $heatmapNames, 50);
- }
-
- $response = $this->addHeatmap(
- $idDestinationSite,
- $newName,
- $heatmap['match_page_rules'],
- $heatmap['sample_limit'] ?? 1000,
- $heatmap['sample_rate'] ?? 5,
- $heatmap['excluded_elements'] ?? false,
- $heatmap['screenshot_url'] ?? false,
- $heatmap['breakpoint_mobile'] ?? false,
- $heatmap['breakpoint_tablet'] ?? false,
- $heatmap['capture_manually'] ?? false
- );
-
- // Check response for success or failure. The only return is the new ID, so make sure it's a valid int
- if (!is_int($response) || $response < 1) {
- $idSitesFailed[] = $idDestinationSite;
- continue;
- }
-
- $idHrsNew[] = $response;
- } catch (\Throwable $e) {
- $idSitesFailed[] = $idDestinationSite;
-
- // Log the error, but continue in case there are other sites to copy to
- $this->logError('Uncaught exception duplicating heatmap: {exception}', $e);
- }
- }
-
- // Set the values for success response
- $duplicateRequestResponse->setSuccess(true);
- $duplicateRequestResponse->setMessage(Piwik::translate('HeatmapSessionRecording_HeatmapCopied'));
- $additionalData['newIds'] = $idHrsNew;
- $duplicateRequestResponse->setAdditionalData($additionalData);
-
- // If any of the sites failed, update to error response
- if (count($idSitesFailed) > 0) {
- $successSites = array_diff($idDestinationSites, $idSitesFailed);
- $sitesString = count($successSites) ? implode(',', $successSites) : Piwik::translate('HeatmapSessionRecording_None');
- $message = Piwik::translate('HeatmapSessionRecording_HeatmapDuplicationError', [implode(',', $idSitesFailed), $sitesString]);
- $duplicateRequestResponse->setSuccess(false);
- $duplicateRequestResponse->setMessage($message);
- }
-
- return $duplicateRequestResponse->getResponseArray();
- }
-
- private function logError(string $message, \Throwable $e): void
- {
- StaticContainer::get(\Piwik\Log\LoggerInterface::class)->error(
- $message,
- [
- 'exception' => $e,
- 'ignoreInScreenWriter' => true,
- ]
- );
- }
-
- private function unsanitizeScreenshotUrl($screenshotUrl)
- {
- if (!empty($screenshotUrl) && is_string($screenshotUrl)) {
- $screenshotUrl = Common::unsanitizeInputValue($screenshotUrl);
- }
-
- return $screenshotUrl;
- }
-
- private function unsanitizePageRules($matchPageRules)
- {
- if (!empty($matchPageRules) && is_array($matchPageRules)) {
- foreach ($matchPageRules as $index => $matchPageRule) {
- if (is_array($matchPageRule) && !empty($matchPageRule['value'])) {
- $matchPageRules[$index]['value'] = Common::unsanitizeInputValue($matchPageRule['value']);
- }
- }
- }
- return $matchPageRules;
- }
-
- /**
- * Updates an existing heatmap.
- *
- * All fields need to be set in order to update a heatmap. Easiest way is to get all values for a heatmap via
- * "HeatmapSessionRecording.getHeatmap", make the needed changes on the heatmap, and send all values back to
- * "HeatmapSessionRecording.updateHeatmap".
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap you want to update.
- * @param string $name The name of heatmap which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1".
- * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc.
- * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken.
- * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive.
- * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive.
- */
- public function updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- if ($breakpointMobile === false || $breakpointMobile === null) {
- $breakpointMobile = $this->systemSettings->breakpointMobile->getValue();
- }
-
- if ($breakpointTablet === false || $breakpointTablet === null) {
- $breakpointTablet = $this->systemSettings->breakpointTablet->getValue();
- }
-
- $updatedDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
- $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl);
-
- $this->siteHsr->updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $updatedDate);
- }
-
- /**
- * Deletes / removes the screenshot from a heatmap
- * @param int $idSite
- * @param int $idSiteHsr
- * @return bool
- * @throws Exception
- */
- public function deleteHeatmapScreenshot($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr);
- if (!empty($heatmap['status']) && $heatmap['status'] === SiteHsrDao::STATUS_ACTIVE) {
- $this->siteHsr->setPageTreeMirror($idSite, $idSiteHsr, null, null);
-
- if (!empty($heatmap['page_treemirror'])) {
- // only needed when a screenshot existed before that
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
- return true;
- } elseif (!empty($heatmap['status'])) {
- throw new Exception('The screenshot can be only removed from active heatmaps');
- }
- }
-
- /**
- * Adds a new session recording.
- *
- * Once added, the system will start recording sessions.
- *
- * @param int $idSite
- * @param string $name The name of session recording which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1". Leave it empty to record any page.
- * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules.
- * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page.
- * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once.
- * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded.
- * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute.
- * @return int
- */
- public function addSessionRecording($idSite, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
-
- $createdDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- return $this->siteHsr->addSessionRecording($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $createdDate);
- }
-
- /**
- * Updates an existing session recording.
- *
- * All fields need to be set in order to update a session recording. Easiest way is to get all values for a
- * session recording via "HeatmapSessionRecording.getSessionRecording", make the needed changes on the recording,
- * and send all values back to "HeatmapSessionRecording.updateSessionRecording".
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to update.
- * @param string $name The name of session recording which will be visible in the reporting UI.
- * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory'))
- * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}.
- * "inverted" should be "0" or "1". Leave it empty to record any page.
- * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules.
- * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically.
- * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%.
- * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported.
- * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page.
- * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once.
- * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded.
- * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute.
- */
- public function updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $updatedDate = Date::now()->getDatetime();
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- $this->siteHsr->updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $updatedDate);
- }
-
- /**
- * Get a specific heatmap by its ID.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- * @return array|false
- */
- public function getHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr);
-
- return $heatmap;
- }
-
- /**
- * Get a specific session recording by its ID.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- * @return array|false
- */
- public function getSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- return $this->siteHsr->getSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Pauses the given heatmap.
- *
- * When a heatmap is paused, all the tracking will be paused until its resumed again.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function pauseHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->pauseHeatmap($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Resumes the given heatmap.
- *
- * When a heatmap is resumed, all the tracking will be enabled.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function resumeHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->resumeHeatmap($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Deletes the given heatmap.
- *
- * When a heatmap is deleted, the report will be no longer available in the API and tracked data for this
- * heatmap might be removed.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function deleteHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
-
- $this->siteHsr->deactivateHeatmap($idSite, $idSiteHsr);
- }
-
- /**
- * Ends / finishes the given heatmap.
- *
- * When you end a heatmap, the heatmap reports will be still available via API and UI but no new heatmap activity
- * will be recorded for this heatmap.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap.
- */
- public function endHeatmap($idSite, $idSiteHsr)
- {
- $this->validator->checkHeatmapReportWritePermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $this->siteHsr->endHeatmap($idSite, $idSiteHsr);
- }
-
- /**
- * Pauses the given session recording.
- *
- * When a session recording is paused, all the tracking will be paused until its resumed again.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function pauseSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->pauseSessionRecording($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Resumes the given session recording.
- *
- * When a session recording is resumed, all the tracking will be enabled.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the heatmap
- */
- public function resumeSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->resumeSessionRecording($idSite, $idSiteHsr);
-
- Cache::deleteCacheWebsiteAttributes($idSite);
- }
-
- /**
- * Deletes the given session recording.
- *
- * When a session recording is deleted, any related recordings be no longer available in the API and tracked data
- * for this session recording might be removed.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording.
- */
- public function deleteSessionRecording($idSite, $idSiteHsr)
- {
-
- $this->validator->checkSessionReportWritePermission($idSite);
-
- $this->siteHsr->deactivateSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Ends / finishes the given session recording.
- *
- * When you end a session recording, the session recording reports will be still available via API and UI but no new
- * session will be recorded anymore.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording.
- */
- public function endSessionRecording($idSite, $idSiteHsr)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->siteHsr->endSessionRecording($idSite, $idSiteHsr);
- }
-
- /**
- * Get all available heatmaps for a specific website or app.
- *
- * It will return active as well as ended heatmaps but not any deleted heatmaps.
- *
- * @param int $idSite
- * @param bool|int $includePageTreeMirror set to 0 if you don't need the page tree mirror for heatmaps (improves performance)
- * @return array
- */
- public function getHeatmaps($idSite, $includePageTreeMirror = true)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
-
- return $this->siteHsr->getHeatmaps($idSite, !empty($includePageTreeMirror));
- }
-
- /**
- * Get all available session recordings for a specific website or app.
- *
- * It will return active as well as ended session recordings but not any deleted session recordings.
- *
- * @param int $idSite
- * @return array
- */
- public function getSessionRecordings($idSite)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
-
- return $this->siteHsr->getSessionRecordings($idSite);
- }
-
- /**
- * Returns all page views that were recorded during a particular session / visit. We do not apply segments as it is
- * used for video player when replaying sessions etc.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of a session recording
- * @param int $idVisit The visit / session id
- * @return array
- */
- private function getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date)
- {
- $timezone = Site::getTimezoneFor($idSite);
-
- // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons
- $pageviews = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment = false);
-
- $isAnonymous = Piwik::isUserIsAnonymous();
-
- foreach ($pageviews as &$pageview) {
- $pageview['server_time_pretty'] = Date::factory($pageview['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT);
-
- if ($isAnonymous) {
- unset($pageview['idvisitor']);
- } else {
- $pageview['idvisitor'] = bin2hex($pageview['idvisitor']);
- }
-
- $formatter = new Formatter();
- $pageview['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($pageview['time_on_page'] / 1000), $asSentence = true);
- }
-
- return $pageviews;
- }
-
- /**
- * Returns all recorded sessions for a specific session recording.
- *
- * To get the actual recorded data for any of the recorded sessions, call {@link getRecordedSession()}.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the session recording you want to retrieve all the recorded sessions for.
- * @param bool $segment
- * @param int $idSubtable Optional visit id if you want to get all recorded pageviews of a specific visitor
- * @return DataTable
- */
- public function getRecordedSessions($idSite, $period, $date, $idSiteHsr, $segment = false, $idSubtable = false)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $idVisit = $idSubtable;
-
- try {
- PeriodFactory::checkPeriodIsEnabled($period);
- } catch (\Exception $e) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_PeriodDisabledErrorMessage', $period));
- }
-
- if (!empty($idVisit)) {
- $recordings = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment);
- } else {
- $recordings = $this->aggregator->getRecordedSessions($idSite, $idSiteHsr, $period, $date, $segment);
- }
-
- $table = new DataTable();
- $table->disableFilter('AddColumnsProcessedMetrics');
- $table->setMetadata('idSiteHsr', $idSiteHsr);
-
- if (!empty($recordings)) {
- $table->addRowsFromSimpleArray($recordings);
- }
-
- if (empty($idVisit)) {
- $table->queueFilter(function (DataTable $table) {
- foreach ($table->getRowsWithoutSummaryRow() as $row) {
- if ($idVisit = $row->getColumn('idvisit')) {
- $row->setNonLoadedSubtableId($idVisit);
- }
- }
- });
- } else {
- $table->disableFilter('Sort');
- }
-
- if (!method_exists(SettingsServer::class, 'isMatomoForWordPress') || !SettingsServer::isMatomoForWordPress()) {
- $table->queueFilter(function (DataTable $table) use ($idSite, $idSiteHsr, $period, $date) {
- foreach ($table->getRowsWithoutSummaryRow() as $row) {
- $idLogHsr = $row->getColumn('idloghsr');
- $row->setMetadata('sessionReplayUrl', SiteHsrModel::completeWidgetUrl('replayRecording', 'idSiteHsr=' . (int) $idSiteHsr . '&idLogHsr=' . (int) $idLogHsr, $idSite, $period, $date));
- }
- });
- }
-
- $table->filter('Piwik\Plugins\HeatmapSessionRecording\DataTable\Filter\EnrichRecordedSessions');
-
- return $table;
- }
-
- /**
- * Get all activities of a specific recorded session.
- *
- * This includes events such as clicks, mouse moves, scrolls, resizes, page / HTML DOM changes, form changed.
- * It is recommended to call this API method with filter_limit = -1 to retrieve all results. It also returns
- * metadata like the viewport size the user had when it was recorded, the browser, operating system, and more.
- *
- * To see what each event type in the events property means, call {@link getEventTypes()}.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to retrieve the data for.
- * @param int $idLogHsr The id of the recorded session you want to retrieve the data for.
- * @return array
- * @throws Exception
- */
- public function getRecordedSession($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons
- $session = $this->aggregator->getRecordedSession($idLogHsr);
-
- if (empty($session['idsite']) || empty($idSite)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
-
- if ($session['idsite'] != $idSite) {
- // important otherwise can fetch any log entry!
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
-
- $session['idvisitor'] = !empty($session['idvisitor']) ? bin2hex($session['idvisitor']) : '';
-
- if (Piwik::isUserIsAnonymous()) {
- foreach (EnrichRecordedSessions::getBlockedFields() as $blockedField) {
- if (isset($session[$blockedField])) {
- $session[$blockedField] = null;
- }
- }
- }
-
-
- $configBrowserName = !empty($session['config_browser_name']) ? $session['config_browser_name'] : '';
- $session['browser_name'] = \Piwik\Plugins\DevicesDetection\getBrowserName($configBrowserName);
- $session['browser_logo'] = \Piwik\Plugins\DevicesDetection\getBrowserLogo($configBrowserName);
- $configOs = !empty($session['config_os']) ? $session['config_os'] : '';
- $session['os_name'] = \Piwik\Plugins\DevicesDetection\getOsFullName($configOs);
- $session['os_logo'] = \Piwik\Plugins\DevicesDetection\getOsLogo($configOs);
- $session['device_name'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($session['config_device_type']);
- $session['device_logo'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLogo($session['config_device_type']);
-
- if (!empty($session['config_device_model'])) {
- $session['device_name'] .= ', ' . $session['config_device_model'];
- }
-
- $session['location_name'] = '';
- $session['location_logo'] = '';
-
- if (!empty($session['location_country'])) {
- $session['location_name'] = \Piwik\Plugins\UserCountry\countryTranslate($session['location_country']);
- $session['location_logo'] = \Piwik\Plugins\UserCountry\getFlagFromCode($session['location_country']);
-
- if (!empty($session['location_region']) && $session['location_region'] != Visit::UNKNOWN_CODE) {
- $session['location_name'] .= ', ' . \Piwik\Plugins\UserCountry\getRegionNameFromCodes($session['location_country'], $session['location_region']);
- }
-
- if (!empty($session['location_city'])) {
- $session['location_name'] .= ', ' . $session['location_city'];
- }
- }
-
- $timezone = Site::getTimezoneFor($idSite);
- $session['server_time_pretty'] = Date::factory($session['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT);
-
- $formatter = new Formatter();
- $session['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($session['time_on_page'] / 1000), $asSentence = true);
-
- // we make sure to get all recorded pageviews in this session
- $serverTime = Date::factory($session['server_time']);
- $from = $serverTime->subDay(1)->toString();
- $to = $serverTime->addDay(1)->toString();
-
- $period = 'range';
- $dateRange = $from . ',' . $to;
-
- $session['events'] = $this->logEvent->getEventsForPageview($idLogHsr);
- $session['pageviews'] = $this->getRecordedPageViewsInSession($idSite, $idSiteHsr, $session['idvisit'], $period, $dateRange);
- $session['numPageviews'] = count($session['pageviews']);
-
- return $session;
- }
-
- /**
- * Deletes all recorded page views within a recorded session.
- *
- * Once a recorded session has been deleted, the replay video will no longer be available in the UI and no data
- * can be retrieved anymore via the API.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to delete the data.
- * @param int $idVisit The visitId of the recorded session you want to delete.
- */
- public function deleteRecordedSession($idSite, $idSiteHsr, $idVisit)
- {
- $this->validator->checkSessionReportWritePermission($idSite);
- // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- // we also need to make sure the visit actually belongs to that site
- $idLogHsrs = $this->logHsr->findLogHsrIdsInVisit($idSite, $idVisit);
-
- foreach ($idLogHsrs as $idLogHsr) {
- $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr);
- }
- }
-
- /**
- * Deletes an individual page view within a recorded session.
- *
- * It only deletes one recorded session of one page view, not all recorded sessions.
- * Once a recorded page view has been deleted, the replay video will no longer be available in the UI and no data
- * can be retrieved anymore via the API for this page view.
- *
- * @param int $idSite
- * @param int $idSiteHsr The id of the session recording you want to delete the data.
- * @param int $idLogHsr The id of the recorded session you want to delete.
- */
- public function deleteRecordedPageview($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkWritePermission($idSite);
- // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site
- $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr);
-
- $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr);
- }
-
- /**
- * Get metadata for a specific heatmap like the number of samples / pageviews that were recorded or the
- * average above the fold per device type.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the heatmap you want to retrieve the meta data for.
- * @param bool|string $segment
- * @return array
- */
- public function getRecordedHeatmapMetadata($idSite, $period, $date, $idSiteHsr, $segment = false)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- $samples = $this->aggregator->getRecordedHeatmapMetadata($idSiteHsr, $idSite, $period, $date, $segment);
-
- $result = array('nb_samples_device_all' => 0);
-
- foreach ($samples as $sample) {
- $result['nb_samples_device_' . $sample['device_type']] = $sample['value'];
- $result['avg_fold_device_' . $sample['device_type']] = round(($sample['avg_fold'] / LogHsr::SCROLL_ACCURACY) * 100, 1);
- $result['nb_samples_device_all'] += $sample['value'];
- }
-
- return $result;
- }
-
- /**
- * Get all activities of a heatmap.
- *
- * For example retrieve all mouse movements made by desktop visitors, or all clicks made my tablet visitors, or
- * all scrolls by mobile users. It is recommended to call this method with filter_limit = -1 to retrieve all
- * results. As there can be many results, you may want to call this method several times using filter_limit and
- * filter_offset.
- *
- * @param int $idSite
- * @param string $period
- * @param string $date
- * @param int $idSiteHsr The id of the heatmap you want to retrieve the data for.
- * @param int $heatmapType To see which heatmap types can be used, call {@link getAvailableHeatmapTypes()}
- * @param int $deviceType To see which device types can be used, call {@link getAvailableDeviceTypes()}
- * @param bool|string $segment
- * @return array
- */
- public function getRecordedHeatmap($idSite, $period, $date, $idSiteHsr, $heatmapType, $deviceType, $segment = false)
- {
- $this->validator->checkHeatmapReportViewPermission($idSite);
- $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr);
-
- if ($heatmapType == RequestProcessor::EVENT_TYPE_SCROLL) {
- $heatmap = $this->aggregator->aggregateScrollHeatmap($idSiteHsr, $deviceType, $idSite, $period, $date, $segment);
- } else {
- $heatmap = $this->aggregator->aggregateHeatmap($idSiteHsr, $heatmapType, $deviceType, $idSite, $period, $date, $segment);
- }
-
- // we do not return dataTable here as it doubles the time it takes to call this method (eg 4s vs 7s when heaps of data)
- // datatable is not really needed here as we don't want to sort it or so
- return $heatmap;
- }
-
- /**
- * @param $idSite
- * @param $idSiteHsr
- * @param $idLogHsr
- * @return array
- * @hide
- */
- public function getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr)
- {
- $this->validator->checkSessionReportViewPermission($idSite);
-
- $aggregator = new Aggregator();
- return $aggregator->getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr);
- }
-
- /**
- * Tests, checks whether the given URL matches the given page rules.
- *
- * This can be used before configuring a heatmap or session recording to make sure the configured target page(s)
- * will match a specific URL.
- *
- * @param string $url
- * @param array $matchPageRules
- * @return array
- * @throws Exception
- */
- public function testUrlMatchPages($url, $matchPageRules = array())
- {
- $this->validator->checkHasSomeWritePermission();
-
- if ($url === '' || $url === false || $url === null) {
- return array('url' => '', 'matches' => false);
- }
-
- if (!empty($matchPageRules) && !is_array($matchPageRules)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorNotAnArray', 'matchPageRules'));
- }
-
- $url = Common::unsanitizeInputValue($url);
-
- if (!empty($matchPageRules)) {
- $pageRules = new PageRules($matchPageRules, '', $needsOneEntry = false);
- $pageRules->check();
- }
-
- $matchPageRules = $this->unsanitizePageRules($matchPageRules);
-
- $allMatch = HsrMatcher::matchesAllPageRules($matchPageRules, $url);
-
- return array('url' => $url, 'matches' => $allMatch);
- }
-
- /**
- * Get a list of valid heatmap and session recording statuses (eg "active", "ended")
- *
- * @return array
- */
- public function getAvailableStatuses()
- {
- $this->validator->checkHasSomeWritePermission();
-
- return array(
- array('value' => SiteHsrDao::STATUS_ACTIVE, 'name' => Piwik::translate('HeatmapSessionRecording_StatusActive')),
- array('value' => SiteHsrDao::STATUS_ENDED, 'name' => Piwik::translate('HeatmapSessionRecording_StatusEnded')),
- );
- }
-
- /**
- * Get a list of all available target attributes and target types for "pageTargets" / "page rules".
- *
- * For example URL, URL Parameter, Path, simple comparison, contains, starts with, and more.
- *
- * @return array
- */
- public function getAvailableTargetPageRules()
- {
- $this->validator->checkHasSomeWritePermission();
-
- return PageRuleMatcher::getAvailableTargetTypes();
- }
-
- /**
- * Get a list of available device types that can be used when fetching a heatmap report.
- *
- * For example desktop, tablet, mobile.
- *
- * @return array
- */
- public function getAvailableDeviceTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array('name' => Piwik::translate('General_Desktop'),
- 'key' => LogHsr::DEVICE_TYPE_DESKTOP,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/desktop.png'),
- array('name' => Piwik::translate('DevicesDetection_Tablet'),
- 'key' => LogHsr::DEVICE_TYPE_TABLET,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/tablet.png'),
- array('name' => Piwik::translate('General_Mobile'),
- 'key' => LogHsr::DEVICE_TYPE_MOBILE,
- 'logo' => 'plugins/Morpheus/icons/dist/devices/smartphone.png'),
- );
- }
-
- /**
- * Get a list of available heatmap types that can be used when fetching a heatmap report.
- *
- * For example click, mouse move, scroll.
- *
- * @return array
- */
- public function getAvailableHeatmapTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'),
- 'key' => RequestProcessor::EVENT_TYPE_CLICK),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'),
- 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL),
- );
- }
-
- /**
- * Get a list of available session recording sample limits.
- *
- * Note: This is only a suggested list of sample limits that should be shown in the UI when creating or editing a
- * session recording. When you configure a session recording via the API directly, any limit can be used.
- *
- * For example 50, 100, 200, 500
- *
- * @return array
- */
- public function getAvailableSessionRecordingSampleLimits()
- {
- $this->validator->checkHasSomeWritePermission();
- $this->validator->checkSessionRecordingEnabled();
-
- return $this->configuration->getSessionRecordingSampleLimits();
- }
-
- /**
- * Get a list of available event types that may be returned eg when fetching a recorded session.
- *
- * @return array
- */
- public function getEventTypes()
- {
- Piwik::checkUserHasSomeViewAccess();
-
- return array(
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'),
- 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'),
- 'key' => RequestProcessor::EVENT_TYPE_CLICK),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityResize'),
- 'key' => RequestProcessor::EVENT_TYPE_RESIZE),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityInitialDom'),
- 'key' => RequestProcessor::EVENT_TYPE_INITIAL_DOM),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityPageChange'),
- 'key' => RequestProcessor::EVENT_TYPE_MUTATION),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormText'),
- 'key' => RequestProcessor::EVENT_TYPE_FORM_TEXT),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormValue'),
- 'key' => RequestProcessor::EVENT_TYPE_FORM_VALUE),
- array(
- 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScrollElement'),
- 'key' => RequestProcessor::EVENT_TYPE_SCROLL_ELEMENT),
- );
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Actions/ActionHsr.php b/files/plugin-HeatmapSessionRecording-5.2.6/Actions/ActionHsr.php
deleted file mode 100644
index 8eb7998..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Actions/ActionHsr.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getParam('url');
-
- $this->setActionUrl($url);
- }
-
- public static function shouldHandle(Request $request)
- {
- $params = $request->getParams();
- $isHsrRequest = Common::getRequestVar(RequestProcessor::TRACKING_PARAM_HSR_ID_VIEW, '', 'string', $params);
-
- return !empty($isHsrRequest);
- }
-
- protected function getActionsToLookup()
- {
- return array();
- }
-
- // Do not track this Event URL as Entry/Exit Page URL (leave the existing entry/exit)
- public function getIdActionUrlForEntryAndExitIds()
- {
- return false;
- }
-
- // Do not track this Event Name as Entry/Exit Page Title (leave the existing entry/exit)
- public function getIdActionNameForEntryAndExitIds()
- {
- return false;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/BaseActivity.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/BaseActivity.php
deleted file mode 100644
index daac776..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/BaseActivity.php
+++ /dev/null
@@ -1,114 +0,0 @@
- $this->getSiteData($idSite),
- 'version' => 'v1',
- 'hsr' => $this->getHsrData($idSiteHsr, $idSite),
- );
- }
-
- private function getSiteData($idSite)
- {
- return array(
- 'site_id' => $idSite,
- 'site_name' => Site::getNameFor($idSite)
- );
- }
-
- private function getHsrData($idSiteHsr, $idSite)
- {
- $dao = $this->getDao();
- $hsr = $dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP);
- if (empty($hsr)) {
- // maybe it is a session? we could make this faster by adding a new method to DAO that returns hsr independent of type
- $hsr = $dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION);
- }
-
- $hsrName = '';
- if (!empty($hsr['name'])) {
- // hsr name might not be set when we are handling deleteExperiment activity
- $hsrName = $hsr['name'];
- }
-
- return array(
- 'id' => $idSiteHsr,
- 'name' => $hsrName
- );
- }
-
- public function getPerformingUser($eventData = null)
- {
- $login = Piwik::getCurrentUserLogin();
-
- if ($login === self::USER_ANONYMOUS || empty($login)) {
- // anonymous cannot change an experiment, in this case the system changed it, eg during tracking it started
- // an experiment
- return self::USER_SYSTEM;
- }
-
- return $login;
- }
-
- private function getDao()
- {
- // we do not get it via DI as it would slow down creation of all activities on all requests. Instead only
- // create instance when needed
- return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao');
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapAdded.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapAdded.php
deleted file mode 100644
index ad04966..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapAdded.php
+++ /dev/null
@@ -1,41 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapAddedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapDeleted.php
deleted file mode 100644
index fae6d25..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapDeleted.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapDeletedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapEnded.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapEnded.php
deleted file mode 100644
index 28afe54..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapEnded.php
+++ /dev/null
@@ -1,44 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapEndedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapPaused.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapPaused.php
deleted file mode 100644
index ddacfb5..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapPaused.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapPausedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapResumed.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapResumed.php
deleted file mode 100644
index 9062306..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapResumed.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapResumedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapScreenshotDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapScreenshotDeleted.php
deleted file mode 100644
index 4275d16..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapScreenshotDeleted.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapScreenshotDeletedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapUpdated.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapUpdated.php
deleted file mode 100644
index 16b0636..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/HeatmapUpdated.php
+++ /dev/null
@@ -1,42 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_HeatmapUpdatedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedPageviewDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedPageviewDeleted.php
deleted file mode 100644
index 0c608b3..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedPageviewDeleted.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_RecordedPageviewDeletedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedSessionDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedSessionDeleted.php
deleted file mode 100644
index 0eb581a..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/RecordedSessionDeleted.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_RecordedSessionDeletedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingAdded.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingAdded.php
deleted file mode 100644
index 6178a0e..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingAdded.php
+++ /dev/null
@@ -1,41 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingAddedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingDeleted.php
deleted file mode 100644
index 2f35f7f..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingDeleted.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingDeletedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingEnded.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingEnded.php
deleted file mode 100644
index dfd0df0..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingEnded.php
+++ /dev/null
@@ -1,44 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingEndedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingPaused.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingPaused.php
deleted file mode 100644
index 7b376fd..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingPaused.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingPausedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingResumed.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingResumed.php
deleted file mode 100644
index b89500d..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingResumed.php
+++ /dev/null
@@ -1,46 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingResumedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingUpdated.php b/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingUpdated.php
deleted file mode 100644
index fd35fa1..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Activity/SessionRecordingUpdated.php
+++ /dev/null
@@ -1,42 +0,0 @@
-formatActivityData($idSiteHsr, $idSite);
- }
-
- public function getTranslatedDescription($activityData, $performingUser)
- {
- $siteName = $this->getSiteNameFromActivityData($activityData);
- $hsrName = $this->getHsrNameFromActivityData($activityData);
-
- return Piwik::translate('HeatmapSessionRecording_SessionRecordingUpdatedActivity', [$hsrName, $siteName]);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Archiver/Aggregator.php b/files/plugin-HeatmapSessionRecording-5.2.6/Archiver/Aggregator.php
deleted file mode 100644
index bc15cef..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Archiver/Aggregator.php
+++ /dev/null
@@ -1,476 +0,0 @@
-forceSleepInQuery) {
- $extraWhere = 'SLEEP(1) AND';
- }
-
- $query = sprintf(
- 'SELECT /* HeatmapSessionRecording.findRecording */ hsrsite.idsitehsr,
- min(hsr.idloghsr) as idloghsr
- FROM %s hsr
- LEFT JOIN %s hsrsite ON hsr.idloghsr = hsrsite.idloghsr
- LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s
- LEFT JOIN %s sitehsr ON hsrsite.idsitehsr = sitehsr.idsitehsr
- WHERE %s hsr.idvisit = ? and sitehsr.record_type = ? and hsrevent.idhsrblob is not null and hsrsite.idsitehsr is not null
- GROUP BY hsrsite.idsitehsr
- LIMIT 1',
- Common::prefixTable('log_hsr'),
- Common::prefixTable('log_hsr_site'),
- Common::prefixTable('log_hsr_event'),
- RequestProcessor::EVENT_TYPE_INITIAL_DOM,
- Common::prefixTable('site_hsr'),
- $extraWhere
- );
-
- $readerDb = $this->getDbReader();
- $query = DbHelper::addMaxExecutionTimeHintToQuery($query, $this->getLiveQueryMaxExecutionTime());
-
- try {
- return $readerDb->fetchRow($query, array($idVisit, SiteHsrDao::RECORD_TYPE_SESSION));
- } catch (\Exception $e) {
- Model::handleMaxExecutionTimeError($readerDb, $e, '', Date::now(), Date::now(), null, 0, ['sql' => $query]);
- throw $e;
- }
- }
-
- private function getDbReader()
- {
- if (method_exists(Db::class, 'getReader')) {
- return Db::getReader();
- } else {
- return Db::get();
- }
- }
-
- public function findRecordings($visitIds)
- {
- if (empty($visitIds)) {
- return array();
- }
-
- $visitIds = array_map('intval', $visitIds);
-
- $extraWhere = '';
- if ($this->forceSleepInQuery) {
- $extraWhere = 'SLEEP(1) AND';
- }
-
- $query = sprintf(
- 'SELECT /* HeatmapSessionRecording.findRecordings */ hsrsite.idsitehsr,
- min(hsr.idloghsr) as idloghsr,
- hsr.idvisit
- FROM %s hsr
- LEFT JOIN %s hsrsite ON hsr.idloghsr = hsrsite.idloghsr
- LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s
- LEFT JOIN %s sitehsr ON hsrsite.idsitehsr = sitehsr.idsitehsr
- WHERE %s hsr.idvisit IN ("%s") and sitehsr.record_type = ? and hsrevent.idhsrblob is not null and hsrsite.idsitehsr is not null
- GROUP BY hsr.idvisit, hsrsite.idsitehsr',
- Common::prefixTable('log_hsr'),
- Common::prefixTable('log_hsr_site'),
- Common::prefixTable('log_hsr_event'),
- RequestProcessor::EVENT_TYPE_INITIAL_DOM,
- Common::prefixTable('site_hsr'),
- $extraWhere,
- implode('","', $visitIds)
- );
-
- $readerDb = $this->getDbReader();
- $query = DbHelper::addMaxExecutionTimeHintToQuery($query, $this->getLiveQueryMaxExecutionTime());
-
- try {
- return $readerDb->fetchAll($query, array(SiteHsrDao::RECORD_TYPE_SESSION));
- } catch (\Exception $e) {
- Model::handleMaxExecutionTimeError($readerDb, $e, '', Date::now(), Date::now(), null, 0, ['sql' => $query]);
- throw $e;
- }
- }
-
- private function getLiveQueryMaxExecutionTime()
- {
- return Config::getInstance()->General['live_query_max_execution_time'];
- }
-
- public function getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr)
- {
- $logHsr = Common::prefixTable('log_hsr');
- $logHsrSite = Common::prefixTable('log_hsr_site');
- $logAction = Common::prefixTable('log_action');
- $logEvent = Common::prefixTable('log_hsr_event');
- $logBlob = Common::prefixTable('log_hsr_blob');
-
- $query = sprintf(
- 'SELECT laction.name as base_url,
- laction.url_prefix, hsrblob.`value` as initial_mutation, hsrblob.compressed
- FROM %s hsr
- LEFT JOIN %s laction ON laction.idaction = hsr.idaction_url
- LEFT JOIN %s hsr_site ON hsr_site.idloghsr = hsr.idloghsr
- LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s
- LEFT JOIN %s hsrblob ON hsrevent.idhsrblob = hsrblob.idhsrblob
- WHERE hsr.idloghsr = ? and hsr.idsite = ? and hsr_site.idsitehsr = ?
- and hsrevent.idhsrblob is not null and `hsrblob`.`value` is not null
- LIMIT 1',
- $logHsr,
- $logAction,
- $logHsrSite,
- $logEvent,
- RequestProcessor::EVENT_TYPE_INITIAL_DOM,
- $logBlob
- );
-
- $row = $this->getDbReader()->fetchRow($query, array($idLogHsr, $idSite, $idSiteHsr));
-
- if (!empty($row['compressed'])) {
- $row['initial_mutation'] = gzuncompress($row['initial_mutation']);
- }
-
- return $row;
- }
-
- public function getRecordedSession($idLogHsr)
- {
- $select = 'log_action.name as url,
- log_visit.idvisit,
- log_visit.idvisitor,
- log_hsr.idsite,
- log_visit.location_country,
- log_visit.location_region,
- log_visit.location_city,
- log_visit.config_os,
- log_visit.config_device_type,
- log_visit.config_device_model,
- log_visit.config_browser_name,
- log_hsr.time_on_page,
- log_hsr.server_time,
- log_hsr.viewport_w_px,
- log_hsr.viewport_h_px,
- log_hsr.scroll_y_max_relative,
- log_hsr.fold_y_relative';
-
- $logHsr = Common::prefixTable('log_hsr');
- $logVisit = Common::prefixTable('log_visit');
- $logAction = Common::prefixTable('log_action');
-
- $query = sprintf('SELECT %s
- FROM %s log_hsr
- LEFT JOIN %s log_visit ON log_hsr.idvisit = log_visit.idvisit
- LEFT JOIN %s log_action ON log_action.idaction = log_hsr.idaction_url
- WHERE log_hsr.idloghsr = ?', $select, $logHsr, $logVisit, $logAction);
-
- return $this->getDbReader()->fetchRow($query, array($idLogHsr));
- }
-
- public function getRecordedSessions($idSite, $idSiteHsr, $period, $date, $segment)
- {
- $period = Period\Factory::build($period, $date);
- $segment = new Segment($segment, array($idSite));
- $site = new Site($idSite);
-
- $from = array(
- 'log_hsr',
- array(
- 'table' => 'log_hsr_site',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr'
- ),
- array(
- 'table' => 'log_visit',
- 'joinOn' => 'log_visit.idvisit = log_hsr.idvisit'
- ),
- array(
- 'table' => 'log_action',
- 'joinOn' => 'log_action.idaction = log_hsr.idaction_url'
- ),
- array(
- 'table' => 'log_hsr_event',
- 'joinOn' => 'log_hsr_event.idloghsr = log_hsr.idloghsr and log_hsr_event.event_type = ' . RequestProcessor::EVENT_TYPE_INITIAL_DOM
- )
- );
-
- // we need to make sure to show only sessions that have an initial mutation with time_since_load = 0, otherwise
- // the recording won't work.
- $logHsrEventTable = Common::prefixTable('log_hsr_event');
-
- $actionQuery = sprintf('SELECT count(*) FROM %1$s as hsr_ev
- WHERE hsr_ev.idloghsr = log_hsr_site.idloghsr and hsr_ev.event_type not in (%2$s, %3$s)', $logHsrEventTable, RequestProcessor::EVENT_TYPE_CSS, RequestProcessor::EVENT_TYPE_INITIAL_DOM);
-
- $select = 'log_hsr.idvisit as label,
- count(*) as nb_pageviews,
- log_hsr.idvisit,
- SUBSTRING_INDEX(GROUP_CONCAT(CAST(log_action.name AS CHAR) ORDER BY log_hsr.server_time ASC SEPARATOR \'##\'), \'##\', 1) as first_url,
- SUBSTRING_INDEX(GROUP_CONCAT(CAST(log_action.name AS CHAR) ORDER BY log_hsr.server_time DESC SEPARATOR \'##\'), \'##\', 1) as last_url,
- sum(log_hsr.time_on_page) as time_on_site,
- (' . $actionQuery . ') as total_events,
- min(log_hsr_site.idloghsr) as idloghsr,
- log_visit.idvisitor,
- log_visit.location_country,
- log_visit.location_region,
- log_visit.location_city,
- log_visit.config_os,
- log_visit.config_device_type,
- log_visit.config_device_model,
- log_visit.config_browser_name,
- min(log_hsr.server_time) as server_time';
-
- $params = new ArchiveProcessor\Parameters($site, $period, $segment);
- $logAggregator = new LogAggregator($params);
-
- $where = $logAggregator->getWhereStatement('log_hsr', 'server_time');
- $where .= sprintf(" and log_hsr_site.idsitehsr = %d and log_hsr_event.idhsrblob is not null", (int) $idSiteHsr);
- $groupBy = 'log_hsr.idvisit';
- $orderBy = 'log_hsr.server_time DESC';
-
- $revertSubselect = $this->applyForceSubselect($segment, 'log_hsr.idvisit,log_hsr_site.idloghsr');
-
- $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
-
- if (!empty($revertSubselect) && is_callable($revertSubselect)) {
- call_user_func($revertSubselect);
- }
-
- $dbReader = $this->getDbReader();
- $query['sql'] = DbHelper::addMaxExecutionTimeHintToQuery($query['sql'], $this->getLiveQueryMaxExecutionTime());
-
- try {
- return $dbReader->fetchAll($query['sql'], $query['bind']);
- } catch (\Exception $e) {
- Model::handleMaxExecutionTimeError($dbReader, $e, '', Date::now(), Date::now(), null, 0, $query);
- throw $e;
- }
- }
-
- private function applyForceSubselect($segment, $subselectForced)
- {
- // for performance reasons we use this and not `LogAggregator->allowUsageSegmentCache()`
- // That's because this is a LIVE query and not archived... and HSR tables usually have few entries < 5000
- // so segmentation should be fairly fast using this method compared to allowUsageSegmentCache
- // which would query the entire log_visit over several days with the applied query and then create the temp table
- // and only then apply the log_hsr query.
- // it should be a lot faster this way
- if (class_exists('Piwik\DataAccess\LogQueryBuilder') && !$segment->isEmpty()) {
- $logQueryBuilder = StaticContainer::get('Piwik\DataAccess\LogQueryBuilder');
- if (
- method_exists($logQueryBuilder, 'getForcedInnerGroupBySubselect') &&
- method_exists($logQueryBuilder, 'forceInnerGroupBySubselect')
- ) {
- $forceGroupByBackup = $logQueryBuilder->getForcedInnerGroupBySubselect();
- $logQueryBuilder->forceInnerGroupBySubselect($subselectForced);
-
- return function () use ($forceGroupByBackup, $logQueryBuilder) {
- $logQueryBuilder->forceInnerGroupBySubselect($forceGroupByBackup);
- };
- }
- }
- }
-
- public function getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment)
- {
- $period = Period\Factory::build($period, $date);
- $segment = new Segment($segment, array($idSite));
- $site = new Site($idSite);
-
- $from = array(
- 'log_hsr',
- array(
- 'table' => 'log_hsr_site',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr'
- ),
- array(
- 'table' => 'log_visit',
- 'joinOn' => 'log_visit.idvisit = log_hsr.idvisit'
- ),
- array(
- 'table' => 'log_action',
- 'joinOn' => 'log_action.idaction = log_hsr.idaction_url'
- ),
- array(
- 'table' => 'log_hsr_event',
- 'joinOn' => 'log_hsr_event.idloghsr = log_hsr.idloghsr and log_hsr_event.event_type = ' . RequestProcessor::EVENT_TYPE_INITIAL_DOM
- )
- );
-
- // we need to make sure to show only sessions that have an initial mutation with time_since_load = 0, otherwise
- // the recording won't work. If this happens often, we might "end / finish" a configured session recording
- // earlier since we have eg recorded 1000 sessions, but user sees only 950 which will be confusing but we can
- // for now not take this into consideration during tracking when we get number of available samples only using
- // log_hsr_site to detect if the number of configured sessions have been reached. ideally we would at some point
- // also make sure to include this check there but will be slower.
-
- $select = 'log_action.name as label,
- log_visit.idvisitor,
- log_hsr_site.idloghsr,
- log_hsr.time_on_page as time_on_page,
- CONCAT(log_hsr.viewport_w_px, "x", log_hsr.viewport_h_px) as resolution,
- log_hsr.server_time,
- log_hsr.scroll_y_max_relative,
- log_hsr.fold_y_relative';
-
- $params = new ArchiveProcessor\Parameters($site, $period, $segment);
- $logAggregator = new LogAggregator($params);
-
- $where = $logAggregator->getWhereStatement('log_hsr', 'server_time');
- $where .= sprintf(" and log_hsr_site.idsitehsr = %d and log_hsr.idvisit = %d and log_hsr_event.idhsrblob is not null ", (int) $idSiteHsr, (int) $idVisit);
- $groupBy = '';
- $orderBy = 'log_hsr.server_time ASC';
-
- $revertSubselect = $this->applyForceSubselect($segment, 'log_hsr.idvisit,log_hsr_site.idloghsr');
-
- $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
-
- if (!empty($revertSubselect) && is_callable($revertSubselect)) {
- call_user_func($revertSubselect);
- }
-
- return $this->getDbReader()->fetchAll($query['sql'], $query['bind']);
- }
-
- public function aggregateHeatmap($idSiteHsr, $heatmapType, $deviceType, $idSite, $period, $date, $segment)
- {
- $heatmapTypeWhere = '';
- if ($heatmapType == RequestProcessor::EVENT_TYPE_CLICK) {
- $heatmapTypeWhere .= 'log_hsr_event.event_type = ' . (int) $heatmapType;
- } elseif ($heatmapType == RequestProcessor::EVENT_TYPE_MOVEMENT) {
- $heatmapTypeWhere .= 'log_hsr_event.event_type IN(' . (int) RequestProcessor::EVENT_TYPE_MOVEMENT . ',' . (int) RequestProcessor::EVENT_TYPE_CLICK . ')';
- } else {
- throw new \Exception('Heatmap type not supported');
- }
-
- $period = Period\Factory::build($period, $date);
- $segment = new Segment($segment, array($idSite));
- $site = new Site($idSite);
-
- $from = array(
- 'log_hsr',
- array(
- 'table' => 'log_hsr_site',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr'
- ),
- array(
- 'table' => 'log_hsr_event',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr_event.idloghsr'
- ),
- array(
- 'table' => 'log_action',
- 'joinOn' => 'log_action.idaction = log_hsr_event.idselector'
- )
- );
-
- $select = 'log_action.name as selector,
- log_hsr_event.x as offset_x,
- log_hsr_event.y as offset_y,
- count(*) as value';
-
- $params = new ArchiveProcessor\Parameters($site, $period, $segment);
- $logAggregator = new LogAggregator($params);
-
- $where = $logAggregator->getWhereStatement('log_hsr', 'server_time');
- $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr . ' and log_hsr_event.idselector is not null and ' . $heatmapTypeWhere;
- $where .= ' and log_hsr.device_type = ' . (int) $deviceType;
-
- $groupBy = 'log_hsr_event.idselector, log_hsr_event.x, log_hsr_event.y';
- $orderBy = '';
-
- $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
-
- return $this->getDbReader()->fetchAll($query['sql'], $query['bind']);
- }
-
- public function getRecordedHeatmapMetadata($idSiteHsr, $idSite, $period, $date, $segment)
- {
- $period = Period\Factory::build($period, $date);
- $segment = new Segment($segment, array($idSite));
- $site = new Site($idSite);
-
- $from = array(
- 'log_hsr',
- array(
- 'table' => 'log_hsr_site',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr'
- )
- );
-
- $select = 'log_hsr.device_type, count(*) as value, avg(log_hsr.fold_y_relative) as avg_fold';
-
- $params = new ArchiveProcessor\Parameters($site, $period, $segment);
- $logAggregator = new LogAggregator($params);
-
- $where = $logAggregator->getWhereStatement('log_hsr', 'server_time');
- $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr;
- $groupBy = 'log_hsr.device_type';
- $orderBy = '';
-
- $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
-
- return $this->getDbReader()->fetchAll($query['sql'], $query['bind']);
- }
-
- public function aggregateScrollHeatmap($idSiteHsr, $deviceType, $idSite, $period, $date, $segment)
- {
- $period = Period\Factory::build($period, $date);
- $segment = new Segment($segment, array($idSite));
- $site = new Site($idSite);
-
- $from = array('log_hsr',
- array(
- 'table' => 'log_hsr_site',
- 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr'
- ),
- );
-
- $select = 'log_hsr.scroll_y_max_relative as label,
- count(*) as value';
-
- $params = new ArchiveProcessor\Parameters($site, $period, $segment);
- $logAggregator = new LogAggregator($params);
- $where = $logAggregator->getWhereStatement('log_hsr', 'server_time');
- $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr;
- $where .= ' and log_hsr.device_type = ' . (int) $deviceType;
-
- $groupBy = 'log_hsr.scroll_y_max_relative';
- $orderBy = 'label ASC'; // labels are no from 0-1000 i.e page from top to bottom, so top label should always come first 0..100..500..1000
-
- $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy);
-
- return $this->getDbReader()->fetchAll($query['sql'], $query['bind']);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/HeatmapCategory.php b/files/plugin-HeatmapSessionRecording-5.2.6/Categories/HeatmapCategory.php
deleted file mode 100644
index 946c2ea..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/HeatmapCategory.php
+++ /dev/null
@@ -1,26 +0,0 @@
-' . Piwik::translate('HeatmapSessionRecording_ManageHeatmapSubcategoryHelp') . '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/ManageSessionRecordingSubcategory.php b/files/plugin-HeatmapSessionRecording-5.2.6/Categories/ManageSessionRecordingSubcategory.php
deleted file mode 100644
index fa5c615..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/ManageSessionRecordingSubcategory.php
+++ /dev/null
@@ -1,32 +0,0 @@
-' . Piwik::translate('HeatmapSessionRecording_ManageSessionRecordingSubcategoryHelp') . '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/SessionRecordingsCategory.php b/files/plugin-HeatmapSessionRecording-5.2.6/Categories/SessionRecordingsCategory.php
deleted file mode 100644
index f376af1..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Categories/SessionRecordingsCategory.php
+++ /dev/null
@@ -1,26 +0,0 @@
-getMetric($row, 'config_browser_name');
- }
-
- public function getDependentMetrics()
- {
- return array(
- 'config_browser_name',
- );
- }
-
- public function showsHtml()
- {
- return true;
- }
-
- public function format($value, Formatter $formatter)
- {
- if (empty($value) || $value === 'UNK') {
- return false;
- }
-
- $title = \Piwik\Plugins\DevicesDetection\getBrowserName($value);
-
- return '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Device.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Device.php
deleted file mode 100644
index c0c0c6b..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Device.php
+++ /dev/null
@@ -1,78 +0,0 @@
- $this->getMetric($row, 'config_device_type'),
- 'model' => $this->getMetric($row, 'config_device_model')
- );
- }
-
- public function getDependentMetrics()
- {
- return array(
- 'config_device_type',
- 'config_device_model',
- );
- }
-
- public function showsHtml()
- {
- return true;
- }
-
- public function format($value, Formatter $formatter)
- {
- if (empty($value['type']) && $value['type'] !== 0 && $value['type'] !== '0') {
- return false;
- }
-
- $title = \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($value['type']);
-
- if (!empty($value['model'])) {
- $title .= ', ' . SafeDecodeLabel::decodeLabelSafe($value['model']);
- }
-
- return '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Location.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Location.php
deleted file mode 100644
index 671a658..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/Location.php
+++ /dev/null
@@ -1,86 +0,0 @@
- $this->getMetric($row, 'location_country'),
- 'region' => $this->getMetric($row, 'location_region'),
- 'city' => $this->getMetric($row, 'location_city'),
- );
- }
-
- public function getDependentMetrics()
- {
- return array(
- 'location_country',
- 'location_region',
- 'location_city'
- );
- }
-
- public function showsHtml()
- {
- return true;
- }
-
- public function format($value, Formatter $formatter)
- {
- if (empty($value['country']) || $value['country'] === Visit::UNKNOWN_CODE) {
- return false;
- }
-
- $title = \Piwik\Plugins\UserCountry\countryTranslate($value['country']);
-
- if (!empty($value['region']) && $value['region'] !== Visit::UNKNOWN_CODE) {
- $title .= ', ' . \Piwik\Plugins\UserCountry\getRegionNameFromCodes($value['country'], $value['region']);
- }
-
- if (!empty($value['city'])) {
- $title .= ', ' . SafeDecodeLabel::decodeLabelSafe($value['city']);
- }
-
- return '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/OperatingSystem.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/OperatingSystem.php
deleted file mode 100644
index d78691b..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/OperatingSystem.php
+++ /dev/null
@@ -1,67 +0,0 @@
-getMetric($row, 'config_os');
- }
-
- public function getDependentMetrics()
- {
- return array(
- 'config_os',
- );
- }
-
- public function showsHtml()
- {
- return true;
- }
-
- public function format($value, Formatter $formatter)
- {
- if (empty($value) || $value === 'UNK') {
- return false;
- }
-
- $title = \Piwik\Plugins\DevicesDetection\getOsFullName($value);
-
- return '';
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/SessionTime.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/SessionTime.php
deleted file mode 100644
index 0de98a4..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/SessionTime.php
+++ /dev/null
@@ -1,97 +0,0 @@
-dateFormat = $dateFormat;
- }
-
- public function getName()
- {
- return 'server_time';
- }
-
- public function getTranslatedName()
- {
- return Piwik::translate('HeatmapSessionRecording_ColumnTime');
- }
-
- public function getDocumentation()
- {
- return Piwik::translate('HeatmapSessionRecording_ColumnTimeDocumentation');
- }
-
- public function compute(Row $row)
- {
- return $this->getMetric($row, 'server_time');
- }
-
- public function getDependentMetrics()
- {
- return array($this->getName());
- }
-
- public function format($value, Formatter $formatter)
- {
- $date = Date::factory($value, $this->timezone);
-
- $dateTimeFormatProvider = StaticContainer::get('Piwik\Intl\Data\Provider\DateTimeFormatProvider');
-
- $template = $dateTimeFormatProvider->getFormatPattern($this->dateFormat);
- $template = str_replace(array(' y ', '.y '), ' ', $template);
-
- return $date->getLocalized($template);
- }
-
- public function beforeFormat($report, DataTable $table)
- {
- $this->idSite = DataTableFactory::getSiteIdFromMetadata($table);
- if (empty($this->idSite)) {
- $this->idSite = Common::getRequestVar('idSite', 0, 'int');
- }
- if (!empty($this->idSite)) {
- $this->timezone = Site::getTimezoneFor($this->idSite);
- return true;
- }
- return false; // skip formatting if there is no site to get currency info from
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnPage.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnPage.php
deleted file mode 100644
index 506472d..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnPage.php
+++ /dev/null
@@ -1,65 +0,0 @@
-getMetric($row, $this->getName());
- }
-
- public function getDependentMetrics()
- {
- return array($this->getName());
- }
-
- public function format($value, Formatter $formatter)
- {
- if (!empty($value)) {
- $value = round($value / 1000, 1); // convert ms to seconds
- $value = (int) round($value);
- }
-
- $time = $formatter->getPrettyTimeFromSeconds($value, $asSentence = false);
-
- if (strpos($time, '00:') === 0) {
- $time = substr($time, 3);
- }
-
- return $time;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnSite.php b/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnSite.php
deleted file mode 100644
index 2ba27d2..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Columns/Metrics/TimeOnSite.php
+++ /dev/null
@@ -1,37 +0,0 @@
-getMetric($row, 'total_events');
- }
-
- public function getDependentMetrics()
- {
- return [];
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Commands/RemoveHeatmapScreenshot.php b/files/plugin-HeatmapSessionRecording-5.2.6/Commands/RemoveHeatmapScreenshot.php
deleted file mode 100644
index 86783de..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Commands/RemoveHeatmapScreenshot.php
+++ /dev/null
@@ -1,101 +0,0 @@
-setName('heatmapsessionrecording:remove-heatmap-screenshot');
- $this->setDescription('Removes a saved heatmap screenshot which can be useful if you want Matomo to re-take this screenshot. If the heatmap is currently ended, it will automatically restart it.');
- $this->addRequiredValueOption('idsite', null, 'The ID of the site the heatmap belongs to');
- $this->addRequiredValueOption('idheatmap', null, 'The ID of the heatamp');
- }
-
- /**
- * @return int
- */
- protected function doExecute(): int
- {
- $this->checkAllRequiredOptionsAreNotEmpty();
- $input = $this->getInput();
- $output = $this->getOutput();
- $idSite = $input->getOption('idsite');
- $idHeatmap = $input->getOption('idheatmap');
-
- $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', array(
- 'idSite' => $idSite,
- 'idSiteHsr' => $idHeatmap
- ));
-
- if ($heatmap['status'] === SiteHsrDao::STATUS_ENDED) {
- $logHsrSite = new LogHsrSite();
- $numSamplesTakenSoFar = $logHsrSite->getNumPageViews($idHeatmap);
-
- $currentSampleLimit = $heatmap['sample_limit'];
- $newSampleLimit = $numSamplesTakenSoFar + 50; // 50 heatmaps should be enough to collect at least once the dom.
-
- $update = array('status' => SiteHsrDao::STATUS_ACTIVE);
- if ($currentSampleLimit >= $newSampleLimit) {
- $output->writeln('Sample limit remains unchanged at ' . $currentSampleLimit);
- if ($currentSampleLimit - $numSamplesTakenSoFar > 75) {
- $output->writeln('make sure to end the heatmap again as soon as a screenshot has been taken!');
- }
- } else {
- $output->writeln('Going to increase sample limit from ' . $currentSampleLimit . ' to ' . $newSampleLimit . ' so a screenshot can be retaken. The heatmap will be automatically ended after about 50 new recordings have been recorded.');
- $output->writeln('Note: This means when you manage this heatmap the selected sample wont be shown correctly in the select field');
- $update['sample_limit'] = $newSampleLimit;
- }
-
- $output->writeln('Going to change status of heatmap from ended to active');
-
- $siteHsr = StaticContainer::get(SiteHsrDao::class);
- $siteHsr->updateHsrColumns($idSite, $idHeatmap, array(
- 'status' => SiteHsrDao::STATUS_ACTIVE,
- 'sample_limit' => $newSampleLimit
- ));
- $output->writeln('Done');
- }
-
- $success = Request::processRequest('HeatmapSessionRecording.deleteHeatmapScreenshot', array(
- 'idSite' => $idSite,
- 'idSiteHsr' => $idHeatmap
- ));
-
- if ($success) {
- Filesystem::deleteAllCacheOnUpdate();
- /** @var HeatmapSessionRecording $hsr */
- $hsr = Plugin\Manager::getInstance()->getLoadedPlugin('HeatmapSessionRecording');
- $hsr->updatePiwikTracker();
- $output->writeln('Screenhot removed');
-
- return self::SUCCESS;
- }
-
- $output->writeln('Heatmap not found');
- return self::FAILURE;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Configuration.php b/files/plugin-HeatmapSessionRecording-5.2.6/Configuration.php
deleted file mode 100644
index b8dd31a..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Configuration.php
+++ /dev/null
@@ -1,145 +0,0 @@
-getConfig();
- $config->HeatmapSessionRecording = array(
- self::KEY_OPTIMIZE_TRACKING_CODE => self::DEFAULT_OPTIMIZE_TRACKING_CODE,
- self::KEY_SESSION_RECORDING_SAMPLE_LIMITS => self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS,
- self::KEY_ENABLE_LOAD_CSS_FROM_DB => self::DEFAULT_ENABLE_LOAD_CSS_FROM_DB,
- self::MAX_ALLOWED_TIME_ON_PAGE_COLUMN_LIMIT => pow(2, 63),
- self::KEY_DEFAULT_HEATMAP_WIDTH => self::DEFAULT_HEATMAP_WIDTH
-
- );
- $config->forceSave();
- }
-
- public function uninstall()
- {
- $config = $this->getConfig();
- $config->HeatmapSessionRecording = array();
- $config->forceSave();
- }
-
- public function shouldOptimizeTrackingCode()
- {
- $value = $this->getConfigValue(self::KEY_OPTIMIZE_TRACKING_CODE, self::DEFAULT_OPTIMIZE_TRACKING_CODE);
-
- return !empty($value);
- }
-
- public function isAnonymousSessionRecordingAccessEnabled($idSite)
- {
- $value = $this->getDiValue(self::KEY_ENABLE_ANONYMOUS_SESSION_RECORDING_ACCESS, self::DEFAULT_ENABLE_ANONYMOUS_SESSION_RECORDING_ACCESS);
- $idSites = explode(',', $value);
- $idSites = array_map('trim', $idSites);
- $idSites = array_filter($idSites);
- return in_array($idSite, $idSites);
- }
-
- public function getSessionRecordingSampleLimits()
- {
- $value = $this->getConfigValue(self::KEY_SESSION_RECORDING_SAMPLE_LIMITS, self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS);
-
- if (empty($value)) {
- $value = self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS;
- }
-
- $value = explode(',', $value);
- $value = array_filter($value, function ($val) {
- return !empty($val);
- });
- $value = array_map(function ($val) {
- return intval(trim($val));
- }, $value);
- natsort($value);
-
- if (empty($value)) {
- // just a fallback in case config is completely misconfigured
- $value = explode(',', self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS);
- }
-
- return array_values($value);
- }
-
- public function isLoadCSSFromDBEnabled()
- {
- return $this->getConfigValue(self::KEY_ENABLE_LOAD_CSS_FROM_DB, self::DEFAULT_ENABLE_LOAD_CSS_FROM_DB);
- }
-
- public function getMaximumAllowedPageTime()
- {
- return $this->getConfigValue(self::MAX_ALLOWED_TIME_ON_PAGE_COLUMN_LIMIT, '');
- }
-
- public function getDefaultHeatmapWidth()
- {
- $width = $this->getConfigValue(self::KEY_DEFAULT_HEATMAP_WIDTH, 1280);
- if (!in_array($width, self::HEATMAP_ALLOWED_WIDTHS)) {
- $width = self::DEFAULT_HEATMAP_WIDTH;
- }
-
- return $width;
- }
-
- private function getConfig()
- {
- return Config::getInstance();
- }
-
- private function getConfigValue($name, $default)
- {
- $config = $this->getConfig();
- $values = $config->HeatmapSessionRecording;
- if (isset($values[$name])) {
- return $values[$name];
- }
- return $default;
- }
-
- private function getDiValue($name, $default)
- {
- $value = $default;
- try {
- $value = StaticContainer::get('HeatmapSessionRecording.' . $name);
- } catch (NotFoundException $ex) {
- // ignore
- }
- return $value;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Controller.php b/files/plugin-HeatmapSessionRecording-5.2.6/Controller.php
deleted file mode 100644
index 2fd7c93..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Controller.php
+++ /dev/null
@@ -1,463 +0,0 @@
-validator = $validator;
- $this->siteHsrModel = $model;
- $this->systemSettings = $settings;
- $this->mutationManipulator = $mutationManipulator;
- $this->mutationManipulator->generateNonce();
- $this->configuration = $configuration;
- }
-
- public function manageHeatmap()
- {
- $idSite = Common::getRequestVar('idSite');
-
- if (strtolower($idSite) === 'all') {
- // prevent fatal error... redirect to a specific site as it is not possible to manage for all sites
- $this->validator->checkHasSomeWritePermission();
- $this->redirectToIndex('HeatmapSessionRecording', 'manageHeatmap');
- exit;
- }
-
- $this->checkSitePermission();
- $this->validator->checkHeatmapReportWritePermission($this->idSite);
-
- return $this->renderTemplate('manageHeatmap', array(
- 'breakpointMobile' => (int) $this->systemSettings->breakpointMobile->getValue(),
- 'breakpointTablet' => (int) $this->systemSettings->breakpointTablet->getValue(),
- 'pauseReason' => Piwik::translate(HeatmapSessionRecording::getTranslationKey('pause'), [Piwik::translate('HeatmapSessionRecording_Heatmap')]),
- 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable()
- ));
- }
-
- public function manageSessions()
- {
- $idSite = Common::getRequestVar('idSite');
-
- if (strtolower($idSite) === 'all') {
- // prevent fatal error... redirect to a specific site as it is not possible to manage for all sites
- $this->validator->checkHasSomeWritePermission();
- $this->redirectToIndex('HeatmapSessionRecording', 'manageSessions');
- exit;
- }
-
- $this->checkSitePermission();
- $this->validator->checkSessionReportWritePermission($this->idSite);
-
- return $this->renderTemplate('manageSessions', array(
- 'pauseReason' => Piwik::translate(HeatmapSessionRecording::getTranslationKey('pause'), [Piwik::translate('HeatmapSessionRecording_SessionRecording')]),
- 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable()
- ));
- }
-
- private function checkNotInternetExplorerWhenUsingToken()
- {
- if (Common::getRequestVar('token_auth', '', 'string') && !empty($_SERVER['HTTP_USER_AGENT'])) {
- // we want to detect device type only once for faster performance
- $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class);
- $deviceDetector = $ddFactory->makeInstance($_SERVER['HTTP_USER_AGENT']);
- $client = $deviceDetector->getClient();
-
- if (
- (!empty($client['short_name']) && $client['short_name'] === 'IE')
- || (!empty($client['name']) && $client['name'] === 'Internet Explorer')
- || (!empty($client['name']) && $client['name'] === 'Opera Mini')
- ) {
- // see https://caniuse.com/?search=noreferrer
- // and https://caniuse.com/?search=referrerpolicy
- throw new \Exception('For security reasons this feature doesn\'t work in this browser when using authentication using token_auth. Please try a different browser or log in to view this.');
- }
- }
- }
-
- public function replayRecording()
- {
- $this->validator->checkSessionReportViewPermission($this->idSite);
- $this->checkNotInternetExplorerWhenUsingToken();
-
- $idLogHsr = Common::getRequestVar('idLogHsr', null, 'int');
- $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int');
-
- $_GET['period'] = 'year'; // setting it randomly to not having to pass it in the URL
- $_GET['date'] = 'today'; // date is ignored anyway
-
- $recording = Request::processRequest('HeatmapSessionRecording.getRecordedSession', array(
- 'idSite' => $this->idSite,
- 'idLogHsr' => $idLogHsr,
- 'idSiteHsr' => $idSiteHsr,
- 'filter_limit' => '-1'
- ), $default = []);
-
- $currentPage = null;
- if (!empty($recording['pageviews']) && is_array($recording['pageviews'])) {
- $allPageviews = array_values($recording['pageviews']);
- foreach ($allPageviews as $index => $pageview) {
- if (!empty($pageview['idloghsr']) && $idLogHsr == $pageview['idloghsr']) {
- $currentPage = $index + 1;
- break;
- }
- }
- }
-
- $settings = $this->getPluginSettings();
- $settings = $settings->load();
- $skipPauses = !empty($settings['skip_pauses']);
- $autoPlayEnabled = !empty($settings['autoplay_pageviews']);
- $replaySpeed = !empty($settings['replay_speed']) ? (int) $settings['replay_speed'] : 1;
- $isVisitorProfileEnabled = Manager::getInstance()->isPluginActivated('Live') && Live::isVisitorProfileEnabled();
-
- if (!empty($recording['events'])) {
- foreach ($recording['events'] as $recordingEventIndex => $recordingEventValue) {
- if (
- !empty($recordingEventValue['event_type']) &&
- (
- $recordingEventValue['event_type'] == RequestProcessor::EVENT_TYPE_INITIAL_DOM ||
- $recordingEventValue['event_type'] == RequestProcessor::EVENT_TYPE_MUTATION
- ) &&
- !empty(
- $recordingEventValue['text']
- )
- ) {
- $recording['events'][$recordingEventIndex]['text'] = $this->mutationManipulator->manipulate($recordingEventValue['text'], $idSiteHsr, $idLogHsr);
- break;
- }
- }
- }
-
- return $this->renderTemplate('replayRecording', array(
- 'idLogHsr' => $idLogHsr,
- 'idSiteHsr' => $idSiteHsr,
- 'recording' => $recording,
- 'scrollAccuracy' => LogHsr::SCROLL_ACCURACY,
- 'offsetAccuracy' => LogHsrEvent::OFFSET_ACCURACY,
- 'autoPlayEnabled' => $autoPlayEnabled,
- 'visitorProfileEnabled' => $isVisitorProfileEnabled,
- 'skipPausesEnabled' => $skipPauses,
- 'replaySpeed' => $replaySpeed,
- 'currentPage' => $currentPage
- ));
- }
-
- protected function setBasicVariablesView($view)
- {
- parent::setBasicVariablesView($view);
-
- if (
- Common::getRequestVar('module', '', 'string') === 'Widgetize'
- && Common::getRequestVar('action', '', 'string') === 'iframe'
- && Common::getRequestVar('moduleToWidgetize', '', 'string') === 'HeatmapSessionRecording'
- ) {
- $action = Common::getRequestVar('actionToWidgetize', '', 'string');
- if (in_array($action, array('replayRecording', 'showHeatmap'), true)) {
- $view->enableFrames = true;
- }
- }
- }
-
- private function getPluginSettings()
- {
- $login = Piwik::getCurrentUserLogin();
-
- $settings = new PluginSettingsTable('HeatmapSessionRecording', $login);
- return $settings;
- }
-
- public function saveSessionRecordingSettings()
- {
- Piwik::checkUserHasSomeViewAccess();
- $this->validator->checkSessionRecordingEnabled();
- // there is no nonce for this action but that should also not be needed here. as it is just replay settings
-
- $autoPlay = Common::getRequestVar('autoplay', '0', 'int');
- $replaySpeed = Common::getRequestVar('replayspeed', '1', 'int');
- $skipPauses = Common::getRequestVar('skippauses', '0', 'int');
-
- $settings = $this->getPluginSettings();
- $settings->save(array('autoplay_pageviews' => $autoPlay, 'replay_speed' => $replaySpeed, 'skip_pauses' => $skipPauses));
- }
-
- private function initHeatmapAuth()
- {
- // todo remove in Matomo 5 when we hopefully no longer support IE 11.
- // This is mostly there to prevent forwarding tokens through referrer to third parties
- // most browsers support this except IE11
- // we said we're technically OK with IE11 forwarding a view token in worst case but we still have this here for now
- $token_auth = Common::getRequestVar('token_auth', '', 'string');
-
- if (!empty($token_auth)) {
- $auth = StaticContainer::get('Piwik\Auth');
- $auth->setTokenAuth($token_auth);
- $auth->setPassword(null);
- $auth->setPasswordHash(null);
- $auth->setLogin(null);
-
- Session::start();
- $sessionInitializer = new SessionInitializer();
- $sessionInitializer->initSession($auth);
-
- $url = preg_replace('/&token_auth=[^&]{20,38}|$/i', '', Url::getCurrentUrl());
- if ($url) {
- Url::redirectToUrl($url);
- return;
- }
- }
-
- // if no token_auth, we just rely on an existing session auth check
- }
-
- protected function setBasicVariablesNoneAdminView($view)
- {
- parent::setBasicVariablesNoneAdminView($view);
- if (Piwik::getAction() === 'embedPage' && Piwik::getModule() === 'HeatmapSessionRecording') {
- $view->setXFrameOptions('allow');
- }
- }
-
- public function embedPage()
- {
- $this->checkNotInternetExplorerWhenUsingToken();
- $this->initHeatmapAuth();
- $nonceRandom = '';
-
- if (
- property_exists($this, 'securityPolicy') &&
- method_exists($this->securityPolicy, 'allowEmbedPage')
- ) {
- $toSearch = array("'unsafe-inline' ", "'unsafe-eval' ", "'unsafe-inline'", "'unsafe-eval'");
- $nonceRandom = $this->mutationManipulator->getNonce();
- $this->securityPolicy->overridePolicy('default-src', $this->securityPolicy::RULE_EMBEDDED_FRAME);
- $this->securityPolicy->overridePolicy('img-src', $this->securityPolicy::RULE_EMBEDDED_FRAME);
- $this->securityPolicy->addPolicy('script-src', str_replace($toSearch, '', $this->securityPolicy::RULE_DEFAULT) . "'nonce-$nonceRandom'");
- }
-
- $pathPrefix = HeatmapSessionRecording::getPathPrefix();
- $jQueryPath = 'node_modules/jquery/dist/jquery.min.js';
- if (HeatmapSessionRecording::isMatomoForWordPress()) {
- $jQueryPath = includes_url('js/jquery/jquery.js');
- }
-
- $idLogHsr = Common::getRequestVar('idLogHsr', 0, 'int');
- $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int');
-
- $_GET['period'] = 'year'; // setting it randomly to not having to pass it in the URL
- $_GET['date'] = 'today'; // date is ignored anyway
-
- if (empty($idLogHsr)) {
- $this->validator->checkHeatmapReportViewPermission($this->idSite);
-
- $heatmap = $this->getHeatmap($this->idSite, $idSiteHsr);
-
- if (isset($heatmap[0])) {
- $heatmap = $heatmap[0];
- }
-
- $baseUrl = $heatmap['screenshot_url'];
- $initialMutation = $heatmap['page_treemirror'];
- } else {
- $this->validator->checkSessionReportViewPermission($this->idSite);
- $this->checkSessionRecordingExists($this->idSite, $idSiteHsr);
-
- $recording = Request::processRequest('HeatmapSessionRecording.getEmbedSessionInfo', [
- 'idSite' => $this->idSite,
- 'idSiteHsr' => $idSiteHsr,
- 'idLogHsr' => $idLogHsr,
- ], $default = []);
-
- if (empty($recording)) {
- throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
-
- $baseUrl = $recording['base_url'];
- $map = array_flip(PageUrl::$urlPrefixMap);
-
- if (isset($recording['url_prefix']) !== null && isset($map[$recording['url_prefix']])) {
- $baseUrl = $map[$recording['url_prefix']] . $baseUrl;
- }
-
- if (!empty($recording['initial_mutation'])) {
- $initialMutation = $recording['initial_mutation'];
- } else {
- $initialMutation = '';
- }
- }
-
- $initialMutation = $this->mutationManipulator->manipulate($initialMutation, $idSiteHsr, $idLogHsr);
-
- return $this->renderTemplate('embedPage', array(
- 'idLogHsr' => $idLogHsr,
- 'idSiteHsr' => $idSiteHsr,
- 'initialMutation' => $initialMutation,
- 'baseUrl' => $baseUrl,
- 'pathPrefix' => $pathPrefix,
- 'jQueryPath' => $jQueryPath,
- 'nonceRandom' => $nonceRandom
- ));
- }
-
- public function showHeatmap()
- {
- $this->validator->checkHeatmapReportViewPermission($this->idSite);
- $this->checkNotInternetExplorerWhenUsingToken();
-
- $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int');
- $heatmapType = Common::getRequestVar('heatmapType', RequestProcessor::EVENT_TYPE_CLICK, 'int');
- $deviceType = Common::getRequestVar('deviceType', LogHsr::DEVICE_TYPE_DESKTOP, 'int');
-
- $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', array(
- 'idSite' => $this->idSite,
- 'idSiteHsr' => $idSiteHsr
- ), $default = []);
-
- if (isset($heatmap[0])) {
- $heatmap = $heatmap[0];
- }
-
- $requestDate = $this->siteHsrModel->getPiwikRequestDate($heatmap);
- $period = $requestDate['period'];
- $dateRange = $requestDate['date'];
-
- if (
- !PeriodFactory::isPeriodEnabledForAPI($period) ||
- Common::getRequestVar('useDateUrl', 0, 'int')
- ) {
- $period = Common::getRequestVar('period', null, 'string');
- $dateRange = Common::getRequestVar('date', null, 'string');
- }
-
- try {
- PeriodFactory::checkPeriodIsEnabled($period);
- } catch (\Exception $e) {
- $periodEscaped = Common::sanitizeInputValue(Piwik::translate('HeatmapSessionRecording_PeriodDisabledErrorMessage', $period));
- return '
' . $periodEscaped . '
';
- }
-
- $metadata = Request::processRequest('HeatmapSessionRecording.getRecordedHeatmapMetadata', array(
- 'idSite' => $this->idSite,
- 'idSiteHsr' => $idSiteHsr,
- 'period' => $period,
- 'date' => $dateRange
- ), $default = []);
-
- if (isset($metadata[0])) {
- $metadata = $metadata[0];
- }
-
- $editUrl = 'index.php' . Url::getCurrentQueryStringWithParametersModified(array(
- 'module' => 'HeatmapSessionRecording',
- 'action' => 'manageHeatmap'
- )) . '#?idSiteHsr=' . (int)$idSiteHsr;
-
- $reportDocumentation = '';
- if ($heatmap['status'] == SiteHsrDao::STATUS_ACTIVE) {
- $reportDocumentation = Piwik::translate('HeatmapSessionRecording_RecordedHeatmapDocStatusActive', array($heatmap['sample_limit'], $heatmap['sample_rate'] . '%'));
- } elseif ($heatmap['status'] == SiteHsrDao::STATUS_ENDED) {
- $reportDocumentation = Piwik::translate('HeatmapSessionRecording_RecordedHeatmapDocStatusEnded');
- }
-
- $includedCountries = $this->systemSettings->getIncludedCountries();
-
- return $this->renderTemplate('showHeatmap', array(
- 'idSiteHsr' => $idSiteHsr,
- 'editUrl' => $editUrl,
- 'heatmapType' => $heatmapType,
- 'deviceType' => $deviceType,
- 'heatmapPeriod' => $period,
- 'heatmapDate' => $dateRange,
- 'heatmap' => $heatmap,
- 'isActive' => $heatmap['status'] == SiteHsrDao::STATUS_ACTIVE,
- 'heatmapMetadata' => $metadata,
- 'reportDocumentation' => $reportDocumentation,
- 'isScroll' => $heatmapType == RequestProcessor::EVENT_TYPE_SCROLL,
- 'offsetAccuracy' => LogHsrEvent::OFFSET_ACCURACY,
- 'heatmapTypes' => API::getInstance()->getAvailableHeatmapTypes(),
- 'deviceTypes' => API::getInstance()->getAvailableDeviceTypes(),
- 'includedCountries' => !empty($includedCountries) ? implode(', ', $includedCountries) : '',
- 'desktopPreviewSize' => $this->configuration->getDefaultHeatmapWidth(),
- 'allowedWidth' => Configuration::HEATMAP_ALLOWED_WIDTHS,
- 'noDataMessageKey' => HeatmapSessionRecording::getTranslationKey('noDataHeatmap'),
- 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable(),
- ));
- }
-
- private function getHeatmap($idSite, $idSiteHsr)
- {
- $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', [
- 'idSite' => $idSite,
- 'idSiteHsr' => $idSiteHsr,
- ], $default = []);
- if (empty($heatmap)) {
- throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapDoesNotExist'));
- }
- return $heatmap;
- }
-
- private function checkSessionRecordingExists($idSite, $idSiteHsr)
- {
- $sessionRecording = Request::processRequest('HeatmapSessionRecording.getSessionRecording', [
- 'idSite' => $idSite,
- 'idSiteHsr' => $idSiteHsr,
- ], $default = []);
- if (empty($sessionRecording)) {
- throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist'));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsr.php b/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsr.php
deleted file mode 100644
index 8851782..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsr.php
+++ /dev/null
@@ -1,375 +0,0 @@
-tablePrefixed = Common::prefixTable($this->table);
- $this->logHsrSite = $logHsrSite;
- }
-
- private function getDb()
- {
- if (!isset($this->db)) {
- $this->db = Db::get();
- }
- return $this->db;
- }
-
- public function install()
- {
- DbHelper::createTable($this->table, "
- `idloghsr` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
- `idsite` INT UNSIGNED NOT NULL,
- `idvisit` BIGINT UNSIGNED NOT NULL,
- `idhsrview` CHAR(6) NOT NULL,
- `idpageview` CHAR(6) NULL,
- `idaction_url` INT(10) UNSIGNED NOT NULL DEFAULT 0,
- `device_type` TINYINT(1) NOT NULL DEFAULT 1,
- `server_time` DATETIME NOT NULL,
- `time_on_page` BIGINT(8) UNSIGNED NOT NULL,
- `viewport_w_px` SMALLINT(5) UNSIGNED DEFAULT 0,
- `viewport_h_px` SMALLINT(5) UNSIGNED DEFAULT 0,
- `scroll_y_max_relative` SMALLINT(5) UNSIGNED DEFAULT 0,
- `fold_y_relative` SMALLINT(5) UNSIGNED DEFAULT 0,
- PRIMARY KEY(`idloghsr`),
- UNIQUE KEY idvisit_idhsrview (`idvisit`,`idhsrview`),
- KEY idsite_servertime (`idsite`,`server_time`)");
-
- // idpageview is only there so we can add it to visitor log later. Please note that idpageview is only set on
- // the first tracking request. As the user may track a new pageview during the recording, the pageview may
- // change over time. This is why we need the idhsrview.
-
- // we need the idhsrview as there can be many recordings during one visit and this way we can control when
- // to trigger a new recording / heatmap in the tracker by changing this id
- }
-
- public function uninstall()
- {
- Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed));
- }
-
- protected function getDeviceWidth($resolution)
- {
- if (!empty($resolution)) {
- $parts = explode('x', $resolution);
- if (count($parts) === 2 && $parts[0] > 1 && $parts[1] > 1) {
- $width = $parts[0];
- return (int) $width;
- }
- }
-
- return 1280; // default desktop
- }
-
- protected function getDeviceType($hsrSiteIds, $idSite, $userAgent, $deviceWidth)
- {
- $deviceType = null;
-
- // we want to detect device type only once for faster performance
- $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class);
- $deviceDetector = $ddFactory->makeInstance($userAgent);
- $device = $deviceDetector->getDevice();
-
- $checkWidth = false;
- if (
- in_array(
- $device,
- array(
- AbstractDeviceParser::DEVICE_TYPE_FEATURE_PHONE,
- AbstractDeviceParser::DEVICE_TYPE_PHABLET,
- AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE,
- AbstractDeviceParser::DEVICE_TYPE_CAMERA,
- AbstractDeviceParser::DEVICE_TYPE_CAR_BROWSER
- ),
- $strict = true
- )
- ) {
- $deviceType = self::DEVICE_TYPE_MOBILE;
- } elseif (in_array($device, array(AbstractDeviceParser::DEVICE_TYPE_TABLET), $strict = true)) {
- $deviceType = self::DEVICE_TYPE_TABLET;
- } elseif ($deviceType === AbstractDeviceParser::DEVICE_TYPE_DESKTOP) {
- $deviceType = LogHsr::DEVICE_TYPE_DESKTOP;
- $checkWidth = true;
- } else {
- $checkWidth = true;
- }
-
- if ($checkWidth && !empty($deviceWidth)) {
- $hsrs = $this->getCachedHsrs($idSite);
-
- foreach ($hsrs as $hsr) {
- // the device type is only relevant for heatmaps so we only look for breakpoints in heatmaps
- if (
- $hsr['record_type'] == SiteHsrDao::RECORD_TYPE_HEATMAP
- && in_array($hsr['idsitehsr'], $hsrSiteIds)
- ) {
- if ($deviceWidth < $hsr['breakpoint_mobile']) {
- // resolution has to be lower than this
- $deviceType = self::DEVICE_TYPE_MOBILE;
- } elseif ($deviceWidth < $hsr['breakpoint_tablet']) {
- $deviceType = self::DEVICE_TYPE_TABLET;
- } else {
- $deviceType = self::DEVICE_TYPE_DESKTOP;
- }
-
- break;
- }
- }
- }
-
- if (empty($deviceType)) {
- $deviceType = LogHsr::DEVICE_TYPE_DESKTOP;
- }
-
- return $deviceType;
- }
-
- protected function getCachedHsrs($idSite)
- {
- $cache = Tracker\Cache::getCacheWebsiteAttributes($idSite);
-
- if (!empty($cache['hsr'])) {
- return $cache['hsr'];
- }
-
- return array();
- }
-
- public function findIdLogHsr($idVisit, $idHsrView)
- {
- $query = sprintf('SELECT idloghsr FROM %s WHERE idvisit = ? and idhsrview = ? LIMIT 1', $this->tablePrefixed);
-
- return $this->getDb()->fetchOne($query, array($idVisit, $idHsrView));
- }
-
- public function hasRecordedIdVisit($idVisit, $idSiteHsr)
- {
- $siteTable = Common::prefixTable('log_hsr_site');
- $query = sprintf('SELECT lhsr.idvisit
- FROM %s lhsr
- LEFT JOIN %s lhsrsite ON lhsr.idloghsr=lhsrsite.idloghsr
- WHERE lhsr.idvisit = ? and lhsrsite.idsitehsr = ?
- LIMIT 1', $this->tablePrefixed, $siteTable);
- $id = $this->getDb()->fetchOne($query, array($idVisit, $idSiteHsr));
- return !empty($id);
- }
-
- // $hsrSiteIds => one recording may be long to several actual recordings.
- public function record($hsrSiteIds, $idSite, $idVisit, $idHsrView, $idPageview, $url, $serverTime, $userAgent, $resolution, $timeOnPage, $viewportW, $viewportH, $scrollYMaxRelative, $foldYRelative)
- {
- if ($foldYRelative > self::SCROLL_ACCURACY) {
- $foldYRelative = self::SCROLL_ACCURACY;
- }
-
- if ($scrollYMaxRelative > self::SCROLL_ACCURACY) {
- $scrollYMaxRelative = self::SCROLL_ACCURACY;
- }
-
- $idLogHsr = $this->findIdLogHsr($idVisit, $idHsrView);
-
- if (empty($idLogHsr)) {
- // to prevent race conditions we use atomic insert. It may lead to more gaps in auto increment but there is
- // no way around it
-
- Piwik::postEvent('HeatmapSessionRecording.trackNewHsrSiteIds', array(&$hsrSiteIds, array('idSite' => $idSite, 'serverTime' => $serverTime, 'idVisit' => $idVisit)));
-
- if (empty($hsrSiteIds)) {
- throw new \Exception('No hsrSiteIds');
- }
-
- $values = array(
- 'idvisit' => $idVisit,
- 'idsite' => $idSite,
- 'idhsrview' => $idHsrView,
- 'idpageview' => $idPageview,
- 'server_time' => $serverTime,
- 'time_on_page' => $timeOnPage,
- 'viewport_w_px' => $viewportW,
- 'viewport_h_px' => $viewportH,
- 'scroll_y_max_relative' => (int)$scrollYMaxRelative,
- 'fold_y_relative' => (int) $foldYRelative,
- );
-
- $columns = implode('`,`', array_keys($values));
- $bind = array_values($values);
- $sql = sprintf('INSERT INTO %s (`%s`) VALUES(?,?,?,?,?,?,?,?,?,?)', $this->tablePrefixed, $columns);
-
- try {
- $result = $this->getDb()->query($sql, $bind);
- } catch (\Exception $e) {
- if (Db::get()->isErrNo($e, \Piwik\Updater\Migration\Db::ERROR_CODE_DUPLICATE_ENTRY)) {
- // race condition where two tried to insert at same time... we need to update instead
-
- $idLogHsr = $this->findIdLogHsr($idVisit, $idHsrView);
- $this->updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative);
- return $idLogHsr;
- }
- throw $e;
- }
-
- $all = $this->getDb()->rowCount($result);
-
- $idLogHsr = $this->getDb()->lastInsertId();
-
- if ($all === 1 || $all === '1') {
- // was inserted, resolve idaction! would be 2 or 0 on update
- // to be efficient we want to resolve idaction only once
- $url = PageUrl::normalizeUrl($url);
- $ids = TableLogAction::loadIdsAction(array('idaction_url' => array($url['url'], Action::TYPE_PAGE_URL, $url['prefixId'])));
-
- if (!empty($viewportW)) {
- $deviceWidth = (int) $viewportW;
- } else {
- $deviceWidth = $this->getDeviceWidth($resolution);
- }
- $deviceType = $this->getDeviceType($hsrSiteIds, $idSite, $userAgent, $deviceWidth);
-
- $idaction = $ids['idaction_url'];
- $this->getDb()->query(
- sprintf('UPDATE %s set idaction_url = ?, device_type = ? where idloghsr = ?', $this->tablePrefixed),
- array($idaction, $deviceType, $idLogHsr)
- );
-
- foreach ($hsrSiteIds as $hsrId) {
- // for performance reasons we check the limit only on hsr start and we make this way sure to still
- // accept all following requests to that hsr
- $this->logHsrSite->linkRecord($idLogHsr, $hsrId);
- }
- }
- } else {
- $this->updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative);
- }
-
- return $idLogHsr;
- }
-
- public function updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative)
- {
- $sql = sprintf(
- 'UPDATE %s SET
- time_on_page = if(? > time_on_page, ?, time_on_page),
- scroll_y_max_relative = if(? > scroll_y_max_relative, ?, scroll_y_max_relative)
- WHERE idloghsr = ?',
- $this->tablePrefixed
- );
-
- $bind = array();
- $bind[] = $timeOnPage;
- $bind[] = $timeOnPage;
- $bind[] = $scrollYMaxRelative;
- $bind[] = $scrollYMaxRelative;
- $bind[] = $idLogHsr;
-
- $this->getDb()->query($sql, $bind);
- }
-
- public function getAllRecords()
- {
- return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed);
- }
-
- public function findLogHsrIdsInVisit($idSite, $idVisit)
- {
- $rows = Db::fetchAll(sprintf('SELECT idloghsr FROM %s WHERE idvisit = ? and idsite = ?', $this->tablePrefixed), array($idVisit, $idSite));
-
- $idLogHsrs = array();
- foreach ($rows as $row) {
- $idLogHsrs[] = (int) $row['idloghsr'];
- }
-
- return $idLogHsrs;
- }
-
- public function findDeletedLogHsrIds()
- {
- // DELETE ALL LOG ENTRIES WHOSE IDSITEHSR DOES NO LONGER EXIST
- $rows = Db::fetchAll(sprintf(
- 'SELECT DISTINCT log_hsr.idloghsr FROM %s log_hsr LEFT OUTER JOIN %s log_hsr_site ON log_hsr.idloghsr = log_hsr_site.idloghsr WHERE log_hsr_site.idsitehsr IS NULL',
- $this->tablePrefixed,
- Common::prefixTable('log_hsr_site')
- ));
-
- $idLogHsrsToDelete = array();
- foreach ($rows as $row) {
- $idLogHsrsToDelete[] = (int) $row['idloghsr'];
- }
-
- return $idLogHsrsToDelete;
- }
-
- public function deleteIdLogHsrsFromAllTables($idLogHsrsToDelete)
- {
- if (!is_array($idLogHsrsToDelete)) {
- throw new \Exception('idLogHsrsToDelete is not an array');
- }
-
- if (empty($idLogHsrsToDelete)) {
- return;
- }
-
- // we delete them in chunks of 2500
- $idLogHsrsToDelete = array_chunk($idLogHsrsToDelete, 2500);
-
- $tablesToDelete = array(
- Common::prefixTable('log_hsr_event'),
- Common::prefixTable('log_hsr_site'),
- Common::prefixTable('log_hsr'),
- );
- foreach ($idLogHsrsToDelete as $idsToDelete) {
- $idsToDelete = array_map('intval', $idsToDelete);
- $idsToDelete = implode(',', $idsToDelete);
- foreach ($tablesToDelete as $tableToDelete) {
- $sql = sprintf('DELETE FROM %s WHERE idloghsr IN(%s)', $tableToDelete, $idsToDelete);
- Db::query($sql);
- }
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrBlob.php b/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrBlob.php
deleted file mode 100644
index 9945d50..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrBlob.php
+++ /dev/null
@@ -1,180 +0,0 @@
-tablePrefixed = Common::prefixTable($this->table);
- }
-
- private function getDb()
- {
- if (!isset($this->db)) {
- $this->db = Db::get();
- }
- return $this->db;
- }
-
- public function install()
- {
- DbHelper::createTable($this->table, "
- `idhsrblob` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
- `hash` INT(10) UNSIGNED NOT NULL,
- `compressed` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
- `value` MEDIUMBLOB NULL DEFAULT NULL,
- PRIMARY KEY (`idhsrblob`),
- INDEX (`hash`)");
-
- // we always build the hash on the raw text for simplicity
- }
-
- public function uninstall()
- {
- Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed));
- }
-
- public function findEntry($textHash, $text, $textCompressed)
- {
- $sql = sprintf('SELECT idhsrblob FROM %s WHERE `hash` = ? and (`value` = ? or `value` = ?) LIMIT 1', $this->tablePrefixed);
- $id = $this->getDb()->fetchOne($sql, array($textHash, $text, $textCompressed));
-
- return $id;
- }
-
- public function createEntry($textHash, $text, $isCompressed)
- {
- $sql = sprintf('INSERT INTO %s (`hash`, `compressed`, `value`) VALUES(?,?,?) ', $this->tablePrefixed);
- $this->getDb()->query($sql, array($textHash, (int) $isCompressed, $text));
-
- return $this->getDb()->lastInsertId();
- }
-
- public function record($text)
- {
- if ($text === null || $text === false) {
- return null;
- }
-
- $textHash = abs(crc32($text));
- $textCompressed = $this->compress($text);
-
- $id = $this->findEntry($textHash, $text, $textCompressed);
-
- if (!empty($id)) {
- return $id;
- }
-
- $isCompressed = 0;
- if ($text !== $textCompressed && strlen($textCompressed) < strlen($text)) {
- // detect if it is more efficient to store compressed or raw text
- $text = $textCompressed;
- $isCompressed = 1;
- }
-
- return $this->createEntry($textHash, $text, $isCompressed);
- }
-
- public function deleteUnusedBlobEntries()
- {
- $eventTable = Common::prefixTable('log_hsr_event');
- $blobTable = Common::prefixTable('log_hsr_blob');
-
- $blobEntries = Db::fetchAll('SELECT distinct idhsrblob FROM ' . $eventTable . ' LIMIT 2');
- $blobEntries = array_filter($blobEntries, function ($val) {
- return $val['idhsrblob'] !== null;
- }); // remove null values.
-
- if (empty($blobEntries)) {
- // no longer any blobs in use... delete all blobs
- $sql = 'DELETE FROM ' . $blobTable;
- Db::query($sql);
- return $sql;
- }
-
- $indexes = Db::fetchAll('SHOW INDEX FROM ' . $eventTable);
- $indexSql = '';
- foreach ($indexes as $index) {
- if (
- (!empty($index['Column_name']) && !empty($index['Key_name']) && $index['Column_name'] === 'idhsrblob')
- || (!empty($index['Key_name']) && $index['Key_name'] === 'idhsrblob')
- || (!empty($index['Key_name']) && $index['Key_name'] === 'index_idhsrblob')
- ) {
- $indexSql = 'FORCE INDEX FOR JOIN (' . $index['Key_name'] . ')';
- break;
- }
- }
-
- $sql = sprintf('DELETE hsrblob
- FROM %s hsrblob
- LEFT JOIN %s hsrevent %s on hsrblob.idhsrblob = hsrevent.idhsrblob
- WHERE hsrevent.idloghsr is null', $blobTable, $eventTable, $indexSql);
-
- Db::query($sql);
- return $sql;
- }
-
- public function getAllRecords()
- {
- $blobs = $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed);
- return $this->enrichRecords($blobs);
- }
-
- private function enrichRecords($blobs)
- {
- if (!empty($blobs)) {
- foreach ($blobs as $index => &$blob) {
- if (!empty($blob['compressed'])) {
- $blob['value'] = $this->uncompress($blob['value']);
- }
- }
- }
-
- return $blobs;
- }
-
- private function compress($data)
- {
- if (!empty($data)) {
- return gzcompress($data);
- }
-
- return $data;
- }
-
- private function uncompress($data)
- {
- if (!empty($data)) {
- return gzuncompress($data);
- }
-
- return $data;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrEvent.php b/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrEvent.php
deleted file mode 100644
index 6a64065..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrEvent.php
+++ /dev/null
@@ -1,166 +0,0 @@
-tablePrefixed = Common::prefixTable($this->table);
- $this->logBlobHsr = $logBlobHsr;
- }
-
- private function getDb()
- {
- if (!isset($this->db)) {
- $this->db = Db::get();
- }
- return $this->db;
- }
-
- public function install()
- {
- DbHelper::createTable($this->table, "
- `idhsrevent` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
- `idloghsr` INT(10) UNSIGNED NOT NULL,
- `time_since_load` BIGINT(8) UNSIGNED NOT NULL DEFAULT 0,
- `event_type` TINYINT UNSIGNED NOT NULL DEFAULT 0,
- `idselector` INT(10) UNSIGNED NULL DEFAULT NULL,
- `x` SMALLINT(5) NOT NULL DEFAULT 0,
- `y` SMALLINT(5) NOT NULL DEFAULT 0,
- `idhsrblob` INT(10) UNSIGNED DEFAULT NULL,
- PRIMARY KEY(`idhsrevent`),
- INDEX idloghsr (`idloghsr`),
- INDEX idhsrblob (`idhsrblob`)");
- // x and y is not unsigned on purpose as it may hold rarely a negative value
- }
-
- public function uninstall()
- {
- Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed));
- }
-
- public function record($idloghsr, $timeSinceLoad, $eventType, $idSelector, $x, $y, $text)
- {
- if ($x > self::MAX_SIZE) {
- $x = self::MAX_SIZE;
- }
-
- if ($y > self::MAX_SIZE) {
- $y = self::MAX_SIZE;
- }
-
- if ($x === null || $x === false) {
- $x = 0;
- }
-
- if ($y === null || $y === false) {
- $y = 0;
- }
-
- $idHsrBlob = $this->logBlobHsr->record($text);
-
- $values = array(
- 'idloghsr' => $idloghsr,
- 'time_since_load' => $timeSinceLoad,
- 'event_type' => $eventType,
- 'idselector' => $idSelector,
- 'x' => $x,
- 'y' => $y,
- 'idhsrblob' => $idHsrBlob,
- );
-
- $columns = implode('`,`', array_keys($values));
-
- $sql = sprintf('INSERT INTO %s (`%s`) VALUES(?,?,?,?,?,?,?) ', $this->tablePrefixed, $columns);
-
- $bind = array_values($values);
-
- $this->getDb()->query($sql, $bind);
- }
-
- public function getEventsForPageview($idLogHsr)
- {
- $sql = sprintf('SELECT %1$s.time_since_load, %1$s.event_type, %1$s.x, %1$s.y, %2$s.name as selector, %3$s.value as text, %3$s.compressed
- FROM %1$s
- LEFT JOIN %2$s ON %1$s.idselector = %2$s.idaction
- LEFT JOIN %3$s ON %1$s.idhsrblob = %3$s.idhsrblob
- WHERE %1$s.idloghsr = ? and %1$s.event_type != ?
- ORDER BY time_since_load ASC', $this->tablePrefixed, Common::prefixTable('log_action'), Common::prefixTable('log_hsr_blob'));
-
- $rows = $this->getDb()->fetchAll($sql, array($idLogHsr, RequestProcessor::EVENT_TYPE_CSS));
- foreach ($rows as $index => $row) {
- if (!empty($row['compressed'])) {
- $rows[$index]['text'] = gzuncompress($row['text']);
- }
- unset($rows[$index]['compressed']);
- }
- return $rows;
- }
-
- public function getCssEvents($idSiteHsr, $idLoghsr = '')
- {
- //idLogHsr will be empty in case of heatmaps, we cannot use it in where clause to resolve that, when its heatmap the where condition is `AND 1=1` and for session recording its `AND x.idloghsr=$idLoghsr`
- $idLogHsrLhs = '1';
- $idLogHsrRhs = '1';
- if (!empty($idLoghsr)) {
- $idLogHsrLhs = 'x.idloghsr';
- $idLogHsrRhs = $idLoghsr;
- }
- $sql = sprintf('SELECT distinct z.idhsrblob,a.name as url, z.value as text, z.compressed
- FROM %2$s x,%3$s y,%4$s z,%5$s a
- WHERE x.idsitehsr=? AND %1$s=? and y.event_type=? and x.idloghsr=y.idloghsr and y.idhsrblob = z.idhsrblob and a.idaction=y.idselector
- order by z.idhsrblob ASC', $idLogHsrLhs, Common::prefixTable('log_hsr_site'), Common::prefixTable('log_hsr_event'), Common::prefixTable('log_hsr_blob'), Common::prefixTable('log_action'));
-
- $rows = $this->getDb()->fetchAll($sql, array($idSiteHsr, $idLogHsrRhs, RequestProcessor::EVENT_TYPE_CSS));
- foreach ($rows as $index => $row) {
- if (!empty($row['compressed'])) {
- $rows[$index]['text'] = gzuncompress($row['text']);
- }
- unset($rows[$index]['compressed']);
- }
- return $rows;
- }
-
- public function getAllRecords()
- {
- return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrSite.php b/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrSite.php
deleted file mode 100644
index 708aba3..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/LogHsrSite.php
+++ /dev/null
@@ -1,141 +0,0 @@
-tablePrefixed = Common::prefixTable($this->table);
- }
-
- private function getDb()
- {
- if (!isset($this->db)) {
- $this->db = Db::get();
- }
- return $this->db;
- }
-
- public function install()
- {
- // it actually also has the advantage that removing an entry will be fast because when a user clicks on
- // "delete heatmap" we could only remove this entry, and then have a daily cronjob to delete all entries that are
- // no longer linked. instead of having to directly delete all data. Also it is more efficient to track when eg
- // a session and a heatmap is being recording at the same time or when several heatmaps are being recorded at once
- DbHelper::createTable($this->table, "
- `idsitehsr` INT(10) UNSIGNED NOT NULL,
- `idloghsr` INT(10) UNSIGNED NOT NULL,
- PRIMARY KEY(`idsitehsr`, `idloghsr`),
- INDEX index_idloghsr (`idloghsr`)");
- }
-
- public function uninstall()
- {
- Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed));
- }
-
- public function linkRecord($idLogHsr, $idSiteHsr)
- {
- $bind = array($idLogHsr,$idSiteHsr);
- $sql = sprintf('INSERT INTO %s (`idloghsr`, `idsitehsr`) VALUES(?,?)', $this->tablePrefixed);
-
- try {
- $this->getDb()->query($sql, $bind);
- } catch (\Exception $e) {
- if (Db::get()->isErrNo($e, \Piwik\Updater\Migration\Db::ERROR_CODE_DUPLICATE_ENTRY)) {
- return;
- }
- throw $e;
- }
- }
-
- // should be fast as covered index
- public function getNumPageViews($idSiteHsr)
- {
- $sql = sprintf('SELECT count(*) as numsamples FROM %s WHERE idsitehsr = ?', $this->tablePrefixed);
-
- return (int) $this->getDb()->fetchOne($sql, array($idSiteHsr));
- }
-
- // should be fast as covered index
- public function getNumSessions($idSiteHsr)
- {
- $sql = sprintf(
- 'SELECT count(distinct idvisit)
- FROM %s loghsrsite
- left join %s loghsr on loghsr.idloghsr = loghsrsite.idloghsr
- left join %s loghsrevent on loghsr.idloghsr = loghsrevent.idloghsr and loghsrevent.event_type = %s
- WHERE loghsrsite.idsitehsr = ? and loghsrevent.idhsrblob is not null',
- $this->tablePrefixed,
- Common::prefixTable('log_hsr'),
- Common::prefixTable('log_hsr_event'),
- RequestProcessor::EVENT_TYPE_INITIAL_DOM
- );
-
- return (int) $this->getDb()->fetchOne($sql, array($idSiteHsr));
- }
-
- public function unlinkRecord($idLogHsr, $idSiteHsr)
- {
- $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ? and idloghsr = ?', $this->tablePrefixed);
-
- return $this->getDb()->query($sql, array($idSiteHsr, $idLogHsr));
- }
-
- public function unlinkSiteRecords($idSiteHsr)
- {
- $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ?', $this->tablePrefixed);
-
- return $this->getDb()->query($sql, array($idSiteHsr));
- }
-
- public function getAllRecords()
- {
- return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed);
- }
-
- public function deleteNoLongerNeededRecords()
- {
- // DELETE ALL linked LOG ENTRIES WHOSE idsite does no longer exist or was removed
- // we delete links for removed site_hsr entries, and for site_hsr entries with status deleted
- // this query should only delete entries when they were deleted manually in the database basically.
- // otherwise the application takes already care of removing the needed links
- $sql = sprintf(
- 'DELETE FROM %1$s WHERE %1$s.idsitehsr NOT IN (select site_hsr.idsitehsr from %2$s site_hsr where site_hsr.status = "%3$s" or site_hsr.status = "%4$s")',
- Common::prefixTable('log_hsr_site'),
- Common::prefixTable('site_hsr'),
- SiteHsrDao::STATUS_ACTIVE,
- SiteHsrDao::STATUS_ENDED
- );
-
- Db::query($sql);
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/SiteHsrDao.php b/files/plugin-HeatmapSessionRecording-5.2.6/Dao/SiteHsrDao.php
deleted file mode 100644
index 037d00c..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Dao/SiteHsrDao.php
+++ /dev/null
@@ -1,422 +0,0 @@
-tablePrefixed = Common::prefixTable($this->table);
- }
-
- private function getDb()
- {
- if (!isset($this->db)) {
- $this->db = Db::get();
- }
- return $this->db;
- }
-
- public function install()
- {
- DbHelper::createTable($this->table, "
- `idsitehsr` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
- `idsite` INT(10) UNSIGNED NOT NULL,
- `name` VARCHAR(" . Name::MAX_LENGTH . ") NOT NULL,
- `sample_rate` DECIMAL(4,1) UNSIGNED NOT NULL DEFAULT " . SampleRate::MAX_RATE . ",
- `sample_limit` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT 1000,
- `match_page_rules` TEXT DEFAULT '',
- `excluded_elements` TEXT DEFAULT '',
- `record_type` TINYINT(1) UNSIGNED DEFAULT 0,
- `page_treemirror` MEDIUMBLOB NULL DEFAULT NULL,
- `screenshot_url` VARCHAR(300) NULL DEFAULT NULL,
- `breakpoint_mobile` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0,
- `breakpoint_tablet` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0,
- `min_session_time` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0,
- `requires_activity` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
- `capture_keystrokes` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
- `created_date` DATETIME NOT NULL,
- `updated_date` DATETIME NOT NULL,
- `status` VARCHAR(10) NOT NULL DEFAULT '" . self::STATUS_ACTIVE . "',
- `capture_manually` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
- PRIMARY KEY(`idsitehsr`),
- INDEX index_status_idsite (`status`, `idsite`),
- INDEX index_idsite_record_type (`idsite`, `record_type`)");
- }
-
- public function createHeatmapRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $status, $captureDomManually, $createdDate)
- {
- $columns = array(
- 'idsite' => $idSite,
- 'name' => $name,
- 'sample_limit' => $sampleLimit,
- 'match_page_rules' => $matchPageRules,
- 'sample_rate' => $sampleRate,
- 'status' => $status,
- 'record_type' => self::RECORD_TYPE_HEATMAP,
- 'created_date' => $createdDate,
- 'updated_date' => $createdDate,
- 'capture_manually' => !empty($captureDomManually) ? 1 : 0,
- );
-
- if (!empty($excludedElements)) {
- $columns['excluded_elements'] = $excludedElements;
- }
-
- if (!empty($screenshotUrl)) {
- $columns['screenshot_url'] = $screenshotUrl;
- }
- if ($breakpointMobile !== false && $breakpointMobile !== null) {
- $columns['breakpoint_mobile'] = $breakpointMobile;
- }
-
- if ($breakpointTablet !== false && $breakpointTablet !== null) {
- $columns['breakpoint_tablet'] = $breakpointTablet;
- }
-
- return $this->insertColumns($columns);
- }
-
- public function createSessionRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $minSessionTime, $requiresActivity, $captureKeystrokes, $status, $createdDate)
- {
- $columns = array(
- 'idsite' => $idSite,
- 'name' => $name,
- 'sample_limit' => $sampleLimit,
- 'match_page_rules' => $matchPageRules,
- 'sample_rate' => $sampleRate,
- 'status' => $status,
- 'record_type' => self::RECORD_TYPE_SESSION,
- 'min_session_time' => !empty($minSessionTime) ? $minSessionTime : 0,
- 'requires_activity' => !empty($requiresActivity) ? 1 : 0,
- 'capture_keystrokes' => !empty($captureKeystrokes) ? 1 : 0,
- 'created_date' => $createdDate,
- 'updated_date' => $createdDate,
- );
-
- return $this->insertColumns($columns);
- }
-
- private function insertColumns($columns)
- {
- $columns = $this->encodeFieldsWhereNeeded($columns);
-
- $bind = array_values($columns);
- $placeholder = Common::getSqlStringFieldsArray($columns);
-
- $sql = sprintf(
- 'INSERT INTO %s (`%s`) VALUES(%s)',
- $this->tablePrefixed,
- implode('`,`', array_keys($columns)),
- $placeholder
- );
-
- $this->getDb()->query($sql, $bind);
-
- $idSiteHsr = $this->getDb()->lastInsertId();
-
- return (int) $idSiteHsr;
- }
-
- protected function getCurrentTime()
- {
- return Date::now()->getDatetime();
- }
-
- public function updateHsrColumns($idSite, $idSiteHsr, $columns)
- {
- $columns = $this->encodeFieldsWhereNeeded($columns);
-
- if (!empty($columns)) {
- if (!isset($columns['updated_date'])) {
- $columns['updated_date'] = $this->getCurrentTime();
- }
-
- if (!empty($columns['page_treemirror'])) {
- $columns['capture_manually'] = 0;
- } elseif (!empty($columns['capture_manually'])) {
- $columns['page_treemirror'] = null;
- }
-
- $fields = array();
- $bind = array();
- foreach ($columns as $key => $value) {
- $fields[] = ' ' . $key . ' = ?';
- $bind[] = $value;
- }
- $fields = implode(',', $fields);
-
- $query = sprintf('UPDATE %s SET %s WHERE idsitehsr = ? AND idsite = ?', $this->tablePrefixed, $fields);
- $bind[] = (int) $idSiteHsr;
- $bind[] = (int) $idSite;
-
- // we do not use $db->update() here as this method is as well used in Tracker mode and the tracker DB does not
- // support "->update()". Therefore we use the query method where we know it works with tracker and regular DB
- $this->getDb()->query($query, $bind);
- }
- }
-
- public function hasRecords($idSite, $recordType)
- {
- $sql = sprintf('SELECT idsite FROM %s WHERE record_type = ? and `status` IN(?,?) and idsite = ? LIMIT 1', $this->tablePrefixed);
- $records = $this->getDb()->fetchRow($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, $idSite));
-
- return !empty($records);
- }
-
- public function deleteRecord($idSite, $idSiteHsr)
- {
- // now we delete the heatmap manually and it should notice all log entries for that heatmap are no longer needed
- $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ? and idsite = ?', $this->tablePrefixed);
- Db::query($sql, array($idSiteHsr, $idSite));
- }
-
- private function getAllFieldNames($includePageTreeMirror)
- {
- $fields = '`idsitehsr`,`idsite`,`name`, `sample_rate`, `sample_limit`, `match_page_rules`, `excluded_elements`, `record_type`, ';
- if (!empty($includePageTreeMirror)) {
- $fields .= '`page_treemirror`,';
- }
- $fields .= '`screenshot_url`, `breakpoint_mobile`, `breakpoint_tablet`, `min_session_time` , `requires_activity`, `capture_keystrokes`, `created_date`, `updated_date`, `status`, `capture_manually`';
- return $fields;
- }
-
- public function getRecords($idSite, $recordType, $includePageTreeMirror)
- {
- $fields = $this->getAllFieldNames($includePageTreeMirror);
- $sql = sprintf('SELECT ' . $fields . ' FROM %s WHERE record_type = ? and `status` IN(?,?,?) and idsite = ? order by created_date desc', $this->tablePrefixed);
- $records = $this->getDb()->fetchAll($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED, $idSite));
-
- return $this->enrichRecords($records);
- }
-
- public function getRecord($idSite, $idSiteHsr, $recordType)
- {
- $sql = sprintf('SELECT * FROM %s WHERE record_type = ? and `status` IN(?,?,?) and idsite = ? and idsitehsr = ? LIMIT 1', $this->tablePrefixed);
- $record = $this->getDb()->fetchRow($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED, $idSite, $idSiteHsr));
-
- return $this->enrichRecord($record);
- }
-
- public function getNumRecordsTotal($recordType)
- {
- $sql = sprintf('SELECT count(*) as total FROM %s WHERE record_type = ? and `status` IN(?,?,?)', $this->tablePrefixed);
- return $this->getDb()->fetchOne($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED));
- }
-
- public function hasActiveRecordsAcrossSites()
- {
- $query = $this->getQueryActiveRequests();
-
- $sql = sprintf("SELECT count(*) as numrecords FROM %s WHERE %s LIMIT 1", $this->tablePrefixed, $query['where']);
- $numRecords = $this->getDb()->fetchOne($sql, $query['bind']);
-
- return !empty($numRecords);
- }
-
- private function getQueryActiveRequests()
- {
- // for sessions we also need to return ended sessions to make sure to record all page views once a user takes part in
- // a session recording. Otherwise as soon as the limit of sessions has reached, it would stop recording any further page views in already started session recordings
-
- // we only fetch recorded sessions with status ended for the last 24 hours to not expose any potential config and for faster processing etc
- $oneDayAgo = Date::now()->subDay(1)->getDatetime();
-
- return array(
- 'where' => '(status = ? or (record_type = ? and status = ? and updated_date > ?))',
- 'bind' => array(self::STATUS_ACTIVE, self::RECORD_TYPE_SESSION, self::STATUS_ENDED, $oneDayAgo)
- );
- }
-
- /**
- * For performance reasons the page_treemirror will be read only partially!
- * @param $idSite
- * @return mixed
- * @throws \Piwik\Tracker\Db\DbException
- */
- public function getActiveRecords($idSite)
- {
- $query = $this->getQueryActiveRequests();
-
- $bind = $query['bind'];
- $bind[] = $idSite;
-
- $fields = $this->getAllFieldNames(false);
- // we want to avoid needing to read all the entire treemirror every time the tracking cache will be updated
- // as in worst case every treemirror can be 16MB or in rare cases even more. Most of the time it's only like 50KB or so
- // but we want to avoid fetching heaps of unneeded data
- $fields .= ', SUBSTRING(page_treemirror, 1, 10) as page_treemirror';
-
- // NOTE: If you adjust this query, you might also
- $sql = sprintf("SELECT " . $fields . " FROM %s WHERE %s and idsite = ? ORDER BY idsitehsr asc", $this->tablePrefixed, $query['where']);
- $records = $this->getDb()->fetchAll($sql, $bind);
-
- foreach ($records as $index => $record) {
- if (!empty($record['page_treemirror'])) {
- // avoids an error when it tries to uncompress
- $records[$index]['page_treemirror'] = $this->compress($record['page_treemirror']);
- }
- }
-
- return $this->enrichRecords($records);
- }
-
- private function enrichRecords($records)
- {
- if (empty($records)) {
- return $records;
- }
-
- foreach ($records as $index => $record) {
- $records[$index] = $this->enrichRecord($record);
- }
-
- return $records;
- }
-
- private function enrichRecord($record)
- {
- if (empty($record)) {
- return $record;
- }
-
- $record['idsitehsr'] = (int) $record['idsitehsr'];
- $record['idsite'] = (int) $record['idsite'];
- $record['sample_rate'] = number_format($record['sample_rate'], 1, '.', '');
- $record['record_type'] = (int) $record['record_type'];
- $record['sample_limit'] = (int) $record['sample_limit'];
- $record['min_session_time'] = (int) $record['min_session_time'];
- $record['breakpoint_mobile'] = (int) $record['breakpoint_mobile'];
- $record['breakpoint_tablet'] = (int) $record['breakpoint_tablet'];
- $record['match_page_rules'] = $this->decodeField($record['match_page_rules']);
- $record['requires_activity'] = !empty($record['requires_activity']);
- $record['capture_keystrokes'] = !empty($record['capture_keystrokes']);
- $record['capture_manually'] = !empty($record['capture_manually']) ? 1 : 0;
-
- if (!empty($record['page_treemirror'])) {
- $record['page_treemirror'] = $this->uncompress($record['page_treemirror']);
- } else {
- $record['page_treemirror'] = '';
- }
-
- return $record;
- }
-
- public function uninstall()
- {
- Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed));
- }
-
- public function getAllEntities()
- {
- $records = $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed);
-
- return $this->enrichRecords($records);
- }
-
- private function encodeFieldsWhereNeeded($columns)
- {
- foreach ($columns as $column => $value) {
- if ($column === 'match_page_rules') {
- $columns[$column] = $this->encodeField($value);
- } elseif ($column === 'page_treemirror') {
- if (!empty($value)) {
- $columns[$column] = $this->compress($value);
- } else {
- $columns[$column] = null;
- }
- } elseif (in_array($column, array('breakpoint_mobile', 'breakpoint_tablet', 'min_session_time', 'sample_rate'), $strict = true)) {
- if ($value > self::MAX_SMALLINT) {
- $columns[$column] = self::MAX_SMALLINT;
- }
- } elseif (in_array($column, array('requires_activity', 'capture_keystrokes'), $strict = true)) {
- if (!empty($value)) {
- $columns[$column] = 1;
- } else {
- $columns[$column] = 0;
- }
- }
- }
-
- return $columns;
- }
-
- private function compress($data)
- {
- if (!empty($data)) {
- return gzcompress($data);
- }
-
- return $data;
- }
-
- private function uncompress($data)
- {
- if (!empty($data)) {
- return gzuncompress($data);
- }
-
- return $data;
- }
-
- private function encodeField($field)
- {
- if (empty($field) || !is_array($field)) {
- $field = array();
- }
-
- return json_encode($field);
- }
-
- private function decodeField($field)
- {
- if (!empty($field)) {
- $field = @json_decode($field, true);
- }
-
- if (empty($field) || !is_array($field)) {
- $field = array();
- }
-
- return $field;
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/DataTable/Filter/EnrichRecordedSessions.php b/files/plugin-HeatmapSessionRecording-5.2.6/DataTable/Filter/EnrichRecordedSessions.php
deleted file mode 100644
index 0a306e7..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/DataTable/Filter/EnrichRecordedSessions.php
+++ /dev/null
@@ -1,73 +0,0 @@
-getRowsWithoutSummaryRow() as $row) {
- if ($isAnonymous) {
- foreach (self::getBlockedFields() as $blockedField) {
- if ($row->getColumn($blockedField) !== false) {
- $row->setColumn($blockedField, false);
- }
- }
- } else {
- $row->setColumn('idvisitor', bin2hex($row->getColumn('idvisitor')));
- }
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Diagnostic/ConfigsPhpCheck.php b/files/plugin-HeatmapSessionRecording-5.2.6/Diagnostic/ConfigsPhpCheck.php
deleted file mode 100644
index c436154..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Diagnostic/ConfigsPhpCheck.php
+++ /dev/null
@@ -1,112 +0,0 @@
-translator = $translator;
- }
-
- public function execute()
- {
- $label = $this->translator->translate('Heatmap & Session Recording Tracking');
-
- $site = new Model();
- $idSites = $site->getSitesId();
- $idSite = array_shift($idSites);
-
- $baseUrl = SettingsPiwik::getPiwikUrl();
- if (!Common::stringEndsWith($baseUrl, '/')) {
- $baseUrl .= '/';
- }
-
- $baseUrl .= HeatmapSessionRecording::getPathPrefix() . '/';
- $baseUrl .= 'HeatmapSessionRecording/configs.php';
- $testUrl = $baseUrl . '?idsite=' . (int) $idSite . '&trackerid=5lX6EM&url=http%3A%2F%2Ftest.test%2F';
-
- $error = null;
- $response = null;
-
- $errorResult = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpErrorResult');
- $manualCheck = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpManualCheck');
-
- if (method_exists('\Piwik\SettingsPiwik', 'isInternetEnabled')) {
- $isInternetEnabled = SettingsPiwik::isInternetEnabled();
- if (!$isInternetEnabled) {
- $unknown = $this->translator->translate('HeatmapSessionRecording_ConfigsInternetDisabled', $testUrl) . ' ' . $manualCheck;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknown));
- }
- }
-
- try {
- $response = Http::sendHttpRequest($testUrl, $timeout = 2);
- } catch (\Exception $e) {
- $error = $e->getMessage();
- }
-
- if (!empty($response)) {
- $response = Common::mb_strtolower($response);
- if (strpos($response, 'piwik.heatmapsessionrecording') !== false) {
- $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpSuccess', $baseUrl);
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $message));
- } elseif (strpos($response, 'forbidden') !== false || strpos($response, ' forbidden') !== false || strpos($response, ' denied ') !== false || strpos($response, '403 ') !== false || strpos($response, '404 ') !== false) {
- // Likely the server returned eg a 403 HTML
- $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpNotAccessible', array($testUrl)) . ' ' . $errorResult;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $message));
- }
- }
-
- if (!empty($error)) {
- $error = Common::mb_strtolower($error);
-
- if (strpos($error, 'forbidden ') !== false || strpos($error, ' forbidden') !== false || strpos($error, 'denied ') !== false || strpos($error, '403 ') !== false || strpos($error, '404 ') !== false) {
- $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpNotAccessible', array($testUrl)) . ' ' . $errorResult;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $message));
- }
-
- if (strpos($error, 'ssl ') !== false || strpos($error, ' ssl') !== false || strpos($error, 'self signed') !== false || strpos($error, 'certificate ') !== false) {
- $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpSelfSignedError', array($testUrl)) . ' ' . $manualCheck;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $message));
- }
-
- $unknownError = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpUnknownError', array($testUrl, $error)) . ' ' . $errorResult;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknownError));
- }
-
- $unknown = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpUnknown', $testUrl) . $manualCheck;
- return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknown));
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/Breakpoint.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/Breakpoint.php
deleted file mode 100644
index bb0eb6a..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/Breakpoint.php
+++ /dev/null
@@ -1,66 +0,0 @@
-breakpoint = $breakpoint;
- $this->name = $name;
- }
-
- public function check()
- {
- $title = Piwik::translate('HeatmapSessionRecording_BreakpointX', array($this->name));
-
- // zero is a valid value!
- if ($this->breakpoint === false || $this->breakpoint === null || $this->breakpoint === '') {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title));
- }
-
- if (!is_numeric($this->breakpoint)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title)));
- }
-
- if ($this->breakpoint < 0) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0)));
- }
-
- if ($this->breakpoint > self::MAX_LIMIT) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/CaptureKeystrokes.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/CaptureKeystrokes.php
deleted file mode 100644
index b6004f9..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/CaptureKeystrokes.php
+++ /dev/null
@@ -1,40 +0,0 @@
-value = $value;
- }
-
- public function check()
- {
- $allowedValues = array('0', '1', 0, 1, true, false);
-
- if (!in_array($this->value, $allowedValues, $strict = true)) {
- $message = Piwik::translate('HeatmapSessionRecording_ErrorXNotWhitelisted', array('captureKeystrokes', '"1", "0"'));
- throw new Exception($message);
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/ExcludedElements.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/ExcludedElements.php
deleted file mode 100644
index 2395013..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/ExcludedElements.php
+++ /dev/null
@@ -1,50 +0,0 @@
-selector = $name;
- }
-
- public function check()
- {
- if ($this->selector === null || $this->selector === false || $this->selector === '') {
- // selecto may not be set
- return;
- }
-
- $title = Piwik::translate('HeatmapSessionRecording_ExcludedElements');
-
- if (Common::mb_strlen($this->selector) > static::MAX_LENGTH) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/MinSessionTime.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/MinSessionTime.php
deleted file mode 100644
index 22dd6d4..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/MinSessionTime.php
+++ /dev/null
@@ -1,57 +0,0 @@
-minSessionTime = $minSessionTime;
- }
-
- public function check()
- {
- $title = 'HeatmapSessionRecording_MinSessionTime';
-
- if ($this->minSessionTime === false || $this->minSessionTime === null || $this->minSessionTime === '') {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title));
- }
-
- if (!is_numeric($this->minSessionTime)) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title)));
- }
-
- if ($this->minSessionTime < 0) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0)));
- }
-
- if ($this->minSessionTime > self::MAX_LIMIT) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/Name.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/Name.php
deleted file mode 100644
index f03692d..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/Name.php
+++ /dev/null
@@ -1,51 +0,0 @@
-name = $name;
- }
-
- public function check()
- {
- $title = 'General_Name';
-
- if (empty($this->name)) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title));
- }
-
- if (Common::mb_strlen($this->name) > static::MAX_LENGTH) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRule.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRule.php
deleted file mode 100644
index 3c09d15..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRule.php
+++ /dev/null
@@ -1,80 +0,0 @@
-target = $targets;
- $this->parameterName = $parameterName;
- $this->index = $index;
- }
-
- public function check()
- {
- $titleSingular = 'HeatmapSessionRecording_PageRule';
-
- if (!is_array($this->target)) {
- $titleSingular = Piwik::translate($titleSingular);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorInnerIsNotAnArray', array($titleSingular, $this->parameterName)));
- }
-
- if (empty($this->target['attribute'])) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('attribute', $this->parameterName, $this->index)));
- }
-
- if (empty($this->target['type'])) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('type', $this->parameterName, $this->index)));
- }
-
- if (!array_key_exists('inverted', $this->target)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('inverted', $this->parameterName, $this->index)));
- }
-
- if (empty($this->target['value']) && Tracker\PageRuleMatcher::doesTargetTypeRequireValue($this->target['type'])) {
- // any is the only target type that may have an empty value
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingValue', array('value', $this->parameterName, $this->index)));
- }
-
- if ($this->target['type'] === Tracker\PageRuleMatcher::TYPE_REGEXP && isset($this->target['value'])) {
- $pattern = Tracker\PageRuleMatcher::completeRegexpPattern($this->target['value']);
- if (@preg_match($pattern, '') === false) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorInvalidRegExp', array($this->target['value'])));
- }
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRules.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRules.php
deleted file mode 100644
index 562c871..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/PageRules.php
+++ /dev/null
@@ -1,61 +0,0 @@
-targets = $targets;
- $this->parameterName = $parameterName;
- $this->needsAtLeastOneEntry = $needsAtLeastOneEntry;
- }
-
- public function check()
- {
- if ($this->needsAtLeastOneEntry && empty($this->targets)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $this->parameterName));
- }
-
- if (!is_array($this->targets)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorNotAnArray', $this->parameterName));
- }
-
- foreach ($this->targets as $index => $target) {
- $target = new PageRule($target, $this->parameterName, $index);
- $target->check();
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/RequiresActivity.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/RequiresActivity.php
deleted file mode 100644
index 61ff6a6..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/RequiresActivity.php
+++ /dev/null
@@ -1,40 +0,0 @@
-value = $value;
- }
-
- public function check()
- {
- $allowedValues = array('0', '1', 0, 1, true, false);
-
- if (!in_array($this->value, $allowedValues, $strict = true)) {
- $message = Piwik::translate('HeatmapSessionRecording_ErrorXNotWhitelisted', array('activated', '"1", "0"'));
- throw new Exception($message);
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleLimit.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleLimit.php
deleted file mode 100644
index daa8686..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleLimit.php
+++ /dev/null
@@ -1,57 +0,0 @@
-sampleLimit = $sampleLimit;
- }
-
- public function check()
- {
- $title = 'HeatmapSessionRecording_SampleLimit';
-
- if ($this->sampleLimit === false || $this->sampleLimit === null || $this->sampleLimit === '') {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title));
- }
-
- if (!is_numeric($this->sampleLimit)) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title)));
- }
-
- if ($this->sampleLimit < 0) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0)));
- }
-
- if ($this->sampleLimit > self::MAX_LIMIT) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleRate.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleRate.php
deleted file mode 100644
index b0f6261..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/SampleRate.php
+++ /dev/null
@@ -1,62 +0,0 @@
-sampleRate = $sampleRate;
- }
-
- public function check()
- {
- $title = 'HeatmapSessionRecording_SampleRate';
-
- if ($this->sampleRate === false || $this->sampleRate === null || $this->sampleRate === '') {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title));
- }
-
- if (!is_numeric($this->sampleRate)) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title)));
- }
-
- if ($this->sampleRate < 0) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0)));
- }
-
- if ($this->sampleRate > self::MAX_RATE) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_RATE)));
- }
-
- if (!preg_match('/^\d{1,3}\.?\d?$/', (string) $this->sampleRate)) {
- $title = Piwik::translate($title);
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Input/ScreenshotUrl.php b/files/plugin-HeatmapSessionRecording-5.2.6/Input/ScreenshotUrl.php
deleted file mode 100644
index 2b40332..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Input/ScreenshotUrl.php
+++ /dev/null
@@ -1,58 +0,0 @@
-url = $name;
- }
-
- public function check()
- {
- if ($this->url === null || $this->url === false || $this->url === '') {
- // url may not be set
- return;
- }
-
- $title = Piwik::translate('HeatmapSessionRecording_ScreenshotUrl');
-
- if (preg_match('/\s/', $this->url)) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXContainsWhitespace', $title));
- }
-
- if (strpos($this->url, '//') === false) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_UrlXDoesNotLookLikeUrl', array($title, static::MAX_LENGTH)));
- }
-
- if (Common::mb_strlen($this->url) > static::MAX_LENGTH) {
- throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH)));
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Install/HtAccess.php b/files/plugin-HeatmapSessionRecording-5.2.6/Install/HtAccess.php
deleted file mode 100644
index dc7017d..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Install/HtAccess.php
+++ /dev/null
@@ -1,66 +0,0 @@
-getPluginDir() . '/.htaccess';
- }
-
- private function getSourcePath()
- {
- return $this->getPluginDir() . '/Install/htaccessTemplate';
- }
-
- private function exists()
- {
- $path = $this->getTargetPath();
- return file_exists($path);
- }
-
- private function canCreate()
- {
- return is_writable($this->getPluginDir());
- }
-
- private function isContentDifferent()
- {
- $templateContent = trim(file_get_contents($this->getSourcePath()));
- $fileContent = trim(file_get_contents($this->getTargetPath()));
-
- return $templateContent !== $fileContent;
- }
-
- public function install()
- {
- if (
- $this->canCreate() && (!$this->exists() || (is_readable($this->getTargetPath()) && $this->isContentDifferent()))
- ) {
- Filesystem::copy($this->getSourcePath(), $this->getTargetPath());
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Install/htaccessTemplate b/files/plugin-HeatmapSessionRecording-5.2.6/Install/htaccessTemplate
deleted file mode 100644
index c302274..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Install/htaccessTemplate
+++ /dev/null
@@ -1,23 +0,0 @@
-# This file is generated by InnoCraft - Piwik, do not edit directly
-# Please report any issue or improvement directly to the InnoCraft team.
-# Allow to serve configs.php which is safe
-
-
-
- Order Allow,Deny
- Allow from All
-
- = 2.4>
- Require all granted
-
-
-
-
- Order Allow,Deny
- Allow from All
-
-
- Require all granted
-
-
-
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/LEGALNOTICE b/files/plugin-HeatmapSessionRecording-5.2.6/LEGALNOTICE
deleted file mode 100644
index b8a6bb2..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/LEGALNOTICE
+++ /dev/null
@@ -1,46 +0,0 @@
-COPYRIGHT
-
- The software package is:
-
- Copyright (C) 2017 InnoCraft Ltd (NZBN 6106769)
-
-
-SOFTWARE LICENSE
-
- This software is licensed under the InnoCraft EULA and the license has been included in this
- software package in the file LICENSE.
-
-
-THIRD-PARTY COMPONENTS AND LIBRARIES
-
- The following components/libraries are redistributed in this package,
- and subject to their respective licenses.
-
- Name: heatmap.js
- Link: https://www.patrick-wied.at/static/heatmapjs/
- License: MIT
- License File: ibs/heatmap.js/LICENSE
-
- Name: mutation-summary
- Link: https://github.com/rafaelw/mutation-summary/
- License: Apache 2.0
- License File: libs/mutation-summary/COPYING
-
- Name: MutationObserver.js
- Link: https://github.com/megawac/MutationObserver.js/tree/master
- License: WTFPL, Version 2
- License File: libs/MutationObserver.js/license
-
- Name: svg.js
- Link: http://tkyk.github.com/jquery-history-plugin/
- License: http://svgjs.com/
- License File: libs/svg.js/LICENSE.txt
-
- Name: Get Element CSS Selector
- Link: https://gist.github.com/asfaltboy/8aea7435b888164e8563
- License: MIT
- License File: included in tracker.min.js
-
- Name: Material icons ("repeat", "looks_one", "looks_two", "looks_four", "looks_six") in angularjs/sessionvis/sessionvis.directive.html
- Link: https://design.google.com/icons/
- License: Apache License Version 2.0
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/LICENSE b/files/plugin-HeatmapSessionRecording-5.2.6/LICENSE
deleted file mode 100644
index 4686f35..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/LICENSE
+++ /dev/null
@@ -1,49 +0,0 @@
-InnoCraft License
-
-This InnoCraft End User License Agreement (the "InnoCraft EULA") is between you and InnoCraft Ltd (NZBN 6106769) ("InnoCraft"). If you are agreeing to this Agreement not as an individual but on behalf of your company, then "Customer" or "you" means your company, and you are binding your company to this Agreement. InnoCraft may modify this Agreement from time to time, subject to the terms in Section (xii) below.
-
-By clicking on the "I’ve read and accept the terms & conditions (https://shop.matomo.org/terms-conditions/)" (or similar button) that is presented to you at the time of your Order, or by using or accessing InnoCraft products, you indicate your assent to be bound by this Agreement.
-
-
-InnoCraft EULA
-
-(i) InnoCraft is the licensor of the Plugin for Matomo Analytics (the "Software").
-
-(ii) Subject to the terms and conditions of this Agreement, InnoCraft grants you a limited, worldwide, non-exclusive, non-transferable and non-sublicensable license to install and use the Software only on hardware systems owned, leased or controlled by you, during the applicable License Term. The term of each Software license ("License Term") will be specified in your Order. Your License Term will end upon any breach of this Agreement.
-
-(iii) Unless otherwise specified in your Order, for each Software license that you purchase, you may install one production instance of the Software in a Matomo Analytics instance owned or operated by you, and accessible via one URL ("Matomo instance"). Additional licenses must be purchased in order to deploy the Software in multiple Matomo instances, including when these multiple Matomo instances are hosted on a single hardware system.
-
-(iv) Licenses granted by InnoCraft are granted subject to the condition that you must ensure the maximum number of Authorized Users and Authorized Sites that are able to access and use the Software is equal to the number of User and Site Licenses for which the necessary fees have been paid to InnoCraft for the Subscription period. You may upgrade your license at any time on payment of the appropriate fees to InnoCraft in order to increase the maximum number of authorized users or sites. The number of User and Site Licenses granted to you is dependent on the fees paid by you. “User License” means a license granted under this EULA to you to permit an Authorized User to use the Software. “Authorized User” means a person who has an account in the Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. "Site License" means a license granted under this EULA to you to permit an Authorized Site to use the Matomo Marketplace Plugin. “Authorized Sites” means a website or a measurable within Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. These restrictions also apply if you install the Matomo Analytics Platform as part of your WordPress.
-
-(v) Piwik Analytics was renamed to Matomo Analytics in January 2018. The same terms and conditions as well as any restrictions or grants apply if you are using any version of Piwik.
-
-(vi) The Software requires a license key in order to operate, which will be delivered to the email addresses specified in your Order when we have received payment of the applicable fees.
-
-(vii) Any information that InnoCraft may collect from you or your device will be subject to InnoCraft Privacy Policy (https://www.innocraft.com/privacy).
-
-(viii) You are bound by the Matomo Marketplace Terms and Conditions (https://shop.matomo.org/terms-conditions/).
-
-(ix) You may not reverse engineer or disassemble or re-distribute the Software in whole or in part, or create any derivative works from or sublicense any rights in the Software, unless otherwise expressly authorized in writing by InnoCraft.
-
-(x) The Software is protected by copyright and other intellectual property laws and treaties. InnoCraft own all title, copyright and other intellectual property rights in the Software, and the Software is licensed to you directly by InnoCraft, not sold.
-
-(xi) The Software is provided under an "as is" basis and without any support or maintenance. Nothing in this Agreement shall require InnoCraft to provide you with support or fixes to any bug, failure, mis-performance or other defect in The Software. InnoCraft may provide you, from time to time, according to his sole discretion, with updates of the Software. You hereby warrant to keep the Software up-to-date and install all relevant updates. InnoCraft shall provide any update free of charge.
-
-(xii) The Software is provided "as is", and InnoCraft hereby disclaim all warranties, including but not limited to any implied warranties of title, non-infringement, merchantability or fitness for a particular purpose. InnoCraft shall not be liable or responsible in any way for any losses or damage of any kind, including lost profits or other indirect or consequential damages, relating to your use of or reliance upon the Software.
-
-(xiii) We may update or modify this Agreement from time to time, including the referenced Privacy Policy and the Matomo Marketplace Terms and Conditions. If a revision meaningfully reduces your rights, we will use reasonable efforts to notify you (by, for example, sending an email to the billing or technical contact you designate in the applicable Order). If we modify the Agreement during your License Term or Subscription Term, the modified version will be effective upon your next renewal of a License Term.
-
-
-About InnoCraft Ltd
-
-At InnoCraft Ltd, we create innovating quality products to grow your business and to maximize your success.
-
-Our software products are built on top of Matomo Analytics: the leading open digital analytics platform used by more than one million websites worldwide. We are the creators and makers of the Matomo Analytics platform.
-
-
-Contact
-
-Email: contact@innocraft.com
-Contact form: https://www.innocraft.com/#contact
-Website: https://www.innocraft.com/
-Buy our products: Premium Features for Matomo Analytics https://plugins.matomo.org/premium
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/Menu.php b/files/plugin-HeatmapSessionRecording-5.2.6/Menu.php
deleted file mode 100644
index 9ec1075..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/Menu.php
+++ /dev/null
@@ -1,50 +0,0 @@
-validator = $validator;
- }
-
- public function configureAdminMenu(MenuAdmin $menu)
- {
- $idSite = Common::getRequestVar('idSite', 0, 'int');
-
- if (!empty($idSite) && !Piwik::isUserIsAnonymous() && $this->validator->canWrite($idSite)) {
- if (!$this->validator->isHeatmapRecordingDisabled()) {
- $menu->addMeasurableItem('HeatmapSessionRecording_Heatmaps', $this->urlForAction('manageHeatmap'), $orderId = 30);
- }
- if (!$this->validator->isSessionRecordingDisabled()) {
- $menu->addMeasurableItem('HeatmapSessionRecording_SessionRecordings', $this->urlForAction('manageSessions'), $orderId = 30);
- }
- }
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/MutationManipulator.php b/files/plugin-HeatmapSessionRecording-5.2.6/MutationManipulator.php
deleted file mode 100644
index 5878e4f..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/MutationManipulator.php
+++ /dev/null
@@ -1,226 +0,0 @@
-configuration = $configuration;
- $this->generateNonce();
- }
-
- public function manipulate($initialMutation, $idSiteHsr, $idLogHsr)
- {
- $parseAndSanitizeCssLinks = $this->updateCssLinks($initialMutation, $idSiteHsr, $idLogHsr);
-
- return $this->sanitizeNodeAttributes($parseAndSanitizeCssLinks);
- }
-
- public function updateCssLinks($initialMutation, $idSiteHsr, $idLogHsr)
- {
- if ($this->configuration->isLoadCSSFromDBEnabled()) {
- $blob = new LogHsrBlob();
- $dao = new LogHsrEvent($blob);
- $cssEvents = $dao->getCssEvents($idSiteHsr, $idLogHsr);
- if (!empty($cssEvents) && !empty($initialMutation)) {
- $initialMutation = $this->updateInitialMutationWithInlineCss($initialMutation, $cssEvents);
- }
- }
-
- return $initialMutation;
- }
-
- public function getNonce()
- {
- if (!$this->nonce) {
- $this->generateNonce();
- }
-
-
- return $this->nonce;
- }
-
- public function generateNonce()
- {
- $this->nonce = $this->generateRandomString();
- }
-
- private function generateRandomString($length = 10)
- {
- $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
- $charactersLength = strlen($characters);
- $randomString = '';
- for ($i = 0; $i < $length; $i++) {
- $randomString .= $characters[rand(0, $charactersLength - 1)];
- }
- return $randomString;
- }
-
- public function sanitizeNodeAttributes($initialMutation)
- {
- $initialMutationArray = json_decode($initialMutation, true);
- if (!empty($initialMutationArray['children'])) {
- $this->parseMutationArrayRecursivelyToSanitizeNodes($initialMutationArray['children']);
- $initialMutation = json_encode($initialMutationArray);
- }
-
- return $initialMutation;
- }
-
- public function updateInitialMutationWithInlineCss($initialMutation, $cssEvents)
- {
- $formattedCssEvents = $this->formatCssEvents($cssEvents);
- $initialMutationArray = json_decode($initialMutation, true);
- if (!empty($initialMutationArray['children']) && !empty($formattedCssEvents)) {
- $this->parseMutationArrayRecursivelyForCssLinks($initialMutationArray['children'], $formattedCssEvents);
-
- $initialMutation = json_encode($initialMutationArray);
- }
-
- return $initialMutation;
- }
-
- public function formatCssEvents($cssEvents)
- {
- $formatted = array();
- foreach ($cssEvents as $cssEvent) {
- if (!isset($formatted[md5(trim($cssEvent['url']))])) { //Only use the first one since the o/p is sorted by ID in ascending order
- $formatted[md5(trim($cssEvent['url']))] = $cssEvent;
- }
- }
-
- return $formatted;
- }
-
- private function parseMutationArrayRecursivelyForCssLinks(&$nodes, $cssEvents, &$id = 900000000)
- {
- foreach ($nodes as &$node) {
- $parseChildNodes = true;
- if (isset($node['tagName']) && $node['tagName'] == 'LINK' && !empty($node['attributes']['url']) && !empty($cssEvents) && !empty($cssEvents[md5(trim($node['attributes']['url']))]['text'])) {
- $parseChildNodes = false;
- $content = $cssEvents[md5(trim($node['attributes']['url']))]['text'];
- if (!empty($content)) {
- $node['tagName'] = 'STYLE';
- $media = $node['attributes']['media'] ?? '';
- if (isset($node['attributes'])) {
- $node['attributes'] = [];
- }
- $node['attributes']['nonce'] = $this->getNonce();
- if ($media) {
- $node['attributes']['media'] = $media;
- }
- $node['childNodes'] = [
- [
- 'nodeType' => 3,
- 'id' => $id++,
- 'textContent' => $content
- ]
- ];
- }
- }
-
- if ($parseChildNodes && !empty($node['childNodes'])) {
- $this->parseMutationArrayRecursivelyForCssLinks($node['childNodes'], $cssEvents, $id);
- }
- }
- }
-
- private function parseMutationArrayRecursivelyToSanitizeNodes(&$nodes)
- {
- foreach ($nodes as &$node) {
- if (!empty($node['attributes'])) {
- // empty all the attributes with base64 and contains javascript/script/"("
- // Eg: OR
- foreach ($node['attributes'] as $nodeAttributeKey => &$nodeAttributeValue) {
- // had to double encode `\x09` as `\\\\x09` in MutationManipulatorTest.php to make json_decode work, else it was giving "syntax error" via json_last_error_msg()
- // Due to double encoding had to add entry for both "\\x09" and "\x09"
- $nodeAttributeValue = str_replace(["\\x09", "\\x0a", "\\x0d", "\\0", "\x09", "\x0a", "\x0d", "\0"], "", $nodeAttributeValue);
- $htmlDecodedAttributeValue = html_entity_decode($nodeAttributeValue, ENT_COMPAT, 'UTF-8');
- if (
- $htmlDecodedAttributeValue &&
- (
- stripos($htmlDecodedAttributeValue, 'ecmascript') !== false ||
- stripos($htmlDecodedAttributeValue, 'javascript') !== false ||
- stripos($htmlDecodedAttributeValue, 'script:') !== false ||
- stripos($htmlDecodedAttributeValue, 'jscript') !== false ||
- stripos($htmlDecodedAttributeValue, 'vbscript') !== false
- )
- ) {
- $nodeAttributeValue = '';
- } elseif (stripos($nodeAttributeValue, 'base64') !== false) {
- $base64KeywordMadeLowerCase = str_ireplace('base64', 'base64', $nodeAttributeValue);
- //For values like data:text/javascript;base64,YWxlcnQoMSk= we split the value into 2 parts
- // part1: data:text/javascript;base64
- // part2: ,YWxlcnQoMSk= we split the value into 2 parts
- // we determine the position of first comma from second part and try to decode the base64 string and check fo possible XSS
- // cannot assume the position of firstComma to be `0` since there can be string with spaces in beginning
- $attributeExploded = explode('base64', $base64KeywordMadeLowerCase);
- array_shift($attributeExploded);
- if (!empty($attributeExploded)) {
- foreach ($attributeExploded as $attributeExplodedValue) {
- $htmlDecodedAttributeString = html_entity_decode($attributeExplodedValue, ENT_COMPAT, 'UTF-8');
- $base64DecodedString = base64_decode($attributeExplodedValue);
- $base64UrlDecodedString = base64_decode(urldecode($attributeExplodedValue));
- if (
- $this->isXssString($base64DecodedString) ||
- $this->isXssString($base64UrlDecodedString) ||
- $this->isXssString($htmlDecodedAttributeString) ||
- $this->isXssString(urldecode($htmlDecodedAttributeString))
- ) {
- $nodeAttributeValue = '';
- break;
- }
- }
- }
- } elseif ($nodeAttributeValue) {
- $htmlDecodedString = html_entity_decode($nodeAttributeValue, ENT_COMPAT, 'UTF-8');
- if (
- $this->isXssString($htmlDecodedString) ||
- $this->isXssString(urldecode($htmlDecodedString))
- ) {
- $nodeAttributeValue = '';
- }
- }
- }
- }
-
- if (!empty($node['childNodes'])) {
- $this->parseMutationArrayRecursivelyToSanitizeNodes($node['childNodes']);
- }
- }
- }
-
- private function isXssString($value)
- {
- if (
- !empty($value) &&
- (
- stripos($value, 'script:') !== false ||
- stripos($value, 'javascript') !== false ||
- stripos($value, 'ecmascript') !== false ||
- stripos($value, '
-```
-
-### Polyfill differences from standard interface
-
-#### MutationObserver
-
-* Implemented using a recursive `setTimeout` (every ~30 ms) rather than using a `setImmediate` polyfill; so calls will be made less frequently and likely with more data than the standard MutationObserver. In addition, it can miss changes that occur and then are lost in the interval window.
-* Setting an observed elements html using `innerHTML` will call `childList` observer listeners with several mutations with only 1 addedNode or removed node per mutation. With the standard you would have 1 call with multiple nodes in addedNodes and removedNodes node lists.
-* With `childList` and `subtree` changes in node order (eg first element gets swapped with last) should fire a `addedNode` and `removedNode` mutation but the correct node may not always be identified.
-
-#### MutationRecord
-
-* `addedNodes` and `removedNodes` are arrays instead of `NodeList`s
-* `oldValue` is always called with attribute changes
-* `nextSibling` and `previousSibling` correctfullness is questionable (hard to know if the order of appended items). I'd suggest not relying on them anyway (my tests are extremely permissive with these attributes)
-
-### Supported MutationObserverInit properties
-
-Currently supports the following [MutationObserverInit properties](https://developer.mozilla.org/en/docs/Web/API/MutationObserver#MutationObserverInit):
-
-* **childList**: Set to truthy if mutations to target's immediate children are to be observed.
-* **subtree**: Set to truthy to do deep scans on a target's children.
-* **attributes**: Set to truthy if mutations to target's children are to be observed. As explained in #4, the `style` attribute may not be matched in ie<8.
-* **attributeFilter**: Set to an array of attribute local names (without namespace) if not all attribute mutations need to be observed.
-* **attributeOldValue**: doesn't do anything attributes are always called with old value
-* **characterData**: currently follows Mozilla's implementation in that it will only watch `textNodes` values and not, like in webkit, where setting .innerHTML will add a characterData mutation.
-
-### Performance
-
-By default, the polyfill will check observed nodes about 25 times per second (30 ms interval) for mutations. Try running [these jsperf.com tests](http://jsperf.com/mutationobserver-shim) and the JSLitmus tests in the test suite for usage performance tests. It may be worthwile to adapt `MutationObserver._period` based on UA or heuristics (todo).
-
-From my tests observing any size element without `subtree` enabled is relatively cheap. Although I've optimized the subtree check to the best of my abilities it can be costly on large trees. You can draw your own conclusions based on the JSLitmus and jsperf tests noting that you can expect the `mo` to do its check 28+ times a second (by default).
-
-Although supported, I'd recommend against watching `attributes` on the `subtree` on large structures, as the check is complex and expensive on terrible hardware like my phone :(
-
-The included minified file has been tuned for performance.
-
-### Compatibility
-
-I've tested and verified compatibility in the following browsers + [these Sauce browsers](https://saucelabs.com/u/mutationobserver)
-
-* Internet Explorer 8 (emulated), 9, 10 in win7 and win8
-* Firefox 4, 21, 24, 26 in OSX, win7 and win8
-* Opera 11.8, 12.16 in win7
-* "Internet" on Android HTC One V
-* Blackberry 6.0.16
-
-Try [running the test suite](https://rawgithub.com/megawac/MutationObserver.js/master/test/index.html) and see some simple example usage:
-
-* http://jsbin.com/suqewogone listen to images being appended dynamically
-* http://jsbin.com/bapohopuwi autoscroll an element as new content is added
-
-See http://dev.opera.com/articles/view/mutation-observers-tutorial/ for some sample usage.
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/README.md b/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/README.md
deleted file mode 100644
index a3e83b2..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-###Compiled files
-
-*Compiled by Google closure compiler in `ADVANCED_OPTIMIZATIONS`*
-
-- Original: 25 kB
-- Minified: 3.7 kB
-- Gzipped: 1.6 kB
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/mutationobserver.min.js b/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/mutationobserver.min.js
deleted file mode 100644
index 94e8949..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/dist/mutationobserver.min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// mutationobserver-shim v0.3.2 (github.com/megawac/MutationObserver.js)
-// Authors: Graeme Yeates (github.com/megawac)
-window.MutationObserver=window.MutationObserver||function(w){function v(a){this.i=[];this.m=a}function I(a){(function c(){var d=a.takeRecords();d.length&&a.m(d,a);a.h=setTimeout(c,v._period)})()}function p(a){var b={type:null,target:null,addedNodes:[],removedNodes:[],previousSibling:null,nextSibling:null,attributeName:null,attributeNamespace:null,oldValue:null},c;for(c in a)b[c]!==w&&a[c]!==w&&(b[c]=a[c]);return b}function J(a,b){var c=C(a,b);return function(d){var f=d.length,n;b.a&&3===a.nodeType&&
-a.nodeValue!==c.a&&d.push(new p({type:"characterData",target:a,oldValue:c.a}));b.b&&c.b&&A(d,a,c.b,b.f);if(b.c||b.g)n=K(d,a,c,b);if(n||d.length!==f)c=C(a,b)}}function L(a,b){return b.value}function M(a,b){return"style"!==b.name?b.value:a.style.cssText}function A(a,b,c,d){for(var f={},n=b.attributes,k,g,x=n.length;x--;)k=n[x],g=k.name,d&&d[g]===w||(D(b,k)!==c[g]&&a.push(p({type:"attributes",target:b,attributeName:g,oldValue:c[g],attributeNamespace:k.namespaceURI})),f[g]=!0);for(g in c)f[g]||a.push(p({target:b,
-type:"attributes",attributeName:g,oldValue:c[g]}))}function K(a,b,c,d){function f(b,c,f,k,y){var g=b.length-1;y=-~((g-y)/2);for(var h,l,e;e=b.pop();)h=f[e.j],l=k[e.l],d.c&&y&&Math.abs(e.j-e.l)>=g&&(a.push(p({type:"childList",target:c,addedNodes:[h],removedNodes:[h],nextSibling:h.nextSibling,previousSibling:h.previousSibling})),y--),d.b&&l.b&&A(a,h,l.b,d.f),d.a&&3===h.nodeType&&h.nodeValue!==l.a&&a.push(p({type:"characterData",target:h,oldValue:l.a})),d.g&&n(h,l)}function n(b,c){for(var g=b.childNodes,
-q=c.c,x=g.length,v=q?q.length:0,h,l,e,m,t,z=0,u=0,r=0;u
-
-This work is free. You can redistribute it and/or modify it under the
-terms of the Do What The Fuck You Want To Public License, Version 2,
-as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
-
-This program is free software. It comes without any warranty, to
-the extent permitted by applicable law. You can redistribute it
-and/or modify it under the terms of the Do What The Fuck You Want
-To Public License, Version 2, as published by Sam Hocevar. See
-http://www.wtfpl.net/ for more details.
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/package.json b/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/package.json
deleted file mode 100644
index f99c9bc..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/MutationObserver.js/package.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "name": "mutationobserver-shim",
- "short name": "mutationobserver",
- "description": "MutationObserver shim for ES3 environments",
- "version": "0.3.2",
- "keywords": [
- "DOM",
- "observer",
- "mutation observer",
- "MutationObserver"
- ],
- "authors": [
- {
- "name": "Graeme Yeates",
- "email": "github.com/megawac"
- }
- ],
- "repository": {
- "type": "git",
- "git": "git@github.com:megawac/MutationObserver.js.git",
- "url": "github.com/megawac/MutationObserver.js"
- },
- "main": "dist/mutationobserver.min.js",
- "scripts": {
- "test": "grunt test --verbose"
- },
- "files": [
- "MutationObserver.js",
- "dist/mutationobserver.min.js"
- ],
- "license": {
- "type": "WTFPL",
- "version": "v2 2004",
- "url": "http://www.wtfpl.net/"
- },
- "devDependencies": {
- "grunt": "~0.4.2",
- "grunt-bumpup": "~0.5.0",
- "grunt-closurecompiler": "0.9",
- "grunt-contrib-connect": "0.7",
- "grunt-contrib-jshint": ">= 0.8",
- "grunt-contrib-qunit": "~0.5.0",
- "grunt-file-info": "~1.0.14",
- "grunt-saucelabs": "~4.1.2",
- "grunt-tagrelease": "~0.3.1",
- "matchdep": "~0.3.0",
- "phantomjs": "1.9.x"
- }
-}
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/COPYING b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/COPYING
deleted file mode 100644
index 65ee1c1..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/COPYING
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
\ No newline at end of file
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/README.md b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/README.md
deleted file mode 100644
index 5eac04f..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# What is this? #
-
-Mutation Summary is a JavaScript library that makes observing changes to the DOM fast, easy and safe.
-
-It's built on top of (and requires) a new browser API called [DOM Mutation Observers](http://dom.spec.whatwg.org/#mutation-observers).
-
- * [Browsers which currently implement DOM Mutation Observers](DOMMutationObservers.md#browser-availability).
- * [DOM Mutation Observers API and its relationship to this library and (the deprecated) DOM Mutation Events](DOMMutationObservers.md).
-
-
-
-# Why do I need it? #
-
-Mutation Summary does five main things for you:
-
- * **It tells you how the document is different now from how it was.** As its name suggests, it summarizes what’s happened. It’s as if it takes a picture of the document when you first create it, and then again after each time it calls you back. When things have changed, it calls you with a concise description of exactly what’s different now from the last picture it took for you.
- * **It handles any and all changes, no matter how complex.** All kinds of things can happen to the DOM: values can change and but put back to what they were, large parts can be pulled out, changed, rearranged, put back. Mutation Summary can take any crazy thing you throw at it. Go ahead, tear the document to shreds, Mutation Summary won’t even blink.
- * **It lets you express what kinds of things you’re interested in.** It presents a query API that lets you tell it exactly what kinds of changes you’re interested in. This includes support for simple CSS-like selector descriptions of elements you care about.
- * **It’s fast.** The time and memory it takes is dependant on number of changes that occurred (which typically involves only a few nodes) -- not the size of your document (which is commonly thousands of nodes).
- * **It can automatically ignore changes you make during your callback.** Mutation Summary is going to call you back when changes have occurred. If you need to react to those changes by making more changes -- won’t you hear about those changes the next time it calls you back? Not unless you [ask for that](APIReference.md#configuration-options). By default, it stops watching the document immediately before it calls you back and resumes watching as soon as your callback finishes.
-
-# What is it useful for? #
-
-Lots of things, here are some examples:
-
- * **Browser extensions.** Want to make a browser extension that creates a link to your mapping application whenever an address appears in a page? You’ll need to know when those addresses appear (and disappear).
- * **Implement missing HTML capabilities.** Think building web apps is too darn hard and you know what’s missing from HTML that would make it a snap? Writing the code for the desired behavior is only half the battle--you’ll also need to know when those elements and attributes show up and what happens to them. In fact, there’s already two widely used classes of libraries which do exactly this, but don’t currently have a good way to observe changes to the DOM.
- * **UI Widget** libraries, e.g. Dojo Widgets
- * **Templating** and/or **Databinding** libraries, e.g. Angular or KnockoutJS
- * **Text Editors.** HTML Text editors often want to observe what’s being input and “fix it up” so that they can maintain a consistent WYSWIG UI.
-
-# What is this _not_ useful for? #
-
-The intent here isn't to be all things to all use-cases. Mutation Summary is not meant to:
-
- * **Use the DOM as some sort of state-transition machine.** It won't report transient states that the DOM moved through. It will only tell you what the difference is between the previous state and the present one.
- * **Observing complex selectors.** It offers support for a simple [subset of CSS selectors](APIReference.md#supported-selector-syntax). Want to observe all elements that match `“div[foo] span.bar > p:first-child”`? Unfortunately, efficiently computing that is much harder and currently outside the scope of this library.
-
-Note that both of the above use cases are possible given the data that the underlying Mutation Observers API provides -- we simply judged them to be outside the "80% use case" that we targeted with this particular library.
-
-# Where can Mutation Summary be used? #
-
-The Mutation Summary library depends on the presence of the Mutation Observer DOM API. Mutation Observers are available in
-
- * [Google Chrome](https://www.google.com/chrome)
- * [Firefox](http://www.mozilla.org/en-US/firefox/new/)
- * [Safari](http://www.apple.com/safari/)
- * [Opera](http://www.opera.com/)
- * [IE11](http://www.microsoft.com/ie)
-
-Mutation Observers is the work of the [W3C WebApps working group](http://www.w3.org/2008/webapps/). In the future it will be implemented in other browsers (we’ll keep the above list of supporting browsers as up-to-date as possible).
-
-# Great. I want to get started. What’s next? #
-
- * Check out the [tutorial](Tutorial.md) and the [API reference](APIReference.md).
-
-# Google groups discussion list #
-
- * [mutation-summary-discuss@googlegroups.com](https://groups.google.com/group/mutation-summary-discuss)
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/package.json b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/package.json
deleted file mode 100644
index 3029ee6..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/package.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "name": "mutation-summary",
- "version": "0.0.0",
- "description": "Makes observing the DOM fast and easy",
- "main": "src/mutation-summary.js",
- "directories": {
- "example": "examples",
- "test": "tests"
- },
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "repository": {
- "type": "git",
- "url": "https://code.google.com/p/mutation-summary/"
- },
- "author": "",
- "license": "Apache 2.0",
- "devDependencies": {
- "chai": "*",
- "mocha": "*"
- }
-}
-
-
-
-
-
-
-
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.js b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.js
deleted file mode 100644
index feef885..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.js
+++ /dev/null
@@ -1,1406 +0,0 @@
-// Copyright 2011 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-var __extends = (this && this.__extends) || (function () {
- var extendStatics = function (d, b) {
- extendStatics = Object.setPrototypeOf ||
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
- return extendStatics(d, b);
- };
- return function (d, b) {
- if (typeof b !== "function" && b !== null)
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
- extendStatics(d, b);
- function __() { this.constructor = d; }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
- };
-})();
-var MutationObserverCtor;
-if (typeof WebKitMutationObserver !== 'undefined')
- MutationObserverCtor = WebKitMutationObserver;
-else
- MutationObserverCtor = MutationObserver;
-if (MutationObserverCtor === undefined) {
- console.error('DOM Mutation Observers are required.');
- console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver');
- throw Error('DOM Mutation Observers are required');
-}
-var NodeMap = /** @class */ (function () {
- function NodeMap() {
- this.nodes = [];
- this.values = [];
- }
- NodeMap.prototype.isIndex = function (s) {
- return +s === s >>> 0;
- };
- NodeMap.prototype.nodeId = function (node) {
- var id = node[NodeMap.ID_PROP];
- if (!id)
- id = node[NodeMap.ID_PROP] = NodeMap.nextId_++;
- return id;
- };
- NodeMap.prototype.set = function (node, value) {
- var id = this.nodeId(node);
- this.nodes[id] = node;
- this.values[id] = value;
- };
- NodeMap.prototype.get = function (node) {
- var id = this.nodeId(node);
- return this.values[id];
- };
- NodeMap.prototype.has = function (node) {
- return this.nodeId(node) in this.nodes;
- };
- NodeMap.prototype["delete"] = function (node) {
- var id = this.nodeId(node);
- delete this.nodes[id];
- this.values[id] = undefined;
- };
- NodeMap.prototype.keys = function () {
- var nodes = [];
- for (var id in this.nodes) {
- if (!this.isIndex(id))
- continue;
- nodes.push(this.nodes[id]);
- }
- return nodes;
- };
- NodeMap.ID_PROP = '__mutation_summary_node_map_id__';
- NodeMap.nextId_ = 1;
- return NodeMap;
-}());
-/**
- * var reachableMatchableProduct = [
- * // STAYED_OUT, ENTERED, STAYED_IN, EXITED
- * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT
- * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED
- * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN
- * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED
- * ];
- */
-var Movement;
-(function (Movement) {
- Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT";
- Movement[Movement["ENTERED"] = 1] = "ENTERED";
- Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN";
- Movement[Movement["REPARENTED"] = 3] = "REPARENTED";
- Movement[Movement["REORDERED"] = 4] = "REORDERED";
- Movement[Movement["EXITED"] = 5] = "EXITED";
-})(Movement || (Movement = {}));
-function enteredOrExited(changeType) {
- return changeType === Movement.ENTERED || changeType === Movement.EXITED;
-}
-var NodeChange = /** @class */ (function () {
- function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) {
- if (childList === void 0) { childList = false; }
- if (attributes === void 0) { attributes = false; }
- if (characterData === void 0) { characterData = false; }
- if (oldParentNode === void 0) { oldParentNode = null; }
- if (added === void 0) { added = false; }
- if (attributeOldValues === void 0) { attributeOldValues = null; }
- if (characterDataOldValue === void 0) { characterDataOldValue = null; }
- this.node = node;
- this.childList = childList;
- this.attributes = attributes;
- this.characterData = characterData;
- this.oldParentNode = oldParentNode;
- this.added = added;
- this.attributeOldValues = attributeOldValues;
- this.characterDataOldValue = characterDataOldValue;
- this.isCaseInsensitive =
- this.node.nodeType === Node.ELEMENT_NODE &&
- this.node instanceof HTMLElement &&
- this.node.ownerDocument instanceof HTMLDocument;
- }
- NodeChange.prototype.getAttributeOldValue = function (name) {
- if (!this.attributeOldValues)
- return undefined;
- if (this.isCaseInsensitive)
- name = name.toLowerCase();
- return this.attributeOldValues[name];
- };
- NodeChange.prototype.getAttributeNamesMutated = function () {
- var names = [];
- if (!this.attributeOldValues)
- return names;
- for (var name in this.attributeOldValues) {
- names.push(name);
- }
- return names;
- };
- NodeChange.prototype.attributeMutated = function (name, oldValue) {
- this.attributes = true;
- this.attributeOldValues = this.attributeOldValues || {};
- if (name in this.attributeOldValues)
- return;
- this.attributeOldValues[name] = oldValue;
- };
- NodeChange.prototype.characterDataMutated = function (oldValue) {
- if (this.characterData)
- return;
- this.characterData = true;
- this.characterDataOldValue = oldValue;
- };
- // Note: is it possible to receive a removal followed by a removal. This
- // can occur if the removed node is added to an non-observed node, that
- // node is added to the observed area, and then the node removed from
- // it.
- NodeChange.prototype.removedFromParent = function (parent) {
- this.childList = true;
- if (this.added || this.oldParentNode)
- this.added = false;
- else
- this.oldParentNode = parent;
- };
- NodeChange.prototype.insertedIntoParent = function () {
- this.childList = true;
- this.added = true;
- };
- // An node's oldParent is
- // -its present parent, if its parentNode was not changed.
- // -null if the first thing that happened to it was an add.
- // -the node it was removed from if the first thing that happened to it
- // was a remove.
- NodeChange.prototype.getOldParent = function () {
- if (this.childList) {
- if (this.oldParentNode)
- return this.oldParentNode;
- if (this.added)
- return null;
- }
- return this.node.parentNode;
- };
- return NodeChange;
-}());
-var ChildListChange = /** @class */ (function () {
- function ChildListChange() {
- this.added = new NodeMap();
- this.removed = new NodeMap();
- this.maybeMoved = new NodeMap();
- this.oldPrevious = new NodeMap();
- this.moved = undefined;
- }
- return ChildListChange;
-}());
-var TreeChanges = /** @class */ (function (_super) {
- __extends(TreeChanges, _super);
- function TreeChanges(rootNode, mutations) {
- var _this = _super.call(this) || this;
- _this.rootNode = rootNode;
- _this.reachableCache = undefined;
- _this.wasReachableCache = undefined;
- _this.anyParentsChanged = false;
- _this.anyAttributesChanged = false;
- _this.anyCharacterDataChanged = false;
- for (var m = 0; m < mutations.length; m++) {
- var mutation = mutations[m];
- switch (mutation.type) {
- case 'childList':
- _this.anyParentsChanged = true;
- for (var i = 0; i < mutation.removedNodes.length; i++) {
- var node = mutation.removedNodes[i];
- _this.getChange(node).removedFromParent(mutation.target);
- }
- for (var i = 0; i < mutation.addedNodes.length; i++) {
- var node = mutation.addedNodes[i];
- _this.getChange(node).insertedIntoParent();
- }
- break;
- case 'attributes':
- _this.anyAttributesChanged = true;
- var change = _this.getChange(mutation.target);
- change.attributeMutated(mutation.attributeName, mutation.oldValue);
- break;
- case 'characterData':
- _this.anyCharacterDataChanged = true;
- var change = _this.getChange(mutation.target);
- change.characterDataMutated(mutation.oldValue);
- break;
- }
- }
- return _this;
- }
- TreeChanges.prototype.getChange = function (node) {
- var change = this.get(node);
- if (!change) {
- change = new NodeChange(node);
- this.set(node, change);
- }
- return change;
- };
- TreeChanges.prototype.getOldParent = function (node) {
- var change = this.get(node);
- return change ? change.getOldParent() : node.parentNode;
- };
- TreeChanges.prototype.getIsReachable = function (node) {
- if (node === this.rootNode)
- return true;
- if (!node)
- return false;
- this.reachableCache = this.reachableCache || new NodeMap();
- var isReachable = this.reachableCache.get(node);
- if (isReachable === undefined) {
- isReachable = this.getIsReachable(node.parentNode);
- this.reachableCache.set(node, isReachable);
- }
- return isReachable;
- };
- // A node wasReachable if its oldParent wasReachable.
- TreeChanges.prototype.getWasReachable = function (node) {
- if (node === this.rootNode)
- return true;
- if (!node)
- return false;
- this.wasReachableCache = this.wasReachableCache || new NodeMap();
- var wasReachable = this.wasReachableCache.get(node);
- if (wasReachable === undefined) {
- wasReachable = this.getWasReachable(this.getOldParent(node));
- this.wasReachableCache.set(node, wasReachable);
- }
- return wasReachable;
- };
- TreeChanges.prototype.reachabilityChange = function (node) {
- if (this.getIsReachable(node)) {
- return this.getWasReachable(node) ?
- Movement.STAYED_IN : Movement.ENTERED;
- }
- return this.getWasReachable(node) ?
- Movement.EXITED : Movement.STAYED_OUT;
- };
- return TreeChanges;
-}(NodeMap));
-var MutationProjection = /** @class */ (function () {
- // TOOD(any)
- function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) {
- this.rootNode = rootNode;
- this.mutations = mutations;
- this.selectors = selectors;
- this.calcReordered = calcReordered;
- this.calcOldPreviousSibling = calcOldPreviousSibling;
- this.treeChanges = new TreeChanges(rootNode, mutations);
- this.entered = [];
- this.exited = [];
- this.stayedIn = new NodeMap();
- this.visited = new NodeMap();
- this.childListChangeMap = undefined;
- this.characterDataOnly = undefined;
- this.matchCache = undefined;
- this.processMutations();
- }
- MutationProjection.prototype.processMutations = function () {
- if (!this.treeChanges.anyParentsChanged &&
- !this.treeChanges.anyAttributesChanged)
- return;
- var changedNodes = this.treeChanges.keys();
- for (var i = 0; i < changedNodes.length; i++) {
- this.visitNode(changedNodes[i], undefined);
- }
- };
- MutationProjection.prototype.visitNode = function (node, parentReachable) {
- if (this.visited.has(node))
- return;
- this.visited.set(node, true);
- var change = this.treeChanges.get(node);
- var reachable = parentReachable;
- // node inherits its parent's reachability change unless
- // its parentNode was mutated.
- if ((change && change.childList) || reachable == undefined)
- reachable = this.treeChanges.reachabilityChange(node);
- if (reachable === Movement.STAYED_OUT)
- return;
- // Cache match results for sub-patterns.
- this.matchabilityChange(node);
- if (reachable === Movement.ENTERED) {
- this.entered.push(node);
- }
- else if (reachable === Movement.EXITED) {
- this.exited.push(node);
- this.ensureHasOldPreviousSiblingIfNeeded(node);
- }
- else if (reachable === Movement.STAYED_IN) {
- var movement = Movement.STAYED_IN;
- if (change && change.childList) {
- if (change.oldParentNode !== node.parentNode) {
- movement = Movement.REPARENTED;
- this.ensureHasOldPreviousSiblingIfNeeded(node);
- }
- else if (this.calcReordered && this.wasReordered(node)) {
- movement = Movement.REORDERED;
- }
- }
- this.stayedIn.set(node, movement);
- }
- if (reachable === Movement.STAYED_IN)
- return;
- // reachable === ENTERED || reachable === EXITED.
- for (var child = node.firstChild; child; child = child.nextSibling) {
- this.visitNode(child, reachable);
- }
- };
- MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) {
- if (!this.calcOldPreviousSibling)
- return;
- this.processChildlistChanges();
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
- var change = this.childListChangeMap.get(parentNode);
- if (!change) {
- change = new ChildListChange();
- this.childListChangeMap.set(parentNode, change);
- }
- if (!change.oldPrevious.has(node)) {
- change.oldPrevious.set(node, node.previousSibling);
- }
- };
- MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) {
- this.selectors = selectors;
- this.characterDataOnly = characterDataOnly;
- for (var i = 0; i < this.entered.length; i++) {
- var node = this.entered[i];
- var matchable = this.matchabilityChange(node);
- if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN)
- summary.added.push(node);
- }
- var stayedInNodes = this.stayedIn.keys();
- for (var i = 0; i < stayedInNodes.length; i++) {
- var node = stayedInNodes[i];
- var matchable = this.matchabilityChange(node);
- if (matchable === Movement.ENTERED) {
- summary.added.push(node);
- }
- else if (matchable === Movement.EXITED) {
- summary.removed.push(node);
- }
- else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
- var movement = this.stayedIn.get(node);
- if (summary.reparented && movement === Movement.REPARENTED)
- summary.reparented.push(node);
- else if (summary.reordered && movement === Movement.REORDERED)
- summary.reordered.push(node);
- }
- }
- for (var i = 0; i < this.exited.length; i++) {
- var node = this.exited[i];
- var matchable = this.matchabilityChange(node);
- if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN)
- summary.removed.push(node);
- }
- };
- MutationProjection.prototype.getOldParentNode = function (node) {
- var change = this.treeChanges.get(node);
- if (change && change.childList)
- return change.oldParentNode ? change.oldParentNode : null;
- var reachabilityChange = this.treeChanges.reachabilityChange(node);
- if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED)
- throw Error('getOldParentNode requested on invalid node.');
- return node.parentNode;
- };
- MutationProjection.prototype.getOldPreviousSibling = function (node) {
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
- var change = this.childListChangeMap.get(parentNode);
- if (!change)
- throw Error('getOldPreviousSibling requested on invalid node.');
- return change.oldPrevious.get(node);
- };
- MutationProjection.prototype.getOldAttribute = function (element, attrName) {
- var change = this.treeChanges.get(element);
- if (!change || !change.attributes)
- throw Error('getOldAttribute requested on invalid node.');
- var value = change.getAttributeOldValue(attrName);
- if (value === undefined)
- throw Error('getOldAttribute requested for unchanged attribute name.');
- return value;
- };
- MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) {
- if (!this.treeChanges.anyAttributesChanged)
- return {}; // No attributes mutations occurred.
- var attributeFilter;
- var caseInsensitiveFilter;
- if (includeAttributes) {
- attributeFilter = {};
- caseInsensitiveFilter = {};
- for (var i = 0; i < includeAttributes.length; i++) {
- var attrName = includeAttributes[i];
- attributeFilter[attrName] = true;
- caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
- }
- }
- var result = {};
- var nodes = this.treeChanges.keys();
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- var change = this.treeChanges.get(node);
- if (!change.attributes)
- continue;
- if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
- Movement.STAYED_IN !== this.matchabilityChange(node)) {
- continue;
- }
- var element = node;
- var changedAttrNames = change.getAttributeNamesMutated();
- for (var j = 0; j < changedAttrNames.length; j++) {
- var attrName = changedAttrNames[j];
- if (attributeFilter &&
- !attributeFilter[attrName] &&
- !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) {
- continue;
- }
- var oldValue = change.getAttributeOldValue(attrName);
- if (oldValue === element.getAttribute(attrName))
- continue;
- if (caseInsensitiveFilter && change.isCaseInsensitive)
- attrName = caseInsensitiveFilter[attrName];
- result[attrName] = result[attrName] || [];
- result[attrName].push(element);
- }
- }
- return result;
- };
- MutationProjection.prototype.getOldCharacterData = function (node) {
- var change = this.treeChanges.get(node);
- if (!change || !change.characterData)
- throw Error('getOldCharacterData requested on invalid node.');
- return change.characterDataOldValue;
- };
- MutationProjection.prototype.getCharacterDataChanged = function () {
- if (!this.treeChanges.anyCharacterDataChanged)
- return []; // No characterData mutations occurred.
- var nodes = this.treeChanges.keys();
- var result = [];
- for (var i = 0; i < nodes.length; i++) {
- var target = nodes[i];
- if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target))
- continue;
- var change = this.treeChanges.get(target);
- if (!change.characterData ||
- target.textContent == change.characterDataOldValue)
- continue;
- result.push(target);
- }
- return result;
- };
- MutationProjection.prototype.computeMatchabilityChange = function (selector, el) {
- if (!this.matchCache)
- this.matchCache = [];
- if (!this.matchCache[selector.uid])
- this.matchCache[selector.uid] = new NodeMap();
- var cache = this.matchCache[selector.uid];
- var result = cache.get(el);
- if (result === undefined) {
- result = selector.matchabilityChange(el, this.treeChanges.get(el));
- cache.set(el, result);
- }
- return result;
- };
- MutationProjection.prototype.matchabilityChange = function (node) {
- var _this = this;
- // TODO(rafaelw): Include PI, CDATA?
- // Only include text nodes.
- if (this.characterDataOnly) {
- switch (node.nodeType) {
- case Node.COMMENT_NODE:
- case Node.TEXT_NODE:
- return Movement.STAYED_IN;
- default:
- return Movement.STAYED_OUT;
- }
- }
- // No element filter. Include all nodes.
- if (!this.selectors)
- return Movement.STAYED_IN;
- // Element filter. Exclude non-elements.
- if (node.nodeType !== Node.ELEMENT_NODE)
- return Movement.STAYED_OUT;
- var el = node;
- var matchChanges = this.selectors.map(function (selector) {
- return _this.computeMatchabilityChange(selector, el);
- });
- var accum = Movement.STAYED_OUT;
- var i = 0;
- while (accum !== Movement.STAYED_IN && i < matchChanges.length) {
- switch (matchChanges[i]) {
- case Movement.STAYED_IN:
- accum = Movement.STAYED_IN;
- break;
- case Movement.ENTERED:
- if (accum === Movement.EXITED)
- accum = Movement.STAYED_IN;
- else
- accum = Movement.ENTERED;
- break;
- case Movement.EXITED:
- if (accum === Movement.ENTERED)
- accum = Movement.STAYED_IN;
- else
- accum = Movement.EXITED;
- break;
- }
- i++;
- }
- return accum;
- };
- MutationProjection.prototype.getChildlistChange = function (el) {
- var change = this.childListChangeMap.get(el);
- if (!change) {
- change = new ChildListChange();
- this.childListChangeMap.set(el, change);
- }
- return change;
- };
- MutationProjection.prototype.processChildlistChanges = function () {
- if (this.childListChangeMap)
- return;
- this.childListChangeMap = new NodeMap();
- for (var i = 0; i < this.mutations.length; i++) {
- var mutation = this.mutations[i];
- if (mutation.type != 'childList')
- continue;
- if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN &&
- !this.calcOldPreviousSibling)
- continue;
- var change = this.getChildlistChange(mutation.target);
- var oldPrevious = mutation.previousSibling;
- function recordOldPrevious(node, previous) {
- if (!node ||
- change.oldPrevious.has(node) ||
- change.added.has(node) ||
- change.maybeMoved.has(node))
- return;
- if (previous &&
- (change.added.has(previous) ||
- change.maybeMoved.has(previous)))
- return;
- change.oldPrevious.set(node, previous);
- }
- for (var j = 0; j < mutation.removedNodes.length; j++) {
- var node = mutation.removedNodes[j];
- recordOldPrevious(node, oldPrevious);
- if (change.added.has(node)) {
- change.added["delete"](node);
- }
- else {
- change.removed.set(node, true);
- change.maybeMoved["delete"](node);
- }
- oldPrevious = node;
- }
- recordOldPrevious(mutation.nextSibling, oldPrevious);
- for (var j = 0; j < mutation.addedNodes.length; j++) {
- var node = mutation.addedNodes[j];
- if (change.removed.has(node)) {
- change.removed["delete"](node);
- change.maybeMoved.set(node, true);
- }
- else {
- change.added.set(node, true);
- }
- }
- }
- };
- MutationProjection.prototype.wasReordered = function (node) {
- if (!this.treeChanges.anyParentsChanged)
- return false;
- this.processChildlistChanges();
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
- var change = this.childListChangeMap.get(parentNode);
- if (!change)
- return false;
- if (change.moved)
- return change.moved.get(node);
- change.moved = new NodeMap();
- var pendingMoveDecision = new NodeMap();
- function isMoved(node) {
- if (!node)
- return false;
- if (!change.maybeMoved.has(node))
- return false;
- var didMove = change.moved.get(node);
- if (didMove !== undefined)
- return didMove;
- if (pendingMoveDecision.has(node)) {
- didMove = true;
- }
- else {
- pendingMoveDecision.set(node, true);
- didMove = getPrevious(node) !== getOldPrevious(node);
- }
- if (pendingMoveDecision.has(node)) {
- pendingMoveDecision["delete"](node);
- change.moved.set(node, didMove);
- }
- else {
- didMove = change.moved.get(node);
- }
- return didMove;
- }
- var oldPreviousCache = new NodeMap();
- function getOldPrevious(node) {
- var oldPrevious = oldPreviousCache.get(node);
- if (oldPrevious !== undefined)
- return oldPrevious;
- oldPrevious = change.oldPrevious.get(node);
- while (oldPrevious &&
- (change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
- oldPrevious = getOldPrevious(oldPrevious);
- }
- if (oldPrevious === undefined)
- oldPrevious = node.previousSibling;
- oldPreviousCache.set(node, oldPrevious);
- return oldPrevious;
- }
- var previousCache = new NodeMap();
- function getPrevious(node) {
- if (previousCache.has(node))
- return previousCache.get(node);
- var previous = node.previousSibling;
- while (previous && (change.added.has(previous) || isMoved(previous)))
- previous = previous.previousSibling;
- previousCache.set(node, previous);
- return previous;
- }
- change.maybeMoved.keys().forEach(isMoved);
- return change.moved.get(node);
- };
- return MutationProjection;
-}());
-var Summary = /** @class */ (function () {
- function Summary(projection, query) {
- var _this = this;
- this.projection = projection;
- this.added = [];
- this.removed = [];
- this.reparented = query.all || query.element || query.characterData ? [] : undefined;
- this.reordered = query.all ? [] : undefined;
- projection.getChanged(this, query.elementFilter, query.characterData);
- if (query.all || query.attribute || query.attributeList) {
- var filter = query.attribute ? [query.attribute] : query.attributeList;
- var attributeChanged = projection.attributeChangedNodes(filter);
- if (query.attribute) {
- this.valueChanged = attributeChanged[query.attribute] || [];
- }
- else {
- this.attributeChanged = attributeChanged;
- if (query.attributeList) {
- query.attributeList.forEach(function (attrName) {
- if (!_this.attributeChanged.hasOwnProperty(attrName))
- _this.attributeChanged[attrName] = [];
- });
- }
- }
- }
- if (query.all || query.characterData) {
- var characterDataChanged = projection.getCharacterDataChanged();
- if (query.characterData)
- this.valueChanged = characterDataChanged;
- else
- this.characterDataChanged = characterDataChanged;
- }
- if (this.reordered)
- this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
- }
- Summary.prototype.getOldParentNode = function (node) {
- return this.projection.getOldParentNode(node);
- };
- Summary.prototype.getOldAttribute = function (node, name) {
- return this.projection.getOldAttribute(node, name);
- };
- Summary.prototype.getOldCharacterData = function (node) {
- return this.projection.getOldCharacterData(node);
- };
- Summary.prototype.getOldPreviousSibling = function (node) {
- return this.projection.getOldPreviousSibling(node);
- };
- return Summary;
-}());
-// TODO(rafaelw): Allow ':' and '.' as valid name characters.
-var validNameInitialChar = /[a-zA-Z_]+/;
-var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
-// TODO(rafaelw): Consider allowing backslash in the attrValue.
-// TODO(rafaelw): There's got a to be way to represent this state machine
-// more compactly???
-function escapeQuotes(value) {
- return '"' + value.replace(/"/, '\\\"') + '"';
-}
-var Qualifier = /** @class */ (function () {
- function Qualifier() {
- }
- Qualifier.prototype.matches = function (oldValue) {
- if (oldValue === null)
- return false;
- if (this.attrValue === undefined)
- return true;
- if (!this.contains)
- return this.attrValue == oldValue;
- var tokens = oldValue.split(' ');
- for (var i = 0; i < tokens.length; i++) {
- if (this.attrValue === tokens[i])
- return true;
- }
- return false;
- };
- Qualifier.prototype.toString = function () {
- if (this.attrName === 'class' && this.contains)
- return '.' + this.attrValue;
- if (this.attrName === 'id' && !this.contains)
- return '#' + this.attrValue;
- if (this.contains)
- return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
- if ('attrValue' in this)
- return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
- return '[' + this.attrName + ']';
- };
- return Qualifier;
-}());
-var Selector = /** @class */ (function () {
- function Selector() {
- this.uid = Selector.nextUid++;
- this.qualifiers = [];
- }
- Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", {
- get: function () {
- return this.tagName.toUpperCase();
- },
- enumerable: false,
- configurable: true
- });
- Object.defineProperty(Selector.prototype, "selectorString", {
- get: function () {
- return this.tagName + this.qualifiers.join('');
- },
- enumerable: false,
- configurable: true
- });
- Selector.prototype.isMatching = function (el) {
- return el[Selector.matchesSelector](this.selectorString);
- };
- Selector.prototype.wasMatching = function (el, change, isMatching) {
- if (!change || !change.attributes)
- return isMatching;
- var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
- if (tagName !== '*' && tagName !== el.tagName)
- return false;
- var attributeOldValues = [];
- var anyChanged = false;
- for (var i = 0; i < this.qualifiers.length; i++) {
- var qualifier = this.qualifiers[i];
- var oldValue = change.getAttributeOldValue(qualifier.attrName);
- attributeOldValues.push(oldValue);
- anyChanged = anyChanged || (oldValue !== undefined);
- }
- if (!anyChanged)
- return isMatching;
- for (var i = 0; i < this.qualifiers.length; i++) {
- var qualifier = this.qualifiers[i];
- var oldValue = attributeOldValues[i];
- if (oldValue === undefined)
- oldValue = el.getAttribute(qualifier.attrName);
- if (!qualifier.matches(oldValue))
- return false;
- }
- return true;
- };
- Selector.prototype.matchabilityChange = function (el, change) {
- var isMatching = this.isMatching(el);
- if (isMatching)
- return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED;
- else
- return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT;
- };
- Selector.parseSelectors = function (input) {
- var selectors = [];
- var currentSelector;
- var currentQualifier;
- function newSelector() {
- if (currentSelector) {
- if (currentQualifier) {
- currentSelector.qualifiers.push(currentQualifier);
- currentQualifier = undefined;
- }
- selectors.push(currentSelector);
- }
- currentSelector = new Selector();
- }
- function newQualifier() {
- if (currentQualifier)
- currentSelector.qualifiers.push(currentQualifier);
- currentQualifier = new Qualifier();
- }
- var WHITESPACE = /\s/;
- var valueQuoteChar;
- var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
- var SELECTOR = 1;
- var TAG_NAME = 2;
- var QUALIFIER = 3;
- var QUALIFIER_NAME_FIRST_CHAR = 4;
- var QUALIFIER_NAME = 5;
- var ATTR_NAME_FIRST_CHAR = 6;
- var ATTR_NAME = 7;
- var EQUIV_OR_ATTR_QUAL_END = 8;
- var EQUAL = 9;
- var ATTR_QUAL_END = 10;
- var VALUE_FIRST_CHAR = 11;
- var VALUE = 12;
- var QUOTED_VALUE = 13;
- var SELECTOR_SEPARATOR = 14;
- var state = SELECTOR;
- var i = 0;
- while (i < input.length) {
- var c = input[i++];
- switch (state) {
- case SELECTOR:
- if (c.match(validNameInitialChar)) {
- newSelector();
- currentSelector.tagName = c;
- state = TAG_NAME;
- break;
- }
- if (c == '*') {
- newSelector();
- currentSelector.tagName = '*';
- state = QUALIFIER;
- break;
- }
- if (c == '.') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
- if (c.match(WHITESPACE))
- break;
- throw Error(SYNTAX_ERROR);
- case TAG_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentSelector.tagName += c;
- break;
- }
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
- if (c == ',') {
- state = SELECTOR;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case QUALIFIER:
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
- if (c == ',') {
- state = SELECTOR;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case QUALIFIER_NAME_FIRST_CHAR:
- if (c.match(validNameInitialChar)) {
- currentQualifier.attrValue = c;
- state = QUALIFIER_NAME;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case QUALIFIER_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentQualifier.attrValue += c;
- break;
- }
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
- if (c == ',') {
- state = SELECTOR;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case ATTR_NAME_FIRST_CHAR:
- if (c.match(validNameInitialChar)) {
- currentQualifier.attrName = c;
- state = ATTR_NAME;
- break;
- }
- if (c.match(WHITESPACE))
- break;
- throw Error(SYNTAX_ERROR);
- case ATTR_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentQualifier.attrName += c;
- break;
- }
- if (c.match(WHITESPACE)) {
- state = EQUIV_OR_ATTR_QUAL_END;
- break;
- }
- if (c == '~') {
- currentQualifier.contains = true;
- state = EQUAL;
- break;
- }
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR;
- break;
- }
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case EQUIV_OR_ATTR_QUAL_END:
- if (c == '~') {
- currentQualifier.contains = true;
- state = EQUAL;
- break;
- }
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR;
- break;
- }
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
- if (c.match(WHITESPACE))
- break;
- throw Error(SYNTAX_ERROR);
- case EQUAL:
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR;
- break;
- }
- throw Error(SYNTAX_ERROR);
- case ATTR_QUAL_END:
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
- if (c.match(WHITESPACE))
- break;
- throw Error(SYNTAX_ERROR);
- case VALUE_FIRST_CHAR:
- if (c.match(WHITESPACE))
- break;
- if (c == '"' || c == "'") {
- valueQuoteChar = c;
- state = QUOTED_VALUE;
- break;
- }
- currentQualifier.attrValue += c;
- state = VALUE;
- break;
- case VALUE:
- if (c.match(WHITESPACE)) {
- state = ATTR_QUAL_END;
- break;
- }
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
- if (c == "'" || c == '"')
- throw Error(SYNTAX_ERROR);
- currentQualifier.attrValue += c;
- break;
- case QUOTED_VALUE:
- if (c == valueQuoteChar) {
- state = ATTR_QUAL_END;
- break;
- }
- currentQualifier.attrValue += c;
- break;
- case SELECTOR_SEPARATOR:
- if (c.match(WHITESPACE))
- break;
- if (c == ',') {
- state = SELECTOR;
- break;
- }
- throw Error(SYNTAX_ERROR);
- }
- }
- switch (state) {
- case SELECTOR:
- case TAG_NAME:
- case QUALIFIER:
- case QUALIFIER_NAME:
- case SELECTOR_SEPARATOR:
- // Valid end states.
- newSelector();
- break;
- default:
- throw Error(SYNTAX_ERROR);
- }
- if (!selectors.length)
- throw Error(SYNTAX_ERROR);
- return selectors;
- };
- Selector.nextUid = 1;
- Selector.matchesSelector = (function () {
- var element = document.createElement('div');
- if (typeof element['webkitMatchesSelector'] === 'function')
- return 'webkitMatchesSelector';
- if (typeof element['mozMatchesSelector'] === 'function')
- return 'mozMatchesSelector';
- if (typeof element['msMatchesSelector'] === 'function')
- return 'msMatchesSelector';
- return 'matchesSelector';
- })();
- return Selector;
-}());
-var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/;
-function validateAttribute(attribute) {
- if (typeof attribute != 'string')
- throw Error('Invalid request opion. attribute must be a non-zero length string.');
- attribute = attribute.trim();
- if (!attribute)
- throw Error('Invalid request opion. attribute must be a non-zero length string.');
- if (!attribute.match(attributeFilterPattern))
- throw Error('Invalid request option. invalid attribute name: ' + attribute);
- return attribute;
-}
-function validateElementAttributes(attribs) {
- if (!attribs.trim().length)
- throw Error('Invalid request option: elementAttributes must contain at least one attribute.');
- var lowerAttributes = {};
- var attributes = {};
- var tokens = attribs.split(/\s+/);
- for (var i = 0; i < tokens.length; i++) {
- var name = tokens[i];
- if (!name)
- continue;
- var name = validateAttribute(name);
- var nameLower = name.toLowerCase();
- if (lowerAttributes[nameLower])
- throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.');
- attributes[name] = true;
- lowerAttributes[nameLower] = true;
- }
- return Object.keys(attributes);
-}
-function elementFilterAttributes(selectors) {
- var attributes = {};
- selectors.forEach(function (selector) {
- selector.qualifiers.forEach(function (qualifier) {
- attributes[qualifier.attrName] = true;
- });
- });
- return Object.keys(attributes);
-}
-var MutationSummary = /** @class */ (function () {
- function MutationSummary(opts) {
- var _this = this;
- this.connected = false;
- this.options = MutationSummary.validateOptions(opts);
- this.observerOptions = MutationSummary.createObserverOptions(this.options.queries);
- this.root = this.options.rootNode;
- this.callback = this.options.callback;
- this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) {
- return query.elementFilter ? query.elementFilter : [];
- }));
- if (!this.elementFilter.length)
- this.elementFilter = undefined;
- this.calcReordered = this.options.queries.some(function (query) {
- return query.all;
- });
- this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this.
- if (MutationSummary.createQueryValidator) {
- this.queryValidators = this.options.queries.map(function (query) {
- return MutationSummary.createQueryValidator(_this.root, query);
- });
- }
- this.observer = new MutationObserverCtor(function (mutations) {
- _this.observerCallback(mutations);
- });
- this.reconnect();
- }
- MutationSummary.createObserverOptions = function (queries) {
- var observerOptions = {
- childList: true,
- subtree: true
- };
- var attributeFilter;
- function observeAttributes(attributes) {
- if (observerOptions.attributes && !attributeFilter)
- return; // already observing all.
- observerOptions.attributes = true;
- observerOptions.attributeOldValue = true;
- if (!attributes) {
- // observe all.
- attributeFilter = undefined;
- return;
- }
- // add to observed.
- attributeFilter = attributeFilter || {};
- attributes.forEach(function (attribute) {
- attributeFilter[attribute] = true;
- attributeFilter[attribute.toLowerCase()] = true;
- });
- }
- queries.forEach(function (query) {
- if (query.characterData) {
- observerOptions.characterData = true;
- observerOptions.characterDataOldValue = true;
- return;
- }
- if (query.all) {
- observeAttributes();
- observerOptions.characterData = true;
- observerOptions.characterDataOldValue = true;
- return;
- }
- if (query.attribute) {
- observeAttributes([query.attribute.trim()]);
- return;
- }
- var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []);
- if (attributes.length)
- observeAttributes(attributes);
- });
- if (attributeFilter)
- observerOptions.attributeFilter = Object.keys(attributeFilter);
- return observerOptions;
- };
- MutationSummary.validateOptions = function (options) {
- for (var prop in options) {
- if (!(prop in MutationSummary.optionKeys))
- throw Error('Invalid option: ' + prop);
- }
- if (typeof options.callback !== 'function')
- throw Error('Invalid options: callback is required and must be a function');
- if (!options.queries || !options.queries.length)
- throw Error('Invalid options: queries must contain at least one query request object.');
- var opts = {
- callback: options.callback,
- rootNode: options.rootNode || document,
- observeOwnChanges: !!options.observeOwnChanges,
- oldPreviousSibling: !!options.oldPreviousSibling,
- queries: []
- };
- for (var i = 0; i < options.queries.length; i++) {
- var request = options.queries[i];
- // all
- if (request.all) {
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. all has no options.');
- opts.queries.push({ all: true });
- continue;
- }
- // attribute
- if ('attribute' in request) {
- var query = {
- attribute: validateAttribute(request.attribute)
- };
- query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']');
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. attribute has no options.');
- opts.queries.push(query);
- continue;
- }
- // element
- if ('element' in request) {
- var requestOptionCount = Object.keys(request).length;
- var query = {
- element: request.element,
- elementFilter: Selector.parseSelectors(request.element)
- };
- if (request.hasOwnProperty('elementAttributes')) {
- query.attributeList = validateElementAttributes(request.elementAttributes);
- requestOptionCount--;
- }
- if (requestOptionCount > 1)
- throw Error('Invalid request option. element only allows elementAttributes option.');
- opts.queries.push(query);
- continue;
- }
- // characterData
- if (request.characterData) {
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. characterData has no options.');
- opts.queries.push({ characterData: true });
- continue;
- }
- throw Error('Invalid request option. Unknown query request.');
- }
- return opts;
- };
- MutationSummary.prototype.createSummaries = function (mutations) {
- if (!mutations || !mutations.length)
- return [];
- var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling);
- var summaries = [];
- for (var i = 0; i < this.options.queries.length; i++) {
- summaries.push(new Summary(projection, this.options.queries[i]));
- }
- return summaries;
- };
- MutationSummary.prototype.checkpointQueryValidators = function () {
- this.queryValidators.forEach(function (validator) {
- if (validator)
- validator.recordPreviousState();
- });
- };
- MutationSummary.prototype.runQueryValidators = function (summaries) {
- this.queryValidators.forEach(function (validator, index) {
- if (validator)
- validator.validate(summaries[index]);
- });
- };
- MutationSummary.prototype.changesToReport = function (summaries) {
- return summaries.some(function (summary) {
- var summaryProps = ['added', 'removed', 'reordered', 'reparented',
- 'valueChanged', 'characterDataChanged'];
- if (summaryProps.some(function (prop) { return summary[prop] && summary[prop].length; }))
- return true;
- if (summary.attributeChanged) {
- var attrNames = Object.keys(summary.attributeChanged);
- var attrsChanged = attrNames.some(function (attrName) {
- return !!summary.attributeChanged[attrName].length;
- });
- if (attrsChanged)
- return true;
- }
- return false;
- });
- };
- MutationSummary.prototype.observerCallback = function (mutations) {
- if (!this.options.observeOwnChanges)
- this.observer.disconnect();
- var summaries = this.createSummaries(mutations);
- this.runQueryValidators(summaries);
- if (this.options.observeOwnChanges)
- this.checkpointQueryValidators();
- if (this.changesToReport(summaries))
- this.callback(summaries);
- // disconnect() may have been called during the callback.
- if (!this.options.observeOwnChanges && this.connected) {
- this.checkpointQueryValidators();
- this.observer.observe(this.root, this.observerOptions);
- }
- };
- MutationSummary.prototype.reconnect = function () {
- if (this.connected)
- throw Error('Already connected');
- this.observer.observe(this.root, this.observerOptions);
- this.connected = true;
- this.checkpointQueryValidators();
- };
- MutationSummary.prototype.takeSummaries = function () {
- if (!this.connected)
- throw Error('Not connected');
- var summaries = this.createSummaries(this.observer.takeRecords());
- return this.changesToReport(summaries) ? summaries : undefined;
- };
- MutationSummary.prototype.disconnect = function () {
- var summaries = this.takeSummaries();
- this.observer.disconnect();
- this.connected = false;
- return summaries;
- };
- MutationSummary.NodeMap = NodeMap; // exposed for use in TreeMirror.
- MutationSummary.parseElementFilter = Selector.parseSelectors; // exposed for testing.
- MutationSummary.optionKeys = {
- 'callback': true,
- 'queries': true,
- 'rootNode': true,
- 'oldPreviousSibling': true,
- 'observeOwnChanges': true
- };
- return MutationSummary;
-}());
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.ts b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.ts
deleted file mode 100644
index ee3a268..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/src/mutation-summary.ts
+++ /dev/null
@@ -1,1750 +0,0 @@
-// Copyright 2011 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-var MutationObserverCtor;
-if (typeof WebKitMutationObserver !== 'undefined')
- MutationObserverCtor = WebKitMutationObserver;
-else
- MutationObserverCtor = MutationObserver;
-
-if (MutationObserverCtor === undefined) {
- console.error('DOM Mutation Observers are required.');
- console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver');
- throw Error('DOM Mutation Observers are required');
-}
-
-interface StringMap {
- [key: string]: T;
-}
-
-interface NumberMap {
- [key: number]: T;
-}
-
-class NodeMap {
-
- private static ID_PROP = '__mutation_summary_node_map_id__';
- private static nextId_:number = 1;
-
- private nodes:Node[];
- private values:T[];
-
- constructor() {
- this.nodes = [];
- this.values = [];
- }
-
- private isIndex(s:string):boolean {
- return +s === s >>> 0;
- }
-
- private nodeId(node:Node) {
- var id = node[NodeMap.ID_PROP];
- if (!id)
- id = node[NodeMap.ID_PROP] = NodeMap.nextId_++;
- return id;
- }
-
- set(node:Node, value:T) {
- var id = this.nodeId(node);
- this.nodes[id] = node;
- this.values[id] = value;
- }
-
- get(node:Node):T {
- var id = this.nodeId(node);
- return this.values[id];
- }
-
- has(node:Node):boolean {
- return this.nodeId(node) in this.nodes;
- }
-
- delete(node:Node) {
- var id = this.nodeId(node);
- delete this.nodes[id];
- this.values[id] = undefined;
- }
-
- keys():Node[] {
- var nodes:Node[] = [];
- for (var id in this.nodes) {
- if (!this.isIndex(id))
- continue;
- nodes.push(this.nodes[id]);
- }
-
- return nodes;
- }
-}
-
-/**
- * var reachableMatchableProduct = [
- * // STAYED_OUT, ENTERED, STAYED_IN, EXITED
- * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT
- * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED
- * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN
- * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED
- * ];
- */
-
-enum Movement {
- STAYED_OUT,
- ENTERED,
- STAYED_IN,
- REPARENTED,
- REORDERED,
- EXITED
-}
-
-function enteredOrExited(changeType:Movement):boolean {
- return changeType === Movement.ENTERED || changeType === Movement.EXITED;
-}
-
-class NodeChange {
-
- public isCaseInsensitive:boolean;
-
- constructor(public node:Node,
- public childList:boolean = false,
- public attributes:boolean = false,
- public characterData:boolean = false,
- public oldParentNode:Node = null,
- public added:boolean = false,
- private attributeOldValues:StringMap = null,
- public characterDataOldValue:string = null) {
- this.isCaseInsensitive =
- this.node.nodeType === Node.ELEMENT_NODE &&
- this.node instanceof HTMLElement &&
- this.node.ownerDocument instanceof HTMLDocument;
- }
-
- getAttributeOldValue(name:string):string {
- if (!this.attributeOldValues)
- return undefined;
- if (this.isCaseInsensitive)
- name = name.toLowerCase();
- return this.attributeOldValues[name];
- }
-
- getAttributeNamesMutated():string[] {
- var names:string[] = [];
- if (!this.attributeOldValues)
- return names;
- for (var name in this.attributeOldValues) {
- names.push(name);
- }
- return names;
- }
-
- attributeMutated(name:string, oldValue:string) {
- this.attributes = true;
- this.attributeOldValues = this.attributeOldValues || {};
-
- if (name in this.attributeOldValues)
- return;
-
- this.attributeOldValues[name] = oldValue;
- }
-
- characterDataMutated(oldValue:string) {
- if (this.characterData)
- return;
- this.characterData = true;
- this.characterDataOldValue = oldValue;
- }
-
- // Note: is it possible to receive a removal followed by a removal. This
- // can occur if the removed node is added to an non-observed node, that
- // node is added to the observed area, and then the node removed from
- // it.
- removedFromParent(parent:Node) {
- this.childList = true;
- if (this.added || this.oldParentNode)
- this.added = false;
- else
- this.oldParentNode = parent;
- }
-
- insertedIntoParent() {
- this.childList = true;
- this.added = true;
- }
-
- // An node's oldParent is
- // -its present parent, if its parentNode was not changed.
- // -null if the first thing that happened to it was an add.
- // -the node it was removed from if the first thing that happened to it
- // was a remove.
- getOldParent() {
- if (this.childList) {
- if (this.oldParentNode)
- return this.oldParentNode;
- if (this.added)
- return null;
- }
-
- return this.node.parentNode;
- }
-}
-
-class ChildListChange {
-
- public added:NodeMap;
- public removed:NodeMap;
- public maybeMoved:NodeMap;
- public oldPrevious:NodeMap;
- public moved:NodeMap;
-
- constructor() {
- this.added = new NodeMap();
- this.removed = new NodeMap();
- this.maybeMoved = new NodeMap();
- this.oldPrevious = new NodeMap();
- this.moved = undefined;
- }
-}
-
-class TreeChanges extends NodeMap {
-
- public anyParentsChanged:boolean;
- public anyAttributesChanged:boolean;
- public anyCharacterDataChanged:boolean;
-
- private reachableCache:NodeMap;
- private wasReachableCache:NodeMap;
-
- private rootNode:Node;
-
- constructor(rootNode:Node, mutations:MutationRecord[]) {
- super();
-
- this.rootNode = rootNode;
- this.reachableCache = undefined;
- this.wasReachableCache = undefined;
- this.anyParentsChanged = false;
- this.anyAttributesChanged = false;
- this.anyCharacterDataChanged = false;
-
- for (var m = 0; m < mutations.length; m++) {
- var mutation = mutations[m];
- switch (mutation.type) {
-
- case 'childList':
- this.anyParentsChanged = true;
- for (var i = 0; i < mutation.removedNodes.length; i++) {
- var node = mutation.removedNodes[i];
- this.getChange(node).removedFromParent(mutation.target);
- }
- for (var i = 0; i < mutation.addedNodes.length; i++) {
- var node = mutation.addedNodes[i];
- this.getChange(node).insertedIntoParent();
- }
- break;
-
- case 'attributes':
- this.anyAttributesChanged = true;
- var change = this.getChange(mutation.target);
- change.attributeMutated(mutation.attributeName, mutation.oldValue);
- break;
-
- case 'characterData':
- this.anyCharacterDataChanged = true;
- var change = this.getChange(mutation.target);
- change.characterDataMutated(mutation.oldValue);
- break;
- }
- }
- }
-
- getChange(node:Node):NodeChange {
- var change = this.get(node);
- if (!change) {
- change = new NodeChange(node);
- this.set(node, change);
- }
- return change;
- }
-
- getOldParent(node:Node):Node {
- var change = this.get(node);
- return change ? change.getOldParent() : node.parentNode;
- }
-
- getIsReachable(node:Node):boolean {
- if (node === this.rootNode)
- return true;
- if (!node)
- return false;
-
- this.reachableCache = this.reachableCache || new NodeMap();
- var isReachable = this.reachableCache.get(node);
- if (isReachable === undefined) {
- isReachable = this.getIsReachable(node.parentNode);
- this.reachableCache.set(node, isReachable);
- }
- return isReachable;
- }
-
- // A node wasReachable if its oldParent wasReachable.
- getWasReachable(node:Node):boolean {
- if (node === this.rootNode)
- return true;
- if (!node)
- return false;
-
- this.wasReachableCache = this.wasReachableCache || new NodeMap();
- var wasReachable:boolean = this.wasReachableCache.get(node);
- if (wasReachable === undefined) {
- wasReachable = this.getWasReachable(this.getOldParent(node));
- this.wasReachableCache.set(node, wasReachable);
- }
- return wasReachable;
- }
-
- reachabilityChange(node:Node):Movement {
- if (this.getIsReachable(node)) {
- return this.getWasReachable(node) ?
- Movement.STAYED_IN : Movement.ENTERED;
- }
-
- return this.getWasReachable(node) ?
- Movement.EXITED : Movement.STAYED_OUT;
- }
-}
-
-class MutationProjection {
-
- private treeChanges:TreeChanges;
- private entered:Node[];
- private exited:Node[];
- private stayedIn:NodeMap;
- private visited:NodeMap;
- private childListChangeMap:NodeMap;
- private characterDataOnly:boolean;
- private matchCache:NumberMap>;
-
- // TOOD(any)
- constructor(public rootNode:Node,
- public mutations:MutationRecord[],
- public selectors:Selector[],
- public calcReordered:boolean,
- public calcOldPreviousSibling:boolean) {
-
- this.treeChanges = new TreeChanges(rootNode, mutations);
- this.entered = [];
- this.exited = [];
- this.stayedIn = new NodeMap();
- this.visited = new NodeMap();
- this.childListChangeMap = undefined;
- this.characterDataOnly = undefined;
- this.matchCache = undefined;
-
- this.processMutations();
- }
-
- processMutations() {
- if (!this.treeChanges.anyParentsChanged &&
- !this.treeChanges.anyAttributesChanged)
- return;
-
- var changedNodes:Node[] = this.treeChanges.keys();
- for (var i = 0; i < changedNodes.length; i++) {
- this.visitNode(changedNodes[i], undefined);
- }
- }
-
- visitNode(node:Node, parentReachable:Movement) {
- if (this.visited.has(node))
- return;
-
- this.visited.set(node, true);
-
- var change = this.treeChanges.get(node);
- var reachable = parentReachable;
-
- // node inherits its parent's reachability change unless
- // its parentNode was mutated.
- if ((change && change.childList) || reachable == undefined)
- reachable = this.treeChanges.reachabilityChange(node);
-
- if (reachable === Movement.STAYED_OUT)
- return;
-
- // Cache match results for sub-patterns.
- this.matchabilityChange(node);
-
- if (reachable === Movement.ENTERED) {
- this.entered.push(node);
- } else if (reachable === Movement.EXITED) {
- this.exited.push(node);
- this.ensureHasOldPreviousSiblingIfNeeded(node);
-
- } else if (reachable === Movement.STAYED_IN) {
- var movement = Movement.STAYED_IN;
-
- if (change && change.childList) {
- if (change.oldParentNode !== node.parentNode) {
- movement = Movement.REPARENTED;
- this.ensureHasOldPreviousSiblingIfNeeded(node);
- } else if (this.calcReordered && this.wasReordered(node)) {
- movement = Movement.REORDERED;
- }
- }
-
- this.stayedIn.set(node, movement);
- }
-
- if (reachable === Movement.STAYED_IN)
- return;
-
- // reachable === ENTERED || reachable === EXITED.
- for (var child = node.firstChild; child; child = child.nextSibling) {
- this.visitNode(child, reachable);
- }
- }
-
- ensureHasOldPreviousSiblingIfNeeded(node:Node) {
- if (!this.calcOldPreviousSibling)
- return;
-
- this.processChildlistChanges();
-
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
-
- var change = this.childListChangeMap.get(parentNode);
- if (!change) {
- change = new ChildListChange();
- this.childListChangeMap.set(parentNode, change);
- }
-
- if (!change.oldPrevious.has(node)) {
- change.oldPrevious.set(node, node.previousSibling);
- }
- }
-
- getChanged(summary:Summary, selectors:Selector[], characterDataOnly:boolean) {
- this.selectors = selectors;
- this.characterDataOnly = characterDataOnly;
-
- for (var i = 0; i < this.entered.length; i++) {
- var node = this.entered[i];
- var matchable = this.matchabilityChange(node);
- if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN)
- summary.added.push(node);
- }
-
- var stayedInNodes = this.stayedIn.keys();
- for (var i = 0; i < stayedInNodes.length; i++) {
- var node = stayedInNodes[i];
- var matchable = this.matchabilityChange(node);
-
- if (matchable === Movement.ENTERED) {
- summary.added.push(node);
- } else if (matchable === Movement.EXITED) {
- summary.removed.push(node);
- } else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) {
- var movement:Movement = this.stayedIn.get(node);
- if (summary.reparented && movement === Movement.REPARENTED)
- summary.reparented.push(node);
- else if (summary.reordered && movement === Movement.REORDERED)
- summary.reordered.push(node);
- }
- }
-
- for (var i = 0; i < this.exited.length; i++) {
- var node = this.exited[i];
- var matchable = this.matchabilityChange(node);
- if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN)
- summary.removed.push(node);
- }
- }
-
- getOldParentNode(node:Node):Node {
- var change = this.treeChanges.get(node);
- if (change && change.childList)
- return change.oldParentNode ? change.oldParentNode : null;
-
- var reachabilityChange = this.treeChanges.reachabilityChange(node);
- if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED)
- throw Error('getOldParentNode requested on invalid node.');
-
- return node.parentNode;
- }
-
- getOldPreviousSibling(node:Node):Node {
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
-
- var change = this.childListChangeMap.get(parentNode);
- if (!change)
- throw Error('getOldPreviousSibling requested on invalid node.');
-
- return change.oldPrevious.get(node);
- }
-
- getOldAttribute(element:Node, attrName:string):string {
- var change = this.treeChanges.get(element);
- if (!change || !change.attributes)
- throw Error('getOldAttribute requested on invalid node.');
-
- var value = change.getAttributeOldValue(attrName);
- if (value === undefined)
- throw Error('getOldAttribute requested for unchanged attribute name.');
-
- return value;
- }
-
- attributeChangedNodes(includeAttributes:string[]):StringMap {
- if (!this.treeChanges.anyAttributesChanged)
- return {}; // No attributes mutations occurred.
-
- var attributeFilter:StringMap;
- var caseInsensitiveFilter:StringMap;
- if (includeAttributes) {
- attributeFilter = {};
- caseInsensitiveFilter = {};
- for (var i = 0; i < includeAttributes.length; i++) {
- var attrName:string = includeAttributes[i];
- attributeFilter[attrName] = true;
- caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
- }
- }
-
- var result:StringMap = {};
- var nodes = this.treeChanges.keys();
-
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
-
- var change = this.treeChanges.get(node);
- if (!change.attributes)
- continue;
-
- if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) ||
- Movement.STAYED_IN !== this.matchabilityChange(node)) {
- continue;
- }
-
- var element = node;
- var changedAttrNames = change.getAttributeNamesMutated();
- for (var j = 0; j < changedAttrNames.length; j++) {
- var attrName = changedAttrNames[j];
-
- if (attributeFilter &&
- !attributeFilter[attrName] &&
- !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) {
- continue;
- }
-
- var oldValue = change.getAttributeOldValue(attrName);
- if (oldValue === element.getAttribute(attrName))
- continue;
-
- if (caseInsensitiveFilter && change.isCaseInsensitive)
- attrName = caseInsensitiveFilter[attrName];
-
- result[attrName] = result[attrName] || [];
- result[attrName].push(element);
- }
- }
-
- return result;
- }
-
- getOldCharacterData(node:Node):string {
- var change = this.treeChanges.get(node);
- if (!change || !change.characterData)
- throw Error('getOldCharacterData requested on invalid node.');
-
- return change.characterDataOldValue;
- }
-
- getCharacterDataChanged():Node[] {
- if (!this.treeChanges.anyCharacterDataChanged)
- return []; // No characterData mutations occurred.
-
- var nodes = this.treeChanges.keys();
- var result:Node[] = [];
- for (var i = 0; i < nodes.length; i++) {
- var target = nodes[i];
- if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target))
- continue;
-
- var change = this.treeChanges.get(target);
- if (!change.characterData ||
- target.textContent == change.characterDataOldValue)
- continue
-
- result.push(target);
- }
-
- return result;
- }
-
- computeMatchabilityChange(selector:Selector, el:Element):Movement {
- if (!this.matchCache)
- this.matchCache = [];
- if (!this.matchCache[selector.uid])
- this.matchCache[selector.uid] = new NodeMap();
-
- var cache = this.matchCache[selector.uid];
- var result = cache.get(el);
- if (result === undefined) {
- result = selector.matchabilityChange(el, this.treeChanges.get(el));
- cache.set(el, result);
- }
- return result;
- }
-
- matchabilityChange(node:Node) {
- // TODO(rafaelw): Include PI, CDATA?
- // Only include text nodes.
- if (this.characterDataOnly) {
- switch (node.nodeType) {
- case Node.COMMENT_NODE:
- case Node.TEXT_NODE:
- return Movement.STAYED_IN;
- default:
- return Movement.STAYED_OUT;
- }
- }
-
- // No element filter. Include all nodes.
- if (!this.selectors)
- return Movement.STAYED_IN;
-
- // Element filter. Exclude non-elements.
- if (node.nodeType !== Node.ELEMENT_NODE)
- return Movement.STAYED_OUT;
-
- var el = node;
-
- var matchChanges = this.selectors.map((selector:Selector) => {
- return this.computeMatchabilityChange(selector, el);
- });
-
- var accum:Movement = Movement.STAYED_OUT;
- var i = 0;
-
- while (accum !== Movement.STAYED_IN && i < matchChanges.length) {
- switch(matchChanges[i]) {
- case Movement.STAYED_IN:
- accum = Movement.STAYED_IN;
- break;
- case Movement.ENTERED:
- if (accum === Movement.EXITED)
- accum = Movement.STAYED_IN;
- else
- accum = Movement.ENTERED;
- break;
- case Movement.EXITED:
- if (accum === Movement.ENTERED)
- accum = Movement.STAYED_IN;
- else
- accum = Movement.EXITED;
- break;
- }
-
- i++;
- }
-
- return accum;
- }
-
- getChildlistChange(el:Element):ChildListChange {
- var change = this.childListChangeMap.get(el);
- if (!change) {
- change = new ChildListChange();
- this.childListChangeMap.set(el, change);
- }
-
- return change;
- }
-
- processChildlistChanges() {
- if (this.childListChangeMap)
- return;
-
- this.childListChangeMap = new NodeMap();
-
- for (var i = 0; i < this.mutations.length; i++) {
- var mutation = this.mutations[i];
- if (mutation.type != 'childList')
- continue;
-
- if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN &&
- !this.calcOldPreviousSibling)
- continue;
-
- var change = this.getChildlistChange(mutation.target);
-
- var oldPrevious = mutation.previousSibling;
-
- function recordOldPrevious(node:Node, previous:Node) {
- if (!node ||
- change.oldPrevious.has(node) ||
- change.added.has(node) ||
- change.maybeMoved.has(node))
- return;
-
- if (previous &&
- (change.added.has(previous) ||
- change.maybeMoved.has(previous)))
- return;
-
- change.oldPrevious.set(node, previous);
- }
-
- for (var j = 0; j < mutation.removedNodes.length; j++) {
- var node = mutation.removedNodes[j];
- recordOldPrevious(node, oldPrevious);
-
- if (change.added.has(node)) {
- change.added.delete(node);
- } else {
- change.removed.set(node, true);
- change.maybeMoved.delete(node);
- }
-
- oldPrevious = node;
- }
-
- recordOldPrevious(mutation.nextSibling, oldPrevious);
-
- for (var j = 0; j < mutation.addedNodes.length; j++) {
- var node = mutation.addedNodes[j];
- if (change.removed.has(node)) {
- change.removed.delete(node);
- change.maybeMoved.set(node, true);
- } else {
- change.added.set(node, true);
- }
- }
- }
- }
-
- wasReordered(node:Node) {
- if (!this.treeChanges.anyParentsChanged)
- return false;
-
- this.processChildlistChanges();
-
- var parentNode = node.parentNode;
- var nodeChange = this.treeChanges.get(node);
- if (nodeChange && nodeChange.oldParentNode)
- parentNode = nodeChange.oldParentNode;
-
- var change = this.childListChangeMap.get(parentNode);
- if (!change)
- return false;
-
- if (change.moved)
- return change.moved.get(node);
-
- change.moved = new NodeMap();
- var pendingMoveDecision = new NodeMap();
-
- function isMoved(node:Node) {
- if (!node)
- return false;
- if (!change.maybeMoved.has(node))
- return false;
-
- var didMove = change.moved.get(node);
- if (didMove !== undefined)
- return didMove;
-
- if (pendingMoveDecision.has(node)) {
- didMove = true;
- } else {
- pendingMoveDecision.set(node, true);
- didMove = getPrevious(node) !== getOldPrevious(node);
- }
-
- if (pendingMoveDecision.has(node)) {
- pendingMoveDecision.delete(node);
- change.moved.set(node, didMove);
- } else {
- didMove = change.moved.get(node);
- }
-
- return didMove;
- }
-
- var oldPreviousCache = new NodeMap();
- function getOldPrevious(node:Node):Node {
- var oldPrevious = oldPreviousCache.get(node);
- if (oldPrevious !== undefined)
- return oldPrevious;
-
- oldPrevious = change.oldPrevious.get(node);
- while (oldPrevious &&
- (change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
- oldPrevious = getOldPrevious(oldPrevious);
- }
-
- if (oldPrevious === undefined)
- oldPrevious = node.previousSibling;
- oldPreviousCache.set(node, oldPrevious);
-
- return oldPrevious;
- }
-
- var previousCache = new NodeMap();
- function getPrevious(node:Node):Node {
- if (previousCache.has(node))
- return previousCache.get(node);
-
- var previous = node.previousSibling;
- while (previous && (change.added.has(previous) || isMoved(previous)))
- previous = previous.previousSibling;
-
- previousCache.set(node, previous);
- return previous;
- }
-
- change.maybeMoved.keys().forEach(isMoved);
- return change.moved.get(node);
- }
-}
-
-class Summary {
- public added:Node[];
- public removed:Node[];
- public reparented:Node[];
- public reordered:Node[];
- public valueChanged:Node[];
- public attributeChanged:StringMap;
- public characterDataChanged:Node[];
-
- constructor(private projection:MutationProjection, query:Query) {
- this.added = [];
- this.removed = [];
- this.reparented = query.all || query.element || query.characterData ? [] : undefined;
- this.reordered = query.all ? [] : undefined;
-
- projection.getChanged(this, query.elementFilter, query.characterData);
-
- if (query.all || query.attribute || query.attributeList) {
- var filter = query.attribute ? [ query.attribute ] : query.attributeList;
- var attributeChanged = projection.attributeChangedNodes(filter);
-
- if (query.attribute) {
- this.valueChanged = attributeChanged[query.attribute] || [];
- } else {
- this.attributeChanged = attributeChanged;
- if (query.attributeList) {
- query.attributeList.forEach((attrName) => {
- if (!this.attributeChanged.hasOwnProperty(attrName))
- this.attributeChanged[attrName] = [];
- });
- }
- }
- }
-
- if (query.all || query.characterData) {
- var characterDataChanged = projection.getCharacterDataChanged()
-
- if (query.characterData)
- this.valueChanged = characterDataChanged;
- else
- this.characterDataChanged = characterDataChanged;
- }
-
- if (this.reordered)
- this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
- }
-
- getOldParentNode(node:Node):Node {
- return this.projection.getOldParentNode(node);
- }
-
- getOldAttribute(node:Node, name: string):string {
- return this.projection.getOldAttribute(node, name);
- }
-
- getOldCharacterData(node:Node):string {
- return this.projection.getOldCharacterData(node);
- }
-
- getOldPreviousSibling(node:Node):Node {
- return this.projection.getOldPreviousSibling(node);
- }
-}
-
-// TODO(rafaelw): Allow ':' and '.' as valid name characters.
-var validNameInitialChar = /[a-zA-Z_]+/;
-var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
-
-// TODO(rafaelw): Consider allowing backslash in the attrValue.
-// TODO(rafaelw): There's got a to be way to represent this state machine
-// more compactly???
-
-function escapeQuotes(value:string):string {
- return '"' + value.replace(/"/, '\\\"') + '"';
-}
-
-class Qualifier {
- public attrName:string;
- public attrValue:string;
- public contains:boolean;
-
- constructor() {}
-
- public matches(oldValue:string):boolean {
- if (oldValue === null)
- return false;
-
- if (this.attrValue === undefined)
- return true;
-
- if (!this.contains)
- return this.attrValue == oldValue;
-
- var tokens = oldValue.split(' ');
- for (var i = 0; i < tokens.length; i++) {
- if (this.attrValue === tokens[i])
- return true;
- }
-
- return false;
- }
-
- public toString():string {
- if (this.attrName === 'class' && this.contains)
- return '.' + this.attrValue;
-
- if (this.attrName === 'id' && !this.contains)
- return '#' + this.attrValue;
-
- if (this.contains)
- return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
-
- if ('attrValue' in this)
- return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
-
- return '[' + this.attrName + ']';
- }
-}
-
-class Selector {
- private static nextUid:number = 1;
- private static matchesSelector:string = (function(){
- var element = document.createElement('div');
- if (typeof element['webkitMatchesSelector'] === 'function')
- return 'webkitMatchesSelector';
- if (typeof element['mozMatchesSelector'] === 'function')
- return 'mozMatchesSelector';
- if (typeof element['msMatchesSelector'] === 'function')
- return 'msMatchesSelector';
-
- return 'matchesSelector';
- })();
-
- public tagName:string;
- public qualifiers:Qualifier[];
- public uid:number;
-
- private get caseInsensitiveTagName():string {
- return this.tagName.toUpperCase();
- }
-
- get selectorString() {
- return this.tagName + this.qualifiers.join('');
- }
-
- constructor() {
- this.uid = Selector.nextUid++;
- this.qualifiers = [];
- }
-
- private isMatching(el:Element):boolean {
- return el[Selector.matchesSelector](this.selectorString);
- }
-
- private wasMatching(el:Element, change:NodeChange, isMatching:boolean):boolean {
- if (!change || !change.attributes)
- return isMatching;
-
- var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
- if (tagName !== '*' && tagName !== el.tagName)
- return false;
-
- var attributeOldValues:string[] = [];
- var anyChanged = false;
- for (var i = 0; i < this.qualifiers.length; i++) {
- var qualifier = this.qualifiers[i];
- var oldValue = change.getAttributeOldValue(qualifier.attrName);
- attributeOldValues.push(oldValue);
- anyChanged = anyChanged || (oldValue !== undefined);
- }
-
- if (!anyChanged)
- return isMatching;
-
- for (var i = 0; i < this.qualifiers.length; i++) {
- var qualifier = this.qualifiers[i];
- var oldValue = attributeOldValues[i];
- if (oldValue === undefined)
- oldValue = el.getAttribute(qualifier.attrName);
- if (!qualifier.matches(oldValue))
- return false;
- }
-
- return true;
- }
-
- public matchabilityChange(el:Element, change:NodeChange):Movement {
- var isMatching = this.isMatching(el);
- if (isMatching)
- return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED;
- else
- return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT;
- }
-
- public static parseSelectors(input:string):Selector[] {
- var selectors:Selector[] = [];
- var currentSelector:Selector;
- var currentQualifier:Qualifier;
-
- function newSelector() {
- if (currentSelector) {
- if (currentQualifier) {
- currentSelector.qualifiers.push(currentQualifier);
- currentQualifier = undefined;
- }
-
- selectors.push(currentSelector);
- }
- currentSelector = new Selector();
- }
-
- function newQualifier() {
- if (currentQualifier)
- currentSelector.qualifiers.push(currentQualifier);
-
- currentQualifier = new Qualifier();
- }
-
- var WHITESPACE = /\s/;
- var valueQuoteChar:string;
- var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
-
- var SELECTOR = 1;
- var TAG_NAME = 2;
- var QUALIFIER = 3;
- var QUALIFIER_NAME_FIRST_CHAR = 4;
- var QUALIFIER_NAME = 5;
- var ATTR_NAME_FIRST_CHAR = 6;
- var ATTR_NAME = 7;
- var EQUIV_OR_ATTR_QUAL_END = 8;
- var EQUAL = 9;
- var ATTR_QUAL_END = 10;
- var VALUE_FIRST_CHAR = 11;
- var VALUE = 12;
- var QUOTED_VALUE = 13;
- var SELECTOR_SEPARATOR = 14;
-
- var state = SELECTOR;
- var i = 0;
- while (i < input.length) {
- var c = input[i++];
-
- switch (state) {
- case SELECTOR:
- if (c.match(validNameInitialChar)) {
- newSelector();
- currentSelector.tagName = c;
- state = TAG_NAME;
- break;
- }
-
- if (c == '*') {
- newSelector();
- currentSelector.tagName = '*';
- state = QUALIFIER;
- break;
- }
-
- if (c == '.') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newSelector();
- newQualifier();
- currentSelector.tagName = '*';
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
-
- if (c.match(WHITESPACE))
- break;
-
- throw Error(SYNTAX_ERROR);
-
- case TAG_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentSelector.tagName += c;
- break;
- }
-
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
-
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
-
- if (c == ',') {
- state = SELECTOR;
- break;
- }
-
- throw Error(SYNTAX_ERROR);
-
- case QUALIFIER:
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- currentQualifier.attrName = '';
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
-
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
-
- if (c == ',') {
- state = SELECTOR;
- break;
- }
-
- throw Error(SYNTAX_ERROR);
-
- case QUALIFIER_NAME_FIRST_CHAR:
- if (c.match(validNameInitialChar)) {
- currentQualifier.attrValue = c;
- state = QUALIFIER_NAME;
- break;
- }
-
- throw Error(SYNTAX_ERROR);
-
- case QUALIFIER_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentQualifier.attrValue += c;
- break;
- }
-
- if (c == '.') {
- newQualifier();
- currentQualifier.attrName = 'class';
- currentQualifier.contains = true;
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '#') {
- newQualifier();
- currentQualifier.attrName = 'id';
- state = QUALIFIER_NAME_FIRST_CHAR;
- break;
- }
- if (c == '[') {
- newQualifier();
- state = ATTR_NAME_FIRST_CHAR;
- break;
- }
-
- if (c.match(WHITESPACE)) {
- state = SELECTOR_SEPARATOR;
- break;
- }
- if (c == ',') {
- state = SELECTOR;
- break
- }
-
- throw Error(SYNTAX_ERROR);
-
- case ATTR_NAME_FIRST_CHAR:
- if (c.match(validNameInitialChar)) {
- currentQualifier.attrName = c;
- state = ATTR_NAME;
- break;
- }
-
- if (c.match(WHITESPACE))
- break;
-
- throw Error(SYNTAX_ERROR);
-
- case ATTR_NAME:
- if (c.match(validNameNonInitialChar)) {
- currentQualifier.attrName += c;
- break;
- }
-
- if (c.match(WHITESPACE)) {
- state = EQUIV_OR_ATTR_QUAL_END;
- break;
- }
-
- if (c == '~') {
- currentQualifier.contains = true;
- state = EQUAL;
- break;
- }
-
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR;
- break;
- }
-
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
-
- throw Error(SYNTAX_ERROR);
-
- case EQUIV_OR_ATTR_QUAL_END:
- if (c == '~') {
- currentQualifier.contains = true;
- state = EQUAL;
- break;
- }
-
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR;
- break;
- }
-
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
-
- if (c.match(WHITESPACE))
- break;
-
- throw Error(SYNTAX_ERROR);
-
- case EQUAL:
- if (c == '=') {
- currentQualifier.attrValue = '';
- state = VALUE_FIRST_CHAR
- break;
- }
-
- throw Error(SYNTAX_ERROR);
-
- case ATTR_QUAL_END:
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
-
- if (c.match(WHITESPACE))
- break;
-
- throw Error(SYNTAX_ERROR);
-
- case VALUE_FIRST_CHAR:
- if (c.match(WHITESPACE))
- break;
-
- if (c == '"' || c == "'") {
- valueQuoteChar = c;
- state = QUOTED_VALUE;
- break;
- }
-
- currentQualifier.attrValue += c;
- state = VALUE;
- break;
-
- case VALUE:
- if (c.match(WHITESPACE)) {
- state = ATTR_QUAL_END;
- break;
- }
- if (c == ']') {
- state = QUALIFIER;
- break;
- }
- if (c == "'" || c == '"')
- throw Error(SYNTAX_ERROR);
-
- currentQualifier.attrValue += c;
- break;
-
- case QUOTED_VALUE:
- if (c == valueQuoteChar) {
- state = ATTR_QUAL_END;
- break;
- }
-
- currentQualifier.attrValue += c;
- break;
-
- case SELECTOR_SEPARATOR:
- if (c.match(WHITESPACE))
- break;
-
- if (c == ',') {
- state = SELECTOR;
- break
- }
-
- throw Error(SYNTAX_ERROR);
- }
- }
-
- switch (state) {
- case SELECTOR:
- case TAG_NAME:
- case QUALIFIER:
- case QUALIFIER_NAME:
- case SELECTOR_SEPARATOR:
- // Valid end states.
- newSelector();
- break;
- default:
- throw Error(SYNTAX_ERROR);
- }
-
- if (!selectors.length)
- throw Error(SYNTAX_ERROR);
-
- return selectors;
- }
-}
-
-var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/;
-
-function validateAttribute(attribute:string) {
- if (typeof attribute != 'string')
- throw Error('Invalid request opion. attribute must be a non-zero length string.');
-
- attribute = attribute.trim();
-
- if (!attribute)
- throw Error('Invalid request opion. attribute must be a non-zero length string.');
-
-
- if (!attribute.match(attributeFilterPattern))
- throw Error('Invalid request option. invalid attribute name: ' + attribute);
-
- return attribute;
-}
-
-function validateElementAttributes(attribs:string):string[] {
- if (!attribs.trim().length)
- throw Error('Invalid request option: elementAttributes must contain at least one attribute.');
-
- var lowerAttributes = {};
- var attributes = {};
-
- var tokens = attribs.split(/\s+/);
- for (var i = 0; i < tokens.length; i++) {
- var name = tokens[i];
- if (!name)
- continue;
-
- var name = validateAttribute(name);
- var nameLower = name.toLowerCase();
- if (lowerAttributes[nameLower])
- throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.');
-
- attributes[name] = true;
- lowerAttributes[nameLower] = true;
- }
-
- return Object.keys(attributes);
-}
-
-
-
-function elementFilterAttributes(selectors:Selector[]):string[] {
- var attributes:StringMap = {};
-
- selectors.forEach((selector) => {
- selector.qualifiers.forEach((qualifier) => {
- attributes[qualifier.attrName] = true;
- });
- });
-
- return Object.keys(attributes);
-}
-
-interface Query {
- element?:string;
- attribute?:string;
- all?:boolean;
- characterData?:boolean;
- elementAttributes?:string;
- attributeList?:string[];
- elementFilter?:Selector[];
-}
-
-interface Options {
- callback:(summaries:Summary[]) => any;
- queries: Query[];
- rootNode?:Node;
- oldPreviousSibling?:boolean;
- observeOwnChanges?:boolean;
-}
-
-class MutationSummary {
-
- public static NodeMap = NodeMap; // exposed for use in TreeMirror.
- public static parseElementFilter = Selector.parseSelectors; // exposed for testing.
-
- public static createQueryValidator:(root:Node, query:Query)=>any;
- private connected:boolean;
- private options:Options;
- private observer:MutationObserver;
- private observerOptions:MutationObserverInit;
- private root:Node;
- private callback:(summaries:Summary[])=>any;
- private elementFilter:Selector[];
- private calcReordered:boolean;
- private queryValidators:any[];
-
- private static optionKeys:StringMap = {
- 'callback': true, // required
- 'queries': true, // required
- 'rootNode': true,
- 'oldPreviousSibling': true,
- 'observeOwnChanges': true
- };
-
- private static createObserverOptions(queries:Query[]):MutationObserverInit {
- var observerOptions:MutationObserverInit = {
- childList: true,
- subtree: true
- };
-
- var attributeFilter:StringMap;
- function observeAttributes(attributes?:string[]) {
- if (observerOptions.attributes && !attributeFilter)
- return; // already observing all.
-
- observerOptions.attributes = true;
- observerOptions.attributeOldValue = true;
-
- if (!attributes) {
- // observe all.
- attributeFilter = undefined;
- return;
- }
-
- // add to observed.
- attributeFilter = attributeFilter || {};
- attributes.forEach((attribute) => {
- attributeFilter[attribute] = true;
- attributeFilter[attribute.toLowerCase()] = true;
- });
- }
-
- queries.forEach((query) => {
- if (query.characterData) {
- observerOptions.characterData = true;
- observerOptions.characterDataOldValue = true;
- return;
- }
-
- if (query.all) {
- observeAttributes();
- observerOptions.characterData = true;
- observerOptions.characterDataOldValue = true;
- return;
- }
-
- if (query.attribute) {
- observeAttributes([query.attribute.trim()]);
- return;
- }
-
- var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []);
- if (attributes.length)
- observeAttributes(attributes);
- });
-
- if (attributeFilter)
- observerOptions.attributeFilter = Object.keys(attributeFilter);
-
- return observerOptions;
- }
-
- private static validateOptions(options:Options):Options {
- for (var prop in options) {
- if (!(prop in MutationSummary.optionKeys))
- throw Error('Invalid option: ' + prop);
- }
-
- if (typeof options.callback !== 'function')
- throw Error('Invalid options: callback is required and must be a function');
-
- if (!options.queries || !options.queries.length)
- throw Error('Invalid options: queries must contain at least one query request object.');
-
- var opts:Options = {
- callback: options.callback,
- rootNode: options.rootNode || document,
- observeOwnChanges: !!options.observeOwnChanges,
- oldPreviousSibling: !!options.oldPreviousSibling,
- queries: []
- };
-
- for (var i = 0; i < options.queries.length; i++) {
- var request = options.queries[i];
-
- // all
- if (request.all) {
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. all has no options.');
-
- opts.queries.push({all: true});
- continue;
- }
-
- // attribute
- if ('attribute' in request) {
- var query:Query = {
- attribute: validateAttribute(request.attribute)
- };
-
- query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']');
-
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. attribute has no options.');
-
- opts.queries.push(query);
- continue;
- }
-
- // element
- if ('element' in request) {
- var requestOptionCount = Object.keys(request).length;
- var query:Query = {
- element: request.element,
- elementFilter: Selector.parseSelectors(request.element)
- };
-
- if (request.hasOwnProperty('elementAttributes')) {
- query.attributeList = validateElementAttributes(request.elementAttributes);
- requestOptionCount--;
- }
-
- if (requestOptionCount > 1)
- throw Error('Invalid request option. element only allows elementAttributes option.');
-
- opts.queries.push(query);
- continue;
- }
-
- // characterData
- if (request.characterData) {
- if (Object.keys(request).length > 1)
- throw Error('Invalid request option. characterData has no options.');
-
- opts.queries.push({ characterData: true });
- continue;
- }
-
- throw Error('Invalid request option. Unknown query request.');
- }
-
- return opts;
- }
-
- private createSummaries(mutations:MutationRecord[]):Summary[] {
- if (!mutations || !mutations.length)
- return [];
-
- var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling);
-
- var summaries:Summary[] = [];
- for (var i = 0; i < this.options.queries.length; i++) {
- summaries.push(new Summary(projection, this.options.queries[i]));
- }
-
- return summaries;
- }
-
- private checkpointQueryValidators() {
- this.queryValidators.forEach((validator) => {
- if (validator)
- validator.recordPreviousState();
- });
- }
-
- private runQueryValidators(summaries:Summary[]) {
- this.queryValidators.forEach((validator, index) => {
- if (validator)
- validator.validate(summaries[index]);
- });
- }
-
- private changesToReport(summaries:Summary[]):boolean {
- return summaries.some((summary) => {
- var summaryProps = ['added', 'removed', 'reordered', 'reparented',
- 'valueChanged', 'characterDataChanged'];
- if (summaryProps.some(function(prop) { return summary[prop] && summary[prop].length; }))
- return true;
-
- if (summary.attributeChanged) {
- var attrNames = Object.keys(summary.attributeChanged);
- var attrsChanged = attrNames.some((attrName) => {
- return !!summary.attributeChanged[attrName].length
- });
- if (attrsChanged)
- return true;
- }
- return false;
- });
- }
-
- constructor(opts:Options) {
- this.connected = false;
- this.options = MutationSummary.validateOptions(opts);
- this.observerOptions = MutationSummary.createObserverOptions(this.options.queries);
- this.root = this.options.rootNode;
- this.callback = this.options.callback;
-
- this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map((query) => {
- return query.elementFilter ? query.elementFilter : [];
- }));
- if (!this.elementFilter.length)
- this.elementFilter = undefined;
-
- this.calcReordered = this.options.queries.some((query) => {
- return query.all;
- });
-
- this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this.
- if (MutationSummary.createQueryValidator) {
- this.queryValidators = this.options.queries.map((query) => {
- return MutationSummary.createQueryValidator(this.root, query);
- });
- }
-
- this.observer = new MutationObserverCtor((mutations:MutationRecord[]) => {
- this.observerCallback(mutations);
- });
-
- this.reconnect();
- }
-
- private observerCallback(mutations:MutationRecord[]) {
- if (!this.options.observeOwnChanges)
- this.observer.disconnect();
-
- var summaries = this.createSummaries(mutations);
- this.runQueryValidators(summaries);
-
- if (this.options.observeOwnChanges)
- this.checkpointQueryValidators();
-
- if (this.changesToReport(summaries))
- this.callback(summaries);
-
- // disconnect() may have been called during the callback.
- if (!this.options.observeOwnChanges && this.connected) {
- this.checkpointQueryValidators();
- this.observer.observe(this.root, this.observerOptions);
- }
- }
-
- reconnect() {
- if (this.connected)
- throw Error('Already connected');
-
- this.observer.observe(this.root, this.observerOptions);
- this.connected = true;
- this.checkpointQueryValidators();
- }
-
- takeSummaries():Summary[] {
- if (!this.connected)
- throw Error('Not connected');
-
- var summaries = this.createSummaries(this.observer.takeRecords());
- return this.changesToReport(summaries) ? summaries : undefined;
- }
-
- disconnect():Summary[] {
- var summaries = this.takeSummaries();
- this.observer.disconnect();
- this.connected = false;
- return summaries;
- }
-}
-
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.js b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.js
deleted file mode 100644
index fb63e09..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.js
+++ /dev/null
@@ -1,268 +0,0 @@
-///
-var TreeMirror = /** @class */ (function () {
- function TreeMirror(root, delegate) {
- this.root = root;
- this.delegate = delegate;
- this.idMap = {};
- }
- TreeMirror.prototype.initialize = function (rootId, children) {
- this.idMap[rootId] = this.root;
- for (var i = 0; i < children.length; i++)
- this.deserializeNode(children[i], this.root);
- };
- TreeMirror.prototype.applyChanged = function (removed, addedOrMoved, attributes, text) {
- var _this = this;
- // NOTE: Applying the changes can result in an attempting to add a child
- // to a parent which is presently an ancestor of the parent. This can occur
- // based on random ordering of moves. The way we handle this is to first
- // remove all changed nodes from their parents, then apply.
- addedOrMoved.forEach(function (data) {
- var node = _this.deserializeNode(data);
- var parent = _this.deserializeNode(data.parentNode);
- var previous = _this.deserializeNode(data.previousSibling);
- if (node.parentNode)
- node.parentNode.removeChild(node);
- });
- removed.forEach(function (data) {
- var node = _this.deserializeNode(data);
- if (node.parentNode)
- node.parentNode.removeChild(node);
- });
- addedOrMoved.forEach(function (data) {
- var node = _this.deserializeNode(data);
- var parent = _this.deserializeNode(data.parentNode);
- var previous = _this.deserializeNode(data.previousSibling);
- parent.insertBefore(node, previous ? previous.nextSibling : parent.firstChild);
- });
- attributes.forEach(function (data) {
- var node = _this.deserializeNode(data);
- Object.keys(data.attributes).forEach(function (attrName) {
- var newVal = data.attributes[attrName];
- if (newVal === null) {
- node.removeAttribute(attrName);
- }
- else {
- if (!_this.delegate ||
- !_this.delegate.setAttribute ||
- !_this.delegate.setAttribute(node, attrName, newVal)) {
- node.setAttribute(attrName, newVal);
- }
- }
- });
- });
- text.forEach(function (data) {
- var node = _this.deserializeNode(data);
- node.textContent = data.textContent;
- });
- removed.forEach(function (node) {
- delete _this.idMap[node.id];
- });
- };
- TreeMirror.prototype.deserializeNode = function (nodeData, parent) {
- var _this = this;
- if (nodeData === null)
- return null;
- var node = this.idMap[nodeData.id];
- if (node)
- return node;
- var doc = this.root.ownerDocument;
- if (doc === null)
- doc = this.root;
- switch (nodeData.nodeType) {
- case Node.COMMENT_NODE:
- node = doc.createComment(nodeData.textContent);
- break;
- case Node.TEXT_NODE:
- node = doc.createTextNode(nodeData.textContent);
- break;
- case Node.DOCUMENT_TYPE_NODE:
- node = doc.implementation.createDocumentType(nodeData.name, nodeData.publicId, nodeData.systemId);
- break;
- case Node.ELEMENT_NODE:
- if (this.delegate && this.delegate.createElement)
- node = this.delegate.createElement(nodeData.tagName);
- if (!node)
- try {
- node = doc.createElement(nodeData.tagName);
- }
- catch (e) {
- console.log("Removing invalid node", nodeData);
- return null;
- }
- Object.keys(nodeData.attributes).forEach(function (name) {
- if (!_this.delegate ||
- !_this.delegate.setAttribute ||
- !_this.delegate.setAttribute(node, name, nodeData.attributes[name])) {
- try {
- node.setAttribute(name, nodeData.attributes[name]);
- }
- catch (e) {
- console.log("Removing node due to invalid attribute", nodeData);
- return null;
- }
- }
- });
- break;
- }
- if (!node)
- throw "ouch";
- this.idMap[nodeData.id] = node;
- if (parent)
- parent.appendChild(node);
- if (nodeData.childNodes) {
- for (var i = 0; i < nodeData.childNodes.length; i++)
- this.deserializeNode(nodeData.childNodes[i], node);
- }
- return node;
- };
- return TreeMirror;
-}());
-var TreeMirrorClient = /** @class */ (function () {
- function TreeMirrorClient(target, mirror, testingQueries) {
- var _this = this;
- this.target = target;
- this.mirror = mirror;
- this.nextId = 1;
- this.knownNodes = new MutationSummary.NodeMap();
- var rootId = this.serializeNode(target).id;
- var children = [];
- for (var child = target.firstChild; child; child = child.nextSibling)
- children.push(this.serializeNode(child, true));
- this.mirror.initialize(rootId, children);
- var self = this;
- var queries = [{ all: true }];
- if (testingQueries)
- queries = queries.concat(testingQueries);
- this.mutationSummary = new MutationSummary({
- rootNode: target,
- callback: function (summaries) {
- _this.applyChanged(summaries);
- },
- queries: queries
- });
- }
- TreeMirrorClient.prototype.disconnect = function () {
- if (this.mutationSummary) {
- this.mutationSummary.disconnect();
- this.mutationSummary = undefined;
- }
- };
- TreeMirrorClient.prototype.rememberNode = function (node) {
- var id = this.nextId++;
- this.knownNodes.set(node, id);
- return id;
- };
- TreeMirrorClient.prototype.forgetNode = function (node) {
- this.knownNodes["delete"](node);
- };
- TreeMirrorClient.prototype.serializeNode = function (node, recursive) {
- if (node === null)
- return null;
- var id = this.knownNodes.get(node);
- if (id !== undefined) {
- return { id: id };
- }
- var data = {
- nodeType: node.nodeType,
- id: this.rememberNode(node)
- };
- switch (data.nodeType) {
- case Node.DOCUMENT_TYPE_NODE:
- var docType = node;
- data.name = docType.name;
- data.publicId = docType.publicId;
- data.systemId = docType.systemId;
- break;
- case Node.COMMENT_NODE:
- case Node.TEXT_NODE:
- data.textContent = node.textContent;
- break;
- case Node.ELEMENT_NODE:
- var elm = node;
- data.tagName = elm.tagName;
- data.attributes = {};
- for (var i = 0; i < elm.attributes.length; i++) {
- var attr = elm.attributes[i];
- data.attributes[attr.name] = attr.value;
- }
- if (recursive && elm.childNodes.length) {
- data.childNodes = [];
- for (var child = elm.firstChild; child; child = child.nextSibling)
- data.childNodes.push(this.serializeNode(child, true));
- }
- break;
- }
- return data;
- };
- TreeMirrorClient.prototype.serializeAddedAndMoved = function (added, reparented, reordered) {
- var _this = this;
- var all = added.concat(reparented).concat(reordered);
- var parentMap = new MutationSummary.NodeMap();
- all.forEach(function (node) {
- var parent = node.parentNode;
- var children = parentMap.get(parent);
- if (!children) {
- children = new MutationSummary.NodeMap();
- parentMap.set(parent, children);
- }
- children.set(node, true);
- });
- var moved = [];
- parentMap.keys().forEach(function (parent) {
- var children = parentMap.get(parent);
- var keys = children.keys();
- while (keys.length) {
- var node = keys[0];
- while (node.previousSibling && children.has(node.previousSibling))
- node = node.previousSibling;
- while (node && children.has(node)) {
- var data = _this.serializeNode(node);
- data.previousSibling = _this.serializeNode(node.previousSibling);
- data.parentNode = _this.serializeNode(node.parentNode);
- moved.push(data);
- children["delete"](node);
- node = node.nextSibling;
- }
- var keys = children.keys();
- }
- });
- return moved;
- };
- TreeMirrorClient.prototype.serializeAttributeChanges = function (attributeChanged) {
- var _this = this;
- var map = new MutationSummary.NodeMap();
- Object.keys(attributeChanged).forEach(function (attrName) {
- attributeChanged[attrName].forEach(function (element) {
- var record = map.get(element);
- if (!record) {
- record = _this.serializeNode(element);
- record.attributes = {};
- map.set(element, record);
- }
- record.attributes[attrName] = element.getAttribute(attrName);
- });
- });
- return map.keys().map(function (node) {
- return map.get(node);
- });
- };
- TreeMirrorClient.prototype.applyChanged = function (summaries) {
- var _this = this;
- var summary = summaries[0];
- var removed = summary.removed.map(function (node) {
- return _this.serializeNode(node);
- });
- var moved = this.serializeAddedAndMoved(summary.added, summary.reparented, summary.reordered);
- var attributes = this.serializeAttributeChanges(summary.attributeChanged);
- var text = summary.characterDataChanged.map(function (node) {
- var data = _this.serializeNode(node);
- data.textContent = node.textContent;
- return data;
- });
- this.mirror.applyChanged(removed, moved, attributes, text);
- summary.removed.forEach(function (node) {
- _this.forgetNode(node);
- });
- };
- return TreeMirrorClient;
-}());
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.ts b/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.ts
deleted file mode 100644
index fd5f0d1..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/mutation-summary/util/tree-mirror.ts
+++ /dev/null
@@ -1,375 +0,0 @@
-///
-
-// Copyright 2013 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-interface NodeData {
- id:number;
- nodeType?:number;
- name?:string;
- publicId?:string;
- systemId?:string;
- textContent?:string;
- tagName?:string;
- attributes?:StringMap;
- childNodes?:NodeData[];
-}
-
-interface PositionData extends NodeData {
- previousSibling:NodeData;
- parentNode:NodeData;
-}
-
-interface AttributeData extends NodeData {
- attributes:StringMap;
-}
-
-interface TextData extends NodeData{
- textContent:string;
-}
-
-class TreeMirror {
-
- private idMap:NumberMap;
-
- constructor(public root:Node, public delegate?:any) {
- this.idMap = {};
- }
-
- initialize(rootId:number, children:NodeData[]) {
- this.idMap[rootId] = this.root;
-
- for (var i = 0; i < children.length; i++)
- this.deserializeNode(children[i], this.root);
- }
-
- applyChanged(removed:NodeData[],
- addedOrMoved:PositionData[],
- attributes:AttributeData[],
- text:TextData[]) {
-
- // NOTE: Applying the changes can result in an attempting to add a child
- // to a parent which is presently an ancestor of the parent. This can occur
- // based on random ordering of moves. The way we handle this is to first
- // remove all changed nodes from their parents, then apply.
- addedOrMoved.forEach((data:PositionData) => {
- var node = this.deserializeNode(data);
- var parent = this.deserializeNode(data.parentNode);
- var previous = this.deserializeNode(data.previousSibling);
- if (node.parentNode)
- node.parentNode.removeChild(node);
- });
-
- removed.forEach((data:NodeData) => {
- var node = this.deserializeNode(data);
- if (node.parentNode)
- node.parentNode.removeChild(node);
- });
-
- addedOrMoved.forEach((data:PositionData) => {
- var node = this.deserializeNode(data);
- var parent = this.deserializeNode(data.parentNode);
- var previous = this.deserializeNode(data.previousSibling);
- parent.insertBefore(node,
- previous ? previous.nextSibling : parent.firstChild);
- });
-
- attributes.forEach((data:AttributeData) => {
- var node = this.deserializeNode(data);
- Object.keys(data.attributes).forEach((attrName) => {
- var newVal = data.attributes[attrName];
- if (newVal === null) {
- node.removeAttribute(attrName);
- } else {
- if (!this.delegate ||
- !this.delegate.setAttribute ||
- !this.delegate.setAttribute(node, attrName, newVal)) {
- node.setAttribute(attrName, newVal);
- }
- }
- });
- });
-
- text.forEach((data:TextData) => {
- var node = this.deserializeNode(data);
- node.textContent = data.textContent;
- });
-
- removed.forEach((node:NodeData) => {
- delete this.idMap[node.id];
- });
- }
-
- private deserializeNode(nodeData:NodeData, parent?:Element):Node {
- if (nodeData === null)
- return null;
-
- var node:Node = this.idMap[nodeData.id];
- if (node)
- return node;
-
- var doc = this.root.ownerDocument;
- if (doc === null)
- doc = this.root;
-
- switch(nodeData.nodeType) {
- case Node.COMMENT_NODE:
- node = doc.createComment(nodeData.textContent);
- break;
-
- case Node.TEXT_NODE:
- node = doc.createTextNode(nodeData.textContent);
- break;
-
- case Node.DOCUMENT_TYPE_NODE:
- node = doc.implementation.createDocumentType(nodeData.name, nodeData.publicId, nodeData.systemId);
- break;
-
- case Node.ELEMENT_NODE:
- if (this.delegate && this.delegate.createElement)
- node = this.delegate.createElement(nodeData.tagName);
- if (!node)
- try {
- node = doc.createElement(nodeData.tagName);
- } catch (e) {
- console.log("Removing invalid node", nodeData);
- return null;
- }
-
- Object.keys(nodeData.attributes).forEach((name) => {
- if (!this.delegate ||
- !this.delegate.setAttribute ||
- !this.delegate.setAttribute(node, name, nodeData.attributes[name])) {
- try {
- (node).setAttribute(name, nodeData.attributes[name]);
- } catch (e) {
- console.log("Removing node due to invalid attribute", nodeData);
- return null;
- }
- }
- });
-
- break;
- }
-
- if (!node)
- throw "ouch";
-
- this.idMap[nodeData.id] = node;
-
- if (parent)
- parent.appendChild(node);
-
- if (nodeData.childNodes) {
- for (var i = 0; i < nodeData.childNodes.length; i++)
- this.deserializeNode(nodeData.childNodes[i], node);
- }
-
- return node;
- }
-}
-
-class TreeMirrorClient {
- private nextId:number;
-
- private mutationSummary:MutationSummary;
- private knownNodes:NodeMap;
-
- constructor(public target:Node, public mirror:any, testingQueries:Query[]) {
- this.nextId = 1;
- this.knownNodes = new MutationSummary.NodeMap();
-
- var rootId = this.serializeNode(target).id;
- var children:NodeData[] = [];
- for (var child = target.firstChild; child; child = child.nextSibling)
- children.push(this.serializeNode(child, true));
-
- this.mirror.initialize(rootId, children);
-
- var self = this;
-
- var queries = [{ all: true }];
-
- if (testingQueries)
- queries = queries.concat(testingQueries);
-
- this.mutationSummary = new MutationSummary({
- rootNode: target,
- callback: (summaries:Summary[]) => {
- this.applyChanged(summaries);
- },
- queries: queries
- });
- }
-
-
- disconnect() {
- if (this.mutationSummary) {
- this.mutationSummary.disconnect();
- this.mutationSummary = undefined;
- }
- }
-
- private rememberNode(node:Node):number {
- var id = this.nextId++;
- this.knownNodes.set(node, id);
- return id;
- }
-
- private forgetNode(node:Node) {
- this.knownNodes.delete(node);
- }
-
- private serializeNode(node:Node, recursive?:boolean):NodeData {
- if (node === null)
- return null;
-
- var id = this.knownNodes.get(node);
- if (id !== undefined) {
- return { id: id };
- }
-
- var data:NodeData = {
- nodeType: node.nodeType,
- id: this.rememberNode(node)
- };
-
- switch(data.nodeType) {
- case Node.DOCUMENT_TYPE_NODE:
- var docType = node;
- data.name = docType.name;
- data.publicId = docType.publicId;
- data.systemId = docType.systemId;
- break;
-
- case Node.COMMENT_NODE:
- case Node.TEXT_NODE:
- data.textContent = node.textContent;
- break;
-
- case Node.ELEMENT_NODE:
- var elm = node;
- data.tagName = elm.tagName;
- data.attributes = {};
- for (var i = 0; i < elm.attributes.length; i++) {
- var attr = elm.attributes[i];
- data.attributes[attr.name] = attr.value;
- }
-
- if (recursive && elm.childNodes.length) {
- data.childNodes = [];
-
- for (var child = elm.firstChild; child; child = child.nextSibling)
- data.childNodes.push(this.serializeNode(child, true));
- }
- break;
- }
-
- return data;
- }
-
- private serializeAddedAndMoved(added:Node[],
- reparented:Node[],
- reordered:Node[]):PositionData[] {
- var all = added.concat(reparented).concat(reordered);
-
- var parentMap = new MutationSummary.NodeMap>();
-
- all.forEach((node) => {
- var parent = node.parentNode;
- var children = parentMap.get(parent)
- if (!children) {
- children = new MutationSummary.NodeMap();
- parentMap.set(parent, children);
- }
-
- children.set(node, true);
- });
-
- var moved:PositionData[] = [];
-
- parentMap.keys().forEach((parent) => {
- var children = parentMap.get(parent);
-
- var keys = children.keys();
- while (keys.length) {
- var node = keys[0];
- while (node.previousSibling && children.has(node.previousSibling))
- node = node.previousSibling;
-
- while (node && children.has(node)) {
- var data = this.serializeNode(node);
- data.previousSibling = this.serializeNode(node.previousSibling);
- data.parentNode = this.serializeNode(node.parentNode);
- moved.push(data);
- children.delete(node);
- node = node.nextSibling;
- }
-
- var keys = children.keys();
- }
- });
-
- return moved;
- }
-
- private serializeAttributeChanges(attributeChanged:StringMap):AttributeData[] {
- var map = new MutationSummary.NodeMap();
-
- Object.keys(attributeChanged).forEach((attrName) => {
- attributeChanged[attrName].forEach((element) => {
- var record = map.get(element);
- if (!record) {
- record = this.serializeNode(element);
- record.attributes = {};
- map.set(element, record);
- }
-
- record.attributes[attrName] = element.getAttribute(attrName);
- });
- });
-
- return map.keys().map((node:Node) => {
- return map.get(node);
- });
- }
-
- applyChanged(summaries:Summary[]) {
- var summary:Summary = summaries[0]
-
- var removed:NodeData[] = summary.removed.map((node:Node) => {
- return this.serializeNode(node);
- });
-
- var moved:PositionData[] =
- this.serializeAddedAndMoved(summary.added,
- summary.reparented,
- summary.reordered);
-
- var attributes:AttributeData[] =
- this.serializeAttributeChanges(summary.attributeChanged);
-
- var text:TextData[] = summary.characterDataChanged.map((node:Node) => {
- var data = this.serializeNode(node);
- data.textContent = node.textContent;
- return data;
- });
-
- this.mirror.applyChanged(removed, moved, attributes, text);
-
- summary.removed.forEach((node:Node) => {
- this.forgetNode(node);
- });
- }
-}
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/CHANGELOG.md b/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/CHANGELOG.md
deleted file mode 100644
index 6d9eb1e..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/CHANGELOG.md
+++ /dev/null
@@ -1,642 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file.
-
-The document follows the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com).
-
-
-====
-
-
-## UNRELEASED 3.0.0
-
-### Added
-- added `'random'` option and `randomize()` method to `SVG.Color` -> __TODO!__
-- added `precision()` method to round numeric element attributes -> __TODO!__
-- added specs for `SVG.FX` -> __TODO!__
-
-### Changed
-- made transform-methods relative as default (breaking change)
-- changed SVG() to use querySelector instead of getElementById (breaking change) -> __TODO!__
-- made `parents()` method on `SVG.Element` return an instance of SVG.Set (breaking change) -> __TODO!__
-- replaced static reference to `masker` in `SVG.Mask` with the `masker()` method (breaking change) -> __TODO!__
-- replaced static reference to `clipper` in `SVG.ClipPath` with the `clipper()` method (breaking change) -> __TODO!__
-- replaced static reference to `targets` in `SVG.Mask` and `SVG.ClipPath` with the `targets()` method (breaking change) -> __TODO!__
-- moved all regexes to `SVG.regex` (in color, element, pointarray, style, transform and viewbox) -> __TODO!__
-
-### Fixed
-- fixed a bug in clipping and masking where empty nodes persists after removal -> __TODO!__
-- fixed a bug in IE11 with `mouseenter` and `mouseleave` -> __TODO!__
-
-
-## [2.6.1] - 2017-04-25
-
-### Fixed
-- fixed a bug in path parser which made it stop parsing when hitting z command (#665)
-
-## [2.6.0] - 2017-04-21
-
-### Added
-- added `options` object to `SVG.on()` and `el.on()` (#661)
-
-### Changed
-- back to sloppy mode because of problems with plugins (#660)
-
-
-## [2.5.3] - 2017-04-15
-
-### Added
-- added gitter badge in readme
-
-
-### Fixed
-- fixed svg.js.d.ts (#644 #648)
-- fixed bug in `el.flip()` which causes an error when calling flip without any argument
-
-### Removed
-- component.json (#652)
-
-
-## [2.5.2] - 2017-04-11
-
-### Changed
-- SVG.js is now running in strict mode
-
-### Fixed
-- `clear()` does not remove the parser in svg documents anymore
-- `len` not declared in FX module, making it a global variable (9737e8a)
-- `bbox` not declared in SVG.Box.transform in the Box module (131df0f)
-- `namespace` not declared in the Event module (e89c97e)
-
-
-## [2.5.1] - 2017-03-27
-
-### Changed
-- make svgjs ready to be used on the server
-
-### Fixed
-- fixed `SVG.PathArray.parse` that did not correctly parsed flat arrays
-- prevented unnecessary parsing of point or path strings
-
-
-## [2.5.0] - 2017-03-10
-
-### Added
-- added a plot and array method to `SVG.TextPath` (#582)
-- added `clone()` method to `SVG.Array/PointArray/PathArray` (#590)
-- added `font()` method to `SVG.Tspan`
-- added `SVG.Box()`
-- added `transform()` method to boxes
-- added `event()` to `SVG.Element` to retrieve the event that was fired last on the element (#550)
-
-### Changed
-- changed CHANGELOG to follow the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com) (#578)
-- make the method plot a getter when no parameter is passed for `SVG.Polyline`,`SVG.Polygon`, `SVG.Line`, `SVG.Path` (related #547)
-- allow `SVG.PointArray` to be passed flat array
-- change the regexp `SVG.PointArray` use to parse string to allow more flexibility in the way spaces and commas can be used
-- allow `plot` to be called with 4 parameters when animating an `SVG.Line`
-- relative value for `SVG.Number` are now calculated in its `morph` method (related #547)
-- clean up the implementation of the `initAnimation` method of the FX module (#547, #552, #584)
-- deprecated `.tbox()`. `.tbox()` now map to `.rbox()`. If you are using `.tbox()`, you can substitute it with `.rbox()` (#594, #602)
-- all boxes now accept 4 values or an object on creation
-- `el.rbox()` now always returns the right boxes in screen coordinates and has an additional paramater to transform the box into other coordinate systems
-- `font()` method can now be used like `attr()` method (#620)
-- events are now cancelable by default (#550)
-
-### Fixed
-- fixed a bug in the plain morphing part of `SVG.MorphObj` that is in the FX module
-- fixed bug which produces an error when removing an event from a node which was formerly removed with a global `off()` (#518)
-- fixed a bug in `size()` for poly elements when their height/width is zero (#505)
-- viewbox now also accepts strings and arrays as constructor arguments
-- `SVG.Array` now accepts a comma seperated string and returns array of numbers instead of strings
-- `SVG.Matrix` now accepts an array as input
-- `SVG.Element.matrix()` now accepts also 6 values
-- `dx()/dy()` now accepts percentage values, too but only if the value on the element is already percentage
-- `flip()` now flips on both axis when no parameter is passed
-- fixed bug with `documentElement.contains()` in IE
-- fixed offset produced by svg parser (#553)
-- fixed a bug with clone which didnt copy over dom data (#621)
-
-
-## [2.4.0] - 2017-01-14
-
-### Added
-- added support for basic path animations (#561)
-
-
-## [2.3.7] - 2017-01-14
-
-### Added
-- added code coverage https://coveralls.io/github/svgdotjs/svg.js (3e614d4)
-- added `npm run test:quick` which aim at being fast rather than correct - great for git hooks (981ce24)
-
-### Changed
-- moved project to [svgdotjs](https://github.com/svgdotjs)
-- made matrixify work with transformation chain separated by commas (#543)
-- updated dev dependencies; request and gulp-chmod - `npm run build` now requires nodejs 4.x+
-
-### Fixed
-- fixed `SVG.Matrix.skew()` (#545)
-- fixed broken animations, if using polyfills for es6/7 proposals (#504)
-- fixed and improved `SVG.FX.dequeue()` (#546)
-- fixed an error in `SVG.FX.step`, if custom properties is added to `Array.prototype` (#549)
-
-
-## [2.3.6] - 2016-10-21
-
-### Changed
-- make SVG.FX.loop modify the last situation instead of the current one (#532)
-
-### Fixed
-- fixed leading and trailing space in SVG.PointArray would return NaN for some points (695f26a) (#529)
-- fixed test of `SVG.FX.afterAll` (#534)
-- fixed `SVG.FX.speed()` (#536)
-
-
-## [2.3.5] - 2016-10-13
-
-### Added
-- added automated unit tests via [Travis](https://travis-ci.org/svgdotjs/svg.js) (#527)
-- added `npm run build` to build a new version of SVG.js without requiring gulp to be globally installed
-
-### Changed
-- calling `fill()`, `stroke()` without an argument is now a nop
-- Polygon now accepts comma less points to achieve parity with Adobe Illustrator (#529)
-- updated dependencies
-
-
-## [2.3.4] - 2016-08-04
-
-### Changed
-- reworked parent module for speed improvemenents
-- reworked `filterSVGElements` utility to use a for loop instead of the native filter function
-
-
-## [2.3.3] - 2016-08-02
-
-### Added
-- add error callback on image loading (#508)
-
-### Fixed
-- fixed bug when getting bbox of text elements which are not in the dom (#514)
-- fixed bug when getting bbox of element which is hidden with css (#516)
-
-
-## [2.3.2] - 2016-06-21
-
-### Added
-- added specs for `SVG.ViewBox`
-- added `parent` parameter for `clone()`
-- added spec for mentioned issue
-
-### Fixed
-- fixed string parsing in viewbox (#483)
-- fixed bbox when element is not in the dom (#480)
-- fixed line constructor which doesn't work with Array as input (#487)
-- fixed problem in IE with `document.contains` (#490) related to (#480)
-- fixed `undo` when undoing transformations (#494)
-
-
-## [2.3.1] - 2016-05-05
-
-### Added
-- added typings for svg.js (#470)
-
-### Fixed
-- fixed `SVG.morph()` (#473)
-- fixed parser error (#471)
-- fixed bug in `SVG.Color` with new fx
-- fixed `radius()` for circles when animating and other related code (#477)
-- fixed bug where `stop(true)` throws an error when element is not animated (#475)
-- fixed bug in `add()` when altering svgs with whitespaces
-- fixed bug in `SVG.Doc().create` where size was set to 100% even if size was already specified
-- fixed bug in `parse()` from `SVG.PathArray` which does not correctly handled `S` and `T` (#485)
-
-
-## [2.3.0] - 2016-03-30
-
-### Added
-- added `SVG.Point` which serves as Wrapper to the native `SVGPoint` (#437)
-- added `element.point(x,y)` which transforms a point from screen coordinates to the elements space (#403)
-- added `element.is()` which helps to check for the object instance faster (instanceof check)
-- added more fx specs
-
-### Changed
-- textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450)
-- fx module rewritten to support animation chaining and several other stuff (see docs)
-
-### Fixed
-- fixed `svgjs:data` attribute which was not set properly in all browsers (#428)
-- fixed `isNumber` and `numberAndUnit` regex (#405)
-- fixed error where a parent node is not found when loading an image but the canvas was cleared (#447)
-- fixed absolute transformation animations (not perfect but better)
-- fixed event listeners which didnt work correctly when identic funtions used
-
-
-## [2.2.5] - 2015-12-29
-
-### Added
-- added check for existence of node (#431)
-
-### Changed
-- `group.move()` now allows string numbers as input (#433)
-- `matrixify()` will not apply the calculated matrix to the node anymore
-
-
-## [2.2.4] - 2015-12-12
-
-### Fixed
-- fixed `transform()` which returns the matrix values (a-f) now, too (#423)
-- double newlines (\n\n) are correctly handled as blank line from `text()`
-- fixed use of scrollX vs pageXOffset in `rbox()` (#425)
-- fixed target array in mask and clip which was removed instead of reinitialized (#429)
-
-
-## [2.2.3] - 2015-11-30
-
-### Fixed
-- fixed null check in image (see 2.2.2)
-- fixed bug related to the new path parser (see 2.2.2)
-- fixed amd loader (#412)
-
-
-## [2.2.2] - 2015-11-28
-
-### Added
-- added null check in image onload callback (#415)
-
-### Changed
-- documentation rework (#407) [thanks @snowyplover]
-
-### Fixed
-- fixed leading point bug in path parsing (#416)
-
-
-## [2.2.1] - 2015-11-18
-
-### Added
-- added workaround for `SvgPathSeg` which is removed in Chrome 48 (#409)
-- added `gbox()` to group to get bbox with translation included (#405)
-
-### Fixed
-- fixed dom data which was not cleaned up properly (#398)
-
-
-## [2.2.0] - 2015-11-06
-
-### Added
-- added `ungroup()/flatten()` (#238), `toParent()` and `toDoc()`
-- added UMD-Wrapper with possibility to pass custom window object (#352)
-- added `morph()` method for paths via plugin [svg.pathmorphing.js](https://github.com/Fuzzyma/svg.pathmorphing.js)
-- added support for css selectors within the `parent()` method
-- added `parents()` method to get an array of all parenting elements
-
-### Changed
-- svgjs now saves crucial data in the dom before export and restores them when element is adopted
-
-### Fixed
-- fixed pattern and gradient animation (#385)
-- fixed mask animation in Firefox (#287)
-- fixed return value of `text()` after import/clone (#393)
-
-
-## [2.1.1] - 2015-10-03
-
-### Added
-- added custom context binding to event callback (default is the element the event is bound to)
-
-
-## [2.1.0] - 2015-09-20
-
-### Added
-- added transform to pattern and gradients (#383)
-
-### Fixed
-- fixed clone of textnodes (#369)
-- fixed transformlists in IE (#372)
-- fixed typo that leads to broken gradients (#370)
-- fixed animate radius for circles (#367)
-
-
-## [2.0.2] - 2015-06-22
-
-### Fixed
-- Fixed zoom consideration in circle and ellipse
-
-
-## [2.0.1] - 2015-06-21
-
-### Added
-- added possibility to remove all events from a certain namespace
-
-### Fixed
-- fixed bug with `doc()` which always should return root svg
-- fixed bug in `SVG.FX` when animating with `plot()`
-
-### Removed
-- removed target reference from use which caused bugs in `dmove()` and `use()` with external file
-- removed scale consideration in `move()` duo to incompatibilities with other move-functions e.g. in `SVG.PointArray`
-
-
-## [2.0.0] - 2015-06-11
-
-### Added
-- implemented an SVG adoption system to be able to manipulate existing SVG's not created with svg.js
-- added polyfill for IE9 and IE10 custom events [thanks @Fuzzyma]
-- added DOM query selector with the `select()` method globally or on parent elements
-- added the intentionally neglected `SVG.Circle` element
-- added `rx()` and `ry()` to `SVG.Rect`, `SVG.Circle`, `SVG.Ellispe` and `SVG.FX`
-- added support to clone manually built text elements
-- added `svg.wiml.js` plugin to plugins list
-- added `ctm()` method to for matrix-centric transformations
-- added `morph()` method to `SVG.Matrix`
-- added support for new matrix system to `SVG.FX`
-- added `native()` method to elements and matrix to get to the native api
-- added `untransform()` method to remove all transformations
-- added raw svg import functionality with the `svg()` method
-- added coding style description to README
-- added reverse functionality for animations
-- documented the `situation` object in `SVG.FX`
-- added distinction between relative and absolute matrix transformations
-- implemented the `element()` method using the `SVG.Bare` class to create elements that are not described by SVG.js
-- added `w` and `h` properties as shorthand for `width` and `height` to `SVG.BBox`
-- added `SVG.TBox` to get a bounding box that is affected by transformation values
-- added event-based or complete detaching of event listeners in `off()` method
-
-### Changed
-- changed `parent` reference on elements to `parent()` method
-- using `CustomEvent` instead of `Event` to be able to fire events with a `detail` object [thanks @Fuzzyma]
-- renamed `SVG.TSpan` class to `SVG.Tspan` to play nice with the adoption system
-- completely reworked `clone()` method to use the adoption system
-- completely reworked transformations to be chainable and more true to their nature
-- changed `lines` reference to `lines()` on `SVG.Text`
-- changed `track` reference to `track()` on `SVG.Text`
-- changed `textPath` reference to `textPath()` on `SVG.Text`
-- changed `array` reference to `array()` method on `SVG.Polyline`, `SVG.Polygon` and `SVG.Path`
-- reworked sup-pixel offset implementation to be more compact
-- switched from Ruby's `rake` to Node's `gulp` for building [thanks to Alex Ewerlöf]
-- changed `to()` method to `at()` method in `SVG.FX`
-- renamed `SVG.SetFX` to `SVG.FX.Set`
-- reworked `SVG.Number` to return new instances with calculations rather than itself
-- reworked animatable matrix rotations
-- removed `SVG.Symbol` but kept the `symbol()` method using the new `element()` method
-
-### Fixed
-- fixed bug in `radius()` method when `y` value equals `0`
-- fixed a bug where events are not detached properly
-
-
-## [1.0.0-rc.9] - 2014-06-17
-
-### Added
-- added `SVG.Marker`
-- added `SVG.Symbol`
-- added `first()` and `last()` methods to `SVG.Set`
-- added `length()` method to `SVG.Text` and `SVG.TSpan` to calculate total text length
-- added `reference()` method to get referenced elements from a given attribute value
-
-### Changed
-- `SVG.get()` will now also fetch elements with a `xlink:href="#elementId"` or `url(#elementId)` value given
-
-### Fixed
-- fixed infinite loop in viewbox when element has a percentage width / height [thanks @shabegger]
-
-
-## [1.0.0-rc.8] - 2014-06-12
-
-### Fixed
-- fixed bug in `SVG.off`
-- fixed offset by window scroll position in `rbox()` [thanks @bryhoyt]
-
-
-## [1.0.0-rc.7] - 2014-06-11
-
-### Added
-- added `classes()`, `hasClass()`, `addClass()`, `removeClass()` and `toggleClass()` [thanks @pklingem]
-
-### Changed
-- binding events listeners to svg.js instance
-- calling `after()` when calling `stop(true)` (fulfill flag) [thanks @vird]
-- text element fires `rebuild` event whenever the `rebuild()` method is called
-
-### Fixed
-- fixed a bug where `Element#style()` would not save empty values in IE11 [thanks @Shtong]
-- fixed `SVG is not defined error` [thanks @anvaka]
-- fixed a bug in `move()`on text elements with a string based value
-- fix for `text()` method on text element when acting as getter [thanks @Lochemage]
-- fix in `style()` method with a css string [thanks @TobiasHeckel]
-
-
-## [1.0.0-rc.6] - 2014-03-03
-
-### Added
-- added `leading()` method to `SVG.FX`
-- added `reverse()` method to `SVG.Array` (and thereby also to `SVG.PointArray` and `SVG.PathArray`)
-- added `fulfill` option to `stop()` method in `SVG.FX` to finalise animations
-- added more output values to `bbox()` and `rbox()` methods
-
-### Changed
-- fine-tuned text element positioning
-- calling `at()` method directly on morphable svg.js instances in `SVG.FX` module
-- moved most `_private` methods to local named functions
-- moved helpers to a separate file
-
-### Fixed
-- fixed a bug in text `dy()` method
-
-### Removed
-- removed internal representation for `style`
-
-
-## [1.0.0-rc.5] - 2014-02-14
-
-### Added
-- added `plain()` method to `SVG.Text` element to add plain text content, without tspans
-- added `plain()` method to parent elements to create a text element without tspans
-- added `build()` to enable/disable build mode
-
-### Changed
-- updated `SVG.TSpan` to accept nested tspan elements, not unlike the `text()` method in `SVG.Text`
-- removed the `relative()` method in favour of `dx()`, `dy()` and `dmove()`
-- switched form objects to arrays in `SVG.PathArray` for compatibility with other libraries and better performance on parsing and rendering (up-to 48% faster than 1.0.0-rc.4)
-- refined docs on element-specific methods and `SVG.PathArray` structure
-- reworked `leading()` implementation to be more font-size "aware"
-- refactored the `attr` method on `SVG.Element`
-- applied Helvetica as default font
-- building `SVG.FX` class with `SVG.invent()` function
-
-### Removed
-- removed verbose style application to tspans
-
-
-## [1.0.0-rc.4] - 2014-02-04
-
-### Added
-- automatic pattern creation by passing an image url or instance as `fill` attribute on elements
-- added `loaded()` method to image tag
-- added `pointAt()` method to `SVG.Path`, wrapping the native `getPointAtLength()`
-
-### Changed
-- switched to `MAJOR`.`MINOR`.`PATCH` versioning format to play nice with package managers
-- made svg.pattern.js part of the core library
-- moved `length()` method to sugar module
-
-### Fixed
-- fix in `animate('=').to()`
-- fix for arcs in patharray `toString()` method [thanks @dotnetCarpenter]
-
-
-## [v1.0rc3] - 2014-02-03
-
-### Added
-- added the `SVG.invent` function to ease invention of new elements
-- added second values for `animate('2s')`
-- added `length()` mehtod to path, wrapping the native `getTotalLength()`
-
-### Changed
-- using `SVG.invent` to generate core shapes as well for leaner code
-
-### Fixed
-- fix for html-less documents
-- fix for arcs in patharray `toString()` method
-
-
-## [v1.0rc2] - 2014-02-01
-
-### Added
-- added `index()` method to `SVG.Parent` and `SVG.Set`
-- added `morph()` and `at()` methods to `SVG.Number` for unit morphing
-
-### Changed
-- modified `cx()` and `cy()` methods on elements with native `x`, `y`, `width` and `height` attributes for better performance
-
-
-## [v1.0rc1] - 2014-01-31
-
-### Added
-- added `SVG.PathArray` for real path transformations
-- added `bbox()` method to `SVG.Set`
-- added `relative()` method for moves relative to the current position
-- added `morph()` and `at()` methods to `SVG.Color` for color morphing
-
-### Changed
-- enabled proportional resizing on `size()` method with `null` for either `width` or `height` values
-- moved data module to separate file
-- `data()` method now accepts object for for multiple key / value assignments
-
-### Removed
-- removed `unbiased` system for paths
-
-
-## [v0.38] - 2014-01-28
-
-### Added
-- added `loop()` method to `SVG.FX`
-
-### Changed
-- switched from `setInterval` to `requestAnimFrame` for animations
-
-
-## [v0.37] - 2014-01-26
-
-### Added
-- added `get()` to `SVG.Set`
-
-### Changed
-- moved `SVG.PointArray` to a separate file
-
-
-## [v0.36] - 2014-01-25
-
-### Added
-- added `linkTo()`, `addTo()` and `putIn()` methods on `SVG.Element`
-
-### Changed
-- provided more detailed documentation on parent elements
-
-### Fixed
-
-
-## [v0.35] - 2014-01-23
-
-### Added
-- added `SVG.A` element with the `link()`
-
-
-## [v0.34] - 2014-01-23
-
-### Added
-- added `pause()` and `play()` to `SVG.FX`
-
-### Changed
-- storing animation values in `situation` object
-
-
-## [v0.33] - 2014-01-22
-
-### Added
-- added `has()` method to `SVG.Set`
-- added `width()` and `height()` as setter and getter methods on all shapes
-- added `replace()` method to elements
-- added `radius()` method to `SVG.Rect` and `SVG.Ellipse`
-- added reference to parent node in defs
-
-### Changed
-- moved sub-pixel offset fix to be an optional method (e.g. `SVG('drawing').fixSubPixelOffset()`)
-- merged plotable.js and path.js
-
-
-## [v0.32]
-
-### Added
-- added library to [cdnjs](http://cdnjs.com)
-
-
-
-[2.6.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.0
-[2.5.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.3
-[2.5.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.2
-[2.5.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.1
-[2.5.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.0
-[2.4.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.4.0
-
-[2.3.7]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.7
-[2.3.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.6
-[2.3.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.5
-[2.3.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.4
-[2.3.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.3
-[2.3.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.2
-[2.3.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.1
-[2.3.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.0
-
-[2.2.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.5
-[2.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.4
-[2.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.3
-[2.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.2
-[2.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.1
-[2.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.0
-
-[2.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.1
-[2.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.0
-
-[2.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.2
-[2.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.1
-[2.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.0
-
-[1.0.0-rc.9]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.9
-[1.0.0-rc.8]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.8
-[1.0.0-rc.7]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.7
-[1.0.0-rc.6]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.6
-[1.0.0-rc.5]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.5
-[1.0.0-rc.4]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.4
-[v1.0rc3]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc3
-[v1.0rc2]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc2
-[v1.0rc1]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc1
-
-[v0.38]: https://github.com/svgdotjs/svg.js/releases/tag/v0.38
-[v0.37]: https://github.com/svgdotjs/svg.js/releases/tag/v0.37
-[v0.36]: https://github.com/svgdotjs/svg.js/releases/tag/v0.36
-[v0.35]: https://github.com/svgdotjs/svg.js/releases/tag/v0.35
-[v0.34]: https://github.com/svgdotjs/svg.js/releases/tag/v0.34
-[v0.33]: https://github.com/svgdotjs/svg.js/releases/tag/v0.33
-[v0.32]: https://github.com/svgdotjs/svg.js/releases/tag/v0.32
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/LICENSE.txt b/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/LICENSE.txt
deleted file mode 100644
index 148b70a..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-Copyright (c) 2012-2017 Wout Fierens
-https://svgdotjs.github.io/
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/README.md b/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/README.md
deleted file mode 100644
index b88c5a5..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# SVG.js
-
-[](https://travis-ci.org/svgdotjs/svg.js)
-[](https://coveralls.io/github/svgdotjs/svg.js?branch=master)
-[](https://cdnjs.com/libraries/svg.js)
-[](https://gitter.im/svgdotjs/svg.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-
-__A lightweight library for manipulating and animating SVG, without any dependencies.__
-
-SVG.js is licensed under the terms of the MIT License.
-
-## Installation
-
-#### Bower:
-
-`bower install svg.js`
-
-#### Node:
-
-`npm install svg.js`
-
-#### Cdnjs:
-
-[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js)
-
-## Documentation
-Check [https://svgdotjs.github.io](https://svgdotjs.github.io/) to learn more.
-
-[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=pay%40woutfierens.com&lc=US&item_name=SVG.JS¤cy_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest)
diff --git a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/dist/svg.js b/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/dist/svg.js
deleted file mode 100644
index d2fd5d3..0000000
--- a/files/plugin-HeatmapSessionRecording-5.2.6/libs/svg.js/dist/svg.js
+++ /dev/null
@@ -1,5518 +0,0 @@
-/*!
-* svg.js - A lightweight library for manipulating and animating SVG.
-* @version 2.6.1
-* https://svgdotjs.github.io/
-*
-* @copyright Wout Fierens
-* @license MIT
-*
-* BUILT: Tue Apr 25 2017 11:58:09 GMT+0200 (Mitteleuropäische Sommerzeit)
-*/;
-(function(root, factory) {
- /* istanbul ignore next */
- if (typeof define === 'function' && define.amd) {
- define(function(){
- return factory(root, root.document)
- })
- } else if (typeof exports === 'object') {
- module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) }
- } else {
- root.SVG = factory(root, root.document)
- }
-}(typeof window !== "undefined" ? window : this, function(window, document) {
-
-// The main wrapping element
-var SVG = this.SVG = function(element) {
- if (SVG.supported) {
- element = new SVG.Doc(element)
-
- if(!SVG.parser.draw)
- SVG.prepare()
-
- return element
- }
-}
-
-// Default namespaces
-SVG.ns = 'http://www.w3.org/2000/svg'
-SVG.xmlns = 'http://www.w3.org/2000/xmlns/'
-SVG.xlink = 'http://www.w3.org/1999/xlink'
-SVG.svgjs = 'http://svgjs.com/svgjs'
-
-// Svg support test
-SVG.supported = (function() {
- return !! document.createElementNS &&
- !! document.createElementNS(SVG.ns,'svg').createSVGRect
-})()
-
-// Don't bother to continue if SVG is not supported
-if (!SVG.supported) return false
-
-// Element id sequence
-SVG.did = 1000
-
-// Get next named element id
-SVG.eid = function(name) {
- return 'Svgjs' + capitalize(name) + (SVG.did++)
-}
-
-// Method for element creation
-SVG.create = function(name) {
- // create element
- var element = document.createElementNS(this.ns, name)
-
- // apply unique id
- element.setAttribute('id', this.eid(name))
-
- return element
-}
-
-// Method for extending objects
-SVG.extend = function() {
- var modules, methods, key, i
-
- // Get list of modules
- modules = [].slice.call(arguments)
-
- // Get object with extensions
- methods = modules.pop()
-
- for (i = modules.length - 1; i >= 0; i--)
- if (modules[i])
- for (key in methods)
- modules[i].prototype[key] = methods[key]
-
- // Make sure SVG.Set inherits any newly added methods
- if (SVG.Set && SVG.Set.inherit)
- SVG.Set.inherit()
-}
-
-// Invent new element
-SVG.invent = function(config) {
- // Create element initializer
- var initializer = typeof config.create == 'function' ?
- config.create :
- function() {
- this.constructor.call(this, SVG.create(config.create))
- }
-
- // Inherit prototype
- if (config.inherit)
- initializer.prototype = new config.inherit
-
- // Extend with methods
- if (config.extend)
- SVG.extend(initializer, config.extend)
-
- // Attach construct method to parent
- if (config.construct)
- SVG.extend(config.parent || SVG.Container, config.construct)
-
- return initializer
-}
-
-// Adopt existing svg elements
-SVG.adopt = function(node) {
- // check for presence of node
- if (!node) return null
-
- // make sure a node isn't already adopted
- if (node.instance) return node.instance
-
- // initialize variables
- var element
-
- // adopt with element-specific settings
- if (node.nodeName == 'svg')
- element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc
- else if (node.nodeName == 'linearGradient')
- element = new SVG.Gradient('linear')
- else if (node.nodeName == 'radialGradient')
- element = new SVG.Gradient('radial')
- else if (SVG[capitalize(node.nodeName)])
- element = new SVG[capitalize(node.nodeName)]
- else
- element = new SVG.Element(node)
-
- // ensure references
- element.type = node.nodeName
- element.node = node
- node.instance = element
-
- // SVG.Class specific preparations
- if (element instanceof SVG.Doc)
- element.namespace().defs()
-
- // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
- element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
-
- return element
-}
-
-// Initialize parsing element
-SVG.prepare = function() {
- // Select document body and create invisible svg element
- var body = document.getElementsByTagName('body')[0]
- , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0)
-
- // Create parser object
- SVG.parser = {
- body: body || document.documentElement
- , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').node
- , poly: draw.polyline().node
- , path: draw.path().node
- , native: SVG.create('svg')
- }
-}
-
-SVG.parser = {
- native: SVG.create('svg')
-}
-
-document.addEventListener('DOMContentLoaded', function() {
- if(!SVG.parser.draw)
- SVG.prepare()
-}, false)
-
-// Storage for regular expressions
-SVG.regex = {
- // Parse unit value
- numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
-
- // Parse hex value
-, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
-
- // Parse rgb value
-, rgb: /rgb\((\d+),(\d+),(\d+)\)/
-
- // Parse reference id
-, reference: /#([a-z0-9\-_]+)/i
-
- // splits a transformation chain
-, transforms: /\)\s*,?\s*/
-
- // Whitespace
-, whitespace: /\s/g
-
- // Test hex value
-, isHex: /^#[a-f0-9]{3,6}$/i
-
- // Test rgb value
-, isRgb: /^rgb\(/
-
- // Test css declaration
-, isCss: /[^:]+:[^;]+;?/
-
- // Test for blank string
-, isBlank: /^(\s+)?$/
-
- // Test for numeric string
-, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
-
- // Test for percent value
-, isPercent: /^-?[\d\.]+%$/
-
- // Test for image url
-, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
-
- // split at whitespace and comma
-, delimiter: /[\s,]+/
-
- // The following regex are used to parse the d attribute of a path
-
- // Matches all hyphens which are not after an exponent
-, hyphen: /([^e])\-/gi
-
- // Replaces and tests for all path letters
-, pathLetters: /[MLHVCSQTAZ]/gi
-
- // yes we need this one, too
-, isPathLetter: /[MLHVCSQTAZ]/i
-
- // matches 0.154.23.45
-, numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi
-
- // matches .
-, dots: /\./g
-}
-
-SVG.utils = {
- // Map function
- map: function(array, block) {
- var i
- , il = array.length
- , result = []
-
- for (i = 0; i < il; i++)
- result.push(block(array[i]))
-
- return result
- }
-
- // Filter function
-, filter: function(array, block) {
- var i
- , il = array.length
- , result = []
-
- for (i = 0; i < il; i++)
- if (block(array[i]))
- result.push(array[i])
-
- return result
- }
-
- // Degrees to radians
-, radians: function(d) {
- return d % 360 * Math.PI / 180
- }
-
- // Radians to degrees
-, degrees: function(r) {
- return r * 180 / Math.PI % 360
- }
-
-, filterSVGElements: function(nodes) {
- return this.filter( nodes, function(el) { return el instanceof window.SVGElement })
- }
-
-}
-
-SVG.defaults = {
- // Default attribute values
- attrs: {
- // fill and stroke
- 'fill-opacity': 1
- , 'stroke-opacity': 1
- , 'stroke-width': 0
- , 'stroke-linejoin': 'miter'
- , 'stroke-linecap': 'butt'
- , fill: '#000000'
- , stroke: '#000000'
- , opacity: 1
- // position
- , x: 0
- , y: 0
- , cx: 0
- , cy: 0
- // size
- , width: 0
- , height: 0
- // radius
- , r: 0
- , rx: 0
- , ry: 0
- // gradient
- , offset: 0
- , 'stop-opacity': 1
- , 'stop-color': '#000000'
- // text
- , 'font-size': 16
- , 'font-family': 'Helvetica, Arial, sans-serif'
- , 'text-anchor': 'start'
- }
-
-}
-// Module for color convertions
-SVG.Color = function(color) {
- var match
-
- // initialize defaults
- this.r = 0
- this.g = 0
- this.b = 0
-
- if(!color) return
-
- // parse color
- if (typeof color === 'string') {
- if (SVG.regex.isRgb.test(color)) {
- // get rgb values
- match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace,''))
-
- // parse numeric values
- this.r = parseInt(match[1])
- this.g = parseInt(match[2])
- this.b = parseInt(match[3])
-
- } else if (SVG.regex.isHex.test(color)) {
- // get hex values
- match = SVG.regex.hex.exec(fullHex(color))
-
- // parse numeric values
- this.r = parseInt(match[1], 16)
- this.g = parseInt(match[2], 16)
- this.b = parseInt(match[3], 16)
-
- }
-
- } else if (typeof color === 'object') {
- this.r = color.r
- this.g = color.g
- this.b = color.b
-
- }
-
-}
-
-SVG.extend(SVG.Color, {
- // Default to hex conversion
- toString: function() {
- return this.toHex()
- }
- // Build hex value
-, toHex: function() {
- return '#'
- + compToHex(this.r)
- + compToHex(this.g)
- + compToHex(this.b)
- }
- // Build rgb value
-, toRgb: function() {
- return 'rgb(' + [this.r, this.g, this.b].join() + ')'
- }
- // Calculate true brightness
-, brightness: function() {
- return (this.r / 255 * 0.30)
- + (this.g / 255 * 0.59)
- + (this.b / 255 * 0.11)
- }
- // Make color morphable
-, morph: function(color) {
- this.destination = new SVG.Color(color)
-
- return this
- }
- // Get morphed color at given position
-, at: function(pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // normalise pos
- pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
-
- // generate morphed color
- return new SVG.Color({
- r: ~~(this.r + (this.destination.r - this.r) * pos)
- , g: ~~(this.g + (this.destination.g - this.g) * pos)
- , b: ~~(this.b + (this.destination.b - this.b) * pos)
- })
- }
-
-})
-
-// Testers
-
-// Test if given value is a color string
-SVG.Color.test = function(color) {
- color += ''
- return SVG.regex.isHex.test(color)
- || SVG.regex.isRgb.test(color)
-}
-
-// Test if given value is a rgb object
-SVG.Color.isRgb = function(color) {
- return color && typeof color.r == 'number'
- && typeof color.g == 'number'
- && typeof color.b == 'number'
-}
-
-// Test if given value is a color
-SVG.Color.isColor = function(color) {
- return SVG.Color.isRgb(color) || SVG.Color.test(color)
-}
-// Module for array conversion
-SVG.Array = function(array, fallback) {
- array = (array || []).valueOf()
-
- // if array is empty and fallback is provided, use fallback
- if (array.length == 0 && fallback)
- array = fallback.valueOf()
-
- // parse array
- this.value = this.parse(array)
-}
-
-SVG.extend(SVG.Array, {
- // Make array morphable
- morph: function(array) {
- this.destination = this.parse(array)
-
- // normalize length of arrays
- if (this.value.length != this.destination.length) {
- var lastValue = this.value[this.value.length - 1]
- , lastDestination = this.destination[this.destination.length - 1]
-
- while(this.value.length > this.destination.length)
- this.destination.push(lastDestination)
- while(this.value.length < this.destination.length)
- this.value.push(lastValue)
- }
-
- return this
- }
- // Clean up any duplicate points
-, settle: function() {
- // find all unique values
- for (var i = 0, il = this.value.length, seen = []; i < il; i++)
- if (seen.indexOf(this.value[i]) == -1)
- seen.push(this.value[i])
-
- // set new value
- return this.value = seen
- }
- // Get morphed array at given position
-, at: function(pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // generate morphed array
- for (var i = 0, il = this.value.length, array = []; i < il; i++)
- array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos)
-
- return new SVG.Array(array)
- }
- // Convert array to string
-, toString: function() {
- return this.value.join(' ')
- }
- // Real value
-, valueOf: function() {
- return this.value
- }
- // Parse whitespace separated string
-, parse: function(array) {
- array = array.valueOf()
-
- // if already is an array, no need to parse it
- if (Array.isArray(array)) return array
-
- return this.split(array)
- }
- // Strip unnecessary whitespace
-, split: function(string) {
- return string.trim().split(SVG.regex.delimiter).map(parseFloat)
- }
- // Reverse array
-, reverse: function() {
- this.value.reverse()
-
- return this
- }
-, clone: function() {
- var clone = new this.constructor()
- clone.value = array_clone(this.value)
- return clone
- }
-})
-// Poly points array
-SVG.PointArray = function(array, fallback) {
- SVG.Array.call(this, array, fallback || [[0,0]])
-}
-
-// Inherit from SVG.Array
-SVG.PointArray.prototype = new SVG.Array
-SVG.PointArray.prototype.constructor = SVG.PointArray
-
-SVG.extend(SVG.PointArray, {
- // Convert array to string
- toString: function() {
- // convert to a poly point string
- for (var i = 0, il = this.value.length, array = []; i < il; i++)
- array.push(this.value[i].join(','))
-
- return array.join(' ')
- }
- // Convert array to line object
-, toLine: function() {
- return {
- x1: this.value[0][0]
- , y1: this.value[0][1]
- , x2: this.value[1][0]
- , y2: this.value[1][1]
- }
- }
- // Get morphed array at given position
-, at: function(pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // generate morphed point string
- for (var i = 0, il = this.value.length, array = []; i < il; i++)
- array.push([
- this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos
- , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos
- ])
-
- return new SVG.PointArray(array)
- }
- // Parse point string and flat array
-, parse: function(array) {
- var points = []
-
- array = array.valueOf()
-
- // if it is an array
- if (Array.isArray(array)) {
- // and it is not flat, there is no need to parse it
- if(Array.isArray(array[0])) {
- return array
- }
- } else { // Else, it is considered as a string
- // parse points
- array = array.trim().split(SVG.regex.delimiter).map(parseFloat)
- }
-
- // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
- // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
- if (array.length % 2 !== 0) array.pop()
-
- // wrap points in two-tuples and parse points as floats
- for(var i = 0, len = array.length; i < len; i = i + 2)
- points.push([ array[i], array[i+1] ])
-
- return points
- }
- // Move point string
-, move: function(x, y) {
- var box = this.bbox()
-
- // get relative offset
- x -= box.x
- y -= box.y
-
- // move every point
- if (!isNaN(x) && !isNaN(y))
- for (var i = this.value.length - 1; i >= 0; i--)
- this.value[i] = [this.value[i][0] + x, this.value[i][1] + y]
-
- return this
- }
- // Resize poly string
-, size: function(width, height) {
- var i, box = this.bbox()
-
- // recalculate position of all points according to new size
- for (i = this.value.length - 1; i >= 0; i--) {
- if(box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x
- if(box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
- }
-
- return this
- }
- // Get bounding box of points
-, bbox: function() {
- SVG.parser.poly.setAttribute('points', this.toString())
-
- return SVG.parser.poly.getBBox()
- }
-})
-
-var pathHandlers = {
- M: function(c, p, p0) {
- p.x = p0.x = c[0]
- p.y = p0.y = c[1]
-
- return ['M', p.x, p.y]
- },
- L: function(c, p) {
- p.x = c[0]
- p.y = c[1]
- return ['L', c[0], c[1]]
- },
- H: function(c, p) {
- p.x = c[0]
- return ['H', c[0]]
- },
- V: function(c, p) {
- p.y = c[0]
- return ['V', c[0]]
- },
- C: function(c, p) {
- p.x = c[4]
- p.y = c[5]
- return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]
- },
- S: function(c, p) {
- p.x = c[2]
- p.y = c[3]
- return ['S', c[0], c[1], c[2], c[3]]
- },
- Q: function(c, p) {
- p.x = c[2]
- p.y = c[3]
- return ['Q', c[0], c[1], c[2], c[3]]
- },
- T: function(c, p) {
- p.x = c[0]
- p.y = c[1]
- return ['T', c[0], c[1]]
- },
- Z: function(c, p, p0) {
- p.x = p0.x
- p.y = p0.y
- return ['Z']
- },
- A: function(c, p) {
- p.x = c[5]
- p.y = c[6]
- return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]
- }
-}
-
-var mlhvqtcsa = 'mlhvqtcsaz'.split('')
-
-for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){
- pathHandlers[mlhvqtcsa[i]] = (function(i){
- return function(c, p, p0) {
- if(i == 'H') c[0] = c[0] + p.x
- else if(i == 'V') c[0] = c[0] + p.y
- else if(i == 'A'){
- c[5] = c[5] + p.x,
- c[6] = c[6] + p.y
- }
- else
- for(var j = 0, jl = c.length; j < jl; ++j) {
- c[j] = c[j] + (j%2 ? p.y : p.x)
- }
-
- return pathHandlers[i](c, p, p0)
- }
- })(mlhvqtcsa[i].toUpperCase())
-}
-
-// Path points array
-SVG.PathArray = function(array, fallback) {
- SVG.Array.call(this, array, fallback || [['M', 0, 0]])
-}
-
-// Inherit from SVG.Array
-SVG.PathArray.prototype = new SVG.Array
-SVG.PathArray.prototype.constructor = SVG.PathArray
-
-SVG.extend(SVG.PathArray, {
- // Convert array to string
- toString: function() {
- return arrayToString(this.value)
- }
- // Move path string
-, move: function(x, y) {
- // get bounding box of current situation
- var box = this.bbox()
-
- // get relative offset
- x -= box.x
- y -= box.y
-
- if (!isNaN(x) && !isNaN(y)) {
- // move every point
- for (var l, i = this.value.length - 1; i >= 0; i--) {
- l = this.value[i][0]
-
- if (l == 'M' || l == 'L' || l == 'T') {
- this.value[i][1] += x
- this.value[i][2] += y
-
- } else if (l == 'H') {
- this.value[i][1] += x
-
- } else if (l == 'V') {
- this.value[i][1] += y
-
- } else if (l == 'C' || l == 'S' || l == 'Q') {
- this.value[i][1] += x
- this.value[i][2] += y
- this.value[i][3] += x
- this.value[i][4] += y
-
- if (l == 'C') {
- this.value[i][5] += x
- this.value[i][6] += y
- }
-
- } else if (l == 'A') {
- this.value[i][6] += x
- this.value[i][7] += y
- }
-
- }
- }
-
- return this
- }
- // Resize path string
-, size: function(width, height) {
- // get bounding box of current situation
- var i, l, box = this.bbox()
-
- // recalculate position of all points according to new size
- for (i = this.value.length - 1; i >= 0; i--) {
- l = this.value[i][0]
-
- if (l == 'M' || l == 'L' || l == 'T') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
-
- } else if (l == 'H') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
-
- } else if (l == 'V') {
- this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
-
- } else if (l == 'C' || l == 'S' || l == 'Q') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
- this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x
- this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
-
- if (l == 'C') {
- this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x
- this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
- }
-
- } else if (l == 'A') {
- // resize radii
- this.value[i][1] = (this.value[i][1] * width) / box.width
- this.value[i][2] = (this.value[i][2] * height) / box.height
-
- // move position values
- this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x
- this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
- }
-
- }
-
- return this
- }
- // Test if the passed path array use the same path data commands as this path array
-, equalCommands: function(pathArray) {
- var i, il, equalCommands
-
- pathArray = new SVG.PathArray(pathArray)
-
- equalCommands = this.value.length === pathArray.value.length
- for(i = 0, il = this.value.length; equalCommands && i < il; i++) {
- equalCommands = this.value[i][0] === pathArray.value[i][0]
- }
-
- return equalCommands
- }
- // Make path array morphable
-, morph: function(pathArray) {
- pathArray = new SVG.PathArray(pathArray)
-
- if(this.equalCommands(pathArray)) {
- this.destination = pathArray
- } else {
- this.destination = null
- }
-
- return this
- }
- // Get morphed path array at given position
-, at: function(pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- var sourceArray = this.value
- , destinationArray = this.destination.value
- , array = [], pathArray = new SVG.PathArray()
- , i, il, j, jl
-
- // Animate has specified in the SVG spec
- // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
- for (i = 0, il = sourceArray.length; i < il; i++) {
- array[i] = [sourceArray[i][0]]
- for(j = 1, jl = sourceArray[i].length; j < jl; j++) {
- array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
- }
- // For the two flags of the elliptical arc command, the SVG spec say:
- // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
- // Elliptical arc command as an array followed by corresponding indexes:
- // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
- // 0 1 2 3 4 5 6 7
- if(array[i][0] === 'A') {
- array[i][4] = +(array[i][4] != 0)
- array[i][5] = +(array[i][5] != 0)
- }
- }
-
- // Directly modify the value of a path array, this is done this way for performance
- pathArray.value = array
- return pathArray
- }
- // Absolutize and parse path to array
-, parse: function(array) {
- // if it's already a patharray, no need to parse it
- if (array instanceof SVG.PathArray) return array.valueOf()
-
- // prepare for parsing
- var i, x0, y0, s, seg, arr
- , x = 0
- , y = 0
- , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7, 'Z':0 }
-
- if(typeof array == 'string'){
-
- array = array
- .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
- .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers
- .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen
- .trim() // trim
- .split(SVG.regex.delimiter) // split into array
-
- }else{
- array = array.reduce(function(prev, curr){
- return [].concat.call(prev, curr)
- }, [])
- }
-
- // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
- var arr = []
- , p = new SVG.Point()
- , p0 = new SVG.Point()
- , index = 0
- , len = array.length
-
- do{
- // Test if we have a path letter
- if(SVG.regex.isPathLetter.test(array[index])){
- s = array[index]
- ++index
- // If last letter was a move command and we got no new, it defaults to [L]ine
- }else if(s == 'M'){
- s = 'L'
- }else if(s == 'm'){
- s = 'l'
- }
-
- arr.push(pathHandlers[s].call(null,
- array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
- p, p0
- )
- )
-
- }while(len > index)
-
- return arr
-
- }
- // Get bounding box of path
-, bbox: function() {
- SVG.parser.path.setAttribute('d', this.toString())
-
- return SVG.parser.path.getBBox()
- }
-
-})
-
-// Module for unit convertions
-SVG.Number = SVG.invent({
- // Initialize
- create: function(value, unit) {
- // initialize defaults
- this.value = 0
- this.unit = unit || ''
-
- // parse value
- if (typeof value === 'number') {
- // ensure a valid numeric value
- this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
-
- } else if (typeof value === 'string') {
- unit = value.match(SVG.regex.numberAndUnit)
-
- if (unit) {
- // make value numeric
- this.value = parseFloat(unit[1])
-
- // normalize
- if (unit[5] == '%')
- this.value /= 100
- else if (unit[5] == 's')
- this.value *= 1000
-
- // store unit
- this.unit = unit[5]
- }
-
- } else {
- if (value instanceof SVG.Number) {
- this.value = value.valueOf()
- this.unit = value.unit
- }
- }
-
- }
- // Add methods
-, extend: {
- // Stringalize
- toString: function() {
- return (
- this.unit == '%' ?
- ~~(this.value * 1e8) / 1e6:
- this.unit == 's' ?
- this.value / 1e3 :
- this.value
- ) + this.unit
- }
- , toJSON: function() {
- return this.toString()
- }
- , // Convert to primitive
- valueOf: function() {
- return this.value
- }
- // Add number
- , plus: function(number) {
- number = new SVG.Number(number)
- return new SVG.Number(this + number, this.unit || number.unit)
- }
- // Subtract number
- , minus: function(number) {
- number = new SVG.Number(number)
- return new SVG.Number(this - number, this.unit || number.unit)
- }
- // Multiply number
- , times: function(number) {
- number = new SVG.Number(number)
- return new SVG.Number(this * number, this.unit || number.unit)
- }
- // Divide number
- , divide: function(number) {
- number = new SVG.Number(number)
- return new SVG.Number(this / number, this.unit || number.unit)
- }
- // Convert to different unit
- , to: function(unit) {
- var number = new SVG.Number(this)
-
- if (typeof unit === 'string')
- number.unit = unit
-
- return number
- }
- // Make number morphable
- , morph: function(number) {
- this.destination = new SVG.Number(number)
-
- if(number.relative) {
- this.destination.value += this.value
- }
-
- return this
- }
- // Get morphed number at given position
- , at: function(pos) {
- // Make sure a destination is defined
- if (!this.destination) return this
-
- // Generate new morphed number
- return new SVG.Number(this.destination)
- .minus(this)
- .times(pos)
- .plus(this)
- }
-
- }
-})
-
-
-SVG.Element = SVG.invent({
- // Initialize node
- create: function(node) {
- // make stroke value accessible dynamically
- this._stroke = SVG.defaults.attrs.stroke
- this._event = null
-
- // initialize data object
- this.dom = {}
-
- // create circular reference
- if (this.node = node) {
- this.type = node.nodeName
- this.node.instance = this
-
- // store current attribute value
- this._stroke = node.getAttribute('stroke') || this._stroke
- }
- }
-
- // Add class methods
-, extend: {
- // Move over x-axis
- x: function(x) {
- return this.attr('x', x)
- }
- // Move over y-axis
- , y: function(y) {
- return this.attr('y', y)
- }
- // Move by center over x-axis
- , cx: function(x) {
- return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
- }
- // Move by center over y-axis
- , cy: function(y) {
- return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2)
- }
- // Move element to given x and y values
- , move: function(x, y) {
- return this.x(x).y(y)
- }
- // Move element by its center
- , center: function(x, y) {
- return this.cx(x).cy(y)
- }
- // Set width of element
- , width: function(width) {
- return this.attr('width', width)
- }
- // Set height of element
- , height: function(height) {
- return this.attr('height', height)
- }
- // Set element size to given width and height
- , size: function(width, height) {
- var p = proportionalSize(this, width, height)
-
- return this
- .width(new SVG.Number(p.width))
- .height(new SVG.Number(p.height))
- }
- // Clone element
- , clone: function(parent, withData) {
- // write dom data to the dom so the clone can pickup the data
- this.writeDataToDom()
-
- // clone element and assign new id
- var clone = assignNewId(this.node.cloneNode(true))
-
- // insert the clone in the given parent or after myself
- if(parent) parent.add(clone)
- else this.after(clone)
-
- return clone
- }
- // Remove element
- , remove: function() {
- if (this.parent())
- this.parent().removeElement(this)
-
- return this
- }
- // Replace element
- , replace: function(element) {
- this.after(element).remove()
-
- return element
- }
- // Add element to given container and return self
- , addTo: function(parent) {
- return parent.put(this)
- }
- // Add element to given container and return container
- , putIn: function(parent) {
- return parent.add(this)
- }
- // Get / set id
- , id: function(id) {
- return this.attr('id', id)
- }
- // Checks whether the given point inside the bounding box of the element
- , inside: function(x, y) {
- var box = this.bbox()
-
- return x > box.x
- && y > box.y
- && x < box.x + box.width
- && y < box.y + box.height
- }
- // Show element
- , show: function() {
- return this.style('display', '')
- }
- // Hide element
- , hide: function() {
- return this.style('display', 'none')
- }
- // Is element visible?
- , visible: function() {
- return this.style('display') != 'none'
- }
- // Return id on string conversion
- , toString: function() {
- return this.attr('id')
- }
- // Return array of classes on the node
- , classes: function() {
- var attr = this.attr('class')
-
- return attr == null ? [] : attr.trim().split(SVG.regex.delimiter)
- }
- // Return true if class exists on the node, false otherwise
- , hasClass: function(name) {
- return this.classes().indexOf(name) != -1
- }
- // Add class to the node
- , addClass: function(name) {
- if (!this.hasClass(name)) {
- var array = this.classes()
- array.push(name)
- this.attr('class', array.join(' '))
- }
-
- return this
- }
- // Remove class from the node
- , removeClass: function(name) {
- if (this.hasClass(name)) {
- this.attr('class', this.classes().filter(function(c) {
- return c != name
- }).join(' '))
- }
-
- return this
- }
- // Toggle the presence of a class on the node
- , toggleClass: function(name) {
- return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
- }
- // Get referenced element form attribute value
- , reference: function(attr) {
- return SVG.get(this.attr(attr))
- }
- // Returns the parent element instance
- , parent: function(type) {
- var parent = this
-
- // check for parent
- if(!parent.node.parentNode) return null
-
- // get parent element
- parent = SVG.adopt(parent.node.parentNode)
-
- if(!type) return parent
-
- // loop trough ancestors if type is given
- while(parent && parent.node instanceof window.SVGElement){
- if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
- parent = SVG.adopt(parent.node.parentNode)
- }
- }
- // Get parent document
- , doc: function() {
- return this instanceof SVG.Doc ? this : this.parent(SVG.Doc)
- }
- // return array of all ancestors of given type up to the root svg
- , parents: function(type) {
- var parents = [], parent = this
-
- do{
- parent = parent.parent(type)
- if(!parent || !parent.node) break
-
- parents.push(parent)
- } while(parent.parent)
-
- return parents
- }
- // matches the element vs a css selector
- , matches: function(selector){
- return matches(this.node, selector)
- }
- // Returns the svg node to call native svg methods on it
- , native: function() {
- return this.node
- }
- // Import raw svg
- , svg: function(svg) {
- // create temporary holder
- var well = document.createElement('svg')
-
- // act as a setter if svg is given
- if (svg && this instanceof SVG.Parent) {
- // dump raw svg
- well.innerHTML = ''
-
- // transplant nodes
- for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++)
- this.node.appendChild(well.firstChild.firstChild)
-
- // otherwise act as a getter
- } else {
- // create a wrapping svg element in case of partial content
- well.appendChild(svg = document.createElement('svg'))
-
- // write svgjs data to the dom
- this.writeDataToDom()
-
- // insert a copy of this node
- svg.appendChild(this.node.cloneNode(true))
-
- // return target element
- return well.innerHTML.replace(/^
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageHeatmap/Manage.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageHeatmap/Manage.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageHeatmap/Manage.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageHeatmap/Manage.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Edit.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/Edit.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Edit.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/Edit.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/List.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/List.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/List.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/List.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Manage.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/Manage.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Manage.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/ManageSessionRecording/Manage.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.less b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/SessionRecordingVis/SessionRecordingVis.less
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.less
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/SessionRecordingVis/SessionRecordingVis.less
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/SessionRecordingVis/SessionRecordingVis.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/SessionRecordingVis/SessionRecordingVis.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.less b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/Tooltip/Tooltip.less
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.less
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/Tooltip/Tooltip.less
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.vue b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/Tooltip/Tooltip.vue
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.vue
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/Tooltip/Tooltip.vue
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/getIframeWindow.ts b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/getIframeWindow.ts
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/getIframeWindow.ts
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/getIframeWindow.ts
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/index.ts b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/index.ts
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/index.ts
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/index.ts
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/oneAtATime.ts b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/oneAtATime.ts
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/oneAtATime.ts
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/oneAtATime.ts
diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/types.ts b/files/plugin-HeatmapSessionRecording-5.3.1/vue/src/types.ts
similarity index 100%
rename from files/plugin-HeatmapSessionRecording-5.2.4/vue/src/types.ts
rename to files/plugin-HeatmapSessionRecording-5.3.1/vue/src/types.ts
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/API.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/API.php
deleted file mode 100644
index 9c00f9b..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/API.php
+++ /dev/null
@@ -1,410 +0,0 @@
-SearchEngineKeywordsPerformance API lets you download all your SEO search keywords from Google,
- * Bing & Yahoo and Yandex, as well as getting a detailed overview of how search robots crawl your websites and any
- * error they may encounter.
- *
- * 1) download all your search keywords as they were searched on Google, Bing & Yahoo and Yandex. This includes Google
- * Images, Google Videos and Google News. This lets you view all keywords normally hidden from view behind "keyword not defined".
- * With this plugin you can view them all!
- *
- * 2) download all crawling overview stats and metrics from Bring and Yahoo and Google. Many metrics are available such
- * as: Crawled pages, Crawl errors, Connection timeouts, HTTP-Status Code 301 (Permanently moved), HTTP-Status Code
- * 400-499 (Request errors), All other HTTP-Status Codes, Total pages in index, Robots.txt exclusion, DNS failures,
- * HTTP-Status Code 200-299, HTTP-Status Code 301 (Temporarily moved), HTTP-Status Code 500-599 (Internal server
- * errors), Malware infected sites, Total inbound links.
- *
- * @package Piwik\Plugins\SearchEngineKeywordsPerformance
- */
-class API extends \Piwik\Plugin\API
-{
- /**
- * Returns Keyword data used on any search
- * Combines imported search keywords with those returned by Referrers plugin
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywords($idSite, $period, $date)
- {
- $keywordsDataTable = $this->getKeywordsImported($idSite, $period, $date);
- $keywordsDataTable->deleteColumns([\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::POSITION, \Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS, \Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::CTR]);
- $keywordsDataTable->filter('ColumnCallbackDeleteRow', [\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_CLICKS, function ($clicks) {
- return $clicks === 0;
- }]);
- $keywordsDataTable->filter('ReplaceColumnNames', [[\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_CLICKS => 'nb_visits']]);
- $setting = new \Piwik\Plugins\SearchEngineKeywordsPerformance\MeasurableSettings($idSite);
- $googleEnabled = !empty($setting->googleSearchConsoleUrl) && $setting->googleSearchConsoleUrl->getValue();
- $bingEnabled = !empty($setting->bingSiteUrl) && $setting->bingSiteUrl->getValue();
- $referrersApi = new \Piwik\Plugins\Referrers\API();
- $referrersKeywords = $referrersApi->getSearchEngines($idSite, $period, $date, \false, \true);
- if ($keywordsDataTable instanceof DataTable\Map) {
- $referrerTables = $referrersKeywords->getDataTables();
- foreach ($keywordsDataTable->getDataTables() as $label => $table) {
- if (!empty($referrerTables[$label])) {
- $this->combineKeywordReports($table, $referrerTables[$label], $googleEnabled, $bingEnabled, $period);
- }
- }
- } else {
- $this->combineKeywordReports($keywordsDataTable, $referrersKeywords, $googleEnabled, $bingEnabled, $period);
- }
- return $keywordsDataTable;
- }
- private function combineKeywordReports(DataTable $keywordsDataTable, DataTable $referrersKeywords, $googleEnabled, $bingEnabled, $period)
- {
- foreach ($referrersKeywords->getRowsWithoutSummaryRow() as $searchEngineRow) {
- $label = $searchEngineRow->getColumn('label');
- if (strpos($label, 'Google') !== \false && $googleEnabled) {
- continue;
- }
- if (strpos($label, 'Bing') !== \false && $bingEnabled && $period !== 'day') {
- continue;
- }
- if (strpos($label, 'Yahoo') !== \false && $bingEnabled && $period !== 'day') {
- continue;
- }
- $keywordsTable = $searchEngineRow->getSubtable();
- if (empty($keywordsTable) || !$keywordsTable->getRowsCount()) {
- continue;
- }
- foreach ($keywordsTable->getRowsWithoutSummaryRow() as $keywordRow) {
- if ($keywordRow->getColumn('label') == \Piwik\Plugins\Referrers\API::LABEL_KEYWORD_NOT_DEFINED) {
- continue;
- }
- $keywordsDataTable->addRow(new DataTable\Row([DataTable\Row::COLUMNS => ['label' => $keywordRow->getColumn('label'), 'nb_visits' => $keywordRow->getColumn(\Piwik\Metrics::INDEX_NB_VISITS)]]));
- }
- }
- $keywordsDataTable->filter('GroupBy', ['label']);
- $keywordsDataTable->filter('ReplaceSummaryRowLabel');
- }
- /**
- * Returns Keyword data used on any imported search engine
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsImported($idSite, $period, $date)
- {
- $googleWebKwds = $this->getKeywordsGoogleWeb($idSite, $period, $date);
- $googleImgKwds = $this->getKeywordsGoogleImage($idSite, $period, $date);
- $googleVidKwds = $this->getKeywordsGoogleVideo($idSite, $period, $date);
- $googleNwsKwds = $this->getKeywordsGoogleNews($idSite, $period, $date);
- $bingKeywords = $this->getKeywordsBing($idSite, $period, $date);
- $yandexKeywords = $this->getKeywordsYandex($idSite, $period, $date);
- return $this->combineDataTables([$googleWebKwds, $googleImgKwds, $googleVidKwds, $googleNwsKwds, $bingKeywords, $yandexKeywords]);
- }
- /**
- * Returns Keyword data used on Google
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsGoogle($idSite, $period, $date)
- {
- $googleWebKwds = $this->getKeywordsGoogleWeb($idSite, $period, $date);
- $googleImgKwds = $this->getKeywordsGoogleImage($idSite, $period, $date);
- $googleVidKwds = $this->getKeywordsGoogleVideo($idSite, $period, $date);
- $googleNwsKwds = $this->getKeywordsGoogleNews($idSite, $period, $date);
- return $this->combineDataTables([$googleWebKwds, $googleImgKwds, $googleVidKwds, $googleNwsKwds]);
- }
- /**
- * Returns a DataTable with the given datatables combined
- *
- * @param DataTable[]|DataTable\Map[] $dataTablesToCombine
- *
- * @return DataTable|DataTable\Map
- */
- protected function combineDataTables(array $dataTablesToCombine)
- {
- if (reset($dataTablesToCombine) instanceof DataTable\Map) {
- $dataTable = new DataTable\Map();
- $dataTables = [];
- foreach ($dataTablesToCombine as $dataTableMap) {
- /** @var DataTable\Map $dataTableMap */
- $tables = $dataTableMap->getDataTables();
- foreach ($tables as $label => $table) {
- if (empty($dataTables[$label])) {
- $dataTables[$label] = new DataTable();
- $dataTables[$label]->setAllTableMetadata($table->getAllTableMetadata());
- $dataTables[$label]->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, \Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::getColumnsAggregationOperations());
- }
- $dataTables[$label]->addDataTable($table);
- }
- }
- foreach ($dataTables as $label => $table) {
- $dataTable->addTable($table, $label);
- }
- } else {
- $dataTable = new DataTable();
- $dataTable->setAllTableMetadata(reset($dataTablesToCombine)->getAllTableMetadata());
- $dataTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, \Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::getColumnsAggregationOperations());
- foreach ($dataTablesToCombine as $table) {
- $dataTable->addDataTable($table);
- }
- }
- return $dataTable;
- }
- /**
- * Returns Bing keyword data used on search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsBing($idSite, $period, $date)
- {
- if (!Bing::getInstance()->isSupportedPeriod($date, $period)) {
- if (Period::isMultiplePeriod($date, $period)) {
- return new DataTable\Map();
- }
- return new DataTable();
- }
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Bing')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(BingRecordBuilder::KEYWORDS_BING_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns Yandex keyword data used on search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsYandex($idSite, $period, $date)
- {
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Yandex')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(YandexRecordBuilders::KEYWORDS_YANDEX_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns Google keyword data used on Web search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsGoogleWeb($idSite, $period, $date)
- {
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Google', 'web')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(GoogleRecordBuilder::KEYWORDS_GOOGLE_WEB_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns Google keyword data used on Image search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsGoogleImage($idSite, $period, $date)
- {
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Google', 'image')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(GoogleRecordBuilder::KEYWORDS_GOOGLE_IMAGE_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns Google keyword data used on Video search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsGoogleVideo($idSite, $period, $date)
- {
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Google', 'video')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(GoogleRecordBuilder::KEYWORDS_GOOGLE_VIDEO_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns Google keyword data used on News search
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getKeywordsGoogleNews($idSite, $period, $date)
- {
- if (!SearchEngineKeywordsPerformance::isReportEnabled('Google', 'news')) {
- return SearchEngineKeywordsPerformance::commonEmptyDataTable($period, $date);
- }
- $dataTable = $this->getDataTable(GoogleRecordBuilder::KEYWORDS_GOOGLE_NEWS_RECORD_NAME, $idSite, $period, $date);
- return $dataTable;
- }
- /**
- * Returns crawling metrics for Bing
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getCrawlingOverviewBing($idSite, $period, $date)
- {
- Piwik::checkUserHasViewAccess($idSite);
- $archive = Archive::build($idSite, $period, $date);
- $dataTable = $archive->getDataTableFromNumeric([
- BingRecordBuilder::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_IN_INDEX_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_IN_LINKS_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_MALWARE_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_BLOCKED_ROBOTS_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_ERRORS_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_DNS_FAILURE_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_TIMEOUT_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_CODE_2XX_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_CODE_301_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_CODE_302_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_CODE_4XX_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_CODE_5XX_RECORD_NAME,
- BingRecordBuilder::CRAWLSTATS_OTHER_CODES_RECORD_NAME
- ]);
- return $dataTable;
- }
- /**
- * Returns crawling metrics for Yandex
- *
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- public function getCrawlingOverviewYandex($idSite, $period, $date)
- {
- Piwik::checkUserHasViewAccess($idSite);
- $archive = Archive::build($idSite, $period, $date);
- $dataTable = $archive->getDataTableFromNumeric([
- YandexRecordBuilders::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_APPEARED_PAGES_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_REMOVED_PAGES_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_IN_INDEX_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_CODE_2XX_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_CODE_3XX_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_CODE_4XX_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_CODE_5XX_RECORD_NAME,
- YandexRecordBuilders::CRAWLSTATS_ERRORS_RECORD_NAME
- ]);
- return $dataTable;
- }
- /**
- * Returns list of pages that had an error while crawling for Bing
- *
- * Note: This methods returns data imported lately. It does not support any historical reports
- *
- * @param $idSite
- *
- * @return DataTable
- */
- public function getCrawlingErrorExamplesBing($idSite)
- {
- Piwik::checkUserHasViewAccess($idSite);
- $dataTable = new DataTable();
- $settings = new \Piwik\Plugins\SearchEngineKeywordsPerformance\MeasurableSettings($idSite);
- $bingSiteSetting = $settings->bingSiteUrl;
- if (empty($bingSiteSetting) || empty($bingSiteSetting->getValue())) {
- return $dataTable;
- }
- list($apiKey, $bingSiteUrl) = explode('##', $bingSiteSetting->getValue());
- $model = new \Piwik\Plugins\SearchEngineKeywordsPerformance\Model\Bing();
- $data = $model->getCrawlErrors($bingSiteUrl);
- if (empty($data)) {
- return $dataTable;
- }
- $dataTable->addRowsFromSerializedArray($data);
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'url'));
- $dataTable->filter('ColumnCallbackReplace', array('label', function ($val) use ($bingSiteUrl) {
- return preg_replace('|https?://[^/]*/|i', '', $val);
- }));
- return $dataTable;
- }
- /**
- * Returns datatable for the requested archive
- *
- * @param string $name name of the archive to use
- * @param string|int|array $idSite A single ID (eg, `'1'`), multiple IDs (eg, `'1,2,3'` or `array(1, 2, 3)`),
- * or `'all'`.
- * @param string $period 'day', `'week'`, `'month'`, `'year'` or `'range'`
- * @param Date|string $date 'YYYY-MM-DD', magic keywords (ie, 'today'; {@link Date::factory()}
- * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
- * @return DataTable|DataTable\Map
- */
- private function getDataTable($name, $idSite, $period, $date)
- {
- Piwik::checkUserHasViewAccess($idSite);
- $archive = Archive::build($idSite, $period, $date, $segment = \false);
- $dataTable = $archive->getDataTable($name);
- $dataTable->queueFilter('ReplaceSummaryRowLabel');
- return $dataTable;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/AccountAdded.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/AccountAdded.php
deleted file mode 100644
index 245770b..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/AccountAdded.php
+++ /dev/null
@@ -1,46 +0,0 @@
- All channels > Channel type*
- * *Acquisition > All channels > Referrers*
- * *Acquisition > Search Engines*
-* Show related reports for reports showing imported keywords to show originally tracked keywords instead
-* Translations for German and Albanian
-
-__3.3.2__
-* Fix sorting for keyword tables
-* Improved compatibility with Roll-Up Reporting plugin
-* Translation updates
-
-__3.3.1__
-* Ensure at least one keyword type is configured for Google imports
-* Deprecated Property Set and Android App imports
-* Improve sorting of keyword reports by adding a secondary sort column
-* Added proper handling for new Domain properties on Google Search Console
-
-__3.3.0__
-* Fixed bug with incorrect numbers for reports including day stats for Bing
-* Improved validation of uploaded Google client configs
-* Updated dependencies
-* Deprecated Google Crawl Errors reports (due to Google API deprecation).
- Old reports will still be available, but no new data can be imported after end of March '19.
- New installs won't show those reports at all.
-* Translation updates
-
-__3.2.7__
-* Fixed notice occurring if search import is force enabled
-
-__3.2.6__
-* Allow force enabling crawling error reports.
-* Improve handling of Google import (avoid importing property set data since it does not exist)
-
-__3.2.5__
-* Security improvements
-* Theme updates
-
-__3.2.4__
-* Improve handling of Bing Crawl Errors (fixes a notice while import)
-* Improve Google import handling of empty results
-* Security improvements
-* UI improvements
-* Translations for Polish
-
-__3.2.3__
-* Various code improvements
-* Translations for Chinese (Taiwan) and Italian
-
-__3.2.0__
-* Changes the _Combined Keywords_ report to also include keywords reported by Referrers.getKeywords
-* Adds new reports _Combined imported keywords_ (which is what the combined keywords was before)
-* Replaces Referrers.getKeywords reports in order to change name and show it as related report
-* Move all reports to the Search Engines & Keywords category (showing Search Engines last)
-
-__3.1.0__
-* New crawl errors reports und Pages > crawl errors showing pages having crawl issues on Google and Bing/Yahoo!
-
-__3.0.10__
-* Improved error handling
-* Row evolution for combined keywords reports
-* Fixed error when generating scheduled reports with evolution charts
-
-__3.0.9__
-* Renamed Piwik to Matomo
-
-__3.0.8__
-* Possibility to show keyword position as float instead of integer
-
-__3.0.7__
-* Added commands to trigger import using console command
-* Various UI/UX improvements
-
-__3.0.6__
-* Now uses Piwik proxy config if defined
-
-__3.0__
-* Possibility to import keyords & crawl stats from Google Search Console
-* Setting per website if web, image and/or video keywords should be imported
-* Possibility to import keywords & crawl stats from Bing/Yahoo! Webmaster API
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/CrawlingOverviewSubcategory.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/CrawlingOverviewSubcategory.php
deleted file mode 100644
index df098d4..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/CrawlingOverviewSubcategory.php
+++ /dev/null
@@ -1,31 +0,0 @@
-' . Piwik::translate('SearchEngineKeywordsPerformance_CrawlingOverview1') . '
';
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/SearchKeywordsSubcategory.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/SearchKeywordsSubcategory.php
deleted file mode 100644
index a60d2ec..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/SearchKeywordsSubcategory.php
+++ /dev/null
@@ -1,26 +0,0 @@
-configuration = $configuration;
- }
- /**
- * Returns configured client api keys
- *
- * @return array
- */
- public function getAccounts()
- {
- return $this->configuration->getAccounts();
- }
- /**
- * Removes client api key
- *
- * @param string $apiKey
- * @return boolean
- */
- public function removeAccount($apiKey)
- {
- $this->configuration->removeAccount($apiKey);
- Piwik::postEvent(
- 'SearchEngineKeywordsPerformance.AccountRemoved',
- [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Bing::getInstance()->getName(), 'account' => substr($apiKey, 0, 5) . '*****' . substr($apiKey, -5, 5)]]
- );
- return \true;
- }
- /**
- * Adds a client api key
- *
- * @param $apiKey
- * @param $username
- * @return boolean
- */
- public function addAccount($apiKey, $username)
- {
- $this->configuration->addAccount($apiKey, $username);
- Piwik::postEvent('SearchEngineKeywordsPerformance.AccountAdded', [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Bing::getInstance()->getName(), 'account' => substr($apiKey, 0, 5) . '*****' . substr($apiKey, -5, 5)]]);
- return \true;
- }
- /**
- * Returns if client is configured
- *
- * @return bool
- */
- public function isConfigured()
- {
- return count($this->getAccounts()) > 0;
- }
- /**
- * Checks if API key can be used to query the API
- *
- * @param $apiKey
- * @return bool
- * @throws \Exception
- */
- public function testConfiguration($apiKey)
- {
- $data = $this->sendAPIRequest($apiKey, 'GetUserSites');
- if (empty($data)) {
- throw new \Exception('Unknown error');
- }
- return \true;
- }
- /**
- * Returns the urls, keyword data is available for (in connected google account)
- *
- * @param $apiKey
- *
- * @return array
- */
- public function getAvailableUrls($apiKey, $removeUrlsWithoutAccess = \true)
- {
- try {
- $data = $this->sendAPIRequest($apiKey, 'GetUserSites');
- } catch (\Exception $e) {
- return [];
- }
- if (empty($data) || !is_array($data['d'])) {
- return [];
- }
- $urls = [];
- foreach ($data['d'] as $item) {
- if (!$removeUrlsWithoutAccess || $item['IsVerified']) {
- $urls[$item['Url']] = $item['IsVerified'];
- }
- }
- return $urls;
- }
- /**
- * Returns search anyalytics data from Bing API
- *
- * @param string $apiKey
- * @param string $url
- * @return array
- */
- public function getSearchAnalyticsData($apiKey, $url)
- {
- $keywordDataSets = $this->sendAPIRequest($apiKey, 'GetQueryStats', ['siteUrl' => $url]);
- $keywords = [];
- if (empty($keywordDataSets['d']) || !is_array($keywordDataSets['d'])) {
- return [];
- }
- foreach ($keywordDataSets['d'] as $keywordDataSet) {
- $timestamp = substr($keywordDataSet['Date'], 6, 10);
- $date = date('Y-m-d', $timestamp);
- if (!isset($keywords[$date])) {
- $keywords[$date] = [];
- }
- $keywords[$date][] = ['keyword' => $keywordDataSet['Query'], 'clicks' => $keywordDataSet['Clicks'], 'impressions' => $keywordDataSet['Impressions'], 'position' => $keywordDataSet['AvgImpressionPosition']];
- }
- return $keywords;
- }
- /**
- * Returns crawl statistics from Bing API
- *
- * @param string $apiKey
- * @param string $url
- * @return array
- */
- public function getCrawlStats($apiKey, $url)
- {
- $crawlStatsDataSets = $this->sendAPIRequest($apiKey, 'GetCrawlStats', ['siteUrl' => $url]);
- $crawlStats = [];
- if (empty($crawlStatsDataSets) || !is_array($crawlStatsDataSets['d'])) {
- return [];
- }
- foreach ($crawlStatsDataSets['d'] as $crawlStatsDataSet) {
- $timestamp = substr($crawlStatsDataSet['Date'], 6, 10);
- $date = date('Y-m-d', $timestamp);
- unset($crawlStatsDataSet['Date']);
- $crawlStats[$date] = $crawlStatsDataSet;
- }
- return $crawlStats;
- }
- /**
- * Returns urls with crawl issues from Bing API
- *
- * @param string $apiKey
- * @param string $url
- * @return array
- */
- public function getUrlWithCrawlIssues($apiKey, $url)
- {
- $crawlErrorsDataSets = $this->sendAPIRequest($apiKey, 'GetCrawlIssues', ['siteUrl' => $url]);
- $crawlErrors = [];
- if (empty($crawlErrorsDataSets) || !is_array($crawlErrorsDataSets['d'])) {
- return [];
- }
- $crawlIssueMapping = [1 => 'Code301', 2 => 'Code302', 4 => 'Code4xx', 8 => 'Code5xx', 16 => 'BlockedByRobotsTxt', 32 => 'ContainsMalware', 64 => 'ImportantUrlBlockedByRobotsTxt'];
- foreach ($crawlErrorsDataSets['d'] as $crawlStatsDataSet) {
- $issues = $crawlStatsDataSet['Issues'];
- $messages = [];
- foreach ($crawlIssueMapping as $code => $message) {
- if ($issues & $code) {
- $messages[] = $message;
- }
- }
- $crawlStatsDataSet['Issues'] = implode(',', $messages);
- $crawlErrors[] = $crawlStatsDataSet;
- }
- return $crawlErrors;
- }
- /**
- * Executes a API-Request to Bing with the given parameters
- *
- * @param string $apiKey
- * @param string $method
- * @param array $params
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws \Exception
- */
- protected function sendAPIRequest($apiKey, $method, $params = [])
- {
- $params['apikey'] = $apiKey;
- $this->throwIfThrottled($apiKey);
- $url = $this->baseAPIUrl . $method . '?' . Http::buildQuery($params);
- $retries = 0;
- while ($retries < 5) {
- try {
- $additionalHeaders = ['X-Forwarded-For: ' . (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] . ',' : '') . IP::getIpFromHeader()];
- $response = Http::sendHttpRequestBy(Http::getTransportMethod(), $url, 2000, null, null, null, 0, false, false, false, false, 'GET', null, null, null, $additionalHeaders);
- $response = json_decode($response, \true);
- } catch (\Exception $e) {
- if ($retries < 4) {
- $retries++;
- usleep($retries * 500);
- continue;
- }
- throw $e;
- }
- if (!empty($response['ErrorCode'])) {
- $isThrottledHost = strpos($response['Message'], 'ThrottleHost') !== \false || $response['ErrorCode'] == self::API_ERROR_THROTTLE_HOST;
- $isThrottledUser = strpos($response['Message'], 'ThrottleUser') !== \false || $response['ErrorCode'] == self::API_ERROR_THROTTLE_USER;
- $isThrottledIp = strpos($response['Message'], 'ThrottleIP') !== false;
- $isThrottled = $isThrottledHost || $isThrottledUser || $isThrottledIp;
- $isUnknownError = $response['ErrorCode'] === self::API_ERROR_UNKNOWN;
- // we retry each request up to 5 times, if the error is unknown or the connection was throttled
- if (($isThrottled || $isUnknownError) && $retries < 4) {
- $retries++;
- usleep($retries * 500);
- continue;
- }
- // if connection is still throttled after retrying, we block additional requests for some time
- if ($isThrottled) {
- Option::set($this->getThrottleOptionKey($apiKey), Date::getNowTimestamp());
- $this->throwIfThrottled($apiKey, $response['ErrorCode']);
- }
- if ($isUnknownError) {
- throw new UnknownAPIException($response['Message'], $response['ErrorCode']);
- }
- $authenticationError = strpos($response['Message'], 'NotAuthorized') !== \false
- || strpos($response['Message'], 'InvalidApiKey') !== \false
- || in_array($response['ErrorCode'], [self::API_ERROR_NOT_AUTHORIZED, self::API_ERROR_INVALID_API_KEY]);
- if ($authenticationError) {
- throw new InvalidCredentialsException($response['Message'], $response['ErrorCode']);
- }
- throw new \Exception($response['Message'], $response['ErrorCode']);
- }
- return $response;
- }
- return null;
- }
- protected function getThrottleOptionKey($apiKey)
- {
- return sprintf(self::OPTION_NAME_THROTTLE_TIME, md5($apiKey));
- }
- public function throwIfThrottled($apiKey, $errorCode = null)
- {
- $throttleTime = Option::get($this->getThrottleOptionKey($apiKey));
- if (empty($throttleTime)) {
- return;
- }
- try {
- $throttleDate = Date::factory((int) $throttleTime);
- } catch (\Exception $e) {
- return;
- }
- if (Date::now()->subHour(self::THROTTLE_BREAK_HOURS)->isEarlier($throttleDate)) {
- $errorCode = !empty($errorCode) ? $errorCode : self::API_ERROR_THROTTLE_USER;
- throw new RateLimitApiException('API requests temporarily throttled till ' . $throttleDate->addHour(self::THROTTLE_BREAK_HOURS)->getDatetime(), $errorCode);
- }
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/BaseConfiguration.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/BaseConfiguration.php
deleted file mode 100644
index af01cc9..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/BaseConfiguration.php
+++ /dev/null
@@ -1,49 +0,0 @@
-accounts)) {
- $accounts = Option::get(self::CLIENT_CONFIG_OPTION_NAME);
- $accounts = @json_decode($accounts, \true);
- if (is_array($accounts)) {
- $this->accounts = $accounts;
- }
- }
- return $this->accounts;
- }
- /**
- * Adds new account
- *
- * @param $apiKey
- * @param $username
- */
- public function addAccount($apiKey, $username)
- {
- $currentAccounts = (array) $this->getAccounts();
- if (array_key_exists($apiKey, $currentAccounts)) {
- return;
- }
- $currentAccounts[$apiKey] = ['apiKey' => $apiKey, 'username' => $username, 'created' => time()];
- $this->setAccounts($currentAccounts);
- }
- /**
- * Removes account with given API-Key
- *
- * @param $apiKey
- */
- public function removeAccount($apiKey)
- {
- $currentAccounts = (array) $this->getAccounts();
- $this->checkPermissionToRemoveAccount($apiKey, $currentAccounts);
- unset($currentAccounts[$apiKey]);
- $this->setAccounts($currentAccounts);
- }
- protected function setAccounts($newAccounts)
- {
- $accounts = json_encode($newAccounts);
- Option::set(self::CLIENT_CONFIG_OPTION_NAME, $accounts);
- $this->accounts = [];
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Google.php
deleted file mode 100644
index f98383e..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Google.php
+++ /dev/null
@@ -1,178 +0,0 @@
-accounts)) {
- $accounts = Option::get(self::OAUTH_CONFIG_OPTION_NAME);
- $accounts = @json_decode($accounts, \true);
- if (is_array($accounts) && !empty($accounts)) {
- $this->accounts = $accounts;
- }
- }
- return $this->accounts;
- }
- /**
- * adds new account data
- *
- * @param string $id
- * @param array $accountData
- * @param string $username
- */
- public function addAccount($id, $accountData, $username)
- {
- $currentAccounts = (array) $this->getAccounts();
- $id = (string) $id;
- if (array_key_exists($id, $currentAccounts)) {
- return;
- }
- $currentAccounts[$id] = ['config' => $accountData, 'username' => $username, 'created' => time()];
- $this->setAccounts($currentAccounts);
- }
- /**
- * removes account data
- *
- * @param string $id
- */
- public function removeAccount($id)
- {
- $currentAccounts = (array) $this->getAccounts();
- $id = (string) $id;
- $this->checkPermissionToRemoveAccount($id, $currentAccounts);
- unset($currentAccounts[$id]);
- $this->setAccounts($currentAccounts);
- }
- protected function setAccounts($newAccounts)
- {
- $accounts = json_encode($newAccounts);
- Option::set(self::OAUTH_CONFIG_OPTION_NAME, $accounts);
- $this->accounts = $newAccounts;
- }
- /**
- * Returns the access token for the given account id
- *
- * @param string $accountId
- * @return mixed|null
- */
- public function getAccessToken($accountId)
- {
- $accounts = $this->getAccounts();
- if (array_key_exists($accountId, $accounts)) {
- return $accounts[$accountId]['config']['accessToken'];
- }
- return null;
- }
- /**
- * Returns the user info for the given account id
- *
- * @param string $accountId
- * @return mixed|null
- */
- public function getUserInfo($accountId)
- {
- $accounts = $this->getAccounts();
- if (array_key_exists($accountId, $accounts)) {
- return $accounts[$accountId]['config']['userInfo'];
- }
- return null;
- }
- /**
- * Returns stored client config
- *
- * @return mixed|null
- */
- public function getClientConfig()
- {
- if (empty($this->clientConfig)) {
- $config = Common::unsanitizeInputValue(Option::get(self::CLIENT_CONFIG_OPTION_NAME));
- if (!empty($config) && ($config = @json_decode($config, \true))) {
- $this->clientConfig = $config;
- }
- }
- return $this->clientConfig;
- }
- /**
- * Sets client config to be used for querying google api
- *
- * NOTE: Check for valid config should be done before
- *
- * @param string $config json encoded client config
- */
- public function setClientConfig($config)
- {
- Option::set(self::CLIENT_CONFIG_OPTION_NAME, $config);
- }
- /**
- * Delete the Google client config option so that the customer will be prompted to upload a new one or use the Cloud
- * config.
- *
- * @return void
- */
- public function deleteClientConfig(): void
- {
- Option::delete(self::CLIENT_CONFIG_OPTION_NAME);
- }
- /**
- * Returns path to client config file that is used for automatic import
- *
- * @return string
- */
- protected function getConfigurationFilename()
- {
- return dirname(dirname(dirname(__FILE__))) . \DIRECTORY_SEPARATOR . 'client_secrets.json';
- }
- /**
- * Imports client config from shipped config file if available
- *
- * @return bool
- */
- public function importShippedClientConfigIfAvailable()
- {
- $configFile = $this->getConfigurationFilename();
- if (!file_exists($configFile)) {
- return \false;
- }
- $config = file_get_contents($configFile);
- if ($decoded = @json_decode($config, \true)) {
- $this->setClientConfig($config);
- return \true;
- }
- return \false;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Yandex.php
deleted file mode 100644
index c5881ef..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Yandex.php
+++ /dev/null
@@ -1,130 +0,0 @@
-accounts)) {
- $accounts = Option::get(self::OAUTH_CONFIG_OPTION_NAME);
- $accounts = @json_decode($accounts, \true);
- if (is_array($accounts) && !empty($accounts)) {
- $this->accounts = $accounts;
- }
- }
- return $this->accounts;
- }
- /**
- * adds new account data
- *
- * @param string $id
- * @param array $accountData
- * @param string $username
- */
- public function addAccount($id, $accountData, $username)
- {
- $currentAccounts = (array) $this->getAccounts();
- $currentAccounts[$id] = ['config' => $accountData, 'username' => $username, 'created' => time()];
- $this->setAccounts($currentAccounts);
- }
- /**
- * removes account data
- *
- * @param string $id
- */
- public function removeAccount($id)
- {
- $currentAccounts = (array) $this->getAccounts();
- $this->checkPermissionToRemoveAccount($id, $currentAccounts);
- unset($currentAccounts[$id]);
- $this->setAccounts($currentAccounts);
- }
- protected function setAccounts($newAccounts)
- {
- $accounts = json_encode($newAccounts);
- Option::set(self::OAUTH_CONFIG_OPTION_NAME, $accounts);
- $this->accounts = [];
- }
- /**
- * Returns the access token for the given account id
- *
- * @param string $accountId
- * @return mixed|null
- */
- public function getAccessToken($accountId)
- {
- $accounts = $this->getAccounts();
- if (array_key_exists($accountId, $accounts)) {
- return $accounts[$accountId]['config']['accessToken'];
- }
- return null;
- }
- /**
- * Returns the user info for the given account id
- *
- * @param string $accountId
- * @return mixed|null
- */
- public function getUserInfo($accountId)
- {
- $accounts = $this->getAccounts();
- if (array_key_exists($accountId, $accounts)) {
- return $accounts[$accountId]['config']['userInfo'];
- }
- return null;
- }
- /**
- * Returns stored client config
- *
- * @return mixed|null
- */
- public function getClientConfig()
- {
- if (empty($this->clientConfig)) {
- $config = Common::unsanitizeInputValue(Option::get(self::CLIENT_CONFIG_OPTION_NAME));
- if (!empty($config) && ($config = @json_decode($config, \true))) {
- $this->clientConfig = $config;
- }
- }
- return $this->clientConfig;
- }
- /**
- * Sets client config to be used for querying yandex api
- *
- * NOTE: Check for valid config should be done before
- *
- * @param string $config json encoded client config
- */
- public function setClientConfig($clientId, $clientSecret)
- {
- $config = ['id' => $clientId, 'secret' => $clientSecret, 'date' => time()];
- Option::set(self::CLIENT_CONFIG_OPTION_NAME, json_encode($config));
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Google.php
deleted file mode 100644
index c285ceb..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Google.php
+++ /dev/null
@@ -1,489 +0,0 @@
-configuration = $configuration;
- }
- /**
- * @return \Google\Client
- */
- protected function getGoogleClient()
- {
- $googleClient = StaticContainer::get('SearchEngineKeywordsPerformance.Google.googleClient');
- $proxyHost = Config::getInstance()->proxy['host'];
- if ($proxyHost) {
- $proxyPort = Config::getInstance()->proxy['port'];
- $proxyUser = Config::getInstance()->proxy['username'];
- $proxyPassword = Config::getInstance()->proxy['password'];
- if ($proxyUser) {
- $proxy = sprintf('http://%s:%s@%s:%s', $proxyUser, $proxyPassword, $proxyHost, $proxyPort);
- } else {
- $proxy = sprintf('http://%s:%s', $proxyHost, $proxyPort);
- }
- $httpClient = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\GuzzleHttp\Client(['proxy' => $proxy, 'exceptions' => \false, 'base_uri' => \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Client::API_BASE_PATH]);
- $googleClient->setHttpClient($httpClient);
- }
- return $googleClient;
- }
- /**
- * Passes through a direct call to the \Google\Client class
- *
- * @param string $method
- * @param array $params
- * @return mixed
- * @throws MissingClientConfigException
- * @throws MissingOAuthConfigException
- */
- public function __call($method, $params = [])
- {
- return call_user_func_array([$this->getConfiguredClient('', \true), $method], $params);
- }
- /**
- * Process the given auth code to gain access and refresh token from google api
- *
- * @param string $authCode
- * @throws MissingClientConfigException
- */
- public function processAuthCode($authCode)
- {
- try {
- $client = $this->getConfiguredClient('');
- } catch (MissingOAuthConfigException $e) {
- // ignore missing oauth config
- }
- $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
- $userInfo = $this->getUserInfoByAccessToken($accessToken);
- $id = $userInfo->getId();
- $this->addAccount($id, $accessToken, Piwik::getCurrentUserLogin());
- Piwik::postEvent('SearchEngineKeywordsPerformance.AccountAdded', [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Google::getInstance()->getName(), 'account' => $userInfo->getName()]]);
- }
- /**
- * Sets the client configuration
- *
- * @param $config
- * @return boolean
- */
- public function setClientConfig($config)
- {
- try {
- $client = $this->getGoogleClient();
- $configArray = @json_decode($config, \true);
- $this->configureClient($client, $configArray);
- } catch (\Exception $e) {
- return \false;
- }
- $this->configuration->setClientConfig($config);
- Piwik::postEvent('SearchEngineKeywordsPerformance.GoogleClientConfigChanged');
- return \true;
- }
- /**
- * Delete the Google client config option so that the customer will be prompted to upload a new one or use the Cloud
- * config.
- *
- * @return void
- */
- public function deleteClientConfig(): void
- {
- $this->configuration->deleteClientConfig();
- Piwik::postEvent('SearchEngineKeywordsPerformance.GoogleClientConfigChanged');
- }
- /**
- * @param \Google\Client $client
- * @param array $config
- *
- * @throws MissingClientConfigException
- */
- protected function configureClient($client, $config)
- {
- try {
- @$client->setAuthConfig($config);
- } catch (\Exception $e) {
- throw new MissingClientConfigException();
- }
- // no client config available
- if (!$client->getClientId() || !$client->getClientSecret()) {
- throw new MissingClientConfigException();
- }
- }
- /**
- * Returns configured client api keys
- *
- * @return array
- */
- public function getAccounts()
- {
- return $this->configuration->getAccounts();
- }
- /**
- * Removes client api key
- *
- * @param $id
- * @return bool
- */
- public function removeAccount($id)
- {
- $userInfo = $this->getUserInfo($id);
- $this->configuration->removeAccount($id);
- Piwik::postEvent('SearchEngineKeywordsPerformance.AccountRemoved', [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Google::getInstance()->getName(), 'account' => $userInfo['name']]]);
- return \true;
- }
- /**
- * Adds a client api key
- *
- * @param $id
- * @param $config
- * @param $username
- * @return boolean
- */
- public function addAccount($id, $accessToken, $username)
- {
- $userInfo = $this->getUserInfoByAccessToken($accessToken);
- $config = ['userInfo' => ['picture' => $userInfo->picture, 'name' => $userInfo->name], 'accessToken' => $accessToken];
- $this->configuration->addAccount($id, $config, $username);
- return \true;
- }
- /**
- * Returns if client is configured
- *
- * @return bool
- */
- public function isConfigured()
- {
- return $this->configuration->getClientConfig() && count($this->configuration->getAccounts()) > 0;
- }
- /**
- * Returns configured \Google\Client object
- *
- * @param string $accessToken
- * @param bool $ignoreMissingConfigs
- * @return \Google\Client
- * @throws MissingClientConfigException
- * @throws MissingOAuthConfigException
- */
- public function getConfiguredClient($accessToken, $ignoreMissingConfigs = \false)
- {
- $client = $this->getGoogleClient();
- try {
- $this->configure($client, $accessToken);
- } catch (\Exception $e) {
- if (!$ignoreMissingConfigs) {
- throw $e;
- }
- }
- return $client;
- }
- /**
- * Returns the Auth Url (including the given state param)
- *
- * @param $state
- * @return string
- * @throws MissingClientConfigException
- * @throws MissingOAuthConfigException
- */
- public function createAuthUrl($state)
- {
- $client = $this->getConfiguredClient('', \true);
- $client->setState($state);
- $client->setPrompt('consent');
- return $client->createAuthUrl();
- }
- /**
- * Loads configuration and sets common configuration for \Google\Client
- *
- * @param \Google\Client $client
- * @param string $accessToken
- * @throws MissingOAuthConfigException
- * @throws MissingClientConfigException
- */
- protected function configure($client, $accessToken)
- {
- // import shipped client config if available
- if (!$this->configuration->getClientConfig()) {
- $this->configuration->importShippedClientConfigIfAvailable();
- }
- $clientConfig = $this->configuration->getClientConfig();
- $this->configureClient($client, $clientConfig);
- // Copied this bit about the redirect_uris from the GA Importer Authorization class
- //since there ie no host defined when running via console it results in error, but we don't need to set any URI when running console commands so can be ignored
- $expectedUri = Url::getCurrentUrlWithoutQueryString() . '?module=SearchEngineKeywordsPerformance&action=processAuthCode';
- if (
- !empty($clientConfig['web']['redirect_uris']) &&
- !Common::isRunningConsoleCommand() &&
- !PluginsArchiver::isArchivingProcessActive() &&
- !$this->isMiscCron() &&
- stripos($expectedUri, 'unknown/_/console?') === false // To handle case where we are unable to determine the correct URI
- ) {
- $uri = $this->getValidUri($clientConfig['web']['redirect_uris']);
- if (empty($uri)) {
- throw new \Exception(Piwik::translate('SearchEngineKeywordsPerformance_InvalidRedirectUriInClientConfiguration', [$expectedUri]));
- }
- $client->setRedirectUri($uri);
- }
- try {
- $client->setAccessToken($accessToken);
- } catch (\Exception $e) {
- throw new MissingOAuthConfigException($e->getMessage());
- }
- }
- public function getUserInfo($accountId)
- {
- return $this->configuration->getUserInfo($accountId);
- }
- protected function getUserInfoByAccessToken($accessToken)
- {
- $service = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\Oauth2($this->getConfiguredClient($accessToken));
- return $service->userinfo->get();
- }
- /**
- * Checks if account can be used to query the API
- *
- * @param string $accountId
- * @return bool
- * @throws \Exception
- */
- public function testConfiguration($accountId)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- try {
- $service = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole($this->getConfiguredClient($accessToken));
- $service->sites->listSites();
- } catch (\Exception $e) {
- $this->handleServiceException($e);
- throw $e;
- }
- return \true;
- }
- /**
- * Returns the urls keyword data is available for (in connected google account)
- *
- * @param string $accountId
- * @param bool $removeUrlsWithoutAccess wether to return unverified urls
- * @return array
- */
- public function getAvailableUrls($accountId, $removeUrlsWithoutAccess = \true)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $sites = [];
- try {
- $service = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole($this->getConfiguredClient($accessToken));
- $service->getClient()->getCache()->clear();
- //After Guzzle upgrade this seems to fetch same token for all the accounts, to solve that we are clearing the caache explicitly
- $response = $service->sites->listSites();
- } catch (\Exception $e) {
- return $sites;
- }
- foreach ($response as $site) {
- if (!$removeUrlsWithoutAccess || $site['permissionLevel'] != 'siteUnverifiedUser') {
- $sites[$site['siteUrl']] = $site['permissionLevel'];
- }
- }
- return $sites;
- }
- /**
- * Returns the search analytics data from google search console for the given parameters
- *
- * @param string $accountId
- * @param string $url url, eg. http://matomo.org
- * @param string $date day string, eg. 2016-12-24
- * @param string $type 'web', 'image', 'video' or 'news'
- * @param int $limit maximum of rows to fetch
- * @return \Google\Service\SearchConsole\SearchAnalyticsQueryResponse
- * @throws InvalidClientConfigException
- * @throws InvalidCredentialsException
- * @throws MissingOAuthConfigException
- * @throws MissingClientConfigException
- * @throws UnknownAPIException
- */
- public function getSearchAnalyticsData($accountId, $url, $date, $type = 'web', $limit = 500)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- if (empty($accessToken)) {
- throw new MissingOAuthConfigException();
- }
- $limit = min($limit, 5000);
- // maximum allowed by API is 5.000
- // Google API is only able to handle dates up to ~490 days old
- $threeMonthBefore = Date::now()->subDay(500);
- $archivedDate = Date::factory($date);
- if ($archivedDate->isEarlier($threeMonthBefore) || $archivedDate->isToday()) {
- Log::debug("[SearchEngineKeywordsPerformance] Skip fetching keywords from Search Console for today and dates more than 500 days in the past");
- return null;
- }
- $service = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole($this->getConfiguredClient($accessToken));
- $request = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole\SearchAnalyticsQueryRequest();
- $request->setStartDate($date);
- $request->setEndDate($date);
- $request->setDimensions(['query']);
- $request->setRowLimit($limit);
- $request->setSearchType($type);
- $request->setDataState('all');
- $retries = 0;
- while ($retries < 5) {
- try {
- $response = $service->searchanalytics->query($url, $request);
- return $response;
- } catch (\Exception $e) {
- $this->handleServiceException($e, $retries < 4);
- usleep(500 * $retries);
- $retries++;
- }
- }
- return null;
- }
- /**
- * Returns an array of dates where search analytics data is availabe for on search console
- *
- * @param string $accountId
- * @param string $url url, eg. http://matomo.org
- * @param boolean $onlyFinalized
- * @return array
- * @throws MissingClientConfigException
- * @throws MissingOAuthConfigException
- * @throws InvalidClientConfigException
- * @throws InvalidCredentialsException
- * @throws UnknownAPIException
- */
- public function getDatesWithSearchAnalyticsData($accountId, $url, $onlyFinalized = \true)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $service = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole($this->getConfiguredClient($accessToken));
- $request = new \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\SearchConsole\SearchAnalyticsQueryRequest();
- $request->setStartDate(Date::now()->subDay(StaticContainer::get('SearchEngineKeywordsPerformance.Google.ImportLastDaysMax'))->toString());
- $request->setEndDate(Date::now()->toString());
- $request->setDimensions(['date']);
- if ($onlyFinalized === \false) {
- $request->setDataState('all');
- }
- $retries = 0;
- while ($retries < 5) {
- try {
- $entries = $service->searchanalytics->query($url, $request);
- if (empty($entries) || !($rows = $entries->getRows())) {
- return [];
- }
- $days = [];
- foreach ($rows as $row) {
- /** @var \Google\Service\SearchConsole\ApiDataRow $row */
- $keys = $keys = $row->getKeys();
- $days[] = array_shift($keys);
- }
- return array_unique($days);
- } catch (\Exception $e) {
- $this->handleServiceException($e, $retries < 4);
- $retries++;
- usleep(500 * $retries);
- }
- }
- return [];
- }
- /**
- * @param \Exception $e
- * @param bool $ignoreUnknowns
- * @throws InvalidClientConfigException
- * @throws InvalidCredentialsException
- * @throws UnknownAPIException
- */
- protected function handleServiceException($e, $ignoreUnknowns = \false)
- {
- if (!$e instanceof \Matomo\Dependencies\SearchEngineKeywordsPerformance\Google\Service\Exception) {
- if (!PluginsArchiver::isArchivingProcessActive()) {
- Log::debug('Exception: ' . $e->getMessage());
- }
- return;
- }
- $error = json_decode($e->getMessage(), \true);
- if (!empty($error['error']) && $error['error'] == 'invalid_client') {
- // invalid credentials
- throw new InvalidClientConfigException($error['error_description']);
- } elseif (!empty($error['error']['code']) && $error['error']['code'] == 401) {
- // invalid credentials
- throw new InvalidCredentialsException($error['error']['message'], $error['error']['code']);
- } elseif (!empty($error['error']['code']) && $error['error']['code'] == 403) {
- // no access for given resource (website / app)
- throw new InvalidCredentialsException($error['error']['message'], $error['error']['code']);
- } elseif (!empty($error['error']['code']) && in_array($error['error']['code'], [500, 503]) && !$ignoreUnknowns) {
- // backend or api server error
- throw new UnknownAPIException($error['error']['message'], $error['error']['code']);
- } else {
- if (!PluginsArchiver::isArchivingProcessActive()) {
- Log::debug('Exception: ' . $e->getMessage());
- }
- }
- }
- /**
- * Returns a valid uri. Copied from GA Importer Authorization class.
- *
- * @param array $uris
- * @return string
- */
- private function getValidUri(array $uris): string
- {
- $validUri = Url::getCurrentUrlWithoutQueryString() . '?module=SearchEngineKeywordsPerformance&action=processAuthCode';
- $settingURL = SettingsPiwik::getPiwikUrl();
- if (stripos($settingURL, 'index.php') === false) {
- $settingURL .= 'index.php';
- }
- // Some MWP installs was not working as expected when using Url::getCurrentUrlWithoutQueryString()
- $validUriFallback = $settingURL . '?module=SearchEngineKeywordsPerformance&action=processAuthCode';
- foreach ($uris as $uri) {
- if (stripos($uri, $validUri) !== \false || stripos($uri, $validUriFallback) !== \false) {
- return $uri;
- }
- }
- return '';
- }
-
- private function isMiscCron()
- {
- $currentURL = Url::getCurrentUrlWithoutQueryString();
-
- return (stripos($currentURL, 'misc/cron/archive.php') !== false);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Yandex.php
deleted file mode 100644
index 5056dea..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Yandex.php
+++ /dev/null
@@ -1,545 +0,0 @@
-configuration = $configuration;
- }
- /**
- * Returns if client is configured
- *
- * @return bool
- */
- public function isConfigured()
- {
- return $this->isClientConfigured() && count($this->configuration->getAccounts()) > 0;
- }
- /**
- * Returns if oauth client config is available
- */
- public function isClientConfigured()
- {
- return \true && $this->getClientConfig();
- }
- /**
- * Returns the client config
- *
- * @return mixed|null
- */
- public function getClientConfig()
- {
- return $this->configuration->getClientConfig();
- }
- /**
- * Checks if account can be used to query the API
- *
- * @param string $accountId
- * @return bool
- * @throws \Exception
- */
- public function testConfiguration($accountId)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $this->getHosts($accessToken);
- return \true;
- }
- /**
- * Updates the client config
- *
- * @param string $clientId new client id
- * @param string $clientSecret new client secret
- */
- public function setClientConfig($clientId, $clientSecret)
- {
- $this->configuration->setClientConfig($clientId, $clientSecret);
- Piwik::postEvent('SearchEngineKeywordsPerformance.GoogleClientConfigChanged');
- }
- /**
- * Returns the urls keyword data is available for (in connected yandex account)
- *
- * @param string $accountId
- * @param bool $removeUrlsWithoutAccess whether to return unverified urls
- * @return array
- */
- public function getAvailableUrls($accountId, $removeUrlsWithoutAccess = \true)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $sites = [];
- try {
- $availableSites = $this->getHosts($accessToken);
- } catch (\Exception $e) {
- return $sites;
- }
- if (property_exists($availableSites, 'hosts')) {
- foreach ($availableSites->hosts as $availableSite) {
- if (!$removeUrlsWithoutAccess || $availableSite->verified) {
- $sites[$availableSite->unicode_host_url] = ['verified' => $availableSite->verified, 'host_id' => $availableSite->host_id];
- }
- }
- }
- return $sites;
- }
- /**
- * Returns popular search queries from Yandex Webmaster API
- *
- * @param string $accountId
- * @param string $hostId
- * @param string $date
- * @return ?array
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- public function getSearchAnalyticsData($accountId, $hostId, $date)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $archivedDate = Date::factory($date);
- if ($archivedDate->isToday()) {
- Log::debug("[SearchEngineKeywordsPerformance] Skip fetching keywords from Yandex Webmaster for today.");
- return null;
- }
- $date = strtotime($date);
- $searchQueries = $this->retryApiMethod(function () use ($accessToken, $hostId, $date) {
- return $this->getPopularQueries($accessToken, $hostId, $date);
- });
- if (empty($searchQueries) || empty($searchQueries->queries)) {
- return null;
- }
- $keywords = [];
- foreach ($searchQueries->queries as $query) {
- $keywords[] = ['keyword' => $query->query_text, 'clicks' => $query->indicators->TOTAL_CLICKS, 'impressions' => $query->indicators->TOTAL_SHOWS, 'position' => $query->indicators->AVG_SHOW_POSITION];
- }
- return $keywords;
- }
- /**
- * Returns crawl statistics from Yandex Webmaster API
- *
- * @param string $accountId
- * @param string $hostId
- * @return array
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- public function getCrawlStats($accountId, $hostId, $date)
- {
- $accessToken = $this->configuration->getAccessToken($accountId);
- $archivedDate = Date::factory($date);
- if ($archivedDate->isToday()) {
- Log::debug("[SearchEngineKeywordsPerformance] Skip fetching crawl stats from Yandex Webmaster for today.");
- return null;
- }
- $dateTs = strtotime($date);
- $crawlStatsByDate = [];
- $crawlStats = $this->retryApiMethod(function () use ($accessToken, $hostId, $dateTs) {
- return $this->getIndexingHistory($accessToken, $hostId, $dateTs);
- });
- if (!empty($crawlStats) && !empty($crawlStats->indicators)) {
- $indicators = (array) $crawlStats->indicators;
- foreach ($indicators as $indicator => $indicatorByDate) {
- foreach ($indicatorByDate as $dateItem) {
- if (strpos($dateItem->date, $date) === 0) {
- $crawlStatsByDate[$indicator] = (int) $dateItem->value;
- }
- }
- }
- }
- $pagesInIndex = $this->retryApiMethod(function () use ($accessToken, $hostId, $dateTs) {
- return $this->getPagesInIndex($accessToken, $hostId, $dateTs);
- });
- if (!empty($pagesInIndex) && !empty($pagesInIndex->history)) {
- $history = (array) $pagesInIndex->history;
- foreach ($history as $entry) {
- // Look for matching date
- if (strpos($entry->date, $date) === 0) {
- $crawlStatsByDate['SEARCHABLE'] = (int) $entry->value;
- }
- }
- }
- $pageChanges = $this->retryApiMethod(function () use ($accessToken, $hostId, $dateTs) {
- return $this->getPageChangesInSearch($accessToken, $hostId, $dateTs);
- });
- if (!empty($pageChanges) && !empty($pageChanges->indicators)) {
- $indicators = (array) $pageChanges->indicators;
- foreach ($indicators as $indicator => $indicatorByDate) {
- foreach ($indicatorByDate as $dateItem) {
- // Look for matching date
- if (strpos($dateItem->date, $date) === 0) {
- $crawlStatsByDate[$indicator] = (int) $dateItem->value;
- }
- }
- }
- }
- return $crawlStatsByDate;
- }
- /**
- * @param callback $method
- * @return mixed
- * @throws \Exception
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function retryApiMethod($method)
- {
- $retries = 0;
- while ($retries < 5) {
- try {
- return $method();
- } catch (InvalidCredentialsException $e) {
- throw $e;
- } catch (\Exception $e) {
- if ($retries >= 4) {
- throw $e;
- }
- usleep(500);
- $retries++;
- }
- }
- }
- /**
- * Process the given auth code to gain access and refresh token from yandex api
- *
- * @param string $authCode
- * @throws InvalidCredentialsException
- * @throws MissingClientConfigException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- public function processAuthCode($authCode)
- {
- if (!$this->isClientConfigured()) {
- throw new MissingClientConfigException();
- }
- $accessToken = $this->fetchAccessTokenWithAuthCode($authCode);
- $userId = $this->getYandexUserId($accessToken);
- $this->addAccount($userId, $accessToken, Piwik::getCurrentUserLogin());
- $userInfo = $this->getUserInfo($userId);
- Piwik::postEvent('SearchEngineKeywordsPerformance.AccountAdded', [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Yandex::getInstance()->getName(), 'account' => $userInfo['name']]]);
- }
- /**
- * Returns user information for given account id
- *
- * @param $accountId
- * @return array|null
- */
- public function getUserInfo($accountId)
- {
- return $this->configuration->getUserInfo($accountId);
- }
- /**
- * Fetches an access token from Yandex OAuth with the given auth code
- *
- * @param string $authCode
- * @return string
- * @throws \Exception
- */
- protected function fetchAccessTokenWithAuthCode($authCode)
- {
- $clientConfig = $this->getClientConfig();
- $response = Http::sendHttpRequestBy(
- Http::getTransportMethod(),
- 'https://oauth.yandex.com/token',
- 2000,
- null,
- null,
- null,
- 0,
- \false,
- \false,
- \false,
- \false,
- 'POST',
- $clientConfig['id'],
- $clientConfig['secret'],
- 'grant_type=authorization_code&code=' . $authCode
- );
- $result = json_decode($response, \true);
- if (isset($result['error'])) {
- throw new \Exception($result['error_description']);
- }
- if (isset($result['access_token'])) {
- return $result['access_token'];
- }
- throw new \Exception('Unknown Error');
- }
- /**
- * Returns Yandex OAuth url
- *
- * @return string
- */
- public function createAuthUrl()
- {
- $clientConfig = $this->getClientConfig();
- return 'https://oauth.yandex.com/authorize?response_type=code&client_id=' . $clientConfig['id'];
- }
- /**
- * Returns connected oauth accounts
- *
- * @return array
- */
- public function getAccounts()
- {
- return $this->configuration->getAccounts();
- }
- /**
- * Removes oauth account
- *
- * @param string $id
- * @return boolean
- */
- public function removeAccount($id)
- {
- $userInfo = $this->getUserInfo($id);
- $this->configuration->removeAccount($id);
- Piwik::postEvent('SearchEngineKeywordsPerformance.AccountRemoved', [['provider' => \Piwik\Plugins\SearchEngineKeywordsPerformance\Provider\Yandex::getInstance()->getName(), 'account' => $userInfo['name']]]);
- return \true;
- }
- /**
- * Adds a oauth account
- *
- * @param string $id
- * @param string $config
- * @param string $username
- * @return boolean
- */
- public function addAccount($id, $accessToken, $username)
- {
- $userInfo = $this->getUserInfoByAccessToken($accessToken);
- $config = ['userInfo' => ['picture' => 'https://avatars.yandex.net/get-yapic/' . $userInfo['default_avatar_id'] . '/islands-retina-50', 'name' => $userInfo['display_name']], 'accessToken' => $accessToken];
- $this->configuration->addAccount($id, $config, $username);
- return \true;
- }
- /**
- * Fetches user info from Yandex Passport API
- *
- * @param string $accessToken
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws UnknownAPIException
- */
- protected function getUserInfoByAccessToken($accessToken)
- {
- $url = 'https://login.yandex.ru/info';
- $response = Http::sendHttpRequestBy(Http::getTransportMethod(), $url, 2000, null, null, null, 0, \false, \false, \false, \false, 'GET', null, null, null, ['Authorization: OAuth ' . $accessToken]);
- $result = json_decode($response, \true);
- if (isset($result['error'])) {
- throw new InvalidCredentialsException($result['error_description'], $result['error']);
- }
- if (empty($result) || !is_array($result) || !isset($result['display_name'])) {
- throw new UnknownAPIException('Unable to receive user information');
- }
- return $result;
- }
- /**
- * @param string $accessToken
- * @param string $hostId
- * @param string $date
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getPopularQueries($accessToken, $hostId, $date)
- {
- return $this->sendApiRequest(
- $accessToken,
- 'user/' . $this->getYandexUserId($accessToken) . '/hosts/' . $hostId . '/search-queries/popular/',
- ['date_from' => date(\DATE_ATOM, $date), 'date_to' => date(\DATE_ATOM, $date + 24 * 3600 - 1), 'order_by' => 'TOTAL_CLICKS', 'query_indicator' => ['TOTAL_CLICKS', 'TOTAL_SHOWS', 'AVG_SHOW_POSITION', 'AVG_CLICK_POSITION'], 'limit' => 500]
- );
- }
- /**
- * @param string $accessToken
- * @param string $hostId
- * @param string $date
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getIndexingHistory($accessToken, $hostId, $date)
- {
- // note we query a weeks data as otherwise the results might not contain the date we actually want to look at
- return $this->sendApiRequest(
- $accessToken,
- 'user/' . $this->getYandexUserId($accessToken) . '/hosts/' . $hostId . '/indexing/history/',
- array('date_from' => date(\DATE_ATOM, $date - 7 * 24 * 3600), 'date_to' => date(\DATE_ATOM, $date + 24 * 3600 - 1))
- );
- }
- /**
- * @param string $accessToken
- * @param string $hostId
- * @param string $date
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getPagesInIndex($accessToken, $hostId, $date)
- {
- // note we query a weeks data as otherwise the results might not contain the date we actually want to look at
- return $this->sendApiRequest(
- $accessToken,
- 'user/' . $this->getYandexUserId($accessToken) . '/hosts/' . $hostId . '/search-urls/in-search/history/',
- array('date_from' => date(\DATE_ATOM, $date - 7 * 24 * 3600), 'date_to' => date(\DATE_ATOM, $date + 24 * 3600 - 1))
- );
- }
- /**
- * @param string $accessToken
- * @param string $hostId
- * @param string $date
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getPageChangesInSearch($accessToken, $hostId, $date)
- {
- // note we query a weeks data as otherwise the results might not contain the date we actually want to look at
- return $this->sendApiRequest(
- $accessToken,
- 'user/' . $this->getYandexUserId($accessToken) . '/hosts/' . $hostId . '/search-urls/events/history/',
- array('date_from' => date(\DATE_ATOM, $date - 7 * 24 * 3600), 'date_to' => date(\DATE_ATOM, $date + 24 * 3600 - 1))
- );
- }
- /**
- * Returns the available hosts for the given access token
- * @param string $accessToken
- * @return object
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getHosts($accessToken)
- {
- return $this->sendApiRequest($accessToken, 'user/' . $this->getYandexUserId($accessToken) . '/hosts');
- }
- /**
- * Returns the Yandex User ID for the given access token
- * @param string $accessToken
- * @return string
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function getYandexUserId($accessToken)
- {
- static $userIdByToken = [];
- if (!empty($userIdByToken[$accessToken])) {
- return $userIdByToken[$accessToken];
- }
- $result = $this->sendApiRequest($accessToken, 'user');
- if (!empty($result->user_id)) {
- $userIdByToken[$accessToken] = $result->user_id;
- return $userIdByToken[$accessToken];
- }
- throw new InvalidCredentialsException('Unable to find user ID');
- }
- /**
- * @param string $accessToken
- * @param string $method
- * @param array $params
- * @return mixed
- * @throws InvalidCredentialsException
- * @throws RateLimitApiException
- * @throws UnknownAPIException
- */
- protected function sendApiRequest($accessToken, $method, $params = [])
- {
- $urlParams = [];
- foreach ($params as $name => $value) {
- if (is_array($value)) {
- foreach ($value as $val) {
- $urlParams[] = $name . '=' . urlencode($val);
- }
- continue;
- }
- $urlParams[] = $name . '=' . urlencode($value);
- }
- $url = $this->baseAPIUrl . $method . '?' . implode('&', $urlParams);
- $additionalHeaders = ['Authorization: OAuth ' . $accessToken, 'Accept: application/json', 'Content-type: application/json'];
- $response = Http::sendHttpRequestBy(
- Http::getTransportMethod(),
- $url,
- $timeout = 60,
- $userAgent = null,
- $destinationPath = null,
- $file = null,
- $followDepth = 0,
- $acceptLanguage = \false,
- $acceptInvalidSslCertificate = \false,
- $byteRange = \false,
- $getExtendedInfo = \true,
- $httpMethod = 'GET',
- $httpUsername = '',
- $httpPassword = '',
- $requestBody = null,
- $additionalHeaders
- );
- if (empty($response['data'])) {
- throw new \Exception('Yandex API returned no data: ' . var_export($response, \true));
- }
- $data = json_decode($response['data'], \false, 512, \JSON_BIGINT_AS_STRING);
- if (!empty($data->error_code)) {
- switch ($data->error_code) {
- case 'INVALID_OAUTH_TOKEN':
- case 'INVALID_USER_ID':
- throw new InvalidCredentialsException($data->error_message, (int) $data->error_code);
- case 'QUOTA_EXCEEDED':
- case 'TOO_MANY_REQUESTS_ERROR':
- throw new RateLimitApiException($data->error_message, (int) $data->error_code);
- }
- throw new UnknownAPIException($data->error_message, (int) $data->error_code);
- }
- return $data;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Columns/Keyword.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Columns/Keyword.php
deleted file mode 100644
index 8745447..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Columns/Keyword.php
+++ /dev/null
@@ -1,28 +0,0 @@
-setName('searchengines:import-bing')->setDescription('Imports Bing Keywords')->addNoValueOption('force', 'f', 'Force reimport for data')->addRequiredValueOption('idsite', '', 'Site id');
- }
- /**
- * @return int
- */
- protected function doExecute(): int
- {
- $input = $this->getInput();
- $output = $this->getOutput();
- $output->writeln("Starting to import Bing Keywords");
- $start = microtime(\true);
- $idSite = $input->getOption('idsite');
- $setting = new MeasurableSettings($idSite);
- $bingSiteUrl = $setting->bingSiteUrl;
- if (!$bingSiteUrl || !$bingSiteUrl->getValue()) {
- $output->writeln("Site with ID {$idSite} not configured for Bing Import");
- }
- $importer = new Bing($idSite, $input->hasOption('force'));
- $importer->importAllAvailableData();
- $output->writeln("Finished in " . round(microtime(\true) - $start, 3) . "s");
- return self::SUCCESS;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportGoogle.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportGoogle.php
deleted file mode 100644
index 0f59541..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportGoogle.php
+++ /dev/null
@@ -1,55 +0,0 @@
-setName('searchengines:import-google')->setDescription('Imports Google Keywords')
- ->addNoValueOption('force', 'f', 'Force reimport for data')
- ->addRequiredValueOption('idsite', '', 'Site id')
- ->addOptionalValueOption('date', 'd', 'specific date');
- }
- /**
- * @return int
- */
- protected function doExecute(): int
- {
- $input = $this->getInput();
- $output = $this->getOutput();
- $output->writeln("Starting to import Google Keywords");
- $start = microtime(\true);
- $idSite = $input->getOption('idsite');
- $setting = new MeasurableSettings($idSite);
- $searchConsoleUrl = $setting->googleSearchConsoleUrl;
- if (!$searchConsoleUrl || !$searchConsoleUrl->getValue()) {
- $output->writeln("Site with ID {$idSite} not configured for Google Import");
- }
- $importer = new Google($idSite, $input->getOption('force'));
- $date = $input->getOption('date') ? $input->getOption('date') : null;
- $importer->importAllAvailableData($date);
- $output->writeln("Finished in " . round(microtime(\true) - $start, 3) . "s");
- return self::SUCCESS;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportYandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportYandex.php
deleted file mode 100644
index f8d243e..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportYandex.php
+++ /dev/null
@@ -1,55 +0,0 @@
-setName('searchengines:import-yandex')->setDescription('Imports Yandex Keywords')
- ->addNoValueOption('force', 'f', 'Force reimport for data')
- ->addRequiredValueOption('idsite', '', 'Site id')
- ->addOptionalValueOption('date', 'd', 'specific date');
- }
- /**
- * @return int
- */
- protected function doExecute(): int
- {
- $input = $this->getInput();
- $output = $this->getOutput();
- $output->writeln("Starting to import Yandex Keywords");
- $start = microtime(\true);
- $idSite = $input->getOption('idsite');
- $setting = new MeasurableSettings($idSite);
- $yandexSiteUrl = $setting->yandexAccountAndHostId;
- if (!$yandexSiteUrl || !$yandexSiteUrl->getValue()) {
- $output->writeln("Site with ID {$idSite} not configured for Yandex Import");
- }
- $importer = new Yandex($idSite, $input->hasOption('force'));
- $date = $input->hasOption('date') ? $input->getOption('date') : 100;
- $importer->importAllAvailableData($date);
- $output->writeln("Finished in " . round(microtime(\true) - $start, 3) . "s");
- return self::SUCCESS;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Controller.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Controller.php
deleted file mode 100644
index ee30156..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Controller.php
+++ /dev/null
@@ -1,1125 +0,0 @@
-showNotificationIfNoWebsiteConfigured($provider);
- }
- SearchEngineKeywordsPerformance::displayNotificationIfRecentApiErrorsExist($viewVariables['providers']);
-
- $viewVariables['providers'] = array_map(function (ProviderAbstract $provider) {
- return $this->toProviderArray($provider);
- }, $viewVariables['providers']);
-
- return $this->renderTemplate('index', $viewVariables);
- }
-
- private function toProviderArray(ProviderAbstract $provider)
- {
- return [
- 'id' => $provider->getId(),
- 'is_configured' => $provider->isConfigured(),
- 'configured_site_ids' => $provider->getConfiguredSiteIds(),
- 'problems' => $provider->getConfigurationProblems(),
- 'is_experimental' => $provider->isExperimental(),
- 'logos' => $provider->getLogoUrls(),
- 'name' => $provider->getName(),
- 'description' => $provider->getDescription(),
- 'note' => $provider->getNote(),
- ];
- }
-
- private function showNotificationIfNoWebsiteConfigured(ProviderAbstract $provider)
- {
- if (!$provider->isConfigured()) {
- return;
- }
-
- if (count($provider->getConfiguredSiteIds()) == 0) {
- $notification = new Notification(Piwik::translate(
- 'SearchEngineKeywordsPerformance_NoWebsiteConfiguredWarning',
- $provider->getName()
- ));
- $notification->context = Notification::CONTEXT_WARNING;
- Notification\Manager::notify($provider->getId() . 'nowebsites', $notification);
- }
-
- $errors = $provider->getConfigurationProblems();
-
- if (count($errors['sites'])) {
- $notification = new Notification(Piwik::translate(
- 'SearchEngineKeywordsPerformance_ProviderXSitesWarning',
- [$provider->getName()]
- ));
- $notification->context = Notification::CONTEXT_WARNING;
- $notification->raw = true;
- Notification\Manager::notify($provider->getId() . 'siteswarning', $notification);
- }
-
- if (count($errors['accounts'])) {
- $notification = new Notification(Piwik::translate(
- 'SearchEngineKeywordsPerformance_ProviderXAccountWarning',
- [$provider->getName()]
- ));
- $notification->context = Notification::CONTEXT_WARNING;
- $notification->raw = true;
- Notification\Manager::notify($provider->getId() . 'accountwarning', $notification);
- }
- }
-
- private function getCurrentSite()
- {
- if ($this->site instanceof Site) {
- return ['id' => $this->site->getId(), 'name' => $this->site->getName()];
- }
-
- $sites = Request::processRequest('SitesManager.getSitesWithAdminAccess', [], []);
-
- if (!empty($sites[0])) {
- return ['id' => $sites[0]['idsite'], 'name' => $sites[0]['name']];
- }
-
- return [];
- }
-
- /*****************************************************************************************
- * Configuration actions for Google provider
- */
-
- /**
- * Show Google configuration page
- *
- * @param bool $hasOAuthError indicates if a oAuth access error occurred
- * @return string
- */
- public function configureGoogle($hasOAuthError = false)
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $configSaved = $this->configureGoogleClientIfProvided();
- if (false === $configSaved) {
- $notification = new Notification(Piwik::translate('SearchEngineKeywordsPerformance_ClientConfigSaveError'));
- $notification->context = Notification::CONTEXT_ERROR;
- Notification\Manager::notify('clientConfigSaved', $notification);
- }
-
- $errorMessage = Common::getRequestVar('error', '');
- if (!empty($errorMessage)) {
- if ($errorMessage === 'access_denied') {
- $errorMessage = Piwik::translate('SearchEngineKeywordsPerformance_OauthFailedMessage');
- } elseif ($errorMessage === 'jwt_validation_error') {
- $errorMessage = Piwik::translate('General_ExceptionSecurityCheckFailed');
- }
- $notification = new Notification($errorMessage);
- $notification->context = Notification::CONTEXT_ERROR;
- $notification->type = Notification::TYPE_TRANSIENT;
- Notification\Manager::notify('configureerror', $notification);
- }
-
- $googleClient = ProviderGoogle::getInstance()->getClient();
- $clientConfigured = true;
-
- try {
- $googleClient->getConfiguredClient('');
- } catch (MissingClientConfigException $e) {
- $clientConfigured = false;
- } catch (MissingOAuthConfigException $e) {
- // ignore missing accounts
- } catch (\Exception $e) {
- // Catch any general exceptions because they likely won't be recoverable. Delete the config so that they can try again
- // If we don't delete the config, the customer won't have any way to fix the issue
- $googleClient->deleteClientConfig();
-
- // Make sure we cancel the success notification because that could confuse the customer since things failed
- Notification\Manager::cancel('clientConfigSaved');
-
- // Mark the client as not configured and notify the user that something is wrong with the configuration
- $clientConfigured = false;
- $notification = new Notification($e->getMessage());
- $notification->context = Notification::CONTEXT_ERROR;
- $notification->type = Notification::TYPE_TRANSIENT;
- Notification\Manager::notify('configureerror', $notification);
- }
-
- $this->addGoogleSiteConfigIfProvided();
- $this->removeGoogleSiteConfigIfProvided();
- $this->removeGoogleAccountIfProvided();
-
- $urlOptions = [];
- $accounts = $googleClient->getAccounts();
- $countOfAccountsWithAccess = 0;
-
- foreach ($accounts as $id => &$account) {
- $userInfo = $googleClient->getUserInfo($id);
- $urls = $googleClient->getAvailableUrls($id, false);
- $account['picture'] = $userInfo['picture'];
- $account['name'] = $userInfo['name'];
- $account['urls'] = $urls;
- $account['hasAccess'] = Piwik::hasUserSuperUserAccessOrIsTheUser($account['username']);
- if ($account['hasAccess']) {
- ++$countOfAccountsWithAccess;
- }
- $account['created_formatted'] = Date::factory(date(
- 'Y-m-d',
- $account['created']
- ))->getLocalized(Date::DATE_FORMAT_LONG);
- try {
- $googleClient->testConfiguration($id);
- } catch (\Exception $e) {
- $account['hasError'] = $e->getMessage();
- }
-
- if ($account['hasAccess']) {
- foreach ($googleClient->getAvailableUrls($id) as $url => $status) {
- $urlOptions[$id . '##' . $url] = $url . ' (' . $account['name'] . ')';
- }
- }
- }
-
- $isClientConfigurable = StaticContainer::get('SearchEngineKeywordsPerformance.Google.isClientConfigurable');
-
- $viewVariables = [];
- $viewVariables['isConfigured'] = $googleClient->isConfigured();
- $viewVariables['clientId'] = $googleClient->getClientId();
- $viewVariables['auth_nonce'] = Nonce::getNonce('SEKP.google.auth');
- $viewVariables['clientSecret'] = preg_replace('/\w/', '*', $googleClient->getClientSecret() ?? '');
- $viewVariables['isClientConfigured'] = $clientConfigured;
- $viewVariables['isClientConfigurable'] = $isClientConfigurable;
- $viewVariables['isOAuthConfigured'] = count($accounts) > 0;
- $viewVariables['accounts'] = $accounts;
- $viewVariables['urlOptions'] = $urlOptions;
- $viewVariables['hasOAuthError'] = $hasOAuthError;
- $viewVariables['configuredMeasurables'] = ProviderGoogle::getInstance()->getConfiguredSiteIds();
- $viewVariables['nonce'] = Nonce::getNonce('SEKP.google.config');
- $viewVariables['sitesInfos'] = [];
- $viewVariables['currentSite'] = $this->getCurrentSite();
- $viewVariables['countOfAccountsWithAccess'] = $countOfAccountsWithAccess;
- $viewVariables['addGoogleSiteConfigNonce'] = Nonce::getNonce(self::GOOGLE_ADD_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeGoogleSiteConfigNonce'] = Nonce::getNonce(self::GOOGLE_REMOVE_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeGoogleAccountNonce'] = Nonce::getNonce(self::GOOGLE_REMOVE_ACCOUNT_NONCE_KEY);
-
- $siteIds = $viewVariables['configuredMeasurables'];
-
- foreach ($siteIds as $siteId => $config) {
- $googleSiteUrl = $config['googleSearchConsoleUrl'];
- $viewVariables['sitesInfos'][$siteId] = Site::getSite($siteId);
- $lastRun = Option::get('GoogleImporterTask_LastRun_' . $siteId);
-
- if ($lastRun) {
- $lastRun = date('Y-m-d H:i', $lastRun) . ' UTC';
- } else {
- $lastRun = Piwik::translate('General_Never');
- }
-
- $viewVariables['sitesInfos'][$siteId]['lastRun'] = $lastRun;
-
- [$accountId, $url] = explode('##', $googleSiteUrl);
-
- try {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = $googleClient->testConfiguration($accountId);
- } catch (\Exception $e) {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = false;
- }
-
- $urls = $googleClient->getAvailableUrls($accountId);
-
- $viewVariables['sitesInfos'][$siteId]['urlValid'] = key_exists($url, $urls);
- }
-
- if (!empty($this->securityPolicy)) {
- $this->securityPolicy->addPolicy('img-src', '*.googleusercontent.com');
- }
-
- $configureConnectionProps = [];
- $configureConnectionProps['baseUrl'] = Url::getCurrentUrlWithoutQueryString();
- $configureConnectionProps['baseDomain'] = Url::getCurrentScheme() . '://' . Url::getCurrentHost();
- $configureConnectionProps['manualConfigNonce'] = $viewVariables['nonce'];
- $configureConnectionProps['primaryText'] = Piwik::translate('SearchEngineKeywordsPerformance_ConfigureTheImporterLabel1');
-
- // There are certain cases where index.php isn't part of the baseUrl when it should be. Append it if missing.
- if (stripos($configureConnectionProps['baseUrl'], 'index.php') === false) {
- $configureConnectionProps['baseUrl'] .= 'index.php';
- }
-
- $isConnectAccountsActivated = Manager::getInstance()->isPluginActivated('ConnectAccounts');
- $authBaseUrl = $isConnectAccountsActivated ? "https://" . StaticContainer::get('CloudAccountsInstanceId') . '/index.php?' : '';
- $jwt = Common::getRequestVar('state', '', 'string');
- if (empty($jwt) && Piwik::hasUserSuperUserAccess() && $isConnectAccountsActivated) {
- // verify an existing user by supplying a jwt too
- $jwt = ConnectHelper::buildOAuthStateJwt(
- SettingsPiwik::getPiwikInstanceId(),
- ConnectAccounts::INITIATED_BY_SEK
- );
- }
- $googleAuthUrl = '';
- if ($isConnectAccountsActivated) {
- $strategyName = GoogleSearchConnect::getStrategyName();
- $googleAuthUrl = $authBaseUrl . Http::buildQuery([
- 'module' => 'ConnectAccounts',
- 'action' => 'initiateOauth',
- 'state' => $jwt,
- 'strategy' => $strategyName
- ]);
- $configureConnectionProps['strategy'] = $strategyName;
- $configureConnectionProps['connectedWith'] = 'Google';
- $configureConnectionProps['unlinkUrl'] = Url::getCurrentUrlWithoutQueryString() . '?' . Http::buildQuery([
- 'module' => 'ConnectAccounts',
- 'action' => 'unlink',
- 'nonce' => ConnectHelper::getUnlinkNonce(),
- 'strategy' => $strategyName
- ]);
- $configureConnectionProps['authUrl'] = $googleAuthUrl;
- $configureConnectionProps['connectAccountsUrl'] = $googleAuthUrl;
- $configureConnectionProps['connectAccountsBtnText'] = Piwik::translate('ConnectAccounts_ConnectWithGoogleText');
- }
-
- $configureConnectionProps['isConnectAccountsActivated'] = $isConnectAccountsActivated;
- if ($isConnectAccountsActivated) {
- $configureConnectionProps['radioOptions'] = [
- 'connectAccounts' => Piwik::translate('SearchEngineKeywordsPerformance_OptionQuickConnectWithGoogle'),
- 'manual' => Piwik::translate('ConnectAccounts_OptionAdvancedConnectWithGa'),
- ];
- }
- $configureConnectionProps['googleAuthUrl'] = $googleAuthUrl;
- $faqUrl = Url::addCampaignParametersToMatomoLink('https://matomo.org/faq/reports/import-google-search-keywords-in-matomo/#how-to-set-up-google-search-console-and-verify-your-website');
- $faqAnchorOpen = "";
- $configureConnectionProps['manualConfigText'] = Piwik::translate('SearchEngineKeywordsPerformance_ConfigureTheImporterLabel2')
- . ' ' . Piwik::translate('SearchEngineKeywordsPerformance_ConfigureTheImporterLabel3', [
- $faqAnchorOpen,
- '',
- ]) . '
' . Piwik::translate('SearchEngineKeywordsPerformance_OAuthExampleText')
- . ' ' . Piwik::translate('SearchEngineKeywordsPerformance_GoogleAuthorizedJavaScriptOrigin')
- . ": {$configureConnectionProps['baseDomain']} "
- . Piwik::translate('SearchEngineKeywordsPerformance_GoogleAuthorizedRedirectUri')
- . ": {$configureConnectionProps['baseUrl']}?module=SearchEngineKeywordsPerformance&action=processAuthCode";
-
- $viewVariables['configureConnectionProps'] = $configureConnectionProps;
- $viewVariables['extensions'] = self::getComponentExtensions();
- $viewVariables['removeConfigUrl'] = Url::getCurrentQueryStringWithParametersModified([ 'action' => 'removeGoogleClientConfig' ]);
-
- return $this->renderTemplate('google\configuration', $viewVariables);
- }
-
- /**
- * Save Google client configuration if set in request
- *
- * @return bool|null bool on success or failure, null if not data present in request
- */
- protected function configureGoogleClientIfProvided()
- {
- $googleClient = ProviderGoogle::getInstance()->getClient();
-
- $config = Common::getRequestVar('client', '');
-
- if (empty($config) && !empty($_FILES['clientfile'])) {
- if (!empty($_FILES['clientfile']['error'])) {
- return false;
- }
-
- $file = $_FILES['clientfile']['tmp_name'];
- if (!file_exists($file)) {
- return false;
- }
-
- $config = file_get_contents($_FILES['clientfile']['tmp_name']);
- }
-
- if (!empty($config)) {
- Nonce::checkNonce('SEKP.google.config', Common::getRequestVar('config_nonce'));
- try {
- $config = Common::unsanitizeInputValue($config);
- $saveResult = $googleClient->setClientConfig($config);
- if (!$saveResult) {
- return false;
- }
-
- // Show success notification
- $notification = new Notification(Piwik::translate('SearchEngineKeywordsPerformance_ClientConfigImported'));
- $notification->context = Notification::CONTEXT_SUCCESS;
- Notification\Manager::notify('clientConfigSaved', $notification);
-
- // Redirect so that it's the correct URL and doesn't try to resubmit the form if the customer refreshes
- Url::redirectToUrl(Url::getCurrentUrlWithoutQueryString() . Url::getCurrentQueryStringWithParametersModified([
- 'action' => 'configureGoogle',
- 'code' => null,
- 'scope' => null,
- 'state' => null,
- 'error' => null,
- ]));
- } catch (\Exception $e) {
- return false;
- }
- }
-
- return null;
- }
-
- /**
- * Save google configuration for a site if given in request
- */
- protected function addGoogleSiteConfigIfProvided()
- {
- $googleSiteId = Common::getRequestVar('googleSiteId', '');
- $googleAccountAndUrl = Common::getRequestVar('googleAccountAndUrl', '');
- $googleTypes = explode(',', Common::getRequestVar('googleTypes', ''));
-
- if (!empty($googleSiteId) && !empty($googleAccountAndUrl)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::GOOGLE_ADD_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('addSiteConfigNonce', ''));
- // Do not allow to configure websites with unsupported type or force enabled config
- if (SearchEngineKeywordsPerformance::isGoogleForceEnabled($googleSiteId) || WebsiteMeasurableType::ID !== Site::getTypeFor($googleSiteId)) {
- $notification = new Notification(
- Piwik::translate('SearchEngineKeywordsPerformance_WebsiteTypeUnsupported', [
- Site::getNameFor($googleSiteId)
- ])
- );
-
- if (class_exists('\Piwik\Plugins\RollUpReporting\Type') && \Piwik\Plugins\RollUpReporting\Type::ID === Site::getTypeFor($googleSiteId)) {
- $notification->message .= ' ' . Piwik::translate('SearchEngineKeywordsPerformance_WebsiteTypeUnsupportedRollUp');
- }
-
- $notification->context = Notification::CONTEXT_ERROR;
- $notification->raw = true;
- $notification->flags = Notification::FLAG_CLEAR;
- Notification\Manager::notify('websiteNotConfigurable', $notification);
-
- return;
- }
-
- $measurableSettings = new MeasurableSettings($googleSiteId);
- $measurableSettings->googleConfigCreatedBy->setValue(Piwik::getCurrentUserLogin());
-
- //Need to explicitly setIsWritableByCurrentUser=true, since it can be set as false when we instantiate MeasurableSettings object due to previously added by another user
- $measurableSettings->googleSearchConsoleUrl->setIsWritableByCurrentUser(true);
- $measurableSettings->googleWebKeywords->setIsWritableByCurrentUser(true);
- $measurableSettings->googleImageKeywords->setIsWritableByCurrentUser(true);
- $measurableSettings->googleNewsKeywords->setIsWritableByCurrentUser(true);
- $measurableSettings->googleVideoKeywords->setIsWritableByCurrentUser(true);
-
- $measurableSettings->googleSearchConsoleUrl->setValue($googleAccountAndUrl);
- $measurableSettings->googleWebKeywords->setValue(in_array('web', $googleTypes));
- $measurableSettings->googleImageKeywords->setValue(in_array('image', $googleTypes));
- $measurableSettings->googleNewsKeywords->setValue(in_array('news', $googleTypes));
- $measurableSettings->googleVideoKeywords->setValue(in_array('video', $googleTypes));
- $measurableSettings->save();
-
- $notification = new Notification(
- Piwik::translate('SearchEngineKeywordsPerformance_WebsiteSuccessfulConfigured', [
- Site::getNameFor($googleSiteId),
- '',
- ''
- ])
- );
- $notification->context = Notification::CONTEXT_SUCCESS;
- $notification->raw = true;
- $notification->flags = Notification::FLAG_CLEAR;
- Notification\Manager::notify('websiteConfigured', $notification);
- }
- }
-
- /**
- * Removes a Google account if `remove` param is given in request
- */
- protected function removeGoogleAccountIfProvided()
- {
- $remove = Common::getRequestVar('remove', '');
-
- if (!empty($remove)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::GOOGLE_REMOVE_ACCOUNT_NONCE_KEY, $request->getStringParameter('removeAccountNonce', ''));
- ProviderGoogle::getInstance()->getClient()->removeAccount($remove);
-
- $sitesWithConfig = ProviderGoogle::getInstance()->getConfiguredSiteIds();
- foreach ($sitesWithConfig as $siteId => $siteConfig) {
- $googleSetting = explode('##', $siteConfig['googleSearchConsoleUrl']);
- if (!empty($googleSetting[0]) && $googleSetting[0] == $remove) {
- $config = new MeasurableSettings($siteId);
- $config->googleSearchConsoleUrl->setValue('0');
- $config->save();
- }
- }
- }
- }
-
- /**
- * Removes a Google site config if `removeConfig` param is given in request
- */
- protected function removeGoogleSiteConfigIfProvided()
- {
- $removeConfig = Common::getRequestVar('removeConfig', '');
-
- if (!empty($removeConfig)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::GOOGLE_REMOVE_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('removeSiteConfigNonce', ''));
- $measurableSettings = new MeasurableSettings($removeConfig);
- $measurableSettings->googleSearchConsoleUrl->setValue('0');
- $measurableSettings->save();
- }
- }
-
- /**
- * Delete the Google client config option so that the customer will be prompted to upload a new one or use the Cloud
- * config. Then refresh the page so show the change.
- */
- public function removeGoogleClientConfig()
- {
- Piwik::checkUserHasSuperUserAccess();
-
- Nonce::checkNonce('SEKP.google.config', Common::getRequestVar('config_nonce'));
-
- ProviderGoogle::getInstance()->getClient()->deleteClientConfig();
-
- Url::redirectToUrl(Url::getCurrentUrlWithoutQueryString() . Url::getCurrentQueryStringWithParametersModified([
- 'action' => 'configureGoogle',
- 'code' => null,
- 'scope' => null,
- 'state' => null,
- 'error' => null,
- ]));
- }
-
- public function forwardToAuth()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- Nonce::checkNonce('SEKP.google.auth', Common::getRequestVar('auth_nonce'));
-
- $client = ProviderGoogle::getInstance()->getClient();
- $state = Nonce::getNonce(self::OAUTH_STATE_NONCE_NAME, 900);
-
- Url::redirectToUrl($client->createAuthUrl($state));
- }
-
- protected function getSession()
- {
- return new SessionNamespace('searchperformance');
- }
-
- /**
- * Processes the response from google oauth service
- *
- * @return string
- * @throws \Exception
- */
- public function processAuthCode()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $error = Common::getRequestVar('error', '');
- $oauthCode = Common::getRequestVar('code', '');
-
- if (!$error) {
- $state = Common::getRequestVar('state');
- if ($state && !empty($_SERVER['HTTP_REFERER']) && stripos($_SERVER['HTTP_REFERER'], 'https://accounts.google.') === 0) {
- //We need tp update this, else it will fail for referer like https://accounts.google.co.in
- $_SERVER['HTTP_REFERER'] = 'https://accounts.google.com';
- }
- try {
- Nonce::checkNonce(static::OAUTH_STATE_NONCE_NAME, $state, defined('PIWIK_TEST_MODE') ? null : 'google.com');
- } catch (\Exception $ex) {
- $error = $ex->getMessage();
- }
- }
-
- if ($error) {
- return $this->configureGoogle(true);
- }
-
- try {
- ProviderGoogle::getInstance()->getClient()->processAuthCode($oauthCode);
- } catch (\Exception $e) {
- return $this->configureGoogle($e->getMessage());
- }
-
- // we need idSite in the url to display all the menus like Conversion Import after redirect
- $siteInfo = $this->getCurrentSite();
- // reload index action to prove everything is configured
- Url::redirectToUrl(Url::getCurrentUrlWithoutQueryString() . Url::getCurrentQueryStringWithParametersModified([
- 'action' => 'configureGoogle',
- 'idSite' => (isset($siteInfo['id']) ? $siteInfo['id'] : 0),
- 'code' => null,
- 'scope' => null,
- 'state' => null
- ]));
- }
- /******************************************************************************************
- *****************************************************************************************/
-
- /*****************************************************************************************
- *****************************************************************************************
- * Configuration actions for Bing provider
- */
-
- /**
- * Show configuration page for Bing
- *
- * @return string
- */
- public function configureBing()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $viewVariables = [];
- $viewVariables['apikey'] = '';
- $bingClient = ProviderBing::getInstance()->getClient();
-
- $apiKey = Common::getRequestVar('apikey', '');
-
- if (!empty($apiKey)) {
- Nonce::checkNonce('SEKP.bing.config', Common::getRequestVar('config_nonce'));
- try {
- $bingClient->testConfiguration($apiKey);
- $bingClient->addAccount($apiKey, Piwik::getCurrentUserLogin());
- } catch (\Exception $e) {
- $viewVariables['error'] = $e->getMessage();
- $viewVariables['apikey'] = $apiKey;
- }
- }
-
- $this->addBingSiteConfigIfProvided();
- $this->removeBingSiteConfigIfProvided();
- $this->removeBingAccountIfProvided();
-
- $urlOptions = [];
- $accounts = $bingClient->getAccounts();
- $countOfAccountsWithAccess = 0;
- foreach ($accounts as &$account) {
- $account['urls'] = [];
- $account['created_formatted'] = Date::factory(date(
- 'Y-m-d',
- $account['created']
- ))->getLocalized(Date::DATE_FORMAT_LONG);
- $account['hasAccess'] = Piwik::hasUserSuperUserAccessOrIsTheUser($account['username']);
- if ($account['hasAccess']) {
- ++$countOfAccountsWithAccess;
- }
- try {
- $bingClient->testConfiguration($account['apiKey']);
- } catch (\Exception $e) {
- $account['hasError'] = $e->getMessage();
- continue;
- }
-
- $account['urls'] = $bingClient->getAvailableUrls($account['apiKey'], false);
-
- if ($account['hasAccess']) {
- foreach ($bingClient->getAvailableUrls($account['apiKey']) as $url => $status) {
- $urlOptions[$account['apiKey'] . '##' . $url] = $url . ' (' . substr(
- $account['apiKey'],
- 0,
- 5
- ) . '*****' . substr($account['apiKey'], -5, 5) . ')';
- }
- }
- }
-
- $viewVariables['nonce'] = Nonce::getNonce('SEKP.bing.config');
- $viewVariables['accounts'] = $accounts;
- $viewVariables['urlOptions'] = $urlOptions;
- $viewVariables['configuredMeasurables'] = ProviderBing::getInstance()->getConfiguredSiteIds();
- $viewVariables['sitesInfos'] = [];
- $viewVariables['currentSite'] = $this->getCurrentSite();
- $viewVariables['countOfAccountsWithAccess'] = $countOfAccountsWithAccess;
- $viewVariables['addBingSiteConfigNonce'] = Nonce::getNonce(self::BING_ADD_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeBingSiteConfigNonce'] = Nonce::getNonce(self::BING_REMOVE_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeBingAccountNonce'] = Nonce::getNonce(self::BING_REMOVE_ACCOUNT_NONCE_KEY);
-
- $siteIds = $viewVariables['configuredMeasurables'];
-
- foreach ($siteIds as $siteId => $config) {
- $viewVariables['sitesInfos'][$siteId] = Site::getSite($siteId);
- $lastRun = Option::get('BingImporterTask_LastRun_' . $siteId);
-
- if ($lastRun) {
- $lastRun = date('Y-m-d H:i', $lastRun) . ' UTC';
- } else {
- $lastRun = Piwik::translate('General_Never');
- }
-
- $viewVariables['sitesInfos'][$siteId]['lastRun'] = $lastRun;
-
- $bingSiteUrl = $config['bingSiteUrl'];
- [$apiKey, $url] = explode('##', $bingSiteUrl);
-
- try {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = $bingClient->testConfiguration($apiKey);
- } catch (\Exception $e) {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = false;
- }
-
- $urls = $bingClient->getAvailableUrls($apiKey);
-
- $viewVariables['sitesInfos'][$siteId]['urlValid'] = key_exists($url, $urls);
- }
-
- return $this->renderTemplate('bing\configuration', $viewVariables);
- }
-
- /**
- * Save Bing configuration for a site if given in request
- */
- protected function addBingSiteConfigIfProvided()
- {
- $bingSiteId = Common::getRequestVar('bingSiteId', '');
- $bingAccountAndUrl = Common::getRequestVar('bingAccountAndUrl', '');
-
- if (!empty($bingSiteId) && !empty($bingAccountAndUrl)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::BING_ADD_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('addSiteConfigNonce', ''));
- // Do not allow to configure websites with unsupported type or force enabled config
- if (SearchEngineKeywordsPerformance::isBingForceEnabled($bingSiteId) || WebsiteMeasurableType::ID !== Site::getTypeFor($bingSiteId)) {
- $notification = new Notification(
- Piwik::translate('SearchEngineKeywordsPerformance_WebsiteTypeUnsupported', [
- Site::getNameFor($bingSiteId)
- ])
- );
-
- if (class_exists('\Piwik\Plugins\RollUpReporting\Type') && \Piwik\Plugins\RollUpReporting\Type::ID === Site::getTypeFor($bingSiteId)) {
- $notification->message .= ' ' . Piwik::translate('SearchEngineKeywordsPerformance_WebsiteTypeUnsupportedRollUp');
- }
-
- $notification->context = Notification::CONTEXT_ERROR;
- $notification->raw = true;
- $notification->flags = Notification::FLAG_CLEAR;
- Notification\Manager::notify('websiteNotConfigurable', $notification);
-
- return;
- }
-
- $measurableSettings = new MeasurableSettings($bingSiteId);
- $measurableSettings->bingConfigCreatedBy->setValue(Piwik::getCurrentUserLogin());
-
- //Need to explicitly setIsWritableByCurrentUser=true, since it can be set as false when we instantiate MeasurableSettings object due to previously added by another user
- $measurableSettings->bingSiteUrl->setIsWritableByCurrentUser(true);
-
- $measurableSettings->bingSiteUrl->setValue($bingAccountAndUrl);
- $measurableSettings->save();
-
- $notification = new Notification(
- Piwik::translate('SearchEngineKeywordsPerformance_WebsiteSuccessfulConfigured', [
- Site::getNameFor($bingSiteId),
- '',
- ''
- ])
- );
- $notification->context = Notification::CONTEXT_SUCCESS;
- $notification->raw = true;
- $notification->flags = Notification::FLAG_CLEAR;
- Notification\Manager::notify('websiteConfigured', $notification);
- }
- }
-
- /**
- * Removes a Bing account if `remove` param is given in request
- */
- protected function removeBingAccountIfProvided()
- {
- $remove = Common::getRequestVar('remove', '');
-
- if (!empty($remove)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::BING_REMOVE_ACCOUNT_NONCE_KEY, $request->getStringParameter('removeAccountNonce', ''));
- ProviderBing::getInstance()->getClient()->removeAccount($remove);
-
- $sitesWithConfig = ProviderBing::getInstance()->getConfiguredSiteIds();
- foreach ($sitesWithConfig as $siteId => $siteConfig) {
- $bingSetting = explode('##', $siteConfig['bingSiteUrl']);
- if (!empty($bingSetting[0]) && $bingSetting[0] == $remove) {
- $config = new MeasurableSettings($siteId);
- $config->bingSiteUrl->setValue('0');
- $config->save();
- }
- }
- }
- }
-
- /**
- * Removes a Bing site config if `removeConfig` param is given in request
- */
- protected function removeBingSiteConfigIfProvided()
- {
- $removeConfig = Common::getRequestVar('removeConfig', '');
-
- if (!empty($removeConfig)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::BING_REMOVE_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('removeSiteConfigNonce', ''));
- $measurableSettings = new MeasurableSettings($removeConfig);
- $measurableSettings->bingSiteUrl->setValue('0');
- $measurableSettings->save();
- }
- }
- /******************************************************************************************
- *****************************************************************************************/
-
-
- /*****************************************************************************************
- *****************************************************************************************
- * Configuration actions for Yandex provider
- */
-
- /**
- * Show Yandex configuration page
- *
- * @param bool $hasOAuthError indicates if a oAuth access error occurred
- * @return string
- */
- public function configureYandex($hasOAuthError = false)
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $configSaved = $this->configureYandexClientIfProvided();
-
- if (true === $configSaved) {
- $notification = new Notification(Piwik::translate('SearchEngineKeywordsPerformance_ClientConfigImported'));
- $notification->context = Notification::CONTEXT_SUCCESS;
- Notification\Manager::notify('clientConfigSaved', $notification);
- } elseif (false === $configSaved) {
- $notification = new Notification(Piwik::translate('SearchEngineKeywordsPerformance_ClientConfigSaveError'));
- $notification->context = Notification::CONTEXT_ERROR;
- Notification\Manager::notify('clientConfigSaved', $notification);
- }
-
- $yandexClient = ProviderYandex::getInstance()->getClient();
- $clientConfigured = $yandexClient->isClientConfigured();
-
- $this->addYandexSiteConfigIfProvided();
- $this->removeYandexSiteConfigIfProvided();
- $this->removeYandexAccountIfProvided();
-
- $urlOptions = [];
- $accounts = $yandexClient->getAccounts();
- $countOfAccountsWithAccess = 0;
-
- foreach ($accounts as $id => &$account) {
- $userInfo = $yandexClient->getUserInfo($id);
- $account['urls'] = [];
- $account['picture'] = $userInfo['picture'];
- $account['name'] = $userInfo['name'];
- $account['created_formatted'] = Date::factory(date(
- 'Y-m-d',
- $account['created']
- ))->getLocalized(Date::DATE_FORMAT_LONG);
- $account['authDaysAgo'] = floor((time() - $account['created']) / (3600 * 24));
- $account['hasAccess'] = Piwik::hasUserSuperUserAccessOrIsTheUser($account['username']);
- if ($account['hasAccess']) {
- ++$countOfAccountsWithAccess;
- }
-
- try {
- $yandexClient->testConfiguration($id);
- } catch (\Exception $e) {
- $account['hasError'] = $e->getMessage();
- continue;
- }
-
- $account['urls'] = $yandexClient->getAvailableUrls($id, false);
-
- if ($account['hasAccess']) {
- foreach ($yandexClient->getAvailableUrls($id) as $url => $hostData) {
- $urlOptions[$id . '##' . $hostData['host_id']] = $url . ' (' . $account['name'] . ')';
- }
- }
- }
-
- $clientConfig = $yandexClient->getClientConfig();
- $viewVariables = [];
- $viewVariables['isConfigured'] = $yandexClient->isConfigured();
- $viewVariables['auth_nonce'] = Nonce::getNonce('SEKP.yandex.auth');
- $viewVariables['clientId'] = isset($clientConfig['id']) ? $clientConfig['id'] : '';
- $viewVariables['clientSecret'] = preg_replace('/\w/', '*', isset($clientConfig['secret']) ? $clientConfig['secret'] : '');
- $viewVariables['isClientConfigured'] = $clientConfigured;
- $viewVariables['isOAuthConfigured'] = count($accounts) > 0;
- $viewVariables['accounts'] = $accounts;
- $viewVariables['urlOptions'] = $urlOptions;
- $viewVariables['hasOAuthError'] = $hasOAuthError;
- $viewVariables['configuredMeasurables'] = ProviderYandex::getInstance()->getConfiguredSiteIds();
- $viewVariables['nonce'] = Nonce::getNonce('SEKP.yandex.config');
- $viewVariables['addYandexSiteConfigNonce'] = Nonce::getNonce(self::YANDEX_ADD_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeYandexSiteConfigNonce'] = Nonce::getNonce(self::YANDEX_REMOVE_SITE_CONFIG_NONCE_KEY);
- $viewVariables['removeYandexAccountNonce'] = Nonce::getNonce(self::YANDEX_REMOVE_ACCOUNT_NONCE_KEY);
- $viewVariables['sitesInfos'] = [];
- $viewVariables['currentSite'] = $this->getCurrentSite();
- $viewVariables['currentSite'] = $this->getCurrentSite();
- $viewVariables['countOfAccountsWithAccess'] = $countOfAccountsWithAccess;
-
- $siteIds = $viewVariables['configuredMeasurables'];
-
- foreach ($siteIds as $siteId => $config) {
- $viewVariables['sitesInfos'][$siteId] = Site::getSite($siteId);
- $lastRun = Option::get('YandexImporterTask_LastRun_' . $siteId);
-
- if ($lastRun) {
- $lastRun = date('Y-m-d H:i', $lastRun) . ' UTC';
- } else {
- $lastRun = Piwik::translate('General_Never');
- }
-
- $viewVariables['sitesInfos'][$siteId]['lastRun'] = $lastRun;
-
- $yandexAccountAndHostId = $config['yandexAccountAndHostId'];
- [$accountId, $url] = explode('##', $yandexAccountAndHostId);
-
- try {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = $yandexClient->testConfiguration($accountId);
- } catch (\Exception $e) {
- $viewVariables['sitesInfos'][$siteId]['accountValid'] = false;
- }
-
- try {
- $urls = $yandexClient->getAvailableUrls($accountId);
- } catch (\Exception $e) {
- $urls = [];
- }
-
- $viewVariables['sitesInfos'][$siteId]['urlValid'] = false;
-
- foreach ($urls as $data) {
- if ($data['host_id'] == $url) {
- $viewVariables['sitesInfos'][$siteId]['urlValid'] = true;
- }
- }
- }
-
- if (!empty($this->securityPolicy)) {
- $this->securityPolicy->addPolicy('img-src', 'avatars.yandex.net');
- }
-
- $viewVariables['baseUrl'] = Url::getCurrentUrlWithoutQueryString();
- $viewVariables['baseDomain'] = Url::getCurrentScheme() . '://' . Url::getCurrentHost();
-
- return $this->renderTemplate('yandex\configuration', $viewVariables);
- }
-
- /**
- * Save Yandex configuration if set in request
- *
- * @return bool|null bool on success or failure, null if not data present in request
- */
- protected function configureYandexClientIfProvided()
- {
- $clientId = Common::getRequestVar('clientid', '');
- $clientSecret = Common::getRequestVar('clientsecret', '');
-
- if (!empty($clientSecret) || !empty($clientId)) {
- Nonce::checkNonce('SEKP.yandex.config', Common::getRequestVar('config_nonce'));
-
- $clientUpdated = false;
-
- if (!empty($clientSecret) && !empty($clientId)) {
- $yandexClient = ProviderYandex::getInstance()->getClient();
- $yandexClient->setClientConfig($clientId, $clientSecret);
- $clientUpdated = true;
- }
-
- return $clientUpdated;
- }
-
- return null;
- }
-
- /**
- * Save yandex configuration for a site if given in request
- */
- protected function addYandexSiteConfigIfProvided()
- {
- $yandexSiteId = Common::getRequestVar('yandexSiteId', '');
- $yandexAccountAndHostId = Common::getRequestVar('yandexAccountAndHostId', '');
-
- if (!empty($yandexSiteId) && !empty($yandexAccountAndHostId)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::YANDEX_ADD_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('addSiteConfigNonce', ''));
- $measurableSettings = new MeasurableSettings($yandexSiteId);
- $measurableSettings->yandexConfigCreatedBy->setValue(Piwik::getCurrentUserLogin());
-
- //Need to explicitly setIsWritableByCurrentUser=true, since it can be set as false when we instantiate MeasurableSettings object due to previously added by another user
- $measurableSettings->yandexAccountAndHostId->setIsWritableByCurrentUser(true);
-
- $measurableSettings->yandexAccountAndHostId->setValue($yandexAccountAndHostId);
-
- $measurableSettings->save();
-
- $notification = new Notification(
- Piwik::translate('SearchEngineKeywordsPerformance_WebsiteSuccessfulConfigured', [
- Site::getNameFor($yandexSiteId),
- '',
- ''
- ])
- );
- $notification->context = Notification::CONTEXT_SUCCESS;
- $notification->raw = true;
- $notification->flags = Notification::FLAG_CLEAR;
- Notification\Manager::notify('websiteConfigured', $notification);
- }
- }
-
- /**
- * Removes a Yandex account if `remove` param is given in request
- */
- protected function removeYandexAccountIfProvided()
- {
- $remove = Common::getRequestVar('remove', '');
-
- if (!empty($remove)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::YANDEX_REMOVE_ACCOUNT_NONCE_KEY, $request->getStringParameter('removeAccountNonce', ''));
- ProviderYandex::getInstance()->getClient()->removeAccount($remove);
-
- $sitesWithConfig = ProviderYandex::getInstance()->getConfiguredSiteIds();
- foreach ($sitesWithConfig as $siteId => $siteConfig) {
- $yandexSetting = explode('##', $siteConfig['yandexAccountAndHostId']);
- if (!empty($yandexSetting[0]) && $yandexSetting[0] == $remove) {
- $config = new MeasurableSettings($siteId);
- $config->yandexAccountAndHostId->setValue('0');
- $config->save();
- }
- }
- }
- }
-
- /**
- * Removes a Yandex site config if `removeConfig` param is given in request
- */
- protected function removeYandexSiteConfigIfProvided()
- {
- $removeConfig = Common::getRequestVar('removeConfig', '');
-
- if (!empty($removeConfig)) {
- $request = \Piwik\Request::fromRequest();
- Nonce::checkNonce(self::YANDEX_REMOVE_SITE_CONFIG_NONCE_KEY, $request->getStringParameter('removeSiteConfigNonce', ''));
- $measurableSettings = new MeasurableSettings($removeConfig);
- $measurableSettings->yandexAccountAndHostId->setValue('0');
- $measurableSettings->save();
- }
- }
-
-
- public function forwardToYandexAuth()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- Nonce::checkNonce('SEKP.yandex.auth', Common::getRequestVar('auth_nonce'));
-
- $session = $this->getSession();
- $session->yandexauthtime = time() + 60 * 15;
-
- Url::redirectToUrl(ProviderYandex::getInstance()->getClient()->createAuthUrl());
- }
-
- /**
- * Processes an auth code given by Yandex
- */
- public function processYandexAuthCode()
- {
- Piwik::checkUserHasSomeAdminAccess();
-
- $error = Common::getRequestVar('error', '');
- $oauthCode = Common::getRequestVar('code', '');
- $timeLimit = $this->getSession()->yandexauthtime;
-
- // if the auth wasn't triggered within the allowed time frame
- if (!$timeLimit || time() > $timeLimit) {
- $error = true;
- }
-
- if ($error) {
- return $this->configureYandex(true);
- }
-
- try {
- ProviderYandex::getInstance()->getClient()->processAuthCode($oauthCode);
- } catch (\Exception $e) {
- return $this->configureYandex($e->getMessage());
- }
-
- // we need idSite in the url to display all the menus like Conversion Import after redirect
- $siteInfo = $this->getCurrentSite();
-
- // reload index action to prove everything is configured
- Url::redirectToUrl(Url::getCurrentUrlWithoutQueryString() . Url::getCurrentQueryStringWithParametersModified([
- 'action' => 'configureYandex',
- 'idSite' => (isset($siteInfo['id']) ? $siteInfo['id'] : 0),
- 'code' => null
- ]));
- }
-
- /**
- * Get the map of component extensions to be passed into the Vue template. This allows other plugins to provide
- * content to display in the template. In this case this plugin will display one component, but that can be
- * overridden by the ConnectAccounts plugin to display a somewhat different component. This is doing something
- * similar to what we use {{ postEvent('MyPlugin.MyEventInATemplate) }} for in Twig templates.
- *
- * @return array Map of component extensions. Like [ 'plugin' => 'PluginName', 'component' => 'ComponentName' ]
- * See {@link https://developer.matomo.org/guides/in-depth-vue#allowing-plugins-to-add-content-to-your-vue-components the developer documentation} for more information.
- */
- public static function getComponentExtensions(): array
- {
- $componentExtensions = [];
- Piwik::postEvent('SearchEngineKeywordsPerformance.getGoogleConfigComponentExtensions', [
- &$componentExtensions
- ]);
- return $componentExtensions;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/BingAccountDiagnostic.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/BingAccountDiagnostic.php
deleted file mode 100644
index 754876a..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/BingAccountDiagnostic.php
+++ /dev/null
@@ -1,74 +0,0 @@
-translator = $translator;
- }
- public function execute()
- {
- $client = ProviderBing::getInstance()->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- // skip if no accounts configured
- }
- $errors = ProviderBing::getInstance()->getConfigurationProblems();
- $resultAccounts = new DiagnosticResult(Bing::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_ConfiguredAccounts'));
- foreach ($accounts as $account) {
- if (array_key_exists($account['apiKey'], $errors['accounts'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, $this->obfuscateApiKey($account['apiKey']) . ': ' . $errors['accounts'][$account['apiKey']]);
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, $this->obfuscateApiKey($account['apiKey']) . ': ' . $this->translator->translate('SearchEngineKeywordsPerformance_BingAccountOk'));
- }
- $resultAccounts->addItem($item);
- }
- $resultMeasurables = new DiagnosticResult(Bing::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_MeasurableConfig'));
- $configuredSiteIds = ProviderBing::getInstance()->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- if (array_key_exists($configuredSiteId, $errors['sites'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')' . ': ' . $errors['sites'][$configuredSiteId]);
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')');
- }
- $resultMeasurables->addItem($item);
- }
- return [$resultAccounts, $resultMeasurables];
- }
- protected function obfuscateApiKey($apiKey)
- {
- return substr($apiKey, 0, 5) . '*****' . substr($apiKey, -5, 5);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/GoogleAccountDiagnostic.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/GoogleAccountDiagnostic.php
deleted file mode 100644
index c7dc492..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/GoogleAccountDiagnostic.php
+++ /dev/null
@@ -1,71 +0,0 @@
-translator = $translator;
- }
- public function execute()
- {
- $client = ProviderGoogle::getInstance()->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- // skip if no accounts configured
- }
- $errors = ProviderGoogle::getInstance()->getConfigurationProblems();
- $resultAccounts = new DiagnosticResult(Google::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_ConfiguredAccounts'));
- foreach ($accounts as $id => $account) {
- $userInfo = $client->getUserInfo($id);
- if (array_key_exists($id, $errors['accounts'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, $userInfo['name'] . ': ' . $errors['accounts'][$id]);
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, $userInfo['name'] . ': ' . $this->translator->translate('SearchEngineKeywordsPerformance_GoogleAccountOk'));
- }
- $resultAccounts->addItem($item);
- }
- $resultMeasurables = new DiagnosticResult(Google::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_MeasurableConfig'));
- $configuredSiteIds = ProviderGoogle::getInstance()->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- if (array_key_exists($configuredSiteId, $errors['sites'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')' . ': ' . $errors['sites'][$configuredSiteId]);
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')');
- }
- $resultMeasurables->addItem($item);
- }
- return [$resultAccounts, $resultMeasurables];
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/YandexAccountDiagnostic.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/YandexAccountDiagnostic.php
deleted file mode 100644
index a2a2f60..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/YandexAccountDiagnostic.php
+++ /dev/null
@@ -1,76 +0,0 @@
-translator = $translator;
- }
- public function execute()
- {
- $client = ProviderYandex::getInstance()->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- // skip if no accounts configured
- }
- $errors = ProviderYandex::getInstance()->getConfigurationProblems();
- $resultAccounts = new DiagnosticResult(Yandex::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_ConfiguredAccounts'));
- foreach ($accounts as $id => $account) {
- $userInfo = $client->getUserInfo($id);
- $oauthDaysAgo = floor((time() - $account['created']) / (3600 * 24));
- if (array_key_exists($id, $errors['accounts'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, $userInfo['name'] . ': ' . $errors['accounts'][$id]);
- } else {
- if ($oauthDaysAgo >= 150) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_WARNING, $userInfo['name'] . ': ' . $this->translator->translate('SearchEngineKeywordsPerformance_OAuthAccessWillTimeOutSoon', 180 - $oauthDaysAgo));
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, $userInfo['name'] . ': ' . $this->translator->translate('SearchEngineKeywordsPerformance_YandexAccountOk'));
- }
- }
- $resultAccounts->addItem($item);
- }
- $resultMeasurables = new DiagnosticResult(Yandex::getInstance()->getName() . ' - ' . $this->translator->translate('SearchEngineKeywordsPerformance_MeasurableConfig'));
- $configuredSiteIds = ProviderYandex::getInstance()->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- if (array_key_exists($configuredSiteId, $errors['sites'])) {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_ERROR, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')' . ': ' . $errors['sites'][$configuredSiteId]);
- } else {
- $item = new DiagnosticResultItem(DiagnosticResult::STATUS_OK, Site::getNameFor($configuredSiteId) . ' (' . Site::getMainUrlFor($configuredSiteId) . ')');
- }
- $resultMeasurables->addItem($item);
- }
- return [$resultAccounts, $resultMeasurables];
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/InvalidClientConfigException.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/InvalidClientConfigException.php
deleted file mode 100644
index 8da8b3e..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/InvalidClientConfigException.php
+++ /dev/null
@@ -1,21 +0,0 @@
-idSite = $idSite;
- $this->force = $force;
- $setting = new MeasurableSettings($idSite);
- $searchConsoleUrl = $setting->bingSiteUrl;
- $siteConfig = $searchConsoleUrl->getValue();
- [$this->apiKey, $this->bingSiteUrl] = explode('##', $siteConfig);
- }
- protected static function getRowCountToImport()
- {
- return Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
- }
- /**
- * Run importer for all available data
- */
- public function importAllAvailableData()
- {
- $dates = self::importAvailablePeriods($this->apiKey, $this->bingSiteUrl, $this->force);
- if (empty($dates)) {
- return;
- }
- $days = $weeks = $months = $years = [];
- foreach ($dates as $date) {
- $date = Date::factory($date);
- $day = new Day($date);
- $days[$day->toString()] = $day;
- $week = new Week($date);
- $weeks[$week->getRangeString()] = $week;
- $month = new Month($date);
- $months[$month->getRangeString()] = $month;
- $year = new Year($date);
- $years[$year->getRangeString()] = $year;
- }
- $periods = $days + $weeks + $months + $years;
- foreach ($periods as $period) {
- $this->completeExistingArchiveIfAny($period);
- }
- }
- /**
- * Imports available data to model storage if not already done
- *
- * @param string $apiKey API key to use
- * @param string $url url, eg http://matomo.org
- * @return array
- */
- public static function importAvailablePeriods($apiKey, $url, $force = \false)
- {
- if (self::$dataImported && !defined('PIWIK_TEST_MODE')) {
- return [];
- }
- $datesImported = [];
- $logger = StaticContainer::get(LoggerInterface::class);
- $model = new BingModel();
- $logger->debug("[SearchEngineKeywordsPerformance] Fetching Bing keywords for {$url}");
- try {
- $keywordData = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Bing')->getSearchAnalyticsData($apiKey, $url);
- foreach ($keywordData as $date => $keywords) {
- $availableKeywords = $model->getKeywordData($url, $date);
- $datesImported[] = $date;
- if (!empty($availableKeywords) && !$force) {
- continue;
- // skip as data was already imported before
- }
- $dataTable = self::getKeywordsAsDataTable($keywords);
- if ($dataTable) {
- $keywordData = $dataTable->getSerialized(self::getRowCountToImport(), null, Metrics::NB_CLICKS);
- $logger->debug("[SearchEngineKeywordsPerformance] Importing Bing keywords for {$url} / {$date}");
- $model->archiveKeywordData($url, $date, $keywordData[0]);
- }
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (UnknownAPIException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (RateLimitApiException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing keywords for ' . $url . ' ErrorCode: ' . $e->getCode() . ' ErrorMessage: ' . $e->getMessage());
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Bing keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- }
- $logger->debug("[SearchEngineKeywordsPerformance] Fetching Bing crawl stats for {$url}");
- try {
- $crawlStatsDataSets = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Bing')->getCrawlStats($apiKey, $url);
- foreach ($crawlStatsDataSets as $date => $crawlStats) {
- $availableCrawlStats = $model->getCrawlStatsData($url, $date);
- $datesImported[] = $date;
- if (!empty($availableCrawlStats)) {
- continue;
- // skip as data was already imported before
- }
- $dataTable = self::getCrawlStatsAsDataTable($crawlStats);
- if ($dataTable) {
- $keywordData = $dataTable->getSerialized();
- $logger->debug("[SearchEngineKeywordsPerformance] Importing Bing crawl stats for {$url} / {$date}");
- $model->archiveCrawlStatsData($url, $date, $keywordData[0]);
- }
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl stats for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (UnknownAPIException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl stats for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (RateLimitApiException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing keywords for ' . $url . ' ErrorCode: ' . $e->getCode() . ' ErrorMessage: ' . $e->getMessage());
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl stats for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- }
- try {
- $crawlErrorsDataSets = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Bing')->getUrlWithCrawlIssues($apiKey, $url);
- $logger->debug("[SearchEngineKeywordsPerformance] Importing Bing crawl issues for {$url}");
- $dataTable = self::getCrawlErrorsAsDataTable($crawlErrorsDataSets);
- if ($dataTable->getRowsCount()) {
- $crawlErrorsData = $dataTable->getSerialized();
- $model->archiveCrawlErrors($url, $crawlErrorsData[0]);
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl issues for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (UnknownAPIException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl issues for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (RateLimitApiException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl issues for ' . $url . ' ErrorCode: ' . $e->getCode() . ' ErrorMessage: ' . $e->getMessage());
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Bing crawl issues for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- }
- $datesImported = array_unique($datesImported);
- sort($datesImported);
- self::$dataImported = \true;
- return $datesImported;
- }
- protected static function getKeywordsAsDataTable($keywords)
- {
- $dataTable = new DataTable();
- foreach ($keywords as $keywordDataSet) {
- $rowData = [
- DataTable\Row::COLUMNS => [
- 'label' => $keywordDataSet['keyword'],
- Metrics::NB_CLICKS => (int) $keywordDataSet['clicks'],
- Metrics::NB_IMPRESSIONS => (int) $keywordDataSet['impressions'],
- Metrics::CTR => (float) round($keywordDataSet['clicks'] / $keywordDataSet['impressions'], 2),
- Metrics::POSITION => (float) $keywordDataSet['position']
- ]
- ];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- return $dataTable;
- }
- protected static function getCrawlStatsAsDataTable($crawlStats)
- {
- $dataTable = new DataTable();
- foreach ($crawlStats as $label => $pagesCount) {
- $rowData = [DataTable\Row::COLUMNS => ['label' => $label, Metrics::NB_PAGES => (int) $pagesCount]];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- return $dataTable;
- }
- protected static function getCrawlErrorsAsDataTable($crawlErrors)
- {
- $dataTable = new DataTable();
- foreach ($crawlErrors as $crawlError) {
- $rowData = [DataTable\Row::COLUMNS => ['label' => $crawlError['Url'], 'category' => $crawlError['Issues'], 'inLinks' => $crawlError['InLinks'], 'responseCode' => $crawlError['HttpCode']]];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- return $dataTable;
- }
- /**
- * Runs the Archiving for SearchEngineKeywordsPerformance plugin if an archive for the given period already exists
- *
- * @param \Piwik\Period $period
- */
- protected function completeExistingArchiveIfAny($period)
- {
- $parameters = new Parameters(new Site($this->idSite), $period, new Segment('', [$this->idSite]));
- $parameters->setRequestedPlugin('SearchEngineKeywordsPerformance');
- $parameters->onlyArchiveRequestedPlugin();
- $result = ArchiveSelector::getArchiveIdAndVisits($parameters, $period->getDateStart()->getDateStartUTC());
- $idArchive = $result[0][0] ?? null;
- if (empty($idArchive)) {
- return;
- // ignore periods that weren't archived before
- }
- $archiveWriter = new ArchiveWriter($parameters);
- $archiveWriter->idArchive = $idArchive;
- $archiveProcessor = new ArchiveProcessor($parameters, $archiveWriter, new LogAggregator($parameters));
- $archiveProcessor->setNumberOfVisits(1, 1);
- $bingRecordBuilder = BingRecordBuilder::make($this->idSite);
- if (empty($bingRecordBuilder)) {
- return;
- }
- if ($period instanceof Day) {
- $bingRecordBuilder->buildFromLogs($archiveProcessor);
- } else {
- $bingRecordBuilder->buildForNonDayPeriod($archiveProcessor);
- }
- $archiveWriter->flushSpools();
- DataTableManager::getInstance()->deleteAll();
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Google.php
deleted file mode 100644
index 613d72f..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Google.php
+++ /dev/null
@@ -1,361 +0,0 @@
-idSite = $idSite;
- $this->force = $force;
- $setting = new MeasurableSettings($idSite);
- $searchConsoleUrl = $setting->googleSearchConsoleUrl;
- [$this->accountId, $this->searchConsoleUrl] = explode('##', $searchConsoleUrl->getValue());
- }
- protected static function getRowCountToImport()
- {
- return Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
- }
- /**
- * Triggers keyword import and plugin archiving for all dates search console has data for
- *
- * @param string|int|null $limitKeywordDates if integer given: limits the amount of imported dates to the last
- * available X if string given: only imports keywords for the given
- * string date
- * @return void
- */
- public function importAllAvailableData($limitKeywordDates = null)
- {
- // if specific date given
- if (is_string($limitKeywordDates) && strlen($limitKeywordDates) == 10) {
- $availableDates = [$limitKeywordDates];
- } else {
- $availableDates = self::getAvailableDates($this->accountId, $this->searchConsoleUrl);
- sort($availableDates);
- if ($limitKeywordDates > 0) {
- $limitKeywordDates += 5;
- // always import 5 days more in the past, to ensure that non final data is imported again.
- $availableDates = array_slice($availableDates, -$limitKeywordDates, $limitKeywordDates);
- }
- }
- $this->importKeywordsForListOfDates($availableDates);
- $this->completeExistingArchivesForListOfDates($availableDates);
- }
- protected function importKeywordsForListOfDates($datesToImport)
- {
- foreach ($datesToImport as $date) {
- foreach (self::$typesToImport as $type) {
- $this->importKeywordsIfNecessary($this->accountId, $this->searchConsoleUrl, $date, $type, $this->force);
- }
- }
- }
- protected function completeExistingArchivesForListOfDates($datesToComplete)
- {
- $days = $weeks = $months = $years = [];
- sort($datesToComplete);
- foreach ($datesToComplete as $date) {
- $date = Date::factory($date);
- $day = new Day($date);
- $days[$day->toString()] = $day;
- $week = new Week($date);
- $weeks[$week->getRangeString()] = $week;
- $month = new Month($date);
- $months[$month->getRangeString()] = $month;
- $year = new Year($date);
- $years[$year->getRangeString()] = $year;
- }
- $periods = $days + $weeks + $months + $years;
- foreach ($periods as $period) {
- $this->completeExistingArchiveIfAny($period);
- }
- }
- /**
- * Imports keyword to model storage if not already done
- *
- * @param string $accountId google account id
- * @param string $url url, eg http://matomo.org
- * @param string $date date string, eg 2016-12-24
- * @param string $type 'web', 'image', 'video' or 'news'
- * @param bool $force force reimport
- * @return boolean
- */
- public function importKeywordsIfNecessary($accountId, $url, $date, $type, $force = \false)
- {
- $model = new GoogleModel();
- $logger = StaticContainer::get(LoggerInterface::class);
- $keywordData = $model->getKeywordData($url, $date, $type);
- // check if available data is temporary and force a reimport in that case
- if ($keywordData) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- $isTemporary = $dataTable->getMetadata(self::DATATABLE_METADATA_TEMPORARY);
- if ($isTemporary === \true) {
- $logger->info('[SearchEngineKeywordsPerformance] Forcing reimport Google keywords for ' . $url . ' as imported data was not final.');
- $force = \true;
- }
- }
- if ($keywordData && !$force) {
- $logger->info('[SearchEngineKeywordsPerformance] Skipping import of Google keywords for ' . $date . ' and ' . $url . ' as data already imported.');
- return \false;
- // skip if data already available and no reimport forced
- }
- $dataTable = $this->getKeywordsFromConsoleAsDataTable($accountId, $url, $date, $type);
- if ($dataTable) {
- $keywordData = $dataTable->getSerialized(self::getRowCountToImport(), null, Metrics::NB_CLICKS);
- $model->archiveKeywordData($url, $date, $type, $keywordData[0]);
- return \true;
- }
- return \false;
- }
- protected static function getAvailableDates($accountId, $url)
- {
- $logger = StaticContainer::get(LoggerInterface::class);
- try {
- if (!array_key_exists($accountId . $url, self::$availableDates) || defined('PIWIK_TEST_MODE')) {
- $finalDates = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Google')->getDatesWithSearchAnalyticsData($accountId, $url);
- self::$availableDates[$accountId . $url] = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Google')->getDatesWithSearchAnalyticsData($accountId, $url, \false);
- self::$availableDatesNonFinal[$accountId . $url] = array_diff(self::$availableDates[$accountId . $url], $finalDates);
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- } catch (InvalidClientConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- } catch (MissingOAuthConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- } catch (MissingClientConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- } catch (UnknownAPIException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return [];
- }
- if (array_key_exists($accountId . $url, self::$availableDates)) {
- return self::$availableDates[$accountId . $url];
- }
- return [];
- }
- private static function isFinalDate($accountId, $url, $date)
- {
- if (array_key_exists($accountId . $url, self::$availableDatesNonFinal)) {
- return !in_array($date, self::$availableDatesNonFinal[$accountId . $url]);
- }
- return \true;
- }
- /**
- * Fetches data from google search console and migrates it to a Matomo Datatable
- *
- * @param string $accountId google account id
- * @param string $url url, eg http://matomo.org
- * @param string $date date string, eg 2016-12-24
- * @param string $type 'web', 'image', 'video' or 'news'
- * @return null|DataTable
- */
- protected function getKeywordsFromConsoleAsDataTable($accountId, $url, $date, $type)
- {
- $dataTable = new DataTable();
- $logger = StaticContainer::get(LoggerInterface::class);
- try {
- if (!defined('PIWIK_TEST_MODE') && !$this->isImportAllowedForDate($date)) {
- $logger->debug("[SearchEngineKeywordsPerformance] Skip fetching keywords from Search Console for today and dates more than 500 days in the past: " . $date);
- return null;
- }
- $availableDates = self::getAvailableDates($accountId, $url);
- if (!in_array($date, $availableDates)) {
- $logger->debug("[SearchEngineKeywordsPerformance] No {$type} keywords available for {$date} and {$url}");
- return null;
- }
- $logger->debug("[SearchEngineKeywordsPerformance] Fetching {$type} keywords for {$date} and {$url}");
- $keywordData = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Google')->getSearchAnalyticsData($accountId, $url, $date, $type, self::getRowCountToImport());
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- } catch (InvalidClientConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- } catch (MissingOAuthConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- } catch (MissingClientConfigException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- } catch (UnknownAPIException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Google keywords for ' . $url . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- return null;
- }
- if (!self::isFinalDate($accountId, $url, $date)) {
- $dataTable->setMetadata(self::DATATABLE_METADATA_TEMPORARY, \true);
- }
- if (empty($keywordData) || !($rows = $keywordData->getRows())) {
- return $dataTable;
- // return empty table so it will be stored
- }
- foreach ($rows as $keywordDataSet) {
- /** @var \Google\Service\SearchConsole\ApiDataRow $keywordDataSet */
- $keys = $keywordDataSet->getKeys();
- $rowData = [
- DataTable\Row::COLUMNS => [
- 'label' => reset($keys),
- Metrics::NB_CLICKS => (int) $keywordDataSet->getClicks(),
- Metrics::NB_IMPRESSIONS => (int) $keywordDataSet->getImpressions(),
- Metrics::CTR => (float) $keywordDataSet->getCtr(),
- Metrics::POSITION => (float) $keywordDataSet->getPosition()
- ]
- ];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- unset($keywordData);
- return $dataTable;
- }
- protected function isImportAllowedForDate($date): bool
- {
- $site = new Site($this->idSite);
- $siteCreationDate = $site->getCreationDate()->subDay(30);
- $earliestDate = Date::now()->subDay(500);
- $earliestImportDate = $siteCreationDate->isEarlier($earliestDate) ? $earliestDate : $siteCreationDate;
- $archivedDate = Date::factory($date);
- if ($archivedDate->isEarlier($earliestImportDate) || $archivedDate->isToday()) {
- return \false;
- }
- return \true;
- }
- /**
- * Runs the Archiving for SearchEngineKeywordsPerformance plugin if an archive for the given period already exists
- *
- * @param \Piwik\Period $period
- */
- protected function completeExistingArchiveIfAny($period)
- {
- $parameters = new Parameters(new Site($this->idSite), $period, new Segment('', [$this->idSite]));
- $parameters->setRequestedPlugin('SearchEngineKeywordsPerformance');
- $parameters->onlyArchiveRequestedPlugin();
- $result = ArchiveSelector::getArchiveIdAndVisits($parameters, $period->getDateStart()->getDateStartUTC());
- $idArchive = $result[0][0] ?? null;
- if (empty($idArchive)) {
- return;
- // ignore periods that weren't archived before
- }
- $archiveWriter = new ArchiveWriter($parameters);
- $archiveWriter->idArchive = $idArchive;
- $archiveProcessor = new ArchiveProcessor($parameters, $archiveWriter, new LogAggregator($parameters));
- $archiveProcessor->setNumberOfVisits(1, 1);
- /** @var GoogleRecordBuilder[] $recordBuilders */
- $recordBuilders = GoogleRecordBuilder::makeAll($this->idSite);
- if (empty($recordBuilders)) {
- return;
- }
- foreach ($recordBuilders as $builder) {
- if ($period instanceof Day) {
- $builder->buildFromLogs($archiveProcessor);
- } else {
- $builder->buildForNonDayPeriod($archiveProcessor);
- }
- }
- $archiveWriter->flushSpools();
- DataTableManager::getInstance()->deleteAll();
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Yandex.php
deleted file mode 100644
index b2ee231..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Yandex.php
+++ /dev/null
@@ -1,292 +0,0 @@
-idSite = $idSite;
- $this->force = $force;
- $setting = new MeasurableSettings($idSite);
- $yandexConfig = $setting->yandexAccountAndHostId;
- $siteConfig = $yandexConfig->getValue();
- [$this->accountId, $this->yandexHostId] = explode('##', $siteConfig);
- }
- protected static function getRowCountToImport()
- {
- return Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
- }
- /**
- * Run importer for all available data
- */
- public function importAllAvailableData($limitDays = 100)
- {
- if (is_string($limitDays) && strlen($limitDays) == 10) {
- $dates = [$limitDays];
- } else {
- for ($i = 0; $i <= $limitDays; $i++) {
- $dates[] = date('Y-m-d', strtotime("-{$i} days"));
- }
- }
- foreach ($dates as $date) {
- self::importAvailableDataForDate($this->accountId, $this->yandexHostId, $date, $this->force);
- }
- if (empty($dates)) {
- return;
- }
- $this->completeExistingArchivesForListOfDates($dates);
- }
- protected function completeExistingArchivesForListOfDates($datesToComplete)
- {
- $days = $weeks = $months = $years = [];
- sort($datesToComplete);
- foreach ($datesToComplete as $date) {
- $date = Date::factory($date);
- $day = new Day($date);
- $days[$day->toString()] = $day;
- $week = new Week($date);
- $weeks[$week->getRangeString()] = $week;
- $month = new Month($date);
- $months[$month->getRangeString()] = $month;
- $year = new Year($date);
- $years[$year->getRangeString()] = $year;
- }
- $periods = $days + $weeks + $months + $years;
- foreach ($periods as $period) {
- $this->completeExistingArchiveIfAny($period);
- }
- }
- /**
- * Imports available data to model storage if not already done
- *
- * @param string $accountId Id oc account to use
- * @param string $hostId url, eg https:piwik.org:443
- * @param string $date date, eg 2019-05-20
- * @return array
- */
- public static function importAvailableDataForDate($accountId, $hostId, $date, $force = \false)
- {
- $datesImported = [];
- $timestamp = strtotime($date);
- if ($timestamp > time()) {
- return [];
- // no import for dates in the future
- }
- $logger = StaticContainer::get(LoggerInterface::class);
- if ($timestamp > time() - self::MAX_DAYS_KEYWORD_DATA_DELAY * 24 * 3600) {
- $force = \true;
- // always reimport the last few days
- }
- $model = new YandexModel();
- try {
- $availableKeywordsDataTable = new DataTable();
- $availableKeywords = $model->getKeywordData($hostId, $date);
- if (!empty($availableKeywords)) {
- $availableKeywordsDataTable->addRowsFromSerializedArray($availableKeywords);
- }
- // Only assume keywords were imported if there are actually some rows available, otherwise try to import them (again)
- if ($availableKeywordsDataTable->getRowsCountWithoutSummaryRow() > 0 && !$force) {
- $logger->debug("[SearchEngineKeywordsPerformance] Yandex keywords already imported for {$hostId} and date {$date}");
- } else {
- $logger->debug("[SearchEngineKeywordsPerformance] Fetching Yandex keywords for {$hostId} and date {$date}");
- $keywords = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Yandex')->getSearchAnalyticsData($accountId, $hostId, $date);
- $datesImported[] = $date;
- $dataTable = self::getKeywordsAsDataTable($keywords);
- // do not store empty results for the last days
- if ($dataTable && ($dataTable->getRowsCountWithoutSummaryRow() > 0 || $timestamp < time() - self::MAX_DAYS_KEYWORD_DATA_DELAY * 24 * 3600)) {
- $keywordData = $dataTable->getSerialized(self::getRowCountToImport(), null, Metrics::NB_CLICKS);
- $logger->debug("[SearchEngineKeywordsPerformance] Importing Yandex keywords for {$hostId} / {$date}");
- $model->archiveKeywordData($hostId, $date, $keywordData[0]);
- }
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Yandex keywords for ' . $hostId . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (RateLimitApiException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Yandex keywords for ' . $hostId . ': ' . $e->getMessage());
- } catch (\Exception $e) {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Yandex keywords for ' . $hostId . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- }
- try {
- $availableCrawlStats = $model->getCrawlStatsData($hostId, $date);
- if (!empty($availableCrawlStats) && !$force) {
- $logger->debug("[SearchEngineKeywordsPerformance] Yandex crawl stats already imported for {$hostId} and date {$date}");
- } else {
- $logger->debug("[SearchEngineKeywordsPerformance] Fetching Yandex crawl stats for {$hostId} and date {$date}");
- $crawlStats = StaticContainer::get('Piwik\\Plugins\\SearchEngineKeywordsPerformance\\Client\\Yandex')->getCrawlStats($accountId, $hostId, $date);
- $datesImported[] = $date;
- $dataTable = self::getCrawlStatsAsDataTable($crawlStats);
- if ($dataTable) {
- $keywordData = $dataTable->getSerialized();
- $logger->debug("[SearchEngineKeywordsPerformance] Importing Yandex crawl stats for {$hostId} and date {$date}");
- $model->archiveCrawlStatsData($hostId, $date, $keywordData[0]);
- }
- }
- } catch (InvalidCredentialsException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Yandex crawl stats for ' . $hostId . ': ' . $e->getMessage());
- Provider::getInstance()->recordNewApiErrorForProvider();
- } catch (RateLimitApiException $e) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Yandex crawl stats for ' . $hostId . ': ' . $e->getMessage());
- } catch (\Exception $e) {
- // ignore empty server reply as they seem temporary only
- if (strpos($e->getMessage(), 'Empty reply from server')) {
- $logger->info('[SearchEngineKeywordsPerformance] Exception while importing Yandex crawl stats for ' . $hostId . ': ' . $e->getMessage());
- } else {
- $logger->error('[SearchEngineKeywordsPerformance] Exception while importing Yandex crawl stats for ' . $hostId . ': ' . $e->getMessage());
- }
- Provider::getInstance()->recordNewApiErrorForProvider();
- }
- $datesImported = array_unique($datesImported);
- sort($datesImported);
- return $datesImported;
- }
- protected static function getKeywordsAsDataTable($keywords)
- {
- $dataTable = new DataTable();
- if (empty($keywords)) {
- return $dataTable;
- }
- foreach ($keywords as $keywordDataSet) {
- // If the keyword is empty, that will cause an error if we try to add the row. Skip and move on to the next.
- if (empty($keywordDataSet['keyword'])) {
- continue;
- }
- $rowData = [
- DataTable\Row::COLUMNS => [
- 'label' => $keywordDataSet['keyword'],
- Metrics::NB_CLICKS => (int) $keywordDataSet['clicks'],
- Metrics::NB_IMPRESSIONS => (int) $keywordDataSet['impressions'],
- Metrics::CTR => (float) round($keywordDataSet['clicks'] / $keywordDataSet['impressions'], 2),
- Metrics::POSITION => (float) $keywordDataSet['position']
- ]
- ];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- return $dataTable;
- }
- protected static function getCrawlStatsAsDataTable($crawlStats)
- {
- $dataTable = new DataTable();
- if (empty($crawlStats) || !is_array($crawlStats)) {
- return $dataTable;
- }
- foreach ($crawlStats as $label => $pagesCount) {
- if (empty($label)) {
- continue;
- }
- $rowData = [DataTable\Row::COLUMNS => ['label' => $label, Metrics::NB_PAGES => (int) $pagesCount]];
- $row = new DataTable\Row($rowData);
- $dataTable->addRow($row);
- }
- return $dataTable;
- }
- /**
- * Runs the Archiving for SearchEngineKeywordsPerformance plugin if an archive for the given period already exists
- *
- * @param \Piwik\Period $period
- */
- protected function completeExistingArchiveIfAny($period)
- {
- $parameters = new Parameters(new Site($this->idSite), $period, new Segment('', [$this->idSite]));
- $parameters->setRequestedPlugin('SearchEngineKeywordsPerformance');
- $parameters->onlyArchiveRequestedPlugin();
- $result = ArchiveSelector::getArchiveIdAndVisits($parameters, $period->getDateStart()->getDateStartUTC());
- $idArchive = $result[0][0] ?? null;
- if (empty($idArchive)) {
- return;
- // ignore periods that weren't archived before
- }
- $archiveWriter = new ArchiveWriter($parameters);
- $archiveWriter->idArchive = $idArchive;
- $archiveProcessor = new ArchiveProcessor($parameters, $archiveWriter, new LogAggregator($parameters));
- $archiveProcessor->setNumberOfVisits(1, 1);
- $builder = YandexRecordBuilder::make($this->idSite);
- if (empty($builder)) {
- return;
- }
- if ($period instanceof Day) {
- $builder->buildFromLogs($archiveProcessor);
- } else {
- $builder->buildForNonDayPeriod($archiveProcessor);
- }
- $archiveWriter->flushSpools();
- DataTableManager::getInstance()->deleteAll();
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/LICENSE b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/LICENSE
deleted file mode 100644
index 4686f35..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/LICENSE
+++ /dev/null
@@ -1,49 +0,0 @@
-InnoCraft License
-
-This InnoCraft End User License Agreement (the "InnoCraft EULA") is between you and InnoCraft Ltd (NZBN 6106769) ("InnoCraft"). If you are agreeing to this Agreement not as an individual but on behalf of your company, then "Customer" or "you" means your company, and you are binding your company to this Agreement. InnoCraft may modify this Agreement from time to time, subject to the terms in Section (xii) below.
-
-By clicking on the "I’ve read and accept the terms & conditions (https://shop.matomo.org/terms-conditions/)" (or similar button) that is presented to you at the time of your Order, or by using or accessing InnoCraft products, you indicate your assent to be bound by this Agreement.
-
-
-InnoCraft EULA
-
-(i) InnoCraft is the licensor of the Plugin for Matomo Analytics (the "Software").
-
-(ii) Subject to the terms and conditions of this Agreement, InnoCraft grants you a limited, worldwide, non-exclusive, non-transferable and non-sublicensable license to install and use the Software only on hardware systems owned, leased or controlled by you, during the applicable License Term. The term of each Software license ("License Term") will be specified in your Order. Your License Term will end upon any breach of this Agreement.
-
-(iii) Unless otherwise specified in your Order, for each Software license that you purchase, you may install one production instance of the Software in a Matomo Analytics instance owned or operated by you, and accessible via one URL ("Matomo instance"). Additional licenses must be purchased in order to deploy the Software in multiple Matomo instances, including when these multiple Matomo instances are hosted on a single hardware system.
-
-(iv) Licenses granted by InnoCraft are granted subject to the condition that you must ensure the maximum number of Authorized Users and Authorized Sites that are able to access and use the Software is equal to the number of User and Site Licenses for which the necessary fees have been paid to InnoCraft for the Subscription period. You may upgrade your license at any time on payment of the appropriate fees to InnoCraft in order to increase the maximum number of authorized users or sites. The number of User and Site Licenses granted to you is dependent on the fees paid by you. “User License” means a license granted under this EULA to you to permit an Authorized User to use the Software. “Authorized User” means a person who has an account in the Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. "Site License" means a license granted under this EULA to you to permit an Authorized Site to use the Matomo Marketplace Plugin. “Authorized Sites” means a website or a measurable within Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. These restrictions also apply if you install the Matomo Analytics Platform as part of your WordPress.
-
-(v) Piwik Analytics was renamed to Matomo Analytics in January 2018. The same terms and conditions as well as any restrictions or grants apply if you are using any version of Piwik.
-
-(vi) The Software requires a license key in order to operate, which will be delivered to the email addresses specified in your Order when we have received payment of the applicable fees.
-
-(vii) Any information that InnoCraft may collect from you or your device will be subject to InnoCraft Privacy Policy (https://www.innocraft.com/privacy).
-
-(viii) You are bound by the Matomo Marketplace Terms and Conditions (https://shop.matomo.org/terms-conditions/).
-
-(ix) You may not reverse engineer or disassemble or re-distribute the Software in whole or in part, or create any derivative works from or sublicense any rights in the Software, unless otherwise expressly authorized in writing by InnoCraft.
-
-(x) The Software is protected by copyright and other intellectual property laws and treaties. InnoCraft own all title, copyright and other intellectual property rights in the Software, and the Software is licensed to you directly by InnoCraft, not sold.
-
-(xi) The Software is provided under an "as is" basis and without any support or maintenance. Nothing in this Agreement shall require InnoCraft to provide you with support or fixes to any bug, failure, mis-performance or other defect in The Software. InnoCraft may provide you, from time to time, according to his sole discretion, with updates of the Software. You hereby warrant to keep the Software up-to-date and install all relevant updates. InnoCraft shall provide any update free of charge.
-
-(xii) The Software is provided "as is", and InnoCraft hereby disclaim all warranties, including but not limited to any implied warranties of title, non-infringement, merchantability or fitness for a particular purpose. InnoCraft shall not be liable or responsible in any way for any losses or damage of any kind, including lost profits or other indirect or consequential damages, relating to your use of or reliance upon the Software.
-
-(xiii) We may update or modify this Agreement from time to time, including the referenced Privacy Policy and the Matomo Marketplace Terms and Conditions. If a revision meaningfully reduces your rights, we will use reasonable efforts to notify you (by, for example, sending an email to the billing or technical contact you designate in the applicable Order). If we modify the Agreement during your License Term or Subscription Term, the modified version will be effective upon your next renewal of a License Term.
-
-
-About InnoCraft Ltd
-
-At InnoCraft Ltd, we create innovating quality products to grow your business and to maximize your success.
-
-Our software products are built on top of Matomo Analytics: the leading open digital analytics platform used by more than one million websites worldwide. We are the creators and makers of the Matomo Analytics platform.
-
-
-Contact
-
-Email: contact@innocraft.com
-Contact form: https://www.innocraft.com/#contact
-Website: https://www.innocraft.com/
-Buy our products: Premium Features for Matomo Analytics https://plugins.matomo.org/premium
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/MeasurableSettings.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/MeasurableSettings.php
deleted file mode 100644
index 2172fc8..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/MeasurableSettings.php
+++ /dev/null
@@ -1,182 +0,0 @@
-configureGoogleSettings();
- $this->configureBingSettings();
- $this->configureYandexSettings();
- }
- /**
- * Configures Settings used for Google Search Console Import
- */
- protected function configureGoogleSettings()
- {
- $googleClient = ProviderGoogle::getInstance()->getClient();
- // check if google search console is configured and available for website type
- if (!\Piwik\Plugins\SearchEngineKeywordsPerformance\SearchEngineKeywordsPerformance::isGoogleForceEnabled($this->idSite) && (!$this->hasMeasurableType(WebsiteMeasurableType::ID) || !$googleClient->isConfigured())) {
- return;
- }
- $this->googleConfigCreatedBy = $this->makeSetting('googleconfigcreatedby', '', FieldConfig::TYPE_STRING, function (FieldConfig $field) {
- $field->uiControl = FieldConfig::UI_CONTROL_HIDDEN;
- });
- $this->googleSearchConsoleUrl = $this->makeSetting('searchconsoleurl', '0', FieldConfig::TYPE_STRING, function (FieldConfig $field) use ($googleClient) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_GoogleSearchConsoleUrl');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_GoogleSearchConsoleUrlDescription');
- $field->uiControl = FieldConfig::UI_CONTROL_SINGLE_SELECT;
- $field->availableValues = ['0' => Piwik::translate('SearchEngineKeywordsPerformance_NotAvailable')];
- foreach ($googleClient->getAccounts() as $id => $account) {
- if (Piwik::hasUserSuperUserAccessOrIsTheUser($account['username'])) {
- $availableSites = $googleClient->getAvailableUrls($id);
- foreach ($availableSites as $url => $accessLevel) {
- $value = $id . '##' . $url;
- $field->availableValues[$value] = $url;
- }
- }
- }
- });
- $this->googleWebKeywords = $this->makeSetting('googlewebkeywords', \true, FieldConfig::TYPE_BOOL, function (FieldConfig $field) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_FetchWebKeyword');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_FetchWebKeywordDesc');
- $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
- $field->condition = 'searchconsoleurl';
- });
- $this->googleImageKeywords = $this->makeSetting('googleimagekeywords', \false, FieldConfig::TYPE_BOOL, function (FieldConfig $field) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_FetchImageKeyword');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_FetchImageKeywordDesc');
- $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
- $field->condition = 'searchconsoleurl && searchconsoleurl.indexOf(\'android-app\') == -1';
- });
- $this->googleVideoKeywords = $this->makeSetting('googlevideokeywords', \false, FieldConfig::TYPE_BOOL, function (FieldConfig $field) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_FetchVideoKeyword');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_FetchVideoKeywordDesc');
- $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
- $field->condition = 'searchconsoleurl && searchconsoleurl.indexOf(\'android-app\') == -1';
- });
- $this->googleNewsKeywords = $this->makeSetting('googlenewskeywords', \false, FieldConfig::TYPE_BOOL, function (FieldConfig $field) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_FetchNewsKeyword');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_FetchNewsKeywordDesc');
- $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
- $field->condition = 'searchconsoleurl && searchconsoleurl.indexOf(\'android-app\') == -1';
- });
- $createdByUser = $this->googleConfigCreatedBy->getValue();
- if (!empty($createdByUser) && !Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser)) {
- $this->googleSearchConsoleUrl->setIsWritableByCurrentUser(\false);
- $this->googleWebKeywords->setIsWritableByCurrentUser(\false);
- $this->googleImageKeywords->setIsWritableByCurrentUser(\false);
- $this->googleVideoKeywords->setIsWritableByCurrentUser(\false);
- $this->googleNewsKeywords->setIsWritableByCurrentUser(\false);
- }
- }
- /**
- * Configures Settings used for Bing Webmaster API Import
- */
- protected function configureBingSettings()
- {
- $bingClient = ProviderBing::getInstance()->getClient();
- // check if Bing Webmaster API is configured and available for website type
- if (!\Piwik\Plugins\SearchEngineKeywordsPerformance\SearchEngineKeywordsPerformance::isBingForceEnabled($this->idSite) && (!$this->hasMeasurableType(WebsiteMeasurableType::ID) || !$bingClient->isConfigured())) {
- return;
- }
- $this->bingConfigCreatedBy = $this->makeSetting('bingconfigcreatedby', '', FieldConfig::TYPE_STRING, function (FieldConfig $field) {
- $field->uiControl = FieldConfig::UI_CONTROL_HIDDEN;
- });
- $this->bingSiteUrl = $this->makeSetting('bingsiteurl', '0', FieldConfig::TYPE_STRING, function (FieldConfig $field) use ($bingClient) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_BingWebmasterApiUrl');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_BingWebmasterApiUrlDescription');
- $field->uiControl = FieldConfig::UI_CONTROL_SINGLE_SELECT;
- $field->availableValues = ['0' => Piwik::translate('SearchEngineKeywordsPerformance_NotAvailable')];
- foreach ($bingClient->getAccounts() as $account) {
- if (Piwik::hasUserSuperUserAccessOrIsTheUser($account['username'])) {
- $availableSites = $bingClient->getAvailableUrls($account['apiKey']);
- foreach ($availableSites as $url => $isVerified) {
- $value = $account['apiKey'] . '##' . $url;
- $field->availableValues[$value] = $url;
- }
- }
- }
- });
- $createdByUser = $this->bingConfigCreatedBy->getValue();
- if (!empty($createdByUser) && !Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser)) {
- $this->bingSiteUrl->setIsWritableByCurrentUser(\false);
- }
- }
- /**
- * Configures Settings used for Yandex Webmaster API Import
- */
- protected function configureYandexSettings()
- {
- $yandexClient = ProviderYandex::getInstance()->getClient();
- // check if Yandex Webmaster API is configured and available for website type
- if (!$this->hasMeasurableType(WebsiteMeasurableType::ID) || !$yandexClient->isConfigured()) {
- return;
- }
- $this->yandexConfigCreatedBy = $this->makeSetting('yandexconfigcreatedby', '', FieldConfig::TYPE_STRING, function (FieldConfig $field) {
- $field->uiControl = FieldConfig::UI_CONTROL_HIDDEN;
- });
- $this->yandexAccountAndHostId = $this->makeSetting('yandexAccountAndHostId', '0', FieldConfig::TYPE_STRING, function (FieldConfig $field) use ($yandexClient) {
- $field->title = Piwik::translate('SearchEngineKeywordsPerformance_YandexWebmasterApiUrl');
- $field->description = Piwik::translate('SearchEngineKeywordsPerformance_YandexWebmasterApiUrlDescription');
- $field->uiControl = FieldConfig::UI_CONTROL_SINGLE_SELECT;
- $field->availableValues = ['0' => Piwik::translate('SearchEngineKeywordsPerformance_NotAvailable')];
- foreach ($yandexClient->getAccounts() as $id => $account) {
- if (Piwik::hasUserSuperUserAccessOrIsTheUser($account['username'])) {
- $availableSites = $yandexClient->getAvailableUrls($id);
- foreach ($availableSites as $url => $hostData) {
- $value = $id . '##' . $hostData['host_id'];
- $field->availableValues[$value] = $url;
- }
- }
- }
- });
- $createdByUser = $this->yandexConfigCreatedBy->getValue();
- if (!empty($createdByUser) && !Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser)) {
- $this->yandexAccountAndHostId->setIsWritableByCurrentUser(\false);
- }
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Menu.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Menu.php
deleted file mode 100644
index d71cd44..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Menu.php
+++ /dev/null
@@ -1,30 +0,0 @@
-addSystemItem('SearchEngineKeywordsPerformance_AdminMenuTitle', $this->urlForAction('index'), $order = 50);
- }
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Metrics.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Metrics.php
deleted file mode 100644
index f9fbd33..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Metrics.php
+++ /dev/null
@@ -1,144 +0,0 @@
- Piwik::translate('SearchEngineKeywordsPerformance_Clicks'),
- self::NB_IMPRESSIONS => Piwik::translate('SearchEngineKeywordsPerformance_Impressions'),
- self::CTR => Piwik::translate('SearchEngineKeywordsPerformance_Ctr'),
- self::POSITION => Piwik::translate('SearchEngineKeywordsPerformance_Position')
- ];
- }
- /**
- * Returns metric semantic types for this plugin's metrics.
- *
- * @return array
- */
- public static function getMetricSemanticTypes(): array
- {
- return [self::NB_CLICKS => Dimension::TYPE_NUMBER, self::NB_IMPRESSIONS => Dimension::TYPE_NUMBER, self::CTR => Dimension::TYPE_NUMBER, self::POSITION => Dimension::TYPE_NUMBER];
- }
- /**
- * Return metric documentations
- *
- * @return array
- */
- public static function getMetricsDocumentation()
- {
- return [
- self::NB_CLICKS => Piwik::translate('SearchEngineKeywordsPerformance_ClicksDocumentation'),
- self::NB_IMPRESSIONS => Piwik::translate('SearchEngineKeywordsPerformance_ImpressionsDocumentation'),
- self::CTR => Piwik::translate('SearchEngineKeywordsPerformance_CtrDocumentation'),
- self::POSITION => Piwik::translate('SearchEngineKeywordsPerformance_PositionDocumentation'),
- Bing::CRAWLSTATS_OTHER_CODES_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlStatsOtherCodesDesc'),
- Bing::CRAWLSTATS_BLOCKED_ROBOTS_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlBlockedByRobotsTxtDesc'),
- Bing::CRAWLSTATS_CODE_2XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlHttpStatus2xxDesc'),
- Bing::CRAWLSTATS_CODE_301_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlHttpStatus301Desc'),
- Bing::CRAWLSTATS_CODE_302_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlHttpStatus302Desc'),
- Bing::CRAWLSTATS_CODE_4XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlHttpStatus4xxDesc'),
- Bing::CRAWLSTATS_CODE_5XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlHttpStatus5xxDesc'),
- Bing::CRAWLSTATS_TIMEOUT_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlConnectionTimeoutDesc'),
- Bing::CRAWLSTATS_MALWARE_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlMalwareInfectedDesc'),
- Bing::CRAWLSTATS_ERRORS_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlErrorsDesc'),
- Bing::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlCrawledPagesDesc'),
- Bing::CRAWLSTATS_DNS_FAILURE_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlDNSFailuresDesc'),
- Bing::CRAWLSTATS_IN_INDEX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlPagesInIndexDesc'),
- Bing::CRAWLSTATS_IN_LINKS_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_BingCrawlInboundLinkDesc'),
- Yandex::CRAWLSTATS_IN_INDEX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlInIndexDesc'),
- Yandex::CRAWLSTATS_APPEARED_PAGES_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlAppearedPagesDesc'),
- Yandex::CRAWLSTATS_REMOVED_PAGES_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlRemovedPagesDesc'),
- Yandex::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlCrawledPagesDesc'),
- Yandex::CRAWLSTATS_CODE_2XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlHttpStatus2xxDesc'),
- Yandex::CRAWLSTATS_CODE_3XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlHttpStatus3xxDesc'),
- Yandex::CRAWLSTATS_CODE_4XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlHttpStatus4xxDesc'),
- Yandex::CRAWLSTATS_CODE_5XX_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlHttpStatus5xxDesc'),
- Yandex::CRAWLSTATS_ERRORS_RECORD_NAME => Piwik::translate('SearchEngineKeywordsPerformance_YandexCrawlErrorsDesc')
- ];
- }
- public static function getMetricIdsToProcessReportTotal()
- {
- return [self::NB_CLICKS, self::NB_IMPRESSIONS];
- }
- /**
- * Returns operations used to aggregate the metric columns
- *
- * @return array
- */
- public static function getColumnsAggregationOperations()
- {
- /*
- * Calculate average CTR based on summed impressions and summed clicks
- */
- $calcCtr = function ($val1, $val2, $thisRow, $rowToSum) {
- $sumImpressions = $thisRow->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS) + $rowToSum->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS);
- $sumClicks = $thisRow->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_CLICKS) + $rowToSum->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_CLICKS);
- if (!$sumImpressions) {
- return 0.0;
- }
- return round($sumClicks / $sumImpressions, 2);
- };
- /*
- * Calculate average position based on impressions and positions
- */
- $calcPosition = function ($val1, $val2, $thisRow, $rowToSum) {
- return round(
- (
- $thisRow->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS)
- * $thisRow->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::POSITION)
- + $rowToSum->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS)
- * $rowToSum->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::POSITION)
- ) / (
- $thisRow->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS)
- + $rowToSum->getColumn(\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::NB_IMPRESSIONS)
- ),
- 2
- );
- };
- return [\Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::CTR => $calcCtr, \Piwik\Plugins\SearchEngineKeywordsPerformance\Metrics::POSITION => $calcPosition];
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Bing.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Bing.php
deleted file mode 100644
index 973ccd5..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Bing.php
+++ /dev/null
@@ -1,168 +0,0 @@
-table = Common::prefixTable(self::$rawTableName);
- }
- /**
- * Installs required database table
- */
- public static function install()
- {
- $table = "`url` VARCHAR( 170 ) NOT NULL ,\n\t\t\t\t\t `date` DATE NOT NULL ,\n\t\t\t\t\t `data` MEDIUMBLOB,\n\t\t\t\t\t `type` VARCHAR( 15 ) NOT NULL,\n\t\t\t\t\t PRIMARY KEY ( `url` , `date` , `type` )";
- // Key length = 170 + 3 byte (date) + 15 = 188
- DbHelper::createTable(self::$rawTableName, $table);
- }
- /**
- * Saves keywords for given url and day
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveKeywordData($url, $date, $keywords)
- {
- return $this->archiveData($url, $date, $keywords, 'keywords');
- }
- /**
- * Returns the saved keyword data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @return null|string serialized keyword data
- */
- public function getKeywordData($url, $date)
- {
- return $this->getData($url, $date, 'keywords');
- }
- /**
- * Returns the latest date keyword data is available for
- *
- * @param string $url url, eg. http://matomo.org
- * @return null|string
- */
- public function getLatestDateKeywordDataIsAvailableFor($url)
- {
- $date = Db::fetchOne('SELECT `date` FROM ' . $this->table . ' WHERE `url` = ? AND `type` = ? ORDER BY `date` DESC LIMIT 1', [$url, 'keywords']);
- return $date;
- }
- /**
- * Saves crawl stats for given url and day
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveCrawlStatsData($url, $date, $keywords)
- {
- return $this->archiveData($url, $date, $keywords, 'crawlstats');
- }
- /**
- * Returns the saved crawl stats for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @return null|string serialized keyword data
- */
- public function getCrawlStatsData($url, $date)
- {
- return $this->getData($url, $date, 'crawlstats');
- }
- /**
- * Saves crawl error for given url
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveCrawlErrors($url, $keywords)
- {
- return $this->archiveData($url, '0000-00-00', $keywords, 'crawlerrors');
- }
- /**
- * Returns the saved crawl stats for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @return null|string serialized keyword data
- */
- public function getCrawlErrors($url)
- {
- return $this->getData($url, '0000-00-00', 'crawlerrors');
- }
- /**
- * Returns the saved data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $type type of data, like keywords, crawlstats,...
- * @return null|string serialized data
- */
- protected function getData($url, $date, $type)
- {
- $keywordData = Db::fetchOne('SELECT `data` FROM ' . $this->table . ' WHERE `url` = ? AND `date` = ? AND `type` = ?', [$url, $date, $type]);
- if ($keywordData) {
- return $this->uncompress($keywordData);
- }
- return null;
- }
- /**
- * Saves data for given type, url and day
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @param string $type type of data, like keywords, crawlstats,...
- * @return bool
- */
- protected function archiveData($url, $date, $data, $type)
- {
- $query = "REPLACE INTO " . $this->table . " (`url`, `date`, `data`, `type`) VALUES (?,?,?,?)";
- $bindSql = [];
- $bindSql[] = $url;
- $bindSql[] = $date;
- $bindSql[] = $this->compress($data);
- $bindSql[] = $type;
- Db::query($query, $bindSql);
- return \true;
- }
- protected function compress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- $data = gzcompress($data);
- }
- return $data;
- }
- protected function uncompress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- $data = gzuncompress($data);
- }
- return $data;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Google.php
deleted file mode 100644
index b815517..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Google.php
+++ /dev/null
@@ -1,131 +0,0 @@
-table = Common::prefixTable(self::$rawTableName);
- }
- /**
- * Installs required database table
- */
- public static function install()
- {
- $dashboard = "`url` VARCHAR( 170 ) NOT NULL ,\n\t\t\t\t\t `date` DATE NOT NULL ,\n\t\t\t\t\t `data` MEDIUMBLOB,\n\t\t\t\t\t `type` VARCHAR( 15 ),\n\t\t\t\t\t PRIMARY KEY ( `url` , `date`, `type` )";
- // Key length = 170 + 3 byte (date) + 15 = 188
- DbHelper::createTable(self::$rawTableName, $dashboard);
- }
- /**
- * Saves keywords for given url and day
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $type 'web', 'image', 'video' or 'news'
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveKeywordData($url, $date, $type, $keywords)
- {
- return $this->archiveData($url, $date, $keywords, 'keywords' . $type);
- }
- /**
- * Returns the saved keyword data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $type 'web', 'image', 'video' or 'news'
- * @return null|string serialized keyword data
- */
- public function getKeywordData($url, $date, $type)
- {
- return $this->getData($url, $date, 'keywords' . $type);
- }
- /**
- * Returns the latest date keyword data is available for
- *
- * @param string $url url, eg. http://matomo.org
- * @param string|null $type 'web', 'image', 'video' or 'news'
- * @return null|string
- */
- public function getLatestDateKeywordDataIsAvailableFor($url, $type = null)
- {
- if ($type === null) {
- $date = Db::fetchOne('SELECT `date` FROM ' . $this->table . ' WHERE `url` = ? AND `type` LIKE ? ORDER BY `date` DESC LIMIT 1', [$url, 'keywords%']);
- } else {
- $date = Db::fetchOne('SELECT `date` FROM ' . $this->table . ' WHERE `url` = ? AND `type` = ? ORDER BY `date` DESC LIMIT 1', [$url, 'keywords' . $type]);
- }
- return $date;
- }
- /**
- * Returns the saved data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $type type of data, like keywords, crawlstats,...
- * @return null|string serialized data
- */
- protected function getData($url, $date, $type)
- {
- $keywordData = Db::fetchOne('SELECT `data` FROM ' . $this->table . ' WHERE `url` = ? AND `date` = ? AND `type` = ?', [$url, $date, $type]);
- if ($keywordData) {
- return $this->uncompress($keywordData);
- }
- return null;
- }
- /**
- * Saves data for given type, url and day
- *
- * @param string $url url, eg. http://matomo.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $data serialized keyword data
- * @param string $type type of data, like keywords, crawlstats,...
- * @return bool
- */
- protected function archiveData($url, $date, $data, $type)
- {
- $query = "REPLACE INTO " . $this->table . " (`url`, `date`, `data`, `type`) VALUES (?,?,?,?)";
- $bindSql = [];
- $bindSql[] = $url;
- $bindSql[] = $date;
- $bindSql[] = $this->compress($data);
- $bindSql[] = $type;
- Db::query($query, $bindSql);
- return \true;
- }
- protected function compress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- $data = gzcompress($data);
- }
- return $data;
- }
- protected function uncompress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- $data = gzuncompress($data);
- }
- return $data;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Yandex.php
deleted file mode 100644
index 5d8d4be..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Yandex.php
+++ /dev/null
@@ -1,147 +0,0 @@
-table = Common::prefixTable(self::$rawTableName);
- }
- /**
- * Installs required database table
- */
- public static function install()
- {
- $table = "`url` VARCHAR( 170 ) NOT NULL ,\n\t\t\t\t\t `date` DATE NOT NULL ,\n\t\t\t\t\t `data` MEDIUMBLOB,\n\t\t\t\t\t `type` VARCHAR(15) NOT NULL,\n\t\t\t\t\t PRIMARY KEY ( `url` , `date` , `type` )";
- // Key length = 170 + 3 byte (date) + 15 = 188
- DbHelper::createTable(self::$rawTableName, $table);
- }
- /**
- * Saves keywords for given url and day
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveKeywordData($url, $date, $keywords)
- {
- return $this->archiveData($url, $date, $keywords, 'keywords');
- }
- /**
- * Returns the saved keyword data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @return null|string serialized keyword data
- */
- public function getKeywordData($url, $date)
- {
- return $this->getData($url, $date, 'keywords');
- }
- /**
- * Returns the latest date keyword data is available for
- *
- * @param string $url url, eg. http://piwik.org
- * @return null|string
- */
- public function getLatestDateKeywordDataIsAvailableFor($url)
- {
- $date = Db::fetchOne('SELECT `date` FROM ' . $this->table . ' WHERE `url` = ? AND `type` = ? ORDER BY `date` DESC LIMIT 1', array($url, 'keywords'));
- return $date;
- }
- /**
- * Saves crawl stats for given url and day
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @return bool
- */
- public function archiveCrawlStatsData($url, $date, $keywords)
- {
- return $this->archiveData($url, $date, $keywords, 'crawlstats');
- }
- /**
- * Returns the saved crawl stats for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @return null|string serialized keyword data
- */
- public function getCrawlStatsData($url, $date)
- {
- return $this->getData($url, $date, 'crawlstats');
- }
- /**
- * Returns the saved data for given parameters (or null if not available)
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $type type of data, like keywords, crawlstats,...
- * @return null|string serialized data
- */
- protected function getData($url, $date, $type)
- {
- $keywordData = Db::fetchOne('SELECT `data` FROM ' . $this->table . ' WHERE `url` = ? AND `date` = ? AND `type` = ?', [$url, $date, $type]);
- if ($keywordData) {
- return $this->uncompress($keywordData);
- }
- return null;
- }
- /**
- * Saves data for given type, url and day
- *
- * @param string $url url, eg. http://piwik.org
- * @param string $date a day string, eg. 2016-12-24
- * @param string $keywords serialized keyword data
- * @param string $type type of data, like keywords, crawlstats,...
- * @return bool
- */
- protected function archiveData($url, $date, $data, $type)
- {
- $query = "REPLACE INTO " . $this->table . " (`url`, `date`, `data`, `type`) VALUES (?,?,?,?)";
- $bindSql = [];
- $bindSql[] = $url;
- $bindSql[] = $date;
- $bindSql[] = $this->compress($data);
- $bindSql[] = $type;
- Db::query($query, $bindSql);
- return \true;
- }
- protected function compress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- return gzcompress($data);
- }
- return $data;
- }
- protected function uncompress($data)
- {
- if (Db::get()->hasBlobDataType()) {
- return gzuncompress($data);
- }
- return $data;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Monolog/Handler/SEKPSystemLogHandler.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Monolog/Handler/SEKPSystemLogHandler.php
deleted file mode 100644
index ab91f05..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Monolog/Handler/SEKPSystemLogHandler.php
+++ /dev/null
@@ -1,33 +0,0 @@
-getClient()->isConfigured();
- }
- public function isSupportedPeriod($date, $period)
- {
- // disabled for date ranges that need days for processing
- if ($period == 'range') {
- $periodObject = new Range($period, $date);
- $subPeriods = $periodObject->getSubperiods();
- foreach ($subPeriods as $subPeriod) {
- if ($subPeriod->getLabel() == 'day') {
- return \false;
- }
- }
- }
- // bing statistics are not available for single days
- if ($period == 'day') {
- return \false;
- }
- return \true;
- }
- /**
- * @inheritdoc
- */
- public function getConfiguredSiteIds()
- {
- $configuredSites = [];
- foreach ($this->getMeasurableHelper()->getAllSiteSettings() as $siteId => $settings) {
- $createdByUser = !is_null($settings->bingConfigCreatedBy) ? $settings->bingConfigCreatedBy->getValue() : '';
- $siteConfig = [];
- if ($settings->bingSiteUrl && $settings->bingSiteUrl->getValue()) {
- $siteConfig['bingSiteUrl'] = $settings->bingSiteUrl->getValue();
- $siteConfig['createdByUser'] = $createdByUser;
- $siteConfig['isDeletionAllowed'] = empty($createdByUser) || Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser);
- }
- if (!empty($siteConfig)) {
- $configuredSites[$siteId] = $siteConfig;
- }
- }
- return $configuredSites;
- }
- public function getConfigurationProblems()
- {
- return ['sites' => $this->getSiteErrors(), 'accounts' => $this->getAccountErrors()];
- }
- protected function getSiteErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- $configuredSiteIds = $this->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- $bingSiteUrl = $config['bingSiteUrl'];
- list($apiKey, $url) = explode('##', $bingSiteUrl);
- if (!key_exists($apiKey, $accounts)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_AccountDoesNotExist', [$this->obfuscateApiKey($apiKey)]);
- continue;
- }
- $urls = $client->getAvailableUrls($apiKey);
- if (!key_exists($url, $urls)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_ConfiguredUrlNotAvailable');
- continue;
- }
- }
- return $errors;
- }
- protected function getAccountErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- }
- foreach ($accounts as $id => $account) {
- try {
- $client->testConfiguration($account['apiKey']);
- } catch (\Exception $e) {
- $errors[$id] = Piwik::translate('SearchEngineKeywordsPerformance_BingAccountError', $e->getMessage());
- }
- }
- return $errors;
- }
- protected function obfuscateApiKey($apiKey)
- {
- return substr($apiKey, 0, 5) . '*****' . substr($apiKey, -5, 5);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Google.php
deleted file mode 100644
index cc2c951..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Google.php
+++ /dev/null
@@ -1,147 +0,0 @@
-getClient()->isConfigured();
- }
- /**
- * @inheritdoc
- */
- public function getConfiguredSiteIds()
- {
- $configuredSites = [];
- foreach ($this->getMeasurableHelper()->getAllSiteSettings() as $siteId => $settings) {
- $createdByUser = !is_null($settings->googleConfigCreatedBy) ? $settings->googleConfigCreatedBy->getValue() : '';
- $siteConfig = [];
- if ($settings->googleSearchConsoleUrl && $settings->googleSearchConsoleUrl->getValue()) {
- $siteConfig['googleSearchConsoleUrl'] = $settings->googleSearchConsoleUrl->getValue();
- $siteConfig['googleWebKeywords'] = $settings->googleWebKeywords->getValue();
- $siteConfig['googleImageKeywords'] = $settings->googleImageKeywords->getValue();
- $siteConfig['googleVideoKeywords'] = $settings->googleVideoKeywords->getValue();
- $siteConfig['googleNewsKeywords'] = $settings->googleNewsKeywords->getValue();
- $siteConfig['createdByUser'] = $createdByUser;
- $siteConfig['isDeletionAllowed'] = empty($createdByUser) || Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser);
- }
- if (!empty($siteConfig)) {
- $configuredSites[$siteId] = $siteConfig;
- }
- }
- return $configuredSites;
- }
- public function getConfigurationProblems()
- {
- $errors = ['sites' => $this->getSiteErrors(), 'accounts' => $this->getAccountErrors()];
- return $errors;
- }
- protected function getSiteErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- $configuredSiteIds = $this->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- $googleSiteUrl = $config['googleSearchConsoleUrl'];
- list($accountId, $url) = explode('##', $googleSiteUrl);
- if (!key_exists($accountId, $accounts)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_AccountDoesNotExist', ['']);
- continue;
- }
- // Property Sets and Apps are deprecated and will be removed, so warn users why it doesn't work anymore
- // @todo can be removed in august 2019
- if (strpos($url, 'sc-set:') === 0) {
- $errors[$configuredSiteId] = 'You are using a property set for importing. Property sets have been deprecated/removed by Google. To ensure no error occurs, please choose another site for import';
- continue;
- }
- if (strpos($url, 'android-app:') === 0) {
- $errors[$configuredSiteId] = 'You are using a Android App for importing. Importing Android Apps has been deprecated/removed by Google. To ensure no error occurs, please choose another site for import';
- continue;
- }
- $urls = $client->getAvailableUrls($accountId);
- if (!key_exists($url, $urls)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_ConfiguredUrlNotAvailable');
- continue;
- }
- }
- return $errors;
- }
- protected function getAccountErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- }
- foreach ($accounts as $id => $account) {
- try {
- $client->testConfiguration($id);
- } catch (\Exception $e) {
- $errors[$id] = $e->getMessage();
- }
- }
- return $errors;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Helper/MeasurableHelper.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Helper/MeasurableHelper.php
deleted file mode 100644
index 995fab8..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Helper/MeasurableHelper.php
+++ /dev/null
@@ -1,53 +0,0 @@
-allSiteSettings)) {
- return $this->allSiteSettings;
- }
-
- $siteManagerModel = StaticContainer::get(SitesManagerModel::class);
- $allSiteIds = $siteManagerModel->getSitesId();
- $this->allSiteSettings = [];
- foreach ($allSiteIds as $siteId) {
- if (!Piwik::isUserHasAdminAccess($siteId)) {
- continue;
- // skip sites without access
- }
- $this->allSiteSettings[$siteId] = $this->getMeasurableSettings($siteId);
- }
-
- return $this->allSiteSettings;
- }
-
- protected function getMeasurableSettings(int $idSite): MeasurableSettings
- {
- return new MeasurableSettings($idSite);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/ProviderAbstract.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/ProviderAbstract.php
deleted file mode 100644
index 4755515..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/ProviderAbstract.php
+++ /dev/null
@@ -1,151 +0,0 @@
- [], accounts => [] ]
- */
- abstract public function getConfigurationProblems();
- /**
- * Record a new timestamp for the current provider indicating an API error occurred.
- *
- * @return void
- */
- public function recordNewApiErrorForProvider(): void
- {
- Option::set(self::OPTION_PREFIX_LAST_ERROR_TIME . $this->getId(), time());
- }
- /**
- * Get the timestamp of the most recent API error for the current provider. If none is found, 0 is returned.
- *
- * @return int Unix timestamp or 0;
- */
- public function getLastApiErrorTimestamp(): int
- {
- $option = Option::get(self::OPTION_PREFIX_LAST_ERROR_TIME . $this->getId());
- if (empty($option)) {
- return 0;
- }
- return (int) $option;
- }
- /**
- * Checks if there has been an API error for the current provider within the past week.
- *
- * @return bool Indicates whether the most recent API error was less than a week ago.
- */
- public function hasApiErrorWithinWeek(): bool
- {
- $timestamp = $this->getLastApiErrorTimestamp();
- if ($timestamp === 0) {
- return \false;
- }
- return $timestamp > strtotime('-1 week');
- }
- /**
- * If there's been an API error within the past week a message string is provided. Otherwise, the string is empty.
- *
- * @return string Either the message or empty string depending on whether there's been a recent error.
- */
- public function getRecentApiErrorMessage(): string
- {
- $message = '';
- if ($this->hasApiErrorWithinWeek()) {
- $message = '' . $this->getName() . ' - Most recent error: ' . (new Formatter())->getPrettyTimeFromSeconds(time() - $this->getLastApiErrorTimestamp()) . ' ago';
- }
- return $message;
- }
-
- protected function getMeasurableHelper(): MeasurableHelper
- {
- return MeasurableHelper::getInstance();
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Yandex.php
deleted file mode 100644
index ce70f3d..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Yandex.php
+++ /dev/null
@@ -1,147 +0,0 @@
-getClient()->isConfigured();
- }
- /**
- * @inheritdoc
- */
- public function getConfiguredSiteIds()
- {
- $configuredSites = [];
- foreach ($this->getMeasurableHelper()->getAllSiteSettings() as $siteId => $settings) {
- $siteConfig = [];
- $createdByUser = !is_null($settings->yandexConfigCreatedBy) ? $settings->yandexConfigCreatedBy->getValue() : '';
- if ($settings->yandexAccountAndHostId && $settings->yandexAccountAndHostId->getValue()) {
- $siteConfig['yandexAccountAndHostId'] = $settings->yandexAccountAndHostId->getValue();
- $siteConfig['createdByUser'] = $createdByUser;
- $siteConfig['isDeletionAllowed'] = empty($createdByUser) || Piwik::hasUserSuperUserAccessOrIsTheUser($createdByUser);
- }
- if (!empty($siteConfig)) {
- $configuredSites[$siteId] = $siteConfig;
- }
- }
- return $configuredSites;
- }
- public function getConfigurationProblems()
- {
- $errors = ['sites' => $this->getSiteErrors(), 'accounts' => $this->getAccountErrors()];
- return $errors;
- }
- protected function getSiteErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- $configuredSiteIds = $this->getConfiguredSiteIds();
- foreach ($configuredSiteIds as $configuredSiteId => $config) {
- $yandexSiteUrl = $config['yandexAccountAndHostId'];
- list($accountId, $url) = explode('##', $yandexSiteUrl);
- if (!key_exists($accountId, $accounts)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_AccountDoesNotExist', ['']);
- continue;
- }
- $urls = [];
- try {
- $urlArray = $client->getAvailableUrls($accountId);
- foreach ($urlArray as $item) {
- $urls[] = $item['host_id'];
- }
- } catch (\Exception $e) {
- }
- if (!in_array($url, $urls)) {
- $errors[$configuredSiteId] = Piwik::translate('SearchEngineKeywordsPerformance_ConfiguredUrlNotAvailable');
- continue;
- }
- }
- return $errors;
- }
- protected function getAccountErrors()
- {
- $errors = [];
- $client = $this->getClient();
- $accounts = $client->getAccounts();
- if (empty($accounts)) {
- return [];
- }
- foreach ($accounts as $id => $account) {
- try {
- $client->testConfiguration($id);
- } catch (\Exception $e) {
- $errors[$id] = $e->getMessage();
- }
- }
- return $errors;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/README.md b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/README.md
deleted file mode 100644
index 9e56904..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/README.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# Search Engine Keywords Performance Plugin for Matomo
-
-## Description
-
-Uncover the keywords people use to find your site in the search engines. Learn which keywords are driving the most traffic, leads, and sales.
-
-There was a time when Google Analytics let you see what keywords people searched for to find your site. But one day, Google curtailed this critical information behind two fateful, cryptic words: "(not provided)."
-
-Since then, there was no way you could access this data. Unless you used Matomo's Search Engine Keywords Performance plugin, that is.
-
-With Search Engine Keywords Performance, the keywords people use to find your site become a dimension in your "Referrers" reports.
-
-Monitor your keywords' positions and boost your SEO performance like in the old days.
-
-### How Search Engine Keywords Performance Works
-
-#### All the Keywords Search Engines Don't Want You to See In One Report
-
-
-
-
Google, Yahoo, and Bing may not want you to see what keywords get you traffic, but we do. How? By leveraging their APIs.
-
Slice the keywords data with one of the 90+ dimensions and mix them with metrics like impressions, clicks, CTR, and the average position in the SERPs.
-
-
-
-
-
-
-#### Get An In-Depth Look at Your Crawling Performance
-
-
-
-
No matter how well you optimise your site, without proper crawling, your SEO efforts will be in vain.
-
Discover the number of pages crawled and indexed, 404 pages found, and other issues that could affect your crawling performance in Yahoo and Bing.
-
The page crawling error reports will show you what pages could not be crawled by a search engine with a detailed reason, so you can fix them right away.
-
-
-
-
-
-
-#### Identify What Keywords Your Images and Videos Bring You Traffic
-
-
-
-
Considering that YouTube and Google Images are the second and third largest search engines, your videos and images can drive significant organic traffic to your site.
-
With the Search Engine Keywords Performance plugin, you can uncover every keyword they rank for and how many visitors they attract, among other metrics.
-
-
-
-
-
-
-#### See How Your Keyword Performance Evolves Over Time
-
-
-
-
Track your top keywords and see how your metrics and KPIs unfold. Monitor, identify, and optimise your SEO strategy for opportunities to get the highest return from your efforts.
-
-
-
-
-
-
-### Try Search Engine Keywords Performance Today
-
-Unveil the true picture of your SEO performance with Matomo's Search Engine Keywords Performance plugin. See once again what keywords you rank for and take your organic traffic to the next level.
-
-It's time you enjoy an unparalleled data-driven SEO strategy with Matomo. Start your 30-day free trial today.
-
-## Dependencies
-This plugin had its vendored dependencies scoped using [matomo scoper](https://github.com/matomo-org/matomo-scoper). This means that composer packages are prefixed so that they won't conflict with the same libraries used by other plugins. If you need to update a dependency, you should be able to run `composer install` to populate the vendor directory, make sure that you have the [DevPluginCommands plugin](https://github.com/innocraft/dev-plugin-commands) installed, and run the following command `./console devplugincommands:process-dependencies --plugin="SearchEngineKeywordsPerformance" --downgrade-php` to scope and transpile the dependencies.
-
-### Features
-* New Search Keywords report in Matomo Referrers section.
-* View Keywords analytics by search type (web VS image VS video).
-* View combined Keywords across all search engines (Google + Bing + Yahoo + Yandex).
-* Monitor Keyword rankings and Search Engine Optimisation performance for each keyword with [Row Evolution](https://matomo.org/docs/row-evolution/).
-* New Crawling overview report show how Search engines bots crawl your websites (Bing + Yahoo and Yandex).
-* View crawling overview key metrics (for Bing + Yahoo and Yandex): crawled pages, total pages in index, total inboud links, robots.txt exclusion page count, crawl errors, DNS failures, connection timeouts, page redirects (301, 302 http status), error pages (4xx http status), internet error pages (5xx http status).
-* Import the detailed list of search keywords for Google search, Google images and Google Videos directly from Google Search Console.
-* Import the detailed list of search keywords from Bing and Yahoo! search directly from Bing Webmaster Tools.
-* Import the detailed list of search keywords from Yandex search directly from Yandex Webmaster API.
-* View all crawling errors with detailed reasons like server errors, robots.txt exclusions, not found pages, ... (Bing + Yahoo)
-* Possibility to add support for other search engines that provide their data through an API (contact us).
-* Get your Keyword analytics SEO reports by [email](https://matomo.org/docs/email-reports/) to you, your colleagues or customers.
-* Export your Keyword analytics report using the [Search Keywords Performance Monitor API](http://developer.matomo.org/api-reference/reporting-api#SearchEngineKeywordsPerformance).
\ No newline at end of file
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Base.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Base.php
deleted file mode 100644
index dbf86c2..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Base.php
+++ /dev/null
@@ -1,32 +0,0 @@
-maxRowsInTable = PiwikConfig::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
- $this->columnToSortByBeforeTruncation = Metrics::NB_CLICKS;
- $this->columnAggregationOps = Metrics::getColumnsAggregationOperations();
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Bing.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Bing.php
deleted file mode 100644
index f376b1c..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Bing.php
+++ /dev/null
@@ -1,204 +0,0 @@
-apiKey = $apiKey;
- $this->apiUrl = $apiUrl;
- $this->logger = $logger;
- $this->columnAggregationOps = array_merge(Metrics::getColumnsAggregationOperations(), [
- self::CRAWLSTATS_OTHER_CODES_RECORD_NAME => 'max',
- self::CRAWLSTATS_BLOCKED_ROBOTS_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_2XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_301_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_302_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_4XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_5XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_TIMEOUT_RECORD_NAME => 'max',
- self::CRAWLSTATS_MALWARE_RECORD_NAME => 'max',
- self::CRAWLSTATS_ERRORS_RECORD_NAME => 'max',
- self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => 'max',
- self::CRAWLSTATS_DNS_FAILURE_RECORD_NAME => 'max',
- self::CRAWLSTATS_IN_INDEX_RECORD_NAME => 'max',
- self::CRAWLSTATS_IN_LINKS_RECORD_NAME => 'max'
- ]);
- }
- public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
- {
- return [
- Record::make(Record::TYPE_BLOB, self::KEYWORDS_BING_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_OTHER_CODES_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_BLOCKED_ROBOTS_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_2XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_301_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_302_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_4XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_5XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_TIMEOUT_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_MALWARE_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_ERRORS_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_DNS_FAILURE_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_IN_INDEX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_IN_LINKS_RECORD_NAME)
- ];
- }
- protected function aggregate(ArchiveProcessor $archiveProcessor): array
- {
- $records = [];
- $parameters = $archiveProcessor->getParams();
- $date = $parameters->getDateStart()->setTimezone('UTC')->toString('Y-m-d');
- $this->logger->debug("[SearchEngineKeywordsPerformance] Archiving bing records for {$date} and {$this->apiUrl}");
- $dataTable = $this->getKeywordsAsDataTable($date);
- if (empty($dataTable)) {
- // ensure data is present (if available)
- BingImporter::importAvailablePeriods($this->apiKey, $this->apiUrl);
- $dataTable = $this->getKeywordsAsDataTable($date);
- }
- if (!empty($dataTable)) {
- $this->logger->debug("[SearchEngineKeywordsPerformance] Archiving bing keywords for {$date} and {$this->apiUrl}");
- $records[self::KEYWORDS_BING_RECORD_NAME] = $dataTable;
- }
- $this->archiveDayCrawlStatNumerics($records, $date);
- return $records;
- }
- /**
- * Returns keyword data for given parameters as DataTable
- */
- protected function getKeywordsAsDataTable(string $date): ?DataTable
- {
- $model = new BingModel();
- $keywordData = $model->getKeywordData($this->apiUrl, $date);
- if (!empty($keywordData)) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- return $dataTable;
- }
- return null;
- }
- /**
- * Inserts various numeric records for crawl stats
- */
- protected function archiveDayCrawlStatNumerics(array &$records, string $date): void
- {
- $dataTable = $this->getCrawlStatsAsDataTable($date);
- if (!empty($dataTable)) {
- Log::debug("[SearchEngineKeywordsPerformance] Archiving bing crawl stats for {$date} and {$this->apiUrl}");
- $getValue = function ($label) use ($dataTable) {
- $row = $dataTable->getRowFromLabel($label);
- if ($row) {
- return (int) $row->getColumn(Metrics::NB_PAGES);
- }
- return 0;
- };
- $records = array_merge($records, [
- self::CRAWLSTATS_OTHER_CODES_RECORD_NAME => $getValue('AllOtherCodes'),
- self::CRAWLSTATS_BLOCKED_ROBOTS_RECORD_NAME => $getValue('BlockedByRobotsTxt'),
- self::CRAWLSTATS_CODE_2XX_RECORD_NAME => $getValue('Code2xx'),
- self::CRAWLSTATS_CODE_301_RECORD_NAME => $getValue('Code301'),
- self::CRAWLSTATS_CODE_302_RECORD_NAME => $getValue('Code302'),
- self::CRAWLSTATS_CODE_4XX_RECORD_NAME => $getValue('Code4xx'),
- self::CRAWLSTATS_CODE_5XX_RECORD_NAME => $getValue('Code5xx'),
- self::CRAWLSTATS_TIMEOUT_RECORD_NAME => $getValue('ConnectionTimeout'),
- self::CRAWLSTATS_MALWARE_RECORD_NAME => $getValue('ContainsMalware'),
- self::CRAWLSTATS_ERRORS_RECORD_NAME => $getValue('CrawlErrors'),
- self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => $getValue('CrawledPages'),
- self::CRAWLSTATS_DNS_FAILURE_RECORD_NAME => $getValue('DnsFailures'),
- self::CRAWLSTATS_IN_INDEX_RECORD_NAME => $getValue('InIndex'),
- self::CRAWLSTATS_IN_LINKS_RECORD_NAME => $getValue('InLinks')
- ]);
- Common::destroy($dataTable);
- unset($dataTable);
- }
- }
- /**
- * Returns crawl stats for given parameters as DataTable
- */
- protected function getCrawlStatsAsDataTable(string $date): ?DataTable
- {
- $model = new BingModel();
- $keywordData = $model->getCrawlStatsData($this->apiUrl, $date);
- if (!empty($keywordData)) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- return $dataTable;
- }
- return null;
- }
- public static function make(int $idSite): ?self
- {
- $site = new Site($idSite);
- $setting = new MeasurableSettings($site->getId(), $site->getType());
- $bingSiteUrl = $setting->bingSiteUrl;
- $doesNotHaveBing = empty($bingSiteUrl) || !$bingSiteUrl->getValue() || \false === strpos($bingSiteUrl->getValue(), '##');
- if ($doesNotHaveBing) {
- // bing api not activated for that site
- return null;
- }
- list($apiKey, $url) = explode('##', $bingSiteUrl->getValue());
- return StaticContainer::getContainer()->make(\Piwik\Plugins\SearchEngineKeywordsPerformance\RecordBuilders\Bing::class, ['apiKey' => $apiKey, 'apiUrl' => $url]);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Google.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Google.php
deleted file mode 100644
index 796f408..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Google.php
+++ /dev/null
@@ -1,155 +0,0 @@
-accountId = $accountId;
- $this->searchConsoleUrl = $searchConsoleUrl;
- $this->recordName = $recordName;
- $this->logger = $logger;
- }
- public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
- {
- return [ArchiveProcessor\Record::make(Record::TYPE_BLOB, $this->recordName)];
- }
- protected function aggregate(ArchiveProcessor $archiveProcessor): array
- {
- $parameters = $archiveProcessor->getParams();
- $date = $parameters->getDateStart()->setTimezone('UTC')->toString('Y-m-d');
- $record = $this->aggregateDayBySearchType($archiveProcessor, $this->recordName, $date);
- if (empty($record)) {
- return [];
- }
- return [$this->recordName => $record];
- }
- public function isEnabled(ArchiveProcessor $archiveProcessor): bool
- {
- $segment = $archiveProcessor->getParams()->getSegment();
- if (!$segment->isEmpty()) {
- $this->logger->debug("Skip Archiving for SearchEngineKeywordsPerformance plugin for segments");
- return \false;
- // do not archive data for segments
- }
- return \true;
- }
- /**
- * Aggregates data for a given day by type of search
- */
- protected function aggregateDayBySearchType(ArchiveProcessor $archiveProcessor, string $recordName, string $date): ?DataTable
- {
- $types = [self::KEYWORDS_GOOGLE_WEB_RECORD_NAME => 'web', self::KEYWORDS_GOOGLE_IMAGE_RECORD_NAME => 'image', self::KEYWORDS_GOOGLE_VIDEO_RECORD_NAME => 'video', self::KEYWORDS_GOOGLE_NEWS_RECORD_NAME => 'news'];
- $this->logger->debug("[SearchEngineKeywordsPerformance] Archiving {$types[$recordName]} keywords for {$date} and {$this->searchConsoleUrl}");
- $dataTable = $this->getKeywordsAsDataTable($archiveProcessor, $date, $types[$recordName]);
- if (empty($dataTable)) {
- return null;
- }
- return $dataTable;
- }
- /**
- * Returns keyword data for given parameters as DataTable
- */
- protected function getKeywordsAsDataTable(ArchiveProcessor $archiveProcessor, string $date, string $type): ?DataTable
- {
- // ensure keywords are present (if available)
- $googleImporter = new GoogleImporter($archiveProcessor->getParams()->getSite()->getId());
- $googleImporter->importKeywordsIfNecessary($this->accountId, $this->searchConsoleUrl, $date, $type);
- $model = new GoogleModel();
- $keywordData = $model->getKeywordData($this->searchConsoleUrl, $date, $type);
- if (!empty($keywordData)) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- return $dataTable;
- }
- return null;
- }
- public static function makeAll(int $idSite): array
- {
- $site = new Site($idSite);
- $settings = new MeasurableSettings($site->getId(), $site->getType());
- $searchConsoleUrl = $settings->googleSearchConsoleUrl;
- if (empty($searchConsoleUrl) || !$searchConsoleUrl->getValue() || \false === strpos($searchConsoleUrl->getValue(), '##')) {
- return [];
- // search console not activated for that site
- }
- $searchConsoleSetting = $settings->googleSearchConsoleUrl->getValue();
- list($accountId, $searchConsoleUrl) = explode('##', $searchConsoleSetting);
- $archives = [];
- if ($settings->googleWebKeywords->getValue()) {
- $archives[] = self::KEYWORDS_GOOGLE_WEB_RECORD_NAME;
- }
- if ($settings->googleImageKeywords->getValue()) {
- $archives[] = self::KEYWORDS_GOOGLE_IMAGE_RECORD_NAME;
- }
- if ($settings->googleVideoKeywords->getValue()) {
- $archives[] = self::KEYWORDS_GOOGLE_VIDEO_RECORD_NAME;
- }
- if ($settings->googleNewsKeywords->getValue()) {
- $archives[] = self::KEYWORDS_GOOGLE_NEWS_RECORD_NAME;
- }
- $logger = StaticContainer::get(LoggerInterface::class);
- $builders = array_map(function ($recordName) use ($accountId, $searchConsoleUrl, $logger) {
- return new self($accountId, $searchConsoleUrl, $recordName, $logger);
- }, $archives);
- return $builders;
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Yandex.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Yandex.php
deleted file mode 100644
index 140a5b8..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Yandex.php
+++ /dev/null
@@ -1,196 +0,0 @@
-accountId = $accountId;
- $this->hostId = $hostId;
- $this->logger = $logger;
- $this->columnAggregationOps = array_merge(Metrics::getColumnsAggregationOperations(), [
- self::CRAWLSTATS_IN_INDEX_RECORD_NAME => 'max',
- self::CRAWLSTATS_APPEARED_PAGES_RECORD_NAME => 'max',
- self::CRAWLSTATS_REMOVED_PAGES_RECORD_NAME => 'max',
- self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_2XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_3XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_4XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_CODE_5XX_RECORD_NAME => 'max',
- self::CRAWLSTATS_ERRORS_RECORD_NAME => 'max'
- ]);
- }
- public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
- {
- return [
- Record::make(Record::TYPE_BLOB, self::KEYWORDS_YANDEX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_IN_INDEX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_APPEARED_PAGES_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_REMOVED_PAGES_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_2XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_3XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_4XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_CODE_5XX_RECORD_NAME),
- Record::make(Record::TYPE_NUMERIC, self::CRAWLSTATS_ERRORS_RECORD_NAME)
- ];
- }
- protected function aggregate(ArchiveProcessor $archiveProcessor): array
- {
- $records = [];
- $parameters = $archiveProcessor->getParams();
- $date = $parameters->getDateStart()->setTimezone('UTC')->toString('Y-m-d');
- $this->logger->debug("[SearchEngineKeywordsPerformance] Archiving yandex records for {$date} and {$this->hostId}");
- $dataTable = $this->getKeywordsAsDataTable($date);
- if (empty($dataTable)) {
- // ensure data is present (if available)
- YandexImporter::importAvailableDataForDate($this->accountId, $this->hostId, $date);
- $dataTable = $this->getKeywordsAsDataTable($date);
- }
- if (!empty($dataTable)) {
- Log::debug("[SearchEngineKeywordsPerformance] Archiving yandex keywords for {$date} and {$this->hostId}");
- $records[self::KEYWORDS_YANDEX_RECORD_NAME] = $dataTable;
- }
- $records = array_merge($records, $this->archiveDayCrawlStatNumerics($date));
- return $records;
- }
- public function isEnabled(ArchiveProcessor $archiveProcessor): bool
- {
- $segment = $archiveProcessor->getParams()->getSegment();
- if (!$segment->isEmpty()) {
- $this->logger->debug("Skip Archiving for SearchEngineKeywordsPerformance plugin for segments");
- return \false;
- // do not archive data for segments
- }
- return \true;
- }
- /**
- * Returns keyword data for given parameters as DataTable
- */
- protected function getKeywordsAsDataTable(string $date): ?DataTable
- {
- $model = new YandexModel();
- $keywordData = $model->getKeywordData($this->hostId, $date);
- if (!empty($keywordData)) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- return $dataTable;
- }
- return null;
- }
- /**
- * Returns keyword data for given parameters as DataTable
- */
- protected function archiveDayCrawlStatNumerics(string $date): array
- {
- $dataTable = $this->getCrawlStatsAsDataTable($date);
- if (!empty($dataTable)) {
- $this->logger->debug("[SearchEngineKeywordsPerformance] Archiving yandex crawl stats for {$date} and {$this->hostId}");
- $getValue = function ($label) use ($dataTable) {
- $row = $dataTable->getRowFromLabel($label);
- if ($row) {
- return (int) $row->getColumn(Metrics::NB_PAGES);
- }
- return 0;
- };
- $numericRecords = [
- self::CRAWLSTATS_IN_INDEX_RECORD_NAME => $getValue('SEARCHABLE'),
- self::CRAWLSTATS_APPEARED_PAGES_RECORD_NAME => $getValue('APPEARED_IN_SEARCH'),
- self::CRAWLSTATS_REMOVED_PAGES_RECORD_NAME => $getValue('REMOVED_FROM_SEARCH'),
- self::CRAWLSTATS_CRAWLED_PAGES_RECORD_NAME => $getValue('HTTP_2XX') + $getValue('HTTP_3XX') + $getValue('HTTP_4XX') + $getValue('HTTP_5XX') + $getValue('OTHER'),
- self::CRAWLSTATS_CODE_2XX_RECORD_NAME => $getValue('HTTP_2XX'),
- self::CRAWLSTATS_CODE_3XX_RECORD_NAME => $getValue('HTTP_3XX'),
- self::CRAWLSTATS_CODE_4XX_RECORD_NAME => $getValue('HTTP_4XX'),
- self::CRAWLSTATS_CODE_5XX_RECORD_NAME => $getValue('HTTP_5XX'),
- self::CRAWLSTATS_ERRORS_RECORD_NAME => $getValue('OTHER')
- ];
- Common::destroy($dataTable);
- unset($dataTable);
- return $numericRecords;
- }
- return [];
- }
- /**
- * Returns crawl stats for given parameters as DataTable
- */
- protected function getCrawlStatsAsDataTable(string $date): ?DataTable
- {
- $model = new YandexModel();
- $keywordData = $model->getCrawlStatsData($this->hostId, $date);
- if (!empty($keywordData)) {
- $dataTable = new DataTable();
- $dataTable->addRowsFromSerializedArray($keywordData);
- return $dataTable;
- }
- return null;
- }
- public static function make(int $idSite): ?self
- {
- $site = new Site($idSite);
- $setting = new MeasurableSettings($site->getId(), $site->getType());
- $yandexConfig = $setting->yandexAccountAndHostId;
- $doesNotHaveYandex = empty($yandexConfig) || !$yandexConfig->getValue() || \false === strpos($yandexConfig->getValue(), '##');
- if ($doesNotHaveYandex) {
- // yandex api not activated for that site
- return null;
- }
- list($accountId, $hostId) = explode('##', $yandexConfig->getValue());
- return StaticContainer::getContainer()->make(self::class, ['accountId' => $accountId, 'hostId' => $hostId]);
- }
-}
diff --git a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/Base.php b/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/Base.php
deleted file mode 100644
index 2006c39..0000000
--- a/files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/Base.php
+++ /dev/null
@@ -1,205 +0,0 @@
-categoryId = 'Referrers_Referrers';
- $this->subcategoryId = 'Referrers_SubmenuSearchEngines';
- $this->defaultSortColumn = Metrics::NB_CLICKS;
- $this->metrics = Metrics::getKeywordMetrics();
- $this->processedMetrics = [];
- if (property_exists($this, 'onlineGuideUrl')) {
- $this->onlineGuideUrl = Url::addCampaignParametersToMatomoLink('https://matomo.org/guide/installation-maintenance/import-search-keywords/');
- }
- }
- public function getMetricsDocumentation()
- {
- return Metrics::getMetricsDocumentation();
- }
- public function configureReportMetadata(&$availableReports, $infos)
- {
- $this->idSite = $infos['idSite'];
- parent::configureReportMetadata($availableReports, $infos);
- }
- public function configureView(ViewDataTable $view)
- {
- $view->config->addTranslations(['label' => $this->dimension->getName()]);
- $view->config->show_limit_control = \true;
- $view->config->show_all_views_icons = \false;
- $view->config->show_table_all_columns = \false;
- $view->config->columns_to_display = ['label', Metrics::NB_CLICKS, Metrics::NB_IMPRESSIONS, Metrics::CTR, Metrics::POSITION];
- $view->requestConfig->filter_limit = 10;
- $this->configureSegmentNotSupported($view);
- }
- public function getSecondarySortColumnCallback()
- {
- return function ($firstSortColumn, $table) {
- return $firstSortColumn === Metrics::NB_CLICKS ? Metrics::NB_IMPRESSIONS : Metrics::NB_CLICKS;
- };
- }
- protected function configureSegmentNotSupported(ViewDataTable $view)
- {
- // show 'not supported' message if segment is chosen
- if (Common::getRequestVar('segment', '')) {
- $view->config->show_footer_message .= '