diff --git a/plugins/CoreVue/types/index.d.ts b/plugins/CoreVue/types/index.d.ts index 2097eaa8777..40fefde3ded 100644 --- a/plugins/CoreVue/types/index.d.ts +++ b/plugins/CoreVue/types/index.d.ts @@ -91,6 +91,8 @@ declare global { sendContentAsDownload(filename: string, content: any, mimeType?: string): void; showVisitorProfilePopup(visitorId: string, idSite: string|number): void; hideAjaxError(): void; + showAjaxLoading(loadingDivID?: string): void; + hideAjaxLoading(loadingDivID?: string): void; refreshAfter(timeoutPeriod: number): void; } diff --git a/plugins/ProfessionalServices/API.php b/plugins/ProfessionalServices/API.php new file mode 100644 index 00000000000..5abbb47bb18 --- /dev/null +++ b/plugins/ProfessionalServices/API.php @@ -0,0 +1,49 @@ +promoWidgetDismissal = $promoWidgetDismissal; + } + + /** + * Dismisses a promo widget to no longer be shown in the menu + * + * @internal + * + * @param string $widgetName + * @return bool + * @throws \Piwik\NoAccessException + */ + public function dismissWidget(string $widgetName): bool + { + Piwik::checkUserIsNotAnonymous(); + + if (!DismissibleWidget::exists($widgetName)) { + throw new \Exception('Can\'t dismiss unknown widget ' . $widgetName); + } + + $this->promoWidgetDismissal->dismissPromoWidget($widgetName); + + return true; + } +} diff --git a/plugins/ProfessionalServices/ProfessionalServices.php b/plugins/ProfessionalServices/ProfessionalServices.php index 53fa459328a..90e18b4d2c2 100644 --- a/plugins/ProfessionalServices/ProfessionalServices.php +++ b/plugins/ProfessionalServices/ProfessionalServices.php @@ -34,6 +34,7 @@ public function registerEvents() 'Template.afterVisitorProfileOverview' => 'getSessionRecordingPromo', 'Template.afterPagePerformanceReport' => 'getSeoWebVitalsPromo', 'Template.afterSearchEngines' => 'getSeoWebVitalsPromo', + 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', ); } @@ -43,6 +44,19 @@ public function getStylesheetFiles(&$stylesheets) $stylesheets[] = 'plugins/ProfessionalServices/stylesheets/widget.less'; } + public function getClientSideTranslationKeys(&$translationKeys) + { + $translationKeys[] = 'ProfessionalServices_DismissedNotification'; + $translationKeys[] = 'ProfessionalServices_PromoFunnels'; + $translationKeys[] = 'ProfessionalServices_PromoFormAnalytics'; + $translationKeys[] = 'ProfessionalServices_PromoMediaAnalytics'; + $translationKeys[] = 'ProfessionalServices_PromoAbTesting'; + $translationKeys[] = 'ProfessionalServices_PromoHeatmaps'; + $translationKeys[] = 'ProfessionalServices_PromoSessionRecording'; + $translationKeys[] = 'ProfessionalServices_PromoCustomReports'; + $translationKeys[] = 'ProfessionalServices_PromoCrashAnalytics'; + } + public function isRequestForDashboardWidget() { $isWidget = Common::getRequestVar('widget', 0, 'int'); @@ -157,5 +171,4 @@ public function getSeoWebVitalsPromo(&$out) $out .= $view->render(); } } - } diff --git a/plugins/ProfessionalServices/PromoWidgetApplicable.php b/plugins/ProfessionalServices/PromoWidgetApplicable.php index ba4e945c9b1..0fe45495f7a 100644 --- a/plugins/ProfessionalServices/PromoWidgetApplicable.php +++ b/plugins/ProfessionalServices/PromoWidgetApplicable.php @@ -24,13 +24,19 @@ class PromoWidgetApplicable */ private $config; - public function __construct(Manager $manager, Config $config) + /** + * @var PromoWidgetDismissal + */ + private $promoWidgetDismissal; + + public function __construct(Manager $manager, Config $config, PromoWidgetDismissal $promoWidgetDismissal) { $this->manager = $manager; $this->config = $config; + $this->promoWidgetDismissal = $promoWidgetDismissal; } - public function check(string $pluginName): bool + public function check(string $pluginName, string $widgetName): bool { if (Advertising::isAdsEnabledInConfig($this->config->General) === false) { return false; @@ -44,6 +50,10 @@ public function check(string $pluginName): bool return false; } + if ($this->promoWidgetDismissal->isPromoWidgetDismissedForCurrentUser($widgetName)) { + return false; + } + return $this->manager->isPluginActivated($pluginName) === false; } } diff --git a/plugins/ProfessionalServices/PromoWidgetDismissal.php b/plugins/ProfessionalServices/PromoWidgetDismissal.php new file mode 100644 index 00000000000..9b11832a580 --- /dev/null +++ b/plugins/ProfessionalServices/PromoWidgetDismissal.php @@ -0,0 +1,44 @@ +getDismissedWidgetOptionName($widgetName), time()); + } + + public function isPromoWidgetDismissedForCurrentUser(string $widgetName): bool + { + $isAnonUser = Piwik::isUserIsAnonymous(); + + if ($isAnonUser) { + return false; + } + + return $this->isPromoWidgetDismissed($widgetName); + } + + private function isPromoWidgetDismissed(string $widgetName): bool + { + return Option::get($this->getDismissedWidgetOptionName($widgetName)) > 0; + } + + private function getDismissedWidgetOptionName(string $widgetName): string + { + return sprintf(self::DISMISSED_WIDGET_OPTION_NAME, $widgetName, Piwik::getCurrentUserLogin()); + } +} diff --git a/plugins/ProfessionalServices/Widgets/DismissibleWidget.php b/plugins/ProfessionalServices/Widgets/DismissibleWidget.php new file mode 100644 index 00000000000..6f8e46b5419 --- /dev/null +++ b/plugins/ProfessionalServices/Widgets/DismissibleWidget.php @@ -0,0 +1,24 @@ +setCategoryId('ProfessionalServices_PromoAbTesting'); $config->setSubcategoryId('ProfessionalServices_PromoOverview'); $config->setIsNotWidgetizable(); $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +37,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php index 36395e322dc..9a75e9800c1 100644 --- a/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php +++ b/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoCrashAnalytics extends Widget +class PromoCrashAnalytics extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'CrashAnalytics'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoCustomReports.php b/plugins/ProfessionalServices/Widgets/PromoCustomReports.php index 6f6e83801ae..93995f6297b 100644 --- a/plugins/ProfessionalServices/Widgets/PromoCustomReports.php +++ b/plugins/ProfessionalServices/Widgets/PromoCustomReports.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoCustomReports extends Widget +class PromoCustomReports extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'CustomReports'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php index 37ae580ab8e..2deabbb8ac8 100644 --- a/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php +++ b/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoFormAnalytics extends Widget +class PromoFormAnalytics extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'FormAnalytics'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoFunnels.php b/plugins/ProfessionalServices/Widgets/PromoFunnels.php index d3c423e56fe..ca3d22e0785 100644 --- a/plugins/ProfessionalServices/Widgets/PromoFunnels.php +++ b/plugins/ProfessionalServices/Widgets/PromoFunnels.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoFunnels extends Widget +class PromoFunnels extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'Funnels'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php b/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php index 7c68fea2daa..7d634da8d5c 100644 --- a/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php +++ b/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoHeatmaps extends Widget +class PromoHeatmaps extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'HeatmapSessionRecording'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', 'Heatmaps'); $view->imageName = 'ad-heatmaps.png'; diff --git a/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php index 0081aa29d7d..4acb72bd65a 100644 --- a/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php +++ b/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoMediaAnalytics extends Widget +class PromoMediaAnalytics extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'MediaAnalytics'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']); $view->listOfFeatures = [ diff --git a/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php b/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php index 585411d629a..b76244f0499 100644 --- a/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php +++ b/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php @@ -11,10 +11,9 @@ use Piwik\Container\StaticContainer; use Piwik\Piwik; use Piwik\View; -use Piwik\Widget\Widget; use Piwik\Widget\WidgetConfig; -class PromoSessionRecordings extends Widget +class PromoSessionRecordings extends DismissibleWidget { private const PROMO_PLUGIN_NAME = 'HeatmapSessionRecording'; @@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config) $promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable'); - $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME); + $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName()); $config->setIsEnabled($isEnabled); } @@ -37,6 +36,8 @@ public function render() $view = new View('@ProfessionalServices/pluginAdvertising'); $view->plugin = $pluginInfo; + $view->widgetName = self::getDismissibleWidgetName(); + $view->userCanDismiss = Piwik::isUserIsAnonymous() === false; $view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', 'Session Recordings'); // custom title $view->imageName = 'ad-sessionrecordings.png'; diff --git a/plugins/ProfessionalServices/lang/en.json b/plugins/ProfessionalServices/lang/en.json index 15e97218a67..b41af2077b9 100644 --- a/plugins/ProfessionalServices/lang/en.json +++ b/plugins/ProfessionalServices/lang/en.json @@ -12,6 +12,8 @@ "PromoCustomReports": "Custom Reports", "PromoCrashAnalytics": "Crashes", "PromoUnlockPowerOf" : "Unlock the Power of %1$s", + "DismissPromoWidget": "If this feature is not relevant to your goals, you can %1$shide this section%2$s.", + "DismissedNotification": "The %1$s menu will no longer be shown, unless the plugin is installed on your Matomo instance.", "AltTextPreviewImageFor": "Preview image for %1$s", "CTAStartFreeTrial": "Start free trial", "CTALearnMore": "Learn more about %1$s", diff --git a/plugins/ProfessionalServices/stylesheets/promos.less b/plugins/ProfessionalServices/stylesheets/promos.less index 5de43862b11..a32d8812c01 100644 --- a/plugins/ProfessionalServices/stylesheets/promos.less +++ b/plugins/ProfessionalServices/stylesheets/promos.less @@ -67,4 +67,10 @@ text-decoration: none; } } + + .promo-dismiss { + padding-top: 16px; + .font-default(12px; 21px); + color: @color-silver-l40; + } } diff --git a/plugins/ProfessionalServices/templates/pluginAdvertising.twig b/plugins/ProfessionalServices/templates/pluginAdvertising.twig index 5f4d678081c..c736c8d242d 100644 --- a/plugins/ProfessionalServices/templates/pluginAdvertising.twig +++ b/plugins/ProfessionalServices/templates/pluginAdvertising.twig @@ -24,4 +24,9 @@ {{ 'CoreAdminHome_LearnMore'|translate }} + {% if userCanDismiss %} +
+ {{ 'ProfessionalServices_DismissPromoWidget'|translate('', '')|raw }} +
+ {% endif %} diff --git a/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js b/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js index 443df27b276..913ac55eb52 100644 --- a/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js +++ b/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js @@ -96,4 +96,20 @@ describe("ProfessionalServices_PluginPromo", function () { expect(await page.screenshotSelector('.pluginPromo')).to.matchImage('promo_sessionrecordings'); }); + it('can dismiss a promo and no longer see it in menu', async function() { + const category = 'ProfessionalServices_PromoFormAnalytics'; + const subcategory = 'ProfessionalServices_PromoOverview'; + + await page.goto(urlBase + 'category=' + category + '&subcategory=' + subcategory); + await page.waitForNetworkIdle(); + + let promoSessionRecordingMenuItemCount = await page.evaluate(() => $('li.menuTab[data-category-id=ProfessionalServices_PromoFormAnalytics]').length); + expect(promoSessionRecordingMenuItemCount).to.equal(1); + + await page.click('.promo-dismiss a'); + await page.waitForNetworkIdle(); + + promoSessionRecordingMenuItemCount = await page.evaluate(() => $('li.menuTab[data-category-id=ProfessionalServices_PromoFormAnalytics]').length); + expect(promoSessionRecordingMenuItemCount).to.equal(0); + }); }); diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png index b35d4eafe47..28b1d2bd4b2 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13d58566743a89e7f492bbde6001739b9cb6a844a4bf31bce6c40aadfd646b48 -size 83141 +oid sha256:84016295b2dc749cedc975aad29a732e32aa89ab40d4098632b69d72072caf26 +size 87834 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png index b1ac1a9578d..81eb54f62ac 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1eac263f94472b943f4fd2fd1c5759afd2558bc8ea2ca2ea413c12e0bee296dc -size 98768 +oid sha256:c8523ca18b34a5032fc622d61439079d046b0137e9de77f75c4c165790b0cd05 +size 103329 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png index e74417a269f..907966cc941 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8e22f3b36a40ffe9eec7d4d27a711795464cc9ee3eeca080d475248e67c8935 -size 81156 +oid sha256:f473c52eab9d28e23da84d1154345d5669610a30b111423340694e24f16a13a4 +size 85649 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png index fce63fc2b1c..b32a303aa62 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e28a3ceefb283488f5d011e446128053b47fd544c5da80efecc1adcda1c59a6 -size 71249 +oid sha256:60ee01f775e940a562d5127c6a837840c49d1f4c5932f0fa30e65a79b853f958 +size 75802 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png index 269e3db698e..f0e08f283f8 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c5359a52f6b85f8d7988bad68b6c652242bcd2af53ec72289c85ceb8c9507f0 -size 72716 +oid sha256:0e2277d0be470ed28ef2d74046f4eaedc2fa72535f84d5e0c9ee74fb3351f8b3 +size 77299 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png index 4716143f237..2f05092edff 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de621f7992c3cf2451d7595c8ec394cc96e16b00d346b2d38897aec2f53ddce8 -size 189642 +oid sha256:9ac6c8166abc6947289f8ca0b7ac0fc253d28032c9555c387ea9489accdd27b4 +size 194243 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png index 25aa4da060e..c54497d1360 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9dc20f0478bafa7b1eba2fc2920fcf6ba09fb014c839a8db39ff131e6be2ffe4 -size 99660 +oid sha256:1a7bf23650e7283c689d6e586c05a5dbf2c41a93539488912424a2bd3371dcd5 +size 104246 diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png index fd137332373..593b4728e0e 100644 --- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png +++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90105ff967da90c4e73f064ed24c65d997b4b57e3f05c189c94a1b056c92282a -size 143124 +oid sha256:bd751f53798b043f9848ad2a2b609df9e05ef875fbd87dc4886d002963fbffde +size 147677 diff --git a/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php b/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php index 575822e3a57..1626238dfcf 100644 --- a/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php +++ b/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php @@ -12,13 +12,14 @@ use Piwik\Config; use Piwik\Plugin\Manager; use Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable; +use Piwik\Plugins\ProfessionalServices\PromoWidgetDismissal; class PromoWidgetApplicableTest extends TestCase { /** * @dataProvider checkDataProvider */ - public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForProfessionalServicesEnabled, bool $marketplaceEnabled, bool $internetAccessEnabled, bool $pluginActivated, bool $expected): void + public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForProfessionalServicesEnabled, bool $marketplaceEnabled, bool $internetAccessEnabled, bool $pluginActivated, bool $isDismissed, bool $expected): void { $manager = $this->createMock(Manager::class); $manager->method('isPluginActivated')->willReturnMap( @@ -35,27 +36,33 @@ public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForPr 'piwik_professional_support_ads_enabled' => $adsForProfessionalServicesEnabled, ]); - $sut = new PromoWidgetApplicable($manager, $config); - $this->assertEquals($expected, $sut->check('MyPlugin')); + $promoWidgetDismissal = $this->createMock(PromoWidgetDismissal::class); + $promoWidgetDismissal->method('isPromoWidgetDismissedForCurrentUser') + ->with('Any') + ->willReturn($isDismissed); + + $sut = new PromoWidgetApplicable($manager, $config, $promoWidgetDismissal); + $this->assertEquals($expected, $sut->check('MyPlugin', 'Any')); } protected function checkDataProvider(): \Generator { - yield [true, true, true, true, false]; - yield [true, true, true, false, true]; - yield [true, true, false, true, false]; - yield [true, true, false, false, false]; - yield [true, false, true, true, false]; - yield [true, false, true, false, false]; - yield [true, false, false, true, false]; - yield [true, false, false, false, false]; - yield [false, true, true, true, false]; - yield [false, true, true, false, false]; - yield [false, true, false, true, false]; - yield [false, true, false, false, false]; - yield [false, false, true, true, false]; - yield [false, false, true, false, false]; - yield [false, false, false, true, false]; - yield [false, false, false, false, false]; + yield [true, true, true, true, false, false]; + yield [true, true, true, false, false, true]; + yield [true, true, false, true, false, false]; + yield [true, true, false, false, false, false]; + yield [true, false, true, true, false, false]; + yield [true, false, true, false, false, false]; + yield [true, false, false, true, false, false]; + yield [true, false, false, false, false, false]; + yield [false, true, true, true, false, false]; + yield [false, true, true, false, false, false]; + yield [false, true, false, true, false, false]; + yield [false, true, false, false, false, false]; + yield [false, false, true, true, false, false]; + yield [false, false, true, false, false, false]; + yield [false, false, false, true, false, false]; + yield [false, false, false, false, false, false]; + yield [true, true, true, false, true, false]; } } diff --git a/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js new file mode 100644 index 00000000000..a783ee6e796 --- /dev/null +++ b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js @@ -0,0 +1,204 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("CoreHome")); + else if(typeof define === 'function' && define.amd) + define(["CoreHome"], factory); + else if(typeof exports === 'object') + exports["ProfessionalServices"] = factory(require("CoreHome")); + else + root["ProfessionalServices"] = factory(root["CoreHome"]); +})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "plugins/ProfessionalServices/vue/dist/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "fae3"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "19dc": +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__; + +/***/ }), + +/***/ "fae3": +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +// ESM COMPAT FLAG +__webpack_require__.r(__webpack_exports__); + +// EXPORTS +__webpack_require__.d(__webpack_exports__, "DismissPromoWidget", function() { return /* reexport */ DismissPromoWidget; }); + +// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js +// This file is imported into lib/wc client bundles. + +if (typeof window !== 'undefined') { + var currentScript = window.document.currentScript + if (false) { var getCurrentScript; } + + var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/) + if (src) { + __webpack_require__.p = src[1] // eslint-disable-line + } +} + +// Indicate to webpack that this file can be concatenated +/* harmony default export */ var setPublicPath = (null); + +// EXTERNAL MODULE: external "CoreHome" +var external_CoreHome_ = __webpack_require__("19dc"); + +// CONCATENATED MODULE: ./plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + +function onClickDismissPromoWidgetLink(binding, event) { + var widgetName = binding.value.widgetName; + var currentCategory = external_CoreHome_["ReportingMenuStore"].activeCategory.value; + event.preventDefault(); + external_CoreHome_["Matomo"].helper.showAjaxLoading(); + return external_CoreHome_["AjaxHelper"].post({ + method: 'ProfessionalServices.dismissWidget' + }, { + widgetName: widgetName + }).catch(function (e) { + external_CoreHome_["Matomo"].helper.hideAjaxLoading(); + throw e; + }).then(function () { + external_CoreHome_["ReportingMenuStore"].reloadMenuItems().then(function () { + external_CoreHome_["Matomo"].helper.hideAjaxLoading(); + external_CoreHome_["MatomoUrl"].updateHash('category=Dashboard_Dashboard&subcategory=1'); + external_CoreHome_["NotificationsStore"].show({ + id: 'ProfessionalServices_PromoWidgetDismissed', + animate: false, + context: 'info', + noclear: true, + message: Object(external_CoreHome_["translate"])('ProfessionalServices_DismissedNotification', Object(external_CoreHome_["translate"])(currentCategory)), + type: 'toast' + }); + }); + }); +} + +/* harmony default export */ var DismissPromoWidget = ({ + mounted: function mounted(element, binding) { + var widgetName = binding.value.widgetName; + + if (!widgetName) { + return; + } + + binding.value.onClickHandler = onClickDismissPromoWidgetLink.bind(null, binding); + element.addEventListener('click', binding.value.onClickHandler); + }, + unmounted: function unmounted(element, binding) { + element.removeEventListener('click', binding.value.onClickHandler); + } +}); +// CONCATENATED MODULE: ./plugins/ProfessionalServices/vue/src/index.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js + + + + +/***/ }) + +/******/ }); +}); +//# sourceMappingURL=ProfessionalServices.umd.js.map \ No newline at end of file diff --git a/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js new file mode 100644 index 00000000000..f55d9b78066 --- /dev/null +++ b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js @@ -0,0 +1,15 @@ +(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome")):"function"===typeof define&&define.amd?define(["CoreHome"],t):"object"===typeof exports?exports["ProfessionalServices"]=t(require("CoreHome")):e["ProfessionalServices"]=t(e["CoreHome"])})("undefined"!==typeof self?self:this,(function(e){return function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}return o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="plugins/ProfessionalServices/vue/dist/",o(o.s="fae3")}({"19dc":function(t,o){t.exports=e},fae3:function(e,t,o){"use strict";if(o.r(t),o.d(t,"DismissPromoWidget",(function(){return u})),"undefined"!==typeof window){var n=window.document.currentScript,r=n&&n.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);r&&(o.p=r[1])}var i=o("19dc"); +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +function a(e,t){var o=e.value.widgetName,n=i["ReportingMenuStore"].activeCategory.value;return t.preventDefault(),i["Matomo"].helper.showAjaxLoading(),i["AjaxHelper"].post({method:"ProfessionalServices.dismissWidget"},{widgetName:o}).catch((function(e){throw i["Matomo"].helper.hideAjaxLoading(),e})).then((function(){i["ReportingMenuStore"].reloadMenuItems().then((function(){i["Matomo"].helper.hideAjaxLoading(),i["MatomoUrl"].updateHash("category=Dashboard_Dashboard&subcategory=1"),i["NotificationsStore"].show({id:"ProfessionalServices_PromoWidgetDismissed",animate:!1,context:"info",noclear:!0,message:Object(i["translate"])("ProfessionalServices_DismissedNotification",Object(i["translate"])(n)),type:"toast"})}))}))}var u={mounted:function(e,t){var o=t.value.widgetName;o&&(t.value.onClickHandler=a.bind(null,t),e.addEventListener("click",t.value.onClickHandler))},unmounted:function(e,t){e.removeEventListener("click",t.value.onClickHandler)}}; +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */}})})); +//# sourceMappingURL=ProfessionalServices.umd.min.js.map \ No newline at end of file diff --git a/plugins/ProfessionalServices/vue/dist/umd.metadata.json b/plugins/ProfessionalServices/vue/dist/umd.metadata.json new file mode 100644 index 00000000000..9ecfcc04562 --- /dev/null +++ b/plugins/ProfessionalServices/vue/dist/umd.metadata.json @@ -0,0 +1,5 @@ +{ + "dependsOn": [ + "CoreHome" + ] +} \ No newline at end of file diff --git a/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts b/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts new file mode 100644 index 00000000000..581f276c14d --- /dev/null +++ b/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts @@ -0,0 +1,73 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { DirectiveBinding } from 'vue'; +import { + AjaxHelper, + Matomo, + MatomoUrl, + NotificationsStore, + ReportingMenuStore, + translate, +} from 'CoreHome'; + +interface DismissPromoWidgetDirectiveValue { + widgetName: string; + onClickHandler?: (event: Event) => void; +} + +function onClickDismissPromoWidgetLink( + binding: DirectiveBinding, + event: Event, +) { + const { widgetName } = binding.value; + const currentCategory = ReportingMenuStore.activeCategory.value as string; + + event.preventDefault(); + + Matomo.helper.showAjaxLoading(); + + return AjaxHelper.post({ + method: 'ProfessionalServices.dismissWidget', + }, { + widgetName, + }).catch((e) => { + Matomo.helper.hideAjaxLoading(); + throw e; + }).then(() => { + ReportingMenuStore.reloadMenuItems().then(() => { + Matomo.helper.hideAjaxLoading(); + MatomoUrl.updateHash('category=Dashboard_Dashboard&subcategory=1'); + NotificationsStore.show({ + id: 'ProfessionalServices_PromoWidgetDismissed', + animate: false, + context: 'info', + noclear: true, + message: translate('ProfessionalServices_DismissedNotification', translate(currentCategory)), + type: 'toast', + }); + }); + }); +} + +export default { + mounted(element: HTMLElement, binding: DirectiveBinding): void { + const { widgetName } = binding.value; + if (!widgetName) { + return; + } + + binding.value.onClickHandler = onClickDismissPromoWidgetLink.bind(null, binding); + element.addEventListener('click', binding.value.onClickHandler!); + }, + unmounted( + element: HTMLElement, + binding: DirectiveBinding, + ): void { + element.removeEventListener('click', binding.value.onClickHandler!); + }, +}; diff --git a/plugins/ProfessionalServices/vue/src/index.ts b/plugins/ProfessionalServices/vue/src/index.ts new file mode 100644 index 00000000000..f65ba894314 --- /dev/null +++ b/plugins/ProfessionalServices/vue/src/index.ts @@ -0,0 +1,8 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +export { default as DismissPromoWidget } from './DismissPromoWidget/DismissPromoWidget';