scoper =& $GLOBALS['scoper'];
$is_administrator = is_content_administrator_rs();
// ---- ABSTRACT ROLE SCOPER HOOKS - wrap around source-specific hooks based on DataSources config ------
//
// Request / Where Filter:
// Support filtering of any query request (WP or plugin-defined) based on scoped roles.
// Resulting content may be narrowed or expanded from WP core results.
// (currently require request/where/join string as first hook arg, ignore other args)
//
// Results Teaser:
// Alternately, if a results hook is defined, unqualified records can
// be left in the result set, but with the content stripped and the excerpt
// replaced or appended with a teaser message.
// (currently require results as array of objects in first hook arg, ignore other args)
//
// suported filter interface:
// request hooks: $arg1 = full request query
// results hooks: $arg1 = results set
// filter args: $item, $src_name, $object_type, $args (note: to customize other args, filter must be called directly)
add_filter('objects_where_rs', array(&$this, 'flt_objects_where'), 2, 4);
add_filter('objects_request_rs', array(&$this, 'flt_objects_request'), 2, 4);
add_filter('objects_results_rs', array(&$this, 'flt_objects_results'), 50, 4);
add_filter('objects_teaser_rs', array(&$this, 'flt_objects_teaser'), 50, 4);
if ( ! $this->scoper->direct_file_access ) {
// Append any limiting clauses to WHERE clause for taxonomy query
// args: ($where, $taxonomy, $object_type = '', $reqd_op = '') e.g. ($where, 'categories', 'post', 'edit')
// Note: If any of the optional args are missing or nullstring, an attempt is made
// to determine them from URI based on Scoped_Taxonomy properties
add_filter('terms_request_rs', array(&$this, 'flt_terms_request'), 50, 4);
add_filter('terms_where_rs', array(&$this, 'flt_terms_where'), 50, 3);
}
// note: If DISABLE_QUERYFILTERS_RS is set, the RS filters are still defined above for selective internal use,
// but in that case are not mapped to the defined data source hooks ('posts_where', etc.) below
if ( ! defined('DISABLE_QUERYFILTERS_RS') ) {
//in effect, make WP pass the hook name so multiple hooks can be registered to a single handler
$rs_hooks = array();
foreach ( $this->scoper->data_sources->get_all() as $src_name => $src ) {
if ( empty($src->query_hooks) )
continue;
if ( ! $is_administrator ) {
if ( isset($src->query_hooks->where) ) {
$rs_hooks[$src->query_hooks->where] = (object) array( 'name' => 'objects_where_rs', 'rs_args' => "'$src_name', '', '' ");
} elseif ( isset($src->query_hooks->request) )
$rs_hooks[$src->query_hooks->request] = (object) array( 'name' => 'objects_request_rs', 'rs_args' => "'$src_name', '', '' ");
}
// log results (to identify restricted posts) even for admin. Also, possibly apply front end teaser
if ( isset($src->query_hooks->results) )
$rs_hooks[$src->query_hooks->results] = (object) array( 'name' => 'objects_results_rs', 'rs_args' => "'$src_name', '', '' ");
} //foreach data_sources
if ( ! $is_administrator ) {
// use late-firing filter so teaser filtering is also applied to sticky posts
add_filter( 'the_posts', array( &$this, 'flt_the_posts' ), 50, 2 );
// manually hook posts_request to pass on object_type value in the referenced wp_query object
add_filter( 'posts_request', array( &$this, 'flt_posts_request'), 50, 2 );
}
// ...but don't include this hook in the filter-wrapping loop below
if ( isset( $rs_hooks['posts_request'] ) )
unset( $rs_hooks['posts_request'] );
// call our abstract handlers with a lambda function that passes in original hook name
foreach ( $rs_hooks as $original_hook => $rs_hook ) {
if ( ! $original_hook )
continue;
$arg_str = '$a';
$comma = ( $rs_hook->rs_args ) ? ',' : '';
$func = "return apply_filters( '$rs_hook->name', $arg_str $comma $rs_hook->rs_args );";
add_filter( $original_hook, create_function( $arg_str, $func ), 50, 1 );
//d_echo ("adding filter: $original_hook -> $func
");
}
}
//add_filter( 'posts_request', array( &$this, 'flt_debug_query'), 999 );
}
//function flt_debug_query( $query ) {
// d_echo( $query . '
' );
// return $query;
//}
function flt_posts_request( $request, $_wp_query = false ) {
if ( is_object( $_wp_query ) && ! empty( $_wp_query->query_vars['post_type'] ) ) {
$object_types = $_wp_query->query_vars['post_type'];
if ( 'any' == $object_types )
$object_types = '';
} else
$object_types = '';
return $this->flt_objects_request( $request, 'post', $object_types );
}
// Append any limiting clauses to WHERE clause for taxonomy query
//$reqd_caps_by_taxonomy[tx_name][op_type] = array of cap names
function flt_terms_request($request, $taxonomies, $args = array()) {
//$defaults = array( 'reqd_caps_by_otype' => array(), 'is_term_admin' => false, 'required_operation' => '', 'post_type' => '' );
// determine term id col (term_id or term_taxonomy_id) for term management queries
if ( strpos( $request, 'AS tt' ) )
$args['term_id_col'] = 'tt.term_taxonomy_id';
elseif ( strpos( $request, 'AS t' ) )
$args['term_id_col'] = 't.term_id';
else {
global $wpdb;
if ( strpos( $request, $wpdb->terms ) )
$args['term_id_col'] = "$wpdb->terms.term_id";
elseif ( strpos( $request, $wpdb->term_taxonomy ) )
$args['term_id_col'] = "$wpdb->term_taxonomy.term_taxonomy_id";
}
if ( $rs_where = $this->flt_terms_where('', $taxonomies, $args) ) {
if ( strpos( $request, ' WHERE ' ) )
$request = str_replace( ' WHERE ', " WHERE 1=1 $rs_where AND ", $request );
elseif ( $pos_suffix = agp_get_suffix_pos( $request ) )
$request = substr( $request, 0, $pos_suffix ) . " WHERE 1=1 $rs_where " . substr( $request, $pos_suffix );
else
$request .= " WHERE 1=1 $rs_where ";
}
//d_echo ("
terms_request output:$request
");
return $request;
}
function flt_terms_where($where, $taxonomies, $args = array()) {
$defaults = array( 'reqd_caps_by_otype' => array(), 'is_term_admin' => false, 'required_operation' => '', 'post_type' => '', 'term_id_col' => '' );
$args = array_merge( $defaults, (array) $args );
extract( $args, EXTR_SKIP );
$taxonomies = (array) $taxonomies;
if ( ! $taxonomies )
return $where;
if ( $post_type )
$post_type = (array) $post_type;
// support multiple taxonomies, but only if they all use the same object data source
$taxonomy_sources = array();
foreach ( $taxonomies as $taxonomy ) {
if ( ! $this->scoper->taxonomies->is_member( $taxonomy ) )
continue;
$src_name = $this->scoper->taxonomies->member_property( $taxonomy, 'object_source' );
if ( is_object($src_name) ) // support legacy code which stored object variable to property (TODO: eliminate this)
$src_name = $src_name->name;
$taxonomy_sources[$src_name] = true;
}
if ( count($taxonomy_sources) != 1 )
return $where;
// if the filter call did not specify required caps...
if ( ! $reqd_caps_by_otype ) {
$reqd_caps_by_otype = array();
// try to determine context from URI (if taxonomy definition includes such clues)
foreach( $taxonomies as $taxonomy ) {
$reqd_caps_by_otype = array_merge( $reqd_caps_by_otype, $this->scoper->get_terms_reqd_caps( $taxonomy, $required_operation, $is_term_admin ) ); // NOTE: get_terms_reqd_caps() returns term management caps on edit-tags.php, otherwise post edit caps
}
if ( $post_type )
$reqd_caps_by_otype = array_intersect_key( $reqd_caps_by_otype, array_flip( $post_type ) );
// if required operation still unknown, default based on access type
if ( ! $reqd_caps_by_otype )
return $where;
}
// prevent hardway-admin filtering of any queries which may be triggered by this filter
$GLOBALS['scoper_status']->querying_db = true;
// Note that term management capabilities (i.e. "manage_categories") are implemented via Term Roles on the Posts data source, with taxonomy as the object type
//
// if this is a term management query, no need to involve objects query filtering
if ( ( 'post' == $src_name ) && isset( $reqd_caps_by_otype[ $taxonomies[0] ] ) ) {
$qualifying_roles = $this->scoper->role_defs->qualify_roles( $reqd_caps_by_otype[$taxonomies[0]], 'rs', $taxonomies[0] ); // otherwise qualify_terms() will not filter out other taxonomy manager roles that also use manage_categories cap
if ( ! $term_id_col ) {
// can't attempt filtering without this info
$GLOBALS['scoper_status']->querying_db = false;
return $where;
}
$return_id_type = ( strpos( $term_id_col, 'term_taxonomy_id' ) ) ? COL_TAXONOMY_ID_RS : COL_ID_RS;
if ( $ids = $this->scoper->qualify_terms( $reqd_caps_by_otype[$taxonomies[0]], $taxonomies[0], $qualifying_roles, compact('return_id_type') ) ) { // returns term_id since COL_TAXONOMY_ID_RS is not passed as args['return_id_type']
$where .= " AND $term_id_col IN ('" . implode( "','", $ids ) . "')";
} else
$where = "1=2";
} else {
// Call objects_where_role_clauses() with src_name of the taxonomy source.
// This works as a slight subversion of the normal flt_objects_where query building because we are also forcing taxonomies explicitly and passing the terms_query arg.
$args['terms_query'] = true;
$args['use_object_roles'] = false;
$args['skip_owner_clause'] = true;
$args['terms_reqd_caps'] = $reqd_caps_by_otype;
$args['taxonomies'] = $taxonomies;
if ( is_admin() )
$args['alternate_reqd_caps'][0] = array( "assign_$taxonomy" );
$where .= $this->flt_objects_where('', $src_name, '', $args);
// For Edit Form display, include currently stored terms. User will still not be able to remove them without proper editing roles for object. (TODO: abstract for other data sources)
if ( ( 'post.php' == $GLOBALS['pagenow'] ) && empty( $_REQUEST['admin_bar'] ) ) {
if ( 'post' == $src_name ) {
if ( $object_id = $this->scoper->data_sources->detect( 'id', $src_name ) ) {
if ( $stored_terms = wp_get_object_terms( $object_id, $taxonomies[0] ) ) {
$tt_ids = array();
foreach( array_keys($stored_terms) as $key )
$tt_ids []= $stored_terms[$key]->term_taxonomy_id;
$where .= " OR tt.term_taxonomy_id IN ('" . implode( "','", $tt_ids ) . "')";
}
}
}
}
}
$GLOBALS['scoper_status']->querying_db = false; // re-enable hardway-admin filtering
return $where;
}
function flt_objects_request($request, $src_name, $object_types = '', $args = array()) {
if ( $args ) {
$defaults = array( 'skip_teaser' => false );
$args = array_diff_key($args, array_flip( array('request', 'src_name', 'object_types' ) ) );
$args = array_merge( $defaults, (array) $args );
extract($args);
}
// if Media Library filtering is disabled, don't filter listing for TinyMCE popup either
if ( is_admin() && defined( 'SCOPER_ALL_UPLOADS_EDITABLE' ) && strpos( $_SERVER['SCRIPT_NAME'], 'wp-admin/media-upload.php' ) )
return $request;
// Filtering in user_has_cap sufficiently controls revision access; a match here should be for internal, pre-validation purposes
if ( strpos( $request, "post_type = 'revision'") )
return $request;
// no need to apply objects query filtering within NextGEN Gallery / Grand Flash Gallery upload operation (was failing with undefined $current_user)
if ( is_admin() ) {
if ( defined( 'SCOPER_ALL_UPLOADS_EDITABLE' ) && ( $GLOBALS['pagenow'] == 'upload.php' ) )
return $request;
$nofilter_scripts = array( '/admin/upload.php' );
if ( $nofilter_scripts = apply_filters( 'noqueryfilter_scripts_rs', $nofilter_scripts ) ) {
foreach( $nofilter_scripts as $_script_name ) {
if ( false !== strpos( $_SERVER['SCRIPT_NAME'], $_script_name ) )
return $request;
}
}
}
// prevent hardway-admin filtering of any queries which may be triggered by this filter
$GLOBALS['scoper_status']->querying_db = true;
if ( empty($skip_teaser) ) {
$this->last_request[$src_name] = $request; // Store for potential use by subsequent teaser filter
}
//$request = agp_force_distinct($request); // in case data source didn't provide a hook for objects_distinct
if ( ! preg_match('/\s*WHERE\s*1=1/', $request) )
$request = preg_replace('/\s*WHERE\s*/', ' WHERE 1=1 AND ', $request);
$pos_where = 0;
$pos_suffix = 0;
$where = agp_parse_after_WHERE_11( $request, $pos_where, $pos_suffix ); // any existing where, orderby or group by clauses remain in $where
if ( ! $pos_where && $pos_suffix ) {
$request = substr($request, 0, $pos_suffix) . ' WHERE 1=1' . substr($request, $pos_suffix);
$pos_where = $pos_suffix;
}
if ( 'post' == $src_name ) {
// If the query uses an alias for the posts table, be sure to use that alias in the WHERE clause also.
//
// NOTE: if query refers to non-active blog, this code will prevent a DB syntax error, but will not cause the correct roles / restrictions to be applied.
// Other plugins need to use switch_to_blog() rather than just executing a query on a non-main blog.
$matches = array();
if ( $return = preg_match( '/SELECT .* FROM [^ ]+posts AS ([^ ]) .*/', $request, $matches ) )
$args['source_alias'] = $matches[2];
elseif ( $return = preg_match( '/SELECT .* FROM ([^ ]+)posts .*/', $request, $matches ) )
$args['source_alias'] = $matches[1] . 'posts';
}
// TODO: abstract this
if ( strpos( $request, "post_type = 'attachment'" ) ) {
global $wpdb;
// filter attachments by inserting a scoped subquery based on user roles on the post/page attachment is tied to
$rs_where = $this->flt_objects_where( '', $src_name, '', $args );
$subqry = "SELECT ID FROM $wpdb->posts WHERE 1=1 $rs_where";
if ( is_admin() ) {
// The listed objects are attachments, so query filter is based on objects they inherit from
$admin_others_attached = scoper_get_option( 'admin_others_attached_files' );
$admin_others_unattached = scoper_get_option( 'admin_others_unattached_files' );
if ( ( ! $admin_others_attached ) || ! $admin_others_unattached )
$can_edit_others_blogwide = $this->scoper->user_can_edit_blogwide( 'post', '', array( 'require_others_cap' => true, 'status' => 'publish' ) );
global $current_user;
// optionally hide other users' unattached uploads, but not from blog-wide Editors
if ( $admin_others_unattached || $can_edit_others_blogwide )
$author_clause = '';
else
$author_clause = "AND $wpdb->posts.post_author = '{$current_user->ID}'";
if ( is_admin() && ( ! defined('SCOPER_BLOCK_UNATTACHED_UPLOADS') || ! SCOPER_BLOCK_UNATTACHED_UPLOADS ) )
$unattached_clause = "( $wpdb->posts.post_parent = 0 $author_clause ) OR";
else
$unattached_clause = '';
$attached_clause = ( $admin_others_attached || $can_edit_others_blogwide ) ? '' : "AND $wpdb->posts.post_author = '{$current_user->ID}'";
$request = str_replace( "$wpdb->posts.post_type = 'attachment'", "( $wpdb->posts.post_type = 'attachment' AND ( $unattached_clause ( $wpdb->posts.post_parent IN ($subqry) $attached_clause ) ) )", $request );
} else
$request = str_replace( "$wpdb->posts.post_type = 'attachment'", "( $wpdb->posts.post_type = 'attachment' AND ( $wpdb->posts.post_parent IN ($subqry) ) )", $request );
} else {
// Generate a query filter based on roles for the listed objects
$rs_where = $this->flt_objects_where($where, $src_name, $object_types, $args);
if ( $pos_where === false )
$request = $request . ' WHERE 1=1 ' . $where;
else
$request = substr($request, 0, $pos_where) . ' WHERE 1=1 ' . $rs_where; // any pre-exising join clauses remain in $request
}
// re-enable hardway-admin filtering
$GLOBALS['scoper_status']->querying_db = false;
//d_echo( "
filtered: $request
" );
return $request;
}
// called by flt_objects_where, flt_objects_results
function _get_object_types( $src, $object_types = '' ) {
if ( ! $object_types ) {
if ( ! is_object($src) )
if ( ! $src = $this->scoper->data_sources->get($src) )
return array();
return array_keys($src->object_types); // include all defined otypes in the query if none were specified
} else
return (array) $object_types; // make sure the passed-in value is an array
}
// called by flt_objects_where, flt_objects_results
function _get_teaser_object_types($src_name, $object_types, $args = array()) {
$args = (array) $args;
if ( ! empty($args['skip_teaser']) || is_admin() || is_content_administrator_rs() || is_attachment_rs() || defined('XMLRPC_REQUEST') || ! empty($this->skip_teaser) )
return array();
if ( is_feed() && defined( 'SCOPER_NO_FEED_TEASER' ) )
return array();
if ( ( ! empty( $args['required_operation'] ) && ( 'read' != $args['required_operation'] ) ) )
return array();
if ( empty($object_types) )
$object_types = $this->_get_object_types($src_name);
$tease_otypes = array();
if ( scoper_get_otype_option('do_teaser', $src_name) ) {
global $current_user;
foreach ( $object_types as $object_type )
if ( scoper_get_otype_option('use_teaser', $src_name, $object_type) ) {
$teased_users = scoper_get_otype_option( 'teaser_logged_only', $src_name, $object_type );
if ( empty( $teased_users )
|| ( ( 'anon' == $teased_users ) && empty($current_user->ID) )
|| ( ( 'anon' != $teased_users ) && ! empty($current_user->ID) ) )
$tease_otypes []= $object_type;
}
}
return $tease_otypes;
}
// NOTE: Setting use_object_roles or use_term_roles to a boolean forces enable/disable for all types / taxonomies. Otherwise stored options will be retrieved for each object type / taxonomy.
function flt_objects_where($where, $src_name, $object_types = '', $args = array() ) {
$defaults = array( 'user' => '', 'use_object_roles' => -1, 'use_term_roles' => -1,
'taxonomies' => array(), 'request' => '', 'terms_query' => 0,
'force_reqd_caps' => '', 'alternate_reqd_caps' => '', 'source_alias' => '',
'required_operation' => '', 'terms_reqd_caps' => '', 'skip_teaser' => false
);
$args = array_merge( $defaults, (array) $args );
extract($args);
// filtering in user_has_cap sufficiently controls revision access; a match here should be for internal, pre-validation purposes
if ( strpos( $where, "post_type = 'revision'") )
return $where;
$where_prepend = '';
//rs_errlog ("object_where input: $where");
//rs_errlog ('');
//d_echo ("
object_where input: $where
");
//echo "
$where
";
if ( ! is_object($user) ) {
$user = $GLOBALS['current_rs_user'];
$args['user'] = $user;
}
if ( ! $src = $this->scoper->data_sources->get($src_name) )
return $where; // the specified data source is not know to Role Scoper
$src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
// verify table name and id col definition (the actual existance checked at time of admin entry)
if ( ! ($src->table && $src->cols->id) ) {
rs_notice( sprintf( 'Role Scoper Configuration Error: table_basename or col_id are undefined for the %s data source.', $src_name) );
return $where;
}
// need to allow ambiguous object type for special cap requirements like comment filtering
$object_types = $this->_get_object_types($src, $object_types);
$tease_otypes = array_intersect( $object_types, $this->_get_teaser_object_types($src_name, $object_types, $args) );
if ( ! empty($src->no_object_roles) )
$use_object_roles = false;
if ( $terms_query && $terms_reqd_caps ) {
foreach ( array_keys($terms_reqd_caps) as $_object_type )
$otype_status_reqd_caps[$_object_type][''] = $terms_reqd_caps[$_object_type]; // terms request does not support multiple statuses
} else {
if ( $force_reqd_caps && is_array($force_reqd_caps) ) {
$otype_status_reqd_caps = $force_reqd_caps;
} else {
global $wpdb;
if ( ! $required_operation )
$required_operation = ( 'front' == CURRENT_ACCESS_NAME_RS ) ? OP_READ_RS : OP_EDIT_RS;
$preview_future = strpos( $where, "$wpdb->posts.post_name =" ) || strpos( $where, "$wpdb->posts.ID =" );
if ( ! $otype_status_reqd_caps = cr_get_reqd_caps( $src_name, $required_operation, -1, -1, false, $preview_future ) )
return $where;
}
$otype_status_reqd_caps = array_intersect_key($otype_status_reqd_caps, array_flip($object_types) );
}
// Since Role Scoper can restrict or expand access regardless of post_status, query must be modified such that
// * the default owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" is removed
// * all statuses are listed apart from owner inclusion clause (and each of these status clauses is subsequently replaced with a scoped equivalent which imposes any necessary access limits)
// * a new scoped owner clause is constructed where appropriate (see $where[$cap_name]['owner'] in function objects_where_role_clauses()
//
if ( $src->cols->owner && $user->ID ) {
// force standard query padding
$where = preg_replace("/{$src->cols->owner}\s*=\s*/", "{$src->cols->owner} = ", $where);
$where = str_replace( " {$src->cols->owner} =", " $src_table.{$src->cols->owner} =", $where);
$where = str_replace( " {$src->cols->owner} IN", " $src_table.{$src->cols->owner} IN", $where);
}
if ( ! empty($src->query_replacements) ) {
foreach ( $src->query_replacements as $find => $replace ) {
// for posts_request, remove the owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" because we'll account for each status based on properties of required caps
$find_ = str_replace('[user_id]', $user->ID, $find);
if ( false !== strpos($find_, '[') || false !== strpos($find_, ']') ) {
rs_notice( sprintf( 'Role Scoper Config Error: invalid query clause search criteria for %1$s (%2$s).
Valid placeholders are:
', $src_name, $find) . print_r(array_keys($map)) );
return ' AND 1=2 ';
}
$replace_ = str_replace('[user_id]', $user->ID, $replace);
if ( false !== strpos($replace_, '[') || false !== strpos($replace_, ']') ) {
rs_notice( sprintf( 'Role Scoper Config Error: invalid query clause replacement criteria for %1$s (%2$s).
Valid placeholders are:
', $src_name, $replace) . print_r(array_keys($map)) );
return ' AND 1=2 ';
}
$where = str_replace($find_, $replace_, $where);
}
}
$force_single_type = false;
$col_type = ( ! empty( $src->cols->type ) ) ? $src->cols->type : '';
if ( $col_type ) {
// If the passed request contains a single object type criteria, maintain that status exclusively (otherwise include type-specific conditions for each available type)
$matches = array();
$num_matches = preg_match_all( "/$col_type\s*=\s*'([^']+)'/", $where, $matches );
if ( 1 == $num_matches ) {
$force_single_type = true;
$object_types = array( $matches[1][0] );
if ( $matched_reqd_caps = array_intersect_key( $otype_status_reqd_caps, array_flip($object_types) ) ) // sanity check prevents running with an empty reqd_caps array if something goes wrong with otype detection
$otype_status_reqd_caps = $matched_reqd_caps;
}
}
if ( ( 'post' == $src_name ) && ! array_intersect( $object_types, array_keys( array_intersect( scoper_get_option( 'use_post_types' ), array( true ) ) ) ) )
return $where;
elseif ( empty( $otype_status_reqd_caps) )
return ' AND 1=2 ';
$basic_status_clause = array();
$force_single_status = false;
$status_clause_pos = 0;
$col_status = ( ! empty( $src->cols->status ) ) ? $src->cols->status : '';
if ( $col_status ) {
// force standard query padding
$where = preg_replace("/$col_status\s*=\s*'/", "$col_status = '", $where);
$where = str_replace(" $col_status =", " {$src_table}.$col_status =", $where);
$where = str_replace(" $col_status IN", " {$src_table}.$col_status IN", $where);
foreach ( array_keys( $otype_status_reqd_caps ) as $listing_otype )
foreach( array_keys( $otype_status_reqd_caps[$listing_otype] ) as $status )
$basic_status_clause[$status] = "{$src_table}.$col_status = '$status'";
// If the passed request contains a single status criteria, maintain that status exclusively (otherwise include status-specific conditions for each available status)
// (But not if user is anon and hidden content teaser is enabled. In that case, we need to replace the default "status=publish" clause)
$matches = array();
if ( $num_matches = preg_match_all( "/{$src_table}.$col_status\s*=\s*'([^']+)'/", $where, $matches ) )
$status_clause_pos = strpos( $where, $matches[0][0] ); // note the match position for use downstream
if ( 1 == $num_matches ) {
$use_status = $matches[1][0];
// Eliminate a primary plugin incompatibility by skipping this preservation of existing single status requirements if we're on the front end and the requirement is 'publish'.
// (i.e. include private posts that this user has access to via RS role assignment).
if ( ! $this->scoper->is_front() || ( 'publish' != $use_status ) || ( empty( $args['user']->ID ) && empty($tease_otypes) ) || defined('SCOPER_RETAIN_PUBLISH_FILTER') ) {
$force_single_status = true;
foreach ( array_keys($otype_status_reqd_caps) as $_object_type )
$otype_status_reqd_caps[$_object_type] = array_intersect_key( $otype_status_reqd_caps[$_object_type], array( $use_status => true ) );
}
}
} else {
// this source doesn't define statuses
$basic_status_clause = array ( '' => '');
}
if ( empty($skip_teaser) && ! array_diff($object_types, $tease_otypes) ) {
if ( $status_clause_pos && $force_single_type ) { // All object types potentially returned by this query will have a teaser filter applied to results, so we don't need to filter the query
// override our sanity safeguard against exposing private posts to anonymous readers
if ( empty($user->ID) ) {
// Since we're dropping out of this function early in advance of teaser filtering,
// must take this opportunity to add private status to the query (otherwise WP excludes private for anon user)
// (But don't do this if teaser is configured to hide private content)
$check_otype = ( count($tease_otypes) && in_array('post', $tease_otypes) ) ? 'post' : $tease_otypes[0];
$post_type_obj = get_post_type_object($check_otype);
if ( ! scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype) && ( ! $post_type_obj->hierarchical || scoper_get_otype_option('private_items_listable', 'post', 'page') ) ) {
if ( $col_status && isset( $otype_status_reqd_caps[$check_otype] ) ) {
$status_or = "{$src_table}.$col_status = '" . implode("' OR {$src_table}.$col_status = '", array_keys($otype_status_reqd_caps[$check_otype]) ) . "'";
$where = str_replace( $basic_status_clause['publish'], "( $status_or )", $where);
} else {
$where = str_replace( $basic_status_clause['publish'], "1=1", $where);
}
}
}
}
return $where;
}
$is_administrator = is_content_administrator_rs(); // make sure administrators never have content limited
$status_or = '';
$status_where = array();
foreach ($otype_status_reqd_caps as $object_type => $status_reqd_caps) {
if ( ! is_array($status_reqd_caps) ) {
rs_notice( sprintf( 'Role Scoper Configuration Error: reqd_caps for the %s data source must be array[operation][object_type][status] where operation is "read", "edit" or "admin".', $src_name) );
return $where;
}
// don't bother generating these parameters if we're just going to pass the object type through for teaser filtering
if ( ! in_array($object_type, $tease_otypes) ) {
if ( true === $use_term_roles ) { // if boolean true was passed in, force usage of all term roles
if ( 'post' == $src_name ) {
//$otype_use_term_roles = array_fill_keys( get_taxonomies( array( 'public' => true, 'object_type' => $object_type ) ), 1 );
$otype_use_term_roles = array();
foreach( get_taxonomies( array( 'public' => true ), 'object' ) as $taxonomy => $taxonomy_obj )
if ( in_array( $object_type, $taxonomy_obj->object_type ) )
$otype_use_term_roles[$taxonomy] = 1;
} else
$otype_use_term_roles = ( ! empty( $src->uses_taxonomies ) ) ? array_fill_keys( $src->uses_taxonomies, true ) : array();
} else {
$check_object_type = ( 'link_category' == $object_type ) ? 'link' : $object_type;
$otype_use_term_roles = ( -1 == $use_term_roles ) ? scoper_get_otype_option('use_term_roles', $src_name, $check_object_type) : false;
}
if ( ( ! $otype_use_term_roles ) && $terms_query )
continue;
// if a boolean was passed in, override the stored option
$otype_use_object_roles = ( -1 == $use_object_roles ) ? scoper_get_otype_option('use_object_roles', $src_name, $object_type) : $use_object_roles;
} else {
$otype_use_term_roles = false;
$otype_use_object_roles = false;
}
//now step through all statuses and corresponding cap requirements for this otype and access type
// (will replace "col_status = status_name" with "col_status = status_name AND ( [scoper requirements] )
foreach ($status_reqd_caps as $status_name => $reqd_caps) {
if ( 'trash' == $status_name ) // in wp-admin, we need to include trash posts for the count query, but not for the listing query unless trash status is requested
if ( ( empty($this->last_request[$src_name]) || ! strpos($this->last_request[$src_name], 'COUNT') ) && ( empty( $_GET['post_status'] ) || ( 'trash' != $_GET['post_status'] ) ) )
continue;
if ( $is_administrator )
$status_where[$status_name][$object_type] = '1=1';
elseif ( empty($skip_teaser) && in_array($object_type, $tease_otypes) )
if ( $terms_query && ! $otype_use_object_roles )
$status_where[$status_name][$object_type] = '1=1';
else
$status_where[$status_name][$object_type] = "{$src_table}.{$src->cols->type} = '$object_type'"; // this object type will be teaser-filtered
else {
// filter defs for otypes which don't define a status will still have a single status element with value ''
$args = array_merge( $args, array( 'object_type' => $object_type, 'otype_use_term_roles' => $otype_use_term_roles, 'otype_use_object_roles' => $otype_use_object_roles ) );
$clause = $this->objects_where_role_clauses($src_name, $reqd_caps, $args);
if ( empty($clause) || ( '1=2' == $clause ) ) // this means no qualifying roles are available
$status_where[$status_name][$object_type] = '1=2';
// array key order status/object is reversed intentionally for subsequent processing
elseif ( (count($otype_status_reqd_caps) > 1) && ( ! $terms_query || $otype_use_object_roles) ) // more than 1 object type
$status_where[$status_name][$object_type] = "( {$src_table}.{$src->cols->type} = '$object_type' AND ( $clause ) )";
else
$status_where[$status_name][$object_type] = $clause;
}
}
}
// all otype clauses concat: object_type1 clause [OR] [object_type2 clause] [OR] ...
foreach ( array_keys($status_where) as $status_name ) {
if ( isset($preserve_or_clause[$status_name]) )
$status_where[$status_name][] = $preserve_or_clause[$status_name];
if ( $tease_otypes )
$check_otype = ( count($tease_otypes) && in_array('post', $tease_otypes) ) ? 'post' : $tease_otypes[0];
// extra line of defense: even if upstream logic goes wrong, never disclose a private item to anon user (but if the where clause was passed in with explicit status=private, must include our condition)
if ( ('private' == $status_name) && ! $force_single_status && empty($GLOBALS['current_user']->ID) && ( ! $tease_otypes || scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype) ) )
unset( $status_where[$status_name] );
else
$status_where[$status_name] = agp_implode(' ) OR ( ', $status_where[$status_name], ' ( ', ' ) ');
}
// combine identical status clauses
$duplicate_clause = array();
$replace_clause = array();
if ( $col_status && count($status_where) > 1 ) { // more than one status clause
foreach ( $status_where as $status_name => $status_clause) {
if ( isset($duplicate_clause[$status_name]) )
continue;
reset($status_where);
if ( $other_status_name = array_search($status_clause, $status_where) ) {
if ( $other_status_name == $status_name ) $other_status_name = array_search($status_clause, $status_where);
if ( $other_status_name && ( $other_status_name != $status_name ) ) {
$duplicate_clause[$other_status_name][$status_name] = true;
$replace_clause[$status_name] = true;
}
}
}
}
$status_where = array_diff_key($status_where, $replace_clause);
foreach ( $status_where as $status_name => $this_status_where) {
if ( $status_clause_pos && $force_single_status ) {
//We are maintaining the single status which was specified in original query
if ( ! $this_status_where || ( $this_status_where == '1=2' ) )
$where_prepend = '1=2';
elseif ( $this_status_where == '1=1' )
$where_prepend = '';
else {
//insert at original status clause position
$where_prepend = '';
$where = substr($where, 0, $status_clause_pos) . "( $this_status_where ) AND " . substr($where, $status_clause_pos);
}
break;
}
// We may be replacing or inserting status clauses
if ( ! empty($duplicate_clause[$status_name]) ) {
// We generated duplicate clauses for some statuses
foreach ( array_keys($duplicate_clause[$status_name]) as $other_status_name ) {
$where = str_replace($basic_status_clause[$other_status_name], '1=2', $where);
}
$duplicate_clause[$status_name] = array_merge($duplicate_clause[$status_name], array($status_name=>1) );
if ( $col_status ) {
$name_in = "'" . implode("', '", array_keys($duplicate_clause[$status_name])) . "'";
$status_prefix = "{$src_table}.$col_status IN ($name_in)";
} else {
$status_prefix = "1=1";
}
} elseif ( $col_status && $status_name ) {
$status_prefix = $basic_status_clause[$status_name];
} else
$status_prefix = '';
if ( $this_status_where && ( $this_status_where != '1=2' || count($status_where) > 1 ) ) { //todo: confirm we can OR the 1=2 even if only one status clause
if ( '1=1' == $this_status_where )
$status_clause = ( $status_prefix ) ? "$status_prefix " : '';
else {
$status_clause = ( $col_status && $status_prefix ) ? "$status_prefix AND " : '';
$status_clause .= "( $this_status_where )"; // TODO: reduce number of parentheses
$status_clause = " ( $status_clause )";
}
} else
$status_clause = '1=2';
if ( $status_clause ) {
if ( $col_status && $status_name && strpos($where, $basic_status_clause[$status_name]) ) {
// Replace existing status clause with our scoped equivalent
$where = str_replace($basic_status_clause[$status_name], "$status_clause", $where);
} elseif ( $status_clause_pos && ( $status_clause != '1=2' ) ) {
// This status was not in the original query, but we now insert it with scoping clause at the position of another existing status clause
$where = substr($where, 0, $status_clause_pos) . "$status_clause OR " . substr($where, $status_clause_pos);
} else {
// Default query makes no mention of status (perhaps because this data source doesn't define statuses),
// so prepend this clause to front of where clause
$where_prepend .= "$status_or $status_clause";
$status_or = ' OR';
}
}
}
// Existance of this variable means no status clause exists in default WHERE. AND away we go.
// Prepend so we don't disturb any orderby/groupby/limit clauses which are along for the ride
if ( $where_prepend ) {
if ( $where )
$where = " AND ( $where_prepend ) $where";
else
$where = " AND ( $where_prepend )";
}
//d_echo ("
objects_where output: $where
");
//echo "
$where
";
//rs_errlog ("object_where output: $where");
//rs_errlog ('');
//rs_errlog ('');
return $where;
}
// core Role Scoper where clause concatenation called by listing filter (flt_objects_request) and single access filter (flt_user_has_cap)
// $reqd_caps[cap_name] = min scope
//
// required args: user, object_type, otype_use_object_roles, otype_use_term_roles
//
function objects_where_role_clauses($src_name, $reqd_caps, $args = array() ) {
$defaults = array( 'taxonomies' => array(), 'terms_query' => false, 'alternate_reqd_caps' => '',
'custom_user_blogcaps' => '', 'skip_owner_clause' => false, 'require_full_object_role' => false );
// Required Args
// NOTE: use_object_roles is a boolean for the single object_type in question, but otype_use_object_roles is array[taxonomy] = true or false
$required = array_fill_keys( array( 'user', 'object_type', 'otype_use_term_roles', 'otype_use_object_roles' ), true );
if ( $missing = array_diff_key( $required, $args ) ) {
rs_notice ( sprintf( 'Role Scoper Runtime Error (%1$s) - Missing argument(s): %2$s', 'objects_where_scope_clauses', implode( ", ", array_keys($missing) ) ) );
return ' 1=2 ';
}
$defaults = array_merge( $defaults, $required );
$args = array_merge( $defaults, (array) $args );
extract($args);
if ( '' === $custom_user_blogcaps )
$custom_user_blogcaps = SCOPER_CUSTOM_USER_BLOGCAPS;
$reqd_caps = (array) $reqd_caps;
$reqd_caps = $this->scoper->role_defs->role_handles_to_caps($reqd_caps);
// accomodate editing of published posts/pages to revision
if ( defined( 'RVY_VERSION' ) && rvy_get_option('pending_revisions') ) {
if ( empty( $GLOBALS['revisionary']->skip_revision_allowance ) ) {
$revision_uris = apply_filters( 'scoper_revision_uris', array( 'edit.php', 'upload.php', 'widgets.php', 'admin-ajax.php', 'rvy-revisions' ) );
if ( is_admin() || ! empty( $_GET['preview'] ) )
$revision_uris []= 'index.php';
$plugin_page = is_admin() ? $GLOBALS['plugin_page_cr'] : '';
if ( is_preview() || in_array( $GLOBALS['pagenow'], $revision_uris ) || in_array( $plugin_page, $revision_uris ) ) {
$strip_capreqs = array();
foreach( (array) $object_type as $_object_type ) {
if ( $type_obj = get_post_type_object( $_object_type ) ) {
$strip_capreqs = array_merge( $strip_capreqs, array( $type_obj->cap->edit_published_posts, $type_obj->cap->edit_private_posts ) );
if ( array_intersect( $reqd_caps, $strip_capreqs ) )
$reqd_caps []= $type_obj->cap->edit_posts;
}
}
$reqd_caps = array_unique( array_diff($reqd_caps, $strip_capreqs) );
}
$do_revision_clause = true;
}
}
if ( ! is_object($user) ) { // TODO: can we skip this now that user is a required arg?
$user = $GLOBALS['current_rs_user'];
}
if ( ! $src = $this->scoper->data_sources->get($src_name) ) {
rs_notice ( sprintf( 'Role Scoper Config Error (%1$s): Data source (%2$s) is not defined.', 'objects_where_role_clauses', $src_name) );
return ' 1=2 ';
}
$src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
// special case to include pending / scheduled revisions by object role
if ( ! isset( $args['objrole_revisions_clause'] ) ) {
$args['objrole_revisions_clause'] = ( 'edit.php' == $GLOBALS['pagenow'] );
}
// These arguments are simply passed on to objects_where_scope_clauses()
if ( 'group' == $src_name )
$args['otype_use_object_roles'] = true;
elseif ( ! empty($src->no_object_roles) )
$args['otype_use_object_roles'] = false;
if ( $args['otype_use_object_roles'] ) {
// Return all object_ids that require any role to be object-assigned
// We will use an ID NOT IN clause so these are not satisfied by blog/term role assignment
$args['objscope_objects'] = $this->scoper->get_restrictions(OBJECT_SCOPE_RS, $src_name);
}
$where = array();
foreach ( $reqd_caps as $cap_name ) {
// If supporting custom user blogcaps, a separate role clause for each cap
// Otherwise (default) all reqd_caps from one role assignment (whatever scope it may be)
if ( $custom_user_blogcaps ) {
$reqd_caps_arg = array($cap_name);
} else {
$reqd_caps_arg = $reqd_caps;
$cap_name = '';
}
$qualifying_roles = $this->scoper->role_defs->qualify_roles($reqd_caps_arg, '', $object_type );
/*
rs_errlog( '' );
rs_errlog( "reqd_caps arg: " . serialize($reqd_caps_arg) );
rs_errlog( "qualifying roles for $object_type: " . serialize($qualifying_roles) );
rs_errlog( '' );
*/
if ( $alternate_reqd_caps && is_array( $alternate_reqd_caps ) ) { // $alternate_reqd_caps[setnum] = array of cap_names
foreach ( $alternate_reqd_caps as $alternate_capset ) {
foreach ( $alternate_capset as $alternate_reqd_caps ) {
if ( $alternate_roles = $this->scoper->role_defs->qualify_roles($alternate_reqd_caps) )
$qualifying_roles = array_merge($qualifying_roles, $alternate_roles);
}
}
}
// this is needed mainly for the chicken-egg situation of uploading a file into a post before a category is stored, when editing rights are based on category
//if ( $args['otype_use_object_roles'] )
$args['ignore_restrictions'] = ( 1 == count($reqd_caps_arg) ) && $this->scoper->cap_defs->member_property( reset($reqd_caps_arg), 'ignore_restrictions' );
if ( $owner_reqd_caps = $this->scoper->cap_defs->get_base_caps($reqd_caps_arg) ) {
$owner_roles = ( $require_full_object_role ) ? $qualifying_roles : $this->scoper->role_defs->qualify_roles($owner_reqd_caps, '', $object_type);
if ( ! empty($alternate_roles ) )
$owner_roles = array_merge( $owner_roles, $alternate_roles );
} else
$owner_roles = array();
// have to pass qualifying_object_roles in for 'user' call because qualifying_roles may not include a qualifying object role (i.e. Page Contributor object role assignment)
if ( $owner_roles && ( empty( $GLOBALS['revisionary'] ) || empty( $GLOBALS['revisionary']->skip_revision_allowance ) ) )
$qualifying_object_roles = $this->scoper->confirm_object_scope( $owner_roles );
else
$qualifying_object_roles = $this->scoper->confirm_object_scope( $qualifying_roles ); // get_base_caps() strips out edit_private_* cap requirement for post owner, in compliance with WP metacap mapping. But for Revisionary, that causes Revisors to have full editing caps if a page is privately published (but not if it's publicly published).
if ( $qualifying_roles || ! empty($qualifying_object_roles) ) {
//d_echo( "regular objects_where_scope_clauses for " . serialize( $reqd_caps ) );
$args = array_merge( $args, compact( 'qualifying_roles', 'qualifying_object_roles' ) );
$where[$cap_name]['user'] = $this->objects_where_scope_clauses($src_name, $reqd_caps_arg, $args );
}
if ( ! empty($src->cols->owner) && ! $skip_owner_clause && $user->ID ) {
if ( ! $require_full_object_role ) {
// if owner qualifies for the operation by any different roles than other users, add separate owner clause
$src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
if ( ! $owner_reqd_caps ) {
// all reqd_caps are granted to owner automatically
$where[$cap_name]['owner'] = "$src_table.{$src->cols->owner} = '$user->ID'";
} elseif ( $owner_reqd_caps != $reqd_caps_arg ) {
if ( $owner_roles ) {
//d_echo( "owner objects_where_scope_clauses: " );
$args = array_merge($args, array( 'qualifying_roles' => $owner_roles ) );
$scope_temp = $this->objects_where_scope_clauses($src_name, $owner_reqd_caps, $args );
if ( ( $scope_temp != $where[$cap_name]['user'] ) && ! is_null($scope_temp) ) { // TODO: why is null ever returned?
$parent_clause = '';
// enable authors to view / edit / approve revisions to their published posts
if ( ! empty( $do_revision_clause ) && ! defined( 'HIDE_REVISIONS_FROM_AUTHOR' ) ) {
static $owner_ids = array();
if ( ! isset( $owner_ids[$user->ID][$object_type] ) ) // also keying by user ID in case this filter is invoked for a non-current user
$owner_ids[$user->ID][$object_type] = scoper_get_col( "SELECT {$src->cols->id} FROM $src->table WHERE {$src->cols->type} = '$object_type' AND {$src->cols->owner} = '$user->ID'" );
if ( $owner_ids[$user->ID][$object_type] )
$parent_clause = "OR $src_table.{$src->cols->type} = 'revision' AND $src_table.{$src->cols->parent} IN ('" . implode( "','", $owner_ids[$user->ID][$object_type] ) . "')";
}
$where[$cap_name]['owner'] = '( ' . $scope_temp . " ) AND ( $src_table.{$src->cols->owner} = '$user->ID' $parent_clause )";
}
}
}
}
}
// all role clauses concat: user clauses [OR] [owner clauses]
if ( ! empty($where[$cap_name]) )
$where[$cap_name] = agp_implode(' ) OR ( ', $where[$cap_name], ' ( ', ' ) ');
// if not supporting custom caps, we actually passed all reqd_caps in first iteration
if ( ! $custom_user_blogcaps )
break;
}
// all reqd caps concat: cap1 clauses [AND] [cap2 clauses] [AND] ...
if ( ! empty($where) )
$where = agp_implode(' ) AND ( ', $where, ' ( ', ' ) ');
else
return '1=2';
return $where;
}
function objects_where_scope_clauses($src_name, $reqd_caps, $args ) {
// Optional Args (will be defaulted to meaningful values)
// Note: ignore_restrictions affects Scoper::qualify_terms() output
$defaults = array( 'taxonomies' => '', 'use_blog_roles' => true, 'terms_query' => false, 'qualifying_object_roles' => false,
'skip_objscope_check' => false, 'require_full_object_role' => false, 'objrole_revisions_clause' => false, 'ignore_restrictions' => false );
// Required Args
// NOTE: use_object_roles is a boolean for the single object_type in question, but use_term_roles is array[taxonomy] = true or false
$required = array_fill_keys( array( 'user', 'object_type', 'qualifying_roles', 'otype_use_term_roles', 'otype_use_object_roles' ), true );
if ( $missing = array_diff_key( $required, $args ) ) {
rs_notice ( sprintf( 'Role Scoper Runtime Error (%1$s) - Missing argument(s): %2$s', 'objects_where_scope_clauses', implode( ", ", array_keys($missing) ) ) );
return ' 1=2 ';
}
$defaults = array_merge( $defaults, $required );
$args = array_merge( $defaults, (array) $args );
extract($args);
if ( ! $src = $this->scoper->data_sources->get($src_name) ) {
rs_notice ( sprintf( 'Role Scoper Config Error (%1$s): Data source (%2$s) is not defined.', 'objects_where_scope_clauses', $src_name ) );
return ' 1=2 ';
}
$src_table = ( ! empty($source_alias) ) ? $source_alias : $src->table;
if ( 'group' == $src_name )
$otype_use_object_roles = true;
elseif ( ! empty($src->no_object_roles) )
$otype_use_object_roles = false;
// primary qualifying_roles array should contain only RS roles
$qualifying_roles = $this->scoper->role_defs->filter( $qualifying_roles, array( 'role_type' => 'rs' ), 'names_as_key' );
if ( $otype_use_object_roles ) {
// For object assignment, replace any "others" reqd_caps array.
// Also exclude any roles which have never been assigned to any object
if ( ! is_array( $qualifying_object_roles ) )
$qualifying_object_roles = $this->scoper->confirm_object_scope( $qualifying_roles, $user );
if ( $skip_objscope_check )
$objscope_objects = array();
else
$objscope_objects = $this->scoper->get_restrictions(OBJECT_SCOPE_RS, $src_name); // this is buffered so redundant calling is not a concern
}
//---------------------------------------------------------------------------------
//dump($qualifying_object_roles);
//dump($objscope_objects);
if ( $otype_use_object_roles )
$user_qualifies_for_obj_roles = ( $user->ID || defined( 'SCOPER_ANON_METAGROUP' ) );
$where = array();
if ( $terms_query ) {
$_taxonomies = $taxonomies;
} elseif ( $otype_use_term_roles && is_array($otype_use_term_roles) ) {
$_taxonomies = array_keys( array_intersect( $otype_use_term_roles, array( 1, '1', true ) ) );
// taxonomies arg is for limiting; default is to include all associated taxonomies in where clause
if ( $taxonomies )
$_taxonomies = array_intersect( $_taxonomies, $taxonomies );
} else {
$_taxonomies = array();
}
if ( $_taxonomies && ( 'post' == $src_name ) ) {
$enabled_taxonomies = array_keys( array_intersect( scoper_get_option( 'use_taxonomies' ), array( 1, '1', true ) ) );
$_taxonomies = array_intersect( $_taxonomies, $enabled_taxonomies );
}
$user_blog_roles = array( '' => array() );
if ( $use_blog_roles ) {
foreach( array_keys($user->blog_roles) as $date_key )
$user_blog_roles[$date_key] = array_intersect_key( $user->blog_roles[$date_key], $qualifying_roles );
// Also include user's WP blogrole(s),
// but via equivalent RS role(s) to support scoping requirements (strict (i.e. restricted) terms, objects)
if ( $wp_qualifying_roles = $this->scoper->role_defs->qualify_roles($reqd_caps, 'wp') ) {
if ( $user_blog_roles_wp = array_intersect_key( $user->blog_roles[ANY_CONTENT_DATE_RS], $wp_qualifying_roles ) ) {
// Credit user's qualifying WP blogrole via contained RS role(s)
// so we can also enforce "term restrictions", which are based on RS roles
$user_blog_roles_via_wp = $this->scoper->role_defs->get_contained_roles( array_keys($user_blog_roles_wp), false, 'rs');
$user_blog_roles_via_wp = array_intersect_key($user_blog_roles_via_wp, $qualifying_roles);
$user_blog_roles[ANY_CONTENT_DATE_RS] = array_merge( $user_blog_roles[ANY_CONTENT_DATE_RS], $user_blog_roles_via_wp);
}
}
}
/*
// --- optional hack to require read_private cap via blog role AND object role
// if the required capabilities include a read_private cap but no edit caps
$require_blog_and_obj_role = ( in_array('read_private_posts', $reqd_caps) || in_array('read_private_pages', $reqd_caps) ) && ( ! array_diff( $reqd_caps, array('read_private_posts', 'read_private_pages', 'read') ) );
// --- end hack ---
*/
//dump($qualifying_roles);
//dump($objscope_objects);
foreach ( array_keys($qualifying_roles) as $role_handle ) {
//dump($role_handle);
if ( $otype_use_object_roles && empty($require_blog_and_obj_role) ) {
if ( ! empty($objscope_objects['restrictions'][$role_handle]) ) {
$objscope_clause = " AND $src_table.{$src->cols->id} NOT IN ('" . implode("', '", array_keys($objscope_objects['restrictions'][$role_handle])) . "')";
}
elseif ( isset($objscope_objects['unrestrictions'][$role_handle]) ) {
if ( ! empty($objscope_objects['unrestrictions'][$role_handle]) )
$objscope_clause = " AND $src_table.{$src->cols->id} IN ('" . implode("', '", array_keys($objscope_objects['unrestrictions'][$role_handle])) . "')";
else
$objscope_clause = " AND 1=2"; // role is default-restricted for this object type, but objects are unrestrictions are set
} else
$objscope_clause = '';
} else
$objscope_clause = '';
//dump($objscope_clause);
$all_terms_qualified = array();
$all_taxonomies_qualified = array();
if ( $_taxonomies ) {
$args['return_id_type'] = COL_TAXONOMY_ID_RS;
$strict_taxonomies = array();
foreach ($_taxonomies as $taxonomy)
if ( $this->scoper->taxonomies->member_property($taxonomy, 'requires_term') )
$strict_taxonomies[$taxonomy] = true;
foreach ($_taxonomies as $taxonomy) {
// we only need a separate clause for each role if considering object roles (and therefore considering that some objects might require some roles to be object-assigned)
if ( ! $otype_use_object_roles )
$role_handle_arg = $qualifying_roles;
else
$role_handle_arg = array( $role_handle => 1 );
// If a taxonomy does not require objects to have a term, its term role assignments
// will be purely supplemental; there is no basis for ignoring blogrole assignments.
//
// So if none of the taxonomies require each object to have a term
// AND the user has a qualifying role via blog assignment, we can skip the taxonomies clause altogether.
// Otherwise, will consider current user's termroles
if ( ! $strict_taxonomies ) {
if ( array_intersect_key($role_handle_arg, $user->blog_roles[ANY_CONTENT_DATE_RS]) ) {
// User has a qualifying role by blog assignment, so term_id clause is not required
$all_taxonomies_qualified[ANY_CONTENT_DATE_RS] = true;
break;
}
}
// qualify_terms returns:
// terms for which current user has a qualifying role
// - AND -
// which are non-restricted (i.e. blend in blog assignments) for a qualifying role which the user has blog-wide
//
// note: $reqd_caps function arg is used; qualify_terms will ignore reqd_caps element in args array
if ( $user_terms = $this->scoper->qualify_terms_daterange($reqd_caps, $taxonomy, $role_handle_arg, $args) ) {
if ( ! isset($term_count[$taxonomy]) )
$term_count[$taxonomy] = $this->scoper->get_terms($taxonomy, UNFILTERED_RS, COL_COUNT_RS);
foreach ( array_keys($user_terms) as $date_key ) {
if ( count($user_terms[$date_key]) ) {
// don't bother applying term requirements if user has cap for all terms in this taxonomy
if ( (count($user_terms[$date_key]) >= $term_count[$taxonomy]) && $this->scoper->taxonomies->member_property($taxonomy, 'requires_term') ) {
// User is qualified for all terms in this taxonomy; no need for any term_id clauses
$all_terms_qualified[$date_key][$taxonomy] = true;
} else {
$where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy] = ( isset($where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy]) ) ? array_unique( array_merge($where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy], $user_terms[$date_key]) ) : $user_terms[$date_key];
}
}
$all_taxonomies_qualified[$date_key] = ! empty( $all_terms_qualified[$date_key] ) && ( count($all_terms_qualified[$date_key]) == count($strict_taxonomies) );
}
}
} // end foreach taxonomy
}
foreach ( array_keys($user_blog_roles) as $date_key ) {
if ( ! empty($all_taxonomies_qualified[$date_key]) || ( ! $_taxonomies && ! empty($user_blog_roles[$date_key][$role_handle]) ) ) {
if ( $date_key || $objscope_clause || ! empty($require_blog_and_obj_role) ) {
$where[$date_key][$objscope_clause][BLOG_SCOPE_RS] = "1=1";
} else {
return "1=1"; // no need to include other clause if user has a qualifying role blog-wide or in all terms, it is not date-limited, and that role does not require object assignment for any objects
}
}
}
// if object roles should be applied, populatate array key to force inclusion of OBJECT_SCOPE_RS query clauses below
if ( $otype_use_object_roles && isset($qualifying_object_roles[$role_handle]) && $user_qualifies_for_obj_roles ) { // want to apply objscope requirements for anon user, but not apply any obj roles
if ( $role_spec = scoper_explode_role_handle($role_handle) )
$where[ANY_CONTENT_DATE_RS][NO_OBJSCOPE_CLAUSE_RS][OBJECT_SCOPE_RS][$role_spec->role_type][$role_spec->role_name] = true;
}
// we only need a separate clause for each role if considering object roles (and therefore considering that some objects might require some roles to be object-assigned)
if ( ! $otype_use_object_roles && ! empty($where[ANY_CONTENT_DATE_RS]) )
break;
} // end foreach role
// also include object scope clauses for any roles which qualify only for object-assignment
if ( $otype_use_object_roles && isset( $qualifying_object_roles ) && $user_qualifies_for_obj_roles ) { // want to apply objscope requirements for anon user, but not apply any obj roles
if ( $obj_only_roles = array_diff_key( $qualifying_object_roles, $qualifying_roles ) ) {
foreach ( array_keys($obj_only_roles) as $role_handle )
if ( $role_spec = scoper_explode_role_handle($role_handle) )
$where[ANY_CONTENT_DATE_RS][NO_OBJSCOPE_CLAUSE_RS][OBJECT_SCOPE_RS][$role_spec->role_type][$role_spec->role_name] = true;
}
}
// DB query perf enhancement: if any terms are included regardless of post ID, don't also include those terms in ID-specific clause
foreach ( array_keys($where) as $date_key ) {
foreach ( array_keys($where[$date_key]) as $objscope_clause ) {
if ( $objscope_clause && isset($where[$date_key][$objscope_clause][TERM_SCOPE_RS]) ) {
foreach ( $where[$date_key][$objscope_clause][TERM_SCOPE_RS] as $taxonomy => $terms ) {
if ( ! empty($terms) && ! empty($where[$date_key][NO_OBJSCOPE_CLAUSE_RS][TERM_SCOPE_RS][$taxonomy]) ) {
$where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy] = array_diff( $where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy], $where[$date_key][NO_OBJSCOPE_CLAUSE_RS][TERM_SCOPE_RS][$taxonomy] );
if ( empty( $where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy] ) ) {
unset( $where[$date_key][$objscope_clause][TERM_SCOPE_RS][$taxonomy] );
// if we removed a taxonomy array, don't leave behind a term scope array with no taxonomies
if ( empty( $where[$date_key][$objscope_clause][TERM_SCOPE_RS] ) ) {
unset( $where[$date_key][$objscope_clause][TERM_SCOPE_RS] );
// if we removed a term scope array, don't leave behind an objscope array with no scopes
if ( empty( $where[$date_key][$objscope_clause] ) )
unset( $where[$date_key][$objscope_clause] );
}
}
}
}
}
}
}
// since object roles are not pre-loaded prior to this call, role date limits are handled via subselect, within the date_key = '' iteration
$object_roles_duration_clause = scoper_get_duration_clause();
// implode the array of where criteria into a query as concisely as possible
foreach ( $where as $date_key => $objscope_clauses ) {
foreach ( $objscope_clauses as $objscope_clause => $scope_criteria ) {
foreach ( array_keys($scope_criteria) as $scope ) {
switch ($scope) {
case BLOG_SCOPE_RS:
$where[$date_key][$objscope_clause][BLOG_SCOPE_RS] = $where[$date_key][$objscope_clause][BLOG_SCOPE_RS] . " $objscope_clause";
break;
case TERM_SCOPE_RS:
$taxonomy_clauses = array();
foreach ( $scope_criteria[TERM_SCOPE_RS] as $taxonomy => $terms ) {
$is_strict = ! empty( $strict_taxonomies[$taxonomy] );
if ( $objscope_clause )
// Avoid " term_id IN (5) OR ( term_id IN (5) AND ID NOT IN (100) )
// Otherwise this redundancy can occur when various qualifying roles require object role assignment for different objects
if ( ! empty($where[$date_key][NO_OBJSCOPE_CLAUSE_RS][TERM_SCOPE_RS][$taxonomy]) )
if ( ! $terms = array_diff($terms, $where[$date_key][NO_OBJSCOPE_CLAUSE_RS][TERM_SCOPE_RS][$taxonomy]) ) {
//unset($scope_criteria[TERM_SCOPE_RS][$taxonomy]); // this doesn't affect anything (removed in v1.1)
continue;
}
$terms = array_unique($terms);
if ( $qvars = $this->scoper->taxonomies->get_terms_query_vars($taxonomy) )
if ( $terms_query && ! $otype_use_object_roles ) {
$qtv = $this->scoper->taxonomies->get_terms_query_vars($taxonomy, true);
$taxonomy_clauses[false] []= "{$qtv->term->alias}.{$qtv->term->col_id} IN ('" . implode("', '", $terms) . "') $objscope_clause";
} else {
$this_tx_clause = "{$qvars->term->alias}.{$qvars->term->col_id} IN ('" . implode("', '", $terms) . "')";
// Use a subselect rather than adding our own LEFT JOIN.
$terms_subselect = "SELECT {$qvars->term->alias}.{$qvars->term->col_obj_id} FROM {$qvars->term->table} {$qvars->term->as} WHERE $this_tx_clause";
if ( defined('RVY_VERSION') && $objrole_revisions_clause )
$revision_clause = "OR ( $src_table.{$src->cols->type} = 'revision' AND $src_table.{$src->cols->parent} IN ( $terms_subselect ) )";
else
$revision_clause = '';
$taxonomy_clauses[$is_strict] []= "( $src_table.{$src->cols->id} IN ( $terms_subselect ) $revision_clause ) $objscope_clause";
}
}
if ( $taxonomy_clauses ) {
// all taxonomy clauses concat: [taxonomy 1 clauses] [OR] [taxonomy 2 clauses] [OR] ...
//$where[$date_key][$objscope_clause][TERM_SCOPE_RS] = agp_implode(' ) OR ( ', $taxonomy_clauses, ' ( ', ' ) ');
// strict taxonomy clauses (if any are present, they must all be satisfied)
if ( ! empty( $taxonomy_clauses[true] ) ) {
$where[$date_key][$objscope_clause][TERM_SCOPE_RS] = agp_implode(' ) AND ( ', $taxonomy_clauses[true], ' ( ', ' ) ');
// non-strict taxonomy clauses
} elseif ( ! empty( $taxonomy_clauses[false] ) ) {
$where[$date_key][$objscope_clause][TERM_SCOPE_RS] = agp_implode(' ) OR ( ', $taxonomy_clauses[false], ' ( ', ' ) ');
} else {
$where[$date_key][$objscope_clause][TERM_SCOPE_RS] = '1=2';
}
// all taxonomy clauses concat: ( [strict taxonomy clause 1] [AND] [strict taxonomy clause 2]... ) [OR] [taxonomy 3 clauses] [OR] ...
//$where[$date_key][$objscope_clause][TERM_SCOPE_RS] = agp_implode(' ) OR ( ', $taxonomy_clauses, ' ( ', ' ) ');
}
break;
case OBJECT_SCOPE_RS: // should only exist with nullstring objscope_clause
if ( $user_qualifies_for_obj_roles ) {
global $wpdb;
$u_g_clause = $user->get_user_clause('uro');
foreach ( array_keys($scope_criteria[OBJECT_SCOPE_RS]) as $role_type ) { //should be only one
if ( $scope_criteria[OBJECT_SCOPE_RS][$role_type] )
ksort( $scope_criteria[OBJECT_SCOPE_RS][$role_type] ); // sort array for efficient membuffering of query results
// Combine all qualifying (and applied) object roles into a single OR clause
$role_in = "'" . implode("', '", array_keys($scope_criteria[OBJECT_SCOPE_RS][$role_type])) . "'";
static $cache_obj_ids = array();
if ( 'post.php' == $GLOBALS['pagenow'] && ! empty($_REQUEST['action']) || did_action( 'save_post' ) || ! empty($_GET['doaction']) )
$force_refresh = true;
$objrole_subselect = "SELECT DISTINCT uro.obj_or_term_id FROM $wpdb->user2role2object_rs AS uro WHERE uro.role_type = '$role_spec->role_type' AND uro.scope = 'object' AND uro.assign_for IN ('entity', 'both') AND uro.role_name IN ($role_in) AND uro.src_or_tx_name = '$src_name' $object_roles_duration_clause $u_g_clause ";
if ( ! isset( $cache_obj_ids[$objrole_subselect] ) || ! empty($force_refresh) )
$cache_obj_ids[$objrole_subselect] = scoper_get_col( $objrole_subselect );
if ( $cache_obj_ids[$objrole_subselect] ) {
$where[$date_key][$objscope_clause][OBJECT_SCOPE_RS] = "$src_table.{$src->cols->id} IN ( '" . implode( "','", $cache_obj_ids[$objrole_subselect] ) . "' )";
} else
$where[$date_key][$objscope_clause][OBJECT_SCOPE_RS] = "1=2";
if ( defined('RVY_VERSION') && $objrole_revisions_clause )
$where[$date_key][$objscope_clause][OBJECT_SCOPE_RS] = "( {$where[$date_key][$objscope_clause]['object']} OR ( $src_table.{$src->cols->type} = 'revision' AND $src_table.{$src->cols->parent} IN ( '" . implode( "','", $cache_obj_ids[$objrole_subselect] ) . "' ) ) )";
}
}
break;
} // end scope switch
} // end foreach scope
/*
// --- optional hack to require read_private cap via blog role AND object role
if ( ! empty($require_blog_and_obj_role) ) {
if ( ! isset($where[$date_key][''][BLOG_SCOPE_RS]) )
$where[$date_key][''][BLOG_SCOPE_RS] = '1=2';
if ( ! isset($where[$date_key][''][TERM_SCOPE_RS]) )
$where[$date_key][''][TERM_SCOPE_RS] = '1=2';
if ( ! isset($where[$date_key][''][OBJECT_SCOPE_RS]) )
$where[$date_key][''][OBJECT_SCOPE_RS] = '1=2';
$where[$date_key][''] = "( ( {$where[$date_key]['']['blog']} ) OR ( {$where[$date_key]['']['term']} ) ) AND ( {$where[$date_key]['']['object']} )";
}
else
// --- end hack
*/
// all scope clauses concat: [object roles] OR [term ids] OR [blogrole1 clause] [OR] [blogrole2 clause] [OR] ...
// Collapse the array to a string even if it's empty
$where[$date_key][$objscope_clause] = agp_implode(' ) OR ( ', $where[$date_key][$objscope_clause], ' ( ', ' ) ');
} // end foreach objscope clause
$date_clause = '';
if ( $date_key && is_serialized($date_key) ) {
$content_date_limits = unserialize($date_key);
if ( $content_date_limits->content_min_date_gmt )
$date_clause .= " AND $src_table.{$src->cols->date} >= '" . $content_date_limits->content_min_date_gmt . "'";
if ( $content_date_limits->content_max_date_gmt )
$date_clause .= " AND $src_table.{$src->cols->date} <= '" . $content_date_limits->content_max_date_gmt . "'";
}
foreach ( array_keys($where[$date_key]) as $objscope_clause )
if ( empty ($where[$date_key][$objscope_clause]) )
unset($where[$date_key][$objscope_clause]);
// all objscope clauses concat: [clauses w/o objscope] [OR] [objscope 1 clauses] [OR] [objscope 2 clauses]
$where[$date_key] = agp_implode(' ) OR ( ', $where[$date_key], ' ( ', ' ) ');
if ( $date_clause && $where[$date_key] )
$where[$date_key] = "( $where[$date_key]{$date_clause} )";
} // end foreach datekey (set of content date limits for which role(s) apply)
foreach ( array_keys($where) as $date_key )
if ( empty ($where[$date_key]) )
unset($where[$date_key]);
// all date clauses concat: [clauses w/o content date limits] [OR] [content date range 1 clauses] [OR] [content date range 2 clauses]
$where = agp_implode(' ) OR ( ', $where, ' ( ', ' ) ');
if ( empty($where) )
$where = '1=2';
return $where;
}
function flt_objects_results($results, $src_name, $object_types, $args = array()) {
if ( ! $object_types || ( is_array($object_types) && count($object_types) > 1 ) )
$object_type = cr_find_post_type();
else
$object_type = strval( $object_types );
if ( ( 'edit.php' == $GLOBALS['pagenow'] ) && ! is_content_administrator_rs() ) {
$post_type_obj = get_post_type_object( $object_type );
if ( ! empty($post_type_obj->hierarchical) ) {
// ScoperAncestry class is loaded by hardway_rs.php
$ancestors = ScoperAncestry::get_page_ancestors(); // array of all ancestor IDs for keyed page_id, with direct parent first
$args = array( 'remap_parents' => false );
ScoperHardway::remap_tree( $results, $ancestors, 'ID', 'post_parent', $args );
}
}
if ( $this->scoper->is_front() && empty($this->skip_teaser) ) {
if ( $tease_otypes = $this->_get_teaser_object_types($src_name, $object_types, $args) ) {
require_once( dirname(__FILE__).'/teaser_rs.php');
$args['force_teaser'] = true;
return ScoperTeaser::posts_teaser_prep_results( $results, $tease_otypes, $args );
}
// won't do anything unless teaser is enabled for object type(s)
//$results = apply_filters('objects_teaser_pre_results_rs', $results, 'post', $object_type, array('force_teaser' => true));
}
return $results;
}
// currently only used to conditionally launch teaser filtering
function flt_the_posts( $results ) {
if ( empty($this->skip_teaser) ) {
$object_type = cr_find_post_type( '', false); // arg: don't return 'post' as a default if detection fails
// won't do anything unless teaser is enabled for object type(s)
$results = apply_filters( 'objects_teaser_rs', $results, 'post', $object_type, array('force_teaser' => true) );
}
return $results;
}
function flt_objects_teaser($results, $src_name, $object_types = '', $args = array()) {
$defaults = array('user' => '', 'use_object_roles' => -1, 'use_term_roles' => -1, 'request' => '', 'force_teaser' => false);
$args = array_merge( $defaults, (array) $args );
extract($args);
global $wpdb;
if ( 'post' != $src_name )
return $results;
if ( is_admin() || defined('XMLRPC_REQUEST') )
return $results;
$object_types = $this->_get_object_types($src_name, $object_types);
$tease_otypes = $this->_get_teaser_object_types($src_name, $object_types, $args);
if ( empty($tease_otypes) || ( empty($force_teaser) && ! array_intersect($object_types, $tease_otypes) ) )
return $results;
require_once( dirname(__FILE__).'/teaser_rs.php');
return ScoperTeaser::posts_teaser($results, $tease_otypes, $args);
}
} // end class
function agp_parse_after_WHERE_11( $request, &$pos_where, &$pos_suffix ) {
$request_u = strtoupper($request);
$pos_where = strpos( $request_u, ' WHERE 1=1');
if ( ! $pos_where ) {
if ( $pos_suffix = agp_get_suffix_pos($request) )
$where = substr($request, $pos_suffix);
} else {
// note: this will still also contain any orderby/limit/groupby clauses ( okay since we won't append anything to the end )
$where = substr($request, $pos_where + strlen(' WHERE 1=1 '));
}
return $where;
}
function agp_get_suffix_pos( $request ) {
$request_u = strtoupper($request);
$pos_suffix = strlen($request) + 1;
foreach ( array(' ORDER BY ', ' GROUP BY ', ' LIMIT ') as $suffix_term )
if ( $pos = strrpos($request_u, $suffix_term) )
if ( $pos < $pos_suffix )
$pos_suffix = $pos;
return $pos_suffix;
}
?>