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; } ?>