Файловый менеджер - Редактировать - /home/harasnat/www/horse/wp-content/plugins/admin-menu-editor/modules/plugin-visibility/plugin-visibility.php
Назад
<?php use YahnisElsts\AjaxActionWrapper\v2\Action; class amePluginVisibility extends amePersistentModule { const HIDE_USAGE_NOTICE_FLAG = 'ws_ame_hide_pv_notice'; /** * Any role that has any of the following capabilities has some degree of control * over plugins, so plugin visibility settings apply to that role. */ const PLUGIN_MANAGEMENT_CAPS = array( 'activate_plugins', 'install_plugins', 'edit_plugins', 'update_plugins', 'delete_plugins', 'manage_network_plugins', ); const CUSTOMIZATION_COMPONENT = 'plugin_visibility'; protected $optionName = 'ws_ame_plugin_visibility'; protected $tabSlug = 'plugin-visibility'; protected $tabTitle = 'Plugins'; protected $tabOrder = 20; protected $defaultSettings = array( 'plugins' => array(), 'grantAccessByDefault' => array(), ); private static $lastInstance = null; /** * @var Action */ private $dismissNoticeAction; /** * @var ameCustomizationFeatureToggle */ private $customizationFeature; public function __construct($menuEditor) { parent::__construct($menuEditor); self::$lastInstance = $this; $this->customizationFeature = new ameCustomizationFeatureToggle( self::CUSTOMIZATION_COMPONENT, $this->menuEditor, $this->tabSlug, function () { return [ __('You will see the unmodified plugin list on the "Plugins" page.', 'admin-menu-editor'), __('Customized plugin list is disabled for your account.', 'admin-menu-editor'), ]; } ); if ( !$this->isEnabledForRequest() ) { return; } //Remove "hidden" plugins from the list on the "Plugins -> Installed Plugins" page. add_filter('all_plugins', array($this, 'filterPluginList'), 15); //Hide updates for hidden plugins. add_filter('site_transient_update_plugins', array($this, 'filterPluginUpdates'), 15); //It's not possible to completely prevent a user from (de)activating "hidden" plugins because plugin API //functions like activate_plugin() and deactivate_plugins() don't provide a way to abort (de)activation. //However, we can still block edits and *some* other actions that WP verifies with check_admin_referer(). add_action('check_admin_referer', array($this, 'authorizePluginAction')); //Also block disallowed AJAX plugin edits by using the "editable_extensions" filter //to remove all file extensions from the list for hidden plugins. //See functions called by wp_ajax_edit_theme_plugin_file(). add_filter('editable_extensions', array($this, 'authorizePluginFileEdit'), 15, 2); //Register the plugin visibility tab. add_action('admin_menu_editor-header', array($this, 'handleFormSubmission'), 10, 2); //Display a usage hint in our tab. add_action('admin_notices', array($this, 'displayUsageNotice')); $this->dismissNoticeAction = Action::builder('ws_ame_dismiss_pv_usage_notice') ->handler(array($this, 'ajaxDismissUsageNotice')) ->permissionCallback(array($this->menuEditor, 'current_user_can_edit_menu')) ->method('post') ->register(); //On save, let the feature toggle know that so it can display a notice if customization //is disabled for the current user. $params = $this->menuEditor->get_query_params(); if ( !empty($params['message']) ) { $this->customizationFeature->onSettingsSaved(); } } /** * Check if a plugin is visible to the current user. * * Goals: * - You can easily hide a plugin from everyone, including new roles. See: isVisibleByDefault * - You can configure a role so that new plugins are hidden by default. See: grantAccessByDefault * - You can change visibility per role and per user, just like with admin menus. * - Roles that don't have access to plugins are not considered when deciding visibility. * - Precedence order: user > super admin > all roles. * * @param string $pluginFileName Plugin file name as returned by plugin_basename(). * @param WP_User $user Current user. * @return bool */ private function isPluginVisible($pluginFileName, $user = null) { //TODO: Can we refactor this to be shorter? static $isMultisite = null; if ( !isset($isMultisite) ) { $isMultisite = is_multisite(); } if ( $user === null ) { $user = wp_get_current_user(); } $settings = $this->loadSettings(); //Do we have custom settings for this plugin? if ( isset($settings['plugins'][$pluginFileName]) ) { $isVisibleByDefault = ameUtils::get($settings['plugins'][$pluginFileName], 'isVisibleByDefault', true); $grantAccess = ameUtils::get($settings['plugins'][$pluginFileName], 'grantAccess', array()); if ( $isVisibleByDefault ) { $grantAccess = array_merge($settings['grantAccessByDefault'], $grantAccess); } } else { $isVisibleByDefault = true; $grantAccess = $settings['grantAccessByDefault']; } //User settings take precedence over everything else. $userActor = 'user:' . $user->get('user_login'); if ( isset($grantAccess[$userActor]) ) { return $grantAccess[$userActor]; } //Super Admin is next. if ( $isMultisite && is_super_admin($user->ID) ) { //By default, the Super Admin has access to everything. return ameUtils::get($grantAccess, 'special:super_admin', true); } //Finally, the user can see the plugin if at least one of their roles can. $anyRoleHasSettings = false; $roles = $this->menuEditor->get_user_roles($user); foreach ($roles as $roleId) { /** @noinspection PhpRedundantOptionalArgumentInspection -- In case the default changes. */ $hasAccess = ameUtils::get($grantAccess, 'role:' . $roleId, null); if ( $hasAccess !== null ) { $anyRoleHasSettings = true; } else { $hasAccess = $isVisibleByDefault && $this->roleCanManagePlugins($roleId); } if ( $hasAccess ) { return true; } } if ( $anyRoleHasSettings ) { //At least one role had per-plugin settings or access-by-default settings, //and those settings did not grant access. return false; } else if ( $isVisibleByDefault ) { //Check user capabilities. return $this->userCanManagePlugins($user); } return false; } /** * @param string $roleId * @param WP_Role $role * @return bool */ private function roleCanManagePlugins($roleId, $role = null) { static $cache = array(); if ( isset($cache[$roleId]) ) { return $cache[$roleId]; } if ( !isset($role) ) { $role = get_role($roleId); if ( !isset($role) ) { //This should never happen, but a user reported that it did on their site. $cache[$roleId] = false; return false; } } $result = false; foreach (self::PLUGIN_MANAGEMENT_CAPS as $cap) { if ( $role->has_cap($cap) ) { $result = true; break; } } $cache[$roleId] = $result; return $result; } /** * @param \WP_User $user * @return boolean */ private function userCanManagePlugins($user) { static $cache = array(); $userId = $user->ID; if ( isset($cache[$userId]) ) { return $cache[$userId]; } $result = false; foreach (self::PLUGIN_MANAGEMENT_CAPS as $cap) { if ( user_can($user, $cap) ) { $result = true; break; } } $cache[$userId] = $result; return $result; } /** * Filter a plugin list by removing plugins that are not visible to the current user. * * @param array $plugins * @return array */ public function filterPluginList($plugins) { if ( !is_array($plugins) && !($plugins instanceof ArrayAccess) ) { return $plugins; } $user = wp_get_current_user(); $settings = $this->loadSettings(); //Don't try to hide plugins outside the WP admin. It prevents WP-CLI from seeing all installed plugins. if ( !$user->exists() || !is_admin() ) { return $plugins; } if ( $this->customizationFeature->isCustomizationDisabled() ) { return $plugins; } $editableProperties = array( 'Name' => 'name', 'Description' => 'description', 'Author' => 'author', 'PluginURI' => 'siteUrl', 'AuthorURI' => 'siteUrl', 'Version' => 'version', ); $pluginFileNames = array_keys($plugins); foreach ($pluginFileNames as $fileName) { //Remove all hidden plugins. if ( !$this->isPluginVisible($fileName, $user) ) { unset($plugins[$fileName]); continue; } //Set custom names, descriptions, and other properties. foreach ($editableProperties as $header => $property) { $customValue = ameUtils::get($settings, array('plugins', $fileName, 'custom' . ucfirst($property)), ''); if ( $customValue !== '' ) { $plugins[$fileName][$header] = $customValue; } } } return $plugins; } /** * Filter out updates associated with plugins that are not visible to the current user. * * @param StdClass|null $updates * @return StdClass|null */ public function filterPluginUpdates($updates) { if ( !isset($updates->response) || !is_array($updates->response) ) { //Either there are no updates or we don't recognize the format. return $updates; } //Let's not hide anything when no one is logged in. We don't check is_admin() here //because plugin updates can appear in the Toolbar and that's visible in the front-end. $user = wp_get_current_user(); if ( !$user->exists() || (defined('DOING_CRON') && DOING_CRON) ) { return $updates; } if ( $this->customizationFeature->isCustomizationDisabled() ) { return $updates; } $pluginFileNames = array_keys($updates->response); foreach ($pluginFileNames as $fileName) { //Remove all hidden plugins. if ( !$this->isPluginVisible($fileName, $user) ) { unset($updates->response[$fileName]); continue; } } return $updates; } /** * Verify that the current user is allowed to see the plugin that they're trying to edit, activate or deactivate. * Note that this doesn't catch bulk (de-)activation or various plugin management plugins. * * This is a callback for the "check_admin_referer" action. * * @param string $action */ public function authorizePluginAction($action) { if ( $this->customizationFeature->isCustomizationDisabled() ) { return; } //PHPCS special case: This hook callback runs inside a function that validates //nonces and selectively overrides the behaviour of that function. //phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- See above //Is the user trying to edit a plugin? if ( preg_match('@^edit-plugin_(?P<file>.+)$@', $action, $matches) ) { //The file that's being edited is part of a plugin. Find that plugin. $selectedPlugin = $this->identifyPluginFromFileName($matches['file']); if ( $selectedPlugin !== null ) { //Can the current user see the selected plugin? $isVisible = $this->isPluginVisible($selectedPlugin); if ( !$isVisible ) { wp_die('You do not have sufficient permissions to edit this plugin.'); } } //Is the user trying to (de-)activate a single plugin? } elseif ( preg_match('@(?P<action>deactivate|activate)-plugin_(?P<plugin>.+)$@', $action, $matches) ) { //Can the current user see this plugin? $isVisible = $this->isPluginVisible($matches['plugin']); if ( !$isVisible ) { wp_die(sprintf( 'You do not have sufficient permissions to %s this plugin.', esc_html($matches['action']) )); } //Are they acting on multiple plugins? One of them might be hidden. } elseif ( ($action === 'bulk-plugins') && isset($_POST['checked']) && is_array($_POST['checked']) ) { $user = wp_get_current_user(); foreach ($_POST['checked'] as $pluginFile) { if ( !$this->isPluginVisible(strval($pluginFile), $user) ) { wp_die(sprintf( 'You do not have sufficient permissions to manage this plugin: "%s".', esc_html($pluginFile) )); } } } //phpcs:enable } /** * Filter the list of file extensions that can be edited in the plugin editor. * * If the current user is not allowed to edit the specified plugin, this function removes * all extensions from the list, effectively disabling plugin editing. * * @param array $extensions * @param string $pluginFile Plugin file name relative to the plugin directory. Added in WP 4.9.0, * so should always be available in practice. * @return array */ public function authorizePluginFileEdit($extensions, $pluginFile = '') { //Sanity check: $pluginFile should be provided. if ( empty($pluginFile) ) { return $extensions; } //$extensions should be an array. if ( !is_array($extensions) ) { return $extensions; } /* * Technically, we could use the "editable_extensions" filter to control plugin editing both * in AJAX requests and on the "Plugins -> Editor" page. However, when the user opens the plugin * editor, WordPress automatically selects the first plugin without checking permissions. * If the user can't edit that plugin, they would get an error message, and they wouldn't * be able to edit *any* plugins. * * To avoid this, we only filter the list of extensions on AJAX requests. Other hooks * are used to prevent the user from editing plugins via form submissions. */ if ( !wp_doing_ajax() ) { return $extensions; } if ( $this->customizationFeature->isCustomizationDisabled() ) { return $extensions; } //Identify the plugin that's being edited. $selectedPlugin = $this->identifyPluginFromFileName($pluginFile); if ( $selectedPlugin !== null ) { $isVisible = $this->isPluginVisible($selectedPlugin); if ( !$isVisible ) { //The user can't see the plugin, so they can't edit it. //Remove all extensions from the list. $extensions = array(); } } return $extensions; } /** * Given a file name, identify the plugin that it belongs to. * * @param string $inputFileName File name relative to the "plugins" directory. * @return string|null */ private function identifyPluginFromFileName($inputFileName) { if ( empty($inputFileName) ) { return null; } $fileName = wp_normalize_path($inputFileName); $fileDirectory = ameUtils::getFirstDirectory($fileName); $selectedPlugin = null; $pluginFiles = array_keys(get_plugins()); foreach ($pluginFiles as $mainPluginFile) { //Is this the main plugin file? if ( $mainPluginFile === $fileName ) { $selectedPlugin = $mainPluginFile; break; } //Is the file inside this plugin's directory? $pluginDirectory = ameUtils::getFirstDirectory($mainPluginFile); if ( ($pluginDirectory !== null) && ($pluginDirectory === $fileDirectory) ) { $selectedPlugin = $mainPluginFile; break; } } return $selectedPlugin; } public function addSettingsTab($tabs) { $tabs[$this->tabSlug] = 'Plugins'; return $tabs; } protected function getTemplateVariables($templateName) { $result = parent::getTemplateVariables($templateName); $result['tabUrl'] = $this->getTabUrl(); return $result; } protected function getWrapClasses() { return array_merge(parent::getWrapClasses(), ['ame-tab-list-bottom-margin-disabled']); } public function handleFormSubmission($action, $post = array()) { //Note: We don't need to check user permissions here because plugin core already did. if ( $action === 'save_plugin_visibility' ) { check_admin_referer($action); $this->settings = json_decode($post['settings'], true); $this->saveSettings(); $params = array('message' => 1); //Re-select the same actor. if ( !empty($post['selected_actor']) ) { $params['selected_actor'] = strval($post['selected_actor']); } wp_safe_redirect($this->getTabUrl($params)); exit; } } public function enqueueTabScripts() { wp_register_auto_versioned_script( 'ame-plugin-visibility', plugins_url('plugin-visibility.js', __FILE__), array( 'ame-lodash', 'ame-knockout', 'ame-actor-selector', $this->dismissNoticeAction->getRegisteredScriptHandle(), ) ); wp_enqueue_script('ame-plugin-visibility'); //Reselect the same actor. $query = $this->menuEditor->get_query_params(); $selectedActor = null; if ( isset($query['selected_actor']) ) { $selectedActor = strval($query['selected_actor']); } $scriptData = $this->getScriptData(); $scriptData['selectedActor'] = $selectedActor; wp_localize_script('ame-plugin-visibility', 'wsPluginVisibilityData', $scriptData); } public function getScriptData() { //Pass the list of installed plugins and their state (active/inactive) to UI JavaScript. $installedPlugins = get_plugins(); $activePlugins = array_map('plugin_basename', wp_get_active_and_valid_plugins()); $activeNetworkPlugins = array(); if ( function_exists('wp_get_active_network_plugins') ) { //This function is only available on Multisite. $activeNetworkPlugins = array_map('plugin_basename', wp_get_active_network_plugins()); } $plugins = array(); foreach ($installedPlugins as $pluginFile => $header) { $isActiveForNetwork = in_array($pluginFile, $activeNetworkPlugins); $isActive = in_array($pluginFile, $activePlugins); $plugins[] = array( 'fileName' => $pluginFile, 'isActive' => $isActive || $isActiveForNetwork, 'name' => $header['Name'], 'description' => isset($header['Description']) ? $header['Description'] : '', 'author' => isset($header['Author']) ? $header['Author'] : '', 'siteUrl' => isset($header['PluginURI']) ? $header['PluginURI'] : '', 'version' => isset($header['Version']) ? $header['Version'] : '', ); } //Flag roles that can manage plugins. $canManagePlugins = array(); $wpRoles = ameRoleUtils::get_roles(); foreach ($wpRoles->role_objects as $id => $role) { $canManagePlugins[$id] = $this->roleCanManagePlugins($id, $role); } return array( 'settings' => $this->loadSettings(), 'installedPlugins' => $plugins, 'canManagePlugins' => $canManagePlugins, 'isMultisite' => is_multisite(), 'isProVersion' => $this->menuEditor->is_pro_version(), ); } public function enqueueTabStyles() { wp_enqueue_auto_versioned_style( 'ame-plugin-visibility-css', plugins_url('plugin-visibility.css', __FILE__) ); } public function displayUsageNotice() { if ( !$this->menuEditor->is_tab_open($this->tabSlug) ) { return; } //If the user has already made some changes, they probably don't need to see this notice any more. $settings = $this->loadSettings(); if ( !empty($settings['plugins']) ) { return; } //The notice is dismissible. if ( get_site_option(self::HIDE_USAGE_NOTICE_FLAG, false) ) { return; } echo '<div class="notice notice-info is-dismissible" id="ame-pv-usage-notice"> <p> <strong>Tip:</strong> This screen lets you hide plugins from other users. These settings only affect the "Plugins" page, not the admin menu or the dashboard. </p> </div>'; } public function ajaxDismissUsageNotice() { $result = update_site_option(self::HIDE_USAGE_NOTICE_FLAG, true); return array('success' => true, 'updateResult' => $result); } /** * Get the most recently created instance of this class. * Note: This function should only be used for testing purposes. * * @return amePluginVisibility|null */ public static function getLastCreatedInstance() { return self::$lastInstance; } /** * Remove any visibility settings associated with the specified plugin. * * @param string $pluginFile */ public function forgetPlugin($pluginFile) { $settings = $this->loadSettings(); unset($settings['plugins'][$pluginFile]); $this->settings = $settings; $this->saveSettings(); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка