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();