Add hierarhical managment

This commit is contained in:
Almira Krdzic
2018-07-09 12:34:06 +02:00
parent 06982f22e5
commit 77cb549a3f
9320 changed files with 436076 additions and 4793 deletions

View File

@@ -0,0 +1,576 @@
<?php
/**
* class-groups-access-meta-boxes.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 1.0.0
*/
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adds meta boxes to edit screens.
*
* @link http://codex.wordpress.org/Function_Reference/add_meta_box
*/
class Groups_Access_Meta_Boxes {
const CAPABILITY_NONCE = 'groups-meta-box-capability-nonce';
const SET_CAPABILITY = 'set-capability';
const READ_ACCESS = 'read-access';
const CAPABILITY = 'capability';
const SHOW_GROUPS = 'access-meta-box-show-groups';
const NONCE = 'groups-meta-box-nonce';
const SET_GROUPS = 'set-groups';
const GROUPS_READ = 'groups-read';
const READ = 'read';
/**
* Sets up an init hook where actions and filters are added.
*/
public static function init() {
add_action( 'init', array( __CLASS__, 'wp_init' ) );
add_action( 'admin_init', array(__CLASS__,'admin_init' ) );
}
/**
* Hooks for capabilities meta box and saving options.
*/
public static function wp_init() {
if ( current_user_can( GROUPS_ACCESS_GROUPS ) ) {
require_once GROUPS_VIEWS_LIB . '/class-groups-uie.php';
add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ), 10, 2 );
add_action( 'save_post', array( __CLASS__, 'save_post' ), 10, 2 );
add_filter( 'wp_insert_post_empty_content', array( __CLASS__, 'wp_insert_post_empty_content' ), 10, 2 );
add_filter( 'attachment_fields_to_edit', array( __CLASS__, 'attachment_fields_to_edit' ), 10, 2 );
add_filter( 'attachment_fields_to_save', array( __CLASS__, 'attachment_fields_to_save' ), 10, 2 );
}
}
/**
* Hooked on admin_init to register our action on admin_enqueue_scripts.
*/
public static function admin_init() {
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
}
/**
* Hooked on admin_enqueue_scripts to timely enqueue resources required
* on the media upload / attachment popup.
*/
public static function admin_enqueue_scripts() {
global $pagenow;
if ( $pagenow == 'upload.php' ) {
Groups_UIE::enqueue( 'select' );
}
}
/**
* Triggered by init() to add capability meta box.
*/
public static function add_meta_boxes( $post_type, $post = null ) {
global $wp_version;
$post_type_object = get_post_type_object( $post_type );
if ( $post_type_object && $post_type != 'attachment' ) {
$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
add_meta_box(
'groups-permissions',
_x( 'Groups', 'Meta box title', 'groups' ),
array( __CLASS__, 'groups' ),
null,
'side',
'high'
);
Groups_UIE::enqueue( 'select' );
if ( self::user_can_restrict() ) {
if ( $screen = get_current_screen() ) {
// help tab for group-based access restrictions
$screen->add_help_tab( array(
'id' => 'groups-groups',
'title' => __( 'Groups', 'groups' ),
'content' =>
'<p>' .
'<strong>' . __( 'Groups', 'groups' ) . '</strong>' .
'</p>' .
'<p>' .
__( 'Use the <em>Groups</em> box to limit the visibility of posts, pages and other post types.', 'groups' ) .
'</p>' .
'<p>' .
__( 'You can select one or more groups to restrict access to its members.', 'groups' ) .
( !current_user_can( GROUPS_ADMINISTER_GROUPS ) ?
' ' .
__( 'Note that you must be a member of a group to use it to restrict access.', 'groups' )
:
''
) .
'</p>' .
'<p>' .
'<strong>' . __( 'Example:', 'groups' ) . '</strong>' .
'</p>' .
__( 'Let\'s assume that you want to limit the visibility of a post to members of the <em>Premium</em> group.', 'groups' ) .
'<p>' .
' ' .
'</p>' .
__( 'Choose or enter <em>Premium</em> in the <em>Read</em> field located in the <em>Groups</em> box and save or update the post (or hit Enter).', 'groups' ) .
'<p>' .
( current_user_can( GROUPS_ADMINISTER_GROUPS ) ?
'<p>' .
__( 'In the same field, you can create a new group and restrict access. Group names are case-sensitive. In order to be able to use the new group, your user account will be assigned to it.', 'groups' ) .
'</p>'
:
''
)
) );
}
}
}
}
}
/**
* Render meta box for groups.
*
* @see do_meta_boxes()
*
* @param Object $object
* @param Object $box
*/
public static function groups( $object = null, $box = null ) {
$output = '';
$post_id = isset( $object->ID ) ? $object->ID : null;
$post_type = isset( $object->post_type ) ? $object->post_type : null;
$post_singular_name = __( 'Post', 'groups' );
if ( $post_type !== null ) {
$post_type_object = get_post_type_object( $post_type );
$labels = isset( $post_type_object->labels ) ? $post_type_object->labels : null;
if ( $labels !== null ) {
if ( isset( $labels->singular_name ) ) {
$post_singular_name = __( $labels->singular_name );
}
}
}
$output .= wp_nonce_field( self::SET_GROUPS, self::NONCE, true, false );
$output .= apply_filters( 'groups_access_meta_boxes_groups_before_read_groups', '', $object, $box );
$output .= '<div class="select-read-groups-container">';
if ( self::user_can_restrict() ) {
$include = self::get_user_can_restrict_group_ids();
$groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
$groups_read = get_post_meta( $post_id, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
$read_help = sprintf(
__( 'You can restrict the visibility of this %1$s to group members. Choose one or more groups that are allowed to read this %2$s. If no groups are chosen, the %3$s is visible to anyone.', 'groups' ),
$post_singular_name,
$post_singular_name,
$post_singular_name
);
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
$read_help .= ' ' . __( 'You can create a new group by indicating the group\'s name.', 'groups' );
}
$output .= sprintf(
'<label title="%s">',
esc_attr( $read_help )
);
$output .= __( 'Read', 'groups' );
$output .= ' ';
$output .= sprintf(
'<select class="select groups-read" name="%s" multiple="multiple" placeholder="%s" data-placeholder="%s" title="%s">',
self::GROUPS_READ . '[]',
esc_attr( __( 'Anyone &hellip;', 'groups' ) ),
esc_attr( __( 'Anyone &hellip;', 'groups' ) ),
esc_attr( $read_help )
);
$output .= '<option value=""></option>';
foreach( $groups as $group ) {
$output .= sprintf( '<option value="%s" %s>', esc_attr( $group->group_id ), in_array( $group->group_id, $groups_read ) ? ' selected="selected" ' : '' );
$output .= wp_filter_nohtml_kses( $group->name );
$output .= '</option>';
}
$output .= '</select>';
$output .= '</label>';
$output .= Groups_UIE::render_select(
'.select.groups-read',
true,
true,
current_user_can( GROUPS_ADMINISTER_GROUPS )
);
$output .= '<p class="description">';
$output .= sprintf(
__( 'Restricts the visibility of this %s to members of the chosen groups.', 'groups' ),
$post_singular_name
);
$output .= '</p>';
} else {
$output .= '<p class="description">';
$output .= sprintf( __( 'You cannot set any access restrictions.', 'groups' ), $post_singular_name );
$style = 'cursor:help;vertical-align:middle;';
if ( current_user_can( GROUPS_ADMINISTER_OPTIONS ) ) {
$style = 'cursor:pointer;vertical-align:middle;';
$output .= sprintf( '<a href="%s">', esc_url( admin_url( 'admin.php?page=groups-admin-options' ) ) );
}
$output .= sprintf( '<img style="%s" alt="?" title="%s" src="%s" />', $style, esc_attr( __( 'You need to have permission to set access restrictions.', 'groups' ) ), esc_attr( GROUPS_PLUGIN_URL . 'images/help.png' ) );
if ( current_user_can( GROUPS_ADMINISTER_OPTIONS ) ) {
$output .= '</a>';
}
$output .= '</p>';
}
$output .= '</div>'; // .select-read-groups-container
$output .= apply_filters( 'groups_access_meta_boxes_groups_after_read_groups', '', $object, $box );
$output = apply_filters( 'groups_access_meta_boxes_groups', $output, $object, $box );
echo $output;
}
/**
* Invokes our save_post() if the post content is considered empty.
* This is required because even on an empty post, we want to allow to
* quick-create group and category as well as assign capabilities.
* At WordPress 3.6.1, this is the only way we can achieve that, because
* the save_post action is not invoked if the post content is considered
* empty.
*
* @param boolean $maybe_empty
* @param array $postarr
* @return boolean
*/
public static function wp_insert_post_empty_content( $maybe_empty, $postarr ) {
// Only consider invoking save_post() here, if the post content is
// considered to be empty at this stage. This is so we don't end up
// having save_post() invoked twice when the post is not empty.
if ( $maybe_empty ) {
$post_id = !empty( $postarr['ID'] ) ? $postarr['ID'] : !empty( $postarr['post_ID'] ) ? $postarr['post_ID'] : null;
if ( $post_id ) {
self::save_post( $post_id );
}
}
return $maybe_empty;
}
/**
* Save the group access restriction.
*
* @param int $post_id
* @param mixed $post post data (not used here)
*/
public static function save_post( $post_id = null, $post = null ) {
// This is called multiple times and if a new post is created and a new group is requested*,
// we can end up without the new group being assigned to the post unless we duely check
// for revision and autosave:
// (* on the second call, the new group exists and it will bail out on "if ( !( $group = Groups_Group::read_by_name( $name ) ) ) { ...")
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE || wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) ) {
} else {
$post_type = get_post_type( $post_id );
$post_type_object = get_post_type_object( $post_type );
if ( $post_type_object && $post_type != 'attachment' ) {
$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
if ( self::user_can_restrict() ) {
if ( isset( $_POST[self::NONCE] ) && wp_verify_nonce( $_POST[self::NONCE], self::SET_GROUPS ) ) {
$post_type = isset( $_POST['post_type'] ) ? $_POST['post_type'] : null;
if ( $post_type !== null ) {
// See http://codex.wordpress.org/Function_Reference/current_user_can 20130119 WP 3.5
// "... Some capability checks (like 'edit_post' or 'delete_page') require this [the post ID] be provided."
// If the post ID is not provided, it will throw:
// PHP Notice: Undefined offset: 0 in /var/www/groups-forums/wp-includes/capabilities.php on line 1067
$edit_post_type = 'edit_' . $post_type;
if ( $post_type_object = get_post_type_object( $post_type ) ) {
if ( !isset( $post_type_object->capabilities ) ) {
// get_post_type_capabilities() (WP 3.8) will throw a warning
// when trying to merge the missing property otherwise. It's either a
// bug or the function's documentation should make it clear that you
// have to provide that.
$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 ( current_user_can( $edit_post_type, $post_id ) ) {
$include = self::get_user_can_restrict_group_ids();
$groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
$user_group_ids_deep = array();
foreach( $groups as $group ) {
$user_group_ids_deep[] = $group->group_id;
}
$group_ids = array();
$submitted_group_ids = !empty( $_POST[self::GROUPS_READ] ) && is_array( $_POST[self::GROUPS_READ] ) ? $_POST[self::GROUPS_READ] : array();
// assign requested groups and create and assign new groups if allowed
foreach( $submitted_group_ids as $group_id ) {
if ( is_numeric( $group_id ) ) {
if ( in_array( $group_id, $user_group_ids_deep ) ) {
$group_ids[] = $group_id;
}
} else {
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
$creator_id = get_current_user_id();
$datetime = date( 'Y-m-d H:i:s', time() );
$name = ucwords( strtolower( trim( preg_replace( '/\s+/', ' ', $group_id ) ) ) );
if ( strlen( $name ) > 0 ) {
if ( !( $group = Groups_Group::read_by_name( $name ) ) ) {
if ( $group_id = Groups_Group::create( compact( 'creator_id', 'datetime', 'name' ) ) ) {
if ( Groups_User_Group::create( array( 'user_id' => $creator_id, 'group_id' => $group_id ) ) ) {
$group_ids[] = $group_id;
}
}
}
}
}
}
}
do_action( 'groups_access_meta_boxes_before_groups_read_update', $post_id, $group_ids );
$update_result = Groups_Post_Access::update( array( 'post_id' => $post_id, 'groups_read' => $group_ids ) );
do_action( 'groups_access_meta_boxes_after_groups_read_update', $post_id, $group_ids, $update_result );
}
}
}
}
}
}
}
}
/**
* Enqueue scripts and styles.
*/
private static function enqueue() {
global $groups_version;
if ( !wp_script_is( 'selectize' ) ) {
wp_enqueue_script( 'selectize', GROUPS_PLUGIN_URL . 'js/selectize/selectize.min.js', array( 'jquery' ), $groups_version, false );
}
if ( !wp_style_is( 'selectize' ) ) {
wp_enqueue_style( 'selectize', GROUPS_PLUGIN_URL . 'css/selectize/selectize.bootstrap2.css', array(), $groups_version );
}
}
/**
* Render groups box for attachment post type (Media).
*
* @param array $form_fields
* @param object $post
* @return array
*/
public static function attachment_fields_to_edit( $form_fields, $post ) {
Groups_UIE::enqueue( 'select' );
$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
if ( self::user_can_restrict() ) {
$include = self::get_user_can_restrict_group_ids();
$groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
$groups_read = get_post_meta( $post->ID, Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
$output = '';
$post_singular_name = __( 'Media', 'groups' );
$output .= __( 'Read', 'groups' );
// On attachments edited within the 'Insert Media' popup, the update is triggered too soon and we end up with only the last capability selected.
// This occurs when using normal checkboxes as well as the select below (Chosen and Selectize tested).
// With checkboxes it's even more confusing, it's actually better to have it using a select as below,
// because the visual feedback corresponds with what is assigned.
// See http://wordpress.org/support/topic/multiple-access-restrictions-for-media-items-are-not-saved-in-grid-view
// and https://core.trac.wordpress.org/ticket/28053 - this is an issue with multiple value fields and should
// be fixed within WordPress.
// $output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
// $output .= '<ul>';
// foreach( $groups as $group ) {
// $checked = in_array( $group->group_id, $groups_read ) ? ' checked="checked" ' : '';
// $output .= '<li>';
// $output .= '<label>';
// $output .= '<input name="attachments[' . $post->ID . '][' . self::GROUPS_READ . '][]" ' . $checked . ' type="checkbox" value="' . esc_attr( $group->group_id ) . '" />';
// $output .= wp_filter_nohtml_kses( $group->name );
// $output .= '</label>';
// $output .= '</li>';
// }
// $output .= '</ul>';
// $output .= '</div>';
$output .= '<div class="select-groups-container">';
$select_id = 'attachments-' . $post->ID . '-' . self::GROUPS_READ;
$output .= sprintf(
'<select id="%s" class="select groups-read" name="%s" multiple="multiple" placeholder="%s" data-placeholder="%s" title="%s">',
$select_id,
'attachments[' . $post->ID . '][' . self::GROUPS_READ . '][]',
esc_attr( __( 'Anyone &hellip;', 'groups' ) ),
esc_attr( __( 'Anyone &hellip;', 'groups' ) ),
__( 'You can restrict the visibility to group members. Choose one or more groups to restrict access. If no groups are chosen, this entry is visible to anyone.', 'groups' ) .
current_user_can( GROUPS_ADMINISTER_GROUPS ) ? ' ' . __( 'You can create a new group by indicating the group\'s name.', 'groups' ) : ''
);
$output .= '<option value=""></option>';
foreach( $groups as $group ) {
$output .= sprintf( '<option value="%s" %s>', esc_attr( $group->group_id ), in_array( $group->group_id, $groups_read ) ? ' selected="selected" ' : '' );
$output .= wp_filter_nohtml_kses( $group->name );
$output .= '</option>';
}
$output .= '</select>';
$output .= Groups_UIE::render_select( '#'.$select_id, true, true, current_user_can( GROUPS_ADMINISTER_GROUPS ) );
$output .= '</div>';
$output .= '<p class="description">';
$output .= __( 'Restricts the visibility of this entry to members of the chosen groups.', 'groups' );
$output .= '</p>';
$output .= '<p class="description">';
$output .= __( 'The attachment page is restricted to authorized users, but due to technical limitations, the file can still be accessed directly via its URL.', 'groups' );
$output .= ' ';
$output .= sprintf( __( 'Please use <a href="%s" target="_blank">Groups File Access</a> for files that require complete protection.', 'groups' ), esc_url( 'http://www.itthinx.com/shop/groups-file-access/' ) );
$output .= '</p>';
$form_fields['groups_read'] = array(
'label' => __( 'Groups', 'groups' ),
'input' => 'html',
'html' => $output
);
}
}
return $form_fields;
}
/**
* Save groups for attachment post type (Media).
* When multiple attachments are saved, this is called once for each.
*
* @param array $post post data
* @param array $attachment attachment field data
* @return array
*/
public static function attachment_fields_to_save( $post, $attachment ) {
$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
// if we're here, we assume the user is allowed to edit attachments,
// but we still need to check if the user can restrict access
if ( self::user_can_restrict() ) {
$post_id = null;
if ( isset( $post['ID'] ) ) {
$post_id = $post['ID'];
} else if ( isset( $post['post_ID'] ) ) {
$post_id = $post['post_ID'];
}
if ( $post_id !== null ) {
$include = self::get_user_can_restrict_group_ids();
$groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
$group_ids = array();
if ( !empty( $attachment[self::GROUPS_READ] ) && is_array( $attachment[self::GROUPS_READ] ) ) {
foreach( $groups as $group ) {
if ( in_array( $group->group_id, $attachment[self::GROUPS_READ] ) ) {
$group_ids[] = $group->group_id;
}
}
}
do_action( 'groups_access_meta_boxes_before_groups_read_update', $post_id, $group_ids );
$update_result = Groups_Post_Access::update( array( 'post_id' => $post_id, 'groups_read' => $group_ids ) );
do_action( 'groups_access_meta_boxes_after_groups_read_update', $post_id, $group_ids, $update_result );
}
}
}
return $post;
}
/**
* Returns true if the user can restrict access to posts. The current user is
* assumed by default unless a user ID is provided.
*
* @param int $user_id indicates the desired user, otherwise for the current user
* @return boolean
*/
public static function user_can_restrict( $user_id = null ) {
if ( $user_id === null ) {
$user_id = get_current_user_id();
}
$user = new Groups_User( $user_id);
return $user->can( GROUPS_RESTRICT_ACCESS );
}
/**
* Returns the group IDs of the groups that the user can use to restrict access.
*
* If the user can GROUPS_RESTRICT_ACCESS, the following group IDs are returned:
* - If the user can GROUPS_ADMINISTER_GROUPS, this will return the IDs of all groups.
* - Otherwise it will return the IDs of all groups that the user belongs to, directly
* or indirectly by group inheritance.
*
* If the user can not GROUPS_RESTRICT_ACCESS, an empty array is returned.
*
* @param int $user_id if provided, retrieve results for the user indicated by user ID, otherwise for the current user
* @return array of int with the group IDs
*/
public static function get_user_can_restrict_group_ids( $user_id = null ) {
$group_ids = array();
if ( $user_id === null ) {
$user_id = get_current_user_id();
}
if ( self::user_can_restrict( $user_id ) ) {
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
$group_ids = Groups_Group::get_group_ids();
} else {
$user = new Groups_User( $user_id );
$group_ids = $user->group_ids_deep;
}
if ( !empty( $group_ids ) && is_array( $group_ids ) ) {
$group_ids = array_map (array( 'Groups_Utility','id'), $group_ids );
}
}
return $group_ids;
}
/**
* @deprecated
* @return array of valid read capabilities for the current or given user
*/
public static function get_valid_read_caps_for_user( $user_id = null ) {
require_once( GROUPS_LEGACY_LIB . '/access/class-groups-access-meta-boxes-legacy.php' );
return Groups_Access_Meta_Boxes_Legacy::get_valid_read_caps_for_user( $user_id );
}
}
Groups_Access_Meta_Boxes::init();

View File

@@ -0,0 +1,185 @@
<?php
/**
* class-groups-access-shortcodes.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 1.0.0
*/
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Shortcode handlers.
*/
class Groups_Access_Shortcodes {
/**
* Defines access shortcodes.
*/
public static function init() {
// group restrictions
add_shortcode( 'groups_member', array( __CLASS__, 'groups_member' ) );
add_shortcode( 'groups_non_member', array( __CLASS__, 'groups_non_member' ) );
// capabilities
add_shortcode( 'groups_can', array( __CLASS__, 'groups_can' ) );
add_shortcode( 'groups_can_not', array( __CLASS__, 'groups_can_not' ) );
}
/**
* Takes one attribute "group" which is a comma-separated list of group
* names or ids (can be mixed).
* The content is shown if the current user belongs to the group(s).
*
* @param array $atts attributes
* @param string $content content to render
*/
public static function groups_member( $atts, $content = null ) {
$output = '';
$options = shortcode_atts( array( 'group' => '' ), $atts );
$show_content = false;
if ( $content !== null ) {
$groups_user = new Groups_User( get_current_user_id() );
$groups = explode( ',', $options['group'] );
foreach ( $groups as $group ) {
$group = trim( $group );
$current_group = Groups_Group::read( $group );
if ( !$current_group ) {
$current_group = Groups_Group::read_by_name( $group );
}
if ( $current_group ) {
if ( Groups_User_Group::read( $groups_user->user->ID , $current_group->group_id ) ) {
$show_content = true;
break;
}
}
}
if ( $show_content ) {
remove_shortcode( 'groups_member' );
$content = do_shortcode( $content );
add_shortcode( 'groups_member', array( __CLASS__, 'groups_member' ) );
$output = $content;
}
}
return $output;
}
/**
* Takes one attribute "group" which is a comma-separated list of group
* names or ids (can be mixed).
* The content is shown if the current user does NOT belong to the group(s).
*
* @param array $atts attributes
* @param string $content content to render
*/
public static function groups_non_member( $atts, $content = null ) {
$output = '';
$options = shortcode_atts( array( 'group' => '' ), $atts );
$show_content = true;
if ( $content !== null ) {
$groups_user = new Groups_User( get_current_user_id() );
$groups = explode( ',', $options['group'] );
foreach ( $groups as $group ) {
$group = trim( $group );
$current_group = Groups_Group::read( $group );
if ( !$current_group ) {
$current_group = Groups_Group::read_by_name( $group );
}
if ( $current_group ) {
if ( Groups_User_Group::read( $groups_user->user->ID , $current_group->group_id ) ) {
$show_content = false;
break;
}
}
}
if ( $show_content ) {
remove_shortcode( 'groups_non_member' );
$content = do_shortcode( $content );
add_shortcode( 'groups_non_member', array( __CLASS__, 'groups_non_member' ) );
$output = $content;
}
}
return $output;
}
/**
* Takes one attribute "capability" that must be a valid capability label
* or a list of capabilities separated by comma.
* The content is shown if the current user has one of the capabilities.
*
* @param array $atts attributes
* @param string $content content to render
*/
public static function groups_can( $atts, $content = null ) {
$output = '';
$options = shortcode_atts( array( 'capability' => '' ), $atts );
if ( $content !== null ) {
$groups_user = new Groups_User( get_current_user_id() );
$capability = $options['capability'];
$capabilities = array_map( 'trim', explode( ',', $capability ) );
$show_content = false;
foreach( $capabilities as $capability ) {
if ( $groups_user->can( $capability ) ) {
$show_content = true;
break;
}
}
if ( $show_content ) {
remove_shortcode( 'groups_can' );
$content = do_shortcode( $content );
add_shortcode( 'groups_can', array( __CLASS__, 'groups_can' ) );
$output = $content;
}
}
return $output;
}
/**
* Takes one attribute "capability" that must be a valid capability label,
* or a comma-separaed list of those.
* The content is shown if the current user has none of the capabilities.
*
* @param array $atts attributes
* @param string $content content to render
*/
public static function groups_can_not( $atts, $content = null ) {
$output = '';
$options = shortcode_atts( array( 'capability' => '' ), $atts );
if ( $content !== null ) {
$groups_user = new Groups_User( get_current_user_id() );
$capability = $options['capability'];
$capabilities = array_map( 'trim', explode( ',', $capability ) );
$show_content = true;
foreach( $capabilities as $capability ) {
if ( $groups_user->can( $capability ) ) {
$show_content = false;
break;
}
}
if ( $show_content ) {
remove_shortcode( 'groups_can_not' );
$content = do_shortcode( $content );
add_shortcode( 'groups_can_not', array( __CLASS__, 'groups_can_not' ) );
$output = $content;
}
}
return $output;
}
}
Groups_Access_Shortcodes::init();

View File

@@ -0,0 +1,320 @@
<?php
/**
* class-groups-comment-access.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.2.0
*/
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Post access restrictions.
*/
class Groups_Comment_Access {
const CACHE_GROUP = 'groups';
const COMMENT_COUNTS = 'comment_counts';
public static function init() {
add_filter( 'comments_array', array( __CLASS__, 'comments_array' ), 10, 2 );
add_filter( 'comment_feed_where', array( __CLASS__, 'comment_feed_where' ), 10, 2 );
add_filter( 'comments_clauses', array( __CLASS__, 'comments_clauses' ), 10, 2 );
// the comments_clauses filter is used in WP_Comment_Query::get_comment_ids() before the
// comments are filtered with the_comments in WP_Comment_Query::get_comments() so we don't need to do this again
//add_filter( 'the_comments', array( __CLASS__, 'the_comments' ), 10, 2 );
add_filter( 'wp_count_comments', array( __CLASS__, 'wp_count_comments' ), 999, 2 ); // see wp-includes/comment.php function wp_count_comments(...)
add_filter( 'get_comments_number', array( __CLASS__, 'get_comments_number' ), 10, 2 );
}
/**
* Filter comments on the post if the user can't read the post.
*
* @param array $comments
* @param int $post_id
*/
public static function comments_array( $comments, $post_id ) {
if ( !apply_filters( 'groups_comment_access_comments_array_apply', true, $comments, $post_id ) ) {
return $comments;
}
$result = array();
if ( Groups_Post_Access::user_can_read_post( $post_id ) ) {
$result = $comments;
}
return $result;
}
/**
* Remove comments on posts that the user cannot read.
*
* @param array $comments
* @param WP_Comment_Query $comment_query
* @return array
*/
public static function the_comments( $comments, $comment_query ) {
if ( !apply_filters( 'groups_comment_access_the_comments_apply', true, $comments, $comment_query ) ) {
return $comments;
}
$_comments = array();
foreach( $comments as $comment ) {
if ( isset( $comment->comment_post_ID ) ) {
if ( Groups_Post_Access::user_can_read_post( $comment->comment_post_ID ) ) {
$_comments[] = $comment;
}
}
}
return $_comments;
}
/**
* Filter feed comments.
*
* @param string $where
* @param WP_Query $query
* @return string
*/
public static function comment_feed_where( $where, $query ) {
if ( !apply_filters( 'groups_comment_access_comment_feed_where_apply', true, $where, $query ) ) {
return $where;
}
if ( _groups_admin_override() ) {
return $where;
}
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
return $where;
}
$where = self::build_where( $where );
return $where;
}
/**
* Filter the comments based on post read access restrictions.
*
* @param array $pieces
* @param WP_Comment_Query $comment_query
* @return array
*/
public static function comments_clauses( $pieces, $comment_query ) {
if ( !apply_filters( 'groups_comment_access_comments_clauses_apply', true, $pieces, $comment_query ) ) {
return $pieces;
}
if ( _groups_admin_override() ) {
return $pieces;
}
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
return $pieces;
}
$where = isset( $pieces['where'] ) ? $pieces['where'] : '';
$where = self::build_where( $where );
$pieces['where'] = $where;
return $pieces;
}
/**
* Adds conditions to $where to restrict comment access.
*
* @param unknown $where
* @return unknown|string
*/
private static function build_where( $where ) {
global $wpdb;
$handles_post_types = Groups_Post_Access::get_handles_post_types();
$post_types = array();
foreach( $handles_post_types as $post_type => $handles ) {
if ( $handles ) {
$post_types[] = $post_type;
}
}
if ( count( $post_types ) == 0 ) {
return $where;
}
$post_types_in = "'" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "'";
// group_ids : all the groups that the user belongs to, including those that are inherited
$user_id = get_current_user_id();
$group_ids = array();
if ( $user = new Groups_User( $user_id ) ) {
$group_ids_deep = $user->group_ids_deep;
if ( is_array( $group_ids_deep ) ) {
$group_ids = $group_ids_deep;
}
}
if ( count( $group_ids ) > 0 ) {
$group_ids = "'" . implode( "','", array_map( 'esc_sql', $group_ids ) ) . "'";
} else {
$group_ids = '\'\'';
}
// only comments from posts that the user can read
$where .= sprintf(
" AND {$wpdb->comments}.comment_post_ID NOT IN ( " .
"SELECT ID FROM $wpdb->posts WHERE " .
"post_type IN (%s) AND " .
"ID IN ( " .
"SELECT post_id FROM $wpdb->postmeta pm WHERE " .
"pm.meta_key = '%s' AND pm.meta_value NOT IN (%s) AND " .
"post_id NOT IN ( SELECT post_id FROM $wpdb->postmeta pm WHERE pm.meta_key = '%s' AND pm.meta_value IN (%s) ) " .
") " .
") ",
$post_types_in,
esc_sql( Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ ),
$group_ids,
esc_sql( Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ ),
$group_ids
);
return $where;
}
/**
* Filters comment counts.
*
* @param array $count
* @param int $post_id
* @return object comment counts as properties of the object
*/
public static function wp_count_comments( $count, $post_id ) {
if ( !apply_filters( 'groups_comment_access_wp_count_comments_apply', true, $count, $post_id ) ) {
return $count;
}
if ( _groups_admin_override() ) {
return $count;
}
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
return $count;
}
$user_id = get_current_user_id();
$cached = Groups_Cache::get( self::COMMENT_COUNTS . '_' . $user_id . '_' . intval( $post_id ), self::CACHE_GROUP );
if ( $cached !== null ) {
$count = $cached->value;
unset( $cached );
} else {
$count = self::get_comment_count( $post_id );
Groups_Cache::set( self::COMMENT_COUNTS . '_' . $user_id . '_' . intval( $post_id ), $count, self::CACHE_GROUP );
}
return $count;
}
/**
* Filters the comments number of a post.
*
* @param int $count
* @param int $post_id
* @return int number of comments (0 if there are none or the user can't read the post)
*/
public static function get_comments_number( $count, $post_id ) {
$num_comments = 0;
if ( Groups_Post_Access::user_can_read_post( $post_id ) ) {
$num_comments = $count;
}
return $num_comments;
}
/**
* Adapated from get_comment_count() to user our filter.
*
* @param number $post_id
* @return object comment counts as properties of the returned object
*/
private static function get_comment_count( $post_id = 0 ) {
global $wpdb;
$post_id = (int) $post_id;
$where = '';
if ( $post_id > 0 ) {
$where = $wpdb->prepare( "WHERE comment_post_ID = %d ", $post_id );
} else {
$where = 'WHERE 1=1 ';
}
$where = self::build_where( $where );
$where = apply_filters( 'groups_comment_access_comment_count_where', $where, $post_id );
$totals = (array) $wpdb->get_results(
"SELECT comment_approved, COUNT( * ) AS total " .
"FROM {$wpdb->comments} " .
"{$where} " .
"GROUP BY comment_approved ",
ARRAY_A
);
$comment_count = array(
'approved' => 0,
'awaiting_moderation' => 0,
'spam' => 0,
'trash' => 0,
'post-trashed' => 0,
'total_comments' => 0,
'all' => 0,
);
foreach ( $totals as $row ) {
switch ( $row['comment_approved'] ) {
case 'trash':
$comment_count['trash'] = $row['total'];
break;
case 'post-trashed':
$comment_count['post-trashed'] = $row['total'];
break;
case 'spam':
$comment_count['spam'] = $row['total'];
$comment_count['total_comments'] += $row['total'];
break;
case '1':
$comment_count['approved'] = $row['total'];
$comment_count['total_comments'] += $row['total'];
$comment_count['all'] += $row['total'];
break;
case '0':
$comment_count['awaiting_moderation'] = $row['total'];
$comment_count['total_comments'] += $row['total'];
$comment_count['all'] += $row['total'];
break;
default:
break;
}
}
$comment_count['moderated'] = $comment_count['awaiting_moderation'];
//unset( $stats['awaiting_moderation'] );
return (object) $comment_count;
}
}
Groups_Comment_Access::init();

View File

@@ -0,0 +1,838 @@
<?php
/**
* class-groups-post-access.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 1.0.0
*/
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Post access restrictions.
*/
class Groups_Post_Access {
/**
* @var string
*/
const POSTMETA_PREFIX = 'groups-';
/**
* @var string
*/
const READ = 'read';
/**
* @var string
*/
const CACHE_GROUP = 'groups';
/**
*
* @var string
*/
const CAN_READ_POST = 'can_read_post';
/**
* @deprecated
* @var string
*/
const READ_POST_CAPABILITY = 'groups_read_post';
/**
* @deprecated
* @var string
*/
const READ_POST_CAPABILITY_NAME = 'Read Post';
/**
* @deprecated
* @var string
*/
const READ_POST_CAPABILITIES = 'read_post_capabilities';
/**
* @var string
*/
const POST_TYPES = 'post_types';
/**
* @var string
*/
const COUNT_POSTS = 'count-posts';
/**
* Work done on activation, currently does nothing.
* @see Groups_Controller::activate()
*/
public static function activate() {
}
/**
* Sets up filters to restrict access.
*/
public static function init() {
// 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 );
}
// If we had a get_post filter https://core.trac.wordpress.org/ticket/12955
// add_filter( 'get_post', ... );
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 );
// @todo these could be interesting to add later ...
// add_filter( "plugin_row_meta", array( __CLASS__, "plugin_row_meta" ), 1 );
// add_filter( "posts_join_paged", array( __CLASS__, "posts_join_paged" ), 1 );
// add_filter( "posts_where_paged", array( __CLASS__, "posts_where_paged" ), 1 );
add_action( 'groups_deleted_group', array( __CLASS__, 'groups_deleted_group' ) );
add_filter( 'wp_count_posts', array( __CLASS__, 'wp_count_posts' ), 10, 3 );
// @todo enable the filter and implement below if needed to correct attachment counts
// add_filter( 'wp_count_attachments', array( __CLASS__, 'wp_count_attachments' ), 10, 2 );
// REST API
$post_types = self::get_handles_post_types();
if ( !empty( $post_types ) ) {
foreach( $post_types as $post_type => $handles ) {
if ( $handles ) {
add_filter( "rest_prepare_{$post_type}", array( __CLASS__, 'rest_prepare_post' ), 10, 3 );
}
}
}
// adjacent posts
add_filter( 'get_previous_post_where', array( __CLASS__, 'get_previous_post_where' ), 10, 5 );
add_filter( 'get_next_post_where', array( __CLASS__, 'get_next_post_where' ), 10, 5 );
}
/**
* Replicates the response for invalid post IDs when unauthorized access to a post is requested.
* There is no filter in WP_REST_Posts_Controller::get_post() nor in get_post() that we could use (WP 4.8).
*
* REST API Handbook https://developer.wordpress.org/rest-api/
*
* For development tests:
*
* 1. Install https://github.com/WP-API/Basic-Auth
* 2. Protect post 1 with group "Test".
* 3. Test access denied: $ curl http://example.com/wp-json/wp/v2/posts/1
* 4. Test access granted $ curl --user username:password https://example.com/wp-json/wp/v2/posts/1
*
* On #4 username:password are cleartext, username must belong to group "Test".
*
* @param array $response
* @param WP_Post $post
* @param string $request
* @return string[]|number[][]
*/
public static function rest_prepare_post( $response, $post, $request ) {
if ( isset( $post->ID ) && !self::user_can_read_post( $post->ID ) ) {
$response = array(
'code' => 'rest_post_invalid_id',
'message' => __( 'Invalid post ID.' ),
'data' => array( 'status' => 404 )
);
}
return $response;
}
/**
* 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 = $args[0]->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;
if ( apply_filters( 'groups_post_access_posts_where_apply', true, $where, $query ) ) {
$user_id = get_current_user_id();
// this only applies to logged in users
if ( _groups_admin_override() ) {
return $where;
}
// Groups admins see everything
if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
return $where;
}
if ( !apply_filters( 'groups_post_access_posts_where_filter_all', false ) ) {
$filter = true;
$post_types = apply_filters(
'groups_post_access_posts_where_query_get_post_types',
$query->get( 'post_type', null ),
$where,
$query
);
if ( 'any' == $post_types ) {
// we need to filter in this case as it affects any post type
} else if ( !empty( $post_types ) && is_array( $post_types ) ) {
// if there is at least one post type we handle, we need to filter
$handled = 0;
$handles_post_types = self::get_handles_post_types();
foreach( $post_types as $post_type ) {
if ( !isset( $handles_post_types[$post_type] ) || $handles_post_types[$post_type] ) {
$handled++;
}
}
$filter = $handled > 0;
} else if ( !empty( $post_types ) && is_string( $post_types ) ) {
$filter = self::handles_post_type( $post_types );
} else if ( $query->is_attachment ) {
$filter = self::handles_post_type( 'attachment' );
} else if ( $query->is_page ) {
$filter = self::handles_post_type( 'page' );
} else {
$filter = self::handles_post_type( 'post' );
}
if ( !$filter ) {
return $where;
}
}
$handles_post_types = Groups_Post_Access::get_handles_post_types();
$post_types = array();
foreach( $handles_post_types as $post_type => $handles ) {
if ( $handles ) {
$post_types[] = $post_type;
}
}
if ( count( $post_types ) == 0 ) {
return $where;
}
$post_types_in = "'" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "'";
// 1. Get all the groups that the user belongs to, including those that are inherited:
$group_ids = array();
if ( $user = new Groups_User( $user_id ) ) {
$group_ids_deep = $user->group_ids_deep;
if ( is_array( $group_ids_deep ) ) {
$group_ids = $group_ids_deep;
}
}
if ( count( $group_ids ) > 0 ) {
$group_ids = "'" . implode( "','", $group_ids ) . "'";
} else {
$group_ids = '\'\'';
}
// 2. Filter the posts:
// This allows the user to access posts where the posts are not restricted or where
// the user belongs to ANY of the groups:
// $where .= sprintf(
// " AND {$wpdb->posts}.ID IN " .
// " ( " .
// " SELECT ID FROM $wpdb->posts WHERE post_type NOT IN (%s) OR ID NOT IN ( SELECT post_id FROM $wpdb->postmeta WHERE {$wpdb->postmeta}.meta_key = '%s' ) " . // posts of a type that is not handled or 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 group the user belongs to
// " ) ",
// $post_types_in,
// self::POSTMETA_PREFIX . self::READ,
// self::POSTMETA_PREFIX . self::READ,
// $group_ids
// );
// New faster version - Exclude any post IDs from:
// posts restricted to groups that the user does not belong to MINUS posts restricted to groups to which the user belongs
$where .= sprintf(
" AND {$wpdb->posts}.ID NOT IN ( " .
"SELECT ID FROM $wpdb->posts WHERE " .
"post_type IN (%s) AND " .
"ID IN ( " .
"SELECT post_id FROM $wpdb->postmeta pm WHERE " .
"pm.meta_key = '%s' AND pm.meta_value NOT IN (%s) AND " .
"post_id NOT IN ( SELECT post_id FROM $wpdb->postmeta pm WHERE pm.meta_key = '%s' AND pm.meta_value IN (%s) ) " .
") " .
") ",
$post_types_in,
esc_sql( self::POSTMETA_PREFIX . self::READ ),
$group_ids,
esc_sql( self::POSTMETA_PREFIX . self::READ ),
$group_ids
);
}
return apply_filters( 'groups_post_access_posts_where', $where, $query );
}
/**
* Filter pages by access capability.
*
* @param array $pages
*/
public static function get_pages( $pages ) {
$result = array();
if ( apply_filters( 'groups_post_access_get_pages_apply', true, $pages ) ) {
$user_id = get_current_user_id();
foreach ( $pages as $page ) {
if ( self::user_can_read_post( $page->ID, $user_id ) ) {
$result[] = $page;
}
}
} else {
$result = $pages;
}
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();
if ( apply_filters( 'groups_post_access_the_posts_apply', true, $posts, $query ) ) {
$user_id = get_current_user_id();
foreach ( $posts as $post ) {
if ( self::user_can_read_post( $post->ID, $user_id ) ) {
$result[] = $post;
}
}
} else {
$result = $posts;
}
return $result;
}
/**
* Filter menu items by access capability.
*
* @todo admin section: this won't inhibit the items being offered to be added, although when they're added they won't show up in the menu
*
* @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();
if ( apply_filters( 'groups_post_access_wp_get_nav_menu_items_apply', true, $items, $menu, $args ) ) {
$user_id = get_current_user_id();
foreach ( $items as $item ) {
// @todo might want to check $item->object and $item->type first,
// for example these are 'page' and 'post_type' for a page
if ( self::user_can_read_post( $item->object_id, $user_id ) ) {
$result[] = $item;
}
}
} else {
$result = $items;
}
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 ( apply_filters( 'groups_post_access_get_the_excerpt_apply', true, $output ) ) {
if ( isset( $post->ID ) ) {
if ( self::user_can_read_post( $post->ID ) ) {
$result = $output;
}
} else {
// not a post, don't interfere
$result = $output;
}
} else {
$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 ( apply_filters( 'groups_post_access_the_content_apply', true, $output ) ) {
if ( isset( $post->ID ) ) {
if ( self::user_can_read_post( $post->ID ) ) {
$result = $output;
}
} else {
// not a post, don't interfere
$result = $output;
}
} else {
$result = $output;
}
return $result;
}
/**
* Hooked on the get_{$adjacent}_post_where filter to remove restricted posts.
*
* @param string $where
* @param boolean $in_same_term
* @param array $excluded_terms
* @param string $taxonomy
* @param WP_Post $post
*
* @return string $where modified if appropriate
*/
public static function get_previous_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
return self::get_next_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post );
}
/**
* Hooked on the get_{$adjacent}_post_where filter to remove restricted posts.
*
* @param string $where
* @param boolean $in_same_term
* @param array $excluded_terms
* @param string $taxonomy
* @param WP_Post $post
*
* @return string $where modified if appropriate
*/
public static function get_next_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
if ( !empty( $post ) ) {
// run it through get_posts with suppress_filters set to false so that our posts_where filter is applied and assures only accessible posts are seen
$post_ids = get_posts( array( 'post_type' => $post->post_type, 'numberposts' => -1, 'suppress_filters' => false, 'fields' => 'ids' ) );
if ( is_array( $post_ids ) && count( $post_ids ) > 0 ) {
$post_ids = array_map( 'intval', $post_ids );
$condition = ' p.ID IN (' . implode( ',', $post_ids ) . ') ';
if ( !empty( $where ) ) {
$where .= ' AND ' . $condition;
} else {
$where = ' WHERE ' . $condition;
}
}
}
return $where;
}
/**
* Adds an access requirement based on post_id and group_id.
*
* (*) 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 must contain 'post_id' (*) and 'group_id'
* @return true if the capability could be added to the post, otherwise false
*/
public static function create( $map ) {
extract( $map );
$result = false;
if ( isset( $capability ) ) {
_doing_it_wrong(
__CLASS__ . '::' . __METHOD__,
__( 'You should use Groups_Post_Access_Legacy::create() to pass a capability restriction instead.', 'groups' ),
'2.0.0'
);
}
if ( !empty( $post_id ) && !empty( $group_id ) ) {
$post_id = Groups_Utility::id( $post_id );
$group_id = Groups_Utility::id( $group_id );
if ( Groups_Group::read( $group_id ) ) {
if ( $revision_parent_id = wp_is_post_revision( $post_id ) ) {
$post_id = $revision_parent_id;
}
if ( !in_array( $group_id, get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ ) ) ) {
$result = add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
}
}
}
return $result;
}
/**
* Returns true if the post requires the user to be a member of the given group(s) to grant access.
*
* @param int $post_id ID of the post
* @param array $map should provide 'post_id' and 'groups_read'
*
* @return true if the group(s) is required, otherwise false
*/
public static function read( $post_id, $map = array() ) {
extract( $map );
$result = false;
if ( !empty( $post_id ) ) {
if ( isset( $groups_read ) ) {
if ( empty( $groups_read ) ) {
$groups_read = array();
} else if ( !is_array( $groups_read ) ) {
$groups_read = array( $groups_read );
}
$group_ids = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
if ( $group_ids ) {
foreach( $groups_read as $group_id ) {
$result = in_array( $group_id, $group_ids );
if ( !$result ) {
break;
}
}
}
}
}
return $result;
}
/**
* Update the post access restrictions.
*
* $map must provide 'post_id' (int) indicating the post's ID and 'groups_read' (int|array of int) holding group IDs that restrict read access.
*
* @param array $map
* @return array of group ids, false on failure
*/
public static function update( $map ) {
extract( $map );
$result = false;
if ( !empty( $post_id ) ) {
if ( empty( $groups_read ) ) {
$groups_read = array();
} else if ( !is_array( $groups_read ) ) {
$groups_read = array( $groups_read );
}
$groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
$current_groups_read = get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
$current_groups_read = array_map( array( 'Groups_Utility', 'id' ), $current_groups_read );
foreach( $groups_read as $group_id ) {
if ( !in_array( $group_id, $current_groups_read ) ) {
add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
}
}
foreach( $current_groups_read as $group_id ) {
if ( !in_array( $group_id, $groups_read ) ) {
delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
}
}
$result = array_map( array( 'Groups_Utility', 'id' ), get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ ) );
}
return $result;
}
/**
* Removes a access restrictions from a post.
*
* @param int $post_id
* @param array $map must provide 'groups_read' holding group IDs to remove from restricting access to the post; if empty, all access restrictions will be removed
* @return true on success, otherwise false
*/
public static function delete( $post_id, $map = array() ) {
extract( $map );
$result = false;
if ( !empty( $post_id ) ) {
if ( empty( $groups_read ) ) {
$groups_read = array();
} else if ( !is_array( $groups_read ) ) {
$groups_read = array( $groups_read );
}
$groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
if ( !empty( $groups_read ) ) {
foreach( $groups_read as $group_id ) {
$result = delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
}
} else {
$result = delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
}
}
return $result;
}
/**
* Returns a list of capabilities that grant access to the post.
*
* @deprecated
* @param int $post_id
* @return array of string, capabilities
*/
public static function get_read_post_capabilities( $post_id ) {
_doing_it_wrong(
__CLASS__ . '::' . __METHOD__,
__( 'This method is deprecated. You should use Groups_Post_Access_Legacy::get_read_post_capabilities() to retrieve the capabilities instead.', 'groups' ),
'2.0.0'
);
require_once( GROUPS_LEGACY_LIB . '/access/class-groups-post-access-legacy.php' );
return Groups_Post_Access_Legacy::get_read_post_capabilities( $post_id );
}
/**
* Returns a list of group IDs that grant read access to the post.
*
* @param int $post_id
* @return array of int, group IDs
*/
public static function get_read_group_ids( $post_id ) {
return get_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ );
}
/**
* Returns true if the user belongs to any of the groups 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 {
// admin override and Groups admins see everything
if ( _groups_admin_override() || current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
$result = true;
} else {
// can read if post type is not handled
if ( $post_type = get_post_type( $post_id ) ) {
if ( !self::handles_post_type( $post_type ) ) {
$result = true;
}
}
// check if the user can read
if ( !$result ) {
$groups_user = new Groups_User( $user_id );
$group_ids = self::get_read_group_ids( $post_id );
if ( empty( $group_ids ) ) {
$result = true;
} else {
$ids = array_intersect( $groups_user->group_ids_deep, $group_ids );
$result = !empty( $ids );
}
}
}
$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_group to remove existing access restrictions
* based on the deleted group.
*
* @param int $group_id the ID of the deleted group
*/
public static function groups_deleted_group( $group_id ) {
if ( $group_id ) {
delete_metadata( 'post', null, self::POSTMETA_PREFIX . self::READ, $group_id, true );
}
}
/**
* Hooked on wp_count_posts to correct the post counts.
*
* Note: at WP 4.7.4 through WP_Posts_List_Table::prepare_items() which obtains $post_status
* independent of the post type, we will come here for any post status, so don't be surprised
* to see this executed e.g. on post type 'post' with e.g. 'wc-completed' post status.
*
* @param object $counts An object containing the current post_type's post counts by status.
* @param string $type the post type
* @param string $perm The permission to determine if the posts are 'readable' by the current user.
*/
public static function wp_count_posts( $counts, $type, $perm ) {
$user_id = get_current_user_id();
$cached = Groups_Cache::get( self::COUNT_POSTS . '_' . $type . '_' . $user_id, self::CACHE_GROUP );
if ( $cached !== null ) {
$counts = $cached->value;
unset( $cached );
} else {
if ( self::handles_post_type( $type ) ) {
foreach( $counts as $post_status => $count ) {
$query_args = array(
'fields' => 'ids',
'post_type' => $type,
'post_status' => $post_status,
'numberposts' => -1, // all
'suppress_filters' => 0, // don't suppres filters as we need to get restrictions taken into account
'orderby' => 'none', // Important! Don't waste time here.
'no_found_rows' => true, // performance, omit unnecessary SQL_CALC_FOUND_ROWS in query here
'nopaging' => true // no paging is needed, in case it would affect performance
);
// WooCommerce Orders
if ( function_exists( 'wc_get_order_statuses' ) && ( $type == 'shop_order' ) ) {
$wc_order_statuses = array_keys( wc_get_order_statuses() );
if ( !in_array( $post_status, $wc_order_statuses ) ) {
// Skip getting the post count for this status as it's
// not a valid order status and WC would raise a PHP Notice.
continue;
}
}
// WooCommerce Subscriptions
if ( function_exists( 'wcs_get_subscription_statuses' ) && ( $type == 'shop_subscription' ) ) {
$wc_subscription_statuses = array_keys( wcs_get_subscription_statuses() );
if ( !in_array( $post_status, $wc_subscription_statuses ) ) {
// Skip as it's not a valid subscription status
continue;
}
}
$posts = get_posts( $query_args );
$count = count( $posts );
unset( $posts );
$counts->$post_status = $count;
}
}
Groups_Cache::set( self::COUNT_POSTS . '_' . $type . '_' . $user_id, $counts, self::CACHE_GROUP );
}
return $counts;
}
/**
* Would be hooked on wp_count_attachments to correct the counts but it's not actually
* being used in the current media library.
*
* @param object $counts An object containing the attachment counts by mime type.
* @param string $mime_type The mime type pattern used to filter the attachments counted.
*/
public static function wp_count_attachments( $counts, $mime_type ) {
return $counts;
}
/**
* Returns true if we are supposed to handle the post type, otherwise false.
*
* @param string $post_type
* @return boolean
*/
public static function handles_post_type( $post_type ) {
$post_types = self::get_handles_post_types();
return isset( $post_types[$post_type] ) && $post_types[$post_type];
}
/**
* Returns an array of post types indicating for each whether we handle it (true) or not.
* The array is indexed by the post type names.
*
* @return array indexed by post type names, indicating the value true if we handle it, otherwise false
*/
public static function get_handles_post_types() {
$result = array();
$post_types_option = Groups_Options::get_option( self::POST_TYPES, array() );
$post_types = get_post_types( array(), 'objects' );
foreach( $post_types as $post_type => $object ) {
$public = isset( $object->public ) ? $object->public : false;
$exclude_from_search = isset( $object->exclude_from_search ) ? $object->exclude_from_search : false;
$publicly_queryable = isset( $object->publicly_queryable ) ? $object->publicly_queryable : false;
$show_ui = isset( $object->show_ui ) ? $object->show_ui : false;
$show_in_nav_menus = isset( $object->show_in_nav_menus ) ? $object->show_in_nav_menus : false;
// by default, handle any post type whose public attribute is true
$managed =
$public && ( !isset( $post_types_option[$post_type] ) || !isset( $post_types_option[$post_type]['add_meta_box'] ) ) ||
isset( $post_types_option[$post_type] ) && isset( $post_types_option[$post_type]['add_meta_box'] ) && $post_types_option[$post_type]['add_meta_box'];
$result[$post_type] = $managed;
}
return $result;
}
/**
* Set which post types we should handle.
*
* @param array $post_types of post type names mapped to booleans, indicating to handle or not a post type
*/
public static function set_handles_post_types( $post_types ) {
$post_types_option = Groups_Options::get_option( self::POST_TYPES, array() );
$available_post_types = get_post_types();
foreach( $available_post_types as $post_type ) {
$post_types_option[$post_type]['add_meta_box'] = isset( $post_types[$post_type] ) && $post_types[$post_type];
}
Groups_Options::update_option( self::POST_TYPES, $post_types_option );
}
}
Groups_Post_Access::init();