PageRenderTime 8ms CodeModel.GetById 812ms app.highlight 206ms RepoModel.GetById 246ms app.codeStats 1ms

/role-scoper/cap-interceptor_rs.php

https://github.com/adityag2/suneha
PHP | 1010 lines | 606 code | 203 blank | 201 comment | 304 complexity | fe32e90dcd9663615f75654dd55b09e2 MD5 | raw file
   1<?php
   2if( basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) )
   3	die();
   4
   5/**
   6 * CapInterceptor_RS PHP class for the WordPress plugin Role Scoper
   7 * cap-interceptor_rs.php
   8 * 
   9 * @author 		Kevin Behrens
  10 * @copyright 	Copyright 2011
  11 * 
  12 */
  13class CapInterceptor_RS
  14{	
  15	var $scoper;
  16	var $scoper_admin;
  17	var $query_interceptor;
  18	
  19	var $in_process = false;
  20	var $skip_id_generation = false;
  21	var $skip_any_term_check = false;
  22	var $skip_any_object_check = false;
  23	var $ignore_object_roles = false;
  24	
  25	function CapInterceptor_RS() {
  26		$this->scoper =& $GLOBALS['scoper'];
  27		$this->query_interceptor =& $GLOBALS['query_interceptor'];
  28		$this->scoper_admin =& $GLOBALS['scoper_admin'];
  29		
  30		// Since scoper installation implies that this plugin should take custody
  31		// of access control, set priority high so we have the final say on group-controlled caps.
  32		// This filter will not mess with any caps which are not scoper-defined.
  33		//
  34		// (note: custom caps from other plugins can be scoper-controlled if they are defined via a Role Scoper Extension plugin)
  35		add_filter('user_has_cap', array(&$this, 'flt_user_has_cap'), 99, 3);  // scoping will be defeated if our filter isn't applied last
  36	}
  37
  38	// hook to wrapper function to avoid recursion
  39	function flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args) {
  40		if ( $this->in_process )
  41			return $wp_blogcaps;
  42			
  43		$this->in_process = true;
  44		$return = $this->_flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args);
  45		$this->in_process = false;
  46		return $return;
  47	}
  48	
  49	// CapInterceptor_RS::flt_user_has_cap
  50	//
  51	// Capability filter applied by WP_User->has_cap (usually via WP current_user_can function)
  52	// Pertains to logged user's capabilities blog-wide, or for a single item
  53	//
  54	// $wp_blogcaps = current user's blog-wide capabilities
  55	// $reqd_caps = primitive capabilities being tested / requested
  56	// $args = array with:
  57	// 		$args[0] = original capability requirement passed to current_user_can (possibly a meta cap)
  58	// 		$args[1] = user being tested
  59	// 		$args[2] = object id (could be a postID, linkID, catID or something else)
  60	//
  61	// The intent here is to add to (or take away from) $wp_blogcaps based on scoper role assignments
  62	// (only offer an opinion on scoper-defined caps, with others left in $allcaps array as blog-wide caps)
  63	//
  64	function _flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args) {
  65		
  66		// =============================== STATIC VARIABLE DECLARATION AND INITIALIZATION (to memcache filtering results) =====
  67		static $cache_tested_ids;
  68		static $cache_okay_ids;
  69		static $cache_where_clause;
  70							
  71		if ( empty($cache_tested_ids) ) {
  72			$cache_where_clause = array();
  73			$cache_tested_ids = array();
  74			$cache_okay_ids = array();
  75		}
  76		// ====================================================================================================================
  77		
  78
  79		// =============================================== TEMPORARY DEBUG CODE ================================================
  80
  81		//dump($orig_reqd_caps);
  82		//dump($args);
  83		
  84		//if ( strpos( $_SERVER['REQUEST_URI'], 'ajax' ) ) {
  85			//if ( ! empty($_REQUEST) )
  86			//	rs_errlog( serialize($_REQUEST) );
  87			//rs_errlog( '' );
  88
  89			//rs_errlog('flt_user_has_cap');
  90			//rs_errlog(serialize($orig_reqd_caps));
  91			//rs_errlog(serialize($args));
  92			//rs_errlog('');
  93		//}
  94		
  95		// ============================================= (end temporary debug code) ==============================================
  96
  97		// convert 'rs_role_name' to corresponding caps (and also make a tinkerable copy of orig_reqd_caps)
  98		$orig_reqd_caps = $this->scoper->role_defs->role_handles_to_caps($orig_reqd_caps);
  99		
 100		
 101		// ================= EARLY EXIT CHECKS (if the provided reqd_caps do not need filtering or need special case filtering ==================
 102		global $pagenow;
 103
 104		// Disregard caps which are not defined in Role Scoper config
 105		if ( ! $rs_reqd_caps = array_intersect( $orig_reqd_caps, $this->scoper->cap_defs->get_all_keys() ) ) {
 106			return $wp_blogcaps;		
 107		}
 108		
 109		// log initial set of RS-filtered caps (in case we swap in equivalent caps for intermediate processing)
 110		$orig_reqd_caps = $rs_reqd_caps;
 111
 112		// permitting this filter to execute early in an attachment request resets the found_posts record, preventing display in the template
 113		if ( is_attachment() && ! is_admin() && ! did_action('template_redirect') ) {
 114			if ( empty( $GLOBALS['scoper_checking_attachment_access'] ) ) {
 115				return $wp_blogcaps;
 116			}
 117		}
 118		
 119		// work around bug in mw_EditPost method (requires publish_pages AND publish_posts cap)
 120		if ( defined('XMLRPC_REQUEST') && ( 'publish_posts' == $orig_reqd_caps[0] ) ) {
 121			if ( ! empty($GLOBALS['xmlrpc_post_type_rs']) && ( 'page' == $GLOBALS['xmlrpc_post_type_rs'] ) ) {
 122				return array( 'publish_posts' => true );
 123			}
 124		}
 125		
 126		// backdoor to deal with rare cases where one of the caps included in RS role defs cannot be filtered properly
 127		if ( defined('UNSCOPED_CAPS_RS') && ! array_diff( $orig_reqd_caps, explode( ',', UNSCOPED_CAPS_RS ) ) ) {
 128			return $wp_blogcaps;
 129		}
 130		
 131		// custom workaround to reveal all private / restricted content in all blogs if logged into main blog 
 132		if ( defined( 'SCOPER_MU_MAIN_BLOG_RULES' ) ) {
 133			include_once( dirname(__FILE__).'/mu-custom.php' );
 134			if ( ! array_diff( $orig_reqd_caps, array( 'read', 'read_private_pages', 'read_private_posts' ) ) )
 135				if ( $return_caps = ScoperMU_Custom::current_user_logged_into_main( $wp_blogcaps, $orig_reqd_caps ) ) {
 136					return $return_caps;
 137				}
 138		}
 139
 140		//define( 'SCOPER_NO_COMMENT_FILTERING', true );
 141		if ( defined( 'SCOPER_NO_COMMENT_FILTERING' ) && ( 'moderate_comments' == $orig_reqd_caps[0] ) && empty( $GLOBALS['current_rs_user']->allcaps['moderate_comments'] ) ) {
 142			return $wp_blogcaps;			
 143		}
 144		
 145		if ( defined( 'SCOPER_ALL_UPLOADS_EDITABLE' ) && ( $pagenow == 'upload.php' ) && in_array( $orig_reqd_caps[0], array( 'upload_files', 'edit_others_posts', 'delete_others_posts' ) ) ) {
 146			return $wp_blogcaps;
 147		}		
 148		// =================================================== (end early exit checks) ======================================================
 149
 150		
 151		// ============================ GLOBAL VARIABLE DECLARATIONS, ARGUMENT TRANSLATION AND STATUS DETECTION =============================
 152		global $current_rs_user;
 153		
 154		$user_id = ( isset($args[1]) ) ? $args[1] : 0;
 155
 156		if ( $user_id && ($user_id != $current_rs_user->ID) )
 157			$user = rs_get_user($user_id);
 158		else
 159			$user = $current_rs_user;
 160
 161		// currently needed for filtering async-upload.php
 162		if ( empty($user->blog_roles ) || empty($user->blog_roles[''] ) )
 163			$this->scoper->refresh_blogroles();
 164			
 165		$object_id = ( isset($args[2]) ) ? (int) $args[2] : 0;
 166		
 167		// WP passes comment ID with 'edit_comment' metacap
 168		if ( $object_id && ( 'edit_comment' == $args[0] ) ) {
 169			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
 170				if ( scoper_get_option( 'require_moderate_comments_cap' ) ) {
 171					$rs_reqd_caps[] = 'moderate_comments';
 172					$modified_caps = true;
 173				}
 174			}
 175				
 176			if ( $comment = get_comment( $object_id ) )
 177				$object_id = $comment->comment_post_ID;
 178			else
 179				$object_id = 0;
 180		}
 181		
 182		// note the data source and object type(s) which are associated with the required caps (based on inclusion in RS Role Definitions)
 183		$is_taxonomy_cap = false;
 184		$src_name = '';
 185		$cap_types = $this->scoper->cap_defs->src_otypes_from_caps( $rs_reqd_caps, $src_name );	  // note: currently only needed for src_name determination
 186		
 187		$doing_admin_menus = is_admin() && (
 188		( did_action( '_admin_menu' ) && ! did_action('admin_menu') ) 	 // menu construction
 189		|| ( did_action( 'admin_head' ) && ! did_action('adminmenu') )	 // menu display
 190		);
 191
 192		// for scoped menu management roles, satisfy edit_theme_options cap requirement
 193		if ( array_key_exists(0, $orig_reqd_caps) && ( 'edit_theme_options' == $orig_reqd_caps[0] ) && empty( $wp_blogcaps['edit_theme_options'] ) ) {
 194			if ( in_array( $GLOBALS['pagenow'], array( 'nav-menus.php', 'admin-ajax.php' ) ) || $doing_admin_menus ) {
 195				$key = array_search( 'edit_theme_options', $rs_reqd_caps );
 196				if ( false !== $key ) {
 197					$tx = get_taxonomy( 'nav_menu' );
 198					$rs_reqd_caps[$key] = $tx->cap->manage_terms;
 199					
 200					$src_name = 'nav_menu';
 201					
 202					// menu-specific manager assignment does not permit deletion of the menu
 203					if ( ! empty( $_REQUEST['action'] ) && ( 'delete' == $_REQUEST['action'] ) )
 204						$this->skip_any_term_check = true;
 205				}
 206			}
 207		}
 208		
 209		if ( ! $src_name ) {
 210			// required capabilities correspond to multiple data sources
 211			return $wp_blogcaps;		
 212		}
 213		
 214		// slight simplification: assume a single cap object type for a few cap substitution checks
 215		$is_taxonomy_cap = $this->scoper->cap_defs->member_property( reset($rs_reqd_caps), 'is_taxonomy_cap' );
 216		
 217		// Establish some context by detecting object type - based on object ID if provided, or otherwise based on http variables.
 218		if ( in_array( $pagenow, array( 'media-upload.php', 'async-upload.php' ) ) ) {
 219			if ( ! empty($GLOBALS['post']) ) 
 220				$object_type = $GLOBALS['post']->post_type;	
 221		
 222		} elseif ( is_admin() && ( 'edit-tags.php' == $GLOBALS['pagenow'] ) && ( 'link_category' == $_REQUEST['taxonomy'] ) ) {
 223			$src_name = 'link';
 224			$object_type = 'link_category';
 225		} elseif ( array_key_exists(0, $orig_reqd_caps) && in_array( $orig_reqd_caps[0], array( 'manage_nav_menus', 'edit_theme_options' ) ) ) {
 226			$src_name = 'nav_menu';
 227		}
 228
 229		if ( empty($object_type) )
 230			$object_type = cr_find_object_type( $src_name, $object_id );
 231
 232		$object_type_obj = cr_get_type_object( $src_name, $object_type );
 233		
 234		$is_att_rev = false;
 235		if ( 'post' == $src_name ) {
 236			if ( in_array( $object_type, array( 'attachment', 'revision' ) ) ) {
 237				$is_att_rev = true;
 238			
 239				if ( $object_id ) {
 240					if ( $_post = get_post( $object_id ) ) {
 241						if ( $_parent = get_post($_post->post_parent) ) {
 242							$object_type = $_parent->post_type;
 243							$object_id = $_parent->ID;
 244							
 245							// deal with case of edit_posts cap check on attachments to revision (with Revisionary)
 246							if ( 'revision' == $object_type ) {
 247								if ( $_orig_post = get_post($_parent->post_parent) ) {
 248									$object_type = $_orig_post->post_type;
 249									$object_id = $_orig_post->ID;
 250								}
 251							}
 252							
 253							$object_type_obj = get_post_type_object( $object_type );
 254						}
 255					}
 256				}
 257			} elseif ( ! $is_taxonomy_cap ) {
 258				$use_post_types = scoper_get_option( 'use_post_types' );
 259				if ( empty( $use_post_types[$object_type] ) )
 260					return $wp_blogcaps;
 261			}
 262		}
 263		
 264		// =====================================================================================================================================
 265		
 266		// ======================================== SUBVERT MISGUIDED CAPABILITY REQUIREMENTS ==================================================
 267		if ( 'post' == $src_name ) {	
 268			if ( ! $is_taxonomy_cap ) {
 269				$modified_caps = false;
 270				
 271				if ( 'post' != $object_type ) {
 272					$replace_post_caps = array( 'publish_posts', 'edit_others_posts', 'edit_published_posts' );
 273					
 274					// 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
 275					if ( did_action( 'admin_init' ) ) {	// otherwise extra padding between menu items due to some items populated but unpermitted
 276						$replace_post_caps []= 'edit_posts';
 277					}
 278	
 279					if ( in_array( $pagenow, array( 'upload.php', 'media.php' ) ) ) {
 280						$replace_post_caps = array_merge( $replace_post_caps, array( 'delete_posts', 'delete_others_posts' ) );
 281					}
 282
 283					foreach( $replace_post_caps as $post_cap_name ) {
 284						$key = array_search( $post_cap_name, $rs_reqd_caps );
 285
 286						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' ) ) ) {				
 287							$rs_reqd_caps[$key] = $object_type_obj->cap->$post_cap_name;
 288							$modified_caps = true;
 289						}
 290					}
 291				}
 292				
 293				// 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.
 294				// (but don't mess if this cap requirement is part of an edit_post metacap check for a specific post)
 295				if ( ! $object_id && ( count($rs_reqd_caps) == 1 ) ) {
 296					if( in_array( reset($rs_reqd_caps), array( 'edit_others_posts' ) ) ) {
 297						require_once( dirname(__FILE__).'/lib/agapetry_wp_admin_lib.php' ); // function awp_metaboxes_started()
 298		
 299						if ( ! awp_metaboxes_started( $object_type ) && ( 'revision.php' != $pagenow ) && ( 'revisions' != $GLOBALS['plugin_page_cr'] ) ) // don't enable contributors to view/restore revisions
 300							$rs_reqd_caps[0] = $object_type_obj->cap->edit_posts;   // don't enable contributors to view/restore revisions
 301						else
 302							$rs_reqd_caps[0] = $object_type_obj->cap->edit_published_posts;	// we will filter / suppress the author dropdown downstream from here
 303						
 304						$modified_caps = true;
 305					}
 306				}
 307				
 308				// 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)
 309				if ( is_admin() && strpos( $_SERVER['SCRIPT_NAME'], 'nav-menus.php' ) ) {
 310					if ( 'edit_posts' == $orig_reqd_caps[0] ) {
 311						$type_obj = get_taxonomy( 'nav_menu' );
 312						$rs_reqd_caps[0] = $type_obj->cap->manage_terms;
 313						$modified_caps = true;
 314					}
 315				}
 316				
 317			} // endif not taxonomy cap
 318		} // endif caps correspond to 'post' data source
 319		//====================================== (end subvert misguided capability requirements) =============================================
 320		
 321		if ( defined( 'RVY_VERSION' ) ) {
 322			require_once( dirname(__FILE__).'/revisionary-helper_rs.php' );
 323			$rs_reqd_caps = Rvy_Helper::convert_post_edit_caps( $rs_reqd_caps, $object_type );
 324		}
 325
 326		//rs_errlog( "matched context for $object_id : $matched_context" );
 327		
 328		// don't apply object-specific filtering for auto-drafts
 329		if ( 'post' == $src_name ) {
 330			if ( $object_id ) {
 331				if ( $_post = get_post($object_id) ) {
 332					if ( ( 'auto-draft' == $_post->post_status ) ) { // && ! empty($_POST['action']) )
 333						$object_id = 0;
 334						
 335						if ( ! $doing_admin_menus )
 336							$this->skip_id_generation = true;
 337					}
 338				}
 339			} else {
 340				if ( ! empty($GLOBALS['post']) && ! is_object($GLOBALS['post']) )
 341					$GLOBALS['post'] = get_post($GLOBALS['post']);
 342				
 343				if ( ! empty( $GLOBALS['post'] ) && ( 'auto-draft' == $GLOBALS['post']->post_status ) && ! $doing_admin_menus )
 344					$this->skip_id_generation = true;
 345			}
 346		}
 347		
 348		//dump($object_id);
 349		
 350		// If no object id was passed in...
 351		if ( ! $object_id ) { // || ! $matched_context ) {
 352		//if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) ) {
 353			if ( ! $doing_admin_menus ) {
 354				if ( ! empty($_REQUEST['action']) && in_array( $pagenow, array('edit.php','edit-tags.php') ) )
 355					$this->skip_id_generation = true;
 356
 357				// ============================================ OBJECT ID DETERMINATION ========================================
 358				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
 359					// Try to generate missing object_id argument for problematic current_user_can calls 
 360					static $generated_id;
 361					
 362					if ( ! isset( $generated_id ) )
 363						$generated_id = array();
 364	
 365					// if the id was not already detected and stored to the static variable...
 366					$caps_key = serialize($rs_reqd_caps);
 367					if ( ! isset( $generated_id[$object_type][$caps_key] ) ) {
 368						$gen_id = 0;
 369	
 370						foreach( $rs_reqd_caps as $cap_name ) {
 371							if ( $gen_id = (int) $this->_detect_object_id( $cap_name ) ) {
 372								break;	// means we are accepting the generated id
 373							}
 374						}
 375	
 376						$generated_id[$object_type][$caps_key] = $gen_id;
 377						$object_id = $gen_id;
 378					} else
 379						$object_id = $generated_id[$object_type][$caps_key];
 380	
 381					//rs_errlog( "detected ID: $object_id" );
 382				} else
 383					$this->skip_id_generation = false; // this is a one-time flag
 384
 385				// ========================================= (end object id determination) =======================================
 386			}
 387			
 388			// If we still have no object id (detection was skipped or failed to identify it)...
 389			if ( ! $object_id ) { // || ! $matched_context ) {
 390				// ============================================ "CAN FOR ANY" CHECKS ===========================================
 391				if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) ) {
 392					// 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.
 393					
 394					// If we are about to fail the blogcap requirement, credit a missing cap if the user has it by term role for ANY term.
 395					// This prevents failing initial UI entrance exams that only consider blog-wide roles.
 396					if ( ! $this->skip_any_term_check ) {
 397						if ( $tax_caps = $this->user_can_for_any_term($missing_caps) )
 398							$wp_blogcaps = array_merge($wp_blogcaps, $tax_caps);
 399
 400						//rs_errlog( "can for any term: " . serialize($tax_caps) );
 401					} else
 402						$this->skip_any_term_check = false;  // this is a one-time flag
 403
 404					// If we are still missing required caps, credit a missing scoper-defined cap if the user has it by object role for ANY object.
 405					// (i.e. don't bar user from "Edit Pages" if they have edit_pages cap for at least one page)
 406					if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) ) {
 407						// prevent object-specific editing roles from allowing new object creation w/o sitewide capability
 408						$add_new_check = strpos( $_SERVER['SCRIPT_NAME'], 'post-new.php' ) && ( 'post' == $src_name ) && ( reset( $rs_reqd_caps ) == $object_type_obj->cap->edit_posts );
 409
 410						if ( ( ! $this->skip_any_object_check ) && ! $add_new_check ) {
 411						//if ( ! $this->skip_any_object_check ) {
 412							if ( $object_caps = $this->user_can_for_any_object( $missing_caps ) )
 413								$wp_blogcaps = array_merge($wp_blogcaps, $object_caps);
 414								
 415							//rs_errlog( "can for any object: " . serialize($object_caps) );
 416						} else
 417							$this->skip_any_object_check = false;  // this is a one-time flag
 418					}
 419				}
 420				// ========================================== (end "can for any" checks ) =========================================
 421				
 422				//rs_errlog( serialize( $wp_blogcaps) );
 423				
 424				if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) )
 425					// normal exit point when no object ID is passed or detected, or when detected object type does not match required capabilities
 426					return $wp_blogcaps;
 427				else {
 428					if ( $restore_caps = array_diff( $orig_reqd_caps, $rs_reqd_caps ) )  // restore original reqd_caps which we substituted for the type-specific scoped query
 429						$wp_blogcaps = array_merge( $wp_blogcaps, array_fill_keys($restore_caps, true) );
 430
 431					return $wp_blogcaps;
 432				}	
 433			}
 434		//} else
 435			//return $wp_blogcaps;
 436			
 437		} 
 438
 439		if ( $object_id && ( 'post' == $src_name ) ) {
 440			$_post = get_post($object_id);
 441			$object_type = $_post->post_type;
 442			$object_type_obj = cr_get_type_object( $src_name, $object_type );
 443			
 444			if ( defined('RVY_VERSION') && in_array( $pagenow, array('edit.php', 'edit-tags.php', 'admin-ajax.php') ) && ( ! empty($_REQUEST['action']) && ( -1 != $_REQUEST['action'] ) ) ) {
 445				$rs_reqd_caps = Rvy_Helper::fix_table_edit_reqd_caps( $rs_reqd_caps, $args[0], $_post, $object_type_obj );
 446			}
 447
 448			// if the top level page structure is locked, don't allow non-administrator to delete a top level page either
 449			if ( ( 'page' == $object_type ) || defined( 'SCOPER_LOCK_OPTION_ALL_TYPES' ) && ! is_content_administrator_rs() ) {
 450				$delete_metacap = ( ! empty($object_type_obj->hierarchical) ) ? $object_type_obj->cap->delete_post : 'delete_page';
 451
 452				// if the top level page structure is locked, don't allow non-administrator to delete a top level page either
 453				if ( $delete_metacap == $args[0] ) {
 454					if ( '1' === scoper_get_option( 'lock_top_pages' ) ) {	  // stored value of 1 means only Administrators are allowed to modify top-level page structure
 455						if ( $page = get_post( $args[2] ) ) {
 456							if ( empty( $page->post_parent ) ) {
 457								$in_process = false;
 458								return false;
 459							}
 460						}
 461					}
 462				}
 463			}
 464		}
 465
 466		// Note: At this point, we have a nonzero object_id...
 467		
 468		// if this is a term administration request, route to user_can_admin_terms()
 469		if ( $is_taxonomy_cap ) {
 470			if ( 'post' == $src_name )
 471				$cap_otype_obj = get_taxonomy( $object_type );
 472
 473			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
 474				// always pass through any assigned blog caps which will not be involved in this filtering
 475				$rs_reqd_caps = array_fill_keys( $rs_reqd_caps, 1 );
 476				$undefined_reqd_caps = array_diff_key( $wp_blogcaps, $rs_reqd_caps);
 477
 478				require_once( dirname(__FILE__).'/admin/permission_lib_rs.php' );
 479				if ( user_can_admin_terms_rs( $object_type, $object_id, $user) ) {
 480					return array_merge($undefined_reqd_caps, $rs_reqd_caps);
 481				} else {
 482					return $undefined_reqd_caps;	// required caps we scrutinized are excluded from this array
 483				}
 484			}
 485		}
 486		
 487		// Workaround to deal with WP core's checking of publish cap prior to storing categories
 488		// Store terms to DB in advance of any cap-checking query which may use those terms to qualify an operation		
 489		if ( ! empty($_REQUEST['action']) && ( in_array( $_REQUEST['action'], array( 'editpost', 'post' ) ) || ('autosave' == $_REQUEST['action']) ) ) {
 490			if ( array_intersect( array( 'publish_posts', 'edit_posts', $object_type_obj->cap->publish_posts,  $object_type_obj->cap->edit_posts ), $rs_reqd_caps) ) {
 491				$uses_taxonomies = scoper_get_taxonomy_usage( $src_name, $object_type );
 492				
 493				static $inserted_terms;
 494				if ( ! isset( $inserted_terms ) )
 495					$inserted_terms = array();
 496				
 497				foreach ( $uses_taxonomies as $taxonomy ) { 	// TODO: only if tx->requires_term is true?
 498					if ( isset( $inserted_terms[$taxonomy][$object_id] ) )
 499						continue;
 500
 501					$inserted_terms[$taxonomy][$object_id] = true;
 502
 503					//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
 504					//	continue;
 505
 506					$stored_terms = $this->scoper->get_terms($taxonomy, UNFILTERED_RS, COL_ID_RS, $object_id);
 507					
 508					require_once( dirname(__FILE__).'/admin/filters-admin-save_rs.php' );
 509					$selected_terms = cr_get_posted_object_terms( $taxonomy );
 510
 511					if ( is_array($selected_terms) ) { // non-hierarchical terms do not need to be pre-inserted
 512						if ( $set_terms = $GLOBALS['scoper_admin_filters']->flt_pre_object_terms($selected_terms, $taxonomy) ) {
 513							$set_terms = array_unique( array_map('intval', $set_terms) );
 514
 515							if ( ( $set_terms != $stored_terms ) && $set_terms && ( $set_terms != array(1) ) ) { // safeguard against unintended clearing of stored categories
 516								wp_set_object_terms( $object_id, $set_terms, $taxonomy );
 517
 518								// delete any buffered cap check results which were queried prior to storage of these object terms
 519								unset( $cache_tested_ids );
 520								unset( $cache_where_clause );
 521								unset( $cache_okay_ids );
 522							}
 523						}
 524					}
 525				}
 526				
 527				// also avoid chicken-egg situation when publish cap is granted by a propagating page role
 528				if ( $object_type_obj->hierarchical && isset( $_POST['parent_id'] ) ) {
 529					if ( $_POST['parent_id'] != get_post_field( 'post_parent', $object_id ) ) {
 530						global $wpdb;
 531						$set_parent = $GLOBALS['scoper_admin_filters']->flt_page_parent( $_POST['parent_id'] );
 532						$GLOBALS['wpdb']->query( "UPDATE $wpdb->posts SET post_parent = '$set_parent' WHERE ID = '$object_id'" );
 533						
 534						require_once( dirname(__FILE__).'/admin/filters-admin-save_rs.php' );
 535						scoper_inherit_parent_roles($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
 536						scoper_inherit_parent_restrictions($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
 537					}
 538				}
 539			}
 540		}
 541
 542		// generate a string key for this set of required caps, for use below in checking, caching the scoped results
 543		$arg_append = '';
 544		$arg_append .= ( ! empty( $this->require_full_object_role ) ) ? '-require_full_object_role-' : '';
 545		$arg_append .= ( ! empty( $GLOBALS['revisionary']->skip_revision_allowance ) ) ? '-skip_revision_allowance-' : '';
 546
 547		sort($rs_reqd_caps);
 548		$capreqs_key = implode($rs_reqd_caps) . $arg_append;  // see ScoperAdmin::user_can_admin_object
 549		
 550	
 551		// ================================ SPECIAL HANDLING FOR ATTACHMENTS AND REVISIONS ==========================================
 552		$maybe_revision = ( 'post' == $src_name && ! isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]) );
 553
 554		$maybe_attachment = in_array( $pagenow, array( 'upload.php', 'media.php' ) );
 555
 556		if ( $maybe_revision || $maybe_attachment ) {
 557			global $wpdb;
 558	
 559			if ( $_post = get_post($object_id) ) {
 560				if ( 'revision' == $_post->post_type ) {
 561					require_once( dirname(__FILE__).'/lib/revisions_lib_rs.php' );
 562					
 563					$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
 564					$revisions = rs_get_post_revisions($_post->post_parent, 'inherit', array( 'fields' => constant('COL_ID_RS'), 'return_flipped' => true, 'where' => $rev_where ) );						
 565				}
 566
 567				if ( ( 'revision' == $_post->post_type ) || ( 'attachment' == $_post->post_type ) ) {
 568					$is_att_rev = true;
 569				
 570					if ( $_post->post_parent ) {
 571						$object_id = $_post->post_parent;
 572
 573						if ( $_parent = get_post($_post->post_parent) ) {
 574							$object_type = $_parent->post_type;
 575							$object_type_obj = get_post_type_object( $object_type );
 576						}
 577					} elseif ( 'attachment' == $_post->post_type ) {
 578						// special case for unattached uploads: uploading user should have their way with them
 579						if ( $_post->post_author == $current_rs_user->ID ) {
 580							$rs_reqd_caps[0] = 'read';
 581
 582							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
 583								$wp_blogcaps = array_merge( $wp_blogcaps, array_fill_keys($restore_caps, true) );
 584						}
 585
 586						return $wp_blogcaps;
 587					}
 588				} //endif retrieved post is a revision or attachment
 589			} // endif post retrieved
 590		} // endif specified id might be a revision or attachment
 591		
 592		if ( $is_att_rev ) {
 593			if ( 'post' != $object_type_obj->name ) {
 594				// Compensate for WP's requirement of posts cap for attachment editing, regardless of whether it's attached to a post or page							
 595				if ( 'edit_others_posts' == $rs_reqd_caps[0] )
 596					$rs_reqd_caps[0] = $object_type_obj->cap->edit_others_posts;
 597					
 598				elseif ( 'delete_others_posts' == $rs_reqd_caps[0] )
 599					$rs_reqd_caps[0] = $object_type_obj->cap->delete_others_posts;
 600					
 601				elseif ( 'edit_posts' == $rs_reqd_caps[0] )
 602					$rs_reqd_caps[0] = $object_type_obj->cap->edit_posts;
 603					
 604				elseif ( 'delete_posts' == $rs_reqd_caps[0] )
 605					$rs_reqd_caps[0] = $object_type_obj->cap->delete_posts;
 606			}
 607		} //endif retrieved post is a revision or attachment
 608		
 609		
 610		// ============================== (end special handling for attachments and revisions) ==========================================
 611		
 612	
 613		// ============ SCOPED QUERY for required caps on object id (if other listed ids are known, query for them also).  Cache results to static var. ===============
 614		
 615		// $force_refresh = 'async-upload.php' == $pagenow;
 616		
 617		// Page refresh following publishing of new page by users who can edit by way of Term Role fails without this workaround
 618		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 ) ) ) ) {
 619			$force_refresh = true;
 620			$cache_tested_ids = array();
 621			$cache_okay_ids = array();
 622			$cache_where_clause = array();
 623		} else
 624			$force_refresh = false;
 625
 626		// 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
 627		if ( $force_refresh || ! isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]) ) {
 628		//if ( ! isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]) ) {
 629	
 630			// retrieve CR_Data_Source object, which contains database column names
 631			$src_table = $this->scoper->data_sources->member_property($src_name, 'table');
 632			$cols = $this->scoper->data_sources->member_property($src_name, 'cols');
 633			
 634			// Before querying for caps on this object, check whether we have a record of other posts listed alongside it.  
 635			// If so, run the scoped query for ALL listed objects in that buffer, and buffer the results to static variable hascap_object_ids. 
 636			//
 637			// (This is useful when front end code must check caps for each post 
 638			//  to determine whether to display 'edit' link, etc.)
 639			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.
 640				$listed_ids = array();
 641			} else {
 642				if ( isset($this->scoper->listed_ids[$src_name]) )
 643					$listed_ids = array_keys($this->scoper->listed_ids[$src_name]);
 644				else // note: don't use wp_object_cache because it includes posts not present in currently displayed resultset listing page
 645					$listed_ids = array();
 646			}
 647			
 648			// make sure our current object_id is in the list
 649			$listed_ids[] = $object_id;
 650
 651			// since the objects_where_role_clauses() output itself is not id-specific, also statically buffer it per reqd_caps
 652			if ( $force_refresh || ! isset( $cache_where_clause[$src_name][$object_type][$capreqs_key] ) ) {
 653				$check_otype = ( 'link_category' == $object_type ) ? 'link' : $object_type;
 654				$use_term_roles = scoper_get_otype_option( 'use_term_roles', $src_name, $check_otype );
 655
 656				$no_object_roles = $this->scoper->data_sources->member_property($src_name, 'no_object_roles');
 657				$use_object_roles = ( $no_object_roles ) ? false : scoper_get_otype_option( 'use_object_roles', $src_name, $object_type );
 658	
 659				$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) );
 660
 661				//rs_errlog( serialize($rs_reqd_caps) );
 662				//rs_errlog( serialize($this_args) );
 663				
 664				$where = $this->query_interceptor->objects_where_role_clauses($src_name, $rs_reqd_caps, $this_args );
 665				
 666				if ( $where )
 667					$where = "AND ( $where )";
 668					
 669				// update static variable
 670				$cache_where_clause[$src_name][$object_type][$capreqs_key] = $where;
 671			} else
 672				$where = $cache_where_clause[$src_name][$object_type][$capreqs_key];
 673				
 674			// run the query
 675			$query = "SELECT $src_table.{$cols->id} FROM $src_table WHERE 1=1 $where AND $src_table.{$cols->id} IN ('" . implode( "', '", array_unique($listed_ids) ) . "')";
 676			
 677			if ( isset( $cache_okay_ids[$query] ) )
 678				$okay_ids = $cache_okay_ids[$query];
 679			else {
 680				if ( $okay_ids = scoper_get_col($query) )
 681					$okay_ids = array_fill_keys($okay_ids, true);
 682			}
 683
 684			//dump($rs_reqd_caps);
 685			//dump($query);
 686			//dump($okay_ids);
 687
 688			//rs_errlog( $query );
 689			//rs_errlog( 'results: ' . serialize( $okay_ids ) );
 690				
 691			// update static cache_tested_ids to log scoped results for this object id, and possibly also for other listed IDs
 692			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
 693				foreach ( $listed_ids as $_id )
 694					$cache_tested_ids[$src_name][$object_type][$capreqs_key][$_id] = isset( $okay_ids[$_id] );
 695					
 696				$cache_okay_ids[$query] = $okay_ids;
 697			}
 698			
 699			$this_id_okay = isset( $okay_ids[$object_id] );
 700		} else {
 701			 // results of this same has_cap inquiry are already stored (from another call within current http request)
 702			$this_id_okay = $cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id];
 703		}
 704
 705		//rs_errlog( "okay ids: " . serialize( $okay_ids ) );
 706		
 707		// if we redirected the cap check to revision parent, also credit all the revisions for passing results
 708		if ( $this_id_okay && ! empty($revisions) ) {
 709			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
 710				$cache_tested_ids[$src_name][$object_type][$capreqs_key] = $cache_tested_ids[$src_name][$object_type][$capreqs_key] + array_fill_keys( $revisions, true );
 711		}
 712		
 713		$rs_reqd_caps = array_fill_keys( $rs_reqd_caps, true );
 714		
 715		if ( ! $this_id_okay ) {
 716			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
 717				return $wp_blogcaps;
 718			}
 719
 720			// ================= TEMPORARY DEBUG CODE ===================
 721			//d_echo("object_id $object_id FAILED !!!!!!!!!!!!!!!!!" );
 722
 723			//rs_errlog( "object_id $object_id FAILED !!!!!!!!!!!!!!!!!"  );
 724			//rs_errlog(serialize($orig_reqd_caps));
 725			//rs_errlog(serialize($rs_reqd_caps));
 726			//rs_errlog('');
 727
 728			/*
 729			$log .= "checked caps: " . serialize($rs_reqd_caps) . "\r\n";
 730			$log .= "object_id $object_id FAILED !!!!!!!!!!!!!!!!!\r\n";
 731			$log .= $query;
 732			rs_errlog( "\r\n{$log}\r\n" );
 733			*/
 734			//d_echo( "FAILED for " . serialize($rs_reqd_caps) );
 735			// ============== (end temporary debug code ==================
 736
 737			return array_diff_key( $wp_blogcaps, $rs_reqd_caps);	// required caps we scrutinized are excluded from this array
 738		} else {
 739			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
 740				$rs_reqd_caps = $rs_reqd_caps + array_fill_keys($restore_caps, true);
 741				
 742			//d_echo( 'OKAY:' );
 743			//dump($args);
 744			//dump($rs_reqd_caps);
 745			//d_echo( '<br />' );
 746			
 747			return array_merge($wp_blogcaps, $rs_reqd_caps);
 748		}
 749	}
 750	
 751	
 752	// Try to generate missing has_cap object_id arguments for problematic caps
 753	// Ideally, this would be rendered unnecessary by updated current_user_can calls in WP core or other offenders
 754	function _detect_object_id( $required_cap ) {
 755		if ( has_filter( 'detect_object_id_rs' ) ) { // currently not used internally 
 756			if ( $object_id = apply_filters( 'detect_object_id_rs', 0, $required_cap ) )
 757				return $object_id;
 758		}
 759		
 760		if ( $this->scoper->cap_defs->member_property( $required_cap, 'is_taxonomy_cap' ) ) {
 761			if ( ! empty($_REQUEST['tag_ID']) )
 762				return $_REQUEST['tag_ID'];
 763		}
 764		
 765		global $pagenow;
 766		if ( in_array( $pagenow, array( 'media-upload.php', 'async-upload.php' ) ) ) {
 767			if ( ! empty($_POST['post_ID']) )
 768				return $_POST['post_ID'];
 769			elseif ( ! empty($_REQUEST['post_id']) )
 770				return $_REQUEST['post_id'];
 771			elseif ( ! empty($_REQUEST['attachment_id']) ) {
 772				if ( $attachment = get_post( $_REQUEST['attachment_id'] ) )
 773					return $attachment->post_parent;
 774			}
 775		}
 776		
 777		if ( ! $src_name = $this->scoper->cap_defs->member_property( $required_cap, 'src_name' ) )
 778			return;
 779		
 780		if ( ! empty( $_POST ) ) {
 781			// special case for comment post ID
 782			if ( ! empty( $_POST['comment_post_ID'] ) )
 783				$_POST['post_ID'] = $_POST['comment_post_ID'];
 784				
 785			// WP core edit_post function requires edit_published_posts or edit_published_pages cap to save a post to "publish" status, but does not pass a post ID
 786			// Similar situation with edit_others_posts, publish_posts.
 787			// So... insert the object ID from POST vars
 788			if ( 'post' == $src_name ) {
 789				if ( ! $id = $this->scoper->data_sources->get_from_http_post('id', 'post') ) {
 790					
 791					if ( 'async-upload.php' != $GLOBALS['pagenow'] ) {
 792						if ( $attach_id = $this->scoper->data_sources->get_from_http_post('attachment_id', 'post') ) {
 793							if ( $attach_id ) {
 794								global $wpdb;
 795								$id = scoper_get_var( "SELECT post_parent FROM $wpdb->posts WHERE post_type = 'attachment' AND ID = '$attach_id'" );
 796								if ( $id > 0 )
 797									return $id;
 798							}
 799						}
 800					} elseif ( ! $id && ! empty($_POST['id']) ) // in case normal POST variable differs from ajax variable
 801						$id = $_POST['id'];
 802				}
 803			}
 804
 805			/* on the moderation page, admin-ajax tests for moderate_comments without passing any ID */
 806			if ( 'moderate_comments' == $required_cap )
 807				if ( $comment = get_comment( $id ) )
 808					return $comment->comment_post_ID;
 809			
 810			if ( ! empty($id) )
 811				return $id;
 812				
 813			// special case for adding categories
 814			if ( 'manage_categories' == $required_cap ) {
 815				if ( ! empty($_POST['newcat_parent']) )
 816					return $_POST['newcat_parent'];
 817				elseif ( ! empty($_POST['category_parent']) )
 818					return $_POST['category_parent'];
 819			}
 820			
 821		} elseif ( defined('XMLRPC_REQUEST') ) {
 822			if ( ! empty($GLOBALS['xmlrpc_post_id_rs']) )
 823				return $GLOBALS['xmlrpc_post_id_rs'];
 824		} else {
 825			//rs_errlog("checking uri for source $src_name");
 826			return $this->scoper->data_sources->get_from_uri('id', $src_name);
 827		}
 828	}
 829	
 830	
 831	// Some users with term or object roles are now able to view and edit certain 
 832	// content, if only the unscoped core would let them in the door.  For example, you can't 
 833	// load edit-pages.php unless current_user_can('edit_pages') blog-wide.
 834	//
 835	// This policy is sensible for unscoped users, as it hides stuff they can't have.
 836	// But it is needlessly oppressive to those who walk according to the law of the scoping. 
 837	// Subvert the all-or-nothing paradigm by reporting a blog-wide cap if the user has 
 838	// the capability for any taxonomy.
 839	//
 840	// Due to subsequent query filtering, this does not unlock additional content blog-wide.  
 841	// It merely enables us to run all pertinent content through our gauntlet (rather than having 
 842	// some contestants disqualified before we arrive at the judging stand).
 843	//
 844	// A happy side effect is that, in a fully scoped blog, all non-administrator users can be set
 845	// to "Subscriber" blogrole so the failure state upon accidental Role Scoper disabling 
 846	// is overly narrow access, not overly open.
 847	function user_can_for_any_term($reqd_caps, $user = '') {
 848		if ( ! is_object($user) ) {
 849			$user = $GLOBALS['current_rs_user'];
 850		}
 851		
 852		// Instead of just intersecting the missing reqd_caps with termcaps from all term_roles,
 853		// require each subset of caps with matching src_name, object type and op_type to 
 854		// all be satisfied by the same role (any assigned term role).  This simulates flt_objects_where behavior (which does so to support role restrictions.
 855		
 856		$grant_caps = array();
 857
 858		$caps_by_otype = $this->scoper->cap_defs->organize_caps_by_otype($reqd_caps);
 859		
 860		// temp workaround
 861		if ( 'manage_categories' == current($reqd_caps) && isset( $caps_by_otype['post']['link'] ) ) {
 862			$caps_by_otype['link']['link_category'] = $caps_by_otype['post']['link'];
 863			unset( $caps_by_otype['post']['link'] );
 864		}
 865		
 866		foreach ( $caps_by_otype as $src_name => $otypes ) {
 867			$object_types = $this->scoper->data_sources->member_property($src_name, 'object_types');
 868		
 869			// deal with upload_files and other capabilities which have no specific object type
 870			if ( ! array_diff_key( $otypes, array( '' => true ) ) ) {
 871				foreach( array_keys( $object_types ) as $_object_type )
 872					$otypes[$_object_type] = $otypes[''];
 873				
 874				unset( $otypes[''] );
 875			}
 876
 877			$uses_taxonomies = scoper_get_taxonomy_usage( $src_name, array_keys($otypes) );
 878			
 879			// this ensures we don't credit term roles on custom taxonomies which have been disabled
 880			if ( ! $uses_taxonomies = array_intersect( $uses_taxonomies, $this->scoper->taxonomies->get_all_keys() ) )
 881				continue;
 882
 883			foreach ( $otypes as $this_otype_caps ) { // keyed by object_type
 884				$caps_by_op = $this->scoper->cap_defs->organize_caps_by_op( (array) $this_otype_caps);
 885				
 886				foreach ( $caps_by_op as $this_op_caps ) { // keyed by op_type
 887					$roles = $this->scoper->role_defs->qualify_roles($this_op_caps);
 888					
 889					foreach ( $uses_taxonomies as $taxonomy ) {
 890						if ( ! isset($user->term_roles[$taxonomy]) )
 891							$user->term_roles[$taxonomy] = $user->get_term_roles_daterange($taxonomy);				// call daterange function populate term_roles property - possible perf enhancement for subsequent code even though we don't conider content_date-limited roles here
 892
 893						if ( array_intersect_key($roles, agp_array_flatten( $user->term_roles[$taxonomy], false ) ) ) {	// okay to include all content date ranges because can_for_any_term checks are only preliminary measures to keep the admin UI open
 894							$grant_caps = array_merge($grant_caps, $this_op_caps);
 895							break;
 896						}
 897					}
 898				}
 899			}
 900		}	
 901		
 902		if ( $grant_caps )
 903			return array_fill_keys( array_unique($grant_caps), true);
 904		else
 905			return array();
 906	}
 907	
 908	// used by flt_user_has_cap prior to failing blogcaps requirement
 909	// Note that this is not to be called if an object_id was provided to (or detected by) flt_user_has_cap
 910	// This is primarily a way to ram open a closed gate prior to selectively re-closing it ourself
 911	function user_can_for_any_object($reqd_caps, $user = '') {
 912		if ( ! empty( $this->ignore_object_roles ) ) {
 913			// use this to force cap via blog/term role for Write Menu item
 914			$this->ignore_object_roles = false;
 915			return array();
 916		}
 917
 918		if ( ! is_object($user) ) {
 919			$user = $GLOBALS['current_rs_user'];
 920		}
 921	
 922		if ( $roles = $this->scoper->qualify_object_roles( $reqd_caps, '', $user, true ) )  // arg: convert 'edit_others', etc. to equivalent owner base cap
 923			return array_fill_keys($reqd_caps, true);
 924
 925		return array();
 926	}
 927} // end class Cap_Interceptor_RS
 928
 929// equivalent to current_user_can, 
 930// except it supports array of reqd_caps, supports non-current user, and does not support numeric reqd_caps
 931function _cr_user_can( $reqd_caps, $object_id = 0, $user_id = 0, $meta_flags = array() ) {	
 932	// $meta_flags currently used for 'skip_revision_allowance', 'skip_any_object_check', 'skip_any_term_check', 'skip_id_generation', 'require_full_object_role'
 933	// For now, skip array_merge with defaults, for perf
 934	if ( $user_id )
 935		$user = new WP_User($user_id);  // don't need Scoped_User because only using allcaps property (which contain WP blogcaps).  flt_user_has_cap will instantiate new WP_Scoped_User based on the user_id we pass
 936	else
 937		$user = wp_get_current_user();
 938	
 939	if ( empty($user) )
 940		return false;
 941
 942	$reqd_caps = (array) $reqd_caps;
 943	$check_caps = $reqd_caps;
 944	foreach ( $check_caps as $cap_name ) {
 945		if ( $meta_caps = map_meta_cap($cap_name, $user->ID, $object_id) ) {
 946			$reqd_caps = array_diff( $reqd_caps, array($cap_name) );
 947			$reqd_caps = array_unique( array_merge( $reqd_caps, $meta_caps ) );
 948		}
 949	}
 950
 951	if ( 'blog' === $object_id ) { // legacy API support
 952		$meta_flags['skip_any_object_check'] = true;
 953		$meta_flags['skip_any_term_check'] = true;
 954		$meta_flags['skip_id_generation'] = true;
 955	} elseif ( ! $object_id ) {
 956		$meta_flags['skip_id_generation'] = true;	
 957	}
 958
 959	if ( $meta_flags ) {
 960		// handle special case revisionary flag
 961		if ( ! empty($meta_flags['skip_revision_allowance']) ) {
 962			if ( defined( 'RVY_VERSION' ) ) {
 963				global $revisionary;
 964				$revisionary->skip_revision_allowance = true;	// this will affect the behavior of Role Scoper's user_has_cap filter
 965			}
 966			
 967			unset( $meta_flags['skip_revision_allowance'] );	// no need to set this flag on cap_interceptor
 968		}
 969	
 970		// set temporary flags for use by our user_has_cap filter
 971		global $cap_interceptor;
 972		if ( isset($cap_interceptor) ) {
 973			foreach( $meta_flags as $flag => $value )
 974				$cap_interceptor->$flag = $value;
 975		} else
 976			$meta_flags = array();
 977	}
 978	
 979	$capabilities = apply_filters('user_has_cap', $user->allcaps, $reqd_caps, array( $reqd_caps, $user->ID, $object_id ) );
 980
 981	if ( $meta_flags ) {
 982		// clear temporary flags
 983		foreach( $meta_flags as $flag => $value )
 984			$cap_interceptor->$flag = false;
 985	}
 986	
 987	if ( ! empty($revisionary) )
 988		$revisionary->skip_revision_allowance = false;
 989
 990	foreach ( $reqd_caps as $cap_name ) {
 991		if( empty($capabilities[$cap_name]) || ! $capabilities[$cap_name] ) {
 992			// if we're about to fail due to a missing create_child_pages cap, honor edit_pages cap as equivalent
 993			// TODO: abstract this with cap_defs property
 994			if ( 'create_child_pages' == $cap_name ) {
 995				$alternate_cap_name = 'edit_pages';
 996				$_args = array( array($alternate_cap_name), $user->ID, $object_id );
 997				$capabilities = apply_filters('user_has_cap', $user->allcaps, array($alternate_cap_name), $_args);
 998				
 999				if ( empty($capabilities[$alternate_cap_name]) || ! $capabilities[$alternate_cap_name] )
1000					return false;
1001			} else
1002				return false;
1003		}
1004	}
1005
1006	return true;
1007}
1008
1009
1010?>