Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
512 lines (405 sloc) 19.2 KB
<?php
// In effect, override corresponding WP functions with a scoped equivalent,
// including per-group wp_cache. Any previous result set modifications by other plugins
// would be discarded. These filters are set to execute as early as possible to avoid such conflict.
//
// (note: if wp_cache is not enabled, WP core queries will execute pointlessly before these filters have a chance)
if( basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']) )
die();
require_once( SCOPER_ABSPATH . '/hardway/hardway_rs.php' ); // for remap_tree()
require_once( SCOPER_ABSPATH . '/lib/ancestry_lib_rs.php' );
/**
* 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 TermsInterceptor_RS
{
var $current_cache_key = array();
var $no_cache;
function TermsInterceptor_RS() {
global $scoper;
// flt_get_pages is required on the front end (even for administrators) to enable the inclusion of private pages
// flt_get_terms '' so private posts are included in count, as basis for display when hide_empty arg is used
if ( ( $scoper->is_front() || ! is_content_administrator_rs() ) && ( ! defined( 'DOING_AUTOSAVE' ) || ! DOING_AUTOSAVE ) ) {
add_filter('get_terms_args', array(&$this, 'flt_get_terms_args'), 50, 2);
add_filter('terms_clauses', array(&$this, 'flt_terms_clauses'), 50, 3);
add_filter('get_terms', array(&$this, '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($this, 'flt_cat_not_in_subquery'), 1);
}
if ( $scoper->is_front() && ! is_content_administrator_rs() ) {
add_filter('get_the_terms', array(&$this, 'flt_get_the_terms'), 10, 3 );
}
$this->no_cache = defined( 'SCOPER_NO_TERMS_CACHE' ) || ( ! defined('SCOPER_QTRANSLATE_COMPAT') && awp_is_plugin_active('qtranslate') );
}
function flt_get_the_terms( $terms, $id, $taxonomy ) {
if ( $terms && is_array($terms) ) {
if ( function_exists('is_teaser_rs') && is_teaser_rs($id) )
return $terms;
static $all_terms;
if ( ! isset($all_terms) )
$all_terms = array();
if ( empty($taxonomy) || ! is_scalar($taxonomy) )
return $terms;
if ( ! isset($all_terms[$taxonomy]) ) {
$all_terms[$taxonomy] = get_terms($taxonomy, array( 'fields'=> 'ids' ) );
}
foreach( array_keys($terms) as $key ) {
if ( ! in_array( $terms[$key]->term_id, $all_terms[$taxonomy] ) )
unset( $terms[$key] );
}
}
return $terms;
}
function get_cache_key( $taxonomy, $args, $criteria ) {
// $default_criteria = array( 'is_term_admin' => false, 'filter_key' => '', 'required_operation' => '' );
$arg_ser = md5( serialize($args) );
if ( ! isset( $this->current_cache_key[$taxonomy][$arg_ser] ) ) {
extract( $criteria );
if ( ! isset($required_operation) ) { $required_operation = ''; }
$this->current_cache_key[$taxonomy][$arg_ser] = md5( $taxonomy . serialize( $args ) . $filter_key . serialize( $GLOBALS['scoper']->get_terms_reqd_caps($taxonomy, $required_operation, $is_term_admin) ) );
}
return $this->current_cache_key[$taxonomy][$arg_ser];
}
function doing_teaser( $args ) {
$fields = ( isset( $args['actual_args']['fields'] ) ) ? $args['actual_args']['fields'] : $args['fields'];
return ( ( 'all' == $fields ) && $GLOBALS['scoper']->is_front() && empty($args['skip_teaser']) && scoper_get_otype_option('do_teaser', 'post') );
}
function skip_filtering( $taxonomies, $args ) {
global $scoper;
if ( ! empty( $args['rs_no_filter'] ) )
return true;
// this filtering 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 ( count($taxonomies) > 1 )
return true;
$taxonomy = reset($taxonomies);
if ( ! $tx_obj = get_taxonomy( $taxonomy ) )
return true;
$use_taxonomies = array();
if ( is_admin() ) {
if ( ( 'nav-menus.php' == $GLOBALS['pagenow'] ) && ( 'nav_menu' != $taxonomy ) ) {
if ( ! scoper_get_option( 'admin_nav_menu_filter_items' ) )
return true;
}
} else {
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 true;
}
}
}
// no wp-admin filtering for administrators (filters not added in that case)
if ( ( is_admin() || defined('XMLRPC_REQUEST') ) && is_content_administrator_rs() )
return true;
// link category roles / restrictions are only scoped for management (TODO: review this)
if ( ( 'link_category' == $taxonomy ) && $scoper->is_front() )
return true;
if ( $args['child_of'] || $args['parent'] ) {
$children = ScoperAncestry::get_terms_children($taxonomy);
if ( $args['child_of'] && ! isset($children[ $args['child_of'] ]) )
return 'empty_result'; // get_terms filter will return empty result array
if ( $args['parent'] && ! isset($children[ $args['parent'] ]) )
return 'empty_result'; // get_terms filter will return empty result array
}
return false;
}
function get_filter_criteria( $args ) {
$return = array();
$return['filter_key'] = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
if ( empty($args['required_operation']) ) {
// support Quick Post Widget plugin
if ( ! empty($args['name']) && ( 'quick_post_cat' == $args['name'] ) ) {
$return['required_operation'] = 'edit';
$return['post_type'] = 'post';
$return['remap_parents'] = true;
}
if ( is_admin() && isset($GLOBALS['plugin_page']) && ( 's2' == $GLOBALS['plugin_page'] ) ) {
$return['required_operation'] = 'read';
}
}
if ( '' === $args['is_term_admin'] ) {
$return['is_term_admin'] = in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'edit-link-categories.php' ) );
} else {
// support Quick Post Widget plugin
if ( ! empty($args['name']) && ( 'quick_post_new_cat_parent' == $args['name'] ) ) {
$return['is_term_admin'] = true;
$return['remap_parents'] = true;
}
}
return $return;
}
function flt_get_terms_args( $args, $taxonomies ) {
if ( $this->skip_filtering( $taxonomies, $args ) )
return $args;
$taxonomy = reset($taxonomies);
$rs_defaults = array(
'depth' => 0, 'skip_teaser' => false,
'remap_parents' => -1, 'enforce_actual_depth' => -1, 'remap_thru_excluded_parent' => -1,
'post_type' => '', 'required_operation' => '', 'is_term_admin' => '',
);
$args = wp_parse_args( $args, $rs_defaults );
$args['child_of'] = (int) $args['child_of']; // null value will confuse subsequent RS checks
if ( $args['post_type'] && is_string($args['post_type']) )
$args['post_type'] = explode( ',', $args['post_type'] );
// 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']) && ( 1 == $args['depth'] ) ) {
$args['exclude_tree'] = $args['exclude'];
}
if ( is_admin() ) {
global $wpdb;
switch( $GLOBALS['pagenow'] ) {
case 'edit-tags.php' :
$tx_obj = get_taxonomy( $taxonomy );
if ( $tx_obj->hierarchical && ! empty( $_REQUEST['tag_ID'] ) ) {
$editing_term_id = intval($_REQUEST['tag_ID']);
// don't offer to set a category as its own parent
if ( ! empty($args['exclude']) )
$args['exclude'] .= ',';
$args['exclude'] .= $_REQUEST['tag_ID'];
}
break;
case 'nav-menus.php' :
if ( ( 'nav_menu' != $taxonomy ) && scoper_get_option( 'admin_nav_menu_filter_items' ) )
$args['hide_empty'] = true;
break;
default:
} // end switch
}
// turn off core post-processing of the result set, but buffer actual arg values for equivalent RS post-processing
$buffer_args = array();
foreach ( array( 'child_of' => 0, 'pad_counts' => false, 'hide_empty' => false, 'number' => '' ) as $arg_name => $force_val ) {
if ( $args[$arg_name] != $force_val ) {
$buffer_args[$arg_name] = $args[$arg_name];
$args[$arg_name] = $force_val;
}
}
if ( in_array( $args['fields'], array( 'ids', 'names', 'id=>parent' ) ) ) {
// flt_get_terms needs parent col in intermediate result set even if final result set will be ids only, will convert result set back to expected format before returning
add_filter( 'get_terms_fields', create_function( '$a,$b', "return array('t.*', 'tt.*');" ), 1, 2 );
$buffer_args['fields'] = $args['fields'];
$args['fields'] = 'force_all';
}
if ( $buffer_args )
$args['actual_args'] = $buffer_args;
return $args;
}
function flt_terms_clauses($clauses, $taxonomies, $args) {
if ( $skip = $this->skip_filtering( $taxonomies, $args ) )
return $clauses;
global $scoper;
$taxonomy = reset($taxonomies);
extract( $args, EXTR_SKIP );
$criteria = $this->get_filter_criteria( $args );
extract( $criteria ); // sets $is_term_admin, $filter_key, $required_operation' (may also force $post_type and $remap_parents)
if ( ! $this->no_cache ) {
$ckey = $this->get_cache_key( $taxonomy, $args, $criteria );
$cache = $GLOBALS['current_rs_user']->cache_get( 'rs_get_terms' );
if ( false !== $cache ) {
if ( !is_array($cache) )
$cache = array();
if ( isset( $cache[ $ckey ] ) ) {
return $clauses; // flt_get_terms() will return a cached result, so no need to further process query pieces
}
}
}
if ( ( 0 === $parent ) && ('ids' == $fields) ) {
// otherwise termroles only work if parent terms also have role
$clauses['where'] = str_replace( " AND tt.parent = '$parent'", '', $clauses['where'] );
}
if ( $hide_empty && ! $orig_args['hide_empty'] && ! $hierarchical ) { // hide_empty may have been set by modify_args()
$clauses['where'] .= ' AND tt.count > 0';
}
if ( is_admin() && ( 'edit-tags.php' == $GLOBALS['pagenow'] ) && ! empty( $_REQUEST['tag_ID'] ) ) {
$tx_obj = get_taxonomy( $taxonomy );
if ( $tx_obj->hierarchical ) {
// don't filter current parent category out of selection UI even if current user can't manage it
$clauses['where'] .= " OR t.term_id = (SELECT parent FROM {$GLOBALS['wpdb']->term_taxonomy} WHERE term_id = '" . intval($_REQUEST['tag_ID']) . "') ";
}
}
// we forced $args['fields'] to preserve parent col in result set, but now filter clause back to proper fields
if ( isset($actual_args['fields']) ) {
$selects = array('t.term_id', 'tt.parent', 'tt.count');
if ( 'names' == $actual_args['fields'] )
$selects []= 't.name';
$clauses['fields'] = implode(', ', apply_filters( 'get_terms_fields', $selects, $args ));
$fields = $actual_args['fields']; // for term_taxonomy_id check below
}
if ( in_array( $fields, array( 'ids', 'id=>parent', 'names' ) ) ) {
if ( $clauses['fields'] && ( false === strpos( $clauses['fields'], 'tt.term_taxonomy_id' ) ) && ( false === strpos( $clauses['fields'], '*' ) ) )
$clauses['fields'] .= ', tt.term_taxonomy_id';
}
// only force application of scoped query filter if we're NOT doing a teaser
$term_id_col = ( strpos( $clauses['join'], ' AS tt' ) ) ? 'tt.term_taxonomy_id' : 't.term_id';
$clauses['where'] = apply_filters( 'terms_where_rs', $clauses['where'], $taxonomy, array( 'term_id_col' => $term_id_col, 'skip_teaser' => ! $this->doing_teaser($args), 'is_term_admin' => $is_term_admin, 'required_operation' => $required_operation, 'post_type' => $post_type ) );
// 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'] = $taxonomy;
if ( 0 !== strpos( $clauses['fields'], 'DISTINCT ' ) )
$clauses['fields'] = 'DISTINCT ' . $clauses['fields'];
return $clauses;
}
// Cap requirements depend on access type, and are specified by Scoper::get_terms_reqd_caps() corresponding to taxonomy in question
function flt_get_terms($terms, $taxonomies, $args) {
if ( empty($terms) ) {
return array();
}
if ( $skip = $this->skip_filtering( $taxonomies, $args ) ) {
if ( 'return_empty' === $skip ) {
return array();
} else {
return $terms;
}
}
$taxonomy = reset($taxonomies);
extract( $args, EXTR_SKIP );
$criteria = $this->get_filter_criteria( $args );
extract( $criteria ); // sets $is_term_admin, $filter_key, $required_operation' (may also force $post_type and $remap_parents)
//d_echo( 'flt_get_terms input:' );
//dump($terms);
if ( ! $this->no_cache ) {
// NOTE: this caching eliminates both the results post-processing below and query clause filtering in flt_terms_clauses()
$ckey = $this->get_cache_key( $taxonomy, $args, $criteria );
$cache_flag = 'rs_get_terms';
$cache = $GLOBALS['current_rs_user']->cache_get( $cache_flag );
if ( false !== $cache ) {
if ( !is_array($cache) )
$cache = array();
if ( isset( $cache[ $ckey ] ) ) {
return $cache[ $ckey ];
}
}
}
// if some args were forced to prevent core post-processing, restore actual values now
if ( ! empty( $args['actual_args'] ) )
extract( $args['actual_args'] );
// 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( $taxonomy );
if ( 'all' == $fields ) {
// buffer term names in case they were filtered previously
$term_names = scoper_get_property_array( $terms, 'term_id', 'name' );
$ancestors = ScoperAncestry::get_term_ancestors( $taxonomy ); // array of all ancestor IDs for keyed term_id, with direct parent first
if ( ( $parent > 0 ) || ! $hierarchical ) {
// in Term Edit form, need to list all editable terms 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' );
ScoperHardway::remap_tree( $terms, $ancestors, 'term_id', 'parent', $remap_args );
}
if ( ( $child_of || $hierarchical ) && ! empty($children) )
$terms = rs_get_term_descendants($child_of, $terms, $taxonomy); // rs_get_term_descendants is RS equivalent to WP _get_term_children()
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 terms that have no public posts (even if this user can read some of the pvt posts).
// Post counts will be incremented to include child terms 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' ) ) ) {
// rs_tally_term_counts() is RS equivalent to WP _pad_term_counts()
rs_tally_term_counts($terms, $taxonomy, array('pad_counts' => $pad_counts, 'skip_teaser' => ! $this->doing_teaser($args), 'post_type' => $post_type ) );
}
}
// Empty terms will be identified via count property set by rs_tally_term_counts() instead of 'count > 0' clause, to reflect logged user's actual post access (including readable private posts)
if ( $hide_empty ) {
if ( $hierarchical ) {
// Remove empty categories, but only if their descendants are all empty too.
foreach ( $terms as $k => $term ) {
if ( ! $term->count ) {
if ( $descendants = rs_get_term_descendants($term->term_id, $terms, $taxonomies[0]) ) {
foreach ( $descendants as $child ) {
if ( $child->count )
continue 2;
}
}
// It really is empty
unset($terms[$k]);
}
}
} else {
foreach ( $terms as $key => $term )
if ( ! $term->count )
unset( $terms[$key] );
}
}
reset ( $terms );
// === Standard WP post-processing for include, fields, number args ===
//
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);
}
// === end standard WP block ===
if ( ! $this->no_cache ) {
$cache[ $ckey ] = $terms;
$GLOBALS['current_rs_user']->cache_set( $cache, $cache_flag );
}
// restore buffered term names in case they were filtered previously
if ( 'all' == $fields )
scoper_restore_property_array( $terms, $term_names, 'term_id', 'name' );
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 = '';
$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;
}
}
?>