From a4f2d5402c49b3e705f18b3ef52f75641386fa10 Mon Sep 17 00:00:00 2001 From: stoletniy Date: Thu, 21 Sep 2017 14:38:16 +0300 Subject: [PATCH] Manage MP3 permissions with WP role capabilities, add Members plugin support [MAILPOET-493] --- RoboFile.php | 1 + assets/css/src/admin-global.styl | 15 ++++ assets/css/src/icons.styl | 24 ++++++ assets/fonts/mailpoet.svg | 11 +++ assets/fonts/mailpoet.ttf | Bin 0 -> 1484 bytes assets/fonts/mailpoet.woff | Bin 0 -> 1560 bytes lib/Config/AccessControl.php | 50 +++++++----- lib/Config/Activator.php | 6 ++ lib/Config/Capabilities.php | 70 +++++++++++++++++ lib/Config/Initializer.php | 6 ++ lib/Config/Menu.php | 43 ++++------- tests/unit/Config/AccessControlTest.php | 6 ++ tests/unit/Config/CapabilitiesTest.php | 96 ++++++++++++++++++++++++ 13 files changed, 283 insertions(+), 45 deletions(-) create mode 100644 assets/css/src/admin-global.styl create mode 100644 assets/css/src/icons.styl create mode 100644 assets/fonts/mailpoet.svg create mode 100644 assets/fonts/mailpoet.ttf create mode 100644 assets/fonts/mailpoet.woff create mode 100644 lib/Config/Capabilities.php create mode 100644 tests/unit/Config/CapabilitiesTest.php 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 0000000000000000000000000000000000000000..de2d3b768d122bd8cf262e4f292613eb2107324f GIT binary patch literal 1484 zcmaJ>O^g#|7=FL+o1adnGt=KG?RJ-u#cok73u_7(kcF)128mg~jmCox+p)W*?bL2b zc8L)$>d9b?Kk?|v3sDY)gE58^2icQ}#Jh=xXu=8N1?n?Dm2z0$nQy-5ecqq%o%z0Z zh!K%aACREgSI+J`U}fd|V1C4SYo+7%ip#%zOvEbC&$Zn39_BRkU{18!n{U7P#_bQG zZxC^5)pMKX5A&Ca#CxzeRzawrh?~%_LEpXF8EmLqBticTdcN(ixKv{{^uM8}I_^e~ z67&`Hf1o>Ex8r?#_Z{Syc3_Rqdj9$VbwcDTft`fdVxN+XwZG=;AM?*oao@;(B+Ze|8tzA;m_ihxK1z8=R`S^Su8Wfic{?1^TB;Afd6R$ z*RUr8V(viW7|YDmnQD%1r3y8=RRLD9quB8$!lJ}@#<)?PsVc}~@i3DWr`X5CWa=O3 z30YxG(q;;VB@4#*ky1_QyC%|Qg+Dz}sWHYBwO|(t302YNoRXvQy_Ly5jMXM9&j__> z8CCOpYo{!8IlHjO*tNL$gl_Ll2vd_XnqjC3P19zIYNK{)cJj%iLR8ARaxzgi&E`VY z%D;Rq8S5`CUD9$TSqT2%D9`I!MV~%oN*pgHY15^IDHs<* zPT7SinQ_I;EllJcMb@)cNl`hRWZN)J!WoH^%MbXYajf9l1vV2l@5S zhvDgs=6I*kZ#ZX>ASOJwK`TMTt+CS-dB~9^QbM1w?|_n&rzsYkShNv+5$7CbeiT4t z(syxQhP6lC!AYgHm>1v=$5;Y>KgKftC_7@TfM1GnlFIaD&<~OZCJjr3Io9gMSb#qs zV+r`97|UeSbc_}7ug5q^&(hV7yVmac-hevfqPjM+`Q*`H+3T;b`CVs!eU_F%^l2St zpStAGeyY>#c7faRXS~*hw%eyONY_Fmj;-d;Lg(~o{O{suhSSo!FB9F`0pFvqQ~*SB&o?M literal 0 HcmV?d00001 diff --git a/assets/fonts/mailpoet.woff b/assets/fonts/mailpoet.woff new file mode 100644 index 0000000000000000000000000000000000000000..c4e91928ba79a2fd5728c14c387323bfebbefff7 GIT binary patch literal 1560 zcmaJ>&5smC6n|CKAJaWE-SgSQ%+4+itFwdRu&`zT1G1QLaf5^`;6~#?o1J!c&CK*V zJ4BX)oY0fO7(e3ClQ-eS!5G8A5F>jsk$=EMLp*qbc!BX(-3^Q}YjwY>-}}}3sOoyv z@1I^+AVz4~4ob>V-HLRH|J_cX-FJY9CBVKd#m=esS9;z$xK-%qq?DF_`KaSbpF_=iiU*1dtG0 z>=RP4wy*dW^sCXx{-il(Kp$@`Gp&@p1Lvco75$B7Oe4pTqL|Nk5(W%RSS zEpE^&^chj!Vm8Z8vCdAod_Uk_+(?%j?^1NZ&iYVli%jaIZ#MoeI>9Ub8 zt3vPxM|mM>RFl((EQMoZiZNYISb}jO)Ra@4QW@8*d~2fMYHBiPmo=TkNOjVdm3CCP zaD3xjE~!NIhBLo&{OrBA>?8L`a%jK)@i463XpB!<-bqwN*x(b^ZFJ{I+#ai@$U==G z#r7Bk?q8^xxQ|mzRxGX)eICbr6#0n*kwxFeaas63h^mv5PHQnQz#opW0{lUYRs5B9 z#8?Br6l0Sr^o6_+iUB5rnFw>_T92^+e>}zt@P{#0$)V{OYvA9Au}RO<^`5uZT@U;r z^~l5Ox`-B#Ps3$@u(1~O-TjSOS_Uzo4NM2rCztk9gJ!pL+>SrvcP@0j0i8jx4qZSd zj|STrF9iLe+wuGUz#IB)_xz^2xDuQSf_{S*!bJ?Rj1D68!=-sN#MlORo;J}h(h96j PLF9rP@({<5=fC(L-O9@l literal 0 HcmV?d00001 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(); + } +}