diff --git a/core/Plugin/Controller.php b/core/Plugin/Controller.php index 57f5bcc9e56..42f99dcc9fc 100644 --- a/core/Plugin/Controller.php +++ b/core/Plugin/Controller.php @@ -726,6 +726,7 @@ protected function setBasicVariablesNoneAdminView($view) { $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions(); $view->isSuperUser = Access::getInstance()->hasSuperUserAccess(); + $view->userCurrentRole = Access::getInstance()->getRoleForSite($this->idSite); $view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess(); $view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess(); $view->isUserIsAnonymous = Piwik::isUserIsAnonymous(); diff --git a/core/Updates/5.7.0-b1.php b/core/Updates/5.7.0-b1.php new file mode 100644 index 00000000000..50027715640 --- /dev/null +++ b/core/Updates/5.7.0-b1.php @@ -0,0 +1,42 @@ +migration = $factory; + } + + public function getMigrations(Updater $updater) + { + return [ + $this->migration->db->addColumns('segment', [ + 'starred' => 'TINYINT(1) NOT NULL DEFAULT 0', + 'starred_by' => 'VARCHAR(100) NULL DEFAULT NULL', + ]), + ]; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/core/Version.php b/core/Version.php index bff370eca44..4d6958c8be7 100644 --- a/core/Version.php +++ b/core/Version.php @@ -22,7 +22,7 @@ final class Version * The current Matomo version. * @var string */ - public const VERSION = '5.7.0-alpha'; + public const VERSION = '5.7.0-b1'; public const MAJOR_VERSION = 5; diff --git a/lang/en.json b/lang/en.json index ad60dd48cea..2f406ce111a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -28,6 +28,18 @@ "BrokenDownReportDocumentation": "It is broken down into various reports, which are displayed in sparklines at the bottom of the page. You can enlarge the graphs by clicking on the report you'd like to see.", "Cancel": "Cancel", "CannotUnzipFile": "Cannot unzip file %1$s: %2$s", + "CanNotEditGlobalSegment": "This is a global segment. Only super users can edit global segments.", + "CanNotStarGlobalSegment": "This is a global segment. Only super users can star global segments.", + "CanNotUnstarGlobalSegment": "This is a global segment. Only super users can unstar global segments.", + "CanEditGlobalSegment": "This is a global segment. Any changes will apply across all websites.", + "CanStarGlobalSegment": "This is a global segment. Adding to Starred will apply across all websites.", + "CanUnstarGlobalSegment": "This is a global segment. Removing from Starred will apply across all websites.", + "CanNotEditSiteSegment": "You can only edit the segments you created yourself.", + "CanNotStarSiteSegment": "You can only add to Starred the segments you created yourself.", + "CanNotUnstarSiteSegment": "You can only remove from Starred the segments you created yourself.", + "CanEditSiteSegment": "Edit the segment for this website.", + "CanStarSiteSegment": "Add to Starred segments for this website.", + "CanUnstarSiteSegment": "Remove from Starred segments for this website.", "ChangeInX": "Change in %1$s", "ChangePassword": "Change password", "ChangeTagCloudView": "Please note, that you can view the report in other ways than as a tag cloud. Use the controls at the bottom of the report to do so.", @@ -463,6 +475,9 @@ "SmtpServerAddress": "SMTP server address", "SmtpUsername": "SMTP username", "Source": "Source", + "Star": "Star", + "StarredBy": "Starred by", + "StarredByYou": "Starred by you", "StatisticsAreNotRecorded": "Matomo Visitor Tracking is currently disabled! Re-enable tracking by setting record_statistics = 1 in your config/config.ini.php file.", "Subtotal": "Subtotal", "Summary": "Summary", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a351575671f..245feecc6be 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4658,11 +4658,6 @@ parameters: count: 1 path: plugins/ScheduledReports/ScheduledReports.php - - - message: "#^Negated boolean expression is always true\\.$#" - count: 1 - path: plugins/SegmentEditor/API.php - - message: "#^Parameter \\#2 \\$idSites of class Piwik\\\\Segment constructor expects array, int\\|null given\\.$#" count: 1 diff --git a/plugins/CoreHome/vue/dist/CoreHome.umd.js b/plugins/CoreHome/vue/dist/CoreHome.umd.js index a8e7fb5910b..79e899c768c 100644 --- a/plugins/CoreHome/vue/dist/CoreHome.umd.js +++ b/plugins/CoreHome/vue/dist/CoreHome.umd.js @@ -3610,6 +3610,20 @@ class Comparisons_store_ComparisonsStore { } this.updateQueryParamsFromComparisons(newComparisons, this.periodComparisons.value, extraParams); } + removeSegmentComparisonByDefinition(segmentDefinition) { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + let segmentIndex = null; + this.getSegmentComparisons().forEach((segment, index) => { + if (segment && segment.params && segment.params.segment === segmentDefinition) { + segmentIndex = index; + } + }); + if (segmentIndex !== null) { + this.removeSegmentComparison(segmentIndex); + } + } addSegmentComparison(params) { if (!this.isComparisonEnabled()) { throw new Error('Comparison disabled.'); diff --git a/plugins/CoreHome/vue/dist/CoreHome.umd.min.js b/plugins/CoreHome/vue/dist/CoreHome.umd.min.js index bec11ccfdec..e7efebabcc2 100644 --- a/plugins/CoreHome/vue/dist/CoreHome.umd.min.js +++ b/plugins/CoreHome/vue/dist/CoreHome.umd.min.js @@ -224,7 +224,7 @@ function $e(e,t,o){const i=t.value.isMouseDown&&t.value.hasScrolled;t.value.isMo * * @link https://matomo.org * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */const yo=8,wo=3;function ko(e){return e?Array.isArray(e)?e:[e]:[]}class So{constructor(){jo(this,"privateState",Object(E["reactive"])({comparisonsDisabledFor:[]})),jo(this,"state",Object(E["readonly"])(this.privateState)),jo(this,"colors",{}),jo(this,"segmentComparisons",Object(E["computed"])(()=>this.parseSegmentComparisons())),jo(this,"periodComparisons",Object(E["computed"])(()=>this.parsePeriodComparisons())),jo(this,"isEnabled",Object(E["computed"])(()=>this.checkEnabledForCurrentPage())),"complete"===document.readyState||"interactive"===document.readyState?this.loadComparisonsDisabledFor():document.addEventListener("DOMContentLoaded",()=>{this.loadComparisonsDisabledFor()}),$(()=>{this.colors=this.getAllSeriesColors()}),Object(E["watch"])(()=>this.getComparisons(),()=>x.postEvent("piwikComparisonsChanged"),{deep:!0})}getComparisons(){return this.getSegmentComparisons().concat(this.getPeriodComparisons())}isComparing(){return this.isComparisonEnabled()&&(this.segmentComparisons.value.length>1||this.periodComparisons.value.length>1)}isComparingPeriods(){return this.getPeriodComparisons().length>1}getSegmentComparisons(){return this.isComparisonEnabled()?this.segmentComparisons.value:[]}getPeriodComparisons(){return this.isComparisonEnabled()?this.periodComparisons.value:[]}getSeriesColor(e,t,o=0){const i=this.getComparisonSeriesIndex(t.index,e.index)%yo;if(0===o)return this.colors["series"+i];const n=o%wo;return this.colors[`series${i}-shade${n}`]}getSeriesColorName(e,t){let o="series"+e%yo;return t>0&&(o+="-shade"+t%wo),o}isComparisonEnabled(){return this.isEnabled.value}getIndividualComparisonRowIndices(e){const t=this.getSegmentComparisons().length,o=e%t,i=Math.floor(e/t);return{segmentIndex:o,periodIndex:i}}getComparisonSeriesIndex(e,t){const o=this.getSegmentComparisons().length;return e*o+t}getAllComparisonSeries(){const e=[];let t=0;return this.getPeriodComparisons().forEach(o=>{this.getSegmentComparisons().forEach(i=>{e.push({index:t,params:Object.assign(Object.assign({},i.params),o.params),color:this.colors["series"+t]}),t+=1})}),e}removeSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=[...this.segmentComparisons.value];t.splice(e,1);const o={};0===e&&(o.segment=t[0].params.segment),this.updateQueryParamsFromComparisons(t,this.periodComparisons.value,o)}addSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=this.segmentComparisons.value.concat([{params:e,index:-1,title:""}]);this.updateQueryParamsFromComparisons(t,this.periodComparisons.value)}updateQueryParamsFromComparisons(e,t,o={}){const i={},n={};let a=!1,r=!1;e.forEach(e=>{a?i[e.params.segment]=!0:a=!0}),t.forEach(e=>{r?n[`${e.params.period}|${e.params.date}`]=!0:r=!0});const s=[],l=[];Object.keys(n).forEach(e=>{const t=e.split("|");s.push(t[0]),l.push(t[1])});const c={compareSegments:Object.keys(i),comparePeriods:s,compareDates:l},d=x.helper.isReportingPage()?_.hashParsed.value:_.urlParsed.value;_.updateLocation(Object.assign(Object.assign(Object.assign({},d),c),o))}getAllSeriesColors(){const{ColorManager:e}=x;if(!e)return[];const t=[];for(let o=0;o{this.privateState.comparisonsDisabledFor=e})}parseSegmentComparisons(){const{availableSegments:e}=Oo.state,t=[...ko(_.parsed.value.compareSegments)];t.unshift(_.parsed.value.segment||"");const o=[];return t.forEach((t,i)=>{let n;e.forEach(e=>{e.definition!==t&&e.definition!==decodeURIComponent(t)&&decodeURIComponent(e.definition)!==t||(n=e)});let r=n?n.name:a("General_Unknown");""===t.trim()&&(r=a("SegmentEditor_DefaultAllVisits")),o.push({params:{segment:t},title:x.helper.htmlDecode(r),index:i})}),o}parsePeriodComparisons(){const e=[...ko(_.parsed.value.comparePeriods)],t=[...ko(_.parsed.value.compareDates)];e.unshift(_.parsed.value.period),t.unshift(_.parsed.value.date);const o=[];for(let n=0;nCo.isComparing()&&!window.broadcast.isNoDataPage()),t=Object(E["computed"])(()=>Co.getSegmentComparisons()),o=Object(E["computed"])(()=>Co.getPeriodComparisons()),i=Co.getSeriesColor.bind(Co);function n(){const e=window.$(this).attr("title");return e?window.vueSanitize(e.replace(/\n/g,"
")):e}return{isComparing:e,segmentComparisons:t,periodComparisons:o,getSeriesColor:i,transformTooltipContent:n}},methods:{comparisonHasSegment(e){return"undefined"!==typeof e.params.segment},removeSegmentComparison(e){window.$(this.$refs.root).tooltip("destroy"),Co.removeSegmentComparison(e)},getComparisonPeriodType(e){const{period:t}=e.params;if("range"===t)return a("CoreHome_PeriodRange");const o=a(`Intl_Period${t.substring(0,1).toUpperCase()}${t.substring(1)}`);return o.substring(0,1).toUpperCase()+o.substring(1)},getComparisonTooltip(e,t){if(this.comparisonTooltips&&Object.keys(this.comparisonTooltips).length)return(this.comparisonTooltips[t.index]||{})[e.index]},getTitleTooltip(e){return this.htmlentities(e.title)+"
"+this.htmlentities(decodeURIComponent(e.params.segment))},getUrlToSegment(e){const t=Object.assign({},_.hashParsed.value);return delete t.comparePeriods,delete t.compareDates,delete t.compareSegments,t.segment=e,`${window.location.search}#?${_.stringify(t)}`},onComparisonsChanged(){if(this.comparisonTooltips=null,!Co.isComparing())return;const e=Co.getPeriodComparisons(),t=Co.getSegmentComparisons();W.fetch({method:"API.getProcessedReport",apiModule:"VisitsSummary",apiAction:"get",compare:"1",compareSegments:_.getSearchParam("compareSegments"),comparePeriods:_.getSearchParam("comparePeriods"),compareDates:_.getSearchParam("compareDates"),format_metrics:"1"}).then(o=>{this.comparisonTooltips={},e.forEach(e=>{this.comparisonTooltips[e.index]={},t.forEach(t=>{const i=this.generateComparisonTooltip(o,e,t);this.comparisonTooltips[e.index][t.index]=i})})})},generateComparisonTooltip(e,t,o){if(!e.reportData.comparisons)return"";const i=Co.getComparisonSeriesIndex(t.index,0),n=e.reportData.comparisons[i],r=Co.getComparisonSeriesIndex(t.index,o.index),s=e.reportData.comparisons[r],l=e.reportData.comparisons[o.index];let c='
',d=(s.nb_visits/n.nb_visits*100).toFixed(2);return d+="%",c+=a("General_ComparisonCardTooltip1",[`'${this.htmlentities(s.compareSegmentPretty)}'`,s.comparePeriodPretty,d,s.nb_visits.toString(),n.nb_visits.toString()]),t.index>0&&(c+="

",c+=a("General_ComparisonCardTooltip2",[s.nb_visits_change.toString(),this.htmlentities(l.compareSegmentPretty),l.comparePeriodPretty])),c+="
",c},htmlentities(e){return x.helper.htmlEntities(e)}},mounted(){x.on("piwikComparisonsChanged",()=>{this.onComparisonsChanged()}),this.onComparisonsChanged()}});Eo.render=bo;var Do=Eo;const Po={ref:"root",class:"menuDropdown"},To=["title"],Vo=["innerHTML"],No=Object(E["createElementVNode"])("span",{class:"icon-chevron-down reporting-menu-sub-icon"},null,-1),xo={class:"items"},Bo={key:0,class:"search"},Io=["placeholder"],Mo=["title"],Lo=["title"];function Fo(e,t,o,i,n,a){const r=Object(E["resolveDirective"])("focus-if"),s=Object(E["resolveDirective"])("focus-anywhere-but-here");return Object(E["withDirectives"])((Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Po,[Object(E["createElementVNode"])("span",{class:"title",onClick:t[0]||(t[0]=t=>e.showItems=!e.showItems),title:e.tooltip},[Object(E["createElementVNode"])("span",{class:"title-label",innerHTML:e.$sanitize(this.actualMenuTitle)},null,8,Vo),No],8,To),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",xo,[e.showSearch&&e.showItems?(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Bo,[Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text","onUpdate:modelValue":t[1]||(t[1]=t=>e.searchTerm=t),onKeydown:t[2]||(t[2]=t=>e.onSearchTermKeydown(t)),placeholder:e.translate("General_Search")},null,40,Io),[[E["vModelText"],e.searchTerm],[r,{focused:e.showItems}]]),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",{class:"search_ico icon-search",title:e.translate("General_Search")},null,8,Mo),[[E["vShow"],!e.searchTerm]]),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",{onClick:t[3]||(t[3]=t=>{e.searchTerm="",e.searchItems("")}),class:"reset icon-close",title:e.translate("General_Clear")},null,8,Lo),[[E["vShow"],e.searchTerm]])])):Object(E["createCommentVNode"])("",!0),Object(E["createElementVNode"])("div",{onClick:t[4]||(t[4]=t=>e.selectItem(t))},[Object(E["renderSlot"])(e.$slots,"default")])],512),[[E["vShow"],e.showItems]])])),[[s,{blur:e.lostFocus}]])}const{$:Ao}=window;var _o=Object(E["defineComponent"])({props:{menuTitle:String,tooltip:String,showSearch:Boolean,menuTitleChangeOnClick:Boolean},directives:{FocusAnywhereButHere:Ge,FocusIf:Je},emits:["afterSelect"],watch:{menuTitle(){this.actualMenuTitle=this.menuTitle}},data(){return{showItems:!1,searchTerm:"",actualMenuTitle:this.menuTitle}},methods:{lostFocus(){this.showItems=!1},selectItem(e){const t=e.target.classList;!t.contains("item")||t.contains("disabled")||t.contains("separator")||(this.menuTitleChangeOnClick&&(this.actualMenuTitle=(e.target.textContent||"").replace(/[\u0000-\u2666]/g,e=>`&#${e.charCodeAt(0)};`)),this.showItems=!1,Ao(this.$slots.default()[0].el).find(".item").removeClass("active"),t.add("active"),this.$emit("afterSelect",e.target))},onSearchTermKeydown(){setTimeout(()=>{this.searchItems(this.searchTerm)})},searchItems(e){const t=e.toLowerCase();Ao(this.$refs.root).find(".item").each((e,o)=>{const i=Ao(o);-1===i.text().toLowerCase().indexOf(t)?i.hide():i.show()})}}});_o.render=Fo;var Ro=_o;const Ho={ref:"root"};function $o(e,t,o,i,n,a){return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Ho,null,512)}const Uo=1,{$:qo}=window;var Wo=Object(E["defineComponent"])({props:{selectedDateStart:Date,selectedDateEnd:Date,highlightedDateStart:Date,highlightedDateEnd:Date,viewDate:[String,Date],stepMonths:Number,disableMonthDropdown:Boolean,options:Object},emits:["cellHover","cellHoverLeave","dateSelect"],setup(e,t){const o=Object(E["ref"])(null);function i(t,o){const i=t.children("a");if(e.selectedDateStart&&e.selectedDateEnd&&o>=e.selectedDateStart&&o<=e.selectedDateEnd?t.addClass("ui-datepicker-current-period"):t.removeClass("ui-datepicker-current-period"),e.highlightedDateStart&&e.highlightedDateEnd&&o>=e.highlightedDateStart&&o<=e.highlightedDateEnd){const e=i.length?i:t;e.addClass("ui-state-hover")}else t.removeClass("ui-state-hover"),i.removeClass("ui-state-hover")}function n(e,t,o){if(e.hasClass("ui-datepicker-other-month"))return a(e,t,o);const i=parseInt(e.children("a,span").text(),10);return new Date(o,t,i)}function a(e,t,o){let i;const a=e.parent(),r=a.children("td");if(a.is(":first-child")){const s=a.children("td:not(.ui-datepicker-other-month)").first();return i=n(s,t,o),i.setDate(r.index(e)-r.index(s)+1),i}const s=a.children("td:not(.ui-datepicker-other-month)").last();return i=n(s,t,o),i.setDate(i.getDate()+r.index(e)-r.index(s)),i}function r(){const e=qo(o.value),t=e.find("td[data-month]"),i=parseInt(t.attr("data-month"),10),n=parseInt(t.attr("data-year"),10);return[i,n]}function s(){const e=qo(o.value),t=e.find(".ui-datepicker-calendar"),a=r(),s=t.find("td"),l=s.first(),c=n(l,a[0],a[1]);s.each((function(){i(qo(this),c),c.setDate(c.getDate()+1)}))}function l(){if(!e.viewDate)return!1;let t;if("string"===typeof e.viewDate)try{t=m(e.viewDate)}catch(a){return!1}else t=e.viewDate;const i=qo(o.value),n=r();return(n[0]!==t.getMonth()||n[1]!==t.getFullYear())&&(i.datepicker("setDate",t),!0)}function c(){const e=qo(o.value);e.find("td[data-event]").off("click"),e.find(".ui-state-active").removeClass("ui-state-active"),e.find(".ui-datepicker-current-day").removeClass("ui-datepicker-current-day"),e.find(".ui-datepicker-prev,.ui-datepicker-next").attr("href","")}function d(){const t=qo(o.value),i=e.stepMonths||Uo;if(t.datepicker("option","stepMonths")===i)return!1;const n=qo(".ui-datepicker-month",t).val(),a=qo(".ui-datepicker-year",t).val();return t.datepicker("option","stepMonths",i).datepicker("setDate",new Date(a,n)),c(),!0}function u(){const t=qo(o.value),i=t.find(".ui-datepicker-month")[0];i&&(i.disabled=e.disableMonthDropdown)}function p(){if(!qo(this).hasClass("ui-state-hover"))return;const e=qo(this).parent(),t=e.parent();e.is(":first-child")?t.find("a").first().click():t.find("a").last().click()}function h(){u(),s()}return Object(E["watch"])(()=>Object.assign({},e),(e,t)=>{let o=!1;[e=>e.selectedDateStart,e=>e.selectedDateEnd,e=>e.highlightedDateStart,e=>e.highlightedDateEnd].forEach(i=>{if(o)return;const n=i(e),a=i(t);!n&&a&&(o=!0),n&&!a&&(o=!0),n&&a&&n.getTime()!==a.getTime()&&(o=!0)}),e.viewDate!==t.viewDate&&l()&&(o=!0),e.stepMonths!==t.stepMonths&&d(),e.disableMonthDropdown!==t.disableMonthDropdown&&u(),o&&s()}),Object(E["onMounted"])(()=>{const i=qo(o.value),a=e.options||{},m=Object.assign(Object.assign(Object.assign({},x.getBaseDatePickerOptions()),a),{},{onChangeMonthYear:()=>{setTimeout(()=>{c()})}});i.datepicker(m),i.on("mouseover","tbody td a",e=>{e.originalEvent&&s()}),i.on("mouseenter","tbody td",(function(){const e=r(),o=qo(this),i=n(o,e[0],e[1]);t.emit("cellHover",{date:i,$cell:o})})),i.on("mouseout","tbody td a",()=>{s()}),i.on("mouseleave","table",()=>t.emit("cellHoverLeave")).on("mouseenter","thead",()=>t.emit("cellHoverLeave")),i.on("click","tbody td.ui-datepicker-other-month",p),i.on("click",e=>{e.preventDefault();const t=qo(e.target).closest("a");(t.is(".ui-datepicker-next")||t.is(".ui-datepicker-prev"))&&h()}),i.on("click","td[data-month]",e=>{const o=qo(e.target).closest("td"),i=parseInt(o.attr("data-month"),10),n=parseInt(o.attr("data-year"),10),a=parseInt(o.children("a,span").text(),10);t.emit("dateSelect",{date:new Date(n,i,a)})});const g=d();l(),u(),g||c(),s()}),{root:o}}});Wo.render=$o;var zo=Wo;const Go={class:"dateRangePicker"},Yo={id:"calendarRangeFrom"},Jo={id:"calendarRangeTo"};function Ko(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("DatePicker");return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Go,[Object(E["createElementVNode"])("div",Yo,[Object(E["createElementVNode"])("h6",null,[Object(E["createTextVNode"])(Object(E["toDisplayString"])(e.translate("General_DateRangeFrom"))+" ",1),Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text",id:"inputCalendarFrom",name:"inputCalendarFrom",class:"browser-default","onUpdate:modelValue":t[0]||(t[0]=t=>e.startDateText=t),onKeydown:t[1]||(t[1]=t=>e.onRangeInputChanged("from",t)),onKeyup:t[2]||(t[2]=t=>e.handleEnterPress(t))},null,544),[[E["vModelText"],e.startDateText]])]),Object(E["createVNode"])(r,{id:"calendarFrom","view-date":e.startDate,"selected-date-start":e.fromPickerSelectedDates[0],"selected-date-end":e.fromPickerSelectedDates[1],"highlighted-date-start":e.fromPickerHighlightedDates[0],"highlighted-date-end":e.fromPickerHighlightedDates[1],onDateSelect:t[3]||(t[3]=t=>e.setStartRangeDate(t.date)),onCellHover:t[4]||(t[4]=t=>e.fromPickerHighlightedDates=e.getNewHighlightedDates(t.date,t.$cell)),onCellHoverLeave:t[5]||(t[5]=t=>e.fromPickerHighlightedDates=[null,null])},null,8,["view-date","selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end"])]),Object(E["createElementVNode"])("div",Jo,[Object(E["createElementVNode"])("h6",null,[Object(E["createTextVNode"])(Object(E["toDisplayString"])(e.translate("General_DateRangeTo"))+" ",1),Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text",id:"inputCalendarTo",name:"inputCalendarTo",class:"browser-default","onUpdate:modelValue":t[6]||(t[6]=t=>e.endDateText=t),onKeydown:t[7]||(t[7]=t=>e.onRangeInputChanged("to",t)),onKeyup:t[8]||(t[8]=t=>e.handleEnterPress(t))},null,544),[[E["vModelText"],e.endDateText]])]),Object(E["createVNode"])(r,{id:"calendarTo","view-date":e.endDate,"selected-date-start":e.toPickerSelectedDates[0],"selected-date-end":e.toPickerSelectedDates[1],"highlighted-date-start":e.toPickerHighlightedDates[0],"highlighted-date-end":e.toPickerHighlightedDates[1],onDateSelect:t[9]||(t[9]=t=>e.setEndRangeDate(t.date)),onCellHover:t[10]||(t[10]=t=>e.toPickerHighlightedDates=e.getNewHighlightedDates(t.date,t.$cell)),onCellHoverLeave:t[11]||(t[11]=t=>e.toPickerHighlightedDates=[null,null])},null,8,["view-date","selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end"])])])}const Qo="YYYY-MM-DD";var Xo=Object(E["defineComponent"])({props:{startDate:String,endDate:String},components:{DatePicker:zo},data(){let e=null;try{this.startDate&&(e=m(this.startDate))}catch(o){}let t=null;try{this.endDate&&(t=m(this.endDate))}catch(o){}return{fromPickerSelectedDates:[e,e],toPickerSelectedDates:[t,t],fromPickerHighlightedDates:[null,null],toPickerHighlightedDates:[null,null],startDateText:this.startDate,endDateText:this.endDate,startDateInvalid:!1,endDateInvalid:!1}},emits:["rangeChange","submit"],watch:{startDate(){this.startDateText=this.startDate,this.setStartRangeDateFromStr(this.startDate)},endDate(){this.endDateText=this.endDate,this.setEndRangeDateFromStr(this.endDate)}},mounted(){this.rangeChanged()},methods:{setStartRangeDate(e){this.fromPickerSelectedDates=[e,e],this.rangeChanged()},setEndRangeDate(e){this.toPickerSelectedDates=[e,e],this.rangeChanged()},onRangeInputChanged(e,t){setTimeout(()=>{"from"===e?this.setStartRangeDateFromStr(t.target.value):this.setEndRangeDateFromStr(t.target.value)})},getNewHighlightedDates(e,t){return t.hasClass("ui-datepicker-unselectable")?null:[e,e]},handleEnterPress(e){13===e.keyCode&&this.$emit("submit",{start:this.startDate,end:this.endDate})},setStartRangeDateFromStr(e){this.startDateInvalid=!0;let t=null;try{e&&e.length===Qo.length&&(t=m(e))}catch(o){}t&&(this.fromPickerSelectedDates=[t,t],this.startDateInvalid=!1,this.rangeChanged())},setEndRangeDateFromStr(e){this.endDateInvalid=!0;let t=null;try{e&&e.length===Qo.length&&(t=m(e))}catch(o){}t&&(this.toPickerSelectedDates=[t,t],this.endDateInvalid=!1,this.rangeChanged())},rangeChanged(){this.$emit("rangeChange",{start:this.fromPickerSelectedDates[0]?d(this.fromPickerSelectedDates[0]):null,end:this.toPickerSelectedDates[0]?d(this.toPickerSelectedDates[0]):null})}}});Xo.render=Ko;var Zo=Xo;function ei(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("DatePicker");return Object(E["openBlock"])(),Object(E["createBlock"])(r,{"selected-date-start":e.selectedDates[0],"selected-date-end":e.selectedDates[1],"highlighted-date-start":e.highlightedDates[0],"highlighted-date-end":e.highlightedDates[1],"view-date":e.viewDate,"step-months":"year"===e.period?12:1,"disable-month-dropdown":"year"===e.period,onCellHover:t[0]||(t[0]=t=>e.onHoverNormalCell(t.date,t.$cell)),onCellHoverLeave:t[1]||(t[1]=t=>e.onHoverLeaveNormalCells()),onDateSelect:t[2]||(t[2]=t=>e.onDateSelected(t.date))},null,8,["selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end","view-date","step-months","disable-month-dropdown"])}const ti=new Date(x.minDateYear,x.minDateMonth-1,x.minDateDay),oi=new Date(x.maxDateYear,x.maxDateMonth-1,x.maxDateDay);var ii=Object(E["defineComponent"])({props:{period:{type:String,required:!0},date:[String,Date]},components:{DatePicker:zo},emits:["select"],setup(e,t){const o=Object(E["ref"])(e.date),i=Object(E["ref"])([null,null]),n=Object(E["ref"])([null,null]);function a(t){const o=c.get(e.period).parse(t).getDateRange();return o[0]=tio[1]?o[1]:oi,o}function r(t,o){const i=toi,r=o.hasClass("ui-datepicker-other-month")&&("month"===e.period||"day"===e.period);n.value=i||r?[null,null]:a(t)}function s(){n.value=[null,null]}function l(e){t.emit("select",{date:e})}function d(){if(!e.period||!e.date)return i.value=[null,null],void(o.value=null);i.value=a(e.date),o.value=m(e.date)}return Object(E["watch"])(e,d),d(),{selectedDates:i,highlightedDates:n,viewDate:o,onHoverNormalCell:r,onHoverLeaveNormalCells:s,onDateSelected:l}}});ii.render=ei;var ni=ii;const ai={key:0},ri=["data-notification-instance-id"],si={key:1},li={class:"notification-body"},ci=["innerHTML"],di={key:1};function ui(e,t,o,i,n,a){return Object(E["openBlock"])(),Object(E["createBlock"])(E["Transition"],{name:"toast"===e.type?"slow-fade-out":void 0,onAfterLeave:t[1]||(t[1]=t=>e.toastClosed())},{default:Object(E["withCtx"])(()=>[e.deleted?Object(E["createCommentVNode"])("",!0):(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",ai,[Object(E["createVNode"])(E["Transition"],{name:"toast"===e.type?"toast-slide-up":void 0,appear:""},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",null,[Object(E["createVNode"])(E["Transition"],{name:e.animate?"fade-in":void 0,appear:""},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",{class:Object(E["normalizeClass"])(["notification system",e.cssClasses]),style:Object(E["normalizeStyle"])(e.style),ref:"root","data-notification-instance-id":e.notificationInstanceId},[e.canClose?(Object(E["openBlock"])(),Object(E["createElementBlock"])("button",{key:0,type:"button",class:"close","data-dismiss":"alert",onClick:t[0]||(t[0]=t=>e.closeNotification(t))}," × ")):Object(E["createCommentVNode"])("",!0),e.title?(Object(E["openBlock"])(),Object(E["createElementBlock"])("strong",si,Object(E["toDisplayString"])(e.title),1)):Object(E["createCommentVNode"])("",!0),Object(E["createElementVNode"])("div",li,[e.message?(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",{key:0,innerHTML:e.$sanitize(e.message)},null,8,ci)):Object(E["createCommentVNode"])("",!0),e.message?Object(E["createCommentVNode"])("",!0):(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",di,[Object(E["renderSlot"])(e.$slots,"default")]))])],14,ri)]),_:3},8,["name"])])]),_:3},8,["name"])]))]),_:3},8,["name"])}const{$:mi}=window;var pi=Object(E["defineComponent"])({props:{notificationId:String,notificationInstanceId:String,title:String,context:String,type:String,noclear:Boolean,toastLength:{type:Number,default:12e3},style:[String,Object],animate:Boolean,message:String,cssClass:String},computed:{cssClasses(){const e={};return this.context&&(e["notification-"+this.context]=!0),this.cssClass&&(e[this.cssClass]=!0),e},canClose(){return"persistent"===this.type||!this.noclear}},emits:["closed"],data(){return{deleted:!1}},mounted(){const e=()=>{setTimeout(()=>{this.deleted=!0},this.toastLength)};"toast"===this.type&&e(),this.style&&mi(this.$refs.root).css(this.style)},methods:{toastClosed(){Object(E["nextTick"])(()=>{this.$emit("closed")})},closeNotification(e){this.canClose&&e&&e.target&&(this.deleted=!0,Object(E["nextTick"])(()=>{this.$emit("closed")})),this.markNotificationAsRead()},markNotificationAsRead(){this.notificationId&&W.post({module:"CoreHome",action:"markNotificationAsRead"},{notificationId:this.notificationId},{withTokenInUrl:!0})}}});pi.render=ui;var hi=pi;const gi={class:"notification-group"},bi=["innerHTML"];function fi(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("Notification");return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",gi,[(Object(E["openBlock"])(!0),Object(E["createElementBlock"])(E["Fragment"],null,Object(E["renderList"])(e.notifications,(t,o)=>(Object(E["openBlock"])(),Object(E["createBlock"])(r,{key:t.id||"no-id-"+o,"notification-id":t.id,title:t.title,context:t.context,type:t.type,noclear:t.noclear,"toast-length":t.toastLength,style:Object(E["normalizeStyle"])(t.style),animate:t.animate,message:t.message,"notification-instance-id":t.notificationInstanceId,"css-class":t.class,onClosed:o=>e.removeNotification(t.id)},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",{innerHTML:e.$sanitize(t.message)},null,8,bi)]),_:2},1032,["notification-id","title","context","type","noclear","toast-length","style","animate","message","notification-instance-id","css-class","onClosed"]))),128))])}function vi(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e} + */const yo=8,wo=3;function ko(e){return e?Array.isArray(e)?e:[e]:[]}class So{constructor(){jo(this,"privateState",Object(E["reactive"])({comparisonsDisabledFor:[]})),jo(this,"state",Object(E["readonly"])(this.privateState)),jo(this,"colors",{}),jo(this,"segmentComparisons",Object(E["computed"])(()=>this.parseSegmentComparisons())),jo(this,"periodComparisons",Object(E["computed"])(()=>this.parsePeriodComparisons())),jo(this,"isEnabled",Object(E["computed"])(()=>this.checkEnabledForCurrentPage())),"complete"===document.readyState||"interactive"===document.readyState?this.loadComparisonsDisabledFor():document.addEventListener("DOMContentLoaded",()=>{this.loadComparisonsDisabledFor()}),$(()=>{this.colors=this.getAllSeriesColors()}),Object(E["watch"])(()=>this.getComparisons(),()=>x.postEvent("piwikComparisonsChanged"),{deep:!0})}getComparisons(){return this.getSegmentComparisons().concat(this.getPeriodComparisons())}isComparing(){return this.isComparisonEnabled()&&(this.segmentComparisons.value.length>1||this.periodComparisons.value.length>1)}isComparingPeriods(){return this.getPeriodComparisons().length>1}getSegmentComparisons(){return this.isComparisonEnabled()?this.segmentComparisons.value:[]}getPeriodComparisons(){return this.isComparisonEnabled()?this.periodComparisons.value:[]}getSeriesColor(e,t,o=0){const i=this.getComparisonSeriesIndex(t.index,e.index)%yo;if(0===o)return this.colors["series"+i];const n=o%wo;return this.colors[`series${i}-shade${n}`]}getSeriesColorName(e,t){let o="series"+e%yo;return t>0&&(o+="-shade"+t%wo),o}isComparisonEnabled(){return this.isEnabled.value}getIndividualComparisonRowIndices(e){const t=this.getSegmentComparisons().length,o=e%t,i=Math.floor(e/t);return{segmentIndex:o,periodIndex:i}}getComparisonSeriesIndex(e,t){const o=this.getSegmentComparisons().length;return e*o+t}getAllComparisonSeries(){const e=[];let t=0;return this.getPeriodComparisons().forEach(o=>{this.getSegmentComparisons().forEach(i=>{e.push({index:t,params:Object.assign(Object.assign({},i.params),o.params),color:this.colors["series"+t]}),t+=1})}),e}removeSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=[...this.segmentComparisons.value];t.splice(e,1);const o={};0===e&&(o.segment=t[0].params.segment),this.updateQueryParamsFromComparisons(t,this.periodComparisons.value,o)}removeSegmentComparisonByDefinition(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");let t=null;this.getSegmentComparisons().forEach((o,i)=>{o&&o.params&&o.params.segment===e&&(t=i)}),null!==t&&this.removeSegmentComparison(t)}addSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=this.segmentComparisons.value.concat([{params:e,index:-1,title:""}]);this.updateQueryParamsFromComparisons(t,this.periodComparisons.value)}updateQueryParamsFromComparisons(e,t,o={}){const i={},n={};let a=!1,r=!1;e.forEach(e=>{a?i[e.params.segment]=!0:a=!0}),t.forEach(e=>{r?n[`${e.params.period}|${e.params.date}`]=!0:r=!0});const s=[],l=[];Object.keys(n).forEach(e=>{const t=e.split("|");s.push(t[0]),l.push(t[1])});const c={compareSegments:Object.keys(i),comparePeriods:s,compareDates:l},d=x.helper.isReportingPage()?_.hashParsed.value:_.urlParsed.value;_.updateLocation(Object.assign(Object.assign(Object.assign({},d),c),o))}getAllSeriesColors(){const{ColorManager:e}=x;if(!e)return[];const t=[];for(let o=0;o{this.privateState.comparisonsDisabledFor=e})}parseSegmentComparisons(){const{availableSegments:e}=Oo.state,t=[...ko(_.parsed.value.compareSegments)];t.unshift(_.parsed.value.segment||"");const o=[];return t.forEach((t,i)=>{let n;e.forEach(e=>{e.definition!==t&&e.definition!==decodeURIComponent(t)&&decodeURIComponent(e.definition)!==t||(n=e)});let r=n?n.name:a("General_Unknown");""===t.trim()&&(r=a("SegmentEditor_DefaultAllVisits")),o.push({params:{segment:t},title:x.helper.htmlDecode(r),index:i})}),o}parsePeriodComparisons(){const e=[...ko(_.parsed.value.comparePeriods)],t=[...ko(_.parsed.value.compareDates)];e.unshift(_.parsed.value.period),t.unshift(_.parsed.value.date);const o=[];for(let n=0;nCo.isComparing()&&!window.broadcast.isNoDataPage()),t=Object(E["computed"])(()=>Co.getSegmentComparisons()),o=Object(E["computed"])(()=>Co.getPeriodComparisons()),i=Co.getSeriesColor.bind(Co);function n(){const e=window.$(this).attr("title");return e?window.vueSanitize(e.replace(/\n/g,"
")):e}return{isComparing:e,segmentComparisons:t,periodComparisons:o,getSeriesColor:i,transformTooltipContent:n}},methods:{comparisonHasSegment(e){return"undefined"!==typeof e.params.segment},removeSegmentComparison(e){window.$(this.$refs.root).tooltip("destroy"),Co.removeSegmentComparison(e)},getComparisonPeriodType(e){const{period:t}=e.params;if("range"===t)return a("CoreHome_PeriodRange");const o=a(`Intl_Period${t.substring(0,1).toUpperCase()}${t.substring(1)}`);return o.substring(0,1).toUpperCase()+o.substring(1)},getComparisonTooltip(e,t){if(this.comparisonTooltips&&Object.keys(this.comparisonTooltips).length)return(this.comparisonTooltips[t.index]||{})[e.index]},getTitleTooltip(e){return this.htmlentities(e.title)+"
"+this.htmlentities(decodeURIComponent(e.params.segment))},getUrlToSegment(e){const t=Object.assign({},_.hashParsed.value);return delete t.comparePeriods,delete t.compareDates,delete t.compareSegments,t.segment=e,`${window.location.search}#?${_.stringify(t)}`},onComparisonsChanged(){if(this.comparisonTooltips=null,!Co.isComparing())return;const e=Co.getPeriodComparisons(),t=Co.getSegmentComparisons();W.fetch({method:"API.getProcessedReport",apiModule:"VisitsSummary",apiAction:"get",compare:"1",compareSegments:_.getSearchParam("compareSegments"),comparePeriods:_.getSearchParam("comparePeriods"),compareDates:_.getSearchParam("compareDates"),format_metrics:"1"}).then(o=>{this.comparisonTooltips={},e.forEach(e=>{this.comparisonTooltips[e.index]={},t.forEach(t=>{const i=this.generateComparisonTooltip(o,e,t);this.comparisonTooltips[e.index][t.index]=i})})})},generateComparisonTooltip(e,t,o){if(!e.reportData.comparisons)return"";const i=Co.getComparisonSeriesIndex(t.index,0),n=e.reportData.comparisons[i],r=Co.getComparisonSeriesIndex(t.index,o.index),s=e.reportData.comparisons[r],l=e.reportData.comparisons[o.index];let c='
',d=(s.nb_visits/n.nb_visits*100).toFixed(2);return d+="%",c+=a("General_ComparisonCardTooltip1",[`'${this.htmlentities(s.compareSegmentPretty)}'`,s.comparePeriodPretty,d,s.nb_visits.toString(),n.nb_visits.toString()]),t.index>0&&(c+="

",c+=a("General_ComparisonCardTooltip2",[s.nb_visits_change.toString(),this.htmlentities(l.compareSegmentPretty),l.comparePeriodPretty])),c+="
",c},htmlentities(e){return x.helper.htmlEntities(e)}},mounted(){x.on("piwikComparisonsChanged",()=>{this.onComparisonsChanged()}),this.onComparisonsChanged()}});Eo.render=bo;var Do=Eo;const Po={ref:"root",class:"menuDropdown"},To=["title"],Vo=["innerHTML"],No=Object(E["createElementVNode"])("span",{class:"icon-chevron-down reporting-menu-sub-icon"},null,-1),xo={class:"items"},Bo={key:0,class:"search"},Io=["placeholder"],Mo=["title"],Lo=["title"];function Fo(e,t,o,i,n,a){const r=Object(E["resolveDirective"])("focus-if"),s=Object(E["resolveDirective"])("focus-anywhere-but-here");return Object(E["withDirectives"])((Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Po,[Object(E["createElementVNode"])("span",{class:"title",onClick:t[0]||(t[0]=t=>e.showItems=!e.showItems),title:e.tooltip},[Object(E["createElementVNode"])("span",{class:"title-label",innerHTML:e.$sanitize(this.actualMenuTitle)},null,8,Vo),No],8,To),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",xo,[e.showSearch&&e.showItems?(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Bo,[Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text","onUpdate:modelValue":t[1]||(t[1]=t=>e.searchTerm=t),onKeydown:t[2]||(t[2]=t=>e.onSearchTermKeydown(t)),placeholder:e.translate("General_Search")},null,40,Io),[[E["vModelText"],e.searchTerm],[r,{focused:e.showItems}]]),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",{class:"search_ico icon-search",title:e.translate("General_Search")},null,8,Mo),[[E["vShow"],!e.searchTerm]]),Object(E["withDirectives"])(Object(E["createElementVNode"])("div",{onClick:t[3]||(t[3]=t=>{e.searchTerm="",e.searchItems("")}),class:"reset icon-close",title:e.translate("General_Clear")},null,8,Lo),[[E["vShow"],e.searchTerm]])])):Object(E["createCommentVNode"])("",!0),Object(E["createElementVNode"])("div",{onClick:t[4]||(t[4]=t=>e.selectItem(t))},[Object(E["renderSlot"])(e.$slots,"default")])],512),[[E["vShow"],e.showItems]])])),[[s,{blur:e.lostFocus}]])}const{$:Ao}=window;var _o=Object(E["defineComponent"])({props:{menuTitle:String,tooltip:String,showSearch:Boolean,menuTitleChangeOnClick:Boolean},directives:{FocusAnywhereButHere:Ge,FocusIf:Je},emits:["afterSelect"],watch:{menuTitle(){this.actualMenuTitle=this.menuTitle}},data(){return{showItems:!1,searchTerm:"",actualMenuTitle:this.menuTitle}},methods:{lostFocus(){this.showItems=!1},selectItem(e){const t=e.target.classList;!t.contains("item")||t.contains("disabled")||t.contains("separator")||(this.menuTitleChangeOnClick&&(this.actualMenuTitle=(e.target.textContent||"").replace(/[\u0000-\u2666]/g,e=>`&#${e.charCodeAt(0)};`)),this.showItems=!1,Ao(this.$slots.default()[0].el).find(".item").removeClass("active"),t.add("active"),this.$emit("afterSelect",e.target))},onSearchTermKeydown(){setTimeout(()=>{this.searchItems(this.searchTerm)})},searchItems(e){const t=e.toLowerCase();Ao(this.$refs.root).find(".item").each((e,o)=>{const i=Ao(o);-1===i.text().toLowerCase().indexOf(t)?i.hide():i.show()})}}});_o.render=Fo;var Ro=_o;const Ho={ref:"root"};function $o(e,t,o,i,n,a){return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Ho,null,512)}const Uo=1,{$:qo}=window;var Wo=Object(E["defineComponent"])({props:{selectedDateStart:Date,selectedDateEnd:Date,highlightedDateStart:Date,highlightedDateEnd:Date,viewDate:[String,Date],stepMonths:Number,disableMonthDropdown:Boolean,options:Object},emits:["cellHover","cellHoverLeave","dateSelect"],setup(e,t){const o=Object(E["ref"])(null);function i(t,o){const i=t.children("a");if(e.selectedDateStart&&e.selectedDateEnd&&o>=e.selectedDateStart&&o<=e.selectedDateEnd?t.addClass("ui-datepicker-current-period"):t.removeClass("ui-datepicker-current-period"),e.highlightedDateStart&&e.highlightedDateEnd&&o>=e.highlightedDateStart&&o<=e.highlightedDateEnd){const e=i.length?i:t;e.addClass("ui-state-hover")}else t.removeClass("ui-state-hover"),i.removeClass("ui-state-hover")}function n(e,t,o){if(e.hasClass("ui-datepicker-other-month"))return a(e,t,o);const i=parseInt(e.children("a,span").text(),10);return new Date(o,t,i)}function a(e,t,o){let i;const a=e.parent(),r=a.children("td");if(a.is(":first-child")){const s=a.children("td:not(.ui-datepicker-other-month)").first();return i=n(s,t,o),i.setDate(r.index(e)-r.index(s)+1),i}const s=a.children("td:not(.ui-datepicker-other-month)").last();return i=n(s,t,o),i.setDate(i.getDate()+r.index(e)-r.index(s)),i}function r(){const e=qo(o.value),t=e.find("td[data-month]"),i=parseInt(t.attr("data-month"),10),n=parseInt(t.attr("data-year"),10);return[i,n]}function s(){const e=qo(o.value),t=e.find(".ui-datepicker-calendar"),a=r(),s=t.find("td"),l=s.first(),c=n(l,a[0],a[1]);s.each((function(){i(qo(this),c),c.setDate(c.getDate()+1)}))}function l(){if(!e.viewDate)return!1;let t;if("string"===typeof e.viewDate)try{t=m(e.viewDate)}catch(a){return!1}else t=e.viewDate;const i=qo(o.value),n=r();return(n[0]!==t.getMonth()||n[1]!==t.getFullYear())&&(i.datepicker("setDate",t),!0)}function c(){const e=qo(o.value);e.find("td[data-event]").off("click"),e.find(".ui-state-active").removeClass("ui-state-active"),e.find(".ui-datepicker-current-day").removeClass("ui-datepicker-current-day"),e.find(".ui-datepicker-prev,.ui-datepicker-next").attr("href","")}function d(){const t=qo(o.value),i=e.stepMonths||Uo;if(t.datepicker("option","stepMonths")===i)return!1;const n=qo(".ui-datepicker-month",t).val(),a=qo(".ui-datepicker-year",t).val();return t.datepicker("option","stepMonths",i).datepicker("setDate",new Date(a,n)),c(),!0}function u(){const t=qo(o.value),i=t.find(".ui-datepicker-month")[0];i&&(i.disabled=e.disableMonthDropdown)}function p(){if(!qo(this).hasClass("ui-state-hover"))return;const e=qo(this).parent(),t=e.parent();e.is(":first-child")?t.find("a").first().click():t.find("a").last().click()}function h(){u(),s()}return Object(E["watch"])(()=>Object.assign({},e),(e,t)=>{let o=!1;[e=>e.selectedDateStart,e=>e.selectedDateEnd,e=>e.highlightedDateStart,e=>e.highlightedDateEnd].forEach(i=>{if(o)return;const n=i(e),a=i(t);!n&&a&&(o=!0),n&&!a&&(o=!0),n&&a&&n.getTime()!==a.getTime()&&(o=!0)}),e.viewDate!==t.viewDate&&l()&&(o=!0),e.stepMonths!==t.stepMonths&&d(),e.disableMonthDropdown!==t.disableMonthDropdown&&u(),o&&s()}),Object(E["onMounted"])(()=>{const i=qo(o.value),a=e.options||{},m=Object.assign(Object.assign(Object.assign({},x.getBaseDatePickerOptions()),a),{},{onChangeMonthYear:()=>{setTimeout(()=>{c()})}});i.datepicker(m),i.on("mouseover","tbody td a",e=>{e.originalEvent&&s()}),i.on("mouseenter","tbody td",(function(){const e=r(),o=qo(this),i=n(o,e[0],e[1]);t.emit("cellHover",{date:i,$cell:o})})),i.on("mouseout","tbody td a",()=>{s()}),i.on("mouseleave","table",()=>t.emit("cellHoverLeave")).on("mouseenter","thead",()=>t.emit("cellHoverLeave")),i.on("click","tbody td.ui-datepicker-other-month",p),i.on("click",e=>{e.preventDefault();const t=qo(e.target).closest("a");(t.is(".ui-datepicker-next")||t.is(".ui-datepicker-prev"))&&h()}),i.on("click","td[data-month]",e=>{const o=qo(e.target).closest("td"),i=parseInt(o.attr("data-month"),10),n=parseInt(o.attr("data-year"),10),a=parseInt(o.children("a,span").text(),10);t.emit("dateSelect",{date:new Date(n,i,a)})});const g=d();l(),u(),g||c(),s()}),{root:o}}});Wo.render=$o;var zo=Wo;const Go={class:"dateRangePicker"},Yo={id:"calendarRangeFrom"},Jo={id:"calendarRangeTo"};function Ko(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("DatePicker");return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",Go,[Object(E["createElementVNode"])("div",Yo,[Object(E["createElementVNode"])("h6",null,[Object(E["createTextVNode"])(Object(E["toDisplayString"])(e.translate("General_DateRangeFrom"))+" ",1),Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text",id:"inputCalendarFrom",name:"inputCalendarFrom",class:"browser-default","onUpdate:modelValue":t[0]||(t[0]=t=>e.startDateText=t),onKeydown:t[1]||(t[1]=t=>e.onRangeInputChanged("from",t)),onKeyup:t[2]||(t[2]=t=>e.handleEnterPress(t))},null,544),[[E["vModelText"],e.startDateText]])]),Object(E["createVNode"])(r,{id:"calendarFrom","view-date":e.startDate,"selected-date-start":e.fromPickerSelectedDates[0],"selected-date-end":e.fromPickerSelectedDates[1],"highlighted-date-start":e.fromPickerHighlightedDates[0],"highlighted-date-end":e.fromPickerHighlightedDates[1],onDateSelect:t[3]||(t[3]=t=>e.setStartRangeDate(t.date)),onCellHover:t[4]||(t[4]=t=>e.fromPickerHighlightedDates=e.getNewHighlightedDates(t.date,t.$cell)),onCellHoverLeave:t[5]||(t[5]=t=>e.fromPickerHighlightedDates=[null,null])},null,8,["view-date","selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end"])]),Object(E["createElementVNode"])("div",Jo,[Object(E["createElementVNode"])("h6",null,[Object(E["createTextVNode"])(Object(E["toDisplayString"])(e.translate("General_DateRangeTo"))+" ",1),Object(E["withDirectives"])(Object(E["createElementVNode"])("input",{type:"text",id:"inputCalendarTo",name:"inputCalendarTo",class:"browser-default","onUpdate:modelValue":t[6]||(t[6]=t=>e.endDateText=t),onKeydown:t[7]||(t[7]=t=>e.onRangeInputChanged("to",t)),onKeyup:t[8]||(t[8]=t=>e.handleEnterPress(t))},null,544),[[E["vModelText"],e.endDateText]])]),Object(E["createVNode"])(r,{id:"calendarTo","view-date":e.endDate,"selected-date-start":e.toPickerSelectedDates[0],"selected-date-end":e.toPickerSelectedDates[1],"highlighted-date-start":e.toPickerHighlightedDates[0],"highlighted-date-end":e.toPickerHighlightedDates[1],onDateSelect:t[9]||(t[9]=t=>e.setEndRangeDate(t.date)),onCellHover:t[10]||(t[10]=t=>e.toPickerHighlightedDates=e.getNewHighlightedDates(t.date,t.$cell)),onCellHoverLeave:t[11]||(t[11]=t=>e.toPickerHighlightedDates=[null,null])},null,8,["view-date","selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end"])])])}const Qo="YYYY-MM-DD";var Xo=Object(E["defineComponent"])({props:{startDate:String,endDate:String},components:{DatePicker:zo},data(){let e=null;try{this.startDate&&(e=m(this.startDate))}catch(o){}let t=null;try{this.endDate&&(t=m(this.endDate))}catch(o){}return{fromPickerSelectedDates:[e,e],toPickerSelectedDates:[t,t],fromPickerHighlightedDates:[null,null],toPickerHighlightedDates:[null,null],startDateText:this.startDate,endDateText:this.endDate,startDateInvalid:!1,endDateInvalid:!1}},emits:["rangeChange","submit"],watch:{startDate(){this.startDateText=this.startDate,this.setStartRangeDateFromStr(this.startDate)},endDate(){this.endDateText=this.endDate,this.setEndRangeDateFromStr(this.endDate)}},mounted(){this.rangeChanged()},methods:{setStartRangeDate(e){this.fromPickerSelectedDates=[e,e],this.rangeChanged()},setEndRangeDate(e){this.toPickerSelectedDates=[e,e],this.rangeChanged()},onRangeInputChanged(e,t){setTimeout(()=>{"from"===e?this.setStartRangeDateFromStr(t.target.value):this.setEndRangeDateFromStr(t.target.value)})},getNewHighlightedDates(e,t){return t.hasClass("ui-datepicker-unselectable")?null:[e,e]},handleEnterPress(e){13===e.keyCode&&this.$emit("submit",{start:this.startDate,end:this.endDate})},setStartRangeDateFromStr(e){this.startDateInvalid=!0;let t=null;try{e&&e.length===Qo.length&&(t=m(e))}catch(o){}t&&(this.fromPickerSelectedDates=[t,t],this.startDateInvalid=!1,this.rangeChanged())},setEndRangeDateFromStr(e){this.endDateInvalid=!0;let t=null;try{e&&e.length===Qo.length&&(t=m(e))}catch(o){}t&&(this.toPickerSelectedDates=[t,t],this.endDateInvalid=!1,this.rangeChanged())},rangeChanged(){this.$emit("rangeChange",{start:this.fromPickerSelectedDates[0]?d(this.fromPickerSelectedDates[0]):null,end:this.toPickerSelectedDates[0]?d(this.toPickerSelectedDates[0]):null})}}});Xo.render=Ko;var Zo=Xo;function ei(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("DatePicker");return Object(E["openBlock"])(),Object(E["createBlock"])(r,{"selected-date-start":e.selectedDates[0],"selected-date-end":e.selectedDates[1],"highlighted-date-start":e.highlightedDates[0],"highlighted-date-end":e.highlightedDates[1],"view-date":e.viewDate,"step-months":"year"===e.period?12:1,"disable-month-dropdown":"year"===e.period,onCellHover:t[0]||(t[0]=t=>e.onHoverNormalCell(t.date,t.$cell)),onCellHoverLeave:t[1]||(t[1]=t=>e.onHoverLeaveNormalCells()),onDateSelect:t[2]||(t[2]=t=>e.onDateSelected(t.date))},null,8,["selected-date-start","selected-date-end","highlighted-date-start","highlighted-date-end","view-date","step-months","disable-month-dropdown"])}const ti=new Date(x.minDateYear,x.minDateMonth-1,x.minDateDay),oi=new Date(x.maxDateYear,x.maxDateMonth-1,x.maxDateDay);var ii=Object(E["defineComponent"])({props:{period:{type:String,required:!0},date:[String,Date]},components:{DatePicker:zo},emits:["select"],setup(e,t){const o=Object(E["ref"])(e.date),i=Object(E["ref"])([null,null]),n=Object(E["ref"])([null,null]);function a(t){const o=c.get(e.period).parse(t).getDateRange();return o[0]=tio[1]?o[1]:oi,o}function r(t,o){const i=toi,r=o.hasClass("ui-datepicker-other-month")&&("month"===e.period||"day"===e.period);n.value=i||r?[null,null]:a(t)}function s(){n.value=[null,null]}function l(e){t.emit("select",{date:e})}function d(){if(!e.period||!e.date)return i.value=[null,null],void(o.value=null);i.value=a(e.date),o.value=m(e.date)}return Object(E["watch"])(e,d),d(),{selectedDates:i,highlightedDates:n,viewDate:o,onHoverNormalCell:r,onHoverLeaveNormalCells:s,onDateSelected:l}}});ii.render=ei;var ni=ii;const ai={key:0},ri=["data-notification-instance-id"],si={key:1},li={class:"notification-body"},ci=["innerHTML"],di={key:1};function ui(e,t,o,i,n,a){return Object(E["openBlock"])(),Object(E["createBlock"])(E["Transition"],{name:"toast"===e.type?"slow-fade-out":void 0,onAfterLeave:t[1]||(t[1]=t=>e.toastClosed())},{default:Object(E["withCtx"])(()=>[e.deleted?Object(E["createCommentVNode"])("",!0):(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",ai,[Object(E["createVNode"])(E["Transition"],{name:"toast"===e.type?"toast-slide-up":void 0,appear:""},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",null,[Object(E["createVNode"])(E["Transition"],{name:e.animate?"fade-in":void 0,appear:""},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",{class:Object(E["normalizeClass"])(["notification system",e.cssClasses]),style:Object(E["normalizeStyle"])(e.style),ref:"root","data-notification-instance-id":e.notificationInstanceId},[e.canClose?(Object(E["openBlock"])(),Object(E["createElementBlock"])("button",{key:0,type:"button",class:"close","data-dismiss":"alert",onClick:t[0]||(t[0]=t=>e.closeNotification(t))}," × ")):Object(E["createCommentVNode"])("",!0),e.title?(Object(E["openBlock"])(),Object(E["createElementBlock"])("strong",si,Object(E["toDisplayString"])(e.title),1)):Object(E["createCommentVNode"])("",!0),Object(E["createElementVNode"])("div",li,[e.message?(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",{key:0,innerHTML:e.$sanitize(e.message)},null,8,ci)):Object(E["createCommentVNode"])("",!0),e.message?Object(E["createCommentVNode"])("",!0):(Object(E["openBlock"])(),Object(E["createElementBlock"])("div",di,[Object(E["renderSlot"])(e.$slots,"default")]))])],14,ri)]),_:3},8,["name"])])]),_:3},8,["name"])]))]),_:3},8,["name"])}const{$:mi}=window;var pi=Object(E["defineComponent"])({props:{notificationId:String,notificationInstanceId:String,title:String,context:String,type:String,noclear:Boolean,toastLength:{type:Number,default:12e3},style:[String,Object],animate:Boolean,message:String,cssClass:String},computed:{cssClasses(){const e={};return this.context&&(e["notification-"+this.context]=!0),this.cssClass&&(e[this.cssClass]=!0),e},canClose(){return"persistent"===this.type||!this.noclear}},emits:["closed"],data(){return{deleted:!1}},mounted(){const e=()=>{setTimeout(()=>{this.deleted=!0},this.toastLength)};"toast"===this.type&&e(),this.style&&mi(this.$refs.root).css(this.style)},methods:{toastClosed(){Object(E["nextTick"])(()=>{this.$emit("closed")})},closeNotification(e){this.canClose&&e&&e.target&&(this.deleted=!0,Object(E["nextTick"])(()=>{this.$emit("closed")})),this.markNotificationAsRead()},markNotificationAsRead(){this.notificationId&&W.post({module:"CoreHome",action:"markNotificationAsRead"},{notificationId:this.notificationId},{withTokenInUrl:!0})}}});pi.render=ui;var hi=pi;const gi={class:"notification-group"},bi=["innerHTML"];function fi(e,t,o,i,n,a){const r=Object(E["resolveComponent"])("Notification");return Object(E["openBlock"])(),Object(E["createElementBlock"])("div",gi,[(Object(E["openBlock"])(!0),Object(E["createElementBlock"])(E["Fragment"],null,Object(E["renderList"])(e.notifications,(t,o)=>(Object(E["openBlock"])(),Object(E["createBlock"])(r,{key:t.id||"no-id-"+o,"notification-id":t.id,title:t.title,context:t.context,type:t.type,noclear:t.noclear,"toast-length":t.toastLength,style:Object(E["normalizeStyle"])(t.style),animate:t.animate,message:t.message,"notification-instance-id":t.notificationInstanceId,"css-class":t.class,onClosed:o=>e.removeNotification(t.id)},{default:Object(E["withCtx"])(()=>[Object(E["createElementVNode"])("div",{innerHTML:e.$sanitize(t.message)},null,8,bi)]),_:2},1032,["notification-id","title","context","type","noclear","toast-length","style","animate","message","notification-instance-id","css-class","onClosed"]))),128))])}function vi(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e} /*! * Matomo - free/libre analytics platform * diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts index af8176f1062..aecbe1df97e 100644 --- a/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts @@ -216,6 +216,21 @@ export default class ComparisonsStore { ); } + removeSegmentComparisonByDefinition(segmentDefinition: string): void { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + let segmentIndex = null; + this.getSegmentComparisons().forEach((segment: SegmentComparison, index: number) => { + if (segment && segment.params && segment.params.segment === segmentDefinition) { + segmentIndex = index; + } + }); + if (segmentIndex !== null) { + this.removeSegmentComparison(segmentIndex); + } + } + addSegmentComparison(params: { [name: string]: string }): void { if (!this.isComparisonEnabled()) { throw new Error('Comparison disabled.'); diff --git a/plugins/CoreVue/types/index.d.ts b/plugins/CoreVue/types/index.d.ts index a25ce2f6c7e..951112c8e09 100644 --- a/plugins/CoreVue/types/index.d.ts +++ b/plugins/CoreVue/types/index.d.ts @@ -171,6 +171,8 @@ declare global { languageName: string; isPagesComparisonApiDisabled: boolean; // can be set to avoid checks on Api.getPagesComparisonsDisabledFor userLogin?: string; + userCurrentRole: string; + isUserAnonymous: boolean; userHasSomeAdminAccess: boolean; requiresPasswordConfirmation: boolean; disableTrackingMatomoAppLinks: boolean; diff --git a/plugins/Morpheus/templates/_jsGlobalVariables.twig b/plugins/Morpheus/templates/_jsGlobalVariables.twig index ade81671f7c..43f91787235 100644 --- a/plugins/Morpheus/templates/_jsGlobalVariables.twig +++ b/plugins/Morpheus/templates/_jsGlobalVariables.twig @@ -68,6 +68,8 @@ piwik.hasSuperUserAccess = {{ hasSuperUserAccess|default(0)|e('js')}}; piwik.userHasSomeAdminAccess = {{ userHasSomeAdminAccess|json_encode|raw }}; + piwik.userCurrentRole = "{{ userCurrentRole|default('view')|e('js') }}"; + piwik.isUserIsAnonymous = {{ isUserIsAnonymous|default(0)|e('js') }}; piwik.userCapabilities = {{ userCapabilities|default([])|json_encode|raw }}; piwik.config = {}; {% if clientSideConfig is defined %} diff --git a/plugins/SegmentEditor/API.php b/plugins/SegmentEditor/API.php index deaa0e51daa..eb0e5526235 100644 --- a/plugins/SegmentEditor/API.php +++ b/plugins/SegmentEditor/API.php @@ -192,7 +192,7 @@ protected function checkUserCanEditOrDeleteSegment(array $segment): void throw new Exception($this->getMessageCannotEditSegmentCreatedBySuperUser()); } - if ((int) $segment['enable_only_idsite'] === 0 && !Piwik::hasUserSuperUserAccess()) { + if ((int) $segment['enable_only_idsite'] === 0) { throw new Exception(Piwik::translate('SegmentEditor_UpdatingAllSitesSegmentPermittedToSuperUser')); } } @@ -215,7 +215,7 @@ public function delete(int $idSegment): void * * @param int $idSegment The ID of the segment being deleted. */ - Piwik::postEvent('SegmentEditor.deactivate', array($idSegment)); + Piwik::postEvent('SegmentEditor.deactivate', [$idSegment]); $this->getModel()->deleteSegment($idSegment); @@ -263,14 +263,14 @@ public function update( $autoArchive = $this->checkAutoArchive($autoArchive, $idSite); - $bind = array( + $bind = [ 'name' => $name, 'definition' => $definition, 'enable_all_users' => (int) $enabledAllUsers, 'enable_only_idsite' => (int) $idSite, 'auto_archive' => (int) $autoArchive, 'ts_last_edit' => Date::now()->getDatetime(), - ); + ]; /** * Triggered before a segment is modified. @@ -280,7 +280,7 @@ public function update( * * @param int $idSegment The ID of the segment which visibility is reduced. */ - Piwik::postEvent('SegmentEditor.update', array($idSegment, $bind)); + Piwik::postEvent('SegmentEditor.update', [$idSegment, $bind]); $this->getModel()->updateSegment($idSegment, $bind); @@ -321,7 +321,7 @@ public function add( $enabledAllUsers = $this->checkEnabledAllUsers($enabledAllUsers); $autoArchive = $this->checkAutoArchive($autoArchive, $idSite); - $bind = array( + $bind = [ 'name' => $name, 'definition' => $definition, 'login' => Piwik::getCurrentUserLogin(), @@ -329,8 +329,10 @@ public function add( 'enable_only_idsite' => (int) $idSite, 'auto_archive' => (int) $autoArchive, 'ts_created' => Date::now()->getDatetime(), + 'starred' => 0, + 'starred_by' => null, 'deleted' => 0, - ); + ]; $id = $this->getModel()->createSegment($bind); @@ -348,6 +350,56 @@ public function add( return $id; } + /** + * Stars a stored segment. + * + * @param int $idSegment + * @return array{result: boolean, starred_by: string} + * @throws Exception if the user is not logged in or does not have the required permissions. + */ + public function star(int $idSegment): array + { + Piwik::checkUserHasSomeViewAccess(); + $segment = $this->getSegmentOrFail($idSegment); + $this->checkUserCanEditOrDeleteSegment($segment); + $login = Piwik::getCurrentUserLogin(); + $bind = [ + 'starred' => 1, + 'starred_by' => $login, + ]; + + $result = $this->getModel()->updateSegment($idSegment, $bind); + + return [ + 'result' => $result, + 'starred_by' => $login, + ]; + } + + /** + * Unstars a stored segment. + * + * @param int $idSegment + * @return array{result: boolean} + * @throws Exception if the user is not logged in or does not have the required permissions. + */ + public function unstar(int $idSegment): array + { + Piwik::checkUserHasSomeViewAccess(); + $segment = $this->getSegmentOrFail($idSegment); + $this->checkUserCanEditOrDeleteSegment($segment); + $bind = [ + 'starred' => 0, + 'starred_by' => null, + ]; + + $result = $this->getModel()->updateSegment($idSegment, $bind); + + return [ + 'result' => $result, + ]; + } + /** * Returns a stored segment by ID * @@ -444,7 +496,7 @@ private function filterSegmentsWithDisabledElements(array $segments, $idSite = n */ private function sortSegmentsCreatedByUserFirst(array $segments): array { - $orderedSegments = array(); + $orderedSegments = []; foreach ($segments as $id => &$segment) { if ($segment['login'] == Piwik::getCurrentUserLogin()) { $orderedSegments[] = $segment; diff --git a/plugins/SegmentEditor/Model.php b/plugins/SegmentEditor/Model.php index 10f9332ba3c..18e38429f76 100644 --- a/plugins/SegmentEditor/Model.php +++ b/plugins/SegmentEditor/Model.php @@ -282,6 +282,8 @@ public static function install() `auto_archive` tinyint(4) NOT NULL default 0, `ts_created` TIMESTAMP NULL, `ts_last_edit` TIMESTAMP NULL, + `starred` tinyint(4) NOT NULL default 0, + `starred_by` VARCHAR(100) NULL default NULL, `deleted` tinyint(4) NOT NULL default 0, PRIMARY KEY (`idsegment`)"; diff --git a/plugins/SegmentEditor/SegmentSelectorControl.php b/plugins/SegmentEditor/SegmentSelectorControl.php index 02857f84e31..f3a3c2cee22 100644 --- a/plugins/SegmentEditor/SegmentSelectorControl.php +++ b/plugins/SegmentEditor/SegmentSelectorControl.php @@ -109,6 +109,18 @@ private function wouldApplySegment($savedSegment) private function getTranslations() { $translationKeys = array( + 'General_CanNotEditGlobalSegment', + 'General_CanNotStarGlobalSegment', + 'General_CanNotUnstarGlobalSegment', + 'General_CanEditGlobalSegment', + 'General_CanStarGlobalSegment', + 'General_CanUnstarGlobalSegment', + 'General_CanNotEditSiteSegment', + 'General_CanNotStarSiteSegment', + 'General_CanNotUnstarSiteSegment', + 'General_CanEditSiteSegment', + 'General_CanStarSiteSegment', + 'General_CanUnstarSiteSegment', 'General_OperationEquals', 'General_OperationNotEquals', 'General_OperationAtMost', @@ -127,6 +139,9 @@ private function getTranslations() 'General_DefaultAppended', 'SegmentEditor_AddNewSegment', 'General_Edit', + 'General_StarredBy', + 'General_StarredByYou', + 'General_Edit', 'General_Search', 'General_SearchNoResults', ); diff --git a/plugins/SegmentEditor/images/edit_segment.png b/plugins/SegmentEditor/images/edit_segment.png deleted file mode 100644 index 6eb039254da..00000000000 Binary files a/plugins/SegmentEditor/images/edit_segment.png and /dev/null differ diff --git a/plugins/SegmentEditor/images/edit_segment.svg b/plugins/SegmentEditor/images/edit_segment.svg new file mode 100644 index 00000000000..4141d942238 --- /dev/null +++ b/plugins/SegmentEditor/images/edit_segment.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/SegmentEditor/javascripts/Segmentation.js b/plugins/SegmentEditor/javascripts/Segmentation.js index 5a08f8f8834..2322b6f456a 100644 --- a/plugins/SegmentEditor/javascripts/Segmentation.js +++ b/plugins/SegmentEditor/javascripts/Segmentation.js @@ -39,10 +39,7 @@ Segmentation = (function($) { self.editorTemplate = self.editorTemplate.detach(); - self.timer = ""; // variable for further use in timing events - self.searchAllowed = true; self.filterTimer = ""; - self.filterAllowed = true; self.availableMatches = []; self.availableMatches["metric"] = []; @@ -75,16 +72,9 @@ Segmentation = (function($) { }; segmentation.prototype.setTooltip = function (segmentDescription) { - - var title = _pk_translate('SegmentEditor_ChooseASegment') + '.'; - title += ' '+ _pk_translate('SegmentEditor_CurrentlySelectedSegment', [segmentDescription]); - - $('a.title', this.content).attr('title', title).tooltip({ - track: true, - show: {delay: 700, duration: 200}, // default from Tooltips.js - hide: false, - content: title, - }); + var title = _pk_translate('SegmentEditor_ChooseASegment') + '.'; + title += ' '+ _pk_translate('SegmentEditor_CurrentlySelectedSegment', [segmentDescription]); + addTooltip($('a.title', this.content), title); }; // We will listen to changes in the Segment Comparison Store // so we can mark compared segments properly. This will now include deletion of compared segments. @@ -93,15 +83,19 @@ Segmentation = (function($) { }); segmentation.prototype.markComparedSegments = function() { - var comparisonService = window.CoreHome.ComparisonsStoreInstance; - var comparedSegments = comparisonService.getSegmentComparisons().map(function (comparison) { + const comparisonService = window.CoreHome.ComparisonsStoreInstance; + const comparedSegments = comparisonService.getSegmentComparisons().map(function (comparison) { return comparison.params.segment; }); - $('div.segmentList ul li[data-definition]', this.target).removeClass('comparedSegment').filter(function () { - var definition = $(this).attr('data-definition'); - return comparedSegments.indexOf(definition) !== -1 || comparedSegments.indexOf(decodeURIComponent(definition)) !== -1; - }).each(function () { - $(this).addClass('comparedSegment'); + $('div.segmentList ul li[data-definition]', this.target).each(function () { + const $segment = $(this); + const definition = $segment.attr('data-definition'); + const isCompared = ( + comparedSegments.indexOf(definition) !== -1 || + comparedSegments.indexOf(decodeURIComponent(definition)) !== -1 + ); + $segment.toggleClass('comparedSegment', isCompared); + $segment.find('.compareSegment').attr('data-state', isCompared ? 'active' : ''); }); self.checkIfComparedSegmentsHasReachedLimit(); }; @@ -109,15 +103,18 @@ Segmentation = (function($) { const limit = piwik.config.data_comparison_segment_limit + 1; const comparisonService = window.CoreHome.ComparisonsStoreInstance; const comparedSegmentsLength = comparisonService.getSegmentComparisons().length; - $('div.segmentList ul li[data-definition] span.compareSegment').each(function() { + $('div.segmentList ul li[data-definition] .compareSegment').each(function() { + const $compareButton = $(this); + const currentState = $compareButton.attr('data-state'); + if (currentState === 'active') { + return; + } if (comparedSegmentsLength >= limit) { - $(this).addClass('no-click'); - $(this).parent().attr('title', _pk_translate('General_MaximumNumberOfSegmentsComparedIs', [limit])); + $compareButton.attr('data-state','disabled'); + addTooltip($compareButton, _pk_translate('General_MaximumNumberOfSegmentsComparedIs', [limit])); } else { - $(this).removeClass('no-click'); - var idSegment = $(this).parent().attr('data-idsegment'); - const title = getSegmentName(getSegmentFromId(idSegment)); - $(this).parent().attr('title', title); + $compareButton.attr('data-state',''); + addTooltip($compareButton, _pk_translate('SegmentEditor_CompareThisSegment')); } }); return false; @@ -128,8 +125,7 @@ Segmentation = (function($) { var segmentationTitle = $(this.content).find(".segmentationTitle"); var title; - if( current != "") - { + if (current != "") { // this code is mad, and may drive you mad. // the whole segmentation editor needs to be rewritten in Vue with clean code var selector = 'div.segmentList ul li[data-definition="'+current+'"]'; @@ -195,85 +191,91 @@ Segmentation = (function($) { + ' ' + self.translations['General_DefaultAppended'] + ''; var comparisonService = window.CoreHome.ComparisonsStoreInstance; - if (comparisonService.isComparisonEnabled() - || comparisonService.isComparisonEnabled() === null // may not be initialized since this code is outside of Vue + if ( + comparisonService.isComparisonEnabled() || + comparisonService.isComparisonEnabled() === null // may not be initialized since this code is outside of Vue ) { - listHtml += ''; + const className = 'compareSegment allVisitsCompareSegment ' + (self.segmentAccess === 'write' ? 'allVisitsCompareSegment--write' : ''); + const title = _pk_translate('SegmentEditor_CompareThisSegment'); + listHtml += ''; } listHtml += ''; - var isVisibleToSuperUserNoticeAlreadyDisplayedOnce = false; - var isVisibleToSuperUserNoticeShouldBeClosed = false; + let isVisibleToSuperUserNoticeAlreadyDisplayedOnce = false; + let isSharedWithMeBySuperUserNoticeAlreadyDisplayedOnce = false; - var isSharedWithMeBySuperUserNoticeAlreadyDisplayedOnce = false; - var isSharedWithMeBySuperUserNoticeShouldBeClosed = false; - - if(self.availableSegments.length > 0) { + if (self.availableSegments.length > 0) { for(var i = 0; i < self.availableSegments.length; i++) { segment = self.availableSegments[i]; - if(isSegmentSharedWithMeBySuperUser(segment) && !isSharedWithMeBySuperUserNoticeAlreadyDisplayedOnce) { + // starred should be an int, but it could be converted as string + // and !"0" would then be false instead of true + segment.starred = Boolean(parseInt(segment.starred, 10)); + + if (isSegmentSharedWithMeBySuperUser(segment) && !isSharedWithMeBySuperUserNoticeAlreadyDisplayedOnce) { isSharedWithMeBySuperUserNoticeAlreadyDisplayedOnce = true; - isSharedWithMeBySuperUserNoticeShouldBeClosed = true; - listHtml += '
' + _pk_translate('SegmentEditor_SharedWithYou') + ':

'; + listHtml += '
' + _pk_translate('SegmentEditor_SharedWithYou') + ':
'; } - if(isSegmentVisibleToSuperUserOnly(segment) && !isVisibleToSuperUserNoticeAlreadyDisplayedOnce) { - // close - if(isSharedWithMeBySuperUserNoticeShouldBeClosed) { - isSharedWithMeBySuperUserNoticeShouldBeClosed = false; - listHtml += ''; - } - + if (isSegmentVisibleToSuperUserOnly(segment) && !isVisibleToSuperUserNoticeAlreadyDisplayedOnce) { isVisibleToSuperUserNoticeAlreadyDisplayedOnce = true; - isVisibleToSuperUserNoticeShouldBeClosed = true; - listHtml += '
' + _pk_translate('SegmentEditor_VisibleToSuperUser') + ':

'; + listHtml += '
' + _pk_translate('SegmentEditor_VisibleToSuperUser') + ':
'; } - injClass = ""; + injClass = []; var checkSelected = segment.definition; + var escapedSegmentName = (segment.definition).replace(/"/g, '"'); - if( checkSelected == self.currentSegmentStr || - checkSelected == decodeURIComponent(self.currentSegmentStr) - ) { - injClass = 'class="segmentSelected"'; + if (checkSelected === self.currentSegmentStr || checkSelected === decodeURIComponent(self.currentSegmentStr)) { + injClass.push('segmentSelected'); + } + if (segment.starred) { + injClass.push('segmentStarred'); } - listHtml += '
  • '+getSegmentName(segment)+''; - if(self.segmentAccess == "write") { - listHtml += ''; + listHtml += '' + + '
  • ' + + '' + getSegmentName(segment) + ''; + + const canEdit = getIsUserCanEditSegment(segment); + // We do not use "disabled" attribute here because it remove pointer events and we want to show tooltips + const disabledAttribute = canEdit ? '' : 'data-state="disabled"'; + const starTitleAttribute = 'title="' + getStarSegmentTitle(segment, canEdit) + '"'; + listHtml += '' + + ''; + if (self.segmentAccess === 'write') { + const editTitleAttribute = 'title="' + getEditSegmentTitle(segment, canEdit) + '"'; + listHtml += ''; } - if (comparisonService.isComparisonEnabled() - || comparisonService.isComparisonEnabled() === null // may not be initialized since this code is outside of Vue + + if ( + comparisonService.isComparisonEnabled() || + comparisonService.isComparisonEnabled() === null // may not be initialized since this code is outside of Vue ) { - listHtml += ''; + listHtml += ''; } listHtml += '
  • '; } - if(isVisibleToSuperUserNoticeShouldBeClosed) { - listHtml += '
    '; - } - - if(isSharedWithMeBySuperUserNoticeShouldBeClosed) { - listHtml += '
    '; - } - $(html).find(".segmentList > ul").append(listHtml); - if(self.segmentAccess === "write"){ + if (self.segmentAccess === "write"){ $(html).find(".add_new_segment").html(self.translations['SegmentEditor_AddNewSegment']); - } - else { + } else { $(html).find(".add_new_segment").hide(); } - } - else - { + } else { $(html).find(".segmentList > ul").append(listHtml); } + return html; }; @@ -364,27 +366,28 @@ Segmentation = (function($) { } var filterSegmentList = function (keyword) { + var search = normalizeSearchString(keyword); var curTitle; clearFilterSegmentList(); - $(self.target).find(" .filterNoResults").remove(); + $(self.target).find(".filterNoResults").remove(); $(self.target).find(".segmentList li").each(function () { - curTitle = $(this).prop('title'); + curTitle = $(this).find('.segname').prop('title'); $(this).hide(); - if (curTitle.toLowerCase().indexOf(keyword.toLowerCase()) !== -1) { + if (normalizeSearchString(curTitle).indexOf(search) !== -1) { $(this).show(); } }); - if ($(self.target).find(".segmentList li:visible").length == 0) { + if ($(self.target).find(".segmentList li:visible").length === 0) { $(self.target).find(".segmentList li:first") .before("
  • " + self.translations['General_SearchNoResults'] + "
  • "); } - if ($(self.target).find(".segmentList .segmentsVisibleToSuperUser li:visible").length == 0) { + if ($(self.target).find(".segmentList .segmentsVisibleToSuperUser li:visible").length === 0) { $(self.target).find(".segmentList .segmentsVisibleToSuperUser").hide(); } - if ($(self.target).find(".segmentList .segmentsSharedWithMeBySuperUser li:visible").length == 0) { + if ($(self.target).find(".segmentList .segmentsSharedWithMeBySuperUser li:visible").length === 0) { $(self.target).find(".segmentList .segmentsSharedWithMeBySuperUser").hide(); } }; @@ -419,25 +422,71 @@ Segmentation = (function($) { }); self.target.on('click', '.editSegment', function(e) { - $(this).closest(".segmentationContainer").trigger("click"); - var target = $(this).parent("li"); + const $button = $(this); + if ($button.attr('data-state') === 'disabled') { + return false; + } + const $segment = $button.parent("li"); + $segment.closest(".segmentationContainer").trigger("click"); - openEditFormGivenSegment(target); + openEditFormGivenSegment($segment); e.stopPropagation(); e.preventDefault(); }); - self.target.on('click', '.compareSegment', function (e) { + self.target.on('click', '[data-star]', function (e) { e.stopPropagation(); e.preventDefault(); - var comparisonService = window.CoreHome.ComparisonsStoreInstance; - comparisonService.addSegmentComparison({ - segment: $(e.target).closest('li').data('definition'), + const $button = $(this); + if ($button.attr('data-state') === 'disabled') { + return false; + } + const $root = $button.closest('li'); + const idSegment = $root.data('idsegment'); + const segment = getSegmentFromId(idSegment); + segment.starred = !segment.starred; + const method = segment.starred ? 'star' : 'unstar'; + updateStarredSegment($root, segment); + + var ajaxHandler = new ajaxHelper(); + ajaxHandler.addParams({ + "module": 'API', + "format": 'json', + "method": 'SegmentEditor.' + method, + "userLogin": piwik.userLogin, + "idSegment": idSegment, + }, 'POST'); + ajaxHandler.setErrorCallback(function () { + segment.starred = !segment.starred; + updateStarredSegment($root, segment, true); + }); + ajaxHandler.setCallback(function (response) { + segment.starred_by = response.starred_by; + updateStarSegmentTooltip($root, segment); }); + ajaxHandler.send(); + }); + + self.target.on('click', '.compareSegment', function (e) { + e.stopPropagation(); + e.preventDefault(); + const $button = $(this); + if ($button.attr('data-state') === 'disabled') { + return false; + } + const comparisonService = window.CoreHome.ComparisonsStoreInstance; + const segmentDefinition = $button.closest('li').data('definition'); + if ($button.attr('data-state') === 'active') { + comparisonService.removeSegmentComparisonByDefinition(segmentDefinition); + } else { + comparisonService.addSegmentComparison({ + segment: segmentDefinition, + }); + } closeAllOpenLists(); }); - self.target.on("click", ".segmentList li span.segname", function (e) { + self.target.on("click", ".segmentList li .segname", function (e) { let parentLi = $(this).parent(); if (parentLi.hasClass("grayed") !== true) { var segmentDefinition = $(parentLi).data("definition"); @@ -462,7 +511,7 @@ Segmentation = (function($) { // emulate a click when pressing enter on one of the segments or the add button self.target.on("keyup", ".segmentList li, .add_new_segment", function (event) { var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.key)); - if(keycode == '13'){ + if (keycode == '13'){ $(this).trigger('click'); } }); @@ -486,20 +535,18 @@ Segmentation = (function($) { self.target.on('keyup', ".segmentFilter", function (e) { var search = $(e.currentTarget).val(); - if (search == self.translations['General_Search']) { + if (search === self.translations['General_Search']) { search = ""; } + clearTimeout(self.filterTimer); + self.filterTimer = false; if (search.length >= 2) { - clearTimeout(self.filterTimer); - self.filterAllowed = true; self.filterTimer = setTimeout(function () { filterSegmentList(search); }, 500); - } - else { - self.filterTimer = false; - clearFilterSegmentList(); + } else { + self.filterTimer = setTimeout(clearFilterSegmentList, 500); } }); @@ -573,17 +620,103 @@ Segmentation = (function($) { } }); - // - // segment manipulation events - // - }; - var getAddOrBlockButtonHtml = function(){ - if(typeof addOrBlockButton === "undefined") { - var addOrBlockButton = self.editorTemplate.find("div.segment-add-or").clone(); + function getIsUserCanEditSegment(segment) { + if (self.segmentAccess !== 'write') { + return false; + } + if (piwik.hasSuperUserAccess || piwik.userCurrentRole === 'admin' || piwik.userCurrentRole === 'write') { + return true; + } + + return (segment.login === piwik.userLogin); + } + + function getStarredByTitlePart(segment) { + const login = segment.starred_by || ''; + if (login === piwik.userLogin) { + return ' (' + self.translations['General_StarredByYou'] + ')'; + } + + return ' (' + self.translations['General_StarredBy'] + ' ' + login + ')'; + } + + function getStarSegmentTitle(segment, canEdit) { + // Anonymous users do not have any action + if (piwik.isUserAnonymous) { + return ''; + } + + // Site-specific segments + if (segment.enable_only_idsite) { + if (canEdit) { + if (segment.starred) { + return self.translations['General_CanUnstarSiteSegment'] + ' ' + getStarredByTitlePart(segment); + } + return self.translations['General_CanStarSiteSegment']; + } else { + if (segment.starred) { + return self.translations['General_CanNotUnstarSiteSegment']; + } + return self.translations['General_CanNotStarSiteSegment']; + } + } + + // Global segments + if (canEdit) { + if (segment.starred) { + return self.translations['General_CanUnstarGlobalSegment'] + ' ' + getStarredByTitlePart(segment); } - return addOrBlockButton.clone(); + return self.translations['General_CanStarGlobalSegment']; + } + if (segment.starred) { + return self.translations['General_CanNotUnstarGlobalSegment']; + } + return self.translations['General_CanNotStarGlobalSegment']; + } + + function getEditSegmentTitle(segment, canEdit) { + // Site-specific segments + if (segment.enable_only_idsite) { + if (canEdit) { + return self.translations['General_CanEditSiteSegment']; + } else { + return self.translations['General_CanNotEditSiteSegment']; + } + } + + // Global segments + if (canEdit) { + return self.translations['General_CanEditGlobalSegment']; + } + return self.translations['General_CanNotEditGlobalSegment']; + } + + function updateStarSegmentTooltip($segment, segment) { + const $starButton = $segment.find('.starSegment'); + const canEdit = getIsUserCanEditSegment(segment); + addTooltip($starButton, getStarSegmentTitle(segment, canEdit)); + } + + function updateStarredSegment($segment, segment, isError = false) { + updateStarSegmentTooltip($segment, segment); + $segment.toggleClass('segmentStarred', segment.starred); + $segment.one('animationend', function avoidAnimationRepetition() { + $segment.removeClass('segmentStarAnimation'); + $segment.removeClass('segmentStarErrorAnimation'); + }); + $segment.toggleClass('segmentStarAnimation', !isError); + $segment.toggleClass('segmentStarErrorAnimation', isError); + } + + function addTooltip(element, title) { + $(element).attr('title', title).tooltip({ + track: true, + show: { delay: 700, duration: 200 }, // default from Tooltips.js + hide: false, + content: title, + }); }; function openEditFormGivenSegment(option) { @@ -638,7 +771,7 @@ Segmentation = (function($) { $(self.form).find('.enable_all_users_select > option[value="' + segment.enable_all_users + '"]').prop("selected",true); // Replace "Visible to me" by "Visible to $login" when user is super user - if(hasSuperUserAccessAndSegmentCreatedByAnotherUser(segment)) { + if (hasSuperUserAccessAndSegmentCreatedByAnotherUser(segment)) { $(self.form).find('.enable_all_users_select > option[value="' + 0 + '"]').text(segment.login); } $(self.form).find('.visible_to_website_select > option[value="'+segment.enable_only_idsite+'"]').prop("selected",true); @@ -731,10 +864,9 @@ Segmentation = (function($) { }; // determine if save or update should be performed - if(segmentId === ""){ + if (segmentId === "") { self.addMethod(params); - } - else{ + } else { jQuery.extend(params, { "idSegment": segmentId }); @@ -865,7 +997,7 @@ Segmentation = (function($) { var html = getListHtml(); - if(typeof self.content !== "undefined"){ + if (typeof self.content !== "undefined") { this.content.html($(html).html()); } else { this.target.append(html); @@ -881,6 +1013,10 @@ Segmentation = (function($) { // Loading message var segmentIsSet = this.getSegment().length; toggleLoadingMessage(segmentIsSet); + + self.target.find('[title]').each(function () { + addTooltip(this, this.getAttribute('title')); + }); }; if (piwikHelper.isReportingPage()) { diff --git a/plugins/SegmentEditor/stylesheets/segmentation.less b/plugins/SegmentEditor/stylesheets/segmentation.less index 0522ac4e95d..ce68984498c 100644 --- a/plugins/SegmentEditor/stylesheets/segmentation.less +++ b/plugins/SegmentEditor/stylesheets/segmentation.less @@ -232,7 +232,13 @@ div.scrollable { min-width: 206px; } +.segmentationContainer hr { + margin: 10px 0; +} + .segmentationContainer .submenu ul { + display: flex; + flex-direction: column; color: @theme-color-text-light; float: none; font-size: 11px; @@ -243,16 +249,26 @@ div.scrollable { padding-top: 10px; } - .segmentationContainer .submenu ul li { padding: 2px 0 2px 0; margin: 3px 0 0 0; cursor: pointer; + order: 2; } -.segmentationContainer .submenu ul li:hover, -.segmentationContainer .submenu ul li:focus, -.segmentationContainer .submenu ul li:focus-within { +.segmentationContainer .submenu ul li[data-idsegment=""] { + order: 0; +} + +.segmentationContainer .submenu ul li.segmentStarred { + order: 1; +} + +.segmentationContainer .submenu ul li.comparedSegment { + font-weight: bold; +} + +.segmentationContainer .submenu ul li:hover { color: #255792; background: @color-silver-l95; outline: none; @@ -268,50 +284,135 @@ div.scrollable { margin-bottom: 5px; } +@keyframes starAnimation { + 0% { transform: scale(1) rotate(0deg); } + 50% { transform: scale(1.2) rotate(180deg); opacity: 1; } + 100% { transform: scale(1) rotate(360deg); } +} + +@keyframes starAnimationPath { + 0% { fill: black; } + 50% { fill: @theme-color-brand; stroke: @theme-color-brand; } + 100% { fill: black; } +} + +@keyframes unstarAnimation { + 0% { transform: scale(1) rotate(0deg); } + 50% { transform: scale(1.2) rotate(-180deg);} + 100% { transform: scale(1) rotate(-360deg); } +} + +@keyframes unstarAnimationPath { + 0% { fill: black; } + 100% { fill: transparent; } +} + +@keyframes starErrorAnimation { + 0% { opacity: 0.5; } + 5%, 15%, 25%, 35%, 45% { transform: translate(-2px)} + 10%, 20%, 30%, 40% { transform: translate(2px)} + 50% { opacity: 1; } + 100% { transform: translate(0); opacity: 0.5; } +} + +@keyframes starErrorAnimationPath { + 0% { stroke: black; fill: black; } + 25%, 75% { stroke: red; fill: red; } + 100% { stroke: black; fill: transparent; } +} + .segmentationContainer ul.submenu > li { - span.editSegment, span.compareSegment { - display: block; - float: right; - text-align: center; - margin-right: 4px; - font-weight: normal; + .editSegment, + .compareSegment, + .starSegment { + flex: none; + display: flex; width: 16px; height: 16px; - .opacity(0.5); + + // Button reset + padding: 0; + border: 0; + background: transparent; + cursor: pointer; + + font-weight: normal; + text-align: center; + opacity: 0.5; &:hover { - .opacity(1); + opacity: 1; + } + + &[data-state=disabled] { + opacity: 0.2; + cursor: not-allowed; } } - span.editSegment { - background: url(plugins/SegmentEditor/images/edit_segment.png) no-repeat; + .editSegment { + background: url(plugins/SegmentEditor/images/edit_segment.svg) no-repeat; + background-size: cover; order: 3; } - span.compareSegment { + .starSegment { + order: 2; + } + + .segmentStarAnimation .starSegment { + animation: unstarAnimation 0.5s 1; + } + + .segmentStarAnimation .starSegment path { + animation: unstarAnimationPath 0.5s 1; + } + + .segmentStarred.segmentStarAnimation .starSegment { + animation-name: starAnimation; + } + + .segmentStarred.segmentStarAnimation .starSegment path { + animation-name: starAnimationPath; + } + + .segmentStarErrorAnimation .starSegment { + animation: starErrorAnimation 2s 1; + } + + .segmentStarErrorAnimation .starSegment path { + animation: starErrorAnimationPath 2s 1; + } + + .segmentStarred.segmentStarErrorAnimation .starSegment path { + animation-direction: reverse; + } + + /* We do not want filled & transparent stars, design wanted by Matthieu */ + .segmentStarred .starSegment[data-state=disabled] { + opacity: 0.5; + } + + .segmentStarred .starSegment path { + fill: black; + } + + .compareSegment { background: url(plugins/Morpheus/images/compare.svg) no-repeat; background-size: cover; order: 2; &.allVisitsCompareSegment { - margin-right: 24px; + margin-left: 20px; } - } - span.compareSegment.no-click { - pointer-events: none; - } - - li.segmentSelected, li.comparedSegment { - span.compareSegment { - pointer-events: none; - opacity: 0.2; + &.allVisitsCompareSegment--write { + margin-right: 20px; } } } html.comparisonsDisabled .segmentationContainer ul.submenu { - span.compareSegment { + .compareSegment { display: none; } } @@ -398,6 +499,8 @@ html.comparisonsDisabled .segmentationContainer ul.submenu { li { display: flex; + align-items: center; + gap: 4px; } } @@ -446,9 +549,7 @@ a.metric_category { .segmentSelected, .segmentSelected:hover, -.segmentEditorPanel .segmentationContainer .submenu li .segmentSelected, -.segmentEditorPanel .segmentationContainer .submenu li:focus, -.segmentEditorPanel .segmentationContainer .submenu li:focus-within { +.segmentEditorPanel .segmentationContainer .submenu li .segmentSelected { font-weight: bold; } @@ -523,9 +624,8 @@ a.metric_category { } .segname { - width: ~"calc(100% - 40px)"; + flex: auto; padding-right: 10px; - display: inline-block; order: 1; } diff --git a/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php b/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php index 6da40901e2c..17778de5cad 100644 --- a/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php +++ b/plugins/SegmentEditor/tests/Integration/SegmentEditorTest.php @@ -83,6 +83,8 @@ public function testAddAndGetSimpleSegment() 'auto_archive' => '0', 'ts_last_edit' => null, 'deleted' => '0', + 'starred' => '0', + 'starred_by' => null, ); $this->assertEquals($segment, $expected); @@ -113,6 +115,8 @@ public function testAddAndGetAnotherSegment() 'auto_archive' => '1', 'ts_last_edit' => null, 'deleted' => '0', + 'starred' => '0', + 'starred_by' => null, ); unset($segment['ts_created']); $this->assertEquals($segment, $expected); @@ -145,7 +149,7 @@ public function testUpdateSegment() $this->clearReArchiveList(); $updatedSegment = array( - 'idsegment' => $idSegment2, + 'idsegment' => (string) $idSegment2, 'name' => 'NEW name', 'definition' => 'searches==0', 'hash' => md5('searches==0'), @@ -156,6 +160,8 @@ public function testUpdateSegment() 'ts_created' => Date::now()->getDatetime(), 'login' => Piwik::getCurrentUserLogin(), 'deleted' => '0', + 'starred' => '0', + 'starred_by' => null, ); API::getInstance()->update( $idSegment2, @@ -178,11 +184,29 @@ public function testUpdateSegment() $this->assertEquals($newSegment, $updatedSegment); - // Check the other segmenet was not updated + // Check the other segment was not updated $newSegment = API::getInstance()->get($idSegment1); $this->assertEquals($newSegment['name'], $nameSegment1); } + public function testStarUnstarSegment() + { + // Set up initial conditions + $idSegment = API::getInstance()->add('hello', 'searches==0'); + $segment = API::getInstance()->get($idSegment); + $this->assertEquals('0', $segment['starred']); + + // Star segment + API::getInstance()->star($idSegment); + $starredSegment = API::getInstance()->get($idSegment); + $this->assertEquals('1', $starredSegment['starred']); + + // Unstar segment + API::getInstance()->unstar($idSegment); + $unstarredSegment = API::getInstance()->get($idSegment); + $this->assertEquals('0', $unstarredSegment['starred']); + } + public function testDeleteSegment() { $this->expectNotToPerformAssertions(); diff --git a/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js b/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js index 6ec4a34cb1d..be740816c80 100644 --- a/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js +++ b/plugins/SegmentEditor/tests/UI/SegmentSelectorEditor_spec.js @@ -8,6 +8,8 @@ */ describe("SegmentSelectorEditorTest", function () { + const getSegmentQuery = n => '.segmentList li:nth-of-type(' + (n+1) + ')'; + const getSegmentStarQuery = n => getSegmentQuery(n) + ' .starSegment'; var selectorsToCapture = ".segmentEditorPanel,.segmentEditorPanel .dropdown-body,.segment-element"; var generalParams = 'idSite=1&period=year&date=2012-08-09'; var url = '?module=CoreHome&action=index&' + generalParams + '#?' + generalParams + '&category=General_Actions&subcategory=General_Pages'; @@ -33,6 +35,26 @@ describe("SegmentSelectorEditorTest", function () { await (await page.jQuery(prefixSelector + ' .metricListBlock .expandableList .secondLevel li:contains(' + name + ')', { waitFor: true })).click(); } + async function switchToAnonymousUser() { + await testEnvironment.callApi('UsersManager.setUserAccess', { + userLogin: 'anonymous', + access: 'view', + idSites: [1], + }); + testEnvironment.testUseMockAuth = 0; + testEnvironment.save(); + } + + async function switchToConnectedUser() { + testEnvironment.testUseMockAuth = 1; + testEnvironment.save(); + await testEnvironment.callApi('UsersManager.setUserAccess', { + userLogin: 'anonymous', + access: 'noaccess', + idSites: [1], + }); + } + it("should load correctly", async function() { await page.goto(url); expect(await page.screenshotSelector(selectorsToCapture)).to.matchImage('0_initial'); @@ -43,9 +65,40 @@ describe("SegmentSelectorEditorTest", function () { expect(await page.screenshotSelector(selectorsToCapture)).to.matchImage('1_selector_open'); }); + it("should star all segments", async function() { + await page.click(getSegmentStarQuery(1)); + await page.click(getSegmentStarQuery(2)); + await page.click(getSegmentStarQuery(3)); + const firstSegmentClassName = await page.evaluate(() => $('.segmentList li:nth-of-type(2)').attr('class')); + expect(firstSegmentClassName).to.match(/segmentStarred/); + const firstSegmentStarState = await page.evaluate(() => $('.segmentList li:nth-of-type(2) .starSegment').attr('data-state') || ''); + expect(firstSegmentStarState).to.equal(''); + expect(await page.screenshotSelector(selectorsToCapture)).to.matchImage('1_selector_starred'); + }); + + it("should unstar first segment", async function() { + await page.click(getSegmentStarQuery(1)); + const firstSegmentClassName = await page.evaluate(() => $('.segmentList li:nth-of-type(2)').attr('class')); + expect(firstSegmentClassName).to.not.match(/segmentStarred/); + const firstSegmentStarState = await page.evaluate(() => $('.segmentList li:nth-of-type(2) .starSegment').attr('data-state') || ''); + expect(firstSegmentStarState).to.equal(''); + expect(await page.screenshotSelector(selectorsToCapture)).to.matchImage('1b_selector_unstarred'); + }); + + it("should have disabled star for anonymous users", async function() { + await switchToAnonymousUser(); + await page.goto(url); + await page.click('.segmentationContainer .title'); + const firstSegmentStarState = await page.evaluate(() => $('.segmentList li:nth-of-type(2) .starSegment').attr('data-state') || ''); + expect(firstSegmentStarState).to.equal('disabled'); + }); + it("should open segment editor when edit link clicked for existing segment", async function() { + await switchToConnectedUser(); + await page.goto(url); + await page.click('.segmentationContainer .title'); await page.evaluate(function() { - $('.segmentList .editSegment:first').click() + $('.segmentList .editSegment:first').click(); }); await page.waitForNetworkIdle(); expect(await page.screenshotSelector(selectorsToCapture)).to.matchImage('2_segment_editor_update'); @@ -115,10 +168,10 @@ describe("SegmentSelectorEditorTest", function () { it("should save a new segment and add it to the segment list when the form is filled out and the save button is clicked", async function() { for (let i = 0; i < 3; i += 1) { - await page.evaluate(function (i) { - $(`.metricValueBlock input:eq(${i})`).val('value ' + i).change(); - }, i); - await page.waitForTimeout(250); + await page.evaluate(function (i) { + $(`.metricValueBlock input:eq(${i})`).val('value ' + i).change(); + }, i); + await page.waitForTimeout(250); } await page.type('input.edit_segment_name', 'new segment'); diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_open.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_open.png index 48ac7f70794..45ad65c5c5c 100644 --- a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_open.png +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_open.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e586e785ae27693d9755ac7b8a27db91aa28c12077056c9208878656c1815355 -size 15993 +oid sha256:09573fe50a1af9797bff7fe43b6d33754d173e3f6ddce5c31856938d7f280320 +size 16297 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_starred.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_starred.png new file mode 100644 index 00000000000..0b9dcdddf1b --- /dev/null +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_starred.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e989b1f587fd2e99969b8e523ff16246002a5f29664b7a87ae94cc71eeabe06 +size 16770 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_unstarred.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_unstarred.png new file mode 100644 index 00000000000..305536fcdd4 --- /dev/null +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1_selector_unstarred.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0a79771f8de5c7cbc536127c2390ce3c1eee0fa6cb9a4ce1955dba0525a2f1c +size 17092 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1b_selector_unstarred.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1b_selector_unstarred.png new file mode 100644 index 00000000000..a860a8b2137 --- /dev/null +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_1b_selector_unstarred.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92b8e4a315ff2a1a2cca6ee3e6de59c6101580e4571a478032edcd84e37774bf +size 17569 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_deleted.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_deleted.png index 48ac7f70794..23212025840 100644 --- a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_deleted.png +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_deleted.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e586e785ae27693d9755ac7b8a27db91aa28c12077056c9208878656c1815355 -size 15993 +oid sha256:cff19e28b4c7be4f9ca9982f5cdd5b93d5e5c2f04b76c2ded226a3a961ee0995 +size 16658 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_enabled_create_realtime_segments_saved.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_enabled_create_realtime_segments_saved.png index e7641ce2b2f..8a05d5c6bf0 100644 --- a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_enabled_create_realtime_segments_saved.png +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_enabled_create_realtime_segments_saved.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9b999f26ab808b11c9cc2c2e9e8388d66de22aab90533cce3cd6bcb27916fa8 -size 21289 +oid sha256:f78a6030d13ffa4936754403a632671851493d43e797e3ea5f7accb4dfb605c8 +size 21583 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_saved.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_saved.png index 13eef5d9e7f..2143094cfb1 100644 --- a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_saved.png +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_saved.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ff9036d74dbc4b3fac73d94247fd5eed415ad386a64df31cc66697fc9b9e379 -size 17846 +oid sha256:86818388be210b0ec14cfd5cadc6ae83abf5134edb5092702161b9e6888d7259 +size 18500 diff --git a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_updated.png b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_updated.png index 14c0c47ee82..b0464625c31 100644 --- a/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_updated.png +++ b/plugins/SegmentEditor/tests/UI/expected-screenshots/SegmentSelectorEditorTest_updated.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1c054fb1eb65d7da38e5fba720a81a52c4bfd36c1f3a3c53e57981fa8b2a400 -size 18507 +oid sha256:ae6c4cc195426fceff6f16b05d0ef3be8b34a2cf4fcc9da913206fcae689deb5 +size 18485 diff --git a/tests/PHPUnit/Integration/Segment/SegmentUnavailableTest.php b/tests/PHPUnit/Integration/Segment/SegmentUnavailableTest.php index 27b503ab155..f8e4f95a3f3 100644 --- a/tests/PHPUnit/Integration/Segment/SegmentUnavailableTest.php +++ b/tests/PHPUnit/Integration/Segment/SegmentUnavailableTest.php @@ -172,6 +172,8 @@ private function checkSegmentAvailable(string $definition, string $name, bool $s 'auto_archive' => 1, 'ts_last_edit' => null, 'deleted' => 0, + 'starred' => 0, + 'starred_by' => null, ], ]; } diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png index 823a22cc406..eb393f285e4 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cb8605d210a59121c6c81183e98fcf4c78a05ae82457c672b3b7243fde56b86 -size 5010159 +oid sha256:163989f3c5e6b7138d9e2a31800fefcc39d0228d90ff65214036af79b55c9580 +size 5021683