88 * @since 4.6.0 Introduced 'term_taxonomy_id' parameter. |
89 * @since 4.6.0 Introduced 'term_taxonomy_id' parameter. |
89 * @since 4.7.0 Introduced 'object_ids' parameter. |
90 * @since 4.7.0 Introduced 'object_ids' parameter. |
90 * @since 4.9.0 Added 'slug__in' support for 'orderby'. |
91 * @since 4.9.0 Added 'slug__in' support for 'orderby'. |
91 * @since 5.1.0 Introduced the 'meta_compare_key' parameter. |
92 * @since 5.1.0 Introduced the 'meta_compare_key' parameter. |
92 * @since 5.3.0 Introduced the 'meta_type_key' parameter. |
93 * @since 5.3.0 Introduced the 'meta_type_key' parameter. |
|
94 * @since 6.4.0 Introduced the 'cache_results' parameter. |
93 * |
95 * |
94 * @param string|array $query { |
96 * @param string|array $query { |
95 * Optional. Array or query string of term query parameters. Default empty. |
97 * Optional. Array or query string of term query parameters. Default empty. |
96 * |
98 * |
97 * @type string|string[] $taxonomy Taxonomy name, or array of taxonomy names, to which results |
99 * @type string|string[] $taxonomy Taxonomy name, or array of taxonomy names, to which results |
175 * @type bool $childless True to limit results to terms that have no children. |
177 * @type bool $childless True to limit results to terms that have no children. |
176 * This parameter has no effect on non-hierarchical taxonomies. |
178 * This parameter has no effect on non-hierarchical taxonomies. |
177 * Default false. |
179 * Default false. |
178 * @type string $cache_domain Unique cache key to be produced when this query is stored in |
180 * @type string $cache_domain Unique cache key to be produced when this query is stored in |
179 * an object cache. Default 'core'. |
181 * an object cache. Default 'core'. |
|
182 * @type bool $cache_results Whether to cache term information. Default true. |
180 * @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true. |
183 * @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true. |
181 * @type string|string[] $meta_key Meta key or keys to filter by. |
184 * @type string|string[] $meta_key Meta key or keys to filter by. |
182 * @type string|string[] $meta_value Meta value or values to filter by. |
185 * @type string|string[] $meta_value Meta value or values to filter by. |
183 * @type string $meta_compare MySQL operator used for comparing the meta value. |
186 * @type string $meta_compare MySQL operator used for comparing the meta value. |
184 * See WP_Meta_Query::__construct for accepted values and default value. |
187 * See WP_Meta_Query::__construct() for accepted values and default value. |
185 * @type string $meta_compare_key MySQL operator used for comparing the meta key. |
188 * @type string $meta_compare_key MySQL operator used for comparing the meta key. |
186 * See WP_Meta_Query::__construct for accepted values and default value. |
189 * See WP_Meta_Query::__construct() for accepted values and default value. |
187 * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons. |
190 * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons. |
188 * See WP_Meta_Query::__construct for accepted values and default value. |
191 * See WP_Meta_Query::__construct() for accepted values and default value. |
189 * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons. |
192 * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons. |
190 * See WP_Meta_Query::__construct for accepted values and default value. |
193 * See WP_Meta_Query::__construct() for accepted values and default value. |
191 * @type array $meta_query An associative array of WP_Meta_Query arguments. |
194 * @type array $meta_query An associative array of WP_Meta_Query arguments. |
192 * See WP_Meta_Query::__construct for accepted values. |
195 * See WP_Meta_Query::__construct() for accepted values. |
193 * } |
196 * } |
194 */ |
197 */ |
195 public function __construct( $query = '' ) { |
198 public function __construct( $query = '' ) { |
196 $this->query_var_defaults = array( |
199 $this->query_var_defaults = array( |
197 'taxonomy' => null, |
200 'taxonomy' => null, |
529 * @param string[] $taxonomies An array of taxonomy names. |
535 * @param string[] $taxonomies An array of taxonomy names. |
530 */ |
536 */ |
531 $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies ); |
537 $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies ); |
532 |
538 |
533 if ( ! empty( $exclusions ) ) { |
539 if ( ! empty( $exclusions ) ) { |
534 // Must do string manipulation here for backward compatibility with filter. |
540 // Strip leading 'AND'. Must do string manipulation here for backward compatibility with filter. |
535 $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions ); |
541 $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions ); |
536 } |
542 } |
537 |
543 |
538 if ( '' === $args['name'] ) { |
544 if ( '' === $args['name'] ) { |
539 $args['name'] = array(); |
545 $args['name'] = array(); |
541 $args['name'] = (array) $args['name']; |
547 $args['name'] = (array) $args['name']; |
542 } |
548 } |
543 |
549 |
544 if ( ! empty( $args['name'] ) ) { |
550 if ( ! empty( $args['name'] ) ) { |
545 $names = $args['name']; |
551 $names = $args['name']; |
|
552 |
546 foreach ( $names as &$_name ) { |
553 foreach ( $names as &$_name ) { |
547 // `sanitize_term_field()` returns slashed data. |
554 // `sanitize_term_field()` returns slashed data. |
548 $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) ); |
555 $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) ); |
549 } |
556 } |
550 |
557 |
648 $this->meta_query->parse_query_vars( $this->query_vars ); |
655 $this->meta_query->parse_query_vars( $this->query_vars ); |
649 $mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' ); |
656 $mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' ); |
650 $meta_clauses = $this->meta_query->get_clauses(); |
657 $meta_clauses = $this->meta_query->get_clauses(); |
651 |
658 |
652 if ( ! empty( $meta_clauses ) ) { |
659 if ( ! empty( $meta_clauses ) ) { |
653 $join .= $mq_sql['join']; |
660 $join .= $mq_sql['join']; |
|
661 |
|
662 // Strip leading 'AND'. |
654 $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] ); |
663 $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] ); |
655 $distinct .= 'DISTINCT'; |
664 |
|
665 $distinct .= 'DISTINCT'; |
656 |
666 |
657 } |
667 } |
658 |
668 |
659 $selects = array(); |
669 $selects = array(); |
660 switch ( $args['fields'] ) { |
670 switch ( $args['fields'] ) { |
698 $distinct = 'DISTINCT'; |
708 $distinct = 'DISTINCT'; |
699 } |
709 } |
700 |
710 |
701 $where = implode( ' AND ', $this->sql_clauses['where'] ); |
711 $where = implode( ' AND ', $this->sql_clauses['where'] ); |
702 |
712 |
703 $clauses = array( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ); |
713 $pieces = array( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ); |
704 |
714 |
705 /** |
715 /** |
706 * Filters the terms query SQL clauses. |
716 * Filters the terms query SQL clauses. |
707 * |
717 * |
708 * @since 3.1.0 |
718 * @since 3.1.0 |
719 * @type string $limits The LIMIT clause of the query. |
729 * @type string $limits The LIMIT clause of the query. |
720 * } |
730 * } |
721 * @param string[] $taxonomies An array of taxonomy names. |
731 * @param string[] $taxonomies An array of taxonomy names. |
722 * @param array $args An array of term query arguments. |
732 * @param array $args An array of term query arguments. |
723 */ |
733 */ |
724 $clauses = apply_filters( 'terms_clauses', compact( $clauses ), $taxonomies, $args ); |
734 $clauses = apply_filters( 'terms_clauses', compact( $pieces ), $taxonomies, $args ); |
725 |
735 |
726 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; |
736 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; |
727 $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; |
737 $join = isset( $clauses['join'] ) ? $clauses['join'] : ''; |
728 $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; |
738 $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; |
729 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : ''; |
739 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : ''; |
730 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : ''; |
740 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : ''; |
731 $order = isset( $clauses['order'] ) ? $clauses['order'] : ''; |
741 $order = isset( $clauses['order'] ) ? $clauses['order'] : ''; |
732 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : ''; |
742 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : ''; |
733 |
743 |
|
744 $fields_is_filtered = implode( ', ', $selects ) !== $fields; |
|
745 |
734 if ( $where ) { |
746 if ( $where ) { |
735 $where = "WHERE $where"; |
747 $where = "WHERE $where"; |
736 } |
748 } |
737 |
749 |
738 $this->sql_clauses['select'] = "SELECT $distinct $fields"; |
750 $this->sql_clauses['select'] = "SELECT $distinct $fields"; |
739 $this->sql_clauses['from'] = "FROM $wpdb->terms AS t $join"; |
751 $this->sql_clauses['from'] = "FROM $wpdb->terms AS t $join"; |
740 $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : ''; |
752 $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : ''; |
741 $this->sql_clauses['limits'] = $limits; |
753 $this->sql_clauses['limits'] = $limits; |
742 |
754 |
743 $this->request = " |
755 // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841. |
744 {$this->sql_clauses['select']} |
756 $this->request = |
745 {$this->sql_clauses['from']} |
757 "{$this->sql_clauses['select']} |
746 {$where} |
758 {$this->sql_clauses['from']} |
747 {$this->sql_clauses['orderby']} |
759 {$where} |
748 {$this->sql_clauses['limits']} |
760 {$this->sql_clauses['orderby']} |
749 "; |
761 {$this->sql_clauses['limits']}"; |
750 |
762 |
751 $this->terms = null; |
763 $this->terms = null; |
752 |
764 |
753 /** |
765 /** |
754 * Filters the terms array before the query takes place. |
766 * Filters the terms array before the query takes place. |
765 |
777 |
766 if ( null !== $this->terms ) { |
778 if ( null !== $this->terms ) { |
767 return $this->terms; |
779 return $this->terms; |
768 } |
780 } |
769 |
781 |
770 // $args can be anything. Only use the args defined in defaults to compute the key. |
782 if ( $args['cache_results'] ) { |
771 $cache_args = wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ); |
783 $cache_key = $this->generate_cache_key( $args, $this->request ); |
772 |
784 $cache = wp_cache_get( $cache_key, 'term-queries' ); |
773 unset( $cache_args['update_term_meta_cache'] ); |
785 |
774 |
786 if ( false !== $cache ) { |
775 if ( 'count' !== $_fields && 'all_with_object_id' !== $_fields ) { |
787 if ( 'ids' === $_fields ) { |
776 $cache_args['fields'] = 'all'; |
788 $cache = array_map( 'intval', $cache ); |
777 } |
789 } elseif ( 'count' !== $_fields ) { |
778 |
790 if ( ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) |
779 $key = md5( serialize( $cache_args ) . serialize( $taxonomies ) . $this->request ); |
791 || ( 'all' === $_fields && $args['pad_counts'] || $fields_is_filtered ) |
780 $last_changed = wp_cache_get_last_changed( 'terms' ); |
792 ) { |
781 $cache_key = "get_terms:$key:$last_changed"; |
793 $term_ids = wp_list_pluck( $cache, 'term_id' ); |
782 $cache = wp_cache_get( $cache_key, 'terms' ); |
794 } else { |
783 |
795 $term_ids = array_map( 'intval', $cache ); |
784 if ( false !== $cache ) { |
796 } |
785 if ( 'ids' === $_fields ) { |
797 |
786 $cache = array_map( 'intval', $cache ); |
798 _prime_term_caches( $term_ids, $args['update_term_meta_cache'] ); |
787 } elseif ( 'count' !== $_fields ) { |
799 |
788 if ( ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) || ( 'all' === $_fields && $args['pad_counts'] ) ) { |
800 $term_objects = $this->populate_terms( $cache ); |
789 $term_ids = wp_list_pluck( $cache, 'term_id' ); |
801 $cache = $this->format_terms( $term_objects, $_fields ); |
790 } else { |
|
791 $term_ids = array_map( 'intval', $cache ); |
|
792 } |
802 } |
793 _prime_term_caches( $term_ids, $args['update_term_meta_cache'] ); |
803 |
794 $term_objects = $this->populate_terms( $cache ); |
804 $this->terms = $cache; |
795 $cache = $this->format_terms( $term_objects, $_fields ); |
805 return $this->terms; |
796 } |
806 } |
797 |
|
798 $this->terms = $cache; |
|
799 return $this->terms; |
|
800 } |
807 } |
801 |
808 |
802 if ( 'count' === $_fields ) { |
809 if ( 'count' === $_fields ) { |
803 $count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
810 $count = $wpdb->get_var( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
804 wp_cache_set( $cache_key, $count, 'terms' ); |
811 if ( $args['cache_results'] ) { |
|
812 wp_cache_set( $cache_key, $count, 'term-queries' ); |
|
813 } |
805 return $count; |
814 return $count; |
806 } |
815 } |
807 |
816 |
808 $terms = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
817 $terms = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
809 |
818 |
810 if ( empty( $terms ) ) { |
819 if ( empty( $terms ) ) { |
811 wp_cache_add( $cache_key, array(), 'terms' ); |
820 if ( $args['cache_results'] ) { |
|
821 wp_cache_add( $cache_key, array(), 'term-queries' ); |
|
822 } |
812 return array(); |
823 return array(); |
813 } |
824 } |
814 |
825 |
815 $term_ids = wp_list_pluck( $terms, 'term_id' ); |
826 $term_ids = wp_list_pluck( $terms, 'term_id' ); |
816 _prime_term_caches( $term_ids, false ); |
827 _prime_term_caches( $term_ids, false ); |
835 // Make sure we show empty categories that have children. |
846 // Make sure we show empty categories that have children. |
836 if ( $hierarchical && $args['hide_empty'] && is_array( $term_objects ) ) { |
847 if ( $hierarchical && $args['hide_empty'] && is_array( $term_objects ) ) { |
837 foreach ( $term_objects as $k => $term ) { |
848 foreach ( $term_objects as $k => $term ) { |
838 if ( ! $term->count ) { |
849 if ( ! $term->count ) { |
839 $children = get_term_children( $term->term_id, $term->taxonomy ); |
850 $children = get_term_children( $term->term_id, $term->taxonomy ); |
|
851 |
840 if ( is_array( $children ) ) { |
852 if ( is_array( $children ) ) { |
841 foreach ( $children as $child_id ) { |
853 foreach ( $children as $child_id ) { |
842 $child = get_term( $child_id, $term->taxonomy ); |
854 $child = get_term( $child_id, $term->taxonomy ); |
843 if ( $child->count ) { |
855 if ( $child->count ) { |
844 continue 2; |
856 continue 2; |
862 } |
874 } |
863 |
875 |
864 // Prime termmeta cache. |
876 // Prime termmeta cache. |
865 if ( $args['update_term_meta_cache'] ) { |
877 if ( $args['update_term_meta_cache'] ) { |
866 $term_ids = wp_list_pluck( $term_objects, 'term_id' ); |
878 $term_ids = wp_list_pluck( $term_objects, 'term_id' ); |
867 update_termmeta_cache( $term_ids ); |
879 wp_lazyload_term_meta( $term_ids ); |
868 } |
880 } |
869 |
881 |
870 if ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) { |
882 if ( 'all_with_object_id' === $_fields && ! empty( $args['object_ids'] ) ) { |
871 $term_cache = array(); |
883 $term_cache = array(); |
872 foreach ( $term_objects as $term ) { |
884 foreach ( $term_objects as $term ) { |
881 $object = new stdClass(); |
893 $object = new stdClass(); |
882 $object->term_id = $term->term_id; |
894 $object->term_id = $term->term_id; |
883 $object->count = $term->count; |
895 $object->count = $term->count; |
884 $term_cache[] = $object; |
896 $term_cache[] = $object; |
885 } |
897 } |
|
898 } elseif ( $fields_is_filtered ) { |
|
899 $term_cache = $term_objects; |
886 } else { |
900 } else { |
887 $term_cache = wp_list_pluck( $term_objects, 'term_id' ); |
901 $term_cache = wp_list_pluck( $term_objects, 'term_id' ); |
888 } |
902 } |
889 wp_cache_add( $cache_key, $term_cache, 'terms' ); |
903 |
|
904 if ( $args['cache_results'] ) { |
|
905 wp_cache_add( $cache_key, $term_cache, 'term-queries' ); |
|
906 } |
|
907 |
890 $this->terms = $this->format_terms( $term_objects, $_fields ); |
908 $this->terms = $this->format_terms( $term_objects, $_fields ); |
891 |
909 |
892 return $this->terms; |
910 return $this->terms; |
893 } |
911 } |
894 |
912 |
895 /** |
913 /** |
896 * Parse and sanitize 'orderby' keys passed to the term query. |
914 * Parse and sanitize 'orderby' keys passed to the term query. |
897 * |
915 * |
898 * @since 4.6.0 |
916 * @since 4.6.0 |
899 * |
|
900 * @global wpdb $wpdb WordPress database abstraction object. |
|
901 * |
917 * |
902 * @param string $orderby_raw Alias for the field to order by. |
918 * @param string $orderby_raw Alias for the field to order by. |
903 * @return string|false Value to used in the ORDER clause. False otherwise. |
919 * @return string|false Value to used in the ORDER clause. False otherwise. |
904 */ |
920 */ |
905 protected function parse_orderby( $orderby_raw ) { |
921 protected function parse_orderby( $orderby_raw ) { |
1129 } |
1145 } |
1130 } |
1146 } |
1131 |
1147 |
1132 return $term_objects; |
1148 return $term_objects; |
1133 } |
1149 } |
|
1150 |
|
1151 /** |
|
1152 * Generate cache key. |
|
1153 * |
|
1154 * @since 6.2.0 |
|
1155 * |
|
1156 * @global wpdb $wpdb WordPress database abstraction object. |
|
1157 * |
|
1158 * @param array $args WP_Term_Query arguments. |
|
1159 * @param string $sql SQL statement. |
|
1160 * |
|
1161 * @return string Cache key. |
|
1162 */ |
|
1163 protected function generate_cache_key( array $args, $sql ) { |
|
1164 global $wpdb; |
|
1165 // $args can be anything. Only use the args defined in defaults to compute the key. |
|
1166 $cache_args = wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ); |
|
1167 |
|
1168 unset( $cache_args['cache_results'], $cache_args['update_term_meta_cache'] ); |
|
1169 |
|
1170 if ( 'count' !== $args['fields'] && 'all_with_object_id' !== $args['fields'] ) { |
|
1171 $cache_args['fields'] = 'all'; |
|
1172 } |
|
1173 $taxonomies = (array) $args['taxonomy']; |
|
1174 |
|
1175 // Replace wpdb placeholder in the SQL statement used by the cache key. |
|
1176 $sql = $wpdb->remove_placeholder_escape( $sql ); |
|
1177 |
|
1178 $key = md5( serialize( $cache_args ) . serialize( $taxonomies ) . $sql ); |
|
1179 $last_changed = wp_cache_get_last_changed( 'terms' ); |
|
1180 return "get_terms:$key:$last_changed"; |
|
1181 } |
1134 } |
1182 } |