PageRenderTime 70ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/role-scoper/cap-interceptor_rs.php

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