From 7ebb7bac177ec42e2b51bdeea7be36d5bf9bb494 Mon Sep 17 00:00:00 2001 From: stoletniy Date: Thu, 11 May 2017 19:18:14 +0300 Subject: [PATCH] Add Premium installation/activation UI [PREMIUM-9] --- lib/API/JSON/v1/Services.php | 6 +- lib/Config/Initializer.php | 8 ++ lib/Config/Installer.php | 116 ++++++++++++++++++++++++ lib/Config/Menu.php | 2 + tests/unit/API/JSON/v1/ServicesTest.php | 4 + tests/unit/Config/InstallerTest.php | 70 ++++++++++++++ tests/unit/Services/BridgeTest.php | 17 +++- views/settings/premium.html | 30 +++++- 8 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 lib/Config/Installer.php create mode 100644 tests/unit/Config/InstallerTest.php diff --git a/lib/API/JSON/v1/Services.php b/lib/API/JSON/v1/Services.php index 555bae77a9..d2078c0ba1 100644 --- a/lib/API/JSON/v1/Services.php +++ b/lib/API/JSON/v1/Services.php @@ -3,6 +3,7 @@ namespace MailPoet\API\JSON\v1; use MailPoet\API\JSON\Endpoint as APIEndpoint; use MailPoet\API\JSON\Error as APIError; +use MailPoet\Config\Installer; use MailPoet\Services\Bridge; use MailPoet\Util\License\License; use MailPoet\WP\DateTime; @@ -97,10 +98,9 @@ class Services extends APIEndpoint { } if($success_message) { - $premium_plugin_active = License::getLicense(); return $this->successResponse( array('message' => $success_message), - array('premium_plugin_active' => $premium_plugin_active) + Installer::getPremiumStatus() ); } @@ -122,4 +122,4 @@ class Services extends APIEndpoint { return $this->errorResponse(array(APIError::BAD_REQUEST => $error)); } -} \ No newline at end of file +} diff --git a/lib/Config/Initializer.php b/lib/Config/Initializer.php index f9b8abfa8c..743d010dbe 100644 --- a/lib/Config/Initializer.php +++ b/lib/Config/Initializer.php @@ -75,6 +75,7 @@ class Initializer { try { $this->maybeDbUpdate(); $this->setupRenderer(); + $this->setupInstaller(); $this->setupLocalizer(); $this->setupMenu(); $this->setupAnalytics(); @@ -136,6 +137,13 @@ class Initializer { $this->renderer = new Renderer($caching, $debugging); } + function setupInstaller() { + $installer = new Installer( + Installer::PREMIUM_PLUGIN_SLUG + ); + $installer->init(); + } + function setupLocalizer() { $localizer = new Localizer($this->renderer); $localizer->init(); diff --git a/lib/Config/Installer.php b/lib/Config/Installer.php new file mode 100644 index 0000000000..696a56b3ad --- /dev/null +++ b/lib/Config/Installer.php @@ -0,0 +1,116 @@ +slug = $slug; + } + + function init() { + add_filter('plugins_api', array($this, 'getPluginInfo'), 10, 3); + } + + function getPluginInfo($data, $action = '', $args = null) { + if($action === 'plugin_information' + && isset($args->slug) + && $args->slug === $this->slug + ) { + $data = $this->retrievePluginInformation(); + } + + return $data; + } + + static function getPremiumStatus() { + $slug = self::PREMIUM_PLUGIN_SLUG; + + $premium_plugin_active = License::getLicense(); + $premium_plugin_installed = $premium_plugin_active || self::isPluginInstalled($slug); + $premium_install_url = $premium_plugin_installed ? '' : self::getPluginInstallUrl($slug); + $premium_activate_url = $premium_plugin_active ? '' : self::getPluginActivateUrl($slug); + + return compact( + 'premium_plugin_active', + 'premium_plugin_installed', + 'premium_install_url', + 'premium_activate_url' + ); + } + + static function isPluginInstalled($slug) { + $installed_plugin = self::getInstalledPlugin($slug); + return !empty($installed_plugin); + } + + static function getPluginInstallUrl($slug) { + $install_url = add_query_arg( + array( + 'action' => 'install-plugin', + 'plugin' => $slug, + '_wpnonce' => wp_create_nonce('install-plugin_' . $slug), + ), + self_admin_url('update.php') + ); + return $install_url; + } + + static function getPluginActivateUrl($slug) { + $plugin_file = self::getPluginFile($slug); + if(empty($plugin_file)) { + return false; + } + $activate_url = add_query_arg( + array( + 'action' => 'activate', + 'plugin' => $plugin_file, + '_wpnonce' => wp_create_nonce('activate-plugin_' . $plugin_file), + ), + self_admin_url('plugins.php') + ); + return $activate_url; + } + + private static function getInstalledPlugin($slug) { + $installed_plugin = array(); + if(is_dir(WP_PLUGIN_DIR . '/' . $slug)) { + $installed_plugin = get_plugins('/' . $slug); + } + return $installed_plugin; + } + + private static function getPluginFile($slug) { + $plugin_file = false; + $installed_plugin = self::getInstalledPlugin($slug); + if(!empty($installed_plugin)) { + $plugin_file = $slug . '/' . key($installed_plugin); + } + return $plugin_file; + } + + function retrievePluginInformation() { + $obj = new \stdClass(); + $obj->slug = $this->slug; + $obj->plugin_name = 'MailPoet Premium'; + $obj->new_version = '3.0.0-alpha.0.0.3.1'; + $obj->requires = '4.6'; + $obj->tested = '4.7.4'; + $obj->downloaded = 12540; + $obj->last_updated = date('Y-m-d'); + $obj->sections = array( + 'description' => 'The new version of the Premium plugin', + 'another_section' => 'This is another section', + 'changelog' => 'Some new features' + ); + $obj->download_link = home_url() . '/wp-content/uploads/mailpoet-premium.zip'; + $obj->package = $obj->download_link; + return $obj; + } +} diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index 0072f29d73..42bcdb86ad 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -350,6 +350,8 @@ class Menu { ) ); + $data = array_merge($data, Installer::getPremiumStatus()); + $this->displayPage('settings.html', $data); } diff --git a/tests/unit/API/JSON/v1/ServicesTest.php b/tests/unit/API/JSON/v1/ServicesTest.php index 568181a75c..f0ee1a580f 100644 --- a/tests/unit/API/JSON/v1/ServicesTest.php +++ b/tests/unit/API/JSON/v1/ServicesTest.php @@ -3,6 +3,7 @@ use Codeception\Util\Stub; use MailPoet\API\JSON\v1\Services; use MailPoet\API\JSON\Response as APIResponse; +use MailPoet\Config\Installer; use MailPoet\Services\Bridge; class ServicesTest extends MailPoetTest { @@ -89,6 +90,9 @@ class ServicesTest extends MailPoetTest { ); $response = $this->services_endpoint->checkPremiumKey($this->data); expect($response->status)->equals(APIResponse::STATUS_OK); + foreach (array_keys(Installer::getPremiumStatus()) as $key) { + expect(isset($response->meta[$key]))->true(); + } } function testItRespondsWithErrorIfPremiumKeyIsInvalid() { diff --git a/tests/unit/Config/InstallerTest.php b/tests/unit/Config/InstallerTest.php new file mode 100644 index 0000000000..edae3b0cd7 --- /dev/null +++ b/tests/unit/Config/InstallerTest.php @@ -0,0 +1,70 @@ +slug = 'some-plugin'; + + $this->installer = new Installer( + $this->slug + ); + } + + function testItInitializes() { + $installer = Stub::make( + $this->installer, + array( + 'getPluginInfo' => Stub::once() + ) + ); + $installer->init(); + apply_filters('plugins_api', null, null, null); + } + + function testItGetsPluginInfo() { + $args = new \StdClass; + $args->slug = $this->slug; + $result = $this->installer->getPluginInfo(false, 'plugin_information', $args); + expect($result->slug)->equals($this->slug); + expect($result->new_version)->notEmpty(); + expect($result->download_link)->notEmpty(); + expect($result->package)->notEmpty(); + } + + function testItIgnoresNonMatchingRequestsWhenGettingPluginInfo() { + $data = new \StdClass; + $data->some_property = '123'; + $result = $this->installer->getPluginInfo($data, 'some_action', null); + expect($result)->equals($data); + $args = new \StdClass; + $args->slug = 'different-slug'; + $result = $this->installer->getPluginInfo($data, 'plugin_information', $args); + expect($result)->equals($data); + } + + function testItGetsPremiumStatus() { + $status = Installer::getPremiumStatus(); + expect(isset($status['premium_plugin_active']))->true(); + expect(isset($status['premium_plugin_installed']))->true(); + expect(isset($status['premium_install_url']))->true(); + expect(isset($status['premium_activate_url']))->true(); + } + + function testItChecksIfAPluginIsInstalled() { + expect(Installer::isPluginInstalled(Env::$plugin_name))->true(); + expect(Installer::isPluginInstalled('some-non-existent-plugin-123'))->false(); + } + + function testItGetsPluginInstallUrl() { + expect(Installer::getPluginInstallUrl(Env::$plugin_name)) + ->startsWith(home_url() . '/wp-admin/update.php?action=install-plugin&plugin=mailpoet&_wpnonce='); + } + + function testItGetsPluginActivateUrl() { + expect(Installer::getPluginActivateUrl(Env::$plugin_name)) + ->startsWith(home_url() . '/wp-admin/plugins.php?action=activate&plugin=mailpoet/mailpoet.php&_wpnonce='); + } +} diff --git a/tests/unit/Services/BridgeTest.php b/tests/unit/Services/BridgeTest.php index 3090d1f348..18fdeb4098 100644 --- a/tests/unit/Services/BridgeTest.php +++ b/tests/unit/Services/BridgeTest.php @@ -77,23 +77,33 @@ class BridgeTest extends MailPoetTest { expect($this->getMSSKey())->notEquals($this->valid_key); } - function testItChecksPremiumKey() { + function testItChecksValidPremiumKey() { $result = $this->bridge->checkPremiumKey($this->valid_key); expect($result)->notEmpty(); expect($result['state'])->equals(Bridge::PREMIUM_KEY_VALID); + expect($this->getPremiumKey())->equals($this->valid_key); + } + function testItChecksInvalidPremiumKey() { $result = $this->bridge->checkPremiumKey($this->invalid_key); expect($result)->notEmpty(); expect($result['state'])->equals(Bridge::PREMIUM_KEY_INVALID); + expect($this->getPremiumKey())->equals($this->invalid_key); + } + function testItChecksAlreadyUsedPremiumKey() { $result = $this->bridge->checkPremiumKey($this->used_premium_key); expect($result)->notEmpty(); expect($result['state'])->equals(Bridge::PREMIUM_KEY_ALREADY_USED); + expect($this->getPremiumKey())->equals($this->used_premium_key); + } + function testItChecksExpiringPremiumKey() { $result = $this->bridge->checkPremiumKey($this->expiring_premium_key); expect($result)->notEmpty(); expect($result['state'])->equals(Bridge::PREMIUM_KEY_EXPIRING); expect($result['data']['expire_at'])->notEmpty(); + expect($this->getPremiumKey())->equals($this->expiring_premium_key); } function testItReturnsErrorStateOnEmptyAPIResponseCodeDuringPremiumCheck() { @@ -102,6 +112,7 @@ class BridgeTest extends MailPoetTest { $result = $this->bridge->checkPremiumKey($this->valid_key); expect($result)->notEmpty(); expect($result['state'])->equals(Bridge::PREMIUM_KEY_CHECK_ERROR); + expect($this->getPremiumKey())->notEquals($this->valid_key); } function testItUpdatesSubscriberCount() { @@ -169,6 +180,10 @@ class BridgeTest extends MailPoetTest { ); } + private function getPremiumKey() { + return Setting::getValue(Bridge::PREMIUM_KEY_SETTING_NAME); + } + function _after() { ORM::raw_execute('TRUNCATE ' . Setting::$_table); } diff --git a/views/settings/premium.html b/views/settings/premium.html index 3eaea8b595..53dae30961 100644 --- a/views/settings/premium.html +++ b/views/settings/premium.html @@ -47,11 +47,15 @@
+ <% if premium_plugin_installed or not(premium_key_valid) %> style="display: none;" <% endif %> > - <%= __('Download Premium now.') %> + <%= __('Install Premium now.') %> + <%= __("[link]Read guide[/link] on how to install Premium.") |replace({ @@ -62,6 +66,18 @@ %>
+
+ style="display: none;" + <% endif %> + > + <%= __('You need to activate the MailPoet Premium plugin.') %> + <%= __('Activate Premium.') %> +
@@ -98,7 +114,7 @@ function verifyMailPoetPremiumKey(key) { $('.mailpoet_premium_key_valid, .mailpoet_premium_key_invalid').addClass('mailpoet_hidden'); - $('.mailpoet_premium_download').hide(); + $('.mailpoet_premium_download, .mailpoet_premium_activate').hide(); return MailPoet.Ajax.post({ api_version: window.mailpoet_api_version, @@ -112,8 +128,14 @@ $('.mailpoet_notice_server').hide(); $('.mailpoet_premium_key_valid').text(response.data.message); $('.mailpoet_premium_key_valid').removeClass('mailpoet_hidden'); - if (!response.meta.premium_plugin_active) { + if (!response.meta.premium_plugin_installed) { + $('.mailpoet_premium_install_link') + .attr('href', response.meta.premium_install_url || '#'); $('.mailpoet_premium_download').show(); + } else if (!response.meta.premium_plugin_active) { + $('.mailpoet_premium_activate_link') + .attr('href', response.meta.premium_activate_url || '#'); + $('.mailpoet_premium_activate').show(); } }).fail(function(response) { if (response.errors.length > 0) {