function get_terms_reqd_caps($taxonomy, $operation = '', $is_term_admin = false) { global $pagenow; if (!($src_name = $this->taxonomies->member_property($taxonomy, 'object_source'))) { if (taxonomy_exists($taxonomy)) { $src_name = 'post'; } } $return_caps = array(); $is_term_admin = $is_term_admin || in_array($pagenow, array('edit-tags.php')) || ('nav_menu' == $taxonomy && 'nav-menus.php' == $pagenow || 'admin-ajax.php' == $pagenow && (!empty($_REQUEST['action']) && in_array($_REQUEST['action'], array('add-menu-item', 'menu-locations-save')))); // possible TODO: abstract for non-WP taxonomies if ($is_term_admin) { // query pertains to the management of terms if ('post' == $src_name) { $taxonomy_obj = get_taxonomy($taxonomy); $return_caps[$taxonomy] = array($taxonomy_obj->cap->manage_terms); } elseif ('link_category' == $taxonomy) { $return_caps[$taxonomy] = array('manage_categories'); } else { global $scoper; $cap_defs = $scoper->cap_defs->get_matching($src_name, $taxonomy, OP_ADMIN_RS); $return_caps[$taxonomy] = $cap_defs ? array_keys($cap_defs) : array(); } } else { // query pertains to reading or editing content within certain terms, or adding terms to content $base_caps_only = true; if ('post' == $src_name) { if (!$operation) { $operation = $this->is_front() || 'profile.php' == $pagenow || is_admin() && 's2' == $GLOBALS['plugin_page'] ? 'read' : 'edit'; } // hack to support subscribe2 categories checklist $status = 'read' == $operation ? 'publish' : 'draft'; // 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 ?) if (in_array($pagenow, array('post.php', 'post-new.php'))) { $object_type = cr_find_post_type(); } } else { if (!$operation) { $operation = $this->is_front() ? 'read' : 'edit'; } $status = ''; } // The return array will indicate term role enable / disable, as well as associated capabilities if (!empty($object_type)) { $check_object_types = array($object_type); } else { if ($check_object_types = (array) $this->data_sources->member_property($src_name, 'object_types')) { $check_object_types = array_keys($check_object_types); } } if ('post' == $src_name) { $use_post_types = scoper_get_option('use_post_types'); } $enabled_object_types = array(); foreach ($check_object_types as $_object_type) { if ($use_term_roles = scoper_get_otype_option('use_term_roles', $src_name, $_object_type)) { if (!empty($use_term_roles[$taxonomy])) { if ('post' != $src_name || !empty($use_post_types[$_object_type])) { $enabled_object_types[] = $_object_type; } } } } foreach ($enabled_object_types as $object_type) { $return_caps[$object_type] = cr_get_reqd_caps($src_name, $operation, $object_type, $status, $base_caps_only); } } return $return_caps; }
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 flt_objects_where($where, $src_name, $object_types = '', $args = array()) { $defaults = array('user' => '', 'use_object_roles' => -1, 'use_term_roles' => -1, 'taxonomies' => array(), 'request' => '', 'terms_query' => 0, 'force_reqd_caps' => '', 'alternate_reqd_caps' => '', 'source_alias' => '', 'required_operation' => '', 'terms_reqd_caps' => '', 'skip_teaser' => false); $args = array_merge($defaults, (array) $args); extract($args); // filtering in user_has_cap sufficiently controls revision access; a match here should be for internal, pre-validation purposes if (strpos($where, "post_type = 'revision'")) { return $where; } $where_prepend = ''; //rs_errlog ("object_where input: $where"); //rs_errlog (''); //d_echo ("<br /><strong>object_where input:</strong> $where<br />"); //echo "<br />$where<br />"; if (!is_object($user)) { $user = $GLOBALS['current_rs_user']; $args['user'] = $user; } if (!($src = $this->scoper->data_sources->get($src_name))) { return $where; } // the specified data source is not know to Role Scoper $src_table = !empty($source_alias) ? $source_alias : $src->table; // verify table name and id col definition (the actual existance checked at time of admin entry) if (!($src->table && $src->cols->id)) { rs_notice(sprintf('Role Scoper Configuration Error: table_basename or col_id are undefined for the %s data source.', $src_name)); return $where; } // need to allow ambiguous object type for special cap requirements like comment filtering $object_types = $this->_get_object_types($src, $object_types); $tease_otypes = array_intersect($object_types, $this->_get_teaser_object_types($src_name, $object_types, $args)); if (!empty($src->no_object_roles)) { $use_object_roles = false; } if ($terms_query && $terms_reqd_caps) { foreach (array_keys($terms_reqd_caps) as $_object_type) { $otype_status_reqd_caps[$_object_type][''] = $terms_reqd_caps[$_object_type]; } // terms request does not support multiple statuses } else { if ($force_reqd_caps && is_array($force_reqd_caps)) { $otype_status_reqd_caps = $force_reqd_caps; } else { global $wpdb; if (!$required_operation) { $required_operation = 'front' == CURRENT_ACCESS_NAME_RS ? OP_READ_RS : OP_EDIT_RS; } $preview_future = strpos($where, "{$wpdb->posts}.post_name =") || strpos($where, "{$wpdb->posts}.ID ="); if (!($otype_status_reqd_caps = cr_get_reqd_caps($src_name, $required_operation, -1, -1, false, $preview_future))) { return $where; } } $otype_status_reqd_caps = array_intersect_key($otype_status_reqd_caps, array_flip($object_types)); } // Since Role Scoper can restrict or expand access regardless of post_status, query must be modified such that // * the default owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" is removed // * all statuses are listed apart from owner inclusion clause (and each of these status clauses is subsequently replaced with a scoped equivalent which imposes any necessary access limits) // * a new scoped owner clause is constructed where appropriate (see $where[$cap_name]['owner'] in function objects_where_role_clauses() // if ($src->cols->owner && $user->ID) { // force standard query padding $where = preg_replace("/{$src->cols->owner}\\s*=\\s*/", "{$src->cols->owner} = ", $where); $where = str_replace(" {$src->cols->owner} =", " {$src_table}.{$src->cols->owner} =", $where); $where = str_replace(" {$src->cols->owner} IN", " {$src_table}.{$src->cols->owner} IN", $where); } if (!empty($src->query_replacements)) { foreach ($src->query_replacements as $find => $replace) { // for posts_request, remove the owner inclusion clause "OR post_author = [user_id] AND post_status = 'private'" because we'll account for each status based on properties of required caps $find_ = str_replace('[user_id]', $user->ID, $find); if (false !== strpos($find_, '[') || false !== strpos($find_, ']')) { rs_notice(sprintf('Role Scoper Config Error: invalid query clause search criteria for %1$s (%2$s).<br /><br />Valid placeholders are:<br />', $src_name, $find) . print_r(array_keys($map))); return ' AND 1=2 '; } $replace_ = str_replace('[user_id]', $user->ID, $replace); if (false !== strpos($replace_, '[') || false !== strpos($replace_, ']')) { rs_notice(sprintf('Role Scoper Config Error: invalid query clause replacement criteria for %1$s (%2$s).<br /><br />Valid placeholders are:<br />', $src_name, $replace) . print_r(array_keys($map))); return ' AND 1=2 '; } $where = str_replace($find_, $replace_, $where); } } $force_single_type = false; $col_type = !empty($src->cols->type) ? $src->cols->type : ''; if ($col_type) { // If the passed request contains a single object type criteria, maintain that status exclusively (otherwise include type-specific conditions for each available type) $matches = array(); $num_matches = preg_match_all("/{$col_type}\\s*=\\s*'([^']+)'/", $where, $matches); if (1 == $num_matches) { $force_single_type = true; $object_types = array($matches[1][0]); if ($matched_reqd_caps = array_intersect_key($otype_status_reqd_caps, array_flip($object_types))) { // sanity check prevents running with an empty reqd_caps array if something goes wrong with otype detection $otype_status_reqd_caps = $matched_reqd_caps; } } } if ('post' == $src_name && !array_intersect($object_types, array_keys(array_intersect(scoper_get_option('use_post_types'), array(true))))) { return $where; } elseif (empty($otype_status_reqd_caps)) { return ' AND 1=2 '; } $basic_status_clause = array(); $force_single_status = false; $status_clause_pos = 0; $col_status = !empty($src->cols->status) ? $src->cols->status : ''; if ($col_status) { // force standard query padding $where = preg_replace("/{$col_status}\\s*=\\s*'/", "{$col_status} = '", $where); $where = str_replace(" {$col_status} =", " {$src_table}.{$col_status} =", $where); $where = str_replace(" {$col_status} IN", " {$src_table}.{$col_status} IN", $where); foreach (array_keys($otype_status_reqd_caps) as $listing_otype) { foreach (array_keys($otype_status_reqd_caps[$listing_otype]) as $status) { $basic_status_clause[$status] = "{$src_table}.{$col_status} = '{$status}'"; } } // If the passed request contains a single status criteria, maintain that status exclusively (otherwise include status-specific conditions for each available status) // (But not if user is anon and hidden content teaser is enabled. In that case, we need to replace the default "status=publish" clause) $matches = array(); if ($num_matches = preg_match_all("/{$src_table}.{$col_status}\\s*=\\s*'([^']+)'/", $where, $matches)) { $status_clause_pos = strpos($where, $matches[0][0]); } // note the match position for use downstream if (1 == $num_matches) { $use_status = $matches[1][0]; // Eliminate a primary plugin incompatibility by skipping this preservation of existing single status requirements if we're on the front end and the requirement is 'publish'. // (i.e. include private posts that this user has access to via RS role assignment). if (!$this->scoper->is_front() || 'publish' != $use_status || empty($args['user']->ID) && empty($tease_otypes) || defined('SCOPER_RETAIN_PUBLISH_FILTER')) { $force_single_status = true; foreach (array_keys($otype_status_reqd_caps) as $_object_type) { $otype_status_reqd_caps[$_object_type] = array_intersect_key($otype_status_reqd_caps[$_object_type], array($use_status => true)); } } } } else { // this source doesn't define statuses $basic_status_clause = array('' => ''); } if (empty($skip_teaser) && !array_diff($object_types, $tease_otypes)) { if ($status_clause_pos && $force_single_type) { // All object types potentially returned by this query will have a teaser filter applied to results, so we don't need to filter the query // override our sanity safeguard against exposing private posts to anonymous readers if (empty($user->ID)) { // Since we're dropping out of this function early in advance of teaser filtering, // must take this opportunity to add private status to the query (otherwise WP excludes private for anon user) // (But don't do this if teaser is configured to hide private content) $check_otype = count($tease_otypes) && in_array('post', $tease_otypes) ? 'post' : $tease_otypes[0]; $post_type_obj = get_post_type_object($check_otype); if (!scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype) && (!$post_type_obj->hierarchical || scoper_get_otype_option('private_items_listable', 'post', 'page'))) { if ($col_status && isset($otype_status_reqd_caps[$check_otype])) { $status_or = "{$src_table}.{$col_status} = '" . implode("' OR {$src_table}.{$col_status} = '", array_keys($otype_status_reqd_caps[$check_otype])) . "'"; $where = str_replace($basic_status_clause['publish'], "( {$status_or} )", $where); } else { $where = str_replace($basic_status_clause['publish'], "1=1", $where); } } } } return $where; } $is_administrator = is_content_administrator_rs(); // make sure administrators never have content limited $status_or = ''; $status_where = array(); foreach ($otype_status_reqd_caps as $object_type => $status_reqd_caps) { if (!is_array($status_reqd_caps)) { rs_notice(sprintf('Role Scoper Configuration Error: reqd_caps for the %s data source must be array[operation][object_type][status] where operation is "read", "edit" or "admin".', $src_name)); return $where; } // don't bother generating these parameters if we're just going to pass the object type through for teaser filtering if (!in_array($object_type, $tease_otypes)) { if (true === $use_term_roles) { // if boolean true was passed in, force usage of all term roles if ('post' == $src_name) { //$otype_use_term_roles = array_fill_keys( get_taxonomies( array( 'public' => true, 'object_type' => $object_type ) ), 1 ); $otype_use_term_roles = array(); foreach (get_taxonomies(array('public' => true), 'object') as $taxonomy => $taxonomy_obj) { if (in_array($object_type, $taxonomy_obj->object_type)) { $otype_use_term_roles[$taxonomy] = 1; } } } else { $otype_use_term_roles = !empty($src->uses_taxonomies) ? array_fill_keys($src->uses_taxonomies, true) : array(); } } else { $check_object_type = 'link_category' == $object_type ? 'link' : $object_type; $otype_use_term_roles = -1 == $use_term_roles ? scoper_get_otype_option('use_term_roles', $src_name, $check_object_type) : false; } if (!$otype_use_term_roles && $terms_query) { continue; } // if a boolean was passed in, override the stored option $otype_use_object_roles = -1 == $use_object_roles ? scoper_get_otype_option('use_object_roles', $src_name, $object_type) : $use_object_roles; } else { $otype_use_term_roles = false; $otype_use_object_roles = false; } //now step through all statuses and corresponding cap requirements for this otype and access type // (will replace "col_status = status_name" with "col_status = status_name AND ( [scoper requirements] ) foreach ($status_reqd_caps as $status_name => $reqd_caps) { if ('trash' == $status_name) { // in wp-admin, we need to include trash posts for the count query, but not for the listing query unless trash status is requested if ((empty($this->last_request[$src_name]) || !strpos($this->last_request[$src_name], 'COUNT')) && (empty($_GET['post_status']) || 'trash' != $_GET['post_status'])) { continue; } } if ($is_administrator) { $status_where[$status_name][$object_type] = '1=1'; } elseif (empty($skip_teaser) && in_array($object_type, $tease_otypes)) { if ($terms_query && !$otype_use_object_roles) { $status_where[$status_name][$object_type] = '1=1'; } else { $status_where[$status_name][$object_type] = "{$src_table}.{$src->cols->type} = '{$object_type}'"; } } else { // filter defs for otypes which don't define a status will still have a single status element with value '' $args = array_merge($args, array('object_type' => $object_type, 'otype_use_term_roles' => $otype_use_term_roles, 'otype_use_object_roles' => $otype_use_object_roles)); $clause = $this->objects_where_role_clauses($src_name, $reqd_caps, $args); if (empty($clause) || '1=2' == $clause) { // this means no qualifying roles are available $status_where[$status_name][$object_type] = '1=2'; } elseif (count($otype_status_reqd_caps) > 1 && (!$terms_query || $otype_use_object_roles)) { // more than 1 object type $status_where[$status_name][$object_type] = "( {$src_table}.{$src->cols->type} = '{$object_type}' AND ( {$clause} ) )"; } else { $status_where[$status_name][$object_type] = $clause; } } } } // all otype clauses concat: object_type1 clause [OR] [object_type2 clause] [OR] ... foreach (array_keys($status_where) as $status_name) { if (isset($preserve_or_clause[$status_name])) { $status_where[$status_name][] = $preserve_or_clause[$status_name]; } if ($tease_otypes) { $check_otype = count($tease_otypes) && in_array('post', $tease_otypes) ? 'post' : $tease_otypes[0]; } // extra line of defense: even if upstream logic goes wrong, never disclose a private item to anon user (but if the where clause was passed in with explicit status=private, must include our condition) if ('private' == $status_name && !$force_single_status && empty($GLOBALS['current_user']->ID) && (!$tease_otypes || scoper_get_otype_option('teaser_hide_private', $src_name, $check_otype))) { unset($status_where[$status_name]); } else { $status_where[$status_name] = agp_implode(' ) OR ( ', $status_where[$status_name], ' ( ', ' ) '); } } // combine identical status clauses $duplicate_clause = array(); $replace_clause = array(); if ($col_status && count($status_where) > 1) { // more than one status clause foreach ($status_where as $status_name => $status_clause) { if (isset($duplicate_clause[$status_name])) { continue; } reset($status_where); if ($other_status_name = array_search($status_clause, $status_where)) { if ($other_status_name == $status_name) { $other_status_name = array_search($status_clause, $status_where); } if ($other_status_name && $other_status_name != $status_name) { $duplicate_clause[$other_status_name][$status_name] = true; $replace_clause[$status_name] = true; } } } } $status_where = array_diff_key($status_where, $replace_clause); foreach ($status_where as $status_name => $this_status_where) { if ($status_clause_pos && $force_single_status) { //We are maintaining the single status which was specified in original query if (!$this_status_where || $this_status_where == '1=2') { $where_prepend = '1=2'; } elseif ($this_status_where == '1=1') { $where_prepend = ''; } else { //insert at original status clause position $where_prepend = ''; $where = substr($where, 0, $status_clause_pos) . "( {$this_status_where} ) AND " . substr($where, $status_clause_pos); } break; } // We may be replacing or inserting status clauses if (!empty($duplicate_clause[$status_name])) { // We generated duplicate clauses for some statuses foreach (array_keys($duplicate_clause[$status_name]) as $other_status_name) { $where = str_replace($basic_status_clause[$other_status_name], '1=2', $where); } $duplicate_clause[$status_name] = array_merge($duplicate_clause[$status_name], array($status_name => 1)); if ($col_status) { $name_in = "'" . implode("', '", array_keys($duplicate_clause[$status_name])) . "'"; $status_prefix = "{$src_table}.{$col_status} IN ({$name_in})"; } else { $status_prefix = "1=1"; } } elseif ($col_status && $status_name) { $status_prefix = $basic_status_clause[$status_name]; } else { $status_prefix = ''; } if ($this_status_where && ($this_status_where != '1=2' || count($status_where) > 1)) { //todo: confirm we can OR the 1=2 even if only one status clause if ('1=1' == $this_status_where) { $status_clause = $status_prefix ? "{$status_prefix} " : ''; } else { $status_clause = $col_status && $status_prefix ? "{$status_prefix} AND " : ''; $status_clause .= "( {$this_status_where} )"; // TODO: reduce number of parentheses $status_clause = " ( {$status_clause} )"; } } else { $status_clause = '1=2'; } if ($status_clause) { if ($col_status && $status_name && strpos($where, $basic_status_clause[$status_name])) { // Replace existing status clause with our scoped equivalent $where = str_replace($basic_status_clause[$status_name], "{$status_clause}", $where); } elseif ($status_clause_pos && $status_clause != '1=2') { // This status was not in the original query, but we now insert it with scoping clause at the position of another existing status clause $where = substr($where, 0, $status_clause_pos) . "{$status_clause} OR " . substr($where, $status_clause_pos); } else { // Default query makes no mention of status (perhaps because this data source doesn't define statuses), // so prepend this clause to front of where clause $where_prepend .= "{$status_or} {$status_clause}"; $status_or = ' OR'; } } } // Existance of this variable means no status clause exists in default WHERE. AND away we go. // Prepend so we don't disturb any orderby/groupby/limit clauses which are along for the ride if ($where_prepend) { if ($where) { $where = " AND ( {$where_prepend} ) {$where}"; } else { $where = " AND ( {$where_prepend} )"; } } //d_echo ("<br /><br /><strong>objects_where output:</strong> $where<br /><br />"); //echo "<br />$where<br />"; //rs_errlog ("object_where output: $where"); //rs_errlog (''); //rs_errlog (''); return $where; }