labels->name ?? $tax_slug; // Get associated post types to place it in the correct menu structure $object_types = $tax_obj->object_type ?? array(); $parent_slug = 'edit.php'; // Fallback to posts if ( ! empty( $object_types ) ) { $first_pt = $object_types[0]; if ( $first_pt !== 'post' ) { $parent_slug = 'edit.php?post_type=' . $first_pt; } } add_submenu_page( $parent_slug, 'Reorder ' . $label, 'Reorder ' . $label, 'manage_categories', 'metaforge_term_reorder_' . $tax_slug, function () use ( $tax_slug, $tax_obj ) { $this->reorder_page( $tax_slug, $tax_obj ); } ); } } // Enqueue Scripts ------------------------------------------------------- public function enqueue_scripts( $hook ) { if ( strpos( $hook, 'metaforge_term_reorder_' ) === false ) { return; } wp_enqueue_script( 'metaforge-sortable-js' ); // registered by MetaForge_Loader $js_ver = METAFORGE_VERSION; wp_enqueue_script( 'metaforge-taxonomy-order-js', METAFORGE_URL . 'assets/js/taxonomy-order.js', array( 'jquery', 'metaforge-sortable-js' ), $js_ver, true ); wp_localize_script( 'metaforge-taxonomy-order-js', 'metaforgeTaxOrderConfig', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'metaforge_term_order_nonce' ), ) ); // admin.css is already enqueued by MetaForge_Loader for $is_reorder pages } // Settings Page --------------------------------------------------------- public function save_settings() { if ( isset( $_POST['metaforge_save_term_order_settings'] ) ) { check_admin_referer( 'metaforge_term_order_settings_action', 'metaforge_term_order_settings_nonce' ); if ( ! current_user_can( 'manage_metaforge' ) ) { wp_die( 'Insufficient permissions.' ); } $enabled = array(); if ( isset( $_POST['enabled_taxonomies'] ) && is_array( $_POST['enabled_taxonomies'] ) ) { $enabled = array_map( 'sanitize_key', $_POST['enabled_taxonomies'] ); } update_option( self::SETTINGS_KEY, array( 'enabled_taxonomies' => $enabled ) ); flush_rewrite_rules(); wp_safe_redirect( admin_url( 'admin.php?page=metaforge_term_order&settings-updated=true' ) ); exit; } } public function settings_page() { if ( ! current_user_can( 'manage_metaforge' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only notice query flag. $saved = isset( $_GET['settings-updated'] ) && sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) === 'true'; $settings = get_option( self::SETTINGS_KEY, array( 'enabled_taxonomies' => array() ) ); $enabled_taxs = $settings['enabled_taxonomies'] ?? array(); $builtin = get_taxonomies( array( 'public' => true, '_builtin' => true, ), 'objects' ); $metaforge_taxs = get_option( 'metaforge_taxonomies', array() ); $all_taxs = $builtin; foreach ( $metaforge_taxs as $tax ) { $slug = $tax['slug'] ?? ''; if ( $slug && ( $obj = get_taxonomy( $slug ) ) ) { $all_taxs[ $slug ] = $obj; } } ?>

Taxonomy Order Settings

← Back

Enable Taxonomy Ordering
TaxonomiesSelect taxonomies to enable drag-and-drop taxonomy ordering.
$tax_obj ) : ?>
labels->name ?? $tax_slug; // Ensure all existing terms have an order meta value before displaying $this->lazy_init_order( $tax_slug ); // Fetch terms sorted by saved order - bypass apply_term_order by using explicit args // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required for taxonomy term reordering interface. $terms = get_terms( array( 'taxonomy' => $tax_slug, 'hide_empty' => false, 'meta_key' => self::META_KEY, 'orderby' => 'meta_value_num', 'order' => 'ASC', ) ); // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key if ( is_wp_error( $terms ) ) { $terms = array(); } ?>

Reorder:

← Back
  • No terms found.
  • name ); ?> #term_id; ?> · count; ?> posts
'Permission denied.' ) ); } $term_ids = isset( $_POST['order'] ) ? array_map( 'intval', (array) $_POST['order'] ) : array(); $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( $_POST['taxonomy'] ) : ''; if ( empty( $term_ids ) || empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) { wp_send_json_error( array( 'message' => 'Invalid data.' ) ); } // Multiply by 10 to leave gaps for future insertions and // avoid collisions with lazy_init values (which start from max+10) foreach ( $term_ids as $position => $term_id ) { update_term_meta( $term_id, self::META_KEY, ( $position + 1 ) * 10 ); } wp_send_json_success( array( 'message' => 'Taxonomy order saved.' ) ); } // Hooks ----------------------------------------------------------------- public function filter_get_terms_args( $args, $taxonomies ) { $settings = get_option( self::SETTINGS_KEY, array() ); $enabled_taxs = $settings['enabled_taxonomies'] ?? array(); if ( empty( $enabled_taxs ) ) { return $args; } $has_enabled = false; $current_tax = ''; foreach ( (array) $taxonomies as $tax ) { if ( in_array( $tax, $enabled_taxs, true ) ) { $has_enabled = true; $current_tax = $tax; break; } } if ( ! $has_enabled ) { return $args; } // Skip lightweight sub-queries — only sort full object result sets $fields = $args['fields'] ?? ''; if ( in_array( $fields, array( 'count', 'ids', 'tt_ids', 'id=>name', 'id=>slug', 'id=>parent', 'names' ), true ) ) { return $args; } // Only intercept default / name / count ordering $current_orderby = $args['orderby'] ?? ''; if ( ! empty( $current_orderby ) && ! in_array( $current_orderby, array( 'name', 'count', '' ), true ) ) { return $args; } // Guarantee every term in this taxonomy has a meta record so that the // INNER JOIN WordPress adds for meta_key does not drop any rows. // lazy_init_order() is a no-op (single fast SELECT) once all terms // already have the key, so the overhead is negligible. $this->lazy_init_order( $current_tax ); // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Required for taxonomy term reordering filter. $args['meta_key'] = self::META_KEY; $args['orderby'] = 'meta_value_num'; $args['order'] = 'ASC'; // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key return $args; } public function set_default_term_order( $term_id, $tt_id, $taxonomy ) { $settings = get_option( self::SETTINGS_KEY, array() ); $enabled_taxs = $settings['enabled_taxonomies'] ?? array(); if ( ! in_array( $taxonomy, $enabled_taxs, true ) ) { return; } $taxonomy_obj = get_taxonomy( $taxonomy ); if ( ! $taxonomy_obj || ! current_user_can( $taxonomy_obj->cap->edit_terms ) || ! current_user_can( 'edit_term', $term_id ) ) { return; } // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Querying max term order meta. $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'fields' => 'ids', 'meta_key' => self::META_KEY, 'orderby' => 'meta_value_num', 'order' => 'DESC', 'number' => 1, ) ); // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key $max = ( ! is_wp_error( $terms ) && ! empty( $terms ) ) ? (int) get_term_meta( $terms[0], self::META_KEY, true ) : 0; update_term_meta( $term_id, self::META_KEY, $max + 10 ); } private function lazy_init_order( $taxonomy ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom query needed to lazy-init missing term order metadata. $term_ids = $wpdb->get_col( $wpdb->prepare( " SELECT tt.term_id FROM {$wpdb->term_taxonomy} tt LEFT JOIN {$wpdb->termmeta} tm ON (tt.term_id = tm.term_id AND tm.meta_key = %s) WHERE tt.taxonomy = %s AND tm.meta_id IS NULL ", self::META_KEY, $taxonomy ) ); if ( empty( $term_ids ) ) { return; } // Find the current max order // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom query needed to get maximum term order. $max = (int) $wpdb->get_var( $wpdb->prepare( " SELECT MAX(CAST(meta_value AS SIGNED)) FROM {$wpdb->termmeta} WHERE meta_key = %s ", self::META_KEY ) ); // Update the missing terms starting from max + 10 $position = $max + 10; foreach ( $term_ids as $term_id ) { update_term_meta( $term_id, self::META_KEY, $position ); $position += 10; } } } }