function flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args)
 {
     if (empty($args[2])) {
         return $wp_blogcaps;
     }
     // Disregard caps which are not defined in Role Scoper config
     if (!($rs_reqd_caps = array_intersect($orig_reqd_caps, $this->scoper->cap_defs->get_all_keys()))) {
         return $wp_blogcaps;
     }
     $user_id = isset($args[1]) ? $args[1] : 0;
     global $current_rs_user;
     if ($user_id && $user_id != $current_rs_user->ID) {
         $user = rs_get_user($user_id);
     } else {
         $user = $current_rs_user;
     }
     $object_id = (int) $args[2];
     if (!($post_type = get_post_field('post_type', $object_id))) {
         return $wp_blogcaps;
     }
     global $wpdb;
     $use_term_roles = scoper_get_otype_option('use_term_roles', 'post', $post_type);
     $use_object_roles = empty($src->no_object_roles) ? scoper_get_otype_option('use_object_roles', 'post', $post_type) : false;
     $this_args = array('object_type' => $post_type, 'user' => $user, 'otype_use_term_roles' => $use_term_roles, 'otype_use_object_roles' => $use_object_roles, 'skip_teaser' => true);
     $where = $this->query_interceptor->objects_where_role_clauses('post', $rs_reqd_caps, $this_args);
     if ($where) {
         $where = "AND ( {$where} )";
     }
     $id_ok = scoper_get_var("SELECT {$wpdb->posts}.ID FROM {$wpdb->posts} WHERE 1=1 {$where} AND {$wpdb->posts}.ID = '{$object_id}' LIMIT 1");
     $rs_reqd_caps = array_fill_keys($rs_reqd_caps, true);
     if (!$id_ok) {
         //d_echo("object_id $object_id not okay!" );
         //rs_errlog( "object_id $object_id not okay!" );
         return array_diff_key($wp_blogcaps, $rs_reqd_caps);
         // required caps we scrutinized are excluded from this array
     } else {
         if ($restore_caps = array_diff($orig_reqd_caps, array_keys($rs_reqd_caps))) {
             $rs_reqd_caps = $rs_reqd_caps + array_fill_keys($restore_caps, true);
         }
         //rs_errlog( 'RETURNING:' );
         //rs_errlog( serialize(array_merge($wp_blogcaps, $rs_reqd_caps)) );
         return array_merge($wp_blogcaps, $rs_reqd_caps);
     }
 }
 function act_update_user_groups($user_id)
 {
     if (empty($_POST['rs_editing_user_groups'])) {
         // otherwise we'd delete group assignments if another plugin calls do_action('profile_update') unexpectedly
         return;
     }
     global $current_rs_user;
     $editable_group_ids = array();
     $stored_groups = array();
     if ($user_id == $current_rs_user->ID) {
         $stored_groups['active'] = $current_rs_user->groups;
     } else {
         $user = rs_get_user($user_id, '', array('skip_role_merge' => 1));
         $stored_groups['active'] = $user->groups;
     }
     // by retrieving filtered groups here, user will only modify membership for groups they can administer
     $editable_group_ids['active'] = ScoperAdminLib::get_all_groups(FILTERED_RS, COL_ID_RS, array('reqd_caps' => 'manage_groups'));
     if (scoper_get_option('group_ajax')) {
         $this->update_user_groups_multi_status($user_id, $stored_groups, $editable_group_ids);
         return;
     } else {
         $stored_groups = $stored_groups['active'];
         $editable_group_ids = $editable_group_ids['active'];
     }
     if (!empty($_POST['groups_csv'])) {
         if ($csv_for_item = ScoperAdminLib::agent_ids_from_csv('groups_csv', 'groups')) {
             $posted_groups = array_merge($posted_groups, $csv_for_item);
         }
     } else {
         $posted_groups = isset($_POST['group']) ? $_POST['group'] : array();
     }
     $posted_groups = array_unique($posted_groups);
     foreach ($editable_group_ids as $group_id) {
         if (in_array($group_id, $posted_groups)) {
             // checkbox is checked
             if (!isset($stored_groups[$group_id])) {
                 ScoperAdminLib::add_group_user($group_id, $user_id);
             }
         } elseif (isset($stored_groups[$group_id])) {
             ScoperAdminLib::remove_group_user($group_id, $user_id);
         }
     }
 }
 function _flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args)
 {
     // =============================== STATIC VARIABLE DECLARATION AND INITIALIZATION (to memcache filtering results) =====
     static $cache_tested_ids;
     static $cache_okay_ids;
     static $cache_where_clause;
     if (empty($cache_tested_ids)) {
         $cache_where_clause = array();
         $cache_tested_ids = array();
         $cache_okay_ids = array();
     }
     // ====================================================================================================================
     // =============================================== TEMPORARY DEBUG CODE ================================================
     //dump($orig_reqd_caps);
     //dump($args);
     //if ( strpos( $_SERVER['REQUEST_URI'], 'ajax' ) ) {
     //if ( ! empty($_REQUEST) )
     //	rs_errlog( serialize($_REQUEST) );
     //rs_errlog( '' );
     //rs_errlog('flt_user_has_cap');
     //rs_errlog(serialize($orig_reqd_caps));
     //rs_errlog(serialize($args));
     //rs_errlog('');
     //}
     // ============================================= (end temporary debug code) ==============================================
     // convert 'rs_role_name' to corresponding caps (and also make a tinkerable copy of orig_reqd_caps)
     $orig_reqd_caps = $this->scoper->role_defs->role_handles_to_caps($orig_reqd_caps);
     // ================= EARLY EXIT CHECKS (if the provided reqd_caps do not need filtering or need special case filtering ==================
     global $pagenow;
     // Disregard caps which are not defined in Role Scoper config
     if (!($rs_reqd_caps = array_intersect($orig_reqd_caps, $this->scoper->cap_defs->get_all_keys()))) {
         return $wp_blogcaps;
     }
     // log initial set of RS-filtered caps (in case we swap in equivalent caps for intermediate processing)
     $orig_reqd_caps = $rs_reqd_caps;
     // permitting this filter to execute early in an attachment request resets the found_posts record, preventing display in the template
     if (is_attachment() && !is_admin() && !did_action('template_redirect')) {
         if (empty($GLOBALS['scoper_checking_attachment_access'])) {
             return $wp_blogcaps;
         }
     }
     // work around bug in mw_EditPost method (requires publish_pages AND publish_posts cap)
     if (defined('XMLRPC_REQUEST') && 'publish_posts' == $orig_reqd_caps[0]) {
         if (!empty($GLOBALS['xmlrpc_post_type_rs']) && 'page' == $GLOBALS['xmlrpc_post_type_rs']) {
             return array('publish_posts' => true);
         }
     }
     // backdoor to deal with rare cases where one of the caps included in RS role defs cannot be filtered properly
     if (defined('UNSCOPED_CAPS_RS') && !array_diff($orig_reqd_caps, explode(',', UNSCOPED_CAPS_RS))) {
         return $wp_blogcaps;
     }
     // custom workaround to reveal all private / restricted content in all blogs if logged into main blog
     if (defined('SCOPER_MU_MAIN_BLOG_RULES')) {
         include_once dirname(__FILE__) . '/mu-custom.php';
         if (!array_diff($orig_reqd_caps, array('read', 'read_private_pages', 'read_private_posts'))) {
             if ($return_caps = ScoperMU_Custom::current_user_logged_into_main($wp_blogcaps, $orig_reqd_caps)) {
                 return $return_caps;
             }
         }
     }
     //define( 'SCOPER_NO_COMMENT_FILTERING', true );
     if (defined('SCOPER_NO_COMMENT_FILTERING') && 'moderate_comments' == $orig_reqd_caps[0] && empty($GLOBALS['current_rs_user']->allcaps['moderate_comments'])) {
         return $wp_blogcaps;
     }
     if (defined('SCOPER_ALL_UPLOADS_EDITABLE') && $pagenow == 'upload.php' && in_array($orig_reqd_caps[0], array('upload_files', 'edit_others_posts', 'delete_others_posts'))) {
         return $wp_blogcaps;
     }
     // =================================================== (end early exit checks) ======================================================
     // ============================ GLOBAL VARIABLE DECLARATIONS, ARGUMENT TRANSLATION AND STATUS DETECTION =============================
     global $current_rs_user;
     $user_id = isset($args[1]) ? $args[1] : 0;
     if ($user_id && $user_id != $current_rs_user->ID) {
         $user = rs_get_user($user_id);
     } else {
         $user = $current_rs_user;
     }
     // currently needed for filtering async-upload.php
     if (empty($user->blog_roles) || empty($user->blog_roles[''])) {
         $this->scoper->refresh_blogroles();
     }
     $object_id = isset($args[2]) ? (int) $args[2] : 0;
     // WP passes comment ID with 'edit_comment' metacap
     if ($object_id && 'edit_comment' == $args[0]) {
         if (!in_array('moderate_comments', $rs_reqd_caps)) {
             // as of WP 3.2.1, 'edit_comment' maps to related post's 'edit_post' caps without requiring moderate_comments
             if (scoper_get_option('require_moderate_comments_cap')) {
                 $rs_reqd_caps[] = 'moderate_comments';
                 $modified_caps = true;
             }
         }
         if ($comment = get_comment($object_id)) {
             $object_id = $comment->comment_post_ID;
         } else {
             $object_id = 0;
         }
     }
     // note the data source and object type(s) which are associated with the required caps (based on inclusion in RS Role Definitions)
     $is_taxonomy_cap = false;
     $src_name = '';
     $cap_types = $this->scoper->cap_defs->src_otypes_from_caps($rs_reqd_caps, $src_name);
     // note: currently only needed for src_name determination
     $doing_admin_menus = is_admin() && (did_action('_admin_menu') && !did_action('admin_menu') || did_action('admin_head') && !did_action('adminmenu'));
     // for scoped menu management roles, satisfy edit_theme_options cap requirement
     if (array_key_exists(0, $orig_reqd_caps) && 'edit_theme_options' == $orig_reqd_caps[0] && empty($wp_blogcaps['edit_theme_options'])) {
         if (in_array($GLOBALS['pagenow'], array('nav-menus.php', 'admin-ajax.php')) || $doing_admin_menus) {
             $key = array_search('edit_theme_options', $rs_reqd_caps);
             if (false !== $key) {
                 $tx = get_taxonomy('nav_menu');
                 $rs_reqd_caps[$key] = $tx->cap->manage_terms;
                 $src_name = 'nav_menu';
                 // menu-specific manager assignment does not permit deletion of the menu
                 if (!empty($_REQUEST['action']) && 'delete' == $_REQUEST['action']) {
                     $this->skip_any_term_check = true;
                 }
             }
         }
     }
     if (!$src_name) {
         // required capabilities correspond to multiple data sources
         return $wp_blogcaps;
     }
     // slight simplification: assume a single cap object type for a few cap substitution checks
     $is_taxonomy_cap = $this->scoper->cap_defs->member_property(reset($rs_reqd_caps), 'is_taxonomy_cap');
     // Establish some context by detecting object type - based on object ID if provided, or otherwise based on http variables.
     if (in_array($pagenow, array('media-upload.php', 'async-upload.php'))) {
         if (!empty($GLOBALS['post'])) {
             $object_type = $GLOBALS['post']->post_type;
         }
     } elseif (is_admin() && 'edit-tags.php' == $GLOBALS['pagenow'] && 'link_category' == $_REQUEST['taxonomy']) {
         $src_name = 'link';
         $object_type = 'link_category';
     } elseif (array_key_exists(0, $orig_reqd_caps) && in_array($orig_reqd_caps[0], array('manage_nav_menus', 'edit_theme_options'))) {
         $src_name = 'nav_menu';
     }
     if (empty($object_type)) {
         $object_type = cr_find_object_type($src_name, $object_id);
     }
     $object_type_obj = cr_get_type_object($src_name, $object_type);
     $is_att_rev = false;
     if ('post' == $src_name) {
         if (in_array($object_type, array('attachment', 'revision'))) {
             $is_att_rev = true;
             if ($object_id) {
                 if ($_post = get_post($object_id)) {
                     if ($_parent = get_post($_post->post_parent)) {
                         $object_type = $_parent->post_type;
                         $object_id = $_parent->ID;
                         // deal with case of edit_posts cap check on attachments to revision (with Revisionary)
                         if ('revision' == $object_type) {
                             if ($_orig_post = get_post($_parent->post_parent)) {
                                 $object_type = $_orig_post->post_type;
                                 $object_id = $_orig_post->ID;
                             }
                         }
                         $object_type_obj = get_post_type_object($object_type);
                     }
                 }
             }
         } elseif (!$is_taxonomy_cap) {
             $use_post_types = scoper_get_option('use_post_types');
             if (empty($use_post_types[$object_type])) {
                 return $wp_blogcaps;
             }
         }
     }
     // =====================================================================================================================================
     // ======================================== SUBVERT MISGUIDED CAPABILITY REQUIREMENTS ==================================================
     if ('post' == $src_name) {
         if (!$is_taxonomy_cap) {
             $modified_caps = false;
             if ('post' != $object_type) {
                 $replace_post_caps = array('publish_posts', 'edit_others_posts', 'edit_published_posts');
                 // Replace edit_posts requirement with corresponding type-specific requirement, but only after admin menu is drawn, or on a submission before the menu is drawn
                 if (did_action('admin_init')) {
                     // otherwise extra padding between menu items due to some items populated but unpermitted
                     $replace_post_caps[] = 'edit_posts';
                 }
                 if (in_array($pagenow, array('upload.php', 'media.php'))) {
                     $replace_post_caps = array_merge($replace_post_caps, array('delete_posts', 'delete_others_posts'));
                 }
                 foreach ($replace_post_caps as $post_cap_name) {
                     $key = array_search($post_cap_name, $rs_reqd_caps);
                     if (false !== $key && !$doing_admin_menus && in_array($pagenow, array('edit.php', 'post.php', 'post-new.php', 'press-this.php', 'admin-ajax.php', 'upload.php', 'media.php'))) {
                         $rs_reqd_caps[$key] = $object_type_obj->cap->{$post_cap_name};
                         $modified_caps = true;
                     }
                 }
             }
             // WP core quirk workaround: edit_others_posts is required as preliminary check for populating authors dropdown for any post type.  Instead, we need to do our own validation based on scoped roles.
             // (but don't mess if this cap requirement is part of an edit_post metacap check for a specific post)
             if (!$object_id && count($rs_reqd_caps) == 1) {
                 if (in_array(reset($rs_reqd_caps), array('edit_others_posts'))) {
                     require_once dirname(__FILE__) . '/lib/agapetry_wp_admin_lib.php';
                     // function awp_metaboxes_started()
                     if (!awp_metaboxes_started($object_type) && 'revision.php' != $pagenow && 'revisions' != $GLOBALS['plugin_page_cr']) {
                         // don't enable contributors to view/restore revisions
                         $rs_reqd_caps[0] = $object_type_obj->cap->edit_posts;
                     } else {
                         $rs_reqd_caps[0] = $object_type_obj->cap->edit_published_posts;
                     }
                     // we will filter / suppress the author dropdown downstream from here
                     $modified_caps = true;
                 }
             }
             // as of WP 3.1, addition of new nav menu items requires edit_posts capability (otherwise nav menu item is orphaned with no menu relationship)
             if (is_admin() && strpos($_SERVER['SCRIPT_NAME'], 'nav-menus.php')) {
                 if ('edit_posts' == $orig_reqd_caps[0]) {
                     $type_obj = get_taxonomy('nav_menu');
                     $rs_reqd_caps[0] = $type_obj->cap->manage_terms;
                     $modified_caps = true;
                 }
             }
         }
         // endif not taxonomy cap
     }
     // endif caps correspond to 'post' data source
     //====================================== (end subvert misguided capability requirements) =============================================
     if (defined('RVY_VERSION')) {
         require_once dirname(__FILE__) . '/revisionary-helper_rs.php';
         $rs_reqd_caps = Rvy_Helper::convert_post_edit_caps($rs_reqd_caps, $object_type);
     }
     //rs_errlog( "matched context for $object_id : $matched_context" );
     // don't apply object-specific filtering for auto-drafts
     if ('post' == $src_name) {
         if ($object_id) {
             if ($_post = get_post($object_id)) {
                 if ('auto-draft' == $_post->post_status) {
                     // && ! empty($_POST['action']) )
                     $object_id = 0;
                     if (!$doing_admin_menus) {
                         $this->skip_id_generation = true;
                     }
                 }
             }
         } else {
             if (!empty($GLOBALS['post']) && !is_object($GLOBALS['post'])) {
                 $GLOBALS['post'] = get_post($GLOBALS['post']);
             }
             if (!empty($GLOBALS['post']) && 'auto-draft' == $GLOBALS['post']->post_status && !$doing_admin_menus) {
                 $this->skip_id_generation = true;
             }
         }
     }
     //dump($object_id);
     // If no object id was passed in...
     if (!$object_id) {
         // || ! $matched_context ) {
         //if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) ) {
         if (!$doing_admin_menus) {
             if (!empty($_REQUEST['action']) && in_array($pagenow, array('edit.php', 'edit-tags.php'))) {
                 $this->skip_id_generation = true;
             }
             // ============================================ OBJECT ID DETERMINATION ========================================
             if (!$this->skip_id_generation && !defined('XMLRPC_REQUEST') && !in_array($pagenow, array('media-upload.php', 'async-upload.php'))) {
                 // lots of superfluous queries in media upload popup otherwise
                 // Try to generate missing object_id argument for problematic current_user_can calls
                 static $generated_id;
                 if (!isset($generated_id)) {
                     $generated_id = array();
                 }
                 // if the id was not already detected and stored to the static variable...
                 $caps_key = serialize($rs_reqd_caps);
                 if (!isset($generated_id[$object_type][$caps_key])) {
                     $gen_id = 0;
                     foreach ($rs_reqd_caps as $cap_name) {
                         if ($gen_id = (int) $this->_detect_object_id($cap_name)) {
                             break;
                             // means we are accepting the generated id
                         }
                     }
                     $generated_id[$object_type][$caps_key] = $gen_id;
                     $object_id = $gen_id;
                 } else {
                     $object_id = $generated_id[$object_type][$caps_key];
                 }
                 //rs_errlog( "detected ID: $object_id" );
             } else {
                 $this->skip_id_generation = false;
             }
             // this is a one-time flag
             // ========================================= (end object id determination) =======================================
         }
         // If we still have no object id (detection was skipped or failed to identify it)...
         if (!$object_id) {
             // || ! $matched_context ) {
             // ============================================ "CAN FOR ANY" CHECKS ===========================================
             if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) {
                 // These checks are only relevant since no object_id was provided.  Otherwise (in the main body of this function), taxonomy and object caps will be credited via scoped query.
                 // If we are about to fail the blogcap requirement, credit a missing cap if the user has it by term role for ANY term.
                 // This prevents failing initial UI entrance exams that only consider blog-wide roles.
                 if (!$this->skip_any_term_check) {
                     if ($tax_caps = $this->user_can_for_any_term($missing_caps)) {
                         $wp_blogcaps = array_merge($wp_blogcaps, $tax_caps);
                     }
                     //rs_errlog( "can for any term: " . serialize($tax_caps) );
                 } else {
                     $this->skip_any_term_check = false;
                 }
                 // this is a one-time flag
                 // If we are still missing required caps, credit a missing scoper-defined cap if the user has it by object role for ANY object.
                 // (i.e. don't bar user from "Edit Pages" if they have edit_pages cap for at least one page)
                 if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) {
                     // prevent object-specific editing roles from allowing new object creation w/o sitewide capability
                     $add_new_check = strpos($_SERVER['SCRIPT_NAME'], 'post-new.php') && 'post' == $src_name && reset($rs_reqd_caps) == $object_type_obj->cap->edit_posts;
                     if (!$this->skip_any_object_check && !$add_new_check) {
                         //if ( ! $this->skip_any_object_check ) {
                         if ($object_caps = $this->user_can_for_any_object($missing_caps)) {
                             $wp_blogcaps = array_merge($wp_blogcaps, $object_caps);
                         }
                         //rs_errlog( "can for any object: " . serialize($object_caps) );
                     } else {
                         $this->skip_any_object_check = false;
                     }
                     // this is a one-time flag
                 }
             }
             // ========================================== (end "can for any" checks ) =========================================
             //rs_errlog( serialize( $wp_blogcaps) );
             if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) {
                 // normal exit point when no object ID is passed or detected, or when detected object type does not match required capabilities
                 return $wp_blogcaps;
             } else {
                 if ($restore_caps = array_diff($orig_reqd_caps, $rs_reqd_caps)) {
                     // restore original reqd_caps which we substituted for the type-specific scoped query
                     $wp_blogcaps = array_merge($wp_blogcaps, array_fill_keys($restore_caps, true));
                 }
                 return $wp_blogcaps;
             }
         }
         //} else
         //return $wp_blogcaps;
     }
     if ($object_id && 'post' == $src_name) {
         $_post = get_post($object_id);
         $object_type = $_post->post_type;
         $object_type_obj = cr_get_type_object($src_name, $object_type);
         if (defined('RVY_VERSION') && in_array($pagenow, array('edit.php', 'edit-tags.php', 'admin-ajax.php')) && (!empty($_REQUEST['action']) && -1 != $_REQUEST['action'])) {
             $rs_reqd_caps = Rvy_Helper::fix_table_edit_reqd_caps($rs_reqd_caps, $args[0], $_post, $object_type_obj);
         }
         // if the top level page structure is locked, don't allow non-administrator to delete a top level page either
         if ('page' == $object_type || defined('SCOPER_LOCK_OPTION_ALL_TYPES') && !is_content_administrator_rs()) {
             $delete_metacap = !empty($object_type_obj->hierarchical) ? $object_type_obj->cap->delete_post : 'delete_page';
             // if the top level page structure is locked, don't allow non-administrator to delete a top level page either
             if ($delete_metacap == $args[0]) {
                 if ('1' === scoper_get_option('lock_top_pages')) {
                     // stored value of 1 means only Administrators are allowed to modify top-level page structure
                     if ($page = get_post($args[2])) {
                         if (empty($page->post_parent)) {
                             $in_process = false;
                             return false;
                         }
                     }
                 }
             }
         }
     }
     // Note: At this point, we have a nonzero object_id...
     // if this is a term administration request, route to user_can_admin_terms()
     if ($is_taxonomy_cap) {
         if ('post' == $src_name) {
             $cap_otype_obj = get_taxonomy($object_type);
         }
         if (('post' != $src_name || $cap_otype_obj && $rs_reqd_caps[0] == $cap_otype_obj->cap->manage_terms) && count($rs_reqd_caps) == 1) {
             // don't re-route if multiple caps are being required
             // always pass through any assigned blog caps which will not be involved in this filtering
             $rs_reqd_caps = array_fill_keys($rs_reqd_caps, 1);
             $undefined_reqd_caps = array_diff_key($wp_blogcaps, $rs_reqd_caps);
             require_once dirname(__FILE__) . '/admin/permission_lib_rs.php';
             if (user_can_admin_terms_rs($object_type, $object_id, $user)) {
                 return array_merge($undefined_reqd_caps, $rs_reqd_caps);
             } else {
                 return $undefined_reqd_caps;
                 // required caps we scrutinized are excluded from this array
             }
         }
     }
     // Workaround to deal with WP core's checking of publish cap prior to storing categories
     // Store terms to DB in advance of any cap-checking query which may use those terms to qualify an operation
     if (!empty($_REQUEST['action']) && (in_array($_REQUEST['action'], array('editpost', 'post')) || 'autosave' == $_REQUEST['action'])) {
         if (array_intersect(array('publish_posts', 'edit_posts', $object_type_obj->cap->publish_posts, $object_type_obj->cap->edit_posts), $rs_reqd_caps)) {
             $uses_taxonomies = scoper_get_taxonomy_usage($src_name, $object_type);
             static $inserted_terms;
             if (!isset($inserted_terms)) {
                 $inserted_terms = array();
             }
             foreach ($uses_taxonomies as $taxonomy) {
                 // TODO: only if tx->requires_term is true?
                 if (isset($inserted_terms[$taxonomy][$object_id])) {
                     continue;
                 }
                 $inserted_terms[$taxonomy][$object_id] = true;
                 //if ( $stored_terms = wp_get_object_terms( $object_id, $taxonomy ) ) // note: this will cause trouble if WP core ever auto-stores object terms on post creation
                 //	continue;
                 $stored_terms = $this->scoper->get_terms($taxonomy, UNFILTERED_RS, COL_ID_RS, $object_id);
                 require_once dirname(__FILE__) . '/admin/filters-admin-save_rs.php';
                 $selected_terms = cr_get_posted_object_terms($taxonomy);
                 if (is_array($selected_terms)) {
                     // non-hierarchical terms do not need to be pre-inserted
                     if ($set_terms = $GLOBALS['scoper_admin_filters']->flt_pre_object_terms($selected_terms, $taxonomy)) {
                         $set_terms = array_unique(array_map('intval', $set_terms));
                         if ($set_terms != $stored_terms && $set_terms && $set_terms != array(1)) {
                             // safeguard against unintended clearing of stored categories
                             wp_set_object_terms($object_id, $set_terms, $taxonomy);
                             // delete any buffered cap check results which were queried prior to storage of these object terms
                             unset($cache_tested_ids);
                             unset($cache_where_clause);
                             unset($cache_okay_ids);
                         }
                     }
                 }
             }
             // also avoid chicken-egg situation when publish cap is granted by a propagating page role
             if ($object_type_obj->hierarchical && isset($_POST['parent_id'])) {
                 if ($_POST['parent_id'] != get_post_field('post_parent', $object_id)) {
                     global $wpdb;
                     $set_parent = $GLOBALS['scoper_admin_filters']->flt_page_parent($_POST['parent_id']);
                     $GLOBALS['wpdb']->query("UPDATE {$wpdb->posts} SET post_parent = '{$set_parent}' WHERE ID = '{$object_id}'");
                     require_once dirname(__FILE__) . '/admin/filters-admin-save_rs.php';
                     scoper_inherit_parent_roles($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
                     scoper_inherit_parent_restrictions($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
                 }
             }
         }
     }
     // generate a string key for this set of required caps, for use below in checking, caching the scoped results
     $arg_append = '';
     $arg_append .= !empty($this->require_full_object_role) ? '-require_full_object_role-' : '';
     $arg_append .= !empty($GLOBALS['revisionary']->skip_revision_allowance) ? '-skip_revision_allowance-' : '';
     sort($rs_reqd_caps);
     $capreqs_key = implode($rs_reqd_caps) . $arg_append;
     // see ScoperAdmin::user_can_admin_object
     // ================================ SPECIAL HANDLING FOR ATTACHMENTS AND REVISIONS ==========================================
     $maybe_revision = 'post' == $src_name && !isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]);
     $maybe_attachment = in_array($pagenow, array('upload.php', 'media.php'));
     if ($maybe_revision || $maybe_attachment) {
         global $wpdb;
         if ($_post = get_post($object_id)) {
             if ('revision' == $_post->post_type) {
                 require_once dirname(__FILE__) . '/lib/revisions_lib_rs.php';
                 $rev_where = defined('RVY_VERSION') && rvy_get_option('revisor_lock_others_revisions') ? " AND post_author = '{$current_rs_user->ID}'" : '';
                 // might need to apply different cap requirement for other users' revisions. todo: skip this clause for sitewide editors
                 $revisions = rs_get_post_revisions($_post->post_parent, 'inherit', array('fields' => constant('COL_ID_RS'), 'return_flipped' => true, 'where' => $rev_where));
             }
             if ('revision' == $_post->post_type || 'attachment' == $_post->post_type) {
                 $is_att_rev = true;
                 if ($_post->post_parent) {
                     $object_id = $_post->post_parent;
                     if ($_parent = get_post($_post->post_parent)) {
                         $object_type = $_parent->post_type;
                         $object_type_obj = get_post_type_object($object_type);
                     }
                 } elseif ('attachment' == $_post->post_type) {
                     // special case for unattached uploads: uploading user should have their way with them
                     if ($_post->post_author == $current_rs_user->ID) {
                         $rs_reqd_caps[0] = 'read';
                         if ($restore_caps = array_diff($orig_reqd_caps, array_keys($rs_reqd_caps))) {
                             // restore original reqd_caps which we substituted for the type-specific scoped query
                             $wp_blogcaps = array_merge($wp_blogcaps, array_fill_keys($restore_caps, true));
                         }
                     }
                     return $wp_blogcaps;
                 }
             }
             //endif retrieved post is a revision or attachment
         }
         // endif post retrieved
     }
     // endif specified id might be a revision or attachment
     if ($is_att_rev) {
         if ('post' != $object_type_obj->name) {
             // Compensate for WP's requirement of posts cap for attachment editing, regardless of whether it's attached to a post or page
             if ('edit_others_posts' == $rs_reqd_caps[0]) {
                 $rs_reqd_caps[0] = $object_type_obj->cap->edit_others_posts;
             } elseif ('delete_others_posts' == $rs_reqd_caps[0]) {
                 $rs_reqd_caps[0] = $object_type_obj->cap->delete_others_posts;
             } elseif ('edit_posts' == $rs_reqd_caps[0]) {
                 $rs_reqd_caps[0] = $object_type_obj->cap->edit_posts;
             } elseif ('delete_posts' == $rs_reqd_caps[0]) {
                 $rs_reqd_caps[0] = $object_type_obj->cap->delete_posts;
             }
         }
     }
     //endif retrieved post is a revision or attachment
     // ============================== (end special handling for attachments and revisions) ==========================================
     // ============ SCOPED QUERY for required caps on object id (if other listed ids are known, query for them also).  Cache results to static var. ===============
     // $force_refresh = 'async-upload.php' == $pagenow;
     // Page refresh following publishing of new page by users who can edit by way of Term Role fails without this workaround
     if (!empty($_POST) && (defined('SCOPER_CACHE_SAFE_MODE') || in_array($pagenow, array('post.php', 'press-this.php')) && $args[0] == $object_type_obj->cap->edit_post)) {
         $force_refresh = true;
         $cache_tested_ids = array();
         $cache_okay_ids = array();
         $cache_where_clause = array();
     } else {
         $force_refresh = false;
     }
     // Check whether this object id was already tested for the same reqd_caps in a previous execution of this function within the same http request
     if ($force_refresh || !isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id])) {
         //if ( ! isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]) ) {
         // retrieve CR_Data_Source object, which contains database column names
         $src_table = $this->scoper->data_sources->member_property($src_name, 'table');
         $cols = $this->scoper->data_sources->member_property($src_name, 'cols');
         // Before querying for caps on this object, check whether we have a record of other posts listed alongside it.
         // If so, run the scoped query for ALL listed objects in that buffer, and buffer the results to static variable hascap_object_ids.
         //
         // (This is useful when front end code must check caps for each post
         //  to determine whether to display 'edit' link, etc.)
         if (is_admin() && 'index.php' == $pagenow) {
             // there's too much happening on the dashboard (and too much low-level query filtering) to buffer listed IDs reliably.
             $listed_ids = array();
         } else {
             if (isset($this->scoper->listed_ids[$src_name])) {
                 $listed_ids = array_keys($this->scoper->listed_ids[$src_name]);
             } else {
                 // note: don't use wp_object_cache because it includes posts not present in currently displayed resultset listing page
                 $listed_ids = array();
             }
         }
         // make sure our current object_id is in the list
         $listed_ids[] = $object_id;
         // since the objects_where_role_clauses() output itself is not id-specific, also statically buffer it per reqd_caps
         if ($force_refresh || !isset($cache_where_clause[$src_name][$object_type][$capreqs_key])) {
             $check_otype = 'link_category' == $object_type ? 'link' : $object_type;
             $use_term_roles = scoper_get_otype_option('use_term_roles', $src_name, $check_otype);
             $no_object_roles = $this->scoper->data_sources->member_property($src_name, 'no_object_roles');
             $use_object_roles = $no_object_roles ? false : scoper_get_otype_option('use_object_roles', $src_name, $object_type);
             $this_args = array('object_type' => $object_type, 'user' => $user, 'otype_use_term_roles' => $use_term_roles, 'otype_use_object_roles' => $use_object_roles, 'skip_teaser' => true, 'require_full_object_role' => !empty($this->require_full_object_role));
             //rs_errlog( serialize($rs_reqd_caps) );
             //rs_errlog( serialize($this_args) );
             $where = $this->query_interceptor->objects_where_role_clauses($src_name, $rs_reqd_caps, $this_args);
             if ($where) {
                 $where = "AND ( {$where} )";
             }
             // update static variable
             $cache_where_clause[$src_name][$object_type][$capreqs_key] = $where;
         } else {
             $where = $cache_where_clause[$src_name][$object_type][$capreqs_key];
         }
         // run the query
         $query = "SELECT {$src_table}.{$cols->id} FROM {$src_table} WHERE 1=1 {$where} AND {$src_table}.{$cols->id} IN ('" . implode("', '", array_unique($listed_ids)) . "')";
         if (isset($cache_okay_ids[$query])) {
             $okay_ids = $cache_okay_ids[$query];
         } else {
             if ($okay_ids = scoper_get_col($query)) {
                 $okay_ids = array_fill_keys($okay_ids, true);
             }
         }
         //dump($rs_reqd_caps);
         //dump($query);
         //dump($okay_ids);
         //rs_errlog( $query );
         //rs_errlog( 'results: ' . serialize( $okay_ids ) );
         // update static cache_tested_ids to log scoped results for this object id, and possibly also for other listed IDs
         if (empty($_GET['doaction']) || 'delete_post' != $args[0] && $object_type_obj->cap->delete_post != $args[0]) {
             // bulk post/page deletion is broken by hascap buffering
             foreach ($listed_ids as $_id) {
                 $cache_tested_ids[$src_name][$object_type][$capreqs_key][$_id] = isset($okay_ids[$_id]);
             }
             $cache_okay_ids[$query] = $okay_ids;
         }
         $this_id_okay = isset($okay_ids[$object_id]);
     } else {
         // results of this same has_cap inquiry are already stored (from another call within current http request)
         $this_id_okay = $cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id];
     }
     //rs_errlog( "okay ids: " . serialize( $okay_ids ) );
     // if we redirected the cap check to revision parent, also credit all the revisions for passing results
     if ($this_id_okay && !empty($revisions)) {
         if (empty($_GET['doaction']) || 'delete_post' != $args[0] && $object_type_obj->cap->delete_post != $args[0]) {
             // bulk post/page deletion is broken by hascap buffering
             $cache_tested_ids[$src_name][$object_type][$capreqs_key] = $cache_tested_ids[$src_name][$object_type][$capreqs_key] + array_fill_keys($revisions, true);
         }
     }
     $rs_reqd_caps = array_fill_keys($rs_reqd_caps, true);
     if (!$this_id_okay) {
         if (array_key_exists(0, $orig_reqd_caps) && 'edit_posts' == $orig_reqd_caps[0] && strpos($_SERVER['REQUEST_URI'], 'async-upload.php')) {
             // temp workaround for ACF with Revisionary
             return $wp_blogcaps;
         }
         // ================= TEMPORARY DEBUG CODE ===================
         //d_echo("object_id $object_id FAILED !!!!!!!!!!!!!!!!!" );
         //rs_errlog( "object_id $object_id FAILED !!!!!!!!!!!!!!!!!"  );
         //rs_errlog(serialize($orig_reqd_caps));
         //rs_errlog(serialize($rs_reqd_caps));
         //rs_errlog('');
         /*
         $log .= "checked caps: " . serialize($rs_reqd_caps) . "\r\n";
         $log .= "object_id $object_id FAILED !!!!!!!!!!!!!!!!!\r\n";
         $log .= $query;
         rs_errlog( "\r\n{$log}\r\n" );
         */
         //d_echo( "FAILED for " . serialize($rs_reqd_caps) );
         // ============== (end temporary debug code ==================
         return array_diff_key($wp_blogcaps, $rs_reqd_caps);
         // required caps we scrutinized are excluded from this array
     } else {
         if ($restore_caps = array_diff($orig_reqd_caps, array_keys($rs_reqd_caps))) {
             // restore original reqd_caps which we substituted for the type-specific scoped query
             $rs_reqd_caps = $rs_reqd_caps + array_fill_keys($restore_caps, true);
         }
         //d_echo( 'OKAY:' );
         //dump($args);
         //dump($rs_reqd_caps);
         //d_echo( '<br />' );
         return array_merge($wp_blogcaps, $rs_reqd_caps);
     }
 }