function scoper_filter_terms_for_status($taxonomy, $selected_terms, &$user_terms, $args = array()) { if (defined('DISABLE_QUERYFILTERS_RS') || defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $selected_terms; } global $scoper; $defaults = array('object_id' => 0, 'object_type' => '', 'status' => ''); $args = array_merge($defaults, $args); extract($args); if (!($tx = $scoper->taxonomies->get($taxonomy))) { return $selected_terms; } if (!($src = $scoper->data_sources->get($tx->object_source))) { return $selected_terms; } if (!isset($src->statuses) || count($src->statuses) < 2) { return $selected_terms; } if (!$object_id) { $object_id = scoper_get_object_id($src->name); } if (!$status) { // determine current post status if (defined('XMLRPC_REQUEST') && !empty($GLOBALS['scoper_xmlrpc_post_status'])) { $status = $GLOBALS['scoper_xmlrpc_post_status']; } else { if (!($status = $scoper->data_sources->get_from_http_post('status', $src))) { if ($object_id) { $status = $scoper->data_sources->get_from_db('status', $src, $object_id); } } } } if (!$object_type) { if (!($object_type = cr_find_object_type($src->name, $object_id))) { if (defined('XMLRPC_REQUEST')) { $object_type = 'post'; } else { return $selected_terms; } } } if ('auto-draft' == $status) { $status = 'draft'; } // make sure _others caps are required only for objects current user doesn't own $base_caps_only = true; if ($object_id && !empty($src->cols->owner)) { $col_owner = $src->cols->owner; if ($object = $scoper->data_sources->get_object($src->name, $object_id)) { if (!empty($object->{$col_owner}) && $object->{$col_owner} != $GLOBALS['current_user']->ID) { $base_caps_only = false; } } } if (!($reqd_caps = cr_get_reqd_caps($src->name, OP_EDIT_RS, $object_type, $status, $base_caps_only))) { return $selected_terms; } $qualifying_roles = $scoper->role_defs->qualify_roles($reqd_caps); if ($qualifying_term_assigner_roles = $scoper->role_defs->qualify_roles(array("assign_{$taxonomy}"))) { $qualifying_roles = array_merge($qualifying_roles, $qualifying_term_assigner_roles); } $user_terms = $scoper->qualify_terms_daterange($reqd_caps, $taxonomy, $qualifying_roles); foreach (array_keys($user_terms) as $date_key) { $date_clause = ''; if ($date_key && is_serialized($date_key)) { // Check stored post date against any role date limits associated whith this set of terms (if not stored, check current date) $content_date_limits = unserialize($date_key); $post_date_gmt = $object_id ? $scoper->data_sources->get_from_db('date', $src, $object_id) : 0; if (!$post_date_gmt) { $post_date_gmt = agp_time_gmt(); } if ($post_date_gmt < $content_date_limits->content_min_date_gmt || $post_date_gmt > $content_date_limits->content_max_date_gmt) { unset($user_terms[$date_key]); } } } $user_terms = agp_array_flatten($user_terms); $selected_terms = array_intersect($selected_terms, $user_terms); return $selected_terms; }
function ui_object_roles($src_name, $args = array()) { $defaults = array('object_type' => ''); $args = array_merge($defaults, (array) $args); extract($args); if (!($src = $this->scoper->data_sources->get($src_name))) { return; } if (!$object_type) { if (!($object_type = cr_find_object_type($src_name))) { return; } } $object_id = scoper_get_object_id($src_name, $object_type); if (!$this->scoper_admin->user_can_admin_object($src_name, $object_type, $object_id)) { return; } $this->init_item_roles_ui(); $this->item_roles_ui->single_object_roles_ui($src_name, $object_type, $object_id); }
function users_queryroles($reqd_caps, $src_name, $object_id = '', $args = array()) { $defaults = array('roles' => '', 'user' => '', 'querying_groups' => 0, 'use_term_roles' => 1, 'use_blog_roles' => 1, 'skip_object_roles' => false, 'ignore_strict_terms' => 0, 'object_terms' => array(), 'object_type' => '', 'objscope_roles' => '', 'any_object' => false); $args = array_merge($defaults, (array) $args); extract($args); $src = $this->scoper->data_sources->get($src_name); // ---- The following default argument generation is included to support potential direct usage of this function // (not needed for flt_users_where call ----------------- // Treat empty reqd_caps array as an error if (empty($reqd_caps)) { return array(); } $reqd_caps = (array) $reqd_caps; // Calling function may save us a little work if it has already made this call if (!$roles) { if (!($roles = $this->scoper->role_defs->qualify_roles($reqd_caps))) { return array(); } } else { $roles = (array) $roles; } // this set of reqd_caps cannot be satisfied by any role if (!$reqd_caps && !$roles) { return; } if ($object_id && !$src_name) { $object_id = 0; } // ----------------------------------------------------------------------------------- // Default to not honoring custom user caps, but support option $custom_user_blogcaps = SCOPER_CUSTOM_USER_BLOGCAPS; if (!$object_type) { if ($object_types = $this->scoper->cap_defs->object_types_from_caps($reqd_caps, $src_name)) { if (count($object_types) == 1) { $object_type = reset($object_types); } } if (!$object_type) { $object_type = cr_find_object_type($src_name, $object_id); } } // RS roles are object type-specific $roles_wp = $this->scoper->role_defs->filter($roles, array('role_type' => 'wp')); $roles_rs = $this->scoper->role_defs->filter($roles, array('role_type' => 'rs')); $this_otype_roles = $this->scoper->role_defs->get_matching('rs', $src_name, $object_type); $roles_rs = array_intersect_key($roles_rs, $this_otype_roles); $roles = array_merge($roles_rs, $roles_wp); $qualifying_roles = array(); // --------- ACCOUNT FOR OBJECT ROLES ----------- // If this set of reqd_caps can be satisfied by a scopable role, check for object role assignements if (!$skip_object_roles && ($object_id || $any_object)) { // exclude roles which have never been assigned to any object if ($object_roles = $this->scoper->qualify_object_roles($reqd_caps, $object_type, -1)) { $qualifying_roles[OBJECT_SCOPE_RS][''] = scoper_role_handles_to_names(array_keys($roles)); } } // If this inquiry is for a particular object, find out which roles must be object-assigned for it if ($object_id) { // For term and blog role clauses, exclude roles which require object assignment for that object // But don't disqualify a role if any of the roles it "contains" also qualify and are not object-scoped. // (i.e. If the required caps are satisfied by admin, editor and contributor, the actual minimum requirement // is contributor. A specification that admin and editor roles "require object assignment" does not apply // in this scenario. if (!is_array($objscope_roles)) { $objscope_roles = $this->get_objscope_roles($src_name, $object_id, '', true); } if ($objscope_roles) { $contained_roles = array(); $roles_wp = $this->scoper->role_defs->filter($roles, array('role_type' => 'wp')); foreach (array_keys($roles_wp) as $role_handle) { // If scoping with RS roles, this will also have the effect of disqualifying a WP blog role if all of the qualifying RS roles it contains are objscoped. $contained_roles[$role_handle] = $this->scoper->role_defs->get_contained_roles($role_handle, false, 'rs'); $contained_roles[$role_handle] = array_intersect_key($contained_roles[$role_handle], $roles); if (!array_diff_key($contained_roles[$role_handle], $objscope_roles)) { unset($roles[$role_handle]); } } foreach (array_keys($roles) as $role_handle) { $contained_roles[$role_handle] = $this->scoper->role_defs->get_contained_roles($role_handle, true, 'rs'); //true: include this role in return array $contained_roles[$role_handle] = array_intersect_key($contained_roles[$role_handle], $roles); if (!array_diff_key($contained_roles[$role_handle], $objscope_roles)) { unset($roles[$role_handle]); } } } } // --------- ACCOUNT FOR TERM ROLES ----------- // Consider term scope settings and role assignments // $uses_taxonomies = scoper_get_taxonomy_usage($src_name, $object_type); if ($use_term_roles && $src_name && $roles && !empty($uses_taxonomies)) { // If scoping with RS roles, strip out WP role definitions (which were included for blogrole clause) $term_roles = $this->scoper->role_defs->filter($roles, array('role_type' => 'rs')); if ($term_roles) { foreach ($uses_taxonomies as $taxonomy) { // include users with a sufficient term role assignment in any term $qualifying_roles[TERM_SCOPE_RS][$taxonomy] = scoper_role_handles_to_names(array_keys($term_roles)); } } // Honor blog-wide assignment of any non-objscope role, but only if at least one term // is not "strict" (i.e. merges blogroles into term-specific assignments). if (!$ignore_strict_terms) { $term_roles = $this->get_unrestricted_term_roles($term_roles, $uses_taxonomies, $object_id, $object_terms); // disqualify a WP blog role if all of the qualifying RS roles it contains were excluded by the strict terms filter. if ($roles_wp = $this->scoper->role_defs->filter($roles, array('role_type' => 'wp'))) { $contained_roles = array(); foreach (array_keys($roles_wp) as $role_handle) { $contained_roles[$role_handle] = $this->scoper->role_defs->get_contained_roles($role_handle, false, 'rs'); $contained_roles[$role_handle] = array_intersect_key($contained_roles[$role_handle], $roles); if (!$term_roles || !$contained_roles[$role_handle] || !array_intersect_key($contained_roles[$role_handle], $term_roles)) { unset($roles[$role_handle]); } } } $roles_current = $this->scoper->role_defs->filter($roles, array('role_type' => 'rs')); foreach (array_keys($roles_current) as $role_handle) { if (!isset($term_roles[$role_handle])) { unset($roles[$role_handle]); } } // Since this term role is restricted for all terms, prevent corresponding blog role from being added to qualifying_roles array by subsequent code } } // --------- ACCOUNT FOR BLOG ROLES ----------- // For each qualifying role, recognize blog assignment if the reqd_caps set is not associated // with a defined data source, if this source/object type does not use term roles, // or if some of the the terms are not strict. // // Note that WP blogrole assignments (if not taxonomy or object-scoped) are honored // regardless of Role Scoper role_type setting. if ($use_blog_roles) { if ($admin_roles = awp_administrator_roles()) { $roles = $roles ? array_merge($roles, $admin_roles) : $admin_roles; } if ($roles) { $role_types = array('rs', 'wp'); foreach ($role_types as $role_type) { //if ( ('rs' == $role_type) && ! RS_BLOG_ROLES ) // rs_blog_roles option has never been active in any RS release; leave commented here in case need arises // continue; $this_type_roles = $this->scoper->role_defs->filter($roles, array('role_type' => $role_type)); $qualifying_roles[BLOG_SCOPE_RS][$role_type] = scoper_role_handles_to_names(array_keys($this_type_roles)); } } if ($custom_user_blogcaps && $use_blog_roles) { // If custom user blogcaps option is enabled, this function is called separately for each reqd cap. // Custom user caps are stored as "hidden" single-cap role of type WP_CAP, sync'd to WP usermeta storage. if ($custom_user_blogcaps) { $qualifying_roles[BLOG_SCOPE_RS]['wp_cap'] = $reqd_caps; } // ...which contains one cap } } return $qualifying_roles; }
function mnt_create_object($src_name, $args, $object_id, $object = '') { $defaults = array('object_type' => ''); $args = array_intersect_key($defaults, (array) $args); extract($args); static $inserted_objects; if (!isset($inserted_objects)) { $inserted_objects = array(); } // so this filter doesn't get called by hook AND internally if (isset($inserted_objects[$src_name][$object_id])) { return; } if (empty($object_type)) { if ($col_type = $GLOBALS['scoper']->data_sources->member_property($src_name, 'cols', 'type')) { $object_type = isset($object->{$col_type}) ? $object->{$col_type} : ''; } } if (empty($object_type)) { if (!isset($object)) { $object = ''; } $object_type = cr_find_object_type($src_name, $object_id, $object); } if ($object_type == 'revision') { return; } $inserted_objects[$src_name][$object_id] = 1; if ('post' == $src_name) { $post_type_obj = get_post_type_object($object_type); if ($post_type_obj->hierarchical) { scoper_flush_cache_groups('get_pages'); } } }
function scoper_get_parent_roles($obj_or_term_id, $scope, $src_or_tx_name, $parent_id, $object_type = '') { global $wpdb, $scoper; $role_clause = ''; if (!$parent_id && OBJECT_SCOPE_RS == $scope) { // for default roles, need to distinguish between otype-specific roles // (note: this only works w/ RS role type. Default object roles are disabled for WP role type because we'd be stuck assigning all default roles to both post & page.) $src = $scoper->data_sources->get($src_or_tx_name); if (!empty($src->cols->type)) { if (!$object_type) { $object_type = cr_find_object_type($src_name, $object_id); } if ($object_type) { $role_defs = $scoper->role_defs->get_matching('rs', $src_or_tx_name, $object_type); if ($role_names = scoper_role_handles_to_names(array_keys($role_defs))) { $role_clause = "AND role_type = 'rs' AND role_name IN ('" . implode("', '", $role_names) . "')"; } } } } // Since this is a new object, propagate roles from parent (if any are marked for propagation) $qry = "SELECT * FROM {$wpdb->user2role2object_rs} WHERE scope = %s AND assign_for IN ('children', 'both') {$role_clause} AND src_or_tx_name = %s AND obj_or_term_id = %d ORDER BY role_type, role_name"; $results = scoper_get_results($wpdb->prepare($qry, $scope, $src_or_tx_name, $parent_id)); return $results; }
function _flt_user_has_cap($wp_blogcaps, $orig_reqd_caps, $args) { // =============================== STATIC VARIABLE DECLARATION AND INITIALIZATION (to memcache filtering results) ===== static $cache_tested_ids; static $cache_okay_ids; static $cache_where_clause; if (empty($cache_tested_ids)) { $cache_where_clause = array(); $cache_tested_ids = array(); $cache_okay_ids = array(); } // ==================================================================================================================== // =============================================== TEMPORARY DEBUG CODE ================================================ //dump($orig_reqd_caps); //dump($args); //if ( strpos( $_SERVER['REQUEST_URI'], 'ajax' ) ) { //if ( ! empty($_REQUEST) ) // rs_errlog( serialize($_REQUEST) ); //rs_errlog( '' ); //rs_errlog('flt_user_has_cap'); //rs_errlog(serialize($orig_reqd_caps)); //rs_errlog(serialize($args)); //rs_errlog(''); //} // ============================================= (end temporary debug code) ============================================== // convert 'rs_role_name' to corresponding caps (and also make a tinkerable copy of orig_reqd_caps) $orig_reqd_caps = $this->scoper->role_defs->role_handles_to_caps($orig_reqd_caps); // ================= EARLY EXIT CHECKS (if the provided reqd_caps do not need filtering or need special case filtering ================== global $pagenow; // Disregard caps which are not defined in Role Scoper config if (!($rs_reqd_caps = array_intersect($orig_reqd_caps, $this->scoper->cap_defs->get_all_keys()))) { return $wp_blogcaps; } // log initial set of RS-filtered caps (in case we swap in equivalent caps for intermediate processing) $orig_reqd_caps = $rs_reqd_caps; // permitting this filter to execute early in an attachment request resets the found_posts record, preventing display in the template if (is_attachment() && !is_admin() && !did_action('template_redirect')) { if (empty($GLOBALS['scoper_checking_attachment_access'])) { return $wp_blogcaps; } } // work around bug in mw_EditPost method (requires publish_pages AND publish_posts cap) if (defined('XMLRPC_REQUEST') && 'publish_posts' == $orig_reqd_caps[0]) { if (!empty($GLOBALS['xmlrpc_post_type_rs']) && 'page' == $GLOBALS['xmlrpc_post_type_rs']) { return array('publish_posts' => true); } } // backdoor to deal with rare cases where one of the caps included in RS role defs cannot be filtered properly if (defined('UNSCOPED_CAPS_RS') && !array_diff($orig_reqd_caps, explode(',', UNSCOPED_CAPS_RS))) { return $wp_blogcaps; } // custom workaround to reveal all private / restricted content in all blogs if logged into main blog if (defined('SCOPER_MU_MAIN_BLOG_RULES')) { include_once dirname(__FILE__) . '/mu-custom.php'; if (!array_diff($orig_reqd_caps, array('read', 'read_private_pages', 'read_private_posts'))) { if ($return_caps = ScoperMU_Custom::current_user_logged_into_main($wp_blogcaps, $orig_reqd_caps)) { return $return_caps; } } } //define( 'SCOPER_NO_COMMENT_FILTERING', true ); if (defined('SCOPER_NO_COMMENT_FILTERING') && 'moderate_comments' == $orig_reqd_caps[0] && empty($GLOBALS['current_rs_user']->allcaps['moderate_comments'])) { return $wp_blogcaps; } if (defined('SCOPER_ALL_UPLOADS_EDITABLE') && $pagenow == 'upload.php' && in_array($orig_reqd_caps[0], array('upload_files', 'edit_others_posts', 'delete_others_posts'))) { return $wp_blogcaps; } // =================================================== (end early exit checks) ====================================================== // ============================ GLOBAL VARIABLE DECLARATIONS, ARGUMENT TRANSLATION AND STATUS DETECTION ============================= global $current_rs_user; $user_id = isset($args[1]) ? $args[1] : 0; if ($user_id && $user_id != $current_rs_user->ID) { $user = rs_get_user($user_id); } else { $user = $current_rs_user; } // currently needed for filtering async-upload.php if (empty($user->blog_roles) || empty($user->blog_roles[''])) { $this->scoper->refresh_blogroles(); } $object_id = isset($args[2]) ? (int) $args[2] : 0; // WP passes comment ID with 'edit_comment' metacap if ($object_id && 'edit_comment' == $args[0]) { if (!in_array('moderate_comments', $rs_reqd_caps)) { // as of WP 3.2.1, 'edit_comment' maps to related post's 'edit_post' caps without requiring moderate_comments if (scoper_get_option('require_moderate_comments_cap')) { $rs_reqd_caps[] = 'moderate_comments'; $modified_caps = true; } } if ($comment = get_comment($object_id)) { $object_id = $comment->comment_post_ID; } else { $object_id = 0; } } // note the data source and object type(s) which are associated with the required caps (based on inclusion in RS Role Definitions) $is_taxonomy_cap = false; $src_name = ''; $cap_types = $this->scoper->cap_defs->src_otypes_from_caps($rs_reqd_caps, $src_name); // note: currently only needed for src_name determination $doing_admin_menus = is_admin() && (did_action('_admin_menu') && !did_action('admin_menu') || did_action('admin_head') && !did_action('adminmenu')); // for scoped menu management roles, satisfy edit_theme_options cap requirement if (array_key_exists(0, $orig_reqd_caps) && 'edit_theme_options' == $orig_reqd_caps[0] && empty($wp_blogcaps['edit_theme_options'])) { if (in_array($GLOBALS['pagenow'], array('nav-menus.php', 'admin-ajax.php')) || $doing_admin_menus) { $key = array_search('edit_theme_options', $rs_reqd_caps); if (false !== $key) { $tx = get_taxonomy('nav_menu'); $rs_reqd_caps[$key] = $tx->cap->manage_terms; $src_name = 'nav_menu'; // menu-specific manager assignment does not permit deletion of the menu if (!empty($_REQUEST['action']) && 'delete' == $_REQUEST['action']) { $this->skip_any_term_check = true; } } } } if (!$src_name) { // required capabilities correspond to multiple data sources return $wp_blogcaps; } // slight simplification: assume a single cap object type for a few cap substitution checks $is_taxonomy_cap = $this->scoper->cap_defs->member_property(reset($rs_reqd_caps), 'is_taxonomy_cap'); // Establish some context by detecting object type - based on object ID if provided, or otherwise based on http variables. if (in_array($pagenow, array('media-upload.php', 'async-upload.php'))) { if (!empty($GLOBALS['post'])) { $object_type = $GLOBALS['post']->post_type; } } elseif (is_admin() && 'edit-tags.php' == $GLOBALS['pagenow'] && 'link_category' == $_REQUEST['taxonomy']) { $src_name = 'link'; $object_type = 'link_category'; } elseif (array_key_exists(0, $orig_reqd_caps) && in_array($orig_reqd_caps[0], array('manage_nav_menus', 'edit_theme_options'))) { $src_name = 'nav_menu'; } if (empty($object_type)) { $object_type = cr_find_object_type($src_name, $object_id); } $object_type_obj = cr_get_type_object($src_name, $object_type); $is_att_rev = false; if ('post' == $src_name) { if (in_array($object_type, array('attachment', 'revision'))) { $is_att_rev = true; if ($object_id) { if ($_post = get_post($object_id)) { if ($_parent = get_post($_post->post_parent)) { $object_type = $_parent->post_type; $object_id = $_parent->ID; // deal with case of edit_posts cap check on attachments to revision (with Revisionary) if ('revision' == $object_type) { if ($_orig_post = get_post($_parent->post_parent)) { $object_type = $_orig_post->post_type; $object_id = $_orig_post->ID; } } $object_type_obj = get_post_type_object($object_type); } } } } elseif (!$is_taxonomy_cap) { $use_post_types = scoper_get_option('use_post_types'); if (empty($use_post_types[$object_type])) { return $wp_blogcaps; } } } // ===================================================================================================================================== // ======================================== SUBVERT MISGUIDED CAPABILITY REQUIREMENTS ================================================== if ('post' == $src_name) { if (!$is_taxonomy_cap) { $modified_caps = false; if ('post' != $object_type) { $replace_post_caps = array('publish_posts', 'edit_others_posts', 'edit_published_posts'); // Replace edit_posts requirement with corresponding type-specific requirement, but only after admin menu is drawn, or on a submission before the menu is drawn if (did_action('admin_init')) { // otherwise extra padding between menu items due to some items populated but unpermitted $replace_post_caps[] = 'edit_posts'; } if (in_array($pagenow, array('upload.php', 'media.php'))) { $replace_post_caps = array_merge($replace_post_caps, array('delete_posts', 'delete_others_posts')); } foreach ($replace_post_caps as $post_cap_name) { $key = array_search($post_cap_name, $rs_reqd_caps); if (false !== $key && !$doing_admin_menus && in_array($pagenow, array('edit.php', 'post.php', 'post-new.php', 'press-this.php', 'admin-ajax.php', 'upload.php', 'media.php'))) { $rs_reqd_caps[$key] = $object_type_obj->cap->{$post_cap_name}; $modified_caps = true; } } } // WP core quirk workaround: edit_others_posts is required as preliminary check for populating authors dropdown for any post type. Instead, we need to do our own validation based on scoped roles. // (but don't mess if this cap requirement is part of an edit_post metacap check for a specific post) if (!$object_id && count($rs_reqd_caps) == 1) { if (in_array(reset($rs_reqd_caps), array('edit_others_posts'))) { require_once dirname(__FILE__) . '/lib/agapetry_wp_admin_lib.php'; // function awp_metaboxes_started() if (!awp_metaboxes_started($object_type) && 'revision.php' != $pagenow && 'revisions' != $GLOBALS['plugin_page_cr']) { // don't enable contributors to view/restore revisions $rs_reqd_caps[0] = $object_type_obj->cap->edit_posts; } else { $rs_reqd_caps[0] = $object_type_obj->cap->edit_published_posts; } // we will filter / suppress the author dropdown downstream from here $modified_caps = true; } } // as of WP 3.1, addition of new nav menu items requires edit_posts capability (otherwise nav menu item is orphaned with no menu relationship) if (is_admin() && strpos($_SERVER['SCRIPT_NAME'], 'nav-menus.php')) { if ('edit_posts' == $orig_reqd_caps[0]) { $type_obj = get_taxonomy('nav_menu'); $rs_reqd_caps[0] = $type_obj->cap->manage_terms; $modified_caps = true; } } } // endif not taxonomy cap } // endif caps correspond to 'post' data source //====================================== (end subvert misguided capability requirements) ============================================= if (defined('RVY_VERSION')) { require_once dirname(__FILE__) . '/revisionary-helper_rs.php'; $rs_reqd_caps = Rvy_Helper::convert_post_edit_caps($rs_reqd_caps, $object_type); } //rs_errlog( "matched context for $object_id : $matched_context" ); // don't apply object-specific filtering for auto-drafts if ('post' == $src_name) { if ($object_id) { if ($_post = get_post($object_id)) { if ('auto-draft' == $_post->post_status) { // && ! empty($_POST['action']) ) $object_id = 0; if (!$doing_admin_menus) { $this->skip_id_generation = true; } } } } else { if (!empty($GLOBALS['post']) && !is_object($GLOBALS['post'])) { $GLOBALS['post'] = get_post($GLOBALS['post']); } if (!empty($GLOBALS['post']) && 'auto-draft' == $GLOBALS['post']->post_status && !$doing_admin_menus) { $this->skip_id_generation = true; } } } //dump($object_id); // If no object id was passed in... if (!$object_id) { // || ! $matched_context ) { //if ( $missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps) ) ) { if (!$doing_admin_menus) { if (!empty($_REQUEST['action']) && in_array($pagenow, array('edit.php', 'edit-tags.php'))) { $this->skip_id_generation = true; } // ============================================ OBJECT ID DETERMINATION ======================================== if (!$this->skip_id_generation && !defined('XMLRPC_REQUEST') && !in_array($pagenow, array('media-upload.php', 'async-upload.php'))) { // lots of superfluous queries in media upload popup otherwise // Try to generate missing object_id argument for problematic current_user_can calls static $generated_id; if (!isset($generated_id)) { $generated_id = array(); } // if the id was not already detected and stored to the static variable... $caps_key = serialize($rs_reqd_caps); if (!isset($generated_id[$object_type][$caps_key])) { $gen_id = 0; foreach ($rs_reqd_caps as $cap_name) { if ($gen_id = (int) $this->_detect_object_id($cap_name)) { break; // means we are accepting the generated id } } $generated_id[$object_type][$caps_key] = $gen_id; $object_id = $gen_id; } else { $object_id = $generated_id[$object_type][$caps_key]; } //rs_errlog( "detected ID: $object_id" ); } else { $this->skip_id_generation = false; } // this is a one-time flag // ========================================= (end object id determination) ======================================= } // If we still have no object id (detection was skipped or failed to identify it)... if (!$object_id) { // || ! $matched_context ) { // ============================================ "CAN FOR ANY" CHECKS =========================================== if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) { // These checks are only relevant since no object_id was provided. Otherwise (in the main body of this function), taxonomy and object caps will be credited via scoped query. // If we are about to fail the blogcap requirement, credit a missing cap if the user has it by term role for ANY term. // This prevents failing initial UI entrance exams that only consider blog-wide roles. if (!$this->skip_any_term_check) { if ($tax_caps = $this->user_can_for_any_term($missing_caps)) { $wp_blogcaps = array_merge($wp_blogcaps, $tax_caps); } //rs_errlog( "can for any term: " . serialize($tax_caps) ); } else { $this->skip_any_term_check = false; } // this is a one-time flag // If we are still missing required caps, credit a missing scoper-defined cap if the user has it by object role for ANY object. // (i.e. don't bar user from "Edit Pages" if they have edit_pages cap for at least one page) if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) { // prevent object-specific editing roles from allowing new object creation w/o sitewide capability $add_new_check = strpos($_SERVER['SCRIPT_NAME'], 'post-new.php') && 'post' == $src_name && reset($rs_reqd_caps) == $object_type_obj->cap->edit_posts; if (!$this->skip_any_object_check && !$add_new_check) { //if ( ! $this->skip_any_object_check ) { if ($object_caps = $this->user_can_for_any_object($missing_caps)) { $wp_blogcaps = array_merge($wp_blogcaps, $object_caps); } //rs_errlog( "can for any object: " . serialize($object_caps) ); } else { $this->skip_any_object_check = false; } // this is a one-time flag } } // ========================================== (end "can for any" checks ) ========================================= //rs_errlog( serialize( $wp_blogcaps) ); if ($missing_caps = array_diff($rs_reqd_caps, array_keys($wp_blogcaps))) { // normal exit point when no object ID is passed or detected, or when detected object type does not match required capabilities return $wp_blogcaps; } else { if ($restore_caps = array_diff($orig_reqd_caps, $rs_reqd_caps)) { // restore original reqd_caps which we substituted for the type-specific scoped query $wp_blogcaps = array_merge($wp_blogcaps, array_fill_keys($restore_caps, true)); } return $wp_blogcaps; } } //} else //return $wp_blogcaps; } if ($object_id && 'post' == $src_name) { $_post = get_post($object_id); $object_type = $_post->post_type; $object_type_obj = cr_get_type_object($src_name, $object_type); if (defined('RVY_VERSION') && in_array($pagenow, array('edit.php', 'edit-tags.php', 'admin-ajax.php')) && (!empty($_REQUEST['action']) && -1 != $_REQUEST['action'])) { $rs_reqd_caps = Rvy_Helper::fix_table_edit_reqd_caps($rs_reqd_caps, $args[0], $_post, $object_type_obj); } // if the top level page structure is locked, don't allow non-administrator to delete a top level page either if ('page' == $object_type || defined('SCOPER_LOCK_OPTION_ALL_TYPES') && !is_content_administrator_rs()) { $delete_metacap = !empty($object_type_obj->hierarchical) ? $object_type_obj->cap->delete_post : 'delete_page'; // if the top level page structure is locked, don't allow non-administrator to delete a top level page either if ($delete_metacap == $args[0]) { if ('1' === scoper_get_option('lock_top_pages')) { // stored value of 1 means only Administrators are allowed to modify top-level page structure if ($page = get_post($args[2])) { if (empty($page->post_parent)) { $in_process = false; return false; } } } } } } // Note: At this point, we have a nonzero object_id... // if this is a term administration request, route to user_can_admin_terms() if ($is_taxonomy_cap) { if ('post' == $src_name) { $cap_otype_obj = get_taxonomy($object_type); } if (('post' != $src_name || $cap_otype_obj && $rs_reqd_caps[0] == $cap_otype_obj->cap->manage_terms) && count($rs_reqd_caps) == 1) { // don't re-route if multiple caps are being required // always pass through any assigned blog caps which will not be involved in this filtering $rs_reqd_caps = array_fill_keys($rs_reqd_caps, 1); $undefined_reqd_caps = array_diff_key($wp_blogcaps, $rs_reqd_caps); require_once dirname(__FILE__) . '/admin/permission_lib_rs.php'; if (user_can_admin_terms_rs($object_type, $object_id, $user)) { return array_merge($undefined_reqd_caps, $rs_reqd_caps); } else { return $undefined_reqd_caps; // required caps we scrutinized are excluded from this array } } } // Workaround to deal with WP core's checking of publish cap prior to storing categories // Store terms to DB in advance of any cap-checking query which may use those terms to qualify an operation if (!empty($_REQUEST['action']) && (in_array($_REQUEST['action'], array('editpost', 'post')) || 'autosave' == $_REQUEST['action'])) { if (array_intersect(array('publish_posts', 'edit_posts', $object_type_obj->cap->publish_posts, $object_type_obj->cap->edit_posts), $rs_reqd_caps)) { $uses_taxonomies = scoper_get_taxonomy_usage($src_name, $object_type); static $inserted_terms; if (!isset($inserted_terms)) { $inserted_terms = array(); } foreach ($uses_taxonomies as $taxonomy) { // TODO: only if tx->requires_term is true? if (isset($inserted_terms[$taxonomy][$object_id])) { continue; } $inserted_terms[$taxonomy][$object_id] = true; //if ( $stored_terms = wp_get_object_terms( $object_id, $taxonomy ) ) // note: this will cause trouble if WP core ever auto-stores object terms on post creation // continue; $stored_terms = $this->scoper->get_terms($taxonomy, UNFILTERED_RS, COL_ID_RS, $object_id); require_once dirname(__FILE__) . '/admin/filters-admin-save_rs.php'; $selected_terms = cr_get_posted_object_terms($taxonomy); if (is_array($selected_terms)) { // non-hierarchical terms do not need to be pre-inserted if ($set_terms = $GLOBALS['scoper_admin_filters']->flt_pre_object_terms($selected_terms, $taxonomy)) { $set_terms = array_unique(array_map('intval', $set_terms)); if ($set_terms != $stored_terms && $set_terms && $set_terms != array(1)) { // safeguard against unintended clearing of stored categories wp_set_object_terms($object_id, $set_terms, $taxonomy); // delete any buffered cap check results which were queried prior to storage of these object terms unset($cache_tested_ids); unset($cache_where_clause); unset($cache_okay_ids); } } } } // also avoid chicken-egg situation when publish cap is granted by a propagating page role if ($object_type_obj->hierarchical && isset($_POST['parent_id'])) { if ($_POST['parent_id'] != get_post_field('post_parent', $object_id)) { global $wpdb; $set_parent = $GLOBALS['scoper_admin_filters']->flt_page_parent($_POST['parent_id']); $GLOBALS['wpdb']->query("UPDATE {$wpdb->posts} SET post_parent = '{$set_parent}' WHERE ID = '{$object_id}'"); require_once dirname(__FILE__) . '/admin/filters-admin-save_rs.php'; scoper_inherit_parent_roles($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type); scoper_inherit_parent_restrictions($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type); } } } } // generate a string key for this set of required caps, for use below in checking, caching the scoped results $arg_append = ''; $arg_append .= !empty($this->require_full_object_role) ? '-require_full_object_role-' : ''; $arg_append .= !empty($GLOBALS['revisionary']->skip_revision_allowance) ? '-skip_revision_allowance-' : ''; sort($rs_reqd_caps); $capreqs_key = implode($rs_reqd_caps) . $arg_append; // see ScoperAdmin::user_can_admin_object // ================================ SPECIAL HANDLING FOR ATTACHMENTS AND REVISIONS ========================================== $maybe_revision = 'post' == $src_name && !isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]); $maybe_attachment = in_array($pagenow, array('upload.php', 'media.php')); if ($maybe_revision || $maybe_attachment) { global $wpdb; if ($_post = get_post($object_id)) { if ('revision' == $_post->post_type) { require_once dirname(__FILE__) . '/lib/revisions_lib_rs.php'; $rev_where = defined('RVY_VERSION') && rvy_get_option('revisor_lock_others_revisions') ? " AND post_author = '{$current_rs_user->ID}'" : ''; // might need to apply different cap requirement for other users' revisions. todo: skip this clause for sitewide editors $revisions = rs_get_post_revisions($_post->post_parent, 'inherit', array('fields' => constant('COL_ID_RS'), 'return_flipped' => true, 'where' => $rev_where)); } if ('revision' == $_post->post_type || 'attachment' == $_post->post_type) { $is_att_rev = true; if ($_post->post_parent) { $object_id = $_post->post_parent; if ($_parent = get_post($_post->post_parent)) { $object_type = $_parent->post_type; $object_type_obj = get_post_type_object($object_type); } } elseif ('attachment' == $_post->post_type) { // special case for unattached uploads: uploading user should have their way with them if ($_post->post_author == $current_rs_user->ID) { $rs_reqd_caps[0] = 'read'; if ($restore_caps = array_diff($orig_reqd_caps, array_keys($rs_reqd_caps))) { // restore original reqd_caps which we substituted for the type-specific scoped query $wp_blogcaps = array_merge($wp_blogcaps, array_fill_keys($restore_caps, true)); } } return $wp_blogcaps; } } //endif retrieved post is a revision or attachment } // endif post retrieved } // endif specified id might be a revision or attachment if ($is_att_rev) { if ('post' != $object_type_obj->name) { // Compensate for WP's requirement of posts cap for attachment editing, regardless of whether it's attached to a post or page if ('edit_others_posts' == $rs_reqd_caps[0]) { $rs_reqd_caps[0] = $object_type_obj->cap->edit_others_posts; } elseif ('delete_others_posts' == $rs_reqd_caps[0]) { $rs_reqd_caps[0] = $object_type_obj->cap->delete_others_posts; } elseif ('edit_posts' == $rs_reqd_caps[0]) { $rs_reqd_caps[0] = $object_type_obj->cap->edit_posts; } elseif ('delete_posts' == $rs_reqd_caps[0]) { $rs_reqd_caps[0] = $object_type_obj->cap->delete_posts; } } } //endif retrieved post is a revision or attachment // ============================== (end special handling for attachments and revisions) ========================================== // ============ SCOPED QUERY for required caps on object id (if other listed ids are known, query for them also). Cache results to static var. =============== // $force_refresh = 'async-upload.php' == $pagenow; // Page refresh following publishing of new page by users who can edit by way of Term Role fails without this workaround if (!empty($_POST) && (defined('SCOPER_CACHE_SAFE_MODE') || in_array($pagenow, array('post.php', 'press-this.php')) && $args[0] == $object_type_obj->cap->edit_post)) { $force_refresh = true; $cache_tested_ids = array(); $cache_okay_ids = array(); $cache_where_clause = array(); } else { $force_refresh = false; } // Check whether this object id was already tested for the same reqd_caps in a previous execution of this function within the same http request if ($force_refresh || !isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id])) { //if ( ! isset($cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]) ) { // retrieve CR_Data_Source object, which contains database column names $src_table = $this->scoper->data_sources->member_property($src_name, 'table'); $cols = $this->scoper->data_sources->member_property($src_name, 'cols'); // Before querying for caps on this object, check whether we have a record of other posts listed alongside it. // If so, run the scoped query for ALL listed objects in that buffer, and buffer the results to static variable hascap_object_ids. // // (This is useful when front end code must check caps for each post // to determine whether to display 'edit' link, etc.) if (is_admin() && 'index.php' == $pagenow) { // there's too much happening on the dashboard (and too much low-level query filtering) to buffer listed IDs reliably. $listed_ids = array(); } else { if (isset($this->scoper->listed_ids[$src_name])) { $listed_ids = array_keys($this->scoper->listed_ids[$src_name]); } else { // note: don't use wp_object_cache because it includes posts not present in currently displayed resultset listing page $listed_ids = array(); } } // make sure our current object_id is in the list $listed_ids[] = $object_id; // since the objects_where_role_clauses() output itself is not id-specific, also statically buffer it per reqd_caps if ($force_refresh || !isset($cache_where_clause[$src_name][$object_type][$capreqs_key])) { $check_otype = 'link_category' == $object_type ? 'link' : $object_type; $use_term_roles = scoper_get_otype_option('use_term_roles', $src_name, $check_otype); $no_object_roles = $this->scoper->data_sources->member_property($src_name, 'no_object_roles'); $use_object_roles = $no_object_roles ? false : scoper_get_otype_option('use_object_roles', $src_name, $object_type); $this_args = array('object_type' => $object_type, 'user' => $user, 'otype_use_term_roles' => $use_term_roles, 'otype_use_object_roles' => $use_object_roles, 'skip_teaser' => true, 'require_full_object_role' => !empty($this->require_full_object_role)); //rs_errlog( serialize($rs_reqd_caps) ); //rs_errlog( serialize($this_args) ); $where = $this->query_interceptor->objects_where_role_clauses($src_name, $rs_reqd_caps, $this_args); if ($where) { $where = "AND ( {$where} )"; } // update static variable $cache_where_clause[$src_name][$object_type][$capreqs_key] = $where; } else { $where = $cache_where_clause[$src_name][$object_type][$capreqs_key]; } // run the query $query = "SELECT {$src_table}.{$cols->id} FROM {$src_table} WHERE 1=1 {$where} AND {$src_table}.{$cols->id} IN ('" . implode("', '", array_unique($listed_ids)) . "')"; if (isset($cache_okay_ids[$query])) { $okay_ids = $cache_okay_ids[$query]; } else { if ($okay_ids = scoper_get_col($query)) { $okay_ids = array_fill_keys($okay_ids, true); } } //dump($rs_reqd_caps); //dump($query); //dump($okay_ids); //rs_errlog( $query ); //rs_errlog( 'results: ' . serialize( $okay_ids ) ); // update static cache_tested_ids to log scoped results for this object id, and possibly also for other listed IDs if (empty($_GET['doaction']) || 'delete_post' != $args[0] && $object_type_obj->cap->delete_post != $args[0]) { // bulk post/page deletion is broken by hascap buffering foreach ($listed_ids as $_id) { $cache_tested_ids[$src_name][$object_type][$capreqs_key][$_id] = isset($okay_ids[$_id]); } $cache_okay_ids[$query] = $okay_ids; } $this_id_okay = isset($okay_ids[$object_id]); } else { // results of this same has_cap inquiry are already stored (from another call within current http request) $this_id_okay = $cache_tested_ids[$src_name][$object_type][$capreqs_key][$object_id]; } //rs_errlog( "okay ids: " . serialize( $okay_ids ) ); // if we redirected the cap check to revision parent, also credit all the revisions for passing results if ($this_id_okay && !empty($revisions)) { if (empty($_GET['doaction']) || 'delete_post' != $args[0] && $object_type_obj->cap->delete_post != $args[0]) { // bulk post/page deletion is broken by hascap buffering $cache_tested_ids[$src_name][$object_type][$capreqs_key] = $cache_tested_ids[$src_name][$object_type][$capreqs_key] + array_fill_keys($revisions, true); } } $rs_reqd_caps = array_fill_keys($rs_reqd_caps, true); if (!$this_id_okay) { if (array_key_exists(0, $orig_reqd_caps) && 'edit_posts' == $orig_reqd_caps[0] && strpos($_SERVER['REQUEST_URI'], 'async-upload.php')) { // temp workaround for ACF with Revisionary return $wp_blogcaps; } // ================= TEMPORARY DEBUG CODE =================== //d_echo("object_id $object_id FAILED !!!!!!!!!!!!!!!!!" ); //rs_errlog( "object_id $object_id FAILED !!!!!!!!!!!!!!!!!" ); //rs_errlog(serialize($orig_reqd_caps)); //rs_errlog(serialize($rs_reqd_caps)); //rs_errlog(''); /* $log .= "checked caps: " . serialize($rs_reqd_caps) . "\r\n"; $log .= "object_id $object_id FAILED !!!!!!!!!!!!!!!!!\r\n"; $log .= $query; rs_errlog( "\r\n{$log}\r\n" ); */ //d_echo( "FAILED for " . serialize($rs_reqd_caps) ); // ============== (end temporary debug code ================== return array_diff_key($wp_blogcaps, $rs_reqd_caps); // required caps we scrutinized are excluded from this array } else { if ($restore_caps = array_diff($orig_reqd_caps, array_keys($rs_reqd_caps))) { // restore original reqd_caps which we substituted for the type-specific scoped query $rs_reqd_caps = $rs_reqd_caps + array_fill_keys($restore_caps, true); } //d_echo( 'OKAY:' ); //dump($args); //dump($rs_reqd_caps); //d_echo( '<br />' ); return array_merge($wp_blogcaps, $rs_reqd_caps); } }