Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
chromed
/
wp-content
/
plugins
/
advanced-access-manager
/
application
/
Service
:
Content.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * ====================================================================== * LICENSE: This file is subject to the terms and conditions defined in * * file 'license.txt', which is part of this source code package. * * ====================================================================== */ /** * Content (aka Posts & Terms) service * * @package AAM * @version 7.0.0 */ class AAM_Service_Content { use AAM_Service_BaseTrait; /** * Default configurations * * @version 7.0.6 */ const DEFAULT_CONFIG = [ 'service.post_types.manage_all' => false, 'service.taxonomies.manage_all' => false, 'service.posts.decorate_teaser' => true ]; /** * Collection of post type caps * * This is a collection of post type capabilities for optimization reasons. It * is used by _map_meta_cap method to determine if additional check needs to be * perform * * @var array * @access private * * @version 7.0.0 */ private $_content_capabilities = array( 'edit_post', 'edit_page', 'read_post', 'read_page', 'publish_post' ); /** * Constructor * * @return void * @access protected * * @version 7.0.4 */ protected function __construct() { add_filter('aam_get_config_filter', function($result, $key) { if (empty($result) && array_key_exists($key, self::DEFAULT_CONFIG)) { $result = self::DEFAULT_CONFIG[$key]; } return $result; }, 10, 2); // Register RESTful API AAM_Restful_Content::bootstrap(); add_action('init', function() { $this->initialize_hooks(); }, PHP_INT_MAX); } /** * Initialize Content service hooks * * @return void * @access protected * * @version 7.0.4 */ protected function initialize_hooks() { if (is_admin()) { // Hook that initialize the AAM UI part of the service add_action('aam_initialize_ui_action', function () { AAM_Backend_Feature_Main_Content::register(); }); // Check if Access Manager metabox feature is enabled $metaboxEnabled = AAM::api()->config->get( 'core.settings.ui.render_access_metabox' ); if ($metaboxEnabled) { // Register custom access control metabox add_action( 'add_meta_boxes', function() { $this->_register_access_manager_metabox(); } ); } } if (!is_admin()) { // Password protected filter add_filter('post_password_required', function($result, $post) { return $this->_is_password_protected($result, $post); }, 10, 2); // Manage password check expiration add_filter('post_password_expires', function($result) { return $this->_post_password_expires($result); }); // Filter navigation pages & taxonomies add_filter('wp_get_nav_menu_items', function($pages) { return $this->_get_nav_menu_items($pages); }, PHP_INT_MAX); // Manage access to frontend posts & pages add_action('wp', function() { global $wp_query; if (is_single() || is_page() || is_post_type_archive() || $wp_query->is_posts_page ) { $this->_authorize_post_access(); } }, PHP_INT_MAX); } // Control post visibility add_filter('posts_clauses_request', function($clauses, $query) { return $this->_posts_clauses_request($clauses, $query); }, 10, 2); // Evaluate if current user can see full content or only a teaser message add_filter( 'the_content', function($content) { return $this->_the_content($content); }, PHP_INT_MAX ); // Evaluate if user can comment on a post add_filter('comments_open', function ($open, $post_id) { // If Leave Comments option is defined then override the default status. // Otherwise keep it as-is if (AAM::api()->posts()->is_denied_to($post_id, 'comment')) { $open = false; } return $open; }, 10, 2); // Check if user has ability to perform certain task based on provided // capability and meta data add_filter('map_meta_cap', function($caps, $cap, $_, $args) { return $this->_map_meta_cap($caps, $cap, $args); }, PHP_INT_MAX, 4); // REST API action authorization. Triggered before call is dispatched add_filter( 'rest_request_before_callbacks', function($response, $_, $request) { return $this->_rest_request_before_callbacks($response, $request); }, 10, 3 ); // Audit all registered post types and adjust access controls accordingly foreach(get_post_types([], 'objects') as $post_type) { // REST API. Control if user is allowed to publish content add_filter("rest_pre_insert_{$post_type->name}", function ($post, $request) { $status = (isset($request['status']) ? $request['status'] : null); if (in_array($status, array('publish', 'future'), true)) { $post_id = intval($request['id']); if (AAM::api()->posts()->is_denied_to($post_id, 'publish')) { $post = new WP_Error( 'rest_cannot_publish', 'You are not allowed to publish this content', [ 'status' => rest_authorization_required_code() ] ); } } return $post; }, 10, 2); // Populate the collection of post type caps foreach ([ 'edit_post', 'read_post', 'delete_post', 'publish_posts' ] as $cap) { $meta_cap = $post_type->cap->{$cap}; if (!empty($meta_cap) && !in_array($meta_cap, $this->_content_capabilities, true) && ($meta_cap !== 'do_not_allow') ) { $this->_content_capabilities[] = $cap; } } } } /** * Main frontend access control hook * * @return void * * @access private * @global WP_Query $wp_query * * @version 7.0.3 */ private function _authorize_post_access() { $post = AAM::api()->misc->get_current_post(); if (!empty($post)) { $service = AAM::api()->posts(); if ($service->is_restricted($post)) { AAM::api()->redirect->do_access_denied_redirect(); } elseif ($service->is_redirected($post)) { AAM::api()->redirect->do_redirect($service->get_redirect($post)); } } } /** * Check if post is password protected * * This callback is used by the Frontend to determine if current post requires * password in order to see its content * * @param boolean $result * @param WP_Post $post * * @return boolean * @access private * * @version 7.0.0 */ private function _is_password_protected($result, $post) { // Honor the manually set password on the post if (($result === false) && is_a($post, 'WP_Post')) { $result = is_wp_error($this->_verify_post_password($post)); } return $result; } /** * Check PASSWORD PROTECTED access option * * If post has password set, return WP_Error so the application can do further * authorization process. * * @param WP_Post $post * * @return boolean|WP_Error * @access private * * @version 7.0.0 */ private function _verify_post_password($post) { $result = true; if (AAM::api()->posts()->is_password_protected($post)) { // Load hash checker if (!class_exists('PasswordHash')) { require_once ABSPATH . WPINC . '/class-phpass.php'; } $checker = new PasswordHash(8, true); // If password is empty or not provided, try to read it from the cookie. // This is the default WordPress behavior when it comes to password // protected posts/pages $is_matched = $checker->CheckPassword( AAM::api()->posts()->get_password($post), wp_unslash(AAM::api()->misc->get( $_COOKIE, 'wp-postpass_' . COOKIEHASH, '' )) ); if ($is_matched === false) { $result = new WP_Error( 'rest_unauthorized', 'The post is password protected. Invalid password provided.', array('status' => 401) ); } } return $result; } /** * Redefine entered password TTL * * @param int $expire * * @return int * @access private * * @version 7.0.0 */ private function _post_password_expires($expire) { $ttl = AAM::api()->config->get( 'service.content.password_ttl', null ); return !empty($ttl) ? time() + strtotime($ttl) : $expire; } /** * Register Access Manager metabox on post edit screen * * @return void * @access private * * @version 7.0.0 */ private function _register_access_manager_metabox() { global $post; if (is_a($post, 'WP_Post')) { add_meta_box( 'aam-access-manager', __('Access Manager', 'advanced-access-manager'), function () { global $post; echo AAM_Backend_View::renderPostMetabox($post); }, null, 'advanced', 'high' ); } } /** * Filter traditional navigation menu * * @param array $pages * * @return array * @access private * * @version 7.0.0 */ private function _get_nav_menu_items($pages) { if (is_array($pages)) { $service = AAM::api()->posts(); foreach ($pages as $i => $page) { if (in_array($page->type, array('post_type', 'custom'), true)) { if ($service->is_hidden($page->object_id)) { unset($pages[$i]); } } } } return $pages; } /** * After post SELECT query * * @param array $clauses * @param WP_Query $wpQuery * * @return array * @access private * * @version 7.0.0 */ private function _posts_clauses_request($clauses, $wp_query) { static $executing = false; if (!$wp_query->is_singular && !$executing) { $executing = true; $clauses['where'] .= apply_filters( 'aam_posts_where_clause_filter', $this->_prepare_post_query($wp_query), $wp_query ); $executing = false; } return $clauses; } /** * Modify content query to hide posts * * @param WP_Query $wp_query * * @return string * @access private * * @version 7.0.0 */ private function _prepare_post_query($wp_query) { global $wpdb; if (!empty($wp_query->query['post_type'])) { $post_type = $wp_query->query['post_type']; } elseif (!empty($wp_query->query_vars['post_type'])) { $post_type = $wp_query->query_vars['post_type']; } elseif ($wp_query->is_attachment) { $post_type = 'attachment'; } elseif ($wp_query->is_page) { $post_type = 'page'; } else { $post_type = 'any'; } if ($post_type === 'any') { $post_type = array_keys(get_post_types(array(), 'names')); } $area = AAM::api()->misc->get_current_area(); $post_types = (array) $post_type; $not_in = []; foreach (AAM::api()->posts()->aggregate() as $id => $perms) { // Extracting post attributes list($post_id, $post_type) = explode('|', $id); // Extracting post LIST permission $perm = isset($perms['list']) ? $perms['list'] : null; if (is_array($perm) && (empty($perm['on']) || in_array($area, $perm['on'], true)) && ($perm['effect'] !== 'allow') && in_array($post_type, $post_types, true) ) { $not_in[] = $post_id; } } if (!empty($not_in)) { $query = " AND {$wpdb->posts}.ID NOT IN (" . implode(',', $not_in) . ")"; } else { $query = ''; } return $query; } /** * Authorize RESTful action before it is dispatched by RESTful Server * * @param mixed $response * @param object $request * * @return mixed * @access private * * @version 7.0.3 */ private function _rest_request_before_callbacks($response, $request) { // Register hooks that check post access foreach (get_post_types(array('show_in_rest' => true)) as $type) { add_filter( "rest_prepare_{$type}", function($response, $post, $request) { if ($request->get_param('context') !== 'edit') { $response = $this->_authorize_post_rest_access( $response, $post, $request ); } return $response; }, 10, 3 ); } // Override the password authentication handling ONLY for posts $attrs = $request->get_attributes(); $callback = isset($attrs['callback']) ? $attrs['callback'] : null; $controller = (is_array($callback) ? array_shift($callback) : null); if (is_a($controller, 'WP_REST_Posts_Controller')) { $post = get_post($request['id']); $has_pass = isset($request['password']); // Honor the manually defined password on the post if (is_a($post, 'WP_Post') && empty($post->post_password) && $has_pass && ($request->get_method() === 'GET') ) { $request['_password'] = $request['password']; unset($request['password']); } } return $response; } /** * Check if post is allowed to be viewed through RESTful * * @param WP_REST_Response $response * @param WP_Post $post * @param WP_REST_Request $request * * @access public * @return WP_REST_Response * * @version 7.0.3 */ private function _authorize_post_rest_access($response, $post, $request) { $service = AAM::api()->posts(); if ($service->is_password_protected($post)) { $password = isset($request['_password']) ? $request['_password'] : null; if ($service->get_password($post) !== $password) { $response->set_status(401); $response->set_data([ 'code' => 'rest_unauthorized', 'message' => 'The post is password protected. Invalid password provided.' ]); } } elseif ($service->is_redirected($post)) { $redirect = $service->get_redirect($post); // Determine redirect HTTP status code and use it if applicable for given // redirect type if (!empty($redirect['http_status_code'])) { $status_code = $redirect['http_status_code']; } else { $status_code = 307; } $response->set_status($status_code); $response->set_data([ 'code' => 'rest_redirected', 'message' => 'The request is redirected to a different location', 'data' => [ 'redirect_url' => AAM::api()->redirect->to_redirect_url( $redirect ) ] ]); } elseif ($service->is_restricted($post)) { $response->set_status(401); $response->set_data([ 'code' => 'rest_unauthorized', 'message' => 'The content is restricted.' ]); } return $response; } /** * Filter post content * * @param string $content * * @return string * @access private * * @version 7.0.6 */ private function _the_content($content) { static $in = false; if (!$in) { $in = true; $post = AAM::api()->misc->get_current_post(); if (!empty($post)){ if (AAM::api()->posts()->is_teaser_message_set($post)) { // Replace the [excerpt] placeholder with posts excerpt and do // short-code evaluation $content = do_shortcode(str_replace( '[excerpt]', $post->post_excerpt, AAM::api()->posts()->get_teaser_message($post) )); // Decorate message if (AAM::api()->config->get('service.posts.decorate_teaser')) { $content = apply_filters('the_content', $content); } } } $in = false; } return $content; } /** * Check user capability * * This is a hack function that add additional layout on top of WordPress * core functionality. Based on the capability passed in the $args array as * "0" element, it performs additional check on user's capability to manage * post, users etc. * * @param array $caps * @param string $cap * @param array $args * * @return array * @access private * * @version 7.0.0 */ private function _map_meta_cap($caps, $cap, $args) { global $post; // For optimization reasons, check only caps that belong to registered post // types if (in_array($cap, $this->_content_capabilities, true)) { // Critical part of the implementation. We do not know ahead what // capability is responsible for what action when it comes to post types. if (isset($args[0]) && is_scalar($args[0])) { $post_id = intval($args[0]); } elseif (is_a($post, 'WP_Post')) { $post_id = $post->ID; } else { $post_id = null; } // If post_id is not empty, then, potentially we are checking // permission to perform one of the action against a post if (!empty($post_id)) { if (is_a($post, WP_Post::class) && $post_id === $post->ID) { $p = $post; } else { $p = get_post($post_id); } if (is_a($p, 'WP_Post')) { $post_type = get_post_type_object($p->post_type); if (is_a($post_type, 'WP_Post_Type')) { $caps = $this->__map_post_type_caps( $post_type, $cap, $caps, $p, $args ); } } } } return $caps; } /** * Map post type capability based on set permissions * * @param WP_Post_Type $post_type * @param string $cap * @param array $caps * @param WP_Post $post * @param array $args * * @return array * @access private * * @version 7.0.10 */ private function __map_post_type_caps( WP_Post_Type $post_type, $cap, $caps, WP_Post $post, $args ) { // Cover the scenario when $cap is not part of the post type capabilities // There is a bug in the WP core when user is checked for 'publish_post' // capability $primitive_cap = array_search($cap, (array) $post_type->cap); if ($primitive_cap === false) { $primitive_cap = $cap; } switch ($primitive_cap) { case 'edit_post': case 'edit_posts': case 'edit_others_posts': case 'edit_private_posts': case 'edit_published_posts': // Cover the scenario when user uses Bulk Action or Quick Edit to // change the Status to Published and post is not allowed to be // published $action = AAM::api()->misc->get($_SERVER, 'action'); $status = AAM::api()->misc->get($_SERVER, '_status'); if ( in_array($action, [ 'edit', 'inline-save'] , true) && $status === 'publish' ) { $caps = $this->_map_publish_post_caps($caps, $post->ID); } else { $caps = $this->_map_edit_post_caps($caps, $post->ID); } break; case 'delete_post': case 'delete_posts': case 'delete_private_posts': case 'delete_published_posts': case 'delete_others_posts': $caps = $this->_map_delete_post_caps($caps, $post->ID); break; case 'read_post': case 'read_private_posts': $password = (isset($args[1]) ? $args[1] : null); $caps = $this->_map_read_post_caps($caps, $post->ID, $password); break; case 'publish_post': case 'publish_posts': $caps = $this->_map_publish_post_caps($caps, $post->ID); break; default: break; } return $caps; } /** * Mutate capability meta map based on ability to publish the post * * @param array $caps * @param int $post_id * * @return array * @access private * * @version 7.0.0 */ private function _map_publish_post_caps($caps, $post_id) { if (AAM::api()->posts()->is_denied_to($post_id, 'publish')) { $caps[] = 'do_not_allow'; } return $caps; } /** * Mutate capability meta map based on ability to edit/update the post * * @param array $caps * @param int $post_id * * @return array * @access private * * @version 7.0.0 */ private function _map_edit_post_caps($caps, $post_id) { $post = get_post($post_id); $is_draft = $post->post_status === 'auto-draft'; if (!$is_draft && (AAM::api()->posts()->is_denied_to($post, 'edit'))) { $caps[] = 'do_not_allow'; } return $caps; } /** * Mutate capability meta map based on ability to trash/delete the post * * @param array $caps * @param int $post_id * * @return array * @access private * * @version 7.0.0 */ private function _map_delete_post_caps($caps, $post_id) { if (AAM::api()->posts()->is_denied_to($post_id, 'delete')) { $caps[] = 'do_not_allow'; } return $caps; } /** * Mutate capability meta map based on ability to edit/update the post * * @param array $caps * @param int $post_id * @param string|null $password * * @return array * @access private * * @version 7.0.2 */ private function _map_read_post_caps($caps, $post_id, $password = null) { $service = AAM::api()->posts(); if ($service->is_password_protected($post_id)) { if ($service->get_password($post_id) !== $password) { $caps[] = 'do_not_allow'; } } elseif ($service->is_restricted($post_id)) { $caps[] = 'do_not_allow'; } elseif ($service->is_teaser_message_set($post_id)) { $caps[] = 'do_not_allow'; } return $caps; } }