Version Description
- Added a "Plugins" tab. It lets you hide specific plugins from other users. Note that this only affects the list on the "Plugins" page and tasks like editing plugin files, but it doesn't affect the admin menu.
- Tested up to WordPress 4.6-beta3.
Download this release
Release Info
Developer | whiteshadow |
Plugin | Admin Menu Editor |
Version | 1.7 |
Comparing to | |
See all releases |
Code changes from version 1.6.2 to 1.7
- includes/ame-utils.php +67 -0
- includes/ameArray.php +0 -11
- includes/editor-page.php +2 -6
- includes/menu-editor-core.php +46 -6
- includes/module.php +70 -0
- includes/settings-page.php +2 -7
- js/actor-manager.ts +1 -1
- js/menu-editor.js +8 -9
- menu-editor.php +1 -1
- modules/access-editor/access-editor.js +16 -14
- modules/actor-selector/actor-selector.js +16 -10
- modules/actor-selector/actor-selector.ts +18 -10
- modules/plugin-visibility/plugin-visibility-template.php +88 -0
- modules/plugin-visibility/plugin-visibility.css +44 -0
- modules/plugin-visibility/plugin-visibility.js +201 -0
- modules/plugin-visibility/plugin-visibility.php +435 -0
- modules/plugin-visibility/plugin-visibility.scss +53 -0
- modules/plugin-visibility/plugin-visibility.ts +293 -0
- readme.txt +6 -2
- uninstall.php +9 -0
includes/ame-utils.php
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Miscellaneous utility functions.
|
5 |
+
*/
|
6 |
+
class ameUtils {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Get a value from a nested array or object based on a path.
|
10 |
+
*
|
11 |
+
* @param array|object $array Get an entry from this array.
|
12 |
+
* @param array|string $path A list of array keys in hierarchy order, or a string path like "foo.bar.baz".
|
13 |
+
* @param mixed $default The value to return if the specified path is not found.
|
14 |
+
* @param string $separator Path element separator. Only applies to string paths.
|
15 |
+
* @return mixed
|
16 |
+
*/
|
17 |
+
public static function get($array, $path, $default = null, $separator = '.') {
|
18 |
+
if (is_string($path)) {
|
19 |
+
$path = explode($separator, $path);
|
20 |
+
}
|
21 |
+
if (empty($path)) {
|
22 |
+
return $default;
|
23 |
+
}
|
24 |
+
|
25 |
+
//Follow the $path into $input as far as possible.
|
26 |
+
$currentValue = $array;
|
27 |
+
$pathExists = true;
|
28 |
+
foreach($path as $node) {
|
29 |
+
if (is_array($currentValue) && array_key_exists($node, $currentValue)) {
|
30 |
+
$currentValue = $currentValue[$node];
|
31 |
+
} else if (is_object($currentValue) && property_exists($currentValue, $node)) {
|
32 |
+
$currentValue = $currentValue->$node;
|
33 |
+
} else {
|
34 |
+
$pathExists = false;
|
35 |
+
break;
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
if ($pathExists) {
|
40 |
+
return $currentValue;
|
41 |
+
}
|
42 |
+
return $default;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get the first non-root directory from a path.
|
47 |
+
*
|
48 |
+
* Examples:
|
49 |
+
* "foo/bar" => "foo"
|
50 |
+
* "/foo/bar/baz.txt" => "foo"
|
51 |
+
* "bar" => null
|
52 |
+
* "baz/" => "baz"
|
53 |
+
* "/" => null
|
54 |
+
*
|
55 |
+
* @param string $fileName
|
56 |
+
* @return string|null
|
57 |
+
*/
|
58 |
+
public static function getFirstDirectory($fileName) {
|
59 |
+
$fileName = ltrim($fileName, '/');
|
60 |
+
|
61 |
+
$segments = explode('/', $fileName, 2);
|
62 |
+
if ((count($segments) > 1) && ($segments[0] !== '')) {
|
63 |
+
return $segments[0];
|
64 |
+
}
|
65 |
+
return null;
|
66 |
+
}
|
67 |
+
}
|
includes/ameArray.php
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
/**
|
4 |
-
* Array utility functions, a la Lodash.
|
5 |
-
*/
|
6 |
-
class ameArray {
|
7 |
-
public static function get($array, $path, $default = null) {
|
8 |
-
//todo
|
9 |
-
return $default;
|
10 |
-
}
|
11 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
includes/editor-page.php
CHANGED
@@ -54,12 +54,8 @@ if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
|
54 |
}
|
55 |
|
56 |
?>
|
57 |
-
<div class="<?php echo esc_attr(implode(' ', $wrap_classes)); ?>">
|
58 |
-
<?php echo '<', WPMenuEditor::$admin_heading_tag, ' id="ws_ame_editor_heading">'; ?>
|
59 |
-
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
|
60 |
-
<?php echo '</', WPMenuEditor::$admin_heading_tag, '>'; ?>
|
61 |
|
62 |
-
|
63 |
|
64 |
<?php
|
65 |
if ( !empty($_GET['message']) ){
|
@@ -313,7 +309,7 @@ function ame_output_sort_buttons($icons) {
|
|
313 |
|
314 |
</div> <!-- / .ws_menu_editor -->
|
315 |
|
316 |
-
|
317 |
|
318 |
|
319 |
|
54 |
}
|
55 |
|
56 |
?>
|
|
|
|
|
|
|
|
|
57 |
|
58 |
+
<?php do_action('admin_menu_editor-display_header'); ?>
|
59 |
|
60 |
<?php
|
61 |
if ( !empty($_GET['message']) ){
|
309 |
|
310 |
</div> <!-- / .ws_menu_editor -->
|
311 |
|
312 |
+
<?php do_action('admin_menu_editor-display_footer'); ?>
|
313 |
|
314 |
|
315 |
|
includes/menu-editor-core.php
CHANGED
@@ -11,10 +11,12 @@ if (class_exists('WPMenuEditor')){
|
|
11 |
$thisDirectory = dirname(__FILE__);
|
12 |
require $thisDirectory . '/shadow_plugin_framework.php';
|
13 |
require $thisDirectory . '/role-utils.php';
|
|
|
14 |
require $thisDirectory . '/menu-item.php';
|
15 |
require $thisDirectory . '/menu.php';
|
16 |
require $thisDirectory . '/auto-versioning.php';
|
17 |
require $thisDirectory . '/ajax-helper.php';
|
|
|
18 |
|
19 |
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
20 |
const WPML_CONTEXT = 'admin-menu-editor menu texts';
|
@@ -240,12 +242,18 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
240 |
//Multisite: Clear role and capability caches when switching to another site.
|
241 |
add_action('switch_blog', array($this, 'clear_site_specific_caches'), 10, 0);
|
242 |
|
|
|
243 |
add_action('admin_menu_editor-display_tabs', array($this, 'display_editor_tabs'));
|
|
|
|
|
244 |
|
245 |
//Modules
|
246 |
include dirname(__FILE__) . '/../modules/actor-selector/actor-selector.php';
|
247 |
new ameActorSelector($this);
|
248 |
|
|
|
|
|
|
|
249 |
$proModuleDirectory = AME_ROOT_DIR . '/extras/modules';
|
250 |
if ( @is_dir($proModuleDirectory) ) {
|
251 |
//The widget module requires PHP 5.3.
|
@@ -256,6 +264,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
256 |
require_once $proModuleDirectory . '/dashboard-widget-editor/load.php';
|
257 |
new ameWidgetEditor($this);
|
258 |
}
|
|
|
|
|
|
|
|
|
|
|
259 |
}
|
260 |
|
261 |
//Set up the tabs for the menu editor page.
|
@@ -1990,6 +2003,25 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1990 |
require dirname(__FILE__) . '/editor-page.php';
|
1991 |
}
|
1992 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1993 |
/**
|
1994 |
* Display the tabs for the settings page.
|
1995 |
*/
|
@@ -2038,9 +2070,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2038 |
* @return bool
|
2039 |
*/
|
2040 |
protected function is_editor_page() {
|
2041 |
-
return
|
2042 |
-
&& isset($this->get['page']) && ($this->get['page'] == 'menu_editor')
|
2043 |
-
&& ($this->current_tab === 'editor');
|
2044 |
}
|
2045 |
|
2046 |
/**
|
@@ -2049,9 +2079,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2049 |
* @return bool
|
2050 |
*/
|
2051 |
protected function is_settings_page() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2052 |
return is_admin()
|
2053 |
-
|
2054 |
-
|
2055 |
}
|
2056 |
|
2057 |
/**
|
@@ -3236,7 +3276,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
3236 |
|
3237 |
}
|
3238 |
|
3239 |
-
|
3240 |
return apply_filters('admin_menu_editor_is_pro', false);
|
3241 |
}
|
3242 |
|
11 |
$thisDirectory = dirname(__FILE__);
|
12 |
require $thisDirectory . '/shadow_plugin_framework.php';
|
13 |
require $thisDirectory . '/role-utils.php';
|
14 |
+
require $thisDirectory . '/ame-utils.php';
|
15 |
require $thisDirectory . '/menu-item.php';
|
16 |
require $thisDirectory . '/menu.php';
|
17 |
require $thisDirectory . '/auto-versioning.php';
|
18 |
require $thisDirectory . '/ajax-helper.php';
|
19 |
+
require $thisDirectory . '/module.php';
|
20 |
|
21 |
class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
22 |
const WPML_CONTEXT = 'admin-menu-editor menu texts';
|
242 |
//Multisite: Clear role and capability caches when switching to another site.
|
243 |
add_action('switch_blog', array($this, 'clear_site_specific_caches'), 10, 0);
|
244 |
|
245 |
+
//Utility actions. Modules can use them in their templates.
|
246 |
add_action('admin_menu_editor-display_tabs', array($this, 'display_editor_tabs'));
|
247 |
+
add_action('admin_menu_editor-display_header', array($this, 'display_settings_page_header'));
|
248 |
+
add_action('admin_menu_editor-display_footer', array($this, 'display_settings_page_footer'));
|
249 |
|
250 |
//Modules
|
251 |
include dirname(__FILE__) . '/../modules/actor-selector/actor-selector.php';
|
252 |
new ameActorSelector($this);
|
253 |
|
254 |
+
include dirname(__FILE__) . '/../modules/plugin-visibility/plugin-visibility.php';
|
255 |
+
new amePluginVisibility($this);
|
256 |
+
|
257 |
$proModuleDirectory = AME_ROOT_DIR . '/extras/modules';
|
258 |
if ( @is_dir($proModuleDirectory) ) {
|
259 |
//The widget module requires PHP 5.3.
|
264 |
require_once $proModuleDirectory . '/dashboard-widget-editor/load.php';
|
265 |
new ameWidgetEditor($this);
|
266 |
}
|
267 |
+
|
268 |
+
if ( is_file($proModuleDirectory . '/super-users/super-users.php') ) {
|
269 |
+
require $proModuleDirectory . '/super-users/super-users.php';
|
270 |
+
new ameSuperUsers($this);
|
271 |
+
}
|
272 |
}
|
273 |
|
274 |
//Set up the tabs for the menu editor page.
|
2003 |
require dirname(__FILE__) . '/editor-page.php';
|
2004 |
}
|
2005 |
|
2006 |
+
/**
|
2007 |
+
* Display the header of the "Menu Editor" page.
|
2008 |
+
* This includes the page heading and tab list.
|
2009 |
+
*/
|
2010 |
+
public function display_settings_page_header() {
|
2011 |
+
echo '<div class="wrap">';
|
2012 |
+
printf(
|
2013 |
+
'<%1$s id="ws_ame_editor_heading">%2$s</%1$s>',
|
2014 |
+
self::$admin_heading_tag,
|
2015 |
+
apply_filters('admin_menu_editor-self_page_title', 'Menu Editor')
|
2016 |
+
);
|
2017 |
+
|
2018 |
+
do_action('admin_menu_editor-display_tabs');
|
2019 |
+
}
|
2020 |
+
|
2021 |
+
public function display_settings_page_footer() {
|
2022 |
+
echo '</div>'; //div.wrap
|
2023 |
+
}
|
2024 |
+
|
2025 |
/**
|
2026 |
* Display the tabs for the settings page.
|
2027 |
*/
|
2070 |
* @return bool
|
2071 |
*/
|
2072 |
protected function is_editor_page() {
|
2073 |
+
return $this->is_tab_open('editor');
|
|
|
|
|
2074 |
}
|
2075 |
|
2076 |
/**
|
2079 |
* @return bool
|
2080 |
*/
|
2081 |
protected function is_settings_page() {
|
2082 |
+
return $this->is_tab_open('settings');
|
2083 |
+
}
|
2084 |
+
|
2085 |
+
/**
|
2086 |
+
* Check if the specified AME settings tab is currently open.
|
2087 |
+
*
|
2088 |
+
* @param string $tab_slug
|
2089 |
+
* @return bool
|
2090 |
+
*/
|
2091 |
+
public function is_tab_open($tab_slug) {
|
2092 |
return is_admin()
|
2093 |
+
&& ($this->current_tab === $tab_slug)
|
2094 |
+
&& isset($this->get['page']) && ($this->get['page'] == 'menu_editor');
|
2095 |
}
|
2096 |
|
2097 |
/**
|
3276 |
|
3277 |
}
|
3278 |
|
3279 |
+
public function is_pro_version() {
|
3280 |
return apply_filters('admin_menu_editor_is_pro', false);
|
3281 |
}
|
3282 |
|
includes/module.php
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
abstract class ameModule {
|
3 |
+
protected $tabSlug = '';
|
4 |
+
protected $tabTitle = '';
|
5 |
+
protected $tabOrder = 10;
|
6 |
+
|
7 |
+
protected $moduleId = '';
|
8 |
+
protected $moduleDir = '';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var WPMenuEditor
|
12 |
+
*/
|
13 |
+
protected $menuEditor;
|
14 |
+
|
15 |
+
public function __construct($menuEditor) {
|
16 |
+
$this->menuEditor = $menuEditor;
|
17 |
+
|
18 |
+
if ( class_exists('ReflectionClass', false) ) {
|
19 |
+
$reflector = new ReflectionClass(get_class($this));
|
20 |
+
$this->moduleDir = dirname($reflector->getFileName());
|
21 |
+
$this->moduleId = basename($this->moduleDir);
|
22 |
+
}
|
23 |
+
|
24 |
+
//Register the module tab.
|
25 |
+
if ( ($this->tabSlug !== '') && is_string($this->tabSlug) ) {
|
26 |
+
add_action('admin_menu_editor-tabs', array($this, 'addTab'), $this->tabOrder);
|
27 |
+
add_action('admin_menu_editor-section-' . $this->tabSlug, array($this, 'displaySettingsPage'));
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
public function addTab($tabs) {
|
32 |
+
$tabs[$this->tabSlug] = !empty($this->tabTitle) ? $this->tabTitle : $this->tabSlug;
|
33 |
+
return $tabs;
|
34 |
+
}
|
35 |
+
|
36 |
+
public function displaySettingsPage() {
|
37 |
+
$this->menuEditor->display_settings_page_header();
|
38 |
+
|
39 |
+
if ( !$this->outputMainTemplate() ) {
|
40 |
+
printf("[ %1\$s : Module \"%2\$s\" doesn't have a primary template. ]", __METHOD__, $this->moduleId);
|
41 |
+
}
|
42 |
+
|
43 |
+
$this->menuEditor->display_settings_page_footer();
|
44 |
+
}
|
45 |
+
|
46 |
+
protected function getTabUrl($queryParameters = array()) {
|
47 |
+
$queryParameters = array_merge(
|
48 |
+
array(
|
49 |
+
'page' => 'menu_editor',
|
50 |
+
'sub_section' => $this->tabSlug
|
51 |
+
),
|
52 |
+
$queryParameters
|
53 |
+
);
|
54 |
+
return add_query_arg($queryParameters, admin_url('options-general.php'));
|
55 |
+
}
|
56 |
+
|
57 |
+
protected function outputMainTemplate() {
|
58 |
+
return $this->outputTemplate($this->moduleId);
|
59 |
+
}
|
60 |
+
|
61 |
+
protected function outputTemplate($name) {
|
62 |
+
$templateFile = $this->moduleDir . '/' . $name . '-template.php';
|
63 |
+
if ( file_exists($templateFile) ) {
|
64 |
+
/** @noinspection PhpIncludeInspection */
|
65 |
+
require $templateFile;
|
66 |
+
return true;
|
67 |
+
}
|
68 |
+
return false;
|
69 |
+
}
|
70 |
+
}
|
includes/settings-page.php
CHANGED
@@ -15,12 +15,7 @@ $formActionUrl = add_query_arg('noheader', 1, $settings_page_url);
|
|
15 |
$isProVersion = apply_filters('admin_menu_editor_is_pro', false);
|
16 |
?>
|
17 |
|
18 |
-
|
19 |
-
<<?php echo WPMenuEditor::$admin_heading_tag; ?> id="ws_ame_editor_heading">
|
20 |
-
<?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
|
21 |
-
</<?php echo WPMenuEditor::$admin_heading_tag; ?>>
|
22 |
-
|
23 |
-
<?php do_action('admin_menu_editor-display_tabs'); ?>
|
24 |
|
25 |
<form method="post" action="<?php echo esc_attr($formActionUrl); ?>" id="ws_plugin_settings_form">
|
26 |
|
@@ -282,7 +277,7 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
|
|
282 |
?>
|
283 |
</form>
|
284 |
|
285 |
-
|
286 |
|
287 |
<script type="text/javascript">
|
288 |
jQuery(function($) {
|
15 |
$isProVersion = apply_filters('admin_menu_editor_is_pro', false);
|
16 |
?>
|
17 |
|
18 |
+
<?php do_action('admin_menu_editor-display_header'); ?>
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
<form method="post" action="<?php echo esc_attr($formActionUrl); ?>" id="ws_plugin_settings_form">
|
21 |
|
277 |
?>
|
278 |
</form>
|
279 |
|
280 |
+
<?php do_action('admin_menu_editor-display_footer'); ?>
|
281 |
|
282 |
<script type="text/javascript">
|
283 |
jQuery(function($) {
|
js/actor-manager.ts
CHANGED
@@ -213,7 +213,7 @@ class AmeActorManager {
|
|
213 |
}
|
214 |
}
|
215 |
|
216 |
-
hasCap(actorId, capability, context?: {[actor: string] : any}): boolean {
|
217 |
context = context || {};
|
218 |
return this.actorHasCap(actorId, capability, [context, this.grantedCapabilities]);
|
219 |
}
|
213 |
}
|
214 |
}
|
215 |
|
216 |
+
hasCap(actorId: string, capability, context?: {[actor: string] : any}): boolean {
|
217 |
context = context || {};
|
218 |
return this.actorHasCap(actorId, capability, [context, this.grantedCapabilities]);
|
219 |
}
|
js/menu-editor.js
CHANGED
@@ -1381,7 +1381,6 @@ function encodeMenuAsJSON(tree){
|
|
1381 |
|
1382 |
//Compress the admin menu.
|
1383 |
tree = compressMenu(tree);
|
1384 |
-
console.log(tree); //xxxx debugging code
|
1385 |
|
1386 |
return $.toJSON(tree);
|
1387 |
}
|
@@ -1505,7 +1504,7 @@ function readItemState(itemDiv, position){
|
|
1505 |
position = (typeof position === 'undefined') ? 0 : position;
|
1506 |
|
1507 |
itemDiv = $(itemDiv);
|
1508 |
-
var item = $.extend({}, wsEditorData.blankMenuItem, itemDiv.data('menu_item'), readAllFields(itemDiv));
|
1509 |
|
1510 |
item.defaults = itemDiv.data('menu_item').defaults;
|
1511 |
|
@@ -2156,7 +2155,7 @@ function ameOnDomReady() {
|
|
2156 |
|
2157 |
AmeItemAccessEditor.setup({
|
2158 |
api: AmeEditorApi,
|
2159 |
-
|
2160 |
postTypes: wsEditorData.postTypes,
|
2161 |
taxonomies: wsEditorData.taxonomies,
|
2162 |
lodash: _,
|
@@ -3415,14 +3414,14 @@ function ameOnDomReady() {
|
|
3415 |
|
3416 |
//The new menu starts out rather bare
|
3417 |
var randomId = randomMenuId();
|
3418 |
-
var menu = $.extend({}, wsEditorData.blankMenuItem, {
|
3419 |
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
3420 |
template_id : '',
|
3421 |
menu_title : 'Custom Menu ' + ws_paste_count,
|
3422 |
file : randomId,
|
3423 |
-
items: []
|
3424 |
-
defaults: $.extend({}, itemTemplates.getDefaults(''))
|
3425 |
});
|
|
|
3426 |
|
3427 |
//Make it accessible only to the current actor if one is selected.
|
3428 |
if (actorSelectorWidget.selectedActor !== null) {
|
@@ -3771,14 +3770,14 @@ function ameOnDomReady() {
|
|
3771 |
|
3772 |
ws_paste_count++;
|
3773 |
|
3774 |
-
var entry = $.extend({}, wsEditorData.blankMenuItem, {
|
3775 |
custom: true,
|
3776 |
template_id : '',
|
3777 |
menu_title : 'Custom Item ' + ws_paste_count,
|
3778 |
file : randomMenuId(),
|
3779 |
-
items: []
|
3780 |
-
defaults: $.extend({}, itemTemplates.getDefaults(''))
|
3781 |
});
|
|
|
3782 |
|
3783 |
//Make it accessible to only the currently selected actor.
|
3784 |
if (actorSelectorWidget.selectedActor !== null) {
|
1381 |
|
1382 |
//Compress the admin menu.
|
1383 |
tree = compressMenu(tree);
|
|
|
1384 |
|
1385 |
return $.toJSON(tree);
|
1386 |
}
|
1504 |
position = (typeof position === 'undefined') ? 0 : position;
|
1505 |
|
1506 |
itemDiv = $(itemDiv);
|
1507 |
+
var item = $.extend(true, {}, wsEditorData.blankMenuItem, itemDiv.data('menu_item'), readAllFields(itemDiv));
|
1508 |
|
1509 |
item.defaults = itemDiv.data('menu_item').defaults;
|
1510 |
|
2155 |
|
2156 |
AmeItemAccessEditor.setup({
|
2157 |
api: AmeEditorApi,
|
2158 |
+
actorSelector: actorSelectorWidget,
|
2159 |
postTypes: wsEditorData.postTypes,
|
2160 |
taxonomies: wsEditorData.taxonomies,
|
2161 |
lodash: _,
|
3414 |
|
3415 |
//The new menu starts out rather bare
|
3416 |
var randomId = randomMenuId();
|
3417 |
+
var menu = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
3418 |
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
3419 |
template_id : '',
|
3420 |
menu_title : 'Custom Menu ' + ws_paste_count,
|
3421 |
file : randomId,
|
3422 |
+
items: []
|
|
|
3423 |
});
|
3424 |
+
menu.defaults = $.extend(true, {}, itemTemplates.getDefaults(''));
|
3425 |
|
3426 |
//Make it accessible only to the current actor if one is selected.
|
3427 |
if (actorSelectorWidget.selectedActor !== null) {
|
3770 |
|
3771 |
ws_paste_count++;
|
3772 |
|
3773 |
+
var entry = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
3774 |
custom: true,
|
3775 |
template_id : '',
|
3776 |
menu_title : 'Custom Item ' + ws_paste_count,
|
3777 |
file : randomMenuId(),
|
3778 |
+
items: []
|
|
|
3779 |
});
|
3780 |
+
entry.defaults = $.extend(true, {}, itemTemplates.getDefaults(''));
|
3781 |
|
3782 |
//Make it accessible to only the currently selected actor.
|
3783 |
if (actorSelectorWidget.selectedActor !== null) {
|
menu-editor.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
-
Version: 1.
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
9 |
*/
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
+
Version: 1.7
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
9 |
*/
|
modules/access-editor/access-editor.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
/* globals AmeCapabilityManager, jQuery */
|
2 |
|
3 |
window.AmeItemAccessEditor = (function ($) {
|
4 |
'use strict';
|
@@ -16,7 +16,7 @@ window.AmeItemAccessEditor = (function ($) {
|
|
16 |
var _,
|
17 |
api,
|
18 |
isProVersion = false,
|
19 |
-
|
20 |
postTypes,
|
21 |
taxonomies,
|
22 |
|
@@ -79,7 +79,9 @@ window.AmeItemAccessEditor = (function ($) {
|
|
79 |
return $(this).data('actor') === selectedActor;
|
80 |
}).addClass('ws_cpt_selected_role');
|
81 |
|
82 |
-
$editor.find('.ws_aed_selected_actor_name').text(
|
|
|
|
|
83 |
}
|
84 |
|
85 |
if (hasExtendedPermissions) {
|
@@ -323,6 +325,7 @@ window.AmeItemAccessEditor = (function ($) {
|
|
323 |
/**
|
324 |
* @param {AmeEditorApi} config.api
|
325 |
* @param {Object} config.actors
|
|
|
326 |
* @param {Object} config.postTypes
|
327 |
* @param {Object} config.taxonomies
|
328 |
* @param {lodash} config.lodash
|
@@ -334,7 +337,7 @@ window.AmeItemAccessEditor = (function ($) {
|
|
334 |
setup: function(config) {
|
335 |
_ = config.lodash;
|
336 |
api = config.api;
|
337 |
-
|
338 |
|
339 |
postTypes = config.postTypes;
|
340 |
taxonomies = config.taxonomies;
|
@@ -377,26 +380,25 @@ window.AmeItemAccessEditor = (function ($) {
|
|
377 |
|
378 |
//Generate the actor list.
|
379 |
var table = $editor.find('.ws_role_table_body tbody').empty(),
|
380 |
-
alternate = ''
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
}
|
385 |
|
386 |
-
var checkboxId = 'allow_' + actor.replace(/[^a-zA-Z0-9_]/g, '_');
|
387 |
var checkbox = $('<input type="checkbox">').addClass('ws_role_access').attr('id', checkboxId);
|
388 |
|
389 |
-
var actorHasAccess = api.actorCanAccessMenu(menuItem, actor);
|
390 |
checkbox.prop('checked', actorHasAccess);
|
391 |
|
392 |
alternate = (alternate === '') ? 'alternate' : '';
|
393 |
|
394 |
var cell = '<td>';
|
395 |
-
var row = $('<tr>').data('actor', actor).attr('class', alternate).append(
|
396 |
$(cell).addClass('ws_column_access').append(checkbox),
|
397 |
$(cell).addClass('ws_column_role post-title').append(
|
398 |
$('<label>').attr('for', checkboxId).append(
|
399 |
-
$('<span>').text(
|
400 |
)
|
401 |
),
|
402 |
$(cell).addClass('ws_column_selected_role_tip').append(
|
@@ -467,7 +469,7 @@ window.AmeItemAccessEditor = (function ($) {
|
|
467 |
|
468 |
if (hasExtendedPermissions) {
|
469 |
//Select either the currently selected actor, or just the first one.
|
470 |
-
setSelectedActor(state.selectedActor ||
|
471 |
|
472 |
//The permission table must be at least as tall as the actor list or the selected row won't look right.
|
473 |
var roleTable = $editor.find('.ws_role_table_body'),
|
1 |
+
/* globals AmeCapabilityManager, jQuery, AmeActors */
|
2 |
|
3 |
window.AmeItemAccessEditor = (function ($) {
|
4 |
'use strict';
|
16 |
var _,
|
17 |
api,
|
18 |
isProVersion = false,
|
19 |
+
actorSelector,
|
20 |
postTypes,
|
21 |
taxonomies,
|
22 |
|
79 |
return $(this).data('actor') === selectedActor;
|
80 |
}).addClass('ws_cpt_selected_role');
|
81 |
|
82 |
+
$editor.find('.ws_aed_selected_actor_name').text(
|
83 |
+
actorSelector.getNiceName(AmeActors.getActor(selectedActor))
|
84 |
+
);
|
85 |
}
|
86 |
|
87 |
if (hasExtendedPermissions) {
|
325 |
/**
|
326 |
* @param {AmeEditorApi} config.api
|
327 |
* @param {Object} config.actors
|
328 |
+
* @param {AmeActorSelector} config.actorSelector
|
329 |
* @param {Object} config.postTypes
|
330 |
* @param {Object} config.taxonomies
|
331 |
* @param {lodash} config.lodash
|
337 |
setup: function(config) {
|
338 |
_ = config.lodash;
|
339 |
api = config.api;
|
340 |
+
actorSelector = config.actorSelector;
|
341 |
|
342 |
postTypes = config.postTypes;
|
343 |
taxonomies = config.taxonomies;
|
380 |
|
381 |
//Generate the actor list.
|
382 |
var table = $editor.find('.ws_role_table_body tbody').empty(),
|
383 |
+
alternate = '',
|
384 |
+
visibleActors = actorSelector.getVisibleActors();
|
385 |
+
for(var index = 0; index < visibleActors.length; index++) {
|
386 |
+
var actor = visibleActors[index];
|
|
|
387 |
|
388 |
+
var checkboxId = 'allow_' + actor.id.replace(/[^a-zA-Z0-9_]/g, '_');
|
389 |
var checkbox = $('<input type="checkbox">').addClass('ws_role_access').attr('id', checkboxId);
|
390 |
|
391 |
+
var actorHasAccess = api.actorCanAccessMenu(menuItem, actor.id);
|
392 |
checkbox.prop('checked', actorHasAccess);
|
393 |
|
394 |
alternate = (alternate === '') ? 'alternate' : '';
|
395 |
|
396 |
var cell = '<td>';
|
397 |
+
var row = $('<tr>').data('actor', actor.id).attr('class', alternate).append(
|
398 |
$(cell).addClass('ws_column_access').append(checkbox),
|
399 |
$(cell).addClass('ws_column_role post-title').append(
|
400 |
$('<label>').attr('for', checkboxId).append(
|
401 |
+
$('<span>').text(actorSelector.getNiceName(actor))
|
402 |
)
|
403 |
),
|
404 |
$(cell).addClass('ws_column_selected_role_tip').append(
|
469 |
|
470 |
if (hasExtendedPermissions) {
|
471 |
//Select either the currently selected actor, or just the first one.
|
472 |
+
setSelectedActor(state.selectedActor || (visibleActors[0].id) || null);
|
473 |
|
474 |
//The permission table must be at least as tall as the actor list or the selected row won't look right.
|
475 |
var roleTable = $editor.find('.ws_role_table_body'),
|
modules/actor-selector/actor-selector.js
CHANGED
@@ -119,16 +119,7 @@ var AmeActorSelector = (function () {
|
|
119 |
actorSelector.append('<li><a href="#" class="current ws_actor_option ws_no_actor" data-text="All">All</a></li>');
|
120 |
var visibleActors = this.getVisibleActors();
|
121 |
for (var i = 0; i < visibleActors.length; i++) {
|
122 |
-
var actor = visibleActors[i];
|
123 |
-
var name_1 = actor.displayName;
|
124 |
-
if (actor instanceof AmeUser) {
|
125 |
-
if (actor.userLogin === this.currentUserLogin) {
|
126 |
-
name_1 = 'Current user (' + actor.userLogin + ')';
|
127 |
-
}
|
128 |
-
else {
|
129 |
-
name_1 = actor.displayName + ' (' + actor.userLogin + ')';
|
130 |
-
}
|
131 |
-
}
|
132 |
actorSelector.append($('<li></li>').append($('<a></a>')
|
133 |
.attr('href', '#' + actor.id)
|
134 |
.attr('data-text', name_1)
|
@@ -190,6 +181,21 @@ var AmeActorSelector = (function () {
|
|
190 |
'visible_users': jQuery.toJSON(this.visibleUsers)
|
191 |
});
|
192 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
AmeActorSelector._ = wsAmeLodash;
|
194 |
return AmeActorSelector;
|
195 |
}());
|
119 |
actorSelector.append('<li><a href="#" class="current ws_actor_option ws_no_actor" data-text="All">All</a></li>');
|
120 |
var visibleActors = this.getVisibleActors();
|
121 |
for (var i = 0; i < visibleActors.length; i++) {
|
122 |
+
var actor = visibleActors[i], name_1 = this.getNiceName(actor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
actorSelector.append($('<li></li>').append($('<a></a>')
|
124 |
.attr('href', '#' + actor.id)
|
125 |
.attr('data-text', name_1)
|
181 |
'visible_users': jQuery.toJSON(this.visibleUsers)
|
182 |
});
|
183 |
};
|
184 |
+
AmeActorSelector.prototype.getCurrentUserActor = function () {
|
185 |
+
return this.actorManager.getUser(this.currentUserLogin);
|
186 |
+
};
|
187 |
+
AmeActorSelector.prototype.getNiceName = function (actor) {
|
188 |
+
var name = actor.displayName;
|
189 |
+
if (actor instanceof AmeUser) {
|
190 |
+
if (actor.userLogin === this.currentUserLogin) {
|
191 |
+
name = 'Current user (' + actor.userLogin + ')';
|
192 |
+
}
|
193 |
+
else {
|
194 |
+
name = actor.displayName + ' (' + actor.userLogin + ')';
|
195 |
+
}
|
196 |
+
}
|
197 |
+
return name;
|
198 |
+
};
|
199 |
AmeActorSelector._ = wsAmeLodash;
|
200 |
return AmeActorSelector;
|
201 |
}());
|
modules/actor-selector/actor-selector.ts
CHANGED
@@ -165,16 +165,8 @@ class AmeActorSelector {
|
|
165 |
|
166 |
var visibleActors = this.getVisibleActors();
|
167 |
for (let i = 0; i < visibleActors.length; i++) {
|
168 |
-
const actor = visibleActors[i]
|
169 |
-
|
170 |
-
let name = actor.displayName;
|
171 |
-
if (actor instanceof AmeUser) {
|
172 |
-
if (actor.userLogin === this.currentUserLogin) {
|
173 |
-
name = 'Current user (' + actor.userLogin + ')';
|
174 |
-
} else {
|
175 |
-
name = actor.displayName + ' (' + actor.userLogin + ')';
|
176 |
-
}
|
177 |
-
}
|
178 |
|
179 |
actorSelector.append(
|
180 |
$('<li></li>').append(
|
@@ -257,4 +249,20 @@ class AmeActorSelector {
|
|
257 |
}
|
258 |
);
|
259 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
}
|
165 |
|
166 |
var visibleActors = this.getVisibleActors();
|
167 |
for (let i = 0; i < visibleActors.length; i++) {
|
168 |
+
const actor = visibleActors[i],
|
169 |
+
name = this.getNiceName(actor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
|
171 |
actorSelector.append(
|
172 |
$('<li></li>').append(
|
249 |
}
|
250 |
);
|
251 |
}
|
252 |
+
|
253 |
+
getCurrentUserActor(): AmeUser {
|
254 |
+
return this.actorManager.getUser(this.currentUserLogin);
|
255 |
+
}
|
256 |
+
|
257 |
+
getNiceName(actor: AmeBaseActor): string {
|
258 |
+
let name = actor.displayName;
|
259 |
+
if (actor instanceof AmeUser) {
|
260 |
+
if (actor.userLogin === this.currentUserLogin) {
|
261 |
+
name = 'Current user (' + actor.userLogin + ')';
|
262 |
+
} else {
|
263 |
+
name = actor.displayName + ' (' + actor.userLogin + ')';
|
264 |
+
}
|
265 |
+
}
|
266 |
+
return name;
|
267 |
+
}
|
268 |
}
|
modules/plugin-visibility/plugin-visibility-template.php
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php do_action('admin_menu_editor-display_header'); ?>
|
2 |
+
|
3 |
+
<div id="ame-plugin-visibility-editor">
|
4 |
+
<form method="post" data-bind="submit: saveChanges" class="ame-pv-save-form" action="<?php
|
5 |
+
echo esc_attr(add_query_arg(
|
6 |
+
array(
|
7 |
+
'page' => 'menu_editor',
|
8 |
+
'noheader' => '1',
|
9 |
+
'sub_section' => amePluginVisibility::TAB_SLUG,
|
10 |
+
),
|
11 |
+
admin_url('options-general.php')
|
12 |
+
));
|
13 |
+
?>">
|
14 |
+
|
15 |
+
<?php submit_button('Save Changes', 'primary', 'submit', false); ?>
|
16 |
+
|
17 |
+
<input type="hidden" name="action" value="save_plugin_visibility">
|
18 |
+
<?php wp_nonce_field('save_plugin_visibility'); ?>
|
19 |
+
|
20 |
+
<input type="hidden" name="settings" value="" data-bind="value: settingsData">
|
21 |
+
<input type="hidden" name="selected_actor" value="" data-bind="value: selectedActor">
|
22 |
+
</form>
|
23 |
+
|
24 |
+
<?php require AME_ROOT_DIR . '/modules/actor-selector/actor-selector-template.php'; ?>
|
25 |
+
|
26 |
+
<table class="widefat plugins">
|
27 |
+
<thead>
|
28 |
+
<tr>
|
29 |
+
<th scope="col" class="ame-check-column">
|
30 |
+
<!--suppress HtmlFormInputWithoutLabel -->
|
31 |
+
<input type="checkbox" data-bind="checked: areAllPluginsChecked">
|
32 |
+
</th>
|
33 |
+
<th scope="col">Plugin</th>
|
34 |
+
<th scope="col">Description</th>
|
35 |
+
</tr>
|
36 |
+
</thead>
|
37 |
+
|
38 |
+
<tbody data-bind="foreach: plugins">
|
39 |
+
<tr
|
40 |
+
data-bind="
|
41 |
+
css: {
|
42 |
+
'active': isActive,
|
43 |
+
'inactive': !isActive
|
44 |
+
}
|
45 |
+
">
|
46 |
+
|
47 |
+
<!--
|
48 |
+
Alas, we can't use the "check-column" class for this checkbox because WP would apply
|
49 |
+
the default "check all boxes" behaviour and override our Knockout bindings.
|
50 |
+
-->
|
51 |
+
<th scope="row" class="ame-check-column">
|
52 |
+
<!--suppress HtmlFormInputWithoutLabel -->
|
53 |
+
<input
|
54 |
+
type="checkbox"
|
55 |
+
data-bind="
|
56 |
+
checked: isChecked,
|
57 |
+
attr: {
|
58 |
+
id: 'ame-plugin-visible-' + $index(),
|
59 |
+
'data-plugin-file': fileName
|
60 |
+
}">
|
61 |
+
</th>
|
62 |
+
|
63 |
+
<td class="plugin-title">
|
64 |
+
<label data-bind="attr: { 'for': 'ame-plugin-visible-' + $index() }">
|
65 |
+
<strong data-bind="text: name"></strong>
|
66 |
+
</label>
|
67 |
+
</td>
|
68 |
+
|
69 |
+
<td><p data-bind="text: description"></p></td>
|
70 |
+
</tr>
|
71 |
+
</tbody>
|
72 |
+
|
73 |
+
<tfoot>
|
74 |
+
<tr>
|
75 |
+
<th scope="col" class="ame-check-column">
|
76 |
+
<!--suppress HtmlFormInputWithoutLabel -->
|
77 |
+
<input type="checkbox" data-bind="checked: areAllPluginsChecked">
|
78 |
+
</th>
|
79 |
+
<th scope="col">Plugin</th>
|
80 |
+
<th scope="col">Description</th>
|
81 |
+
</tr>
|
82 |
+
</tfoot>
|
83 |
+
|
84 |
+
</table>
|
85 |
+
|
86 |
+
</div> <!-- /module container -->
|
87 |
+
|
88 |
+
<?php do_action('admin_menu_editor-display_footer'); ?>
|
modules/plugin-visibility/plugin-visibility.css
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Plugin visibility module
|
3 |
+
------------------------
|
4 |
+
*/
|
5 |
+
.plugins thead .ame-check-column,
|
6 |
+
.plugins tfoot .ame-check-column {
|
7 |
+
padding: 4px 0 0 6px;
|
8 |
+
vertical-align: middle;
|
9 |
+
width: 2.2em; }
|
10 |
+
|
11 |
+
.plugins .ame-check-column {
|
12 |
+
vertical-align: top; }
|
13 |
+
|
14 |
+
/*
|
15 |
+
Plugin status indicator on the check column
|
16 |
+
*/
|
17 |
+
.plugins .active th.ame-check-column,
|
18 |
+
.plugin-update-tr.active td {
|
19 |
+
border-left: 4px solid #2ea2cc; }
|
20 |
+
|
21 |
+
.plugins thead th.ame-check-column,
|
22 |
+
.plugins tfoot th.ame-check-column,
|
23 |
+
.plugins .inactive th.ame-check-column {
|
24 |
+
padding-left: 6px; }
|
25 |
+
|
26 |
+
.plugins tbody th.ame-check-column,
|
27 |
+
.plugins tbody {
|
28 |
+
padding: 8px 0 0 2px; }
|
29 |
+
|
30 |
+
.plugins tbody th.ame-check-column input[type=checkbox] {
|
31 |
+
margin-top: 4px; }
|
32 |
+
|
33 |
+
/*
|
34 |
+
The "Save Changes" form
|
35 |
+
*/
|
36 |
+
.ame-pv-save-form {
|
37 |
+
float: right;
|
38 |
+
margin-top: 5px;
|
39 |
+
margin-bottom: 6px; }
|
40 |
+
|
41 |
+
#ws_actor_selector_container {
|
42 |
+
margin-right: 130px; }
|
43 |
+
|
44 |
+
/*# sourceMappingURL=plugin-visibility.css.map */
|
modules/plugin-visibility/plugin-visibility.js
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference path="../../js/knockout.d.ts" />
|
2 |
+
/// <reference path="../../js/jquery.d.ts" />
|
3 |
+
/// <reference path="../../js/jqueryui.d.ts" />
|
4 |
+
/// <reference path="../../js/lodash-3.10.d.ts" />
|
5 |
+
/// <reference path="../../modules/actor-selector/actor-selector.ts" />
|
6 |
+
var AmePluginVisibilityModule = (function () {
|
7 |
+
function AmePluginVisibilityModule(scriptData) {
|
8 |
+
var _this = this;
|
9 |
+
var _ = AmePluginVisibilityModule._;
|
10 |
+
this.actorSelector = new AmeActorSelector(AmeActors, scriptData.isProVersion);
|
11 |
+
//Wrap the selected actor in a computed observable so that it can be used with Knockout.
|
12 |
+
var _selectedActor = ko.observable(this.actorSelector.selectedActor);
|
13 |
+
this.selectedActor = ko.computed({
|
14 |
+
read: function () {
|
15 |
+
return _selectedActor();
|
16 |
+
},
|
17 |
+
write: function (newActor) {
|
18 |
+
_this.actorSelector.setSelectedActor(newActor);
|
19 |
+
}
|
20 |
+
});
|
21 |
+
this.actorSelector.onChange(function (newSelectedActor) {
|
22 |
+
_selectedActor(newSelectedActor);
|
23 |
+
});
|
24 |
+
//Re-select the previously selected actor, or select "All" (null) by default.
|
25 |
+
this.selectedActor(scriptData.selectedActor);
|
26 |
+
this.canRoleManagePlugins = scriptData.canManagePlugins;
|
27 |
+
this.isMultisite = scriptData.isMultisite;
|
28 |
+
this.grantAccessByDefault = {};
|
29 |
+
_.forEach(this.actorSelector.getVisibleActors(), function (actor) {
|
30 |
+
_this.grantAccessByDefault[actor.id] = ko.observable(_.get(scriptData.settings.grantAccessByDefault, actor.id, _this.canManagePlugins(actor)));
|
31 |
+
});
|
32 |
+
this.plugins = _.map(scriptData.installedPlugins, function (plugin) {
|
33 |
+
return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), _this);
|
34 |
+
});
|
35 |
+
this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
|
36 |
+
if (this.isMultisite) {
|
37 |
+
this.privilegedActors.push(AmeActors.getSuperAdmin());
|
38 |
+
}
|
39 |
+
this.areAllPluginsChecked = ko.computed({
|
40 |
+
read: function () {
|
41 |
+
return _.every(_this.plugins, function (plugin) {
|
42 |
+
return _this.isPluginVisible(plugin);
|
43 |
+
});
|
44 |
+
},
|
45 |
+
write: function (isChecked) {
|
46 |
+
if (_this.selectedActor() !== null) {
|
47 |
+
var canSeePluginsByDefault = _this.getGrantAccessByDefault(_this.selectedActor());
|
48 |
+
canSeePluginsByDefault(isChecked);
|
49 |
+
}
|
50 |
+
_.forEach(_this.plugins, function (plugin) {
|
51 |
+
_this.setPluginVisibility(plugin, isChecked);
|
52 |
+
});
|
53 |
+
}
|
54 |
+
});
|
55 |
+
//This observable will be populated when saving changes.
|
56 |
+
this.settingsData = ko.observable('');
|
57 |
+
}
|
58 |
+
AmePluginVisibilityModule.prototype.isPluginVisible = function (plugin) {
|
59 |
+
var actorId = this.selectedActor();
|
60 |
+
if (actorId === null) {
|
61 |
+
return plugin.isVisibleByDefault();
|
62 |
+
}
|
63 |
+
else {
|
64 |
+
var canSeePluginsByDefault = this.getGrantAccessByDefault(actorId), isVisible = plugin.getGrantObservable(actorId, plugin.isVisibleByDefault() && canSeePluginsByDefault());
|
65 |
+
return isVisible();
|
66 |
+
}
|
67 |
+
};
|
68 |
+
AmePluginVisibilityModule.prototype.setPluginVisibility = function (plugin, isVisible) {
|
69 |
+
var _this = this;
|
70 |
+
var selectedActor = this.selectedActor();
|
71 |
+
if (selectedActor === null) {
|
72 |
+
plugin.isVisibleByDefault(isVisible);
|
73 |
+
//Show/hide from everyone except the current user and Super Admin.
|
74 |
+
//However, don't enable plugins for roles that can't access the "Plugins" page in the first place.
|
75 |
+
var _1 = AmePluginVisibilityModule._;
|
76 |
+
_1.forEach(this.actorSelector.getVisibleActors(), function (actor) {
|
77 |
+
var allowAccess = plugin.getGrantObservable(actor.id, isVisible);
|
78 |
+
if (!_this.canManagePlugins(actor)) {
|
79 |
+
allowAccess(false);
|
80 |
+
}
|
81 |
+
else if (_1.includes(_this.privilegedActors, actor)) {
|
82 |
+
allowAccess(true);
|
83 |
+
}
|
84 |
+
else {
|
85 |
+
allowAccess(isVisible);
|
86 |
+
}
|
87 |
+
});
|
88 |
+
}
|
89 |
+
else {
|
90 |
+
//Show/hide from the selected role or user.
|
91 |
+
var allowAccess = plugin.getGrantObservable(selectedActor, isVisible);
|
92 |
+
allowAccess(isVisible);
|
93 |
+
}
|
94 |
+
};
|
95 |
+
AmePluginVisibilityModule.prototype.canManagePlugins = function (actor) {
|
96 |
+
var _this = this;
|
97 |
+
var _ = AmePluginVisibilityModule._;
|
98 |
+
if ((actor instanceof AmeRole) && _.has(this.canRoleManagePlugins, actor.name)) {
|
99 |
+
return this.canRoleManagePlugins[actor.name];
|
100 |
+
}
|
101 |
+
if (actor instanceof AmeSuperAdmin) {
|
102 |
+
return true;
|
103 |
+
}
|
104 |
+
if (actor instanceof AmeUser) {
|
105 |
+
//Can any of the user's roles manage plugins?
|
106 |
+
var result_1 = false;
|
107 |
+
_.forEach(actor.roles, function (roleId) {
|
108 |
+
if (_.get(_this.canRoleManagePlugins, roleId, false)) {
|
109 |
+
result_1 = true;
|
110 |
+
return false;
|
111 |
+
}
|
112 |
+
});
|
113 |
+
return (result_1 || AmeActors.hasCap(actor.id, 'activate_plugins'));
|
114 |
+
}
|
115 |
+
return false;
|
116 |
+
};
|
117 |
+
AmePluginVisibilityModule.prototype.getGrantAccessByDefault = function (actorId) {
|
118 |
+
if (!this.grantAccessByDefault.hasOwnProperty(actorId)) {
|
119 |
+
this.grantAccessByDefault[actorId] = ko.observable(this.canManagePlugins(AmeActors.getActor(actorId)));
|
120 |
+
}
|
121 |
+
return this.grantAccessByDefault[actorId];
|
122 |
+
};
|
123 |
+
AmePluginVisibilityModule.prototype.getSettings = function () {
|
124 |
+
var _ = AmePluginVisibilityModule._;
|
125 |
+
var result = {};
|
126 |
+
result.grantAccessByDefault = _.mapValues(this.grantAccessByDefault, function (allow) {
|
127 |
+
return allow();
|
128 |
+
});
|
129 |
+
result.plugins = {};
|
130 |
+
_.forEach(this.plugins, function (plugin) {
|
131 |
+
result.plugins[plugin.fileName] = {
|
132 |
+
isVisibleByDefault: plugin.isVisibleByDefault(),
|
133 |
+
grantAccess: _.mapValues(plugin.grantAccess, function (allow) {
|
134 |
+
return allow();
|
135 |
+
})
|
136 |
+
};
|
137 |
+
});
|
138 |
+
return result;
|
139 |
+
};
|
140 |
+
AmePluginVisibilityModule.prototype.saveChanges = function () {
|
141 |
+
var settings = this.getSettings();
|
142 |
+
//Remove settings associated with roles and users that no longer exist or are not visible.
|
143 |
+
var _ = AmePluginVisibilityModule._, visibleActorIds = _.pluck(this.actorSelector.getVisibleActors(), 'id');
|
144 |
+
_.forEach(settings.plugins, function (plugin) {
|
145 |
+
plugin.grantAccess = _.pick(plugin.grantAccess, visibleActorIds);
|
146 |
+
});
|
147 |
+
//Populate form field(s).
|
148 |
+
this.settingsData(jQuery.toJSON(settings));
|
149 |
+
return true;
|
150 |
+
};
|
151 |
+
AmePluginVisibilityModule._ = wsAmeLodash;
|
152 |
+
return AmePluginVisibilityModule;
|
153 |
+
}());
|
154 |
+
var AmePlugin = (function () {
|
155 |
+
function AmePlugin(details, visibility, module) {
|
156 |
+
var _this = this;
|
157 |
+
this.name = AmePlugin.stripAllTags(details.name);
|
158 |
+
this.description = AmePlugin.stripAllTags(details.description);
|
159 |
+
this.fileName = details.fileName;
|
160 |
+
this.isActive = details.isActive;
|
161 |
+
var _ = AmePluginVisibilityModule._;
|
162 |
+
this.isVisibleByDefault = ko.observable(_.get(visibility, 'isVisibleByDefault', true));
|
163 |
+
var emptyGrant = {};
|
164 |
+
this.grantAccess = _.mapValues(_.get(visibility, 'grantAccess', emptyGrant), function (hasAccess) {
|
165 |
+
return ko.observable(hasAccess);
|
166 |
+
});
|
167 |
+
this.isChecked = ko.computed({
|
168 |
+
read: function () {
|
169 |
+
return module.isPluginVisible(_this);
|
170 |
+
},
|
171 |
+
write: function (isVisible) {
|
172 |
+
return module.setPluginVisibility(_this, isVisible);
|
173 |
+
}
|
174 |
+
});
|
175 |
+
}
|
176 |
+
AmePlugin.prototype.getGrantObservable = function (actorId, defaultValue) {
|
177 |
+
if (defaultValue === void 0) { defaultValue = true; }
|
178 |
+
if (!this.grantAccess.hasOwnProperty(actorId)) {
|
179 |
+
this.grantAccess[actorId] = ko.observable(defaultValue);
|
180 |
+
}
|
181 |
+
return this.grantAccess[actorId];
|
182 |
+
};
|
183 |
+
AmePlugin.stripAllTags = function (input) {
|
184 |
+
//Based on: http://phpjs.org/functions/strip_tags/
|
185 |
+
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
|
186 |
+
return input.replace(commentsAndPhpTags, '').replace(tags, '');
|
187 |
+
};
|
188 |
+
return AmePlugin;
|
189 |
+
}());
|
190 |
+
jQuery(function ($) {
|
191 |
+
amePluginVisibility = new AmePluginVisibilityModule(wsPluginVisibilityData);
|
192 |
+
ko.applyBindings(amePluginVisibility, document.getElementById('ame-plugin-visibility-editor'));
|
193 |
+
//Permanently dismiss the usage hint via AJAX.
|
194 |
+
$('#ame-pv-usage-notice').on('click', '.notice-dismiss', function () {
|
195 |
+
$.post(wsPluginVisibilityData.adminAjaxUrl, {
|
196 |
+
'action': 'ws_ame_dismiss_pv_usage_notice',
|
197 |
+
'_ajax_nonce': wsPluginVisibilityData.dismissNoticeNonce
|
198 |
+
});
|
199 |
+
});
|
200 |
+
});
|
201 |
+
//# sourceMappingURL=plugin-visibility.js.map
|
modules/plugin-visibility/plugin-visibility.php
ADDED
@@ -0,0 +1,435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
class amePluginVisibility {
|
3 |
+
const OPTION_NAME = 'ws_ame_plugin_visibility';
|
4 |
+
const TAB_SLUG = 'plugin-visibility';
|
5 |
+
|
6 |
+
const HIDE_USAGE_NOTICE_FLAG = 'ws_ame_hide_pv_notice';
|
7 |
+
|
8 |
+
private static $lastInstance = null;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var WPMenuEditor
|
12 |
+
*/
|
13 |
+
private $menuEditor;
|
14 |
+
private $settings = array();
|
15 |
+
|
16 |
+
public function __construct($menuEditor) {
|
17 |
+
$this->menuEditor = $menuEditor;
|
18 |
+
self::$lastInstance = $this;
|
19 |
+
|
20 |
+
//Remove "hidden" plugins from the list on the "Plugins -> Installed Plugins" page.
|
21 |
+
add_filter('all_plugins', array($this, 'filterPluginList'));
|
22 |
+
|
23 |
+
//It's not possible to completely prevent a user from (de)activating "hidden" plugins because plugin API
|
24 |
+
//functions like activate_plugin() and deactivate_plugins() don't provide a way to abort (de)activation.
|
25 |
+
//However, we can still block edits and *some* other actions that WP verifies with check_admin_referer().
|
26 |
+
add_action('check_admin_referer', array($this, 'authorizePluginAction'));
|
27 |
+
|
28 |
+
//Register the plugin visibility tab.
|
29 |
+
add_action('admin_menu_editor-tabs', array($this, 'addSettingsTab'), 20);
|
30 |
+
add_action('admin_menu_editor-section-' . self::TAB_SLUG, array($this, 'displayUi'));
|
31 |
+
add_action('admin_menu_editor-header', array($this, 'handleFormSubmission'), 10, 2);
|
32 |
+
|
33 |
+
//Enqueue scripts and styles.
|
34 |
+
add_action('admin_menu_editor-enqueue_scripts-' . self::TAB_SLUG, array($this, 'enqueueScripts'));
|
35 |
+
add_action('admin_menu_editor-enqueue_styles-' . self::TAB_SLUG, array($this, 'enqueueStyles'));
|
36 |
+
|
37 |
+
//Display a usage hint in our tab.
|
38 |
+
add_action('admin_notices', array($this, 'displayUsageNotice'));
|
39 |
+
$dismissNoticeAction = new ameAjaxAction('ws_ame_dismiss_pv_usage_notice');
|
40 |
+
$dismissNoticeAction
|
41 |
+
->setAuthCallback(array($this->menuEditor, 'current_user_can_edit_menu'))
|
42 |
+
->setHandler(array($this, 'ajaxDismissUsageNotice'));
|
43 |
+
}
|
44 |
+
|
45 |
+
public function getSettings() {
|
46 |
+
if (!empty($this->settings)) {
|
47 |
+
return $this->settings;
|
48 |
+
}
|
49 |
+
|
50 |
+
if ( $this->menuEditor->get_plugin_option('menu_config_scope') === 'site' ) {
|
51 |
+
$json = get_option(self::OPTION_NAME, null);
|
52 |
+
} else {
|
53 |
+
$json = get_site_option(self::OPTION_NAME, null);
|
54 |
+
}
|
55 |
+
|
56 |
+
if ( is_string($json) ) {
|
57 |
+
$settings = json_decode($json, true);
|
58 |
+
} else {
|
59 |
+
$settings = array();
|
60 |
+
}
|
61 |
+
|
62 |
+
$this->settings = array_merge(
|
63 |
+
array(
|
64 |
+
'plugins' => array(),
|
65 |
+
'grantAccessByDefault' => array(),
|
66 |
+
),
|
67 |
+
$settings
|
68 |
+
);
|
69 |
+
|
70 |
+
return $this->settings;
|
71 |
+
}
|
72 |
+
|
73 |
+
private function saveSettings() {
|
74 |
+
//Save per site or site-wide based on plugin configuration.
|
75 |
+
$settings = json_encode($this->settings);
|
76 |
+
if ($this->menuEditor->get_plugin_option('menu_config_scope') === 'site') {
|
77 |
+
update_option(self::OPTION_NAME, $settings);
|
78 |
+
} else {
|
79 |
+
WPMenuEditor::atomic_update_site_option(self::OPTION_NAME, $settings);
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Check if a plugin is visible to the current user.
|
85 |
+
*
|
86 |
+
* Goals:
|
87 |
+
* - You can easily hide a plugin from everyone, including new roles. See: isVisibleByDefault
|
88 |
+
* - You can configure a role so that new plugins are hidden by default. See: grantAccessByDefault
|
89 |
+
* - You can change visibility per role and per user, just like with admin menus.
|
90 |
+
* - Roles that don't have access to plugins are not considered when deciding visibility.
|
91 |
+
* - Precedence order: user > super admin > all roles.
|
92 |
+
*
|
93 |
+
* @param string $pluginFileName Plugin file name as returned by plugin_basename().
|
94 |
+
* @param WP_User $user Current user.
|
95 |
+
* @return bool
|
96 |
+
*/
|
97 |
+
private function isPluginVisible($pluginFileName, $user = null) {
|
98 |
+
//TODO: Can we refactor this to be shorter?
|
99 |
+
static $isMultisite = null;
|
100 |
+
if (!isset($isMultisite)) {
|
101 |
+
$isMultisite = is_multisite();
|
102 |
+
}
|
103 |
+
|
104 |
+
if ($user === null) {
|
105 |
+
$user = wp_get_current_user();
|
106 |
+
}
|
107 |
+
$settings = $this->getSettings();
|
108 |
+
|
109 |
+
//Do we have custom settings for this plugin?
|
110 |
+
if (isset($settings['plugins'][$pluginFileName])) {
|
111 |
+
$isVisibleByDefault = $settings['plugins'][$pluginFileName]['isVisibleByDefault'];
|
112 |
+
$grantAccess = $settings['plugins'][$pluginFileName]['grantAccess'];
|
113 |
+
|
114 |
+
if ($isVisibleByDefault) {
|
115 |
+
$grantAccess = array_merge($settings['grantAccessByDefault'], $grantAccess);
|
116 |
+
}
|
117 |
+
} else {
|
118 |
+
$isVisibleByDefault = true;
|
119 |
+
$grantAccess = $settings['grantAccessByDefault'];
|
120 |
+
}
|
121 |
+
|
122 |
+
//User settings take precedence over everything else.
|
123 |
+
$userActor = 'user:' . $user->get('user_login');
|
124 |
+
if (isset($grantAccess[$userActor])) {
|
125 |
+
return $grantAccess[$userActor];
|
126 |
+
}
|
127 |
+
|
128 |
+
//Super Admin is next.
|
129 |
+
if ($isMultisite && is_super_admin($user->ID)) {
|
130 |
+
//By default the Super Admin has access to everything.
|
131 |
+
return ameUtils::get($grantAccess, 'special:super_admin', true);
|
132 |
+
}
|
133 |
+
|
134 |
+
//Finally, the user can see the plugin if at least one of their roles can.
|
135 |
+
$roles = $this->menuEditor->get_user_roles($user);
|
136 |
+
foreach ($roles as $roleId) {
|
137 |
+
if (ameUtils::get($grantAccess, 'role:' . $roleId, $isVisibleByDefault && $this->canManagePlugins($roleId))) {
|
138 |
+
return true;
|
139 |
+
}
|
140 |
+
}
|
141 |
+
|
142 |
+
return false;
|
143 |
+
}
|
144 |
+
|
145 |
+
|
146 |
+
/**
|
147 |
+
* @param string $roleId
|
148 |
+
* @param WP_Role $role
|
149 |
+
* @return bool
|
150 |
+
*/
|
151 |
+
private function canManagePlugins($roleId, $role = null) {
|
152 |
+
static $cache = array();
|
153 |
+
|
154 |
+
if (isset($cache[$roleId])) {
|
155 |
+
return $cache[$roleId];
|
156 |
+
}
|
157 |
+
|
158 |
+
//Any role that has any of the following capabilities has some degree of control over plugins,
|
159 |
+
//so plugin visibility settings apply to that role.
|
160 |
+
$pluginCaps = array(
|
161 |
+
'activate_plugins', 'install_plugins', 'edit_plugins', 'update_plugins', 'delete_plugins',
|
162 |
+
'manage_network_plugins',
|
163 |
+
);
|
164 |
+
|
165 |
+
if (!isset($role)) {
|
166 |
+
$role = get_role($roleId);
|
167 |
+
}
|
168 |
+
|
169 |
+
$result = false;
|
170 |
+
foreach ($pluginCaps as $cap) {
|
171 |
+
if ($role->has_cap($cap)) {
|
172 |
+
$result = true;
|
173 |
+
break;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
$cache[$roleId] = $result;
|
178 |
+
|
179 |
+
return $result;
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Filter a plugin list by removing plugins that are not visible to the current user.
|
184 |
+
*
|
185 |
+
* @param array $plugins
|
186 |
+
* @return array
|
187 |
+
*/
|
188 |
+
public function filterPluginList($plugins) {
|
189 |
+
$user = wp_get_current_user();
|
190 |
+
|
191 |
+
//Remove all hidden plugins.
|
192 |
+
$pluginFileNames = array_keys($plugins);
|
193 |
+
foreach($pluginFileNames as $fileName) {
|
194 |
+
if ( !$this->isPluginVisible($fileName, $user) ) {
|
195 |
+
unset($plugins[$fileName]);
|
196 |
+
}
|
197 |
+
}
|
198 |
+
|
199 |
+
return $plugins;
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Verify that the current user is allowed to see the plugin that they're trying to edit, activate or deactivate.
|
204 |
+
* Note that this doesn't catch bulk (de-)activation or various plugin management plugins.
|
205 |
+
*
|
206 |
+
* This is a callback for the "check_admin_referer" action.
|
207 |
+
* @param string $action
|
208 |
+
*/
|
209 |
+
public function authorizePluginAction($action) {
|
210 |
+
//Is the user trying to edit a plugin?
|
211 |
+
if (preg_match('@^edit-plugin_(?P<file>.+)$@', $action, $matches)) {
|
212 |
+
|
213 |
+
//The file that's being edited is part of a plugin. Find that plugin.
|
214 |
+
$fileName = wp_normalize_path($matches['file']);
|
215 |
+
$fileDirectory = ameUtils::getFirstDirectory($fileName);
|
216 |
+
$selectedPlugin = null;
|
217 |
+
|
218 |
+
$pluginFiles = array_keys(get_plugins());
|
219 |
+
foreach ($pluginFiles as $pluginFile) {
|
220 |
+
//Is the user editing the main plugin file?
|
221 |
+
if ($pluginFile === $fileName) {
|
222 |
+
$selectedPlugin = $pluginFile;
|
223 |
+
break;
|
224 |
+
}
|
225 |
+
|
226 |
+
//Is the file inside this plugin's directory?
|
227 |
+
$pluginDirectory = ameUtils::getFirstDirectory($pluginFile);
|
228 |
+
if (($pluginDirectory !== null) && ($pluginDirectory === $fileDirectory)) {
|
229 |
+
$selectedPlugin = $pluginFile;
|
230 |
+
break;
|
231 |
+
}
|
232 |
+
}
|
233 |
+
|
234 |
+
if ($selectedPlugin !== null) {
|
235 |
+
//Can the current user see the selected plugin?
|
236 |
+
$isVisible = $this->isPluginVisible($selectedPlugin);
|
237 |
+
|
238 |
+
if (!$isVisible) {
|
239 |
+
wp_die('You do not have sufficient permissions to edit this plugin.');
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
//Is the user trying to (de-)activate a single plugin?
|
244 |
+
} elseif (preg_match('@(?P<action>deactivate|activate)-plugin_(?P<plugin>.+)$@', $action, $matches)) {
|
245 |
+
//Can the current user see this plugin?
|
246 |
+
$isVisible = $this->isPluginVisible($matches['plugin']);
|
247 |
+
|
248 |
+
if (!$isVisible) {
|
249 |
+
wp_die(sprintf(
|
250 |
+
'You do not have sufficient permissions to %s this plugin.',
|
251 |
+
$matches['action']
|
252 |
+
));
|
253 |
+
}
|
254 |
+
|
255 |
+
//Are they acting on multiple plugins? One of them might be hidden.
|
256 |
+
} elseif (($action === 'bulk-plugins') && isset($_POST['checked']) && is_array($_POST['checked'])) {
|
257 |
+
|
258 |
+
$user = wp_get_current_user();
|
259 |
+
foreach ($_POST['checked'] as $pluginFile) {
|
260 |
+
if (!$this->isPluginVisible(strval($pluginFile), $user)) {
|
261 |
+
wp_die(sprintf(
|
262 |
+
'You do not have sufficient permissions to manage this plugin: "%s".',
|
263 |
+
$pluginFile
|
264 |
+
));
|
265 |
+
}
|
266 |
+
}
|
267 |
+
}
|
268 |
+
}
|
269 |
+
|
270 |
+
public function addSettingsTab($tabs) {
|
271 |
+
$tabs[self::TAB_SLUG] = 'Plugins';
|
272 |
+
return $tabs;
|
273 |
+
}
|
274 |
+
|
275 |
+
public function displayUi() {
|
276 |
+
require dirname(__FILE__) . '/plugin-visibility-template.php';
|
277 |
+
}
|
278 |
+
|
279 |
+
public function handleFormSubmission($action, $post = array()) {
|
280 |
+
//Note: We don't need to check user permissions here because plugin core already did.
|
281 |
+
if ( $action === 'save_plugin_visibility' ) {
|
282 |
+
check_admin_referer($action);
|
283 |
+
|
284 |
+
$this->settings = json_decode($post['settings'], true);
|
285 |
+
$this->saveSettings();
|
286 |
+
|
287 |
+
$params = array('updated' => 1);
|
288 |
+
|
289 |
+
//Re-select the same actor.
|
290 |
+
if ( !empty($post['selected_actor']) ) {
|
291 |
+
$params['selected_actor'] = strval($post['selected_actor']);
|
292 |
+
}
|
293 |
+
|
294 |
+
wp_redirect($this->getTabUrl($params));
|
295 |
+
exit;
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
private function getTabUrl($queryParameters = array()) {
|
300 |
+
$queryParameters = array_merge(
|
301 |
+
array(
|
302 |
+
'page' => 'menu_editor',
|
303 |
+
'sub_section' => self::TAB_SLUG
|
304 |
+
),
|
305 |
+
$queryParameters
|
306 |
+
);
|
307 |
+
return add_query_arg($queryParameters, admin_url('options-general.php'));
|
308 |
+
}
|
309 |
+
|
310 |
+
public function enqueueScripts() {
|
311 |
+
wp_register_auto_versioned_script(
|
312 |
+
'knockout',
|
313 |
+
plugins_url('js/knockout.js', $this->menuEditor->plugin_file)
|
314 |
+
);
|
315 |
+
|
316 |
+
wp_register_auto_versioned_script(
|
317 |
+
'ame-plugin-visibility',
|
318 |
+
plugins_url('plugin-visibility.js', __FILE__),
|
319 |
+
array('ame-lodash', 'knockout', 'ame-actor-selector', 'jquery-json',)
|
320 |
+
);
|
321 |
+
wp_enqueue_script('ame-plugin-visibility');
|
322 |
+
|
323 |
+
//Reselect the same actor.
|
324 |
+
$query = $this->menuEditor->get_query_params();
|
325 |
+
$selectedActor = null;
|
326 |
+
if ( isset($query['selected_actor']) ) {
|
327 |
+
$selectedActor = strval($query['selected_actor']);
|
328 |
+
}
|
329 |
+
|
330 |
+
$scriptData = $this->getScriptData();
|
331 |
+
$scriptData['selectedActor'] = $selectedActor;
|
332 |
+
wp_localize_script('ame-plugin-visibility', 'wsPluginVisibilityData', $scriptData);
|
333 |
+
}
|
334 |
+
|
335 |
+
public function getScriptData(){
|
336 |
+
//Pass the list of installed plugins and their state (active/inactive) to UI JavaScript.
|
337 |
+
$installedPlugins = get_plugins();
|
338 |
+
|
339 |
+
$activePlugins = array_map('plugin_basename', wp_get_active_and_valid_plugins());
|
340 |
+
$activeNetworkPlugins = array();
|
341 |
+
if (function_exists('wp_get_active_network_plugins')) {
|
342 |
+
//This function is only available on Multisite.
|
343 |
+
$activeNetworkPlugins = array_map('plugin_basename', wp_get_active_network_plugins());
|
344 |
+
}
|
345 |
+
|
346 |
+
$plugins = array();
|
347 |
+
foreach($installedPlugins as $pluginFile => $header) {
|
348 |
+
$isActiveForNetwork = in_array($pluginFile, $activeNetworkPlugins);
|
349 |
+
$isActive = in_array($pluginFile, $activePlugins);
|
350 |
+
|
351 |
+
$plugins[] = array(
|
352 |
+
'fileName' => $pluginFile,
|
353 |
+
'name' => $header['Name'],
|
354 |
+
'description' => isset($header['Description']) ? $header['Description'] : '',
|
355 |
+
'isActive' => $isActive || $isActiveForNetwork,
|
356 |
+
);
|
357 |
+
}
|
358 |
+
|
359 |
+
//Flag roles that can manage plugins.
|
360 |
+
$canManagePlugins = array();
|
361 |
+
$wpRoles = ameRoleUtils::get_roles();
|
362 |
+
foreach($wpRoles->role_objects as $id => $role) {
|
363 |
+
$canManagePlugins[$id] = $this->canManagePlugins($id, $role);
|
364 |
+
}
|
365 |
+
|
366 |
+
return array(
|
367 |
+
'settings' => $this->getSettings(),
|
368 |
+
'installedPlugins' => $plugins,
|
369 |
+
'canManagePlugins' => $canManagePlugins,
|
370 |
+
'isMultisite' => is_multisite(),
|
371 |
+
'isProVersion' => $this->menuEditor->is_pro_version(),
|
372 |
+
|
373 |
+
'dismissNoticeNonce' => wp_create_nonce('ws_ame_dismiss_pv_usage_notice'),
|
374 |
+
'adminAjaxUrl' => admin_url('admin-ajax.php'),
|
375 |
+
);
|
376 |
+
}
|
377 |
+
|
378 |
+
public function enqueueStyles() {
|
379 |
+
wp_enqueue_auto_versioned_style(
|
380 |
+
'ame-plugin-visibility-css',
|
381 |
+
plugins_url('plugin-visibility.css', __FILE__)
|
382 |
+
);
|
383 |
+
}
|
384 |
+
|
385 |
+
public function displayUsageNotice() {
|
386 |
+
if ( !$this->menuEditor->is_tab_open(self::TAB_SLUG) ) {
|
387 |
+
return;
|
388 |
+
}
|
389 |
+
|
390 |
+
//If the user has already made some changes, they probably don't need to see this notice any more.
|
391 |
+
$settings = $this->getSettings();
|
392 |
+
if ( !empty($settings['plugins']) ) {
|
393 |
+
return;
|
394 |
+
}
|
395 |
+
|
396 |
+
//The notice is dismissible.
|
397 |
+
if ( get_site_option(self::HIDE_USAGE_NOTICE_FLAG, false) ) {
|
398 |
+
return;
|
399 |
+
}
|
400 |
+
|
401 |
+
echo '<div class="notice notice-info is-dismissible" id="ame-pv-usage-notice">
|
402 |
+
<p>
|
403 |
+
<strong>Tip:</strong> This screen lets you hide plugins from other users.
|
404 |
+
These settings only affect the "Plugins" page, not the admin menu or the dashboard.
|
405 |
+
</p>
|
406 |
+
</div>';
|
407 |
+
}
|
408 |
+
|
409 |
+
public function ajaxDismissUsageNotice() {
|
410 |
+
$result = update_site_option(self::HIDE_USAGE_NOTICE_FLAG, true);
|
411 |
+
return array('success' => true, 'updateResult' => $result);
|
412 |
+
}
|
413 |
+
|
414 |
+
/**
|
415 |
+
* Get the most recently created instance of this class.
|
416 |
+
* Note: This function should only be used for testing purposes.
|
417 |
+
*
|
418 |
+
* @return amePluginVisibility|null
|
419 |
+
*/
|
420 |
+
public static function getLastCreatedInstance() {
|
421 |
+
return self::$lastInstance;
|
422 |
+
}
|
423 |
+
|
424 |
+
/**
|
425 |
+
* Remove any visibility settings associated with the specified plugin.
|
426 |
+
*
|
427 |
+
* @param string $pluginFile
|
428 |
+
*/
|
429 |
+
public function forgetPlugin($pluginFile) {
|
430 |
+
$settings = $this->getSettings();
|
431 |
+
unset($settings['plugins'][$pluginFile]);
|
432 |
+
$this->settings = $settings;
|
433 |
+
$this->saveSettings();
|
434 |
+
}
|
435 |
+
}
|
modules/plugin-visibility/plugin-visibility.scss
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Plugin visibility module
|
3 |
+
------------------------
|
4 |
+
*/
|
5 |
+
|
6 |
+
.plugins thead .ame-check-column,
|
7 |
+
.plugins tfoot .ame-check-column {
|
8 |
+
padding: 4px 0 0 6px;
|
9 |
+
vertical-align: middle;
|
10 |
+
width: 2.2em;
|
11 |
+
}
|
12 |
+
|
13 |
+
.plugins .ame-check-column {
|
14 |
+
vertical-align: top;
|
15 |
+
}
|
16 |
+
|
17 |
+
/*
|
18 |
+
Plugin status indicator on the check column
|
19 |
+
*/
|
20 |
+
.plugins .active th.ame-check-column,
|
21 |
+
.plugin-update-tr.active td {
|
22 |
+
border-left: 4px solid #2ea2cc;
|
23 |
+
}
|
24 |
+
|
25 |
+
.plugins thead th.ame-check-column,
|
26 |
+
.plugins tfoot th.ame-check-column,
|
27 |
+
.plugins .inactive th.ame-check-column {
|
28 |
+
padding-left: 6px;
|
29 |
+
}
|
30 |
+
|
31 |
+
.plugins tbody th.ame-check-column,
|
32 |
+
.plugins tbody {
|
33 |
+
padding: 8px 0 0 2px;
|
34 |
+
}
|
35 |
+
|
36 |
+
.plugins tbody th.ame-check-column input[type=checkbox] {
|
37 |
+
margin-top: 4px;
|
38 |
+
}
|
39 |
+
|
40 |
+
/*
|
41 |
+
The "Save Changes" form
|
42 |
+
*/
|
43 |
+
|
44 |
+
.ame-pv-save-form {
|
45 |
+
float: right;
|
46 |
+
margin-top: 5px;
|
47 |
+
margin-bottom: 6px;
|
48 |
+
}
|
49 |
+
|
50 |
+
//Make room for th save button.
|
51 |
+
#ws_actor_selector_container {
|
52 |
+
margin-right: 130px;
|
53 |
+
}
|
modules/plugin-visibility/plugin-visibility.ts
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference path="../../js/knockout.d.ts" />
|
2 |
+
/// <reference path="../../js/jquery.d.ts" />
|
3 |
+
/// <reference path="../../js/jqueryui.d.ts" />
|
4 |
+
/// <reference path="../../js/lodash-3.10.d.ts" />
|
5 |
+
/// <reference path="../../modules/actor-selector/actor-selector.ts" />
|
6 |
+
|
7 |
+
declare var amePluginVisibility: AmePluginVisibilityModule;
|
8 |
+
declare var wsPluginVisibilityData: PluginVisibilityScriptData;
|
9 |
+
|
10 |
+
interface PluginVisibilityScriptData {
|
11 |
+
isMultisite: boolean,
|
12 |
+
canManagePlugins: {[roleId : string] : boolean},
|
13 |
+
selectedActor: string,
|
14 |
+
installedPlugins: Array<PvPluginInfo>,
|
15 |
+
settings: PluginVisibilitySettings,
|
16 |
+
isProVersion: boolean,
|
17 |
+
|
18 |
+
adminAjaxUrl: string,
|
19 |
+
dismissNoticeNonce: string
|
20 |
+
}
|
21 |
+
|
22 |
+
interface PluginVisibilitySettings {
|
23 |
+
grantAccessByDefault: GrantAccessMap,
|
24 |
+
plugins: {
|
25 |
+
[fileName : string] : {
|
26 |
+
isVisibleByDefault: boolean,
|
27 |
+
grantAccess: GrantAccessMap
|
28 |
+
}
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
interface GrantAccessMap {
|
33 |
+
[actorId : string] : boolean
|
34 |
+
}
|
35 |
+
|
36 |
+
interface PvPluginInfo {
|
37 |
+
name: string,
|
38 |
+
fileName: string,
|
39 |
+
description: string,
|
40 |
+
isActive: boolean
|
41 |
+
}
|
42 |
+
|
43 |
+
class AmePluginVisibilityModule {
|
44 |
+
static _ = wsAmeLodash;
|
45 |
+
|
46 |
+
plugins: Array<AmePlugin>;
|
47 |
+
private canRoleManagePlugins: {[roleId: string] : boolean};
|
48 |
+
grantAccessByDefault: {[actorId: string] : KnockoutObservable<boolean>};
|
49 |
+
private isMultisite: boolean;
|
50 |
+
|
51 |
+
actorSelector: AmeActorSelector;
|
52 |
+
selectedActor: KnockoutComputed<string>;
|
53 |
+
settingsData: KnockoutObservable<string>;
|
54 |
+
|
55 |
+
areAllPluginsChecked: KnockoutComputed<boolean>;
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Actors that don't lose access to a plugin when you uncheck it in the "All" view.
|
59 |
+
* This is a convenience feature that lets the user quickly hide a bunch of plugins from everyone else.
|
60 |
+
*/
|
61 |
+
private privilegedActors: Array<AmeBaseActor>;
|
62 |
+
|
63 |
+
constructor(scriptData: PluginVisibilityScriptData) {
|
64 |
+
const _ = AmePluginVisibilityModule._;
|
65 |
+
|
66 |
+
this.actorSelector = new AmeActorSelector(AmeActors, scriptData.isProVersion);
|
67 |
+
|
68 |
+
//Wrap the selected actor in a computed observable so that it can be used with Knockout.
|
69 |
+
var _selectedActor = ko.observable(this.actorSelector.selectedActor);
|
70 |
+
this.selectedActor = ko.computed<string>({
|
71 |
+
read: function () {
|
72 |
+
return _selectedActor();
|
73 |
+
},
|
74 |
+
write: (newActor: string) => {
|
75 |
+
this.actorSelector.setSelectedActor(newActor);
|
76 |
+
}
|
77 |
+
});
|
78 |
+
this.actorSelector.onChange((newSelectedActor: string) => {
|
79 |
+
_selectedActor(newSelectedActor);
|
80 |
+
});
|
81 |
+
|
82 |
+
//Re-select the previously selected actor, or select "All" (null) by default.
|
83 |
+
this.selectedActor(scriptData.selectedActor);
|
84 |
+
|
85 |
+
this.canRoleManagePlugins = scriptData.canManagePlugins;
|
86 |
+
this.isMultisite = scriptData.isMultisite;
|
87 |
+
|
88 |
+
this.grantAccessByDefault = {};
|
89 |
+
_.forEach(this.actorSelector.getVisibleActors(), (actor: AmeBaseActor) => {
|
90 |
+
this.grantAccessByDefault[actor.id] = ko.observable<boolean>(
|
91 |
+
_.get(scriptData.settings.grantAccessByDefault, actor.id, this.canManagePlugins(actor))
|
92 |
+
);
|
93 |
+
});
|
94 |
+
|
95 |
+
this.plugins = _.map(scriptData.installedPlugins, (plugin) => {
|
96 |
+
return new AmePlugin(plugin, _.get(scriptData.settings.plugins, plugin.fileName, {}), this);
|
97 |
+
});
|
98 |
+
|
99 |
+
this.privilegedActors = [this.actorSelector.getCurrentUserActor()];
|
100 |
+
if (this.isMultisite) {
|
101 |
+
this.privilegedActors.push(AmeActors.getSuperAdmin());
|
102 |
+
}
|
103 |
+
|
104 |
+
this.areAllPluginsChecked = ko.computed({
|
105 |
+
read: () => {
|
106 |
+
return _.every(this.plugins, (plugin) => {
|
107 |
+
return this.isPluginVisible(plugin);
|
108 |
+
});
|
109 |
+
},
|
110 |
+
write: (isChecked) => {
|
111 |
+
if (this.selectedActor() !== null) {
|
112 |
+
let canSeePluginsByDefault = this.getGrantAccessByDefault(this.selectedActor());
|
113 |
+
canSeePluginsByDefault(isChecked);
|
114 |
+
}
|
115 |
+
_.forEach(this.plugins, (plugin) => {
|
116 |
+
this.setPluginVisibility(plugin, isChecked);
|
117 |
+
});
|
118 |
+
}
|
119 |
+
});
|
120 |
+
|
121 |
+
//This observable will be populated when saving changes.
|
122 |
+
this.settingsData = ko.observable('');
|
123 |
+
}
|
124 |
+
|
125 |
+
isPluginVisible(plugin: AmePlugin): boolean {
|
126 |
+
let actorId = this.selectedActor();
|
127 |
+
if (actorId === null) {
|
128 |
+
return plugin.isVisibleByDefault();
|
129 |
+
} else {
|
130 |
+
let canSeePluginsByDefault = this.getGrantAccessByDefault(actorId),
|
131 |
+
isVisible = plugin.getGrantObservable(actorId, plugin.isVisibleByDefault() && canSeePluginsByDefault());
|
132 |
+
return isVisible();
|
133 |
+
}
|
134 |
+
}
|
135 |
+
|
136 |
+
setPluginVisibility(plugin: AmePlugin, isVisible: boolean) {
|
137 |
+
const selectedActor = this.selectedActor();
|
138 |
+
if (selectedActor === null) {
|
139 |
+
plugin.isVisibleByDefault(isVisible);
|
140 |
+
|
141 |
+
//Show/hide from everyone except the current user and Super Admin.
|
142 |
+
//However, don't enable plugins for roles that can't access the "Plugins" page in the first place.
|
143 |
+
const _ = AmePluginVisibilityModule._;
|
144 |
+
_.forEach(this.actorSelector.getVisibleActors(), (actor: AmeBaseActor) => {
|
145 |
+
let allowAccess = plugin.getGrantObservable(actor.id, isVisible);
|
146 |
+
if (!this.canManagePlugins(actor)) {
|
147 |
+
allowAccess(false);
|
148 |
+
} else if (_.includes(this.privilegedActors, actor)) {
|
149 |
+
allowAccess(true);
|
150 |
+
} else {
|
151 |
+
allowAccess(isVisible);
|
152 |
+
}
|
153 |
+
});
|
154 |
+
} else {
|
155 |
+
//Show/hide from the selected role or user.
|
156 |
+
let allowAccess = plugin.getGrantObservable(selectedActor, isVisible);
|
157 |
+
allowAccess(isVisible);
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
private canManagePlugins(actor: AmeBaseActor) {
|
162 |
+
const _ = AmePluginVisibilityModule._;
|
163 |
+
if ((actor instanceof AmeRole) && _.has(this.canRoleManagePlugins, actor.name)) {
|
164 |
+
return this.canRoleManagePlugins[actor.name];
|
165 |
+
}
|
166 |
+
if (actor instanceof AmeSuperAdmin) {
|
167 |
+
return true;
|
168 |
+
}
|
169 |
+
|
170 |
+
if (actor instanceof AmeUser) {
|
171 |
+
//Can any of the user's roles manage plugins?
|
172 |
+
let result = false;
|
173 |
+
_.forEach(actor.roles, (roleId) => {
|
174 |
+
if (_.get(this.canRoleManagePlugins, roleId, false)) {
|
175 |
+
result = true;
|
176 |
+
return false;
|
177 |
+
}
|
178 |
+
});
|
179 |
+
return (result || AmeActors.hasCap(actor.id, 'activate_plugins'));
|
180 |
+
}
|
181 |
+
|
182 |
+
return false;
|
183 |
+
}
|
184 |
+
|
185 |
+
private getGrantAccessByDefault(actorId: string): KnockoutObservable<boolean> {
|
186 |
+
if (!this.grantAccessByDefault.hasOwnProperty(actorId)) {
|
187 |
+
this.grantAccessByDefault[actorId] = ko.observable(this.canManagePlugins(AmeActors.getActor(actorId)));
|
188 |
+
}
|
189 |
+
return this.grantAccessByDefault[actorId];
|
190 |
+
}
|
191 |
+
|
192 |
+
private getSettings(): PluginVisibilitySettings {
|
193 |
+
const _ = AmePluginVisibilityModule._;
|
194 |
+
let result: PluginVisibilitySettings = <PluginVisibilitySettings>{};
|
195 |
+
|
196 |
+
result.grantAccessByDefault = _.mapValues(this.grantAccessByDefault, (allow): boolean => {
|
197 |
+
return allow();
|
198 |
+
});
|
199 |
+
result.plugins = {};
|
200 |
+
_.forEach(this.plugins, (plugin: AmePlugin) => {
|
201 |
+
result.plugins[plugin.fileName] = {
|
202 |
+
isVisibleByDefault: plugin.isVisibleByDefault(),
|
203 |
+
grantAccess: _.mapValues(plugin.grantAccess, (allow): boolean => {
|
204 |
+
return allow();
|
205 |
+
})
|
206 |
+
};
|
207 |
+
});
|
208 |
+
|
209 |
+
return result;
|
210 |
+
}
|
211 |
+
|
212 |
+
saveChanges() {
|
213 |
+
const settings = this.getSettings();
|
214 |
+
|
215 |
+
//Remove settings associated with roles and users that no longer exist or are not visible.
|
216 |
+
const _ = AmePluginVisibilityModule._,
|
217 |
+
visibleActorIds = _.pluck(this.actorSelector.getVisibleActors(), 'id');
|
218 |
+
_.forEach(settings.plugins, (plugin) => {
|
219 |
+
plugin.grantAccess = _.pick<GrantAccessMap, GrantAccessMap>(plugin.grantAccess, visibleActorIds);
|
220 |
+
});
|
221 |
+
|
222 |
+
//Populate form field(s).
|
223 |
+
this.settingsData(jQuery.toJSON(settings));
|
224 |
+
|
225 |
+
return true;
|
226 |
+
}
|
227 |
+
}
|
228 |
+
|
229 |
+
class AmePlugin implements PvPluginInfo {
|
230 |
+
name: string;
|
231 |
+
fileName: string;
|
232 |
+
description: string;
|
233 |
+
isActive: boolean;
|
234 |
+
|
235 |
+
isChecked: KnockoutComputed<boolean>;
|
236 |
+
|
237 |
+
isVisibleByDefault: KnockoutObservable<boolean>;
|
238 |
+
grantAccess: {[actorId : string] : KnockoutObservable<boolean>};
|
239 |
+
|
240 |
+
constructor(details: PvPluginInfo, visibility: Object, module: AmePluginVisibilityModule) {
|
241 |
+
this.name = AmePlugin.stripAllTags(details.name);
|
242 |
+
this.description = AmePlugin.stripAllTags(details.description);
|
243 |
+
this.fileName = details.fileName;
|
244 |
+
this.isActive = details.isActive;
|
245 |
+
|
246 |
+
const _ = AmePluginVisibilityModule._;
|
247 |
+
this.isVisibleByDefault = ko.observable(_.get(visibility, 'isVisibleByDefault', true));
|
248 |
+
|
249 |
+
const emptyGrant: {[actorId : string] : boolean} = {};
|
250 |
+
this.grantAccess = _.mapValues(_.get(visibility, 'grantAccess', emptyGrant), (hasAccess) => {
|
251 |
+
return ko.observable<boolean>(hasAccess);
|
252 |
+
});
|
253 |
+
|
254 |
+
this.isChecked = ko.computed<boolean>({
|
255 |
+
read: () => {
|
256 |
+
return module.isPluginVisible(this);
|
257 |
+
},
|
258 |
+
write: (isVisible: boolean) => {
|
259 |
+
return module.setPluginVisibility(this, isVisible);
|
260 |
+
}
|
261 |
+
});
|
262 |
+
}
|
263 |
+
|
264 |
+
getGrantObservable(actorId: string, defaultValue: boolean = true): KnockoutObservable<boolean> {
|
265 |
+
if (!this.grantAccess.hasOwnProperty(actorId)) {
|
266 |
+
this.grantAccess[actorId] = ko.observable<boolean>(defaultValue);
|
267 |
+
}
|
268 |
+
return this.grantAccess[actorId];
|
269 |
+
}
|
270 |
+
|
271 |
+
static stripAllTags(input): string {
|
272 |
+
//Based on: http://phpjs.org/functions/strip_tags/
|
273 |
+
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
|
274 |
+
commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
|
275 |
+
return input.replace(commentsAndPhpTags, '').replace(tags, '');
|
276 |
+
}
|
277 |
+
}
|
278 |
+
|
279 |
+
jQuery(function ($) {
|
280 |
+
amePluginVisibility = new AmePluginVisibilityModule(wsPluginVisibilityData);
|
281 |
+
ko.applyBindings(amePluginVisibility, document.getElementById('ame-plugin-visibility-editor'));
|
282 |
+
|
283 |
+
//Permanently dismiss the usage hint via AJAX.
|
284 |
+
$('#ame-pv-usage-notice').on('click', '.notice-dismiss', function() {
|
285 |
+
$.post(
|
286 |
+
wsPluginVisibilityData.adminAjaxUrl,
|
287 |
+
{
|
288 |
+
'action' : 'ws_ame_dismiss_pv_usage_notice',
|
289 |
+
'_ajax_nonce' : wsPluginVisibilityData.dismissNoticeNonce
|
290 |
+
}
|
291 |
+
);
|
292 |
+
});
|
293 |
+
});
|
readme.txt
CHANGED
@@ -3,8 +3,8 @@ Contributors: whiteshadow
|
|
3 |
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
Requires at least: 4.1
|
6 |
-
Tested up to: 4.
|
7 |
-
Stable tag: 1.
|
8 |
|
9 |
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
|
10 |
|
@@ -63,6 +63,10 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
|
|
63 |
|
64 |
== Changelog ==
|
65 |
|
|
|
|
|
|
|
|
|
66 |
= 1.6.2 =
|
67 |
* Fixed a bug that made menu items "jump" slightly to the left when you start to drag them.
|
68 |
* Fixed a Multisite-specific bug where temporarily switching to another site using the switch_to_blog() function could result in the user having the wrong permissions.
|
3 |
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
Requires at least: 4.1
|
6 |
+
Tested up to: 4.6-beta3
|
7 |
+
Stable tag: 1.7
|
8 |
|
9 |
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
|
10 |
|
63 |
|
64 |
== Changelog ==
|
65 |
|
66 |
+
= 1.7 =
|
67 |
+
* Added a "Plugins" tab. It lets you hide specific plugins from other users. Note that this only affects the list on the "Plugins" page and tasks like editing plugin files, but it doesn't affect the admin menu.
|
68 |
+
* Tested up to WordPress 4.6-beta3.
|
69 |
+
|
70 |
= 1.6.2 =
|
71 |
* Fixed a bug that made menu items "jump" slightly to the left when you start to drag them.
|
72 |
* Fixed a Multisite-specific bug where temporarily switching to another site using the switch_to_blog() function could result in the user having the wrong permissions.
|
uninstall.php
CHANGED
@@ -19,4 +19,13 @@ if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
|
|
19 |
if ( function_exists('delete_metadata') ) {
|
20 |
delete_metadata('user', 0, 'ame_show_hints', '', true);
|
21 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
}
|
19 |
if ( function_exists('delete_metadata') ) {
|
20 |
delete_metadata('user', 0, 'ame_show_hints', '', true);
|
21 |
}
|
22 |
+
|
23 |
+
//Remove module settings.
|
24 |
+
delete_option('ws_ame_plugin_visibility');
|
25 |
+
delete_option('ws_ame_dashboard_widgets');
|
26 |
+
if ( function_exists('delete_site_option') ){
|
27 |
+
delete_site_option('ws_ame_plugin_visibility');
|
28 |
+
delete_site_option('ws_ame_hide_pv_notice');
|
29 |
+
delete_site_option('ws_ame_dashboard_widgets');
|
30 |
+
}
|
31 |
}
|