get_relationship_type($relationship); } // Sort Taxonomy Types to prefer term over post_type usort($relationships, fn (array $a, array $b):bool => array_search($a['type'], self::TYPE_ORDER) > array_search($b['type'], self::TYPE_ORDER)); return $relationships; } public function init() { $this->relationships = $this->get_relationships(); if (empty($this->relationships)) return; // Update all order meta if (is_admin() && function_exists('is_plugin_active') && is_plugin_active('wp-rocket/wp-rocket.php')) { add_action('admin_post_purge_cache', [$this, 'refresh_purge_cache'], 1); } else if (empty($_GET) && is_admin()) { add_action('current_screen', [$this, 'refresh_screen'], 10, 1); } // Check if has active relationship in admin, sets current_relationship if (is_admin()) { add_action('current_screen', [$this, 'current_screen']); } // Post Updating add_action('save_post', [$this, 'save_post'], 10, 3); // Post Types add_action('pre_get_posts', [$this, 'pre_get_posts'], 10, 1); add_filter('get_previous_post_where', [$this, 'previous_post_where']); add_filter('get_previous_post_sort', [$this, 'previous_post_sort']); add_filter('get_next_post_where', [$this, 'next_post_where']); add_filter('get_next_post_sort', [$this, 'next_post_sort']); // Taxonomy add_filter('terms_clauses', [$this, 'terms_clauses'], 10, 3); add_filter('get_terms_orderby', [$this, 'get_terms_orderby'], 10, 3); add_filter('wp_get_object_terms', [$this, 'wp_get_object_terms'], 10, 4); add_filter('get_terms', [$this, 'get_terms'], 10, 4); add_action('create_term', [$this, 'add_term_relationship'], 10, 3); // Ajax add_action('wp_ajax_ogre_sort', [$this, 'sort']); } public function current_screen() { $screen = get_current_screen(); foreach ($this->relationships as $relationship) { switch ($relationship['type']) { case 'term': if ($screen->base == 'edit' && $screen->post_type == $relationship['post_type'] && (isset($_GET[$relationship['taxonomy']]) || ($relationship['taxonomy'] === 'category' && isset($_GET['category_name'])) || (isset($_GET['taxonomy']) && $_GET['taxonomy'] == $relationship['taxonomy']))) { $this->current_relationship = $relationship; break; } break; case 'post_type': if ($screen->base == 'edit' && $screen->post_type == $relationship['post_type']) { $taxes = get_object_taxonomies($screen->post_type, 'names'); $tax_active = false; foreach ($taxes as $tax) { if (isset($_GET[$tax])) { $tax_active = true; break; } } if (!$tax_active) $this->current_relationship = $relationship; break; } break; case 'taxonomy': if ($screen->base == 'edit-tags' && $screen->taxonomy == $relationship['taxonomy']) { $this->current_relationship = $relationship; break; } break; } if (!empty($this->current_relationship)) break; } if (empty($this->current_relationship)) return; // Ajax add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']); } public function enqueue_scripts() { $vars = [ 'ajaxurl' => admin_url('admin-ajax.php'), 'current_relationship' => $this->current_relationship, 'relationships' => $this->relationships, ]; if (!empty($term = get_queried_object())) $vars['term'] = $term; wp_enqueue_script('jquery'); wp_enqueue_script('jquery-ui-sortable'); wp_enqueue_script('ogre-sort', Plugin::get_url('assets/sort.js'), ['jquery'], Plugin::get_version(), true); wp_localize_script('ogre-sort', 'ogre_sort', $vars); wp_enqueue_style('ogre-sort', Plugin::get_url('assets/sort.css'), false, Plugin::get_version()); } // Add Meta when Post is Updated public function save_post($post_id, $post, $update) { global $wpdb; $_post = get_post($post_id); if (wp_is_post_revision($post_id) || $_post->post_stauts == 'auto-draft') return; if (empty($this->relationships)) return; $relationships = []; foreach ($this->relationships as $relationship) { switch ($relationship['type']) { case 'post_type': if (isset($relationship['post_type']) && $relationship['post_type'] == $_post->post_type) { $relationships[] = $relationship; } break; case 'term': $taxonomies = get_object_taxonomies($_post->post_type); if (isset($relationship['taxonomy']) && is_array($taxonomies) && in_array($relationship['taxonomy'], $taxonomies)) { $relationships[] = $relationship; } break; } } if (empty($relationships)) return; foreach ($relationships as $relationship) { switch ($relationship['type']) { case 'post_type': if ($_post->post_status == 'draft' && !$update) { $wpdb->update($wpdb->posts, [ 'menu_order' => -1, ], [ 'ID' => $post_id, ]); } break; case 'term': $terms = wp_get_object_terms($post_id, $relationship['taxonomy'], [ 'fields' => 'ids', ]); if (is_array($terms) && !empty($terms)) { foreach ($terms as $term_id) { if (metadata_exists('post', $post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}")) continue; add_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}", -1, true); } } break; } } } // Query Filters public function pre_get_posts(WP_Query $wp_query):void { if (empty($this->relationships)) return; if (isset($wp_query->query['orderby']) && !empty($wp_query->query['orderby']) && ((is_admin() && isset($_GET['orderby'])) || $wp_query->query['orderby'] != 'date')) return; //if (!is_admin() && isset($wp_query->query['suppress_filters'])) return; // NOTE: suppress_filters set to true by default in get_posts //if (!isset($wp_query->query['post_type'])) $wp_query->set('post_type', 'post'); $relationships = []; foreach ($this->relationships as $relationship) { switch ($relationship['type']) { case 'post_type': if (isset($relationship['post_type']) && isset($wp_query->query['post_type']) && $relationship['post_type'] == $wp_query->query['post_type']) { $relationships[] = $relationship; } break; case 'term': if (isset($wp_query->query[$relationship['taxonomy']])) { $relationships[] = $relationship; } else if (isset($wp_query->query['taxonomy']) && $wp_query->query['taxonomy'] == $relationship['taxonomy']) { $relationships[] = $relationship; } else if (isset($wp_query->query['tax_query']) && is_array($wp_query->query['tax_query']) && !empty($wp_query->query['tax_query'])) { foreach ($wp_query->query['tax_query'] as $tax) { if (!isset($tax['taxonomy'])) continue; if ($relationship['taxonomy'] != $tax['taxonomy']) continue; $relationships[] = $relationship; break; } } else if ($relationship['taxonomy'] === 'category' && isset($wp_query->query['category_name'])) { $relationships[] = $relationship; } break; } } if (empty($relationships)) return; $current_relationship = $relationships[0]; foreach ($relationships as $relationship) { // Term relationships have higher priority if ($relationship['type'] == 'term') { $current_relationship = $relationship; break; } } if (empty($current_relationship)) $current_relationship = $relationships[0]; if (empty($current_relationship)) return; switch ($current_relationship['type']) { case 'term': $term_by = 'slug'; $term_id = ''; if (isset($wp_query->query[$current_relationship['taxonomy']])) { $term_id = $wp_query->query[$current_relationship['taxonomy']]; } else if (isset($wp_query->query['taxonomy']) && $wp_query->query['taxonomy'] == $current_relationship['taxonomy'] && isset($wp_query->query['term'])) { $term_id = $wp_query->query['term']; } else if (isset($wp_query->query['tax_query']) && is_array($wp_query->query['tax_query']) && !empty($wp_query->query['tax_query'])) { foreach ($wp_query->query['tax_query'] as $tax) { if (!isset($tax['taxonomy'])) continue; if ($current_relationship['taxonomy'] != $tax['taxonomy']) continue; $term_by = $tax['field']; $term_id = $tax['terms']; break; } } else if ($current_relationship['taxonomy'] === 'category' && isset($wp_query->query['category_name'])) { $term_id = $wp_query->query['category_name']; } if (empty($term_id)) break; $term = get_term_by($term_by, $term_id, $current_relationship['taxonomy']); if (!is_a($term, 'WP_Term')) break; $wp_query->set('meta_key', "ogre-sort_{$current_relationship['taxonomy']}_{$term->term_id}"); $wp_query->set('orderby', 'meta_value_num'); break; case 'post_type': $wp_query->set('orderby', 'menu_order'); break; } $wp_query->set('order', 'ASC'); } public function previous_post_where($where) { if (empty($this->relationships)) return $where; global $post; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'post_type' && $relationship['post_type'] == $post->post_type) { $active = true; break; } } if ($active == false) return $where; return str_replace("p.post_date < '{$post->post_date}'", "p.menu_order > '{$post->menu_order}'", $where); } public function previous_post_sort($orderby) { if (empty($this->relationships)) return $orderby; global $post; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'post_type' && $relationship['post_type'] == $post->post_type) { $active = true; break; } } if ($active == false) return $orderby; return 'ORDER BY p.menu_order ASC LIMIT 1'; } public function next_post_where($where) { if (empty($this->relationships)) return $where; global $post; if (empty($this->relationships)) return $where; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'post_type' && $relationship['post_type'] == $post->post_type) { $active = true; break; } } if ($active == false) return $where; return str_replace("p.post_date > '{$post->post_date}'", "p.menu_order < '{$post->menu_order}'", $where); } public function next_post_sort($orderby) { if (empty($this->relationships)) return $orderby; global $post; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'post_type' && $relationship['post_type'] == $post->post_type) { $active = true; break; } } if ($active == false) return $orderby; return 'ORDER BY p.menu_order DESC LIMIT 1'; } public function terms_clauses($pieces, $taxonomies, $args) { global $wpdb; if ((is_admin() && isset($_GET['orderby'])) || empty($this->relationships) || !isset($pieces['fields']) || strpos($pieces['fields'], 'tr.') || !isset($pieces['join']) || strpos($pieces['join'], $wpdb->term_relationships) != false) return $pieces; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'taxonomy' && $args['taxonomy'] != NULL && ($relationship['taxonomy'] == $args['taxonomy'] || in_array($relationship['taxonomy'], $args['taxonomy']))) { $active = true; break; } } if ($active == false) return $pieces; $pieces['fields'] .= ", tr.term_order AS term_order"; $pieces['join'] .= " INNER JOIN {$wpdb->term_relationships} AS tr ON ( tr.term_taxonomy_id = t.term_id AND tr.term_taxonomy_id = tt.term_taxonomy_id AND tr.object_id = t.term_id )"; return $pieces; } public function get_terms_orderby($orderby, $args, $taxonomies) { if ((is_admin() && isset($_GET['orderby'])) || empty($this->relationships)) return $orderby; $active = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'taxonomy' && $args['taxonomy'] != NULL && ($relationship['taxonomy'] == $args['taxonomy'] || in_array($relationship['taxonomy'], $args['taxonomy']))) { $active = true; break; } } if ($active == false) return $orderby; return 'tr.term_order'; } public function wp_get_object_terms($terms, $object_ids, $taxonomies, $args) { return $this->get_object_terms($terms, $args); } public function get_terms($terms, $taxonomies, $args, $term_query) { return $this->get_object_terms($terms, $args); } public function get_object_terms($terms, $args) { global $wpdb; if (empty($terms) || (is_admin() && isset($_GET['orderby'])) || empty($this->relationships) || !isset($args['taxonomy'])) return $terms; // Check if valid query $active_relationship = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'taxonomy' && ($relationship['taxonomy'] == $args['taxonomy'] || in_array($relationship['taxonomy'], $args['taxonomy']))) { $active_relationship = $relationship; break; } } if ($active_relationship == false) return $terms; // Add term_relationships.term_order to WP_Term object foreach ($terms as $key => $term) { if (is_numeric($term)) $term = get_term($term); $results = $wpdb->get_results(" SELECT term_relationships.term_order AS term_order FROM {$wpdb->term_relationships} AS term_relationships WHERE term_relationships.term_taxonomy_id = {$term->term_taxonomy_id} AND term_relationships.object_id = {$term->term_id} "); if (!empty($results)) { $term->term_order = intval($results[0]->term_order); } } usort($terms, function ($a, $b):int { if (is_object($a)) { if ($a->term_order == $b->term_order) return 0; return ($a->term_order < $b->term_order) ? -1 : 1; } else { if ($a == $b) return 0; return ($a < $b) ? -1 : 1; } }); return $terms; } public function add_term_relationship($term_id, $tt_id, $taxonomy) { // Check if taxonomy is within relationships $tax_relationship = false; foreach ($this->relationships as $relationship) { if ($relationship['type'] == 'taxonomy' && $relationship['taxonomy'] == $taxonomy) { $tax_relationship = $relationship; break; } } if ($tax_relationship == false) return; // Reset taxonomy terms to define term_relationship to added term $this->refresh($tax_relationship); } // Ajax Functions public function sort() { global $wpdb; $relationship = $_POST['relationship']; if (!is_array($relationship)) { wp_send_json_error(__('Invalid sort relationship.', 'ogrecore')); exit; } if (empty(array_filter($this->relationships, fn (array $item):bool => empty(array_diff($item, $relationship))))) { wp_send_json_error(__('Relationship does not exist.', 'ogrecore')); exit; } parse_str($_POST['order'], $data); if (!is_array($data)) { wp_send_json_error(__('Order data invalid.', 'ogrecore')); exit; } // Get objects per now page $object_ids = []; if (isset($data['post'])) { $object_ids = array_filter(array_map('intval', $data['post']), function ($object_id) { return is_int($object_id) && $object_id > 0; }); } else if (isset($data['tag'])) { $object_ids = array_filter(array_map('intval', $data['tag']), function ($object_id) { return is_int($object_id) && $object_id > 0; }); } if (empty($object_ids)) { wp_send_json_error(__('Unable to extract object ids from order data.', 'ogrecore')); exit; } switch ($relationship['type']) { case 'term': $term = $_POST['term']; if (!is_array($term)) { wp_send_json_error(__('Invalid term provided.', 'ogrecore')); exit; } foreach ($object_ids as $i => $post_id) { update_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term['term_id']}", $i + 1); } break; case 'post_type': foreach ($object_ids as $i => $post_id) { $wpdb->update($wpdb->posts, [ 'menu_order' => $i + 1, ], [ 'ID' => $post_id, ]); } break; case 'taxonomy': foreach ($object_ids as $i => $term_id) { $result = $wpdb->get_row(" SELECT term_taxonomy.term_taxonomy_id AS term_taxonomy_id, term_relationships.term_order AS term_order FROM {$wpdb->term_taxonomy} AS term_taxonomy INNER JOIN {$wpdb->term_relationships} AS term_relationships ON ( term_taxonomy.term_taxonomy_id = term_relationships.term_taxonomy_id AND term_taxonomy.term_id = term_relationships.object_id ) WHERE term_taxonomy.term_id = {$term_id} "); $wpdb->update($wpdb->term_relationships, [ 'term_order' => $i + 1, ], [ 'object_id' => $term_id, 'term_taxonomy_id' => $result->term_taxonomy_id, ]); } break; } wp_send_json_success(); exit; } // Core Functions public function valid($relationship, $post_id = false, $term_id = false) { switch ($relationship['type']) { case 'term': if ($post_id == false || !get_post_status($post_id)) { return false; } if ($term_id == false || !term_exists($term_id, $relationship['taxonomy'])) { return false; } break; case 'post_type': if ($post_id == false || !get_post_status($post_id)) { return false; } break; case 'taxonomy': if ($term_id == false || !term_exists($term_id, $relationship['taxonomy'])) { return false; } break; default: return false; } return true; } public function set($relationship, $order, $post_id = false, $term_id = false, $term_taxonomy_id = false) { global $wpdb; if (!$this->valid($relationship, $post_id, $term_id) || !is_int($order) || $order < 0) { return false; } switch ($relationship['type']) { case 'term': update_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}", $order); break; case 'post_type': $wpdb->update($wpdb->posts, [ 'menu_order' => $order, ], [ 'ID' => $post_id, ]); break; case 'taxonomy': $wpdb->update($wpdb->term_relationships, [ 'term_order' => $order, ], [ 'object_id' => $term_id, 'term_taxonomy_id' => $term_taxonomy_id, ]); break; } return true; } public function get($relationship, $post_id = false, $term_id = false, $term_taxonomy_id = false) { global $wpdb; if (!$this->valid($relationship, $post_id, $term_id)) { return false; } switch ($relationship['type']) { case 'term': return intval(get_post_meta($post_id, 'ogre-sort_' . $relationship['taxonomy'] . '_' . $term_id, true)); case 'post_type': $_post = get_post($post_id); return intval($_post->menu_order); case 'taxonomy': $results = $wpdb->get_results("SELECT term_order FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = {$term_taxonomy_id} AND object_id = {$term_id}"); if (!empty($results)) { return intval($results[0]->term_order); } else { $orders = get_option('term_order_' . $relationship['taxonomy'], []); if (!empty($orders)) { foreach ($orders as $position => $value) { if (intval($value) === intval($term_id)) return intval($position); } } } break; } return false; } public function reset($relationship) { global $wpdb; switch ($relationship['type']) { case 'term': $terms = get_terms([ 'taxonomy' => $relationship['taxonomy'], 'hide_empty' => false, 'fields' => 'ids', ]); if (!empty($terms)) { foreach ($terms as $term_id) { $posts = get_posts([ 'post_type' => $relationship['post_type'], 'posts_per_page' => -1, 'tax_query' => [[ 'taxonomy' => $relationship['taxonomy'], 'field' => 'id', 'terms' => $term_id, ]], 'fields' => 'ids', ]); if (!empty($posts)) { foreach ($posts as $post_id) { delete_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}"); } } } } break; case 'post_type': $results = $wpdb->get_results("SELECT ID FROM {$wpdb->posts} WHERE post_type = {$relationship['taxonomy']} AND post_status IN ('publish', 'pending', 'draft', 'private', 'future') ORDER BY post_date DESC"); if (!empty($results)) { $i = 0; foreach ($results as $row) { $wpdb->update($wpdb->posts, [ 'menu_order' => $i, ], [ 'ID' => $row->ID, ]); $i++; } } break; case 'taxonomy': $terms = get_terms([ 'taxonomy' => $relationship['taxonomy'], 'hide_empty' => false, 'fields' => 'all', 'orderby' => 'name', 'order' => 'ASC', ]); if (!empty($terms)) { foreach ($terms as $term) { $wpdb->delete($wpdb->term_relationships, [ 'object_id' => $term->term_id, 'term_taxonomy_id' => $term->term_taxonomy_id, ]); } } break; } return true; } public function reset_all() { foreach ($this->relationships as $relationship) { $this->reset($relationship); } } public function refresh($relationship) { global $wpdb; switch ($relationship['type']) { case 'term': $terms = get_terms([ 'taxonomy' => $relationship['taxonomy'], 'hide_empty' => true, 'fields' => 'ids', ]); if (empty($terms)) return false; $terms = array_unique($terms); foreach ($terms as $term_id) { // Set all undefined meta data $posts = get_posts([ 'post_type' => $relationship['post_type'], 'posts_per_page' => -1, 'offset' => 0, 'post_status' => 'any', 'fields' => 'ids', 'tax_query' => [[ 'taxonomy' => $relationship['taxonomy'], 'field' => 'term_id', 'terms' => $term_id, 'include_children' => true, 'operator' => 'IN', ]], 'meta_query' => [ 'relation' => 'OR', [ 'key' => "ogre-sort_{$relationship['taxonomy']}_{$term_id}", 'compare' => 'NOT EXISTS', 'value' => '', ], [ 'key' => "ogre-sort_{$relationship['taxonomy']}_{$term_id}", 'value' => '', ], ], ]); $posts = array_unique($posts); foreach ($posts as $post_id) { update_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}", -1); } // Reorder $posts = get_posts([ 'post_type' => $relationship['post_type'], 'posts_per_page' => -1, 'offset' => 0, 'post_status' => 'any', 'fields' => 'ids', 'tax_query' => [[ 'taxonomy' => $relationship['taxonomy'], 'field' => 'term_id', 'terms' => $term_id, 'include_children' => true, 'operator' => 'IN', ]], 'meta_key' => "ogre-sort_{$relationship['taxonomy']}_{$term_id}", 'orderby' => 'meta_value_num', ]); $posts = array_values(array_unique($posts)); foreach ($posts as $i => $post_id) { update_post_meta($post_id, "ogre-sort_{$relationship['taxonomy']}_{$term_id}", $i + 1); } } break; case 'post_type': $result = $wpdb->get_row(" SELECT count(*) as cnt, max(menu_order) as max, min(menu_order) as min FROM {$wpdb->posts} WHERE post_type = '{$relationship['post_type']}' AND post_status IN ('publish', 'pending', 'draft', 'private', 'future') "); if ($result->cnt == 0 || $result->cnt == $result->max) return false; $results = $wpdb->get_results(" SELECT ID FROM {$wpdb->posts} WHERE post_type = '{$relationship['post_type']}' AND post_status IN ('publish', 'pending', 'draft', 'private', 'future') ORDER BY menu_order ASC "); foreach ($results as $key => $result) { $wpdb->update($wpdb->posts, [ 'menu_order' => $key + 1, ], [ 'ID' => $result->ID, ]); } break; case 'taxonomy': // Set all undefined term_orders $results = $wpdb->get_results(" SELECT terms.term_id AS term_id, term_taxonomy.term_taxonomy_id AS term_taxonomy_id FROM {$wpdb->terms} AS terms INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy ON ( terms.term_id = term_taxonomy.term_id ) WHERE term_taxonomy.taxonomy = '{$relationship['taxonomy']}' "); if (empty($results)) return false; foreach ($results as $key => $result) { $term_order = $wpdb->get_results(" SELECT term_relationships.term_order, term_relationships.object_id FROM {$wpdb->term_relationships} AS term_relationships WHERE term_relationships.term_taxonomy_id = {$result->term_taxonomy_id} AND term_relationships.object_id = {$result->term_id} "); if (empty($term_order)) { $wpdb->insert($wpdb->term_relationships, [ 'object_id' => $result->term_id, 'term_taxonomy_id' => $result->term_taxonomy_id, 'term_order' => -1, ]); } } // Reorder term_orders $results = $wpdb->get_results(" SELECT terms.term_id AS term_id, term_taxonomy.term_taxonomy_id AS term_taxonomy_id, term_relationships.term_order FROM {$wpdb->terms} AS terms INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy ON ( terms.term_id = term_taxonomy.term_id ) INNER JOIN {$wpdb->term_relationships} AS term_relationships ON ( term_taxonomy.term_taxonomy_id = term_relationships.term_taxonomy_id AND term_taxonomy.term_id = term_relationships.object_id ) WHERE term_taxonomy.taxonomy = '{$relationship['taxonomy']}' ORDER BY term_order ASC "); foreach ($results as $key => $result) { $wpdb->update($wpdb->term_relationships, [ 'term_order' => $key + 1, ], [ 'object_id' => $result->term_id, 'term_taxonomy_id' => $result->term_taxonomy_id, ]); } break; } return true; } public function refresh_purge_cache() { if (!isset($_GET['type'], $_GET['_wpnonce'])) return; $_type = explode('-', $_GET['type']); $_type = reset($_type); if ($_type == 'all') { $this->refresh_all(); } } public function refresh_screen($current_screen) { // TODO: Move this to button on settings with post action if ($current_screen->id == 'dashboard') { $this->refresh_all(); } } public function refresh_all() { foreach ($this->relationships as $relationship) { $this->refresh($relationship); } } } Sort::instance();