is_front() || ! is_content_administrator_rs() ) { add_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); // WPML registers at priority 1 // Since the NOT IN subquery is a painful aberration for filtering, replace it with the separate term query used by WP prior to 2.7 add_filter('posts_where', array('ScoperHardwayTaxonomy', 'flt_cat_not_in_subquery'), 1); } /** * ScoperHardwayTaxonomy PHP class for the WordPress plugin Role Scoper * hardway-taxonomy_rs.php * * @author Kevin Behrens * @copyright Copyright 2011 * * Used by Role Scoper Plugin as a container for statically-called functions * */ class ScoperHardwayTaxonomy { // Scoped equivalent to WP 3.0 core get_terms // Currently, scoped roles cannot be enforced without replicating the whole function // // Cap requirements depend on access type, and are specified by Scoper::get_terms_reqd_caps() corresponding to taxonomy in question function flt_get_terms($results, $taxonomies, $args) { global $wpdb; $empty_array = array(); //d_echo( 'flt_get_terms input:' ); $single_taxonomy = false; if ( !is_array($taxonomies) ) { $single_taxonomy = true; $taxonomies = array($taxonomies); } // === BEGIN Role Scoper MODIFICATION: single-item array is still a single taxonomy === elseif( count($taxonomies) < 2 ) $single_taxonomy = true; // === END Role Scoper MODIFICATION === foreach ( (array) $taxonomies as $taxonomy ) { if ( ! taxonomy_exists($taxonomy) ) { // === BEGIN Role Scoper MODIFICATION: this caused plugin activation error in some situations (though at that time, the error object was created and return on a single line, not byRef as now) === // //$error = & new WP_Error('invalid_taxonomy', __awp('Invalid Taxonomy')); //return $error; return array(); // // === END Role Scoper MODIFICATION === } } // === BEGIN Role Scoper ADDITION: global var; various special case exemption checks === // global $scoper; if ( $tx_obj = get_taxonomy( $taxonomies[0] ) ) { // don't require use_taxonomies setting for link_categories or other non-post taxonomies if ( array_intersect( $tx_obj->object_type, get_post_types( array( 'public' => true ) ) ) ) { $use_taxonomies = scoper_get_option( 'use_taxonomies' ); if ( empty( $use_taxonomies[$taxonomy] ) ) return $results; } } // no backend filter for administrators $parent_or = ''; if ( ( is_admin() || defined('XMLRPC_REQUEST') ) ) { if ( is_content_administrator_rs() ) { return $results; } else { if ( $tx = $scoper->taxonomies->get($taxonomies[0]) ) { // is a Category Edit form being displayed? if ( ! empty( $tx->uri_vars ) ) $term_id = $scoper->data_sources->detect('id', $tx); else $term_id = $scoper->data_sources->detect('id', $tx->source); if ( $term_id ) // don't filter current parent category out of selection UI even if current user can't manage it $parent_or = " OR t.term_id = (SELECT parent FROM $wpdb->term_taxonomy WHERE term_id = '$term_id') "; } } } // need to skip cache retrieval if QTranslate is filtering get_terms with a priority of 1 or less static $no_cache; if ( ! isset($no_cache) ) $no_cache = defined( 'SCOPER_NO_TERMS_CACHE' ) || ( ! defined('SCOPER_QTRANSLATE_COMPAT') && awp_is_plugin_active('qtranslate') ); // this filter currently only supports a single taxonomy for each get_terms call // (although the terms_where filter does support multiple taxonomies and this function could be made to do so) if ( ! $single_taxonomy ) return $results; // link category roles / restrictions are only scoped for management (TODO: abstract this) if ( $single_taxonomy && ( 'link_category' == $taxonomies[0] ) && $scoper->is_front() ) return $results; // depth is not really a get_terms arg, but remap exclude arg to exclude_tree if wp_list_terms called with depth=1 if ( ! empty($args['exclude']) && empty($args['exclude_tree']) && ! empty($args['depth']) && ( 1 == $args['depth'] ) ) $args['exclude_tree'] = $args['exclude']; // don't offer to set a category as its own parent if ( 'edit-tags.php' == $GLOBALS['pagenow'] ) { if ( $tx_obj->hierarchical ) { if ( $editing_cat_id = $scoper->data_sources->get_from_uri('id', 'term') ) { if ( ! empty($args['exclude']) ) $args['exclude'] .= ','; $args['exclude'] .= $editing_cat_id; } } } // we'll need this array in most cases, to support a disjointed tree with some parents missing (note alternate function call - was _get_term_hierarchy) $children = ScoperAncestry::get_terms_children($taxonomies[0]); // // === END Role Scoper ADDITION === // ================================= $in_taxonomies = "'" . implode("', '", $taxonomies) . "'"; $defaults = array('orderby' => 'name', 'order' => 'ASC', 'hide_empty' => true, 'exclude' => '', 'exclude_tree' => '', 'include' => '', 'number' => '', 'fields' => 'all', 'slug' => '', 'parent' => '', 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'pad_counts' => false, 'offset' => '', 'search' => '', 'skip_teaser' => false, 'depth' => 0, 'remap_parents' => -1, 'enforce_actual_depth' => -1, 'remap_thru_excluded_parent' => -1, 'post_type' => '' ); // Role Scoper arguments added above $args = wp_parse_args( $args, $defaults ); $args['number'] = (int) $args['number']; $args['offset'] = absint( $args['offset'] ); $args['child_of'] = (int) $args['child_of']; // Role Scoper modification: null value will confuse children array check if ( !$single_taxonomy || !is_taxonomy_hierarchical($taxonomies[0]) || '' !== $args['parent'] ) { $args['child_of'] = 0; $args['hierarchical'] = false; $args['pad_counts'] = false; } if ( 'all' == $args['get'] ) { $args['child_of'] = 0; $args['hide_empty'] = 0; $args['hierarchical'] = false; $args['pad_counts'] = false; } extract($args, EXTR_SKIP); // === BEGIN Role Scoper MODIFICATION: use the $children array we already have === // if ( 'nav-menus.php' == $GLOBALS['pagenow'] ) { if ( 'nav_menu' != $taxonomies[0] ) { if ( ! scoper_get_option( 'admin_nav_menu_filter_items' ) ) return $results; else $hide_empty = 1; } } if ( $child_of && ! isset($children[$child_of]) ) return array(); if ( $parent && ! isset($children[$parent]) ) return array(); if ( $post_type && is_string($post_type) ) $post_type = explode( ',', $post_type ); // // === END Role Scoper MODIFICATION === // ==================================== $is_term_admin = in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'edit-link-categories.php' ) ); $filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : ''; $key = md5( serialize( compact(array_keys($defaults)) ) . serialize( $taxonomies ) . $filter_key ); // === BEGIN Role Scoper MODIFICATION: cache key specific to access type and user/groups === // support Quick Post Widget plugin if ( isset($name) && 'quick_post_cat' == $name ) { $required_operation = 'edit'; $post_type = 'post'; $remap_parents = true; } elseif ( isset($name) && 'quick_post_new_cat_parent' == $name ) { $is_term_admin = true; $required_operation = ''; $remap_parents = true; } else { $required_operation = ''; } $object_src_name = $scoper->taxonomies->member_property($taxonomies[0], 'object_source', 'name'); $ckey = md5( $key . serialize( $scoper->get_terms_reqd_caps($taxonomies[0], $required_operation, $is_term_admin) ) ); global $current_rs_user; $cache_flag = 'rs_get_terms'; $cache = $current_rs_user->cache_get( $cache_flag ); if ( false !== $cache ) { if ( !is_array($cache) ) $cache = array(); if ( ! $no_cache && isset( $cache[ $ckey ] ) ) { // RS Modification: alternate filter name (get_terms filter is already applied by WP) remove_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); $terms = apply_filters('get_terms', $cache[ $ckey ], $taxonomies, $args); $terms = apply_filters('get_terms_rs', $terms, $taxonomies, $args); add_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); return $terms; } } // buffer term names in case they were filtered previously if ( 'all' == $fields ) $term_names = scoper_get_property_array( $results, 'term_id', 'name' ); // // === END Role Scoper MODIFICATION === // ===================================== $_orderby = strtolower($orderby); if ( 'count' == $_orderby ) $orderby = 'tt.count'; else if ( 'name' == $_orderby ) $orderby = 't.name'; else if ( 'slug' == $_orderby ) $orderby = 't.slug'; else if ( 'term_group' == $_orderby ) $orderby = 't.term_group'; else if ( 'none' == $_orderby ) { $orderby = ''; $order = ''; } else if ( empty($_orderby) || 'id' == $_orderby ) $orderby = 't.term_id'; elseif ( 'order' == $_orderby ) $orderby = 't.term_order'; else $orderby = 't.name'; $orderby = apply_filters( 'get_terms_orderby', $orderby, $args ); if ( !empty($orderby) ) $orderby = "ORDER BY $orderby"; $where = ''; // === Role Scoper MODIFICATION: if an include argument is provided, strip out non-matching terms after filtering is done. === /* $inclusions = ''; if ( !empty($include) ) { $exclude = ''; $exclude_tree = ''; $interms = wp_parse_id_list($include); if ( count($interms) ) { foreach ( $interms as $interm ) { if (empty($inclusions)) $inclusions = ' AND ( t.term_id = ' . intval($interm) . ' '; else $inclusions .= ' OR t.term_id = ' . intval($interm) . ' '; } } } if ( !empty($inclusions) ) $inclusions .= ')'; $where .= $inclusions; */ // === END Role Scoper MODIFICATION === $exclusions = ''; if ( ! empty( $exclude_tree ) ) { // === BEGIN Role Scoper MODIFICATION: temporarily unhook this filter for unfiltered get_terms calls === remove_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); // === END Role Scoper MODIFICATION === $excluded_trunks = wp_parse_id_list($exclude_tree); foreach( (array) $excluded_trunks as $extrunk ) { $excluded_children = (array) get_terms($taxonomies[0], array('child_of' => intval($extrunk), 'fields' => 'ids')); $excluded_children[] = $extrunk; foreach( $excluded_children as $exterm ) { if ( empty($exclusions) ) $exclusions = ' AND ( t.term_id <> ' . intval($exterm) . ' '; else $exclusions .= ' AND t.term_id <> ' . intval($exterm) . ' '; } } // === BEGIN Role Scoper MODIFICATION: re-hook this filter add_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); // === END Role Scoper MODIFICATION === } if ( !empty($exclude) ) { $exterms = wp_parse_id_list($exclude); foreach ( $exterms as $exterm ) { if (empty($exclusions)) $exclusions = ' AND ( t.term_id <> "' . intval($exterm) . '" '; else $exclusions .= ' AND t.term_id <> "' . intval($exterm) . '" '; } } if ( !empty($exclusions) ) $exclusions .= ')'; // WPML attempts to pull taxonomy out of debug_backtrace() unless set in $_GET or $_POST; previous filter execution throws it off if ( defined('ICL_SITEPRESS_VERSION') && ! isset($_GET['taxonomy'] ) ) $_GET['taxonomy'] = current($taxonomies); $exclusions = apply_filters('list_terms_exclusions', $exclusions, $args); $where .= $exclusions; if ( !empty($slug) ) { $slug = sanitize_title($slug); $where .= " AND t.slug = '$slug'"; } if ( !empty($name__like) ) $where .= " AND t.name LIKE '{$name__like}%'"; if ( '' !== $parent ) { $parent = (int) $parent; // === BEGIN Role Scoper MODIFICATION: otherwise termroles only work if parent terms also have role if ( ( $parent ) || ('ids' != $fields) ) $where .= " AND tt.parent = '$parent'"; // === END Role Scoper MODIFICATION === } // === BEGIN Role Scoper MODIFICATION: instead, manually remove truly empty cats at the bottom of this function, so we don't exclude cats with private but readable posts //if ( $hide_empty && !$hierarchical ) // $where .= ' AND tt.count > 0'; // === END Role Scoper MODIFICATION === // don't limit the query results when we have to descend the family tree if ( ! empty($number) && ! $hierarchical && empty( $child_of ) && '' == $parent ) { if ( $offset ) $limit = 'LIMIT ' . $offset . ',' . $number; else $limit = 'LIMIT ' . $number; } else $limit = ''; if ( ! empty($search) ) { $search = like_escape($search); $where .= " AND (t.name LIKE '%$search%')"; } $selects = array(); switch ( $fields ) { case 'all': $selects = array('t.*', 'tt.*'); break; case 'ids': case 'id=>parent': $selects = array('t.term_id', 'tt.term_taxonomy_id', 'tt.parent', 'tt.count'); break; case 'names': $selects = array('t.term_id', 'tt.term_taxonomy_id', 'tt.parent', 'tt.count', 't.name'); break; case 'count': $orderby = ''; $order = ''; $selects = array('COUNT(*)'); } $select_this = implode(', ', apply_filters( 'get_terms_fields', $selects, $args )); // === BEGIN Role Scoper MODIFICATION: run the query through scoping filter // $query_base = "SELECT DISTINCT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE 1=1 AND tt.taxonomy IN ($in_taxonomies) $where $parent_or $orderby $order $limit"; // only force application of scoped query filter if we're NOT doing a teaser if ( 'all' == $fields ) $do_teaser = ( $scoper->is_front() && empty($skip_teaser) && scoper_get_otype_option('do_teaser', 'post') ); else $do_teaser = false; $query = apply_filters( 'terms_request_rs', $query_base, $taxonomies[0], array('skip_teaser' => ! $do_teaser, 'is_term_admin' => $is_term_admin, 'required_operation' => $required_operation, 'post_type' => $post_type ) ); // if no filering was applied because the teaser is enabled, prevent a redundant query if ( ! empty($exclude_tree) || ($query_base != $query) || $parent || ( 'all' != $fields ) ) { $terms = scoper_get_results($query); } else $terms = $results; if ( 'count' == $fields ) { $term_count = $wpdb->get_var($query); return $term_count; } if ( ( 'all' == $fields ) && empty($include) ) update_term_cache($terms); // RS: don't cache an empty array, just in case something went wrong if ( empty($terms) ) { return array(); } // // === END Role Scoper MODIFICATION === // ==================================== // === BEGIN Role Scoper ADDITION: Support a disjointed terms tree with some parents hidden // if ( 'all' == $fields ) { $ancestors = ScoperAncestry::get_term_ancestors( $taxonomy ); // array of all ancestor IDs for keyed term_id, with direct parent first if ( ( $parent > 0 ) || ! $hierarchical ) { // in Category Edit form, need to list all editable cats even if parent is not editable $remap_parents = false; $enforce_actual_depth = true; $remap_thru_excluded_parent = false; } else { // if these settings were passed into this get_terms call, use them if ( is_admin() ) { $remap_parents = true; } else { if ( -1 === $remap_parents ) $remap_parents = scoper_get_option( 'remap_term_parents' ); if ( $remap_parents ) { if ( -1 === $enforce_actual_depth ) $enforce_actual_depth = scoper_get_option( 'enforce_actual_term_depth' ); if ( -1 === $remap_thru_excluded_parent ) $remap_thru_excluded_parent = scoper_get_option( 'remap_thru_excluded_term_parent' ); } } } $remap_args = compact( 'child_of', 'parent', 'depth', 'orderby', 'remap_parents', 'enforce_actual_depth', 'remap_thru_excluded_parent' ); // one or more of these args may have been modified after extraction ScoperHardway::remap_tree( $terms, $ancestors, 'term_id', 'parent', $remap_args ); } // // === END Role Scoper ADDITION === // ================================ // === BEGIN Role Scoper MODIFICATION: call alternate functions // rs_tally_term_counts() replaces _pad_term_counts() // rs_get_term_descendants replaces _get_term_children() // if ( ( $child_of || $hierarchical ) && ! empty($children) ) $terms = rs_get_term_descendants($child_of, $terms, $taxonomies[0]); if ( ! $terms ) return array(); // Replace DB-stored term counts with actual number of posts this user can read. // In addition, without the rs_tally_term_counts call, WP will hide categories that have no public posts (even if this user can read some of the pvt posts). // Post counts will be incremented to include child categories only if $pad_counts is true if ( ! defined('XMLRPC_REQUEST') && in_array( $fields, array( 'all', 'ids', 'names' ) ) && ! $is_term_admin ) { if ( ! is_admin() || ! in_array( $GLOBALS['pagenow'], array( 'post.php', 'post-new.php' ) ) ) { //-- RoleScoper Modification - alternate function call (was _pad_term_counts) --// rs_tally_term_counts($terms, $taxonomies[0], array('pad_counts' => $pad_counts, 'skip_teaser' => ! $do_teaser, 'post_type' => $post_type ) ); } } // Make sure we show empty categories that have children. if ( $hierarchical && $hide_empty ) { foreach ( $terms as $k => $term ) { if ( ! $term->count ) { //-- RoleScoper Modification - call alternate function (was _get_term_children) --// if ( $children = rs_get_term_descendants($term->term_id, $terms, $taxonomies[0]) ) foreach ( $children as $child ) if ( $child->count ) continue 2; // It really is empty unset($terms[$k]); } } } reset ( $terms ); // // === END Role Scoper MODIFICATION === // ==================================== // === BEGIN Role Scoper ADDITION: hide empty cats based on actual query result instead of 'count > 0' clause, so we don't exclude cats with private but readable posts if ( $terms && empty( $hierarchical ) && ! empty( $hide_empty ) ) { foreach ( $terms as $key => $term ) if ( ! $term->count ) unset( $terms[$key] ); } // // === END Role Scoper ADDITION === // ================================ if ( ! empty($include) ) { $interms = wp_parse_id_list($include); foreach( $terms as $key => $term ) { if ( ! in_array( $term->term_id, $interms ) ) unset( $terms[$key] ); } } $_terms = array(); if ( 'id=>parent' == $fields ) { while ( $term = array_shift($terms) ) $_terms[$term->term_id] = $term->parent; $terms = $_terms; } elseif ( 'ids' == $fields ) { while ( $term = array_shift($terms) ) $_terms[] = $term->term_id; $terms = $_terms; } elseif ( 'names' == $fields ) { while ( $term = array_shift($terms) ) $_terms[] = $term->name; $terms = $_terms; } if ( 0 < $number && intval(@count($terms)) > $number ) { $terms = array_slice($terms, $offset, $number); } // === BEGIN Role Scoper MODIFICATION: cache key is specific to user/group // if ( ! $no_cache ) { $cache[ $ckey ] = $terms; $current_rs_user->cache_set( $cache, $cache_flag ); } // RS Modification: alternate filter name (get_terms filter is already applied by WP) remove_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); $terms = apply_filters('get_terms', $terms, $taxonomies, $args); $terms = apply_filters('get_terms_rs', $terms, $taxonomies, $args); add_filter('get_terms', array('ScoperHardwayTaxonomy', 'flt_get_terms'), 0, 3); // restore buffered term names in case they were filtered previously if ( 'all' == $fields ) scoper_restore_property_array( $terms, $term_names, 'term_id', 'name' ); // // === END Role Scoper MODIFICATION === // ==================================== //dump($terms); return $terms; } function flt_cat_not_in_subquery( $where ) { if ( strpos( $where, "ID NOT IN ( SELECT tr.object_id" ) ) { global $wp_query; global $wpdb; // Since the NOT IN subquery is a painful aberration for filtering, // replace it with the separatare term query used by WP prior to 2.7 if ( strpos( $where, "AND {$wpdb->posts}.ID NOT IN ( SELECT tr.object_id" ) ) { // global wp_query is not set on manual WP_Query calls by template code $whichcat = ''; //if ( ! empty($wp_query->query_vars['category__not_in']) ) { $ids = get_objects_in_term($wp_query->query_vars['category__not_in'], 'category'); if ( is_wp_error( $ids ) ) $ids = array(); if ( is_array($ids) && count($ids > 0) ) { $out_posts = "'" . implode("', '", $ids) . "'"; $whichcat .= " AND $wpdb->posts.ID NOT IN ($out_posts)"; } $where = preg_replace( "/ AND {$wpdb->posts}\.ID NOT IN \( SELECT tr\.object_id [^)]*\) \)/", $whichcat, $where ); } } return $where; } } ?>