* @license GPLv3 * @copyright 2020 by Joachim Jensen */ defined('ABSPATH') || exit; /** * * Taxonomy Module * * Detects if current content has/is: * a) any term of specific taxonomy or specific term * b) taxonomy archive or specific term archive * */ class WPCAModule_taxonomy extends WPCAModule_Base { /** * @var string */ protected $category = 'taxonomy'; /** * Registered public taxonomies * * @var array */ private $taxonomy_objects = array(); /** * Terms of a given singular * * @var array */ private $post_terms; /** * Taxonomies for a given singular * @var array */ private $post_taxonomies; /** * Constructor */ public function __construct() { parent::__construct('taxonomy', __('Taxonomies', WPCA_DOMAIN)); $this->query_name = 'ct'; } public function initiate() { parent::initiate(); add_action( 'created_term', array($this,'term_ancestry_check'), 10, 3 ); if (is_admin()) { foreach ($this->_get_taxonomies() as $taxonomy) { add_action( 'wp_ajax_wpca/module/'.$this->id.'-'.$taxonomy->name, array($this,'ajax_print_content') ); } } } /** * Determine if content is relevant * * @since 1.0 * @return boolean */ public function in_context() { if (is_singular()) { $tax = $this->_get_taxonomies(); $this->post_terms = array(); $this->post_taxonomies = array(); // Check if content has any taxonomies supported foreach (get_object_taxonomies(get_post_type()) as $taxonomy) { //Only want taxonomies selectable in admin if (isset($tax[$taxonomy])) { //Check term caches, Core most likely used it $terms = get_object_term_cache(get_the_ID(), $taxonomy); if ($terms === false) { $terms = wp_get_object_terms(get_the_ID(), $taxonomy); } if ($terms) { $this->post_taxonomies[] = $taxonomy; $this->post_terms = array_merge($this->post_terms, $terms); } } } return !!$this->post_terms; } return is_tax() || is_category() || is_tag(); } /** * Remove posts if they have data from * other contexts (meaning conditions arent met) * * @since 3.2 * @param array $posts * @return array */ public function filter_excluded_context($posts) { $posts = parent::filter_excluded_context($posts); if ($posts) { global $wpdb; $obj_w_tags = $wpdb->get_col("SELECT object_id FROM $wpdb->term_relationships WHERE object_id IN (".implode(',', array_keys($posts)).') GROUP BY object_id'); if ($obj_w_tags) { $posts = array_diff_key($posts, array_flip($obj_w_tags)); } } return $posts; } /** * Query join * * @since 1.0 * @return string */ public function db_join() { global $wpdb; $joins = parent::db_join(); $joins .= "LEFT JOIN $wpdb->term_relationships term ON term.object_id = p.ID "; return $joins; } /** * Get data from context * * @since 1.0 * @return array */ public function get_context_data() { $name = $this->get_query_name(); //In more recent WP versions, term_id = term_tax_id //but term_tax_id has always been unique if (is_singular()) { $terms = array(); foreach ($this->post_terms as $term) { $terms[] = $term->term_taxonomy_id; } return '(term.term_taxonomy_id IS NULL OR term.term_taxonomy_id IN ('.implode(',', $terms).")) AND ($name.meta_value IS NULL OR $name.meta_value IN('".implode("','", $this->post_taxonomies)."'))"; } $term = get_queried_object(); return "(term.term_taxonomy_id IS NULL OR term.term_taxonomy_id = '".$term->term_taxonomy_id."') AND ($name.meta_value IS NULL OR $name.meta_value = '".$term->taxonomy."')"; } /** * Get content for sidebar editor * * @since 1.0 * @param array $args * @return array */ protected function _get_content($args = array()) { $total_items = wp_count_terms($args['taxonomy'], array( 'hide_empty' => $args['hide_empty'] )); $start = $args['offset']; $end = $start + $args['number']; $walk_tree = false; $retval = array(); if ($total_items) { $taxonomy = get_taxonomy($args['taxonomy']); if ($taxonomy->hierarchical && !$args['search'] && !$args['include']) { $args['number'] = 0; $args['offset'] = 0; $walk_tree = true; } $terms = new WP_Term_Query($args); if ($walk_tree) { $sorted_terms = array(); foreach ($terms->terms as $term) { $sorted_terms[$term->parent][] = $term; } $i = 0; $this->_walk_tree($sorted_terms, $sorted_terms[0], $i, $start, $end, 0, $retval); } else { //Hierarchical taxonomies use ids instead of slugs //see http://codex.wordpress.org/Function_Reference/wp_set_post_objects $value_var = ($taxonomy->hierarchical ? 'term_id' : 'slug'); foreach ($terms->terms as $term) { //term names are encoded $retval[$term->$value_var] = htmlspecialchars_decode($term->name); } } } return $retval; } /** * Get hierarchical content with level param * * @since 3.7.2 * @param array $all_terms * @param array $terms * @param int $i * @param int $start * @param int $end * @param int $level * @param array &$retval * @return void */ protected function _walk_tree($all_terms, $terms, &$i, $start, $end, $level, &$retval) { foreach ($terms as $term) { if ($i >= $end) { break; } if ($i >= $start) { $retval[] = array( 'id' => $term->term_id, 'text' => htmlspecialchars_decode($term->name), 'level' => $level ); } $i++; if (isset($all_terms[$term->term_id])) { $this->_walk_tree($all_terms, $all_terms[$term->term_id], $i, $start, $end, $level + 1, $retval); } } } /** * Get registered public taxonomies * * @since 1.0 * @return array */ protected function _get_taxonomies() { // List public taxonomies if (empty($this->taxonomy_objects)) { foreach (get_taxonomies(array('public' => true), 'objects') as $tax) { $this->taxonomy_objects[$tax->name] = $tax; } if (defined('POLYLANG_VERSION')) { unset($this->taxonomy_objects['language']); } } return $this->taxonomy_objects; } /** * Get data for condition group * * @since 2.0 * @param array $group_data * @param int $post_id * @return array */ public function get_group_data($group_data, $post_id) { $ids = array_flip((array)get_post_custom_values(WPCACore::PREFIX . $this->id, $post_id)); //Fetch all terms and group by tax to prevent lazy loading $terms = wp_get_object_terms( $post_id, array_keys($this->_get_taxonomies()) // array( // 'update_term_meta_cache' => false // ) ); $terms_by_tax = array(); foreach ($terms as $term) { $terms_by_tax[$term->taxonomy][] = $term; } $title_count = $this->get_title_count(); foreach ($this->_get_taxonomies() as $taxonomy) { $posts = isset($terms_by_tax[$taxonomy->name]) ? $terms_by_tax[$taxonomy->name] : 0; if ($posts || isset($ids[$taxonomy->name])) { $group_data[$this->id.'-'.$taxonomy->name] = $this->get_list_data($taxonomy, $title_count[$taxonomy->label]); $group_data[$this->id.'-'.$taxonomy->name]['label'] = $group_data[$this->id.'-'.$taxonomy->name]['text']; if ($posts) { $retval = array(); //Hierarchical taxonomies use ids instead of slugs //see http://codex.wordpress.org/Function_Reference/wp_set_post_objects $value_var = ($taxonomy->hierarchical ? 'term_id' : 'slug'); foreach ($posts as $post) { $retval[$post->$value_var] = $post->name; } $group_data[$this->id.'-'.$taxonomy->name]['data'] = $retval; } } } return $group_data; } /** * Count taxonomy labels to find shared ones * * @return array */ protected function get_title_count() { $title_count = array(); foreach ($this->_get_taxonomies() as $taxonomy) { if (!isset($title_count[$taxonomy->label])) { $title_count[$taxonomy->label] = 0; } $title_count[$taxonomy->label]++; } return $title_count; } protected function get_list_data($taxonomy, $title_count) { $placeholder = '/'.sprintf(__('%s Archives', WPCA_DOMAIN), $taxonomy->labels->singular_name); $placeholder = $taxonomy->labels->all_items.$placeholder; $label = $taxonomy->label; if (count($taxonomy->object_type) === 1 && $title_count > 1) { $post_type = get_post_type_object($taxonomy->object_type[0]); $label .= ' (' . $post_type->label . ')'; } return array( 'text' => $label, 'placeholder' => $placeholder, 'default_value' => $taxonomy->name ); } /** * @since 2.0 * @param array $list * * @return array */ public function list_module($list) { $title_count = $this->get_title_count(); foreach ($this->_get_taxonomies() as $taxonomy) { $data = $this->get_list_data($taxonomy, $title_count[$taxonomy->label]); $data['id'] = $this->id.'-'.$taxonomy->name; $list[] = $data; } return $list; } /** * @param array $args * * @return array */ protected function parse_query_args($args) { if (isset($args['item_object'])) { preg_match('/taxonomy-(.+)$/i', $args['item_object'], $matches); $args['item_object'] = isset($matches[1]) ? $matches[1] : '___'; $taxonomy_name = $args['item_object']; } else { $taxonomy_name = 'category'; } return array( 'include' => $args['include'], 'taxonomy' => $taxonomy_name, 'number' => $args['limit'], 'offset' => ($args['paged'] - 1) * $args['limit'], 'orderby' => 'name', 'order' => 'ASC', 'search' => $args['search'], 'hide_empty' => false, 'update_term_meta_cache' => false ); } /** * Save data on POST * * @since 1.0 * @param int $post_id * @return void */ public function save_data($post_id) { //parent::save_data($post_id); $meta_key = WPCACore::PREFIX . $this->id; $old = array_flip(get_post_meta($post_id, $meta_key, false)); $tax_input = $_POST['conditions']; //Save terms //Loop through each public taxonomy foreach ($this->_get_taxonomies() as $taxonomy) { //If no terms, maybe delete old ones if (!isset($tax_input[$this->id.'-'.$taxonomy->name])) { $terms = array(); if (isset($old[$taxonomy->name])) { delete_post_meta($post_id, $meta_key, $taxonomy->name); } } else { $terms = $tax_input[$this->id.'-'.$taxonomy->name]; $found_key = array_search($taxonomy->name, $terms); //If meta key found maybe add it if ($found_key !== false) { if (!isset($old[$taxonomy->name])) { add_post_meta($post_id, $meta_key, $taxonomy->name); } unset($terms[$found_key]); //Otherwise maybe delete it } elseif (isset($old[$taxonomy->name])) { delete_post_meta($post_id, $meta_key, $taxonomy->name); } //Hierarchical taxonomies use ids instead of slugs //see http://codex.wordpress.org/Function_Reference/wp_set_post_terms if ($taxonomy->hierarchical) { $terms = array_unique(array_map('intval', $terms)); } } wp_set_object_terms($post_id, $terms, $taxonomy->name); } } /** * Auto-select children of selected ancestor * * @since 1.0 * @param int $term_id * @param int $tt_id * @param string $taxonomy * @return void */ public function term_ancestry_check($term_id, $tt_id, $taxonomy) { if (is_taxonomy_hierarchical($taxonomy)) { $term = get_term($term_id, $taxonomy); if ($term->parent != '0') { // Get sidebars with term ancestor wanting to auto-select term $query = new WP_Query(array( 'post_type' => WPCACore::TYPE_CONDITION_GROUP, 'post_status' => array(WPCACore::STATUS_OR,WPCACore::STATUS_EXCEPT,WPCACore::STATUS_PUBLISHED), 'meta_query' => array( array( 'key' => WPCACore::PREFIX . 'autoselect', 'value' => 1, 'compare' => '=' ) ), 'tax_query' => array( array( 'taxonomy' => $taxonomy, 'field' => 'id', 'terms' => get_ancestors($term_id, $taxonomy), 'include_children' => false ) ) )); if ($query && $query->found_posts) { foreach ($query->posts as $post) { wp_set_post_terms($post->ID, $term_id, $taxonomy, true); } do_action('wpca/modules/auto-select/'.$this->category, $query->posts, $term); } } } } }