Пример #1
0
 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;
 }