425 lines
13 KiB
PHP
425 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* class-groups-post-access-legacy.php
|
|
*
|
|
* Copyright (c) "kento" Karim Rahimpur www.itthinx.com
|
|
*
|
|
* This code is released under the GNU General Public License.
|
|
* See COPYRIGHT.txt and LICENSE.txt.
|
|
*
|
|
* This code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* This header and all notices must be kept intact.
|
|
*
|
|
* @author Karim Rahimpur
|
|
* @package groups
|
|
* @since groups 2.0.0
|
|
*/
|
|
|
|
if ( !defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Post access restrictions.
|
|
*/
|
|
class Groups_Post_Access_Legacy {
|
|
|
|
const POSTMETA_PREFIX = 'groups-';
|
|
|
|
const CACHE_GROUP = 'groups';
|
|
const CAN_READ_POST = 'legacy_can_read_post';
|
|
|
|
const READ_POST_CAPABILITY = 'groups_read_post';
|
|
const READ_POST_CAPABILITY_NAME = 'Read Post';
|
|
const READ_POST_CAPABILITIES = 'read_post_capabilities';
|
|
const POST_TYPES = 'post_types';
|
|
|
|
/**
|
|
* Create needed capabilities on plugin activation.
|
|
* Must be called explicitly or hooked into activation.
|
|
*/
|
|
public static function activate() {
|
|
if ( !Groups_Capability::read_by_capability( self::READ_POST_CAPABILITY ) ) {
|
|
Groups_Capability::create( array( 'capability' => self::READ_POST_CAPABILITY ) );
|
|
// default read caps
|
|
Groups_Options::update_option( Groups_Post_Access_Legacy::READ_POST_CAPABILITIES, array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) );
|
|
// for translation
|
|
// @see self::READ_POST_CAPABILITY_NAME
|
|
__( 'Read Post', 'groups' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up filters to restrict access.
|
|
*/
|
|
public static function init() {
|
|
// Before Groups 2.0.0 this was called through Groups_Controller::activate() but
|
|
// now we only need to create the capabilities if legacy is enabled.
|
|
self::activate();
|
|
// post access
|
|
add_filter( 'posts_where', array( __CLASS__, 'posts_where' ), 10, 2 );
|
|
add_filter( 'get_pages', array( __CLASS__, 'get_pages' ), 1 );
|
|
if ( apply_filters( 'groups_filter_the_posts', false ) ) {
|
|
add_filter( 'the_posts', array( __CLASS__, 'the_posts' ), 1, 2 );
|
|
}
|
|
add_filter( 'wp_get_nav_menu_items', array( __CLASS__, 'wp_get_nav_menu_items' ), 1, 3 );
|
|
// content access
|
|
add_filter( 'get_the_excerpt', array( __CLASS__, 'get_the_excerpt' ), 1 );
|
|
add_filter( 'the_content', array( __CLASS__, 'the_content' ), 1 );
|
|
// edit & delete post
|
|
add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap' ), 10, 4 );
|
|
add_action( 'groups_deleted_capability_capability', array( __CLASS__, 'groups_deleted_capability_capability' ) );
|
|
}
|
|
|
|
/**
|
|
* Restrict access to edit or delete posts based on the post's access restrictions.
|
|
*
|
|
* @param array $caps
|
|
* @param string $cap
|
|
* @param int $user_id
|
|
* @param array $args
|
|
* @return array
|
|
*/
|
|
public static function map_meta_cap( $caps, $cap, $user_id, $args ) {
|
|
if ( isset( $args[0] ) ) {
|
|
if ( strpos( $cap, 'edit_' ) === 0 || strpos( $cap, 'delete_' ) === 0 ) {
|
|
if ( $post_type = get_post_type( $args[0] ) ) {
|
|
|
|
$edit_post_type = 'edit_' . $post_type;
|
|
$delete_post_type = 'delete_' . $post_type;
|
|
if ( $post_type_object = get_post_type_object( $post_type ) ) {
|
|
if ( !isset( $post_type_object->capabilities ) ) {
|
|
$post_type_object->capabilities = array();
|
|
}
|
|
$caps_object = get_post_type_capabilities( $post_type_object );
|
|
if ( isset( $caps_object->edit_post ) ) {
|
|
$edit_post_type = $caps_object->edit_post;
|
|
}
|
|
if ( isset( $caps_object->delete_post ) ) {
|
|
$delete_post_type = $caps_object->delete_post;
|
|
}
|
|
}
|
|
|
|
if ( $cap === $edit_post_type || $cap === $delete_post_type ) {
|
|
$post_id = null;
|
|
if ( is_numeric( $args[0] ) ) {
|
|
$post_id = $args[0];
|
|
} else if ( $args[0] instanceof WP_Post ) {
|
|
$post_id = $post->ID;
|
|
}
|
|
if ( $post_id ) {
|
|
if ( !self::user_can_read_post( $post_id, $user_id ) ) {
|
|
$caps[] = 'do_not_allow';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $caps;
|
|
}
|
|
|
|
/**
|
|
* Filters out posts that the user should not be able to access.
|
|
*
|
|
* @param string $where current where conditions
|
|
* @param WP_Query $query current query
|
|
* @return string modified $where
|
|
*/
|
|
public static function posts_where( $where, $query ) {
|
|
|
|
global $wpdb;
|
|
|
|
$user_id = get_current_user_id();
|
|
|
|
// if administrators can override access, don't filter
|
|
if ( _groups_admin_override() ) {
|
|
return $where;
|
|
}
|
|
|
|
// 1. Get all the capabilities that the user has, including those that are inherited:
|
|
$caps = array();
|
|
if ( $user = new Groups_User( $user_id ) ) {
|
|
$capabilities = $user->capabilities_deep;
|
|
if ( is_array( $capabilities ) ) {
|
|
foreach ( $capabilities as $capability ) {
|
|
$caps[] = "'". $capability . "'";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( count( $caps ) > 0 ) {
|
|
$caps = implode( ',', $caps );
|
|
} else {
|
|
$caps = '\'\'';
|
|
}
|
|
|
|
// 2. Filter the posts that require a capability that the user doesn't
|
|
// have, or in other words: exclude posts that the user must NOT access:
|
|
|
|
// The following is not correct in that it requires the user to have ALL capabilities:
|
|
// $where .= sprintf(
|
|
// " AND {$wpdb->posts}.ID NOT IN (SELECT DISTINCT ID FROM $wpdb->posts LEFT JOIN $wpdb->postmeta on {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id WHERE {$wpdb->postmeta}.meta_key = '%s' AND {$wpdb->postmeta}.meta_value NOT IN (%s) ) ",
|
|
// self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY,
|
|
// $caps
|
|
// );
|
|
|
|
// This allows the user to access posts where the posts are not restricted or where
|
|
// the user has ANY of the capabilities:
|
|
$where .= sprintf(
|
|
" AND {$wpdb->posts}.ID IN " .
|
|
" ( " .
|
|
" SELECT ID FROM $wpdb->posts WHERE ID NOT IN ( SELECT post_id FROM $wpdb->postmeta WHERE {$wpdb->postmeta}.meta_key = '%s' ) " . // posts without access restriction
|
|
" UNION ALL " . // we don't care about duplicates here, just make it quick
|
|
" SELECT post_id AS ID FROM $wpdb->postmeta WHERE {$wpdb->postmeta}.meta_key = '%s' AND {$wpdb->postmeta}.meta_value IN (%s) " . // posts that require any capability the user has
|
|
" ) ",
|
|
self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY,
|
|
self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY,
|
|
$caps
|
|
);
|
|
|
|
return $where;
|
|
}
|
|
|
|
/**
|
|
* Filter pages by access capability.
|
|
*
|
|
* @param array $pages
|
|
*/
|
|
public static function get_pages( $pages ) {
|
|
$result = array();
|
|
$user_id = get_current_user_id();
|
|
foreach ( $pages as $page ) {
|
|
if ( self::user_can_read_post( $page->ID, $user_id ) ) {
|
|
$result[] = $page;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Filter posts by access capability.
|
|
*
|
|
* @param array $posts list of posts
|
|
* @param WP_Query $query
|
|
*/
|
|
public static function the_posts( $posts, &$query ) {
|
|
$result = array();
|
|
$user_id = get_current_user_id();
|
|
foreach ( $posts as $post ) {
|
|
if ( self::user_can_read_post( $post->ID, $user_id ) ) {
|
|
$result[] = $post;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Filter menu items by access capability.
|
|
*
|
|
* @param array $items
|
|
* @param mixed $menu
|
|
* @param array $args
|
|
*/
|
|
public static function wp_get_nav_menu_items( $items = null, $menu = null, $args = null ) {
|
|
$result = array();
|
|
$user_id = get_current_user_id();
|
|
foreach ( $items as $item ) {
|
|
if ( self::user_can_read_post( $item->object_id, $user_id ) ) {
|
|
$result[] = $item;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Filter excerpt by access capability.
|
|
*
|
|
* @param string $output
|
|
* @return $output if access granted, otherwise ''
|
|
*/
|
|
public static function get_the_excerpt( $output ) {
|
|
global $post;
|
|
$result = '';
|
|
if ( isset( $post->ID ) ) {
|
|
if ( self::user_can_read_post( $post->ID ) ) {
|
|
$result = $output;
|
|
}
|
|
} else {
|
|
// not a post, don't interfere
|
|
$result = $output;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Filter content by access capability.
|
|
*
|
|
* @param string $output
|
|
* @return $output if access granted, otherwise ''
|
|
*/
|
|
public static function the_content( $output ) {
|
|
global $post;
|
|
$result = '';
|
|
if ( isset( $post->ID ) ) {
|
|
if ( self::user_can_read_post( $post->ID ) ) {
|
|
$result = $output;
|
|
}
|
|
} else {
|
|
// not a post, don't interfere
|
|
$result = $output;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Adds an access capability requirement.
|
|
*
|
|
* $map must contain 'post_id' (*)
|
|
*
|
|
* For now this only should be used to add the READ_POST_CAPABILITY which
|
|
* it does automatically. Nothing else is checked for granting access.
|
|
*
|
|
* (*) Revisions : As of Groups 1.3.13 and at WordPress 3.6.1, as
|
|
* add_post_meta stores postmeta for the revision's parent, we retrieve
|
|
* the parent's post ID if it applies and check against that to see if
|
|
* that capability is already present. This is to avoid duplicating
|
|
* the already existing postmeta entry (which ocurred in previous
|
|
* versions).
|
|
*
|
|
* @param array $map
|
|
* @return true if the capability could be added to the post, otherwis false
|
|
*/
|
|
public static function create( $map ) {
|
|
extract( $map );
|
|
$result = false;
|
|
|
|
if ( !isset( $capability ) ) {
|
|
$capability = self::READ_POST_CAPABILITY;
|
|
}
|
|
|
|
if ( !empty( $post_id ) && !empty( $capability) ) {
|
|
if ( Groups_Capability::read_by_capability( $capability ) ) {
|
|
if ( $revision_parent_id = wp_is_post_revision( $post_id ) ) {
|
|
$post_id = $revision_parent_id;
|
|
}
|
|
if ( !in_array( $capability, get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY ) ) ) {
|
|
$result = add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY, $capability );
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the post requires the given capability to grant access.
|
|
*
|
|
* Currently only READ_POST_CAPABILITY should be used, this is also taken
|
|
* as the default.
|
|
*
|
|
* @param int $post_id
|
|
* @param string $capability capability label
|
|
* @return true if the capability is required, otherwise false
|
|
*/
|
|
public static function read( $post_id, $capability = self::READ_POST_CAPABILITY ) {
|
|
$result = false;
|
|
$caps = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY );
|
|
if ( $caps ) {
|
|
$result = in_array( $capability, $caps );
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Currently does nothing, always returns false.
|
|
*
|
|
* @param array $map
|
|
* @return false
|
|
*/
|
|
public static function update( $map ) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes a capability requirement from a post.
|
|
*
|
|
* @param int $post_id
|
|
* @param string $capability defaults to groups_read_post, removes all if null is given
|
|
* @return true on success, otherwise false
|
|
*/
|
|
public static function delete( $post_id, $capability = self::READ_POST_CAPABILITY ) {
|
|
$result = false;
|
|
if ( !empty( $post_id ) ) {
|
|
if ( !empty( $capability ) ) {
|
|
$result = delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY, $capability );
|
|
} else {
|
|
$result = delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY );
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of capabilities that grant access to the post.
|
|
*
|
|
* @param int $post_id
|
|
* @return array of string, capabilities
|
|
*/
|
|
public static function get_read_post_capabilities( $post_id ) {
|
|
return get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY );
|
|
}
|
|
|
|
/**
|
|
* Returns true if the user has any of the capabilities that grant access to the post.
|
|
*
|
|
* @param int $post_id post id
|
|
* @param int $user_id user id or null for current user
|
|
* @return boolean true if user can read the post
|
|
*/
|
|
public static function user_can_read_post( $post_id, $user_id = null ) {
|
|
$result = false;
|
|
if ( !empty( $post_id ) ) {
|
|
if ( $user_id === null ) {
|
|
$user_id = get_current_user_id();
|
|
}
|
|
$cached = Groups_Cache::get( self::CAN_READ_POST . '_' . $user_id . '_' . $post_id, self::CACHE_GROUP );
|
|
if ( $cached !== null ) {
|
|
$result = $cached->value;
|
|
unset( $cached ) ;
|
|
} else {
|
|
$groups_user = new Groups_User( $user_id );
|
|
$read_caps = self::get_read_post_capabilities( $post_id );
|
|
if ( !empty( $read_caps ) ) {
|
|
foreach( $read_caps as $read_cap ) {
|
|
if ( $groups_user->can( $read_cap ) ) {
|
|
$result = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$result = true;
|
|
}
|
|
$result = apply_filters( 'groups_post_access_user_can_read_post', $result, $post_id, $user_id );
|
|
Groups_Cache::set( self::CAN_READ_POST . '_' . $user_id . '_' . $post_id, $result, self::CACHE_GROUP );
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Hooks into groups_deleted_capability_capability to remove existing access
|
|
* restrictions based on the deleted capability.
|
|
*
|
|
* @param string $name of the deleted capability
|
|
*/
|
|
public static function groups_deleted_capability_capability( $capability ) {
|
|
delete_metadata( 'post', null, self::POSTMETA_PREFIX . self::READ_POST_CAPABILITY, $capability, true );
|
|
}
|
|
|
|
}
|
|
Groups_Post_Access_Legacy::init();
|