diff --git a/RoboFile.php b/RoboFile.php index b391a86a6d..d66318ce6b 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -90,6 +90,7 @@ class RoboFile extends \Robo\Tasks { $css_files = array( 'assets/css/src/admin.styl', + 'assets/css/src/admin-global.styl', 'assets/css/src/newsletter_editor/newsletter_editor.styl', 'assets/css/src/public.styl', 'assets/css/src/rtl.styl', diff --git a/assets/css/src/admin-global.styl b/assets/css/src/admin-global.styl new file mode 100644 index 0000000000..d0ae6315d1 --- /dev/null +++ b/assets/css/src/admin-global.styl @@ -0,0 +1,15 @@ +@import 'nib' + +@require 'icons' + +/* +Style for Members plugin +*/ + +.members-tab-title + .mailpoet-icon-logo + vertical-align: middle; + height: 20px; + width: 20px; + font-size: 20px; + margin-right: 3px; diff --git a/assets/css/src/icons.styl b/assets/css/src/icons.styl new file mode 100644 index 0000000000..51f75c662a --- /dev/null +++ b/assets/css/src/icons.styl @@ -0,0 +1,24 @@ +icon-font-path ?= "../fonts" + +@font-face + font-family 'mailpoet' + src url(icon-font-path + '/mailpoet.ttf?mx0b6n') format('truetype'), url(icon-font-path + '/mailpoet.woff?mx0b6n') format('woff'), url(icon-font-path + '/mailpoet.svg?mx0b6n#mailpoet') format('svg') + font-weight normal + font-style normal + +[class^="mailpoet-icon-"], [class*=" mailpoet-icon-"] + font-family 'mailpoet' !important + speak none + font-style normal + font-weight normal + font-variant normal + text-transform none + line-height 1 + + /* Better Font Rendering =========== */ + -webkit-font-smoothing antialiased + -moz-osx-font-smoothing grayscale + +.mailpoet-icon-logo + &:before + content "\e900" diff --git a/assets/fonts/mailpoet.svg b/assets/fonts/mailpoet.svg new file mode 100644 index 0000000000..aed88c06d3 --- /dev/null +++ b/assets/fonts/mailpoet.svg @@ -0,0 +1,11 @@ + + + +Generated by IcoMoon + + + + + + + \ No newline at end of file diff --git a/assets/fonts/mailpoet.ttf b/assets/fonts/mailpoet.ttf new file mode 100644 index 0000000000..de2d3b768d Binary files /dev/null and b/assets/fonts/mailpoet.ttf differ diff --git a/assets/fonts/mailpoet.woff b/assets/fonts/mailpoet.woff new file mode 100644 index 0000000000..c4e91928ba Binary files /dev/null and b/assets/fonts/mailpoet.woff differ diff --git a/lib/Config/AccessControl.php b/lib/Config/AccessControl.php index 97a142140d..79156b2630 100644 --- a/lib/Config/AccessControl.php +++ b/lib/Config/AccessControl.php @@ -8,26 +8,26 @@ if(!defined('ABSPATH')) exit; require_once(ABSPATH . 'wp-includes/pluggable.php'); class AccessControl { - const PERMISSION_ACCESS_PLUGIN_ADMIN = 'access_plugin_admin'; - const PERMISSION_MANAGE_SETTINGS = 'manage_settings'; - const PERMISSION_MANAGE_EMAILS = 'manage_emails'; - const PERMISSION_MANAGE_SUBSCRIBERS = 'manage_subscribers'; - const PERMISSION_MANAGE_FORMS = 'manage_forms'; - const PERMISSION_MANAGE_SEGMENTS = 'manage_segments'; - const PERMISSION_UPDATE_PLUGIN = 'update_plugin'; - const NO_ACCESS_RESTRICTION = 'no_access_restriction'; + const PERMISSION_ACCESS_PLUGIN_ADMIN = 'mailpoet_access_plugin_admin'; + const PERMISSION_MANAGE_SETTINGS = 'mailpoet_manage_settings'; + const PERMISSION_MANAGE_EMAILS = 'mailpoet_manage_emails'; + const PERMISSION_MANAGE_SUBSCRIBERS = 'mailpoet_manage_subscribers'; + const PERMISSION_MANAGE_FORMS = 'mailpoet_manage_forms'; + const PERMISSION_MANAGE_SEGMENTS = 'mailpoet_manage_segments'; + const PERMISSION_UPDATE_PLUGIN = 'mailpoet_update_plugin'; + const NO_ACCESS_RESTRICTION = 'mailpoet_no_access_restriction'; public $permissions; - public $current_user_roles; + public $user_roles; public $user_capabilities; function __construct() { - $this->permissions = $this->getDefaultPermissions(); + $this->permissions = self::getDefaultPermissions(); $this->user_roles = $this->getUserRoles(); $this->user_capabilities = $this->getUserCapabilities(); } - private function getDefaultPermissions() { + static function getDefaultPermissions() { return array( self::PERMISSION_ACCESS_PLUGIN_ADMIN => WPHooks::applyFilters( 'mailpoet_permission_access_plugin_admin', @@ -76,6 +76,18 @@ class AccessControl { ); } + static function getPermissionLabels() { + return array( + self::PERMISSION_ACCESS_PLUGIN_ADMIN => __('Access plugin admin', 'mailpoet'), + self::PERMISSION_MANAGE_SETTINGS => __('Manage settings', 'mailpoet'), + self::PERMISSION_MANAGE_EMAILS => __('Manage emails', 'mailpoet'), + self::PERMISSION_MANAGE_SUBSCRIBERS => __('Manage subscribers', 'mailpoet'), + self::PERMISSION_MANAGE_FORMS => __('Manage forms', 'mailpoet'), + self::PERMISSION_MANAGE_SEGMENTS => __('Manage segments', 'mailpoet'), + self::PERMISSION_UPDATE_PLUGIN => __('Update plugin', 'mailpoet'), + ); + } + function getUserRoles() { $user = wp_get_current_user(); return $user->roles; @@ -94,11 +106,13 @@ class AccessControl { function validatePermission($permission) { if($permission === self::NO_ACCESS_RESTRICTION) return true; - if(empty($this->permissions[$permission])) return false; - $permitted_roles = array_intersect( - $this->user_roles, - $this->permissions[$permission] - ); - return (!empty($permitted_roles)); + foreach($this->user_roles as $role) { + if($role_object = get_role($role)) { + if($role_object->has_cap($permission)) { + return true; + } + } + } + return false; } -} \ No newline at end of file +} diff --git a/lib/Config/Activator.php b/lib/Config/Activator.php index fe03cd7a0f..33f6e9a9ac 100644 --- a/lib/Config/Activator.php +++ b/lib/Config/Activator.php @@ -14,10 +14,16 @@ class Activator { $populator = new Populator(); $populator->up(); Setting::setValue('db_version', Env::$version); + + $caps = new Capabilities(); + $caps->setupWPCapabilities(); } function deactivate() { $migrator = new Migrator(); $migrator->down(); + + $caps = new Capabilities(); + $caps->removeWPCapabilities(); } } \ No newline at end of file diff --git a/lib/Config/Capabilities.php b/lib/Config/Capabilities.php new file mode 100644 index 0000000000..325324e507 --- /dev/null +++ b/lib/Config/Capabilities.php @@ -0,0 +1,70 @@ +setupMembersCapabilities(); + } + + function setupWPCapabilities() { + $permissions = AccessControl::getDefaultPermissions(); + $role_objects = array(); + foreach($permissions as $name => $roles) { + foreach($roles as $role) { + if(!isset($role_objects[$role])) { + $role_objects[$role] = get_role($role); + } + $role_objects[$role]->add_cap($name); + } + } + } + + function removeWPCapabilities() { + $permissions = AccessControl::getDefaultPermissions(); + $role_objects = array(); + foreach($permissions as $name => $roles) { + foreach($roles as $role) { + if(!isset($role_objects[$role])) { + $role_objects[$role] = get_role($role); + } + $role_objects[$role]->remove_cap($name); + } + } + } + + function setupMembersCapabilities() { + wp_enqueue_style('mailpoet-admin-global', Env::$assets_url . '/css/admin-global.css'); + Hooks::addAction('members_register_cap_groups', array($this, 'registerMembersCapGroup')); + Hooks::addAction('members_register_caps', array($this, 'registerMembersCapabilities')); + } + + function registerMembersCapGroup() { + members_register_cap_group( + self::MEMBERS_CAP_GROUP_NAME, + array( + 'label' => __('MailPoet', 'mailpoet'), + 'caps' => array(), + 'icon' => 'mailpoet-icon-logo', + 'priority' => 30 + ) + ); + } + + function registerMembersCapabilities() { + $permissions = AccessControl::getPermissionLabels(); + foreach($permissions as $name => $label) { + members_register_cap( + $name, + array( + 'label' => $label, + 'group' => self::MEMBERS_CAP_GROUP_NAME + ) + ); + } + } +} diff --git a/lib/Config/Initializer.php b/lib/Config/Initializer.php index 64e2dee971..2864930bea 100644 --- a/lib/Config/Initializer.php +++ b/lib/Config/Initializer.php @@ -125,6 +125,7 @@ class Initializer { $this->setupUpdater(); $this->setupLocalizer(); + $this->setupCapabilities(); $this->setupMenu(); $this->setupShortcodes(); $this->setupImages(); @@ -189,6 +190,11 @@ class Initializer { $localizer->init(); } + function setupCapabilities() { + $caps = new Capabilities(); + $caps->init(); + } + function setupMenu() { $menu = new Menu($this->renderer, Env::$assets_url, $this->access_control); $menu->init(); diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index 60849a541f..26e09c1a6f 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -37,7 +37,6 @@ class Menu { $this->renderer = $renderer; $this->assets_url = $assets_url; $this->access_control = $access_control; - $this->user_capability = $this->access_control->getUserFirstCapability(); $subscribers_feature = new SubscribersFeature(); $this->subscribers_over_limit = $subscribers_feature->check(); $this->checkMailPoetAPIKey(); @@ -70,7 +69,7 @@ class Menu { add_menu_page( 'MailPoet', 'MailPoet', - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, self::MAIN_PAGE_SLUG, null, $this->assets_url . '/img/menu_icon.png', @@ -78,12 +77,11 @@ class Menu { ); // Emails page - if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_EMAILS)) { $newsletters_page = add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Emails', 'mailpoet')), __('Emails', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_EMAILS, self::MAIN_PAGE_SLUG, array( $this, @@ -108,22 +106,20 @@ class Menu { true, $this->setPageTitle(__('Newsletter', 'mailpoet')), __('Newsletter Editor', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_EMAILS, 'mailpoet-newsletter-editor', array( $this, 'newletterEditor' ) ); - } // Forms page - if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_FORMS)) { $forms_page = add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Forms', 'mailpoet')), __('Forms', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_FORMS, 'mailpoet-forms', array( $this, @@ -148,22 +144,20 @@ class Menu { true, $this->setPageTitle(__('Form Editor', 'mailpoet')), __('Form Editor', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_FORMS, 'mailpoet-form-editor', array( $this, 'formEditor' ) ); - } // Subscribers page - if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SUBSCRIBERS)) { $subscribers_page = add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Subscribers', 'mailpoet')), __('Subscribers', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_SUBSCRIBERS, 'mailpoet-subscribers', array( $this, @@ -188,7 +182,7 @@ class Menu { 'admin.php?page=mailpoet-subscribers', $this->setPageTitle(__('Import', 'mailpoet')), __('Import', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_SUBSCRIBERS, 'mailpoet-import', array( $this, @@ -201,22 +195,20 @@ class Menu { true, $this->setPageTitle(__('Export', 'mailpoet')), __('Export', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_SUBSCRIBERS, 'mailpoet-export', array( $this, 'export' ) ); - } // Segments page - if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SEGMENTS)) { $segments_page = add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Lists', 'mailpoet')), __('Lists', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_SEGMENTS, 'mailpoet-segments', array( $this, @@ -235,29 +227,26 @@ class Menu { 'option' => 'mailpoet_segments_per_page' )); }); - } // Settings page - if($this->access_control->validatePermission(AccessControl::PERMISSION_MANAGE_SETTINGS)) { add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Settings', 'mailpoet')), __('Settings', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_MANAGE_SETTINGS, 'mailpoet-settings', array( $this, 'settings' ) ); - } // Help page add_submenu_page( self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Help', 'mailpoet')), __('Help', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, 'mailpoet-help', array( $this, @@ -271,7 +260,7 @@ class Menu { License::getLicense() ? true : self::MAIN_PAGE_SLUG, $this->setPageTitle(__('Premium', 'mailpoet')), __('Premium', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, 'mailpoet-premium', array( $this, @@ -284,7 +273,7 @@ class Menu { true, $this->setPageTitle(__('Welcome', 'mailpoet')), __('Welcome', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, 'mailpoet-welcome', array( $this, @@ -297,7 +286,7 @@ class Menu { true, $this->setPageTitle(__('Update', 'mailpoet')), __('Update', 'mailpoet'), - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, 'mailpoet-update', array( $this, @@ -310,7 +299,7 @@ class Menu { true, $this->setPageTitle(__('Migration', 'mailpoet')), '', - $this->user_capability, + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, 'mailpoet-migration', array( $this, @@ -658,7 +647,7 @@ class Menu { true, 'MailPoet', 'MailPoet', - $access_control->getUserFirstCapability(), + AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN, $_REQUEST['page'], array( __CLASS__, diff --git a/tests/unit/Config/AccessControlTest.php b/tests/unit/Config/AccessControlTest.php index 75302c4586..1196d13fae 100644 --- a/tests/unit/Config/AccessControlTest.php +++ b/tests/unit/Config/AccessControlTest.php @@ -109,6 +109,12 @@ class AccessControlTest extends \MailPoetTest { ); } + function testItGetsPermissionLabels() { + $permissions = AccessControl::getDefaultPermissions(); + $labels = AccessControl::getPermissionLabels(); + expect(count($permissions))->equals(count($labels)); + } + function _after() { WPHooksHelper::releaseAllHooks(); } diff --git a/tests/unit/Config/CapabilitiesTest.php b/tests/unit/Config/CapabilitiesTest.php new file mode 100644 index 0000000000..8ac57496c5 --- /dev/null +++ b/tests/unit/Config/CapabilitiesTest.php @@ -0,0 +1,96 @@ +caps = new Capabilities(); + } + + function testItInitializes() { + $caps = Stub::makeEmptyExcept( + $this->caps, + 'init', + array('setupMembersCapabilities' => Stub::once()), + $this + ); + $caps->init(); + } + + function testItSetsUpWPCapabilities() { + $permissions = AccessControl::getDefaultPermissions(); + $this->caps->setupWPCapabilities(); + $checked = false; + foreach($permissions as $name => $roles) { + foreach($roles as $role) { + $checked = true; + expect(get_role($role)->has_cap($name))->true(); + } + } + expect($checked)->true(); + } + + function testItRemovesWPCapabilities() { + $permissions = AccessControl::getDefaultPermissions(); + $this->caps->removeWPCapabilities(); + $checked = false; + foreach($permissions as $name => $roles) { + foreach($roles as $role) { + $checked = true; + expect(get_role($role)->has_cap($name))->false(); + } + } + expect($checked)->true(); + // Restore capabilities + $this->caps->setupWPCapabilities(); + } + + function testItSetsUpMembersCapabilities() { + WPHooksHelper::interceptAddAction(); + + $this->caps->setupMembersCapabilities(); + + $hook_name = 'members_register_cap_groups'; + expect(WPHooksHelper::isActionAdded($hook_name))->true(); + expect(is_callable(WPHooksHelper::getActionAdded($hook_name)[0]))->true(); + + $hook_name = 'members_register_caps'; + expect(WPHooksHelper::isActionAdded($hook_name))->true(); + expect(is_callable(WPHooksHelper::getActionAdded($hook_name)[0]))->true(); + } + + function testItRegistersMembersCapGroup() { + if(function_exists('members_register_cap_group')) { // Members plugin active + $this->caps->registerMembersCapGroup(); + expect_that(members_cap_group_exists(Capabilities::MEMBERS_CAP_GROUP_NAME)); + } else { + $func = Mock::func('MailPoet\Config', 'members_register_cap_group', true); + $this->caps->registerMembersCapGroup(); + $func->verifyInvoked([Capabilities::MEMBERS_CAP_GROUP_NAME]); + } + } + + function testItRegistersMembersCapabilities() { + $permissions = AccessControl::getPermissionLabels(); + $permission_count = count($permissions); + if(function_exists('members_register_cap')) { // Members plugin active + $this->caps->registerMembersCapabilities(); + expect(members_get_cap_group(Capabilities::MEMBERS_CAP_GROUP_NAME)->caps) + ->count($permission_count); + } else { + $func = Mock::func('MailPoet\Config', 'members_register_cap', true); + $this->caps->registerMembersCapabilities(); + $func->verifyInvokedMultipleTimes($permission_count); + } + } + + function _after() { + WPHooksHelper::releaseAllHooks(); + Mock::clean(); + } +}