File "BackendMenu.php"
Full Path: /home/rubycabfbt/www/wp-content/plugins/advanced-access-manager/application/Framework/Service/BackendMenu.php
File size: 17.74 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* ======================================================================
* LICENSE: This file is subject to the terms and conditions defined in *
* file 'license.txt', which is part of this source code package. *
* ======================================================================
*/
/**
* Framework service to manage access to the backend (admin) menu
*
* @package AAM
* @version 7.0.0
*/
class AAM_Framework_Service_BackendMenu
{
use AAM_Framework_Service_BaseTrait;
/**
* DB cache option
*
* @version 7.0.0
*/
const CACHE_OPTION = 'aam_menu';
/**
* Return the complete backend menu list with permissions
*
* @return array
* @access public
*
* @version 7.0.0
* @todo - Move to AAM_Service_BackendMenu
*/
public function get_items()
{
try {
$result = [];
// Getting the menu cache so we can build the list
$menu = $this->_get_raw_menu();
if (!empty($menu)) {
foreach ($menu['menu'] as $item) {
if (preg_match('/^separator/', $item[2])) {
continue; //skip separator
}
array_push($result, $this->_prepare_menu_item($item));
}
}
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* Alias for the get_items method
*
* @return array
* @access public
*
* @version 7.0.0
* @todo - Move to AAM_Service_BackendMenu
*/
public function items()
{
return $this->get_items();
}
/**
* Get existing menu by ID
*
* @param string $menu_slug
*
* @return array
* @access public
*
* @version 7.0.0
* @todo - Move to AAM_Service_BackendMenu
*/
public function get_item($menu_slug)
{
try {
$result = false;
$menu_slug = $this->_normalize_resource_identifier($menu_slug);
foreach($this->get_items() as $item) {
if ($item['slug'] === $menu_slug) {
$result = $item;
} elseif (isset($item['children'])) {
foreach($item['children'] as $child) {
if ($child['slug'] === $menu_slug) {
$result = $child;
}
}
}
// If we found menu, just break the search
if ($result !== false) { break; }
}
if ($result === false) {
throw new OutOfRangeException('Backend menu item does not exist');
}
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* An alias for the get_item method
*
* @param string $menu_slug
*
* @return array
* @access public
*
* @version 7.0.0
* @todo - Move to AAM_Service_BackendMenu
*/
public function item($menu_slug)
{
return $this->get_item($menu_slug);
}
/**
* Restrict access to a given menu item
*
* @param string $menu_slug
*
* @return bool|WP_Error
* @access public
*
* @version 7.0.0
*/
public function deny($menu_slug)
{
try {
$result = $this->_set_item_permission($menu_slug, 'deny');
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* Allow access to a given menu item
*
* @param string $menu_slug
*
* @return bool|WP_Error
* @access public
*
* @version 7.0.0
*/
public function allow($menu_slug)
{
try {
$result = $this->_set_item_permission($menu_slug, 'allow');
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* Reset all backend menu permissions
*
* @param string $menu_slug [Optional]
*
* @return bool|WP_Error
* @access public
*
* @version 7.0.0
*/
public function reset($menu_slug = null)
{
try {
$resource = $this->_get_resource();
if (!empty($menu_slug)) {
$result = $resource->remove_permission(
$this->_normalize_resource_identifier($menu_slug),
'access'
);
} else {
$result = $resource->reset();
}
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* Check if menu item is restricted
*
* @param string $menu_slug
*
* @return bool|WP_Error
* @access public
*
* @version 7.0.0
*/
public function is_denied($menu_slug)
{
try {
$result = null;
// Normalize the input data based on top level flat
$slug = $this->_normalize_resource_identifier($menu_slug);
$parent_slug = null;
// The default dashboard landing page is always excluded
if ($slug !== 'index.php') {
$resource = $this->_get_resource();
// Check if menu is explicitly allowed
$permission = $resource->get_permission($slug, 'access');
if (!empty($permission)) {
$result = $permission['effect'] !== 'allow';
}
// If menu is not top level item, assume that this is a submenu item
// and check if parent menu item is restricted
if (is_null($result) && strpos($slug, 'menu/') !== 0) {
if ($slug === 'post.php') { // Submitting post
$post_type = $this->misc->get($_POST, 'post_type');
$parent_slug = 'menu/edit.php';
// Here we are covering the post management screens. WP core
// recycles the "edit.php" screen to manage all post types.
// However, if "Posts" (default WP posts) get restricted, it
// creates an issues for all other custom post type screens.
// This is why we are taking extra steps to ensure proper
// access controls
if(!empty($post_type) && $post_type !== 'post') {
$parent_slug .= '?post_type=' . $post_type;
} elseif (isset($_GET['post'])) {
$post = get_post(filter_input(INPUT_GET, 'post'));
if (is_a($post, WP_Post::class)
&& $post->post_type !== 'post'
) {
$parent_slug .= '?post_type=' . $post->post_type;
}
}
}
if (empty($parent_slug)){
$parent_slug = $this->_get_parent_slug($slug);
}
// If we found a parent menu item, check permissions
if (!empty($parent_slug)) {
$permission = $resource->get_permission(
$parent_slug, 'access'
);
if (!empty($permission)) {
$result = $permission['effect'] !== 'allow';
}
}
}
// Step #3. Allow third-party services to hook into the decision
// process
$result = apply_filters(
'aam_backend_menu_is_denied_filter',
$result,
$slug, // Note! Passing already normalized menu slug
$resource,
$parent_slug
);
// Prepare the final answer
$result = is_bool($result) ? $result : false;
} else {
$result = false;
}
} catch (Exception $e) {
$result = $this->_handle_error($e);
}
return $result;
}
/**
* Check if menu item is allowed
*
* @param string $menu_slug
*
* @return bool|WP_Error
* @access public
*
* @version 7.0.0
*/
public function is_allowed($menu_slug)
{
$result = $this->is_denied($menu_slug);
return is_bool($result) ? !$result : $result;
}
/**
* Set permissions for a given menu slug
*
* @param string $menu_slug
* @param string $effect
*
* @return bool
* @access private
*
* @version 7.0.0
*/
private function _set_item_permission($menu_slug, $effect)
{
return $this->_get_resource()->set_permission(
$this->_normalize_resource_identifier($menu_slug),
'access',
$effect
);
}
/**
* Get backend menu resource
*
* @return AAM_Framework_Resource_BackendMenu
* @access private
*
* @version 7.0.0
*/
private function _get_resource()
{
return $this->_get_access_level()->get_resource(
AAM_Framework_Type_Resource::BACKEND_MENU
);
}
/**
* Get raw Admin Menu
*
* This method also caches the admin menu for future usage
*
* @return array
* @access private
*
* @version 7.0.2
*/
private function _get_raw_menu()
{
static $_cache = [];
global $menu, $submenu;
if ((defined('DOING_AJAX') && DOING_AJAX)
|| $this->misc->get_current_area() === 'api'
) {
$result = $_cache = $this->cache->get(self::CACHE_OPTION);
} elseif (empty($_cache)) {
$result = [];
$persist_cache = false;
if (!empty($menu)) {
$result['menu'] = $this->_prepare_menu_items($menu);
$persist_cache = true;
}
if (!empty($submenu)) {
$result['submenu'] = $this->_prepare_submenu_items($submenu);
$persist_cache = true;
}
if ($persist_cache) {
$this->cache->set(self::CACHE_OPTION, $result, 31536000);
}
$_cache = $result; // Avoid doing the same thing over & over again
} else {
$result = $_cache;
}
return is_array($result) ? $result : [];
}
/**
* Prepare menu items to be cached
*
* @param array $items
*
* @return array
* @access private
*
* @version 7.0.2
*/
private function _prepare_menu_items($items)
{
$response = [];
if (is_array($items)) {
foreach($items as $i => $item) {
$response[$i] = $this->_get_menu_item_attributes($item);
}
}
return $response;
}
/**
* Prepare submenu item list
*
* @param array $items
*
* @return array
* @access private
*
* @version 7.0.2
*/
private function _prepare_submenu_items($items)
{
$response = [];
if (is_array($items)) {
foreach($items as $menu_id => $sub_level) {
$response[$menu_id] = [];
foreach($sub_level as $i => $item) {
$response[$menu_id][$i] = $this->_get_menu_item_attributes($item);
}
}
}
return $response;
}
/**
* Get menu item attributes
*
* Return only attributes we are interested in
*
* @param array $item
*
* @return array
* @access private
*
* @version 7.0.0
*/
private function _get_menu_item_attributes($item)
{
return [
// Name
base64_encode(is_string($item[0]) ? $item[0] : __('No Label', 'advanced-access-manager')),
// Capability
$item[1],
// Slug
$item[2]
];
}
/**
* Normalize and prepare the menu item model
*
* @param array $menu_item
* @param bool $is_top_level
*
* @return array
* @access private
*
* @version 7.0.0
*/
private function _prepare_menu_item($menu_item, $is_top_level = true)
{
$normalized = $this->_normalize_resource_identifier($menu_item[2]);
$slug = $is_top_level ? 'menu/' . $normalized : $normalized;
$response = array(
'slug' => $slug,
'path' => $this->_prepare_admin_uri($menu_item[2]),
'name' => $this->_filter_menu_name($menu_item[0]),
'capability' => $menu_item[1],
'is_restricted' => $this->is_denied($slug)
);
if ($is_top_level) {
$menu = $this->_get_raw_menu();
$response['children'] = $this->_get_submenu(
$menu_item[2],
isset($menu['submenu']) ? $menu['submenu'] : []
);
}
return $response;
}
/**
* Normalize the menu slug
*
* @param string $resource_identifier
*
* @return string
* @access private
*
* @version 7.0.0
*/
private function _normalize_resource_identifier($resource_identifier)
{
if (strpos($resource_identifier, '.php') !== false) {
$parsed_url = wp_parse_url($resource_identifier);
$parsed_slug = $parsed_url['path'];
if (isset($parsed_url['query'])) {
parse_str(html_entity_decode($parsed_url['query']), $query_params);
// Removing some redundant query params
$redundant_params = apply_filters(
'aam_ignored_backend_menu_item_query_params_filter',
['return', 'path']
);
foreach($redundant_params as $to_remove) {
if (array_key_exists($to_remove, $query_params)) {
unset($query_params[$to_remove]);
}
}
// Finally, sort the list of query params in alphabetical order to
// ensure consistent order
ksort($query_params);
if (count($query_params)) {
$parsed_slug .= '?' . http_build_query($query_params);
}
}
} else {
$parsed_slug = trim($resource_identifier);
}
return urldecode($parsed_slug);
}
/**
* Get parent menu
*
* @param string $slug
*
* @return string|null
* @access private
* @global array $submenu
*
* @version 7.0.0
*/
private function _get_parent_slug($search)
{
global $submenu;
$result = $this->_find_parent(
is_array($submenu) ? $submenu : [],
$search
);
// If we cannot find parent menu in current $submenu array, try to find it
// in the cached menu generated by super admin. This is important to cover
// scenarios where submenus bubble up to menu. E.g. Profile
if (is_null($result)) {
$menu = $this->_get_raw_menu();
$result = $this->_find_parent(
isset($menu['submenu']) ? $menu['submenu'] : [],
$search
);
}
return $result;
}
/**
* Find parent menu from the array of menu items
*
* @param array $array
* @param string $search
*
* @return null|string
* @access private
*
* @version 7.0.0
*/
private function _find_parent($array, $search)
{
$result = null;
foreach ($array as $parent => $subs) {
foreach ($subs as $sub) {
$slug = $this->_normalize_resource_identifier($sub[2]);
if ($slug === $search) {
$result = 'menu/' . $parent;
}
}
if ($result !== null) {
break;
}
}
return $result;
}
/**
* Prepare admin URI for the menu item
*
* @param string $menu_slug
*
* @return string
* @access private
*
* @version 7.0.0
*/
private function _prepare_admin_uri($menu_slug)
{
if (strpos($menu_slug, '.php') === false) {
$uri = admin_url('admin.php?page=' . $menu_slug);
} else {
$uri = '/wp-admin/' . trim($menu_slug, '/');
}
// Only prepare the relative path
return $this->misc->sanitize_url($uri);
}
/**
* Filter menu name
*
* Strip any HTML tags from the menu name and also remove the trailing
* numbers in case of Plugin or Comments menu name.
*
* @param string $name
*
* @return string
* @access private
*
* @version 7.0.0
*/
private function _filter_menu_name($name)
{
if (is_string($name)) {
$filtered = trim(wp_strip_all_tags(base64_decode($name), true));
} else {
$filtered = '';
}
return preg_replace('/([\d]+)$/', '', $filtered);
}
/**
* Prepare filtered submenu
*
* @param string $menu
* @param array $submenu,
*
* @return array
* @access private
*
* @version 7.0.0
*/
private function _get_submenu($parent_slug, $submenu)
{
$response = [];
if (array_key_exists($parent_slug, $submenu)) {
foreach ($submenu[$parent_slug] as $item) {
array_push($response, $this->_prepare_menu_item($item, false));
}
}
return $response;
}
}