PageRenderTime 147ms CodeModel.GetById 78ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 1ms

/role-scoper/role-scoper_main.php

https://github.com/adityag2/suneha
PHP | 1105 lines | 737 code | 259 blank | 109 comment | 256 complexity | dede4713aaf221bcb4f3077d5d1c1683 MD5 | raw file
   1<?php
   2if( basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) )
   3	die( 'This page cannot be called directly.' );
   4	
   5/**
   6 * Scoper PHP class for the WordPress plugin Role Scoper
   7 * role-scoper_main.php
   8 * 
   9 * @author 		Kevin Behrens
  10 * @copyright 	Copyright 2012
  11 * 
  12 */
  13class Scoper
  14{
  15	var $definitions;
  16	var $access_types;
  17	var $data_sources;
  18	var $taxonomies;
  19	var $cap_defs;
  20	var $role_defs;
  21	
  22	var $cap_interceptor;		// legacy API
  23	
  24	// === Temporary status variables ===
  25	var $direct_file_access;
  26	var $listed_ids = array();  // $listed_ids[src_name][object_id] = true : general purpose memory cache for non-post data sources; primary use is with has_cap filter to avoid a separate db query for each listed item 
  27
  28	var $default_restrictions = array();
  29
  30	// minimal config retrieval to support pre-init usage by WP_Scoped_User before text domain is loaded
  31	function Scoper() {
  32		$this->definitions = array( 'data_sources' => 'Data_Sources', 'taxonomies' => 'Taxonomies', 'cap_defs' => 'Capabilities', 'role_defs' => 'Roles' );	
  33		require_once( dirname(__FILE__).'/definitions_cr.php' );
  34		
  35		if ( defined( 'RVY_VERSION' ) )
  36			$this->cap_interceptor = (object) array();	// legacy support for Revisionary < 1.1 which set flags on this object property
  37	}
  38	
  39	function load_config() {
  40		require_once( dirname(__FILE__).'/lib/agapetry_config_items.php');
  41		$this->access_types = new AGP_Config_Items();
  42		$this->access_types->init( cr_access_types() );  // 'front' and 'admin' are the hardcoded access types
  43		
  44		// establish access type for this http request
  45		$access_name = ( is_admin() || defined('XMLRPC_REQUEST') ) ? 'admin' : 'front';
  46		$access_name = apply_filters( 'scoper_access_name', $access_name );		// others plugins can apply additional criteria for treating a particular URL with wp-admin or front-end filtering
  47		if ( ! defined('CURRENT_ACCESS_NAME_RS') )
  48			define('CURRENT_ACCESS_NAME_RS', $access_name);
  49
  50		// disable RS filtering of access type(s) if specified in realm options 
  51		if ( ! is_admin() || ! defined('SCOPER_REALM_ADMIN_RS') ) {		// don't remove items if the option is being editied
  52			if ( $disabled_access_types = scoper_get_option('disabled_access_types') )
  53				$this->access_types->remove_members_by_key($disabled_access_types, true);
  54				
  55			// If the detected access type (admin, front or custom) was "disabled", it is still detected, but we note that query filters should not be applied
  56			if ( ! $this->access_types->is_member($access_name) )
  57				define('DISABLE_QUERYFILTERS_RS', true);
  58		}
  59		
  60		// populate data_sources, taxonomies, cap_defs, role_defs arrays
  61		foreach( array_keys($this->definitions) as $topic )
  62			$this->load_definition( $topic );	
  63			
  64		foreach( array_keys($this->definitions) as $topic )
  65			$this->$topic->lock();
  66
  67		// clean up after 3rd party plugins (such as Role Scoping for NGG) which don't set object type and src_name properties for roles
  68		if ( has_filter( 'define_roles_rs' ) ) {
  69			require_once( dirname(__FILE__).'/extension-helper_rs.php' );
  70			scoper_adjust_legacy_extension_cfg( $this->role_defs, $this->cap_defs );
  71		}
  72		
  73		add_action( 'set_current_user', array( &$this, 'credit_blogroles' ) );
  74		
  75		$this->credit_blogroles();
  76
  77		do_action('config_loaded_rs');
  78	}
  79	
  80	function credit_blogroles() {
  81		// credit non-logged and "no role" users for any anonymous roles
  82		global $current_rs_user;
  83		
  84		if ( $current_rs_user ) {
  85			if ( empty($current_rs_user->ID) ) {
  86				foreach ( $this->role_defs->filter_keys( -1, array( 'anon_user_blogrole' => true ) ) as $role_handle) {
  87					$current_rs_user->assigned_blog_roles[ANY_CONTENT_DATE_RS][$role_handle] = true;
  88					$current_rs_user->blog_roles[ANY_CONTENT_DATE_RS][$role_handle] = true;
  89				}
  90			}
  91	
  92			if ( isset($current_rs_user->assigned_blog_roles) )
  93				$this->refresh_blogroles();
  94		}
  95	}
  96	
  97	function refresh_blogroles() {
  98		global $current_rs_user;
  99		
 100		if ( empty($current_rs_user) )
 101			return;
 102		
 103		$current_rs_user->merge_scoped_blogcaps();
 104		$GLOBALS['current_user']->allcaps = array_merge( $GLOBALS['current_user']->allcaps, $current_rs_user->allcaps );
 105		
 106		if ( empty($GLOBALS['current_user']->data) )
 107			$GLOBALS['current_user']->data = (object) array();
 108
 109		foreach( array( 'groups', 'blog_roles', 'assigned_blog_roles' ) as $var ) {
 110			if ( isset($current_rs_user->$var) )
 111				$GLOBALS['current_user']->$var = $current_rs_user->$var;
 112		}
 113
 114		if ( $current_rs_user->ID || defined( 'SCOPER_ANON_METAGROUP' ) ) {
 115			foreach ( array_keys($current_rs_user->assigned_blog_roles) as $date_key )
 116				$current_rs_user->blog_roles[$date_key] = $this->role_defs->add_contained_roles( $current_rs_user->assigned_blog_roles[$date_key] );
 117		}
 118	}
 119	
 120	function load_definition( $topic ) {
 121		$class_name = "CR_" . $this->definitions[$topic];
 122		require_once( strtolower($this->definitions[$topic]) . '_rs.php' );
 123
 124		$filter_name = "define_" . strtolower($this->definitions[$topic]) . "_rs";
 125		$this->$topic = apply_filters( $filter_name, new $class_name( call_user_func("cr_{$topic}") ) );
 126
 127		if ( 'role_defs' == $topic ) {
 128			$this->role_defs->role_caps = apply_filters('define_role_caps_rs', cr_role_caps() );
 129			
 130			if ( $user_role_caps = scoper_get_option( 'user_role_caps' ) )
 131				$this->role_defs->add_role_caps( $user_role_caps );
 132
 133			$this->log_cap_usage( $this->role_defs, $this->cap_defs );  // add any otype associations from new user_role_caps, but don't remove an otype association due to disabled_role_caps
 134
 135			if ( $disabled_role_caps = scoper_get_option( 'disabled_role_caps' ) )
 136				$this->role_defs->remove_role_caps( $disabled_role_caps );
 137
 138			$this->role_defs->remove_invalid(); // currently don't allow additional custom-defined post, page or link roles
 139
 140			$this->customize_role_objscope();
 141			
 142			// To support merging in of WP role assignments, always note actual WP-defined roles 
 143			// regardless of which role type we are scoping with.
 144			$this->log_wp_roles( $this->role_defs );
 145		}
 146	}
 147	
 148	function log_cap_usage( &$role_defs, &$cap_defs ) {
 149		foreach( $role_defs->members as $role_handle => $role_def ) {
 150			
 151			foreach( array_keys( $role_defs->role_caps[$role_handle] ) as $cap_name ) {
 152				if ( empty( $cap_defs->members[$cap_name]->object_types ) || ! in_array( $role_def->object_type, $cap_defs->members[$cap_name]->object_types ) ) {
 153					if ( 'post' == $role_def->src_name )
 154						$cap_defs->members[$cap_name]->object_types[] = $role_def->object_type;
 155						
 156					elseif ( in_array( $role_def->src_name, array( 'link', 'group' ) ) )	// TODO: other data sources?
 157						$cap_defs->members[$cap_name]->object_types[] = $role_def->src_name;
 158				}
 159			}
 160		}
 161	}
 162	
 163	function customize_role_objscope() {
 164		foreach ( $this->role_defs->get_all_keys() as $role_handle ) {
 165			if ( ! empty($this->role_defs->members[$role_handle]->objscope_equivalents) ) {
 166				foreach( $this->role_defs->members[$role_handle]->objscope_equivalents as $equiv_key => $equiv_role_handle ) {
 167					
 168					if ( scoper_get_option( "{$equiv_role_handle}_role_objscope" ) ) {	// If "Additional Object Role" option is set for this role, treat it as a regular direct-assigned Object Role
 169
 170						if ( isset($this->role_defs->members[$equiv_role_handle]->valid_scopes) )
 171							$this->role_defs->members[$equiv_role_handle]->valid_scopes = array('blog' => 1, 'term' => 1, 'object' => 1);
 172
 173						unset( $this->role_defs->members[$role_handle]->objscope_equivalents[$equiv_key] );
 174				
 175						if ( ! defined( 'DISABLE_OBJSCOPE_EQUIV_' . $role_handle ) )
 176							define( 'DISABLE_OBJSCOPE_EQUIV_' . $role_handle, true );	// prevent Role Caption / Abbrev from being substituted from equivalent role
 177					}
 178				}
 179			}
 180		}	
 181	}
 182	
 183	function log_wp_roles( &$role_defs ) {
 184		global $wp_roles;
 185		if ( ! isset($wp_roles) )
 186			$wp_roles = new WP_Roles();
 187			
 188		// populate WP roles least-role-first to match RS roles
 189		$keys = array_keys($wp_roles->role_objects);
 190		$keys = array_reverse($keys);
 191
 192		$cr_cap_names = $this->cap_defs->get_all_keys();
 193		
 194		$last_lock = $role_defs->locked;
 195		$role_defs->locked = false;
 196
 197		foreach ( $keys as $role_name ) {
 198			if ( ! empty( $wp_roles->role_objects[$role_name]->capabilities ) ) {
 199				// remove any WP caps which are in array, but have value = false
 200				if ( $caps = array_intersect( $wp_roles->role_objects[$role_name]->capabilities, array(true) ) )
 201					$caps = array_intersect_key( $caps, array_flip($cr_cap_names) );  // we only care about WP caps that are RS-defined
 202			} else
 203				$caps = array();
 204
 205			$role_defs->add( $role_name, 'wordpress', '', '', 'wp' );
 206
 207			// temp hardcode for site-wide Nav Menu cap
 208			if ( ! empty( $caps['edit_theme_options'] ) )
 209				$caps['manage_nav_menus'] = true;
 210
 211			$role_defs->role_caps['wp_' . $role_name] = $caps;
 212		}
 213		
 214		$role_defs->locked = $last_lock;
 215	}
 216	
 217	
 218	function init() {
 219		scoper_version_check();
 220		
 221		if ( ! isset($this->data_sources) )
 222			$this->load_config();
 223		
 224		$is_administrator = is_content_administrator_rs();
 225		
 226		if ( $doing_cron = defined('DOING_CRON') )
 227			if ( ! defined('DISABLE_QUERYFILTERS_RS') )
 228				define('DISABLE_QUERYFILTERS_RS', true);
 229				
 230		if ( ! $this->direct_file_access = strpos($_SERVER['QUERY_STRING'], 'rs_rewrite') )
 231			$this->add_main_filters();
 232			
 233		// ===== Special early exit if this is a plugin install script
 234		if ( is_admin() ) {
 235			if ( in_array( $GLOBALS['pagenow'], array( 'plugin-install.php', 'plugin-editor.php' ) ) ) {
 236				// flush RS cache on activation of any plugin, in case we cached results based on its presence / absence
 237				if ( ( ! empty($_POST) ) || ( ! empty($_REQUEST['action']) ) ) {
 238					if ( ! empty($_POST['networkwide']) || ( 'plugin-editor.php' == $GLOBALS['pagenow'] ) )
 239						wpp_cache_flush_all_sites();
 240					else
 241						wpp_cache_flush();
 242				}
 243
 244				do_action( 'scoper_init' );
 245				return; // no further filtering on WP plugin maintenance scripts
 246			}
 247		}
 248		// =====
 249
 250		require_once( dirname(__FILE__).'/attachment-interceptor_rs.php');
 251		$GLOBALS['attachment_interceptor'] = new AttachmentInterceptor_RS(); // .htaccess file is always there, so we always need to handle its rewrites
 252				
 253		// ===== Content Filters to limit/enable the current user
 254		$disable_queryfilters = defined('DISABLE_QUERYFILTERS_RS');
 255		
 256		if ( $disable_queryfilters ) {
 257			// Some wp-admin pages need to list pages or categories based on front-end access.  Classic example is Subscribe2 categories checklist, included in Subscriber profile
 258			// In that case, filtering will be applied even if wp-admin filtering is disabled.  API hook enables other plugins to defined their own "always filter" URIs.
 259			$always_filter_uris = apply_filters( 'scoper_always_filter_uris', array( 'p-admin/profile.php' ) );
 260
 261			if ( in_array( $GLOBALS['pagenow'], $always_filter_uris ) || in_array( $GLOBALS['plugin_page_cr'], $always_filter_uris ) ) {
 262				$disable_queryfilters = false;
 263				break;
 264			}
 265		}
 266		
 267		// register a map_meta_cap filter to handle the type-specific meta caps we are forcing
 268		require_once( dirname(__FILE__).'/meta_caps_rs.php' );	
 269
 270		if ( ! $disable_queryfilters ) {
 271			 if ( ! $is_administrator ) {
 272				if ( $this->direct_file_access ) {
 273					require_once( dirname(__FILE__).'/cap-interceptor-basic_rs.php');  // only need to support basic read_post / read_page check for direct file access
 274					$GLOBALS['cap_interceptor_basic'] = new CapInterceptorBasic_RS();
 275				} else {
 276					require_once( dirname(__FILE__).'/cap-interceptor_rs.php');
 277					$GLOBALS['cap_interceptor'] = new CapInterceptor_RS();
 278				}
 279			}
 280
 281			// (also use content filters on front end to FILTER IN private content which WP inappropriately hides from administrators)
 282			if ( ( ! $is_administrator ) || $this->is_front() ) {
 283				require_once( dirname(__FILE__).'/query-interceptor_rs.php');
 284				$GLOBALS['query_interceptor'] = new QueryInterceptor_RS();
 285			}
 286
 287			if ( ( ! $this->direct_file_access ) && ( ! $is_administrator || ! defined('XMLRPC_REQUEST') ) ) { // don't tempt trouble by adding hardway filters on XMLRPC for logged administrator
 288				$this->add_hardway_filters();
 289				
 290				if ( $this->is_front() || ! $is_administrator ) {
 291					require_once( dirname(__FILE__).'/terms-query-lib_rs.php');
 292				
 293					if ( awp_ver( '3.1' ) && ! defined( 'SCOPER_LEGACY_TERMS_FILTER' ) ) {
 294						require_once( dirname(__FILE__).'/terms-interceptor_rs.php');
 295						$GLOBALS['terms_interceptor'] = new TermsInterceptor_RS();
 296					} else
 297						require_once( dirname(__FILE__).'/hardway/hardway-taxonomy-legacy_rs.php');
 298				}
 299			}
 300
 301		} // endif query filtering not disabled for this access type
 302
 303		if ( $is_administrator ) {
 304			if ( $this->is_front() )
 305				require_once( 'comments-int-administrator_rs.php' );
 306		} else
 307			require_once( 'comments-interceptor_rs.php' );
 308		
 309		if ( is_admin() )
 310			$this->add_admin_ui_filters( $is_administrator );
 311		
 312		do_action( 'scoper_init' );
 313		
 314		// ===== end Content Filters
 315		
 316	} // end function init
 317	
 318	
 319	// filters which are only needed for the wp-admin UI
 320	function add_admin_ui_filters( $is_administrator ) {
 321		global $pagenow;
 322		
 323		// ===== Admin filters (menu and other basics) which are (almost) always loaded 
 324		require_once( dirname(__FILE__).'/admin/admin_rs.php');
 325		$GLOBALS['scoper_admin'] = new ScoperAdmin();
 326		
 327		if ( 'async-upload.php' != $pagenow ) {
 328			if ( ! defined('DISABLE_QUERYFILTERS_RS') || $is_administrator ) {
 329				require_once( dirname(__FILE__).'/admin/filters-admin-ui_rs.php' );
 330				$GLOBALS['scoper_admin_filters_ui'] = new ScoperAdminFiltersUI();
 331			}
 332		}
 333		// =====
 334
 335		// ===== Script-specific Admin filters 
 336		if ( 'users.php' == $pagenow ) {
 337			require_once( dirname(__FILE__).'/admin/filters-admin-users_rs.php' );
 338			
 339		} elseif ( 'edit.php' == $pagenow ) {
 340			if ( ! defined('DISABLE_QUERYFILTERS_RS') || $is_administrator )
 341				require_once( dirname(__FILE__).'/admin/filters-admin-ui-listing_rs.php' );
 342
 343		} elseif ( in_array( $pagenow, array( 'edit-tags.php', 'edit-link-categories.php' ) ) ) {
 344			if ( ! defined('DISABLE_QUERYFILTERS_RS') )
 345				require_once( dirname(__FILE__).'/admin/filters-admin-terms_rs.php' );
 346		}
 347		// =====
 348		
 349		if ( scoper_get_option( 'group_ajax' ) && ( isset( $_GET['rs_user_search'] ) || isset( $_GET['rs_group_search'] ) ) ) {
 350			require_once( dirname(__FILE__).'/admin/user_query_rs.php' );
 351			exit;	
 352		} 
 353	}
 354	
 355	
 356	function add_hardway_filters() {
 357		// port or low-level query filters to work around limitations in WP core API
 358		require_once( dirname(__FILE__).'/hardway/hardway_rs.php'); // need get_pages() filtering to include private pages for some 3rd party plugin config UI (Simple Section Nav)
 359		
 360		// buffering of taxonomy children is disabled with non-admin user logged in
 361		// But that non-admin user may add cats.  Don't allow unfiltered admin to rely on an old copy of children
 362		global $wp_taxonomies;
 363		if ( ! empty($wp_taxonomies) ) {
 364			foreach ( array_keys($wp_taxonomies) as $taxonomy )
 365				add_filter ( "option_{$taxonomy}_children", create_function( '$option_value', "return rs_get_terms_children('$taxonomy', " . '$option_value );') );
 366				//add_filter("option_{$taxonomy}_children", create_function( '', "return rs_get_terms_children('$taxonomy');") );
 367		}
 368
 369		if ( is_admin() || defined('XMLRPC_REQUEST') ) {
 370            global $pagenow;
 371			
 372			if ( ! in_array( $pagenow, array( 'plugin-editor.php', 'plugins.php' ) ) ) {
 373	            global $plugin_page_cr;
 374
 375				// low-level filtering for miscellaneous admin operations which are not well supported by the WP API
 376				$hardway_uris = array(
 377				'index.php',		'revision.php',			'admin.php?page=rvy-revisions',
 378				'post.php', 		'post-new.php', 		'edit.php', 
 379				'upload.php', 		'edit-comments.php', 	'edit-tags.php',
 380				'profile.php',		'admin-ajax.php',
 381				'link-manager.php', 'link-add.php',			'link.php',		 
 382				'edit-link-category.php', 	'edit-link-categories.php',
 383				'media-upload.php',	'nav-menus.php'  
 384				);
 385
 386				$hardway_uris = apply_filters( 'scoper_admin_hardway_uris', $hardway_uris );
 387																															// support for rs-config-ngg <= 1.0
 388				if ( defined('XMLRPC_REQUEST') || in_array( $pagenow, $hardway_uris ) || in_array( $plugin_page_cr, $hardway_uris ) || in_array( "p-admin/admin.php?page=$plugin_page_cr", $hardway_uris ) )
 389					require_once( dirname(__FILE__).'/hardway/hardway-admin_rs.php' );
 390        	}
 391		} // endif is_admin or xmlrpc
 392	}
 393	
 394	
 395	// add filters which were skipped due to direct file access, but are now needed for the error page display
 396	function add_main_filters() {
 397		$is_admin = is_admin();
 398		$is_administrator = is_content_administrator_rs();
 399		$disable_queryfilters = defined('DISABLE_QUERYFILTERS_RS');
 400		$frontend_admin = false;
 401		
 402		if ( ! defined('DOING_CRON') ) {
 403			if ( $this->is_front() ) {
 404				if ( ! $disable_queryfilters )
 405					require_once( dirname(__FILE__).'/query-interceptor-front_rs.php');
 406	
 407				if ( ! $is_administrator ) {
 408					require_once( dirname(__FILE__).'/qry-front_non-administrator_rs.php');
 409					$GLOBALS['feed_interceptor'] = new FeedInterceptor_RS(); // file already required in role-scoper.php
 410				}
 411	
 412				require_once( dirname(__FILE__).'/template-interceptor_rs.php');
 413				$GLOBALS['template_interceptor'] = new TemplateInterceptor_RS();
 414	
 415				$frontend_admin = ! scoper_get_option('no_frontend_admin'); // potential performance enhancement	
 416
 417				if ( ! empty($_REQUEST['s']) && function_exists('relevanssi_query') ) {
 418					require_once( dirname(__FILE__).'/relevanssi-helper-front_rs.php' );
 419					$rel_helper_rs = new Relevanssi_Search_Filter_RS();
 420				}
 421			}
 422
 423			// ===== Filters which are always loaded (except on plugin scripts), for any access type
 424			include_once( dirname(__FILE__).'/hardway/wp-patches_agp.php' ); // simple patches for WP
 425			
 426			if ( $this->is_front() || ( 'edit.php' == $GLOBALS['pagenow'] ) ) {
 427				require_once( dirname(__FILE__).'/query-interceptor-base_rs.php');
 428				$GLOBALS['query_interceptor_base'] = new QueryInterceptorBase_RS();  // listing filter used for role status indication in edit posts/pages and on front end by template functions
 429			}
 430		}
 431		
 432		// ===== Filters which support automated role maintenance following content creation/update
 433		// Require an explicitly set option to skip these for front end access, just in case other plugins modify content from the front end.
 434		if ( ( $is_admin || defined('XMLRPC_REQUEST') || $frontend_admin || defined('DOING_CRON') ) ) {
 435			require_once( dirname(__FILE__).'/admin/cache_flush_rs.php' );
 436			require_once( dirname(__FILE__).'/admin/filters-admin_rs.php' );
 437			$GLOBALS['scoper_admin_filters'] = new ScoperAdminFilters();
 438			
 439			if ( defined( 'RVY_VERSION' ) ) // Support Revisionary references to $scoper->filters_admin (TODO: eventually phase this out)
 440				$this->filters_admin =& $GLOBALS['scoper_admin_filters'];
 441		}
 442		// =====
 443	}
 444	
 445
 446	function init_users_interceptor() {
 447		if ( ! isset($GLOBALS['users_interceptor']) ) {
 448			require_once( dirname(__FILE__).'/users-interceptor_rs.php');
 449			$GLOBALS['users_interceptor'] = new UsersInterceptor_RS();
 450
 451			//log_mem_usage_rs( 'init Users Interceptor' );
 452		}
 453		
 454		return $GLOBALS['users_interceptor'];
 455	}
 456	
 457	
 458	// Primarily for internal use. Drops some features of WP core get_terms while adding the following versatility:
 459	// - supports any RS-defined taxonomy, with or without WP taxonomy schema
 460	// - optionally return term_id OR term_taxonomy_id as single column
 461	// - specify filtered or unfiltered via argument
 462	// - optionally get terms for a specific object
 463	// - option to order by term hierarchy (but structure as flat array)
 464	function get_terms($taxonomy, $filtering = true, $cols = COLS_ALL_RS, $object_id = 0, $args = array()) {
 465		if ( ! $tx = $this->taxonomies->get($taxonomy) )
 466			return array();
 467
 468		global $wpdb;
 469
 470		$defaults = array( 'order_by' => '', 'use_object_roles' => false, 'operation' => '' ); // IMPORTANT to default operation to nullstring
 471		$args = array_merge( $defaults, (array) $args );
 472		extract($args);
 473
 474		if (  is_administrator_rs( $this->taxonomies->member_property( $taxonomy, 'object_source' ) ) )
 475			$filtering = false;
 476
 477		// try to pull it out of wpcache
 478		$ckey = md5( $taxonomy . $cols . $object_id . serialize($args) . $order_by );
 479		
 480		if ( $filtering ) {
 481			$src_name = $this->taxonomies->member_property($taxonomy, 'object_source', 'name');
 482
 483			$args['reqd_caps_by_otype'] = $this->get_terms_reqd_caps( $taxonomy, $operation, ADMIN_TERMS_FILTER_RS === $filtering );
 484
 485			$ckey = md5( $ckey . serialize($args['reqd_caps_by_otype']) ); ; // can vary based on request URI
 486		
 487			global $current_rs_user;
 488			$cache_flag = 'rs_scoper_get_terms';
 489			$cache = $current_rs_user->cache_get($cache_flag);
 490		} else {			
 491			$cache_flag = "all_terms";
 492			$cache_id = 'all';
 493			$cache = wpp_cache_get( $cache_id, $cache_flag );
 494		}
 495
 496		if ( isset( $cache[ $ckey ] ) ) {
 497			return $cache[ $ckey ];
 498		}
 499			
 500		// call base class method to build query
 501		$terms_only = ( ! $filtering || empty($use_object_roles) );
 502	
 503		$query_base = $this->taxonomies->get_terms_query($taxonomy, $cols, $object_id, $terms_only );
 504
 505		if ( ! $query_base )
 506			return array();
 507
 508		$query = ( $filtering ) ? apply_filters('terms_request_rs', $query_base, $taxonomy, $args) : $query_base;
 509
 510		// avoid sending alarms to SQL purists if this query was not modified by RS filter
 511		if ( $query_base == $query )
 512			$query = str_replace( 'WHERE 1=1 AND', 'WHERE', $query );
 513		
 514		if ( COL_ID_RS == $cols )
 515			$results = scoper_get_col($query);
 516		elseif ( COL_COUNT_RS == $cols )
 517			$results = intval( scoper_get_var($query) );
 518		else {
 519			// TODO: why is this still causing an extra (and costly) scoped query?
 520			/*
 521			// for COLS_ALL query, need to call core get_terms call in case another plugin is translating term names
 522			if ( has_filter( 'get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms') ) ) {
 523				remove_filter( 'get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 1, 3 );
 524				$all_terms = get_terms($taxonomy);
 525				add_filter( 'get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 1, 3 );
 526
 527				$term_names = scoper_get_property_array( $all_terms, 'term_id', 'name' );
 528			}
 529			*/
 530			
 531			$results = scoper_get_results($query);
 532
 533			//scoper_restore_property_array( $results, $term_names, 'term_id', 'name' );
 534				
 535			if ( ORDERBY_HIERARCHY_RS == $order_by ) {
 536				require_once( dirname(__FILE__).'/admin/admin_lib_rs.php');
 537				
 538				if ( $src = $this->data_sources->get( $tx->source ) ) {
 539					if ( ! empty($src->cols->id) && ! empty($src->cols->parent) ) {
 540						require_once( dirname(__FILE__).'/admin/admin_lib-bulk-parent_rs.php');
 541						$results = ScoperAdminBulkParent::order_by_hierarchy($results, $src->cols->id, $src->cols->parent);
 542					}
 543				}
 544			}
 545		}
 546		
 547		$cache[ $ckey ] = $results;
 548
 549		if ( $results || empty( $_POST ) ) { // todo: why do we get an empty array for unfiltered request for object terms early in POST processing? (on submission of a new post by a contributor)
 550			if ( $filtering )
 551				$current_rs_user->cache_force_set( $cache, $cache_flag );
 552			else
 553				wpp_cache_force_set( $cache_id, $cache, $cache_flag );	
 554		}
 555		
 556		return $results;
 557	}
 558	
 559	function get_default_restrictions($scope, $args = array()) {
 560		$defaults = array( 'force_refresh' => false );
 561		$args = array_merge( $defaults, (array) $args );
 562		extract($args);
 563	
 564		if ( isset($this->default_restrictions[$scope]) && ! $force_refresh )
 565			return $this->default_restrictions[$scope];
 566		
 567		if ( empty($force_refresh) ) {
 568			$cache_flag = "rs_{$scope}_def_restrictions";
 569			$cache_id = md5('');	// maintain default id generation from previous versions
 570
 571			$default_strict = wpp_cache_get($cache_id, $cache_flag);
 572		}
 573		
 574		if ( $force_refresh || ! is_array($default_strict) ) {
 575			global $wpdb;
 576			
 577			$qry = "SELECT src_or_tx_name, role_name FROM $wpdb->role_scope_rs WHERE role_type = 'rs' AND topic = '$scope' AND max_scope = '$scope' AND obj_or_term_id = '0'";
 578
 579			$default_strict = array();
 580			if ( $results = scoper_get_results($qry) ) {
 581				foreach ( $results as $row ) {
 582					$role_handle = scoper_get_role_handle($row->role_name, 'rs');
 583					$default_strict[$row->src_or_tx_name][$role_handle] = true;
 584					
 585					if (OBJECT_SCOPE_RS == $scope) {
 586						if ( $objscope_equivalents = $this->role_defs->member_property($role_handle, 'objscope_equivalents') )
 587							foreach ( $objscope_equivalents as $equiv_role_handle )
 588								$default_strict[$row->src_or_tx_name][$equiv_role_handle] = true;
 589					}
 590					
 591				}
 592			}
 593		}
 594		
 595		$this->default_restrictions[$scope] = $default_strict;
 596
 597		wpp_cache_set($cache_id, $default_strict, $cache_flag);
 598		
 599		return $default_strict;
 600	}
 601	
 602	// for any given role requirement, a strict term is one which won't blend in blog role assignments
 603	// (i.e. a term which requires the specified role to be assigned as a term role or object role)
 604	//
 605	// returns $arr['restrictions'][role_handle][obj_or_term_id] = array( 'assign_for' => $row->assign_for, 'inherited_from' => $row->inherited_from ),
 606	//				['unrestrictions'][role_handle][obj_or_term_id] = array( 'assign_for' => $row->assign_for, 'inherited_from' => $row->inherited_from )
 607	function get_restrictions($scope, $src_or_tx_name, $args = array()) {
 608		$def_cols = COL_ID_RS;
 609
 610		// Note: propogating child restrictions are always directly assigned to the child term(s).
 611		// Use include_child_restrictions to force inclusion of restrictions that are set for child items only,
 612		// for direct admin of these restrictions and for propagation on term/object creation.
 613		$defaults = array( 	'id' => 0,					'include_child_restrictions' => false,
 614						 	'force_refresh' => false, 
 615						 	'cols' => $def_cols,		'return_array' => false );
 616		$args = array_merge( $defaults, (array) $args );
 617		extract($args);
 618		
 619		$cache_flag = "rs_{$scope}_restrictions_{$src_or_tx_name}";
 620		$cache_id = md5($src_or_tx_name . $cols . strval($return_array) . strval($include_child_restrictions) );
 621
 622		if ( ! $force_refresh ) {
 623			$items = wpp_cache_get($cache_id, $cache_flag);
 624
 625			if ( is_array($items) ) {
 626				if ( $id ) {
 627					foreach ( $items as $setting_type => $roles )
 628						foreach ( array_keys($roles) as $role_handle )
 629							$items[$setting_type][$role_handle] = array_intersect_key( $items[$setting_type][$role_handle], array( $id => true ) );
 630				}
 631
 632				return $items;
 633			}
 634		}
 635		
 636		if ( ! isset($this->default_restrictions[$scope]) )
 637			$this->default_restrictions[$scope] = $this->get_default_restrictions($scope);
 638
 639		global $wpdb;
 640
 641		if ( ! empty($this->default_restrictions[$scope][$src_or_tx_name]) ) {
 642			if ( $strict_roles = array_keys($this->default_restrictions[$scope][$src_or_tx_name]) ) {
 643				if ( OBJECT_SCOPE_RS == $scope ) {
 644					// apply default_strict handling to objscope equivalents of each strict role
 645					foreach ( $strict_roles as $role_handle )
 646						if ( $objscope_equivalents = $this->role_defs->member_property($role_handle, 'objscope_equivalents') )
 647							$strict_roles = array_merge($strict_roles, $objscope_equivalents);
 648							
 649					$strict_roles = array_unique($strict_roles);
 650				}
 651			}
 652			
 653			$strict_role_in = "'" . implode("', '", scoper_role_handles_to_names($strict_roles) ) . "'";
 654		} else
 655			$strict_role_in = '';
 656		
 657		$items = array();				
 658		if ( ! empty($strict_roles) ) {
 659			foreach ( $strict_roles as $role_handle )
 660				$items['unrestrictions'][$role_handle] = array();  // calling code will use this as an indication that the role is default strict
 661		}
 662		
 663		$default_strict_modes = array( false );
 664		
 665		if ( $strict_role_in )
 666			$default_strict_modes []= true;
 667
 668		foreach ( $default_strict_modes as $default_strict ) {
 669			$setting_type = ( $default_strict ) ? 'unrestrictions' : 'restrictions';
 670
 671			if ( TERM_SCOPE_RS == $scope )
 672				$max_scope = ( $default_strict ) ? 'blog' : 'term';  // note: max_scope='object' entries are treated as separate, overriding requirements
 673			else
 674				$max_scope = ( $default_strict ) ? 'blog' : 'object'; // Storage of 'blog' max_scope as object restriction does not eliminate any term restrictions.  It merely indicates, for data sources that are default strict, that this object does not restrict roles
 675				
 676			if ( $default_strict )
 677				$role_clause = "AND role_name IN ($strict_role_in)";
 678			elseif ($strict_role_in)
 679				$role_clause = "AND role_name NOT IN ($strict_role_in)";
 680			else
 681				$role_clause = '';
 682
 683			$for_clause = ( $include_child_restrictions ) ? '' : "AND require_for IN ('entity', 'both')";
 684			
 685			$qry_base = "FROM $wpdb->role_scope_rs WHERE role_type = 'rs' AND topic = '$scope' AND max_scope = '$max_scope' AND src_or_tx_name = '$src_or_tx_name' $for_clause $role_clause";
 686			
 687			if ( COL_COUNT_RS == $cols )
 688				$qry = "SELECT role_name, count(obj_or_term_id) AS item_count, require_for $qry_base GROUP BY role_name";
 689			else
 690				$qry = "SELECT role_name, obj_or_term_id, require_for AS assign_for, inherited_from $qry_base";
 691
 692			if ( $results = scoper_get_results($qry) ) {
 693				foreach( $results as $row) {
 694					$role_handle = scoper_get_role_handle($row->role_name, 'rs');
 695					
 696					if ( COL_COUNT_RS == $cols )
 697						$items[$setting_type][$role_handle] = $row->item_count;
 698					elseif ( $return_array )
 699						$items[$setting_type][$role_handle][$row->obj_or_term_id] = array( 'assign_for' => $row->assign_for, 'inherited_from' => $row->inherited_from );
 700					else
 701						$items[$setting_type][$role_handle][$row->obj_or_term_id] = $row->assign_for;
 702				}
 703			}
 704			
 705		} // end foreach default_strict_mode
 706
 707		wpp_cache_force_set($cache_id, $items, $cache_flag);
 708
 709		if ( $id ) {
 710			foreach ( $items as $setting_type => $roles )
 711				foreach ( array_keys($roles) as $role_handle )
 712					$items[$setting_type][$role_handle] = array_intersect_key( $items[$setting_type][$role_handle], array( $id => true ) );
 713		}
 714
 715		return $items;
 716	}
 717	
 718	
 719	// wrapper for back-compat with calling code expecting array without date limit dimension
 720	function qualify_terms($reqd_caps, $taxonomy = 'category', $qualifying_roles = '', $args = array()) {
 721		$terms = $this->qualify_terms_daterange( $reqd_caps, $taxonomy, $qualifying_roles, $args );
 722		
 723		if ( isset($terms['']) && is_array($terms['']) )
 724			return $terms[''];
 725		else
 726			return array();
 727	}
 728
 729	// $qualifying_roles = array[role_handle] = 1 : qualifying roles
 730	// returns array of term_ids (terms which have at least one of the qualifying roles assigned)
 731	function qualify_terms_daterange($reqd_caps, $taxonomy = 'category', $qualifying_roles = '', $args = array()) {
 732		$defaults = array( 'user' => '', 'return_id_type' => COL_ID_RS, 'use_blog_roles' => true, 'ignore_restrictions' => false );
 733
 734		if ( isset($args['qualifying_roles']) )
 735			unset($args['qualifying_roles']);
 736			
 737		if ( isset($args['reqd_caps']) )
 738			unset($args['reqd_caps']);
 739
 740		$args = array_merge( $defaults, (array) $args );
 741		extract($args);
 742
 743		if ( ! $qualifying_roles )  // calling function might save a little work or limit to a subset of qualifying roles
 744			$qualifying_roles = $this->role_defs->qualify_roles( $reqd_caps );
 745
 746		if ( ! $this->taxonomies->is_member($taxonomy) )
 747			return array( '' => array() );
 748		
 749		if ( ! is_object($user) ) {
 750			$user = $GLOBALS['current_rs_user'];
 751		}
 752		
 753		// If the taxonomy does not require objects to have at least one term, there are no strict terms.
 754		if ( ! $this->taxonomies->member_property($taxonomy, 'requires_term') )
 755			$ignore_restrictions = true;
 756			
 757		if ( ! is_array($qualifying_roles) )
 758			$qualifying_roles = array($qualifying_roles => 1);	
 759
 760		// no need to serialize and md5 the whole user object
 761		if ( ! empty($user) )
 762			$args['user'] = $user->ID;
 763
 764		// try to pull previous result out of memcache
 765		ksort($qualifying_roles);
 766		$rolereq_key = md5( serialize($reqd_caps) . serialize( array_keys($qualifying_roles) ) . serialize($args) );
 767		
 768		if ( isset($user->qualified_terms[$taxonomy][$rolereq_key]) )
 769			return $user->qualified_terms[$taxonomy][$rolereq_key];
 770			
 771		if ( ! $qualifying_roles )
 772			return array( '' => array() );
 773
 774		$all_terms = $this->get_terms($taxonomy, UNFILTERED_RS, COL_ID_RS); // returns term_id, even for WP > 2.3
 775
 776		if ( ! isset($user->term_roles[$taxonomy]) )
 777			$user->get_term_roles_daterange($taxonomy);  // returns term_id for categories
 778
 779		$good_terms = array( '' => array() );
 780		
 781		if ( $user->term_roles[$taxonomy] ) {
 782			foreach ( array_keys($user->term_roles[$taxonomy]) as $date_key ) {
 783				//narrow down to roles which satisfy this call AND are owned by current user
 784				if ( $good_terms[$date_key] = array_intersect_key( $user->term_roles[$taxonomy][$date_key], $qualifying_roles ) )
 785					// flatten from term_roles_terms[role_handle] = array of term_ids
 786					// to term_roles_terms = array of term_ids
 787					$good_terms[$date_key] = agp_array_flatten( $good_terms[$date_key] );
 788			}
 789		}
 790
 791		if ( $use_blog_roles ) {
 792			foreach ( array_keys($user->blog_roles) as $date_key ) {	
 793				$user_blog_roles = array_intersect_key( $user->blog_roles[$date_key], $qualifying_roles );
 794
 795				// Also include user's WP blogrole(s) which correspond to the qualifying RS role(s)
 796				if ( $wp_qualifying_roles = $this->role_defs->qualify_roles($reqd_caps, 'wp') ) {
 797					
 798					if ( $user_blog_roles_wp = array_intersect_key( $user->blog_roles[$date_key], $wp_qualifying_roles ) ) {
 799					
 800						// Credit user's qualifying WP blogrole via equivalent RS role(s)
 801						// so we can also enforce "term restrictions", which are based on RS roles
 802						$user_blog_roles_via_wp = $this->role_defs->get_contained_roles( array_keys($user_blog_roles_wp), false, 'rs' );
 803						$user_blog_roles_via_wp = array_intersect_key( $user_blog_roles_via_wp, $qualifying_roles );
 804						$user_blog_roles = array_merge( $user_blog_roles, $user_blog_roles_via_wp );
 805					}
 806				}
 807				
 808				if ( $user_blog_roles ) {
 809					if ( empty($ignore_restrictions) ) {
 810						// array of term_ids that require the specified role to be assigned via taxonomy or object role (user blog caps ignored)
 811						$strict_terms = $this->get_restrictions(TERM_SCOPE_RS, $taxonomy);
 812					} else
 813						$strict_terms = array();
 814
 815					foreach ( array_keys($user_blog_roles) as $role_handle ) {
 816						if ( isset($strict_terms['restrictions'][$role_handle]) && is_array($strict_terms['restrictions'][$role_handle]) )
 817							$terms_via_this_role = array_diff( $all_terms, array_keys($strict_terms['restrictions'][$role_handle]) );
 818						
 819						elseif ( isset($strict_terms['unrestrictions'][$role_handle]) && is_array($strict_terms['unrestrictions'][$role_handle]) )
 820							$terms_via_this_role = array_intersect( $all_terms, array_keys( $strict_terms['unrestrictions'][$role_handle] ) );
 821						
 822						else
 823							$terms_via_this_role = $all_terms;
 824	
 825						if( $good_terms[$date_key] )
 826							$good_terms[$date_key] = array_merge( $good_terms[$date_key], $terms_via_this_role );
 827						else
 828							$good_terms[$date_key] = $terms_via_this_role;
 829					}
 830				}
 831			}
 832		}
 833
 834		foreach ( array_keys($good_terms) as $date_key ) {
 835			if ( $good_terms[$date_key] = array_intersect( $good_terms[$date_key], $all_terms ) )  // prevent orphaned category roles from skewing access
 836				$good_terms[$date_key] = array_unique( $good_terms[$date_key] );
 837		
 838			// if COL_TAXONOMY_ID_RS, return a term_taxonomy_id instead of term_id
 839			if ( $good_terms[$date_key] && (COL_TAXONOMY_ID_RS == $return_id_type) && taxonomy_exists($taxonomy) ) {
 840				$all_terms_cols = $this->get_terms( $taxonomy, UNFILTERED_RS );
 841				$good_tt_ids = array();
 842				foreach ( $good_terms[$date_key] as $term_id )
 843					foreach ( array_keys($all_terms_cols) as $termkey )
 844						if ( $all_terms_cols[$termkey]->term_id == $term_id ) {
 845							$good_tt_ids []= $all_terms_cols[$termkey]->term_taxonomy_id;
 846							break;
 847						}
 848						
 849				$good_terms[$date_key] = $good_tt_ids;
 850			}
 851		}
 852		
 853		$user->qualified_terms[$taxonomy][$rolereq_key] = $good_terms;
 854
 855		return $good_terms;
 856	}
 857	
 858	// account for different contexts of get_terms calls 
 859	// (Scoped roles can dictate different results for front end, edit page/post, manage categories)
 860	function get_terms_reqd_caps( $taxonomy, $operation = '', $is_term_admin = false ) {
 861		global $pagenow;
 862
 863		if ( ! $src_name = $this->taxonomies->member_property( $taxonomy, 'object_source' ) ) {
 864			if ( taxonomy_exists( $taxonomy ) )
 865				$src_name = 'post';
 866		}
 867
 868		$return_caps = array();
 869
 870		$is_term_admin = $is_term_admin 
 871		|| in_array( $pagenow, array( 'edit-tags.php' ) ) 
 872		|| ( 'nav_menu' == $taxonomy && ( 'nav-menus.php' == $pagenow ) 
 873		|| ( ( 'admin-ajax.php' == $pagenow ) && ( ! empty($_REQUEST['action']) && in_array( $_REQUEST['action'], array( 'add-menu-item', 'menu-locations-save' ) ) ) )
 874		);	// possible TODO: abstract for non-WP taxonomies
 875
 876		if ( $is_term_admin ) {
 877			// query pertains to the management of terms
 878			if ( 'post' == $src_name ) {
 879				$taxonomy_obj = get_taxonomy( $taxonomy );
 880				$return_caps[$taxonomy] = array( $taxonomy_obj->cap->manage_terms );
 881			} elseif ( 'link_category' == $taxonomy ) { 
 882				$return_caps[$taxonomy] = array( 'manage_categories' );
 883			} else {
 884				global $scoper;
 885				$cap_defs = $scoper->cap_defs->get_matching( $src_name, $taxonomy, OP_ADMIN_RS );
 886				$return_caps[$taxonomy] = $cap_defs ? array_keys( $cap_defs ) : array();
 887			}
 888
 889		} else {
 890			// query pertains to reading or editing content within certain terms, or adding terms to content
 891			
 892			$base_caps_only = true;
 893			
 894			if ( 'post' == $src_name ) {
 895				if ( ! $operation )
 896					$operation = ( $this->is_front() || ( 'profile.php' == $pagenow ) || ( is_admin() && array_key_exists('plugin_page', $GLOBALS) && ( 's2' == $GLOBALS['plugin_page'] ) ) ) ? 'read' : 'edit';  // hack to support subscribe2 categories checklist
 897
 898				$status = ( 'read' == $operation ) ? 'publish' : 'draft';
 899				
 900				// terms query should be limited to a single object type for post.php, post-new.php, so only return caps for that object type (TODO: do this in wp-admin regardless of URI ?)
 901				if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) )
 902					$object_type = cr_find_post_type();
 903			} else {
 904				if ( ! $operation )
 905					$operation = ( $this->is_front() ) ? 'read' : 'edit';
 906
 907				$status = '';
 908			}
 909				
 910			// The return array will indicate term role enable / disable, as well as associated capabilities
 911			if ( ! empty($object_type) )
 912				$check_object_types = array( $object_type );
 913			else {
 914				if ( $check_object_types = (array) $this->data_sources->member_property( $src_name, 'object_types' ) )
 915					$check_object_types = array_keys( $check_object_types );
 916			}
 917				
 918			if ( 'post' == $src_name )
 919				$use_post_types = scoper_get_option( 'use_post_types' );	
 920			
 921			$enabled_object_types = array();
 922			foreach ( $check_object_types as $_object_type ) {
 923				if ( $use_term_roles = scoper_get_otype_option( 'use_term_roles', $src_name, $_object_type ) )
 924					if ( ! empty( $use_term_roles[$taxonomy] ) ) {
 925						if ( ( 'post' != $src_name ) || ! empty( $use_post_types[$_object_type] ) )
 926							$enabled_object_types []= $_object_type;
 927					}
 928			}
 929
 930			foreach( $enabled_object_types as $object_type )
 931				$return_caps[$object_type] = cr_get_reqd_caps( $src_name, $operation, $object_type, $status, $base_caps_only );	
 932		}
 933		
 934		return $return_caps;
 935	}
 936	
 937	function users_who_can($reqd_caps, $cols = COLS_ALL_RS, $object_src_name = '', $object_id = 0, $args = array() ) {
 938		// if there are not capability requirements, no need to load Users_Interceptor filtering class
 939		if ( ! $reqd_caps ) {
 940			if ( COL_ID_RS == $cols )
 941				$qcols = 'ID';
 942			elseif ( COLS_ID_NAME_RS == $cols )
 943				$qcols = "ID, user_login AS display_name";	// calling code assumes display_name property for user or group object
 944			elseif ( COLS_ID_DISPLAYNAME_RS == $cols )
 945				$qcols = "ID, display_name";
 946			elseif ( COLS_ALL_RS == $cols )
 947				$qcols = "*";
 948			else
 949				$qcols = $cols;
 950				
 951			global $wpdb;
 952				
 953			$orderby = ( $cols == COL_ID_RS ) ? '' : 'ORDER BY display_name';
 954
 955			if ( IS_MU_RS && ! scoper_get_option( 'mu_sitewide_groups' ) && ! defined( 'FORCE_ALL_SITE_USERS_RS' ) )
 956				$qry = "SELECT $qcols FROM $wpdb->users INNER JOIN $wpdb->usermeta AS um ON $wpdb->users.ID = um.user_id AND um.meta_key = '{$wpdb->prefix}capabilities' $orderby";
 957			else
 958				$qry = "SELECT $qcols FROM $wpdb->users $orderby";
 959
 960			if ( COL_ID_RS == $cols )
 961				return scoper_get_col( $qry );
 962			else
 963				return scoper_get_results( $qry );	
 964			
 965		} else {
 966			$defaults = array( 'where' => '', 'orderby' => '', 'disable_memcache' => false, 'group_ids' => '', 'force_refresh' => false, 'force_all_users' => false );
 967			$args = array_merge( $defaults, (array) $args );
 968			extract($args);
 969	
 970			$cache_flag = "rs_users_who_can";
 971			$cache_id = md5(serialize($reqd_caps) . $cols . 'src' . $object_src_name . 'id' . $object_id . serialize($args) );
 972		
 973			if ( ! $force_refresh ) {
 974				// if we already have the results cached, no need to load Users_Interceptor filtering class
 975				$users = wpp_cache_get($cache_id, $cache_flag);
 976	
 977				if ( is_array($users) )
 978					return $users;
 979			}
 980			
 981			$this->init_users_interceptor();
 982			$users = $GLOBALS['users_interceptor']->users_who_can($reqd_caps, $cols, $object_src_name, $object_id, $args );
 983
 984			wpp_cache_set($cache_id, $users, $cache_flag);
 985			return $users;
 986		}
 987	}
 988	
 989	function groups_who_can($reqd_caps, $cols = COLS_ALL_RS, $object_src_name = '', $object_id = 0, $args = array() ) {
 990		$this->init_users_interceptor();
 991		return $GLOBALS['users_interceptor']->groups_who_can($reqd_caps, $cols, $object_src_name, $object_id, $args );
 992	}
 993	
 994	function is_front() {
 995		return ( defined('CURRENT_ACCESS_NAME_RS') && ( 'front' == CURRENT_ACCESS_NAME_RS ) );
 996	}
 997	
 998	
 999	// returns array of role names which have the required caps (or their basecap equivalent)
1000	// AND have been applied to at least one object, for any user or group
1001	function qualify_object_roles( $reqd_caps, $object_type = '', $user = '', $base_caps_only = false ) {
1002		$roles = array();
1003
1004		if ( $base_caps_only )
1005			$reqd_caps = $this->cap_defs->get_base_caps($reqd_caps);
1006
1007		$roles = $this->role_defs->qualify_roles($reqd_caps, 'rs', $object_type);
1008
1009		return $this->confirm_object_scope( $roles, $user );
1010	}
1011
1012	// $roles[$role_handle] = array
1013	// returns arr[$role_handle] 
1014	function confirm_object_scope( $roles, $user = '' ) {
1015		foreach ( array_keys($roles) as $role_handle ) {
1016			if ( empty( $this->role_defs->members[$role_handle]->valid_scopes['object'] ) )
1017				unset( $roles[$role_handle] );
1018		}
1019
1020		if ( ! $roles )
1021			return array();
1022		
1023		if ( is_object($user) )
1024			$applied_obj_roles = $this->get_applied_object_roles( $user );
1025		elseif ( empty($user) ) {
1026			$applied_obj_roles = $this->get_applied_object_roles( $GLOBALS['current_rs_user'] );
1027		} else // -1 value passed to indicate check for all users
1028			$applied_obj_roles = $this->get_applied_object_roles();
1029			
1030		return array_intersect_key( $roles, $applied_obj_roles );	
1031	}
1032	
1033	
1034	// returns array of role_handles which have been applied to any object
1035	// if $user arg is supplied, returns only roles applied for that user (or that user's groups) 
1036	function get_applied_object_roles( $user = '' ) {
1037		if ( is_object( $user ) ) {
1038			$cache_flag = 'rs_object-roles';			// v 1.1: changed cache key from "object_roles" to "object-roles" to match new key format for blog, term roles
1039			$cache = $user->cache_get($cache_flag);
1040			
1041			$limit = '';
1042			$u_g_clause = $user->get_user_clause('');
1043		} else {
1044			$cache_flag = 'rs_applied_object-roles';	// v 1.1: changed cache key from "object_roles" to "object-roles" to match new key format for blog, term roles
1045			$cache_id = 'all';
1046			$cache = wpp_cache_get($cache_id, $cache_flag);
1047			
1048			$u_g_clause = '';
1049		}
1050		
1051		if ( is_array($cache) )
1052			return $cache;
1053		
1054		$role_handles = array();
1055			
1056		global $wpdb;
1057		
1058		// object roles support date limits, but content date limits (would be redundant and a needless performance hit)
1059		$duration_clause = scoper_get_duration_clause( '', $wpdb->user2role2object_rs );
1060
1061		if ( $role_names = scoper_get_col("SELECT DISTINCT role_name FROM $wpdb->user2role2object_rs WHERE role_type='rs' AND scope='object' $duration_clause $u_g_clause") )
1062			$role_handles = scoper_role_names_to_handles($role_names, 'rs', true); //arg: return role keys as array key
1063		
1064		if ( is_object($user) ) {
1065			$user->cache_force_set($role_handles, $cache_flag);
1066		} else
1067			wpp_cache_force_set($cache_id, $role_handles, $cache_flag);
1068		
1069		return $role_handles;
1070	}
1071	
1072	function user_can_edit_blogwide( $src_name = '', $object_type = '', $args = '' ) {
1073		if ( is_administrator_rs($src_name) )
1074			return true;
1075	
1076		require_once( dirname(__FILE__).'/admin/permission_lib_rs.php' );
1077		return user_can_edit_blogwide_rs($src_name, $object_type, $args);
1078	}
1079	
1080} // end Scoper class
1081
1082
1083// (needed to stop using shared core library function with Revisionary due to changes in meta_flag handling)
1084if ( ! function_exists('awp_user_can') ) {
1085function awp_user_can( $reqd_caps, $object_id = 0, $user_id = 0, $meta_flags = array() ) {	
1086	return cr_user_can( $reqd_caps, $object_id, $user_id, $meta_flags );
1087}
1088}
1089
1090// equivalent to current_user_can, 
1091// except it supports array of reqd_caps, supports non-current user, and does not support numeric reqd_caps
1092function cr_user_can( $reqd_caps, $object_id = 0, $user_id = 0, $meta_flags = array() ) {	
1093	if ( ! $user_id ) {
1094		if ( function_exists('is_super_admin') && is_super_admin() ) 
1095			return true;
1096			
1097		if ( is_content_administrator_rs() || ! function_exists( '_cr_user_can' ) )
1098			return current_user_can( $reqd_caps );
1099	}
1100
1101	if ( function_exists( '_cr_user_can' ) )
1102		return _cr_user_can( $reqd_caps, $object_id, $user_id, $meta_flags );
1103}
1104
1105?>