From 90cb7d667e8f8d4e8bdd8d0a4e3dfbe76c4d798d Mon Sep 17 00:00:00 2001 From: Thomas Steur Date: Fri, 22 Jun 2018 11:47:49 +1200 Subject: Validate premium feature license correctly when cancelled (#13064) * Check license correctly and disable if needed * make sure instance has internet features * make sure to verify it in tracker mode * because the text changed we need to change the translation key * prevent update check from being executed * simplify and improve performance * set caching time to 6 hours and invalidate cache when viewing subscriptions * fix tests * update ui screenshot --- plugins/Marketplace/Controller.php | 2 + plugins/Marketplace/Marketplace.php | 5 ++ plugins/Marketplace/Plugins.php | 33 ++++++++++++- plugins/Marketplace/Plugins/InvalidLicenses.php | 5 +- plugins/Marketplace/lang/en.json | 2 +- .../Integration/Plugins/InvalidLicensesTest.php | 4 +- .../Marketplace/tests/Integration/PluginsTest.php | 54 ++++++++++++++++++++++ ...ketplace_notification_plugincheck_noLicense.png | 4 +- ...-paid-access_token-consumer1_paid2_custom1.json | 2 +- ...s-201-access_token-consumer1_paid2_custom1.json | 2 +- 10 files changed, 103 insertions(+), 10 deletions(-) (limited to 'plugins/Marketplace') diff --git a/plugins/Marketplace/Controller.php b/plugins/Marketplace/Controller.php index 679ff77cf7..407eb86aa8 100644 --- a/plugins/Marketplace/Controller.php +++ b/plugins/Marketplace/Controller.php @@ -89,6 +89,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin // this is also like a self-repair to clear the caches :) $this->marketplaceApi->clearAllCacheEntries(); $this->consumer->clearCache(); + // invalidate cache for plugin/manager + Plugin\Manager::getLicenseCache()->flushAll(); $hasLicenseKey = $this->licenseKey->has(); diff --git a/plugins/Marketplace/Marketplace.php b/plugins/Marketplace/Marketplace.php index a274094544..a5991a9f80 100644 --- a/plugins/Marketplace/Marketplace.php +++ b/plugins/Marketplace/Marketplace.php @@ -32,6 +32,11 @@ class Marketplace extends \Piwik\Plugin ); } + public function isTrackerPlugin() + { + return true; + } + public function checkForUpdates() { $marketplace = StaticContainer::get('Piwik\Plugins\Marketplace\Api\Client'); diff --git a/plugins/Marketplace/Plugins.php b/plugins/Marketplace/Plugins.php index 558a83c4da..57c33e2131 100644 --- a/plugins/Marketplace/Plugins.php +++ b/plugins/Marketplace/Plugins.php @@ -64,6 +64,17 @@ class Plugins return $plugin; } + public function getLicenseValidInfo($pluginName) + { + $plugin = $this->marketplaceClient->getPluginInfo($pluginName); + $plugin = $this->enrichLicenseInformation($plugin); + + return array( + 'hasExceededLicense' => !empty($plugin['hasExceededLicense']), + 'isMissingLicense' => !empty($plugin['isMissingLicense']) + ); + } + public function getAvailablePluginNames($themesOnly) { if ($themesOnly) { @@ -238,8 +249,13 @@ class Plugins $plugin['isInvalid'] = $this->pluginManager->isPluginThirdPartyAndBogus($plugin['name']); $plugin['canBeUpdated'] = $plugin['isInstalled'] && $this->hasPluginUpdate($plugin); $plugin['lastUpdated'] = $this->toShortDate($plugin['lastUpdated']); - $plugin['hasExceededLicense'] = !empty($plugin['isInstalled']) && !empty($plugin['shop']) && empty($plugin['isFree']) && empty($plugin['isDownloadable']) && !empty($plugin['consumer']['license']['isValid']) && !empty($plugin['consumer']['license']['isExceeded']); - $plugin['isMissingLicense'] = !empty($plugin['isInstalled']) && !empty($plugin['shop']) && empty($plugin['isFree']) && empty($plugin['isDownloadable']) && empty($plugin['consumer']['license']); + + if ($plugin['isInstalled']) { + $plugin = $this->enrichLicenseInformation($plugin); + } else { + $plugin['hasExceededLicense'] = false; + $plugin['isMissingLicense'] = false; + } if (!empty($plugin['owner']) && strtolower($plugin['owner']) === 'piwikpro' @@ -273,6 +289,19 @@ class Plugins return $plugin; } + private function enrichLicenseInformation($plugin) + { + if (empty($plugin)) { + return $plugin; + } + + $isPremiumFeature = !empty($plugin['shop']) && empty($plugin['isFree']) && empty($plugin['isDownloadable']); + $plugin['hasExceededLicense'] = $isPremiumFeature && !empty($plugin['consumer']['license']['isValid']) && !empty($plugin['consumer']['license']['isExceeded']); + $plugin['isMissingLicense'] = $isPremiumFeature && (empty($plugin['consumer']['license']) || (!empty($plugin['consumer']['license']['status']) && $plugin['consumer']['license']['status'] === 'Cancelled')); + + return $plugin; + } + private function toLongDate($date) { if (!empty($date)) { diff --git a/plugins/Marketplace/Plugins/InvalidLicenses.php b/plugins/Marketplace/Plugins/InvalidLicenses.php index 7ad01eefd2..2ecd469de6 100644 --- a/plugins/Marketplace/Plugins/InvalidLicenses.php +++ b/plugins/Marketplace/Plugins/InvalidLicenses.php @@ -118,7 +118,7 @@ class InvalidLicenses $loginUrlEnd = ''; } - $message = $this->translator->translate('Marketplace_LicenseMissingDescription', array($plugins, '
', "" . $loginUrl, $loginUrlEnd. "")); + $message = $this->translator->translate('Marketplace_LicenseMissingDeactivatedDescription', array($plugins, '
', "" . $loginUrl, $loginUrlEnd. "")); if (Piwik::hasUserSuperUserAccess()) { $message .= ' ' . $this->getSubscritionSummaryMessage(); @@ -198,6 +198,9 @@ class InvalidLicenses $pluginNames['noLicense'][] = $pluginName; } elseif (!empty($plugin['consumer']['license']['isExceeded'])) { $pluginNames['exceeded'][] = $pluginName; + } elseif (isset($plugin['consumer']['license']['status']) + && $plugin['consumer']['license']['status'] === 'Cancelled') { + $pluginNames['noLicense'][] = $pluginName; } elseif (isset($plugin['consumer']['license']['isValid']) && empty($plugin['consumer']['license']['isValid'])) { $pluginNames['expired'][] = $pluginName; diff --git a/plugins/Marketplace/lang/en.json b/plugins/Marketplace/lang/en.json index a32f3cd724..6fcee18b43 100644 --- a/plugins/Marketplace/lang/en.json +++ b/plugins/Marketplace/lang/en.json @@ -31,7 +31,7 @@ "LicenseKeyDeletedSuccess": "License key successfully deleted.", "Exceeded": "Exceeded", "LicenseMissing": "License missing", - "LicenseMissingDescription": "You are using the following plugins without a license: %1$s. %2$sTo resolve this issue either update your license key, %3$sget a subscription now%4$s or deactivate the plugin.", + "LicenseMissingDeactivatedDescription": "The following plugins have been deactivated because you are using them without a license: %1$s. %2$sTo resolve this issue either update your license key, %3$sget a subscription now%4$s or deactivate the plugin.", "PluginLicenseMissingDescription": "You are not allowed to download this plugin because there is no license for this plugin. To resolve this issue either update your license key, get a subscription or uninstall the plugin.", "LicenseExceeded": "License exceeded", "LicenseExceededDescription": "The licenses for the following plugins are no longer valid as the number of authorized users for the license is exceeded: %1$s. %2$sYou will not be able to download updates for these plugins. To resolve this issue either delete some users or %3$supgrade the subscription now%4$s.", diff --git a/plugins/Marketplace/tests/Integration/Plugins/InvalidLicensesTest.php b/plugins/Marketplace/tests/Integration/Plugins/InvalidLicensesTest.php index b31aea1e2e..28c748dee0 100644 --- a/plugins/Marketplace/tests/Integration/Plugins/InvalidLicensesTest.php +++ b/plugins/Marketplace/tests/Integration/Plugins/InvalidLicensesTest.php @@ -223,7 +223,7 @@ class InvalidLicensesTest extends IntegrationTestCase $this->assertEquals('', $expired->getMessageExceededLicenses()); $this->assertEquals('', $expired->getMessageExpiredLicenses()); - $this->assertEquals('You are using the following plugins without a license: PaidPlugin1.
To resolve this issue either update your license key, get a subscription now or deactivate the plugin.
View your plugin subscriptions.', $expired->getMessageNoLicense()); + $this->assertEquals('The following plugins have been deactivated because you are using them without a license: PaidPlugin1.
To resolve this issue either update your license key, get a subscription now or deactivate the plugin.
View your plugin subscriptions.', $expired->getMessageNoLicense()); } public function test_getMessageExceededLicenses_getMessageExpiredLicenses_invalidLicenses_PaidPluginActivated() @@ -244,7 +244,7 @@ class InvalidLicensesTest extends IntegrationTestCase public function test_getMessageMissingLicenses_getMessageMissingLicenses_PaidPluginActivated() { $expired = $this->buildWithNoLicense(); - $this->assertEquals('You are using the following plugins without a license: PaidPlugin1.
To resolve this issue either update your license key, get a subscription now or deactivate the plugin.
View your plugin subscriptions.', $expired->getMessageNoLicense()); + $this->assertEquals('The following plugins have been deactivated because you are using them without a license: PaidPlugin1.
To resolve this issue either update your license key, get a subscription now or deactivate the plugin.
View your plugin subscriptions.', $expired->getMessageNoLicense()); } private function buildWithValidLicense() diff --git a/plugins/Marketplace/tests/Integration/PluginsTest.php b/plugins/Marketplace/tests/Integration/PluginsTest.php index b467b1477d..d61589a5a9 100644 --- a/plugins/Marketplace/tests/Integration/PluginsTest.php +++ b/plugins/Marketplace/tests/Integration/PluginsTest.php @@ -128,6 +128,60 @@ class PluginsTest extends IntegrationTestCase $this->assertSame('plugins', $this->service->action); } + public function test_getLicenseValidInfo_noSuchPluginExists() + { + $plugin = $this->plugins->getPluginInfo('fooBarBaz'); + $this->assertSame(array(), $plugin); + } + + public function test_getLicenseValidInfo_shouldEnrichLicenseInformation() + { + $this->service->returnFixture('v2.0_plugins_Barometer_info.json'); + $plugin = $this->plugins->getLicenseValidInfo('PaidPlugin1'); + + unset($plugin['versions']); + + $expected = array ( + 'hasExceededLicense' => false, + 'isMissingLicense' => false, + ); + $this->assertEquals($expected, $plugin); + } + + public function test_getLicenseValidInfo_missingLicense() + { + $this->service->returnFixture('v2.0_plugins_PaidPlugin1_info.json'); + $plugin = $this->plugins->getLicenseValidInfo('PaidPlugin1'); + + unset($plugin['versions']); + + $expected = array ( + 'hasExceededLicense' => false, + 'isMissingLicense' => true, + ); + $this->assertEquals($expected, $plugin); + } + + public function test_getLicenseValidInfo_validLicense() + { + $this->service->returnFixture('v2.0_consumer-access_token-consumer2_paid1.json'); + $plugin = $this->plugins->getLicenseValidInfo('Barometer'); + + unset($plugin['versions']); + + $expected = array ( + 'hasExceededLicense' => false, + 'isMissingLicense' => false, + ); + $this->assertEquals($expected, $plugin); + } + + public function test_getLicenseValidInfo_notInstalledPlugin_shouldCallCorrectService() + { + $this->plugins->getLicenseValidInfo('Barometer'); + $this->assertSame('plugins/Barometer/info', $this->service->action); + } + public function test_getPluginInfo_noSuchPluginExists() { $plugin = $this->plugins->getPluginInfo('fooBarBaz'); diff --git a/plugins/Marketplace/tests/UI/expected-screenshots/Marketplace_notification_plugincheck_noLicense.png b/plugins/Marketplace/tests/UI/expected-screenshots/Marketplace_notification_plugincheck_noLicense.png index 6e1ffbc3fe..de3802228e 100644 --- a/plugins/Marketplace/tests/UI/expected-screenshots/Marketplace_notification_plugincheck_noLicense.png +++ b/plugins/Marketplace/tests/UI/expected-screenshots/Marketplace_notification_plugincheck_noLicense.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c3de246e0bc1b50b8d7164bb4f9b1968fcf8008fe093e012204ffe01ba97a66 -size 22057 +oid sha256:501735f2e19db857247c19900d256680c207abebd5567a2699d92280482f6ff7 +size 24328 diff --git a/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-access_token-consumer1_paid2_custom1.json b/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-access_token-consumer1_paid2_custom1.json index 0124cefe39..93ec66b78e 100644 --- a/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-access_token-consumer1_paid2_custom1.json +++ b/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-access_token-consumer1_paid2_custom1.json @@ -108,7 +108,7 @@ "documentation":"", "changelog":""},"download":null}],"isDownloadable":false,"changelog":{"url":""},"consumer":{"license":{"startDate":"2014-05-27 04:46:05", "endDate":"2014-06-01 06:22:35", - "nextPaymentDate":null,"status":"Cancelled", + "nextPaymentDate":null,"status":"On Hold", "productType":"Up to 4 users", "isValid":false,"isExceeded":false,"isExpiredSoon":false},"loginUrl":"https:\/\/shop.piwik.org\/my-account"}},{"name":"PaidPlugin2", "displayName":"Paid Plugin 2", diff --git a/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-num_users-201-access_token-consumer1_paid2_custom1.json b/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-num_users-201-access_token-consumer1_paid2_custom1.json index 7d6ae78038..fe23e88d54 100644 --- a/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-num_users-201-access_token-consumer1_paid2_custom1.json +++ b/plugins/Marketplace/tests/resources/v2.0_plugins-purchase_type-paid-num_users-201-access_token-consumer1_paid2_custom1.json @@ -109,7 +109,7 @@ "documentation":"", "changelog":""},"download":null}],"isDownloadable":false,"changelog":{"url":""},"consumer":{"license":{"startDate":"2014-05-27 04:46:05", "endDate":"2014-06-01 06:22:35", - "nextPaymentDate":null,"status":"Cancelled", + "nextPaymentDate":null,"status":"On Hold", "productType":"Up to 4 users", "isValid":false,"isExceeded":false,"isExpiredSoon":false},"loginUrl":"https:\/\/shop.piwik.org\/my-account"}},{"name":"PaidPlugin2", "displayName":"Paid Plugin 2", -- cgit v1.2.3