Skip to:
Content

BuddyPress.org

Changeset 9253


Ignore:
Timestamp:
12/22/2014 06:38:04 PM (10 years ago)
Author:
r-a-y
Message:

Core: Introduce new class - BP_Recursive_Query.

BP_Recursive_Query is a new abstract class based off of work done in
WordPress 4.1 to support the creation of advanced SQL clauses (nested
clauses and multiple operators). See:
https://core.trac.wordpress.org/ticket/29642#comment:6

The BP_XProfile_Query class already uses a version of this class. The
goal is for BP_XProfile_Query to extend BP_Recursive_Query in a future
commit (see https://buddypress.trac.wordpress.org/ticket/5839#comment:9).
Also, the activity component will be the next to use the class to support
advanced filtering (#4988).

Props boonebgorges.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-core/bp-core-classes.php

    r9211 r9253  
    27552755    }
    27562756}
     2757
     2758/**
     2759 * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params.
     2760 *
     2761 * @since BuddyPress (2.2.0)
     2762 */
     2763abstract class BP_Recursive_Query {
     2764
     2765        /**
     2766         * Query arguments passed to the constructor.
     2767         *
     2768         * @since BuddyPress (2.2.0)
     2769         * @access public
     2770         * @var array
     2771         */
     2772        public $queries = array();
     2773
     2774        /**
     2775         * Generate SQL clauses to be appended to a main query.
     2776         *
     2777         * Extending classes should call this method from within a publicly
     2778         * accessible get_sql() method, and manipulate the SQL as necessary.
     2779         * For example, {@link BP_XProfile_Query::get_sql()} is merely a wrapper for
     2780         * get_sql_clauses(), while {@link BP_Activity_Query::get_sql()} discards
     2781         * the empty 'join' clause, and only passes the 'where' clause.
     2782         *
     2783         * @since BuddyPress (2.2.0)
     2784         * @access protected
     2785         *
     2786         * @param  string $primary_table
     2787         * @param  string $primary_id_column
     2788         * @return array
     2789         */
     2790        protected function get_sql_clauses() {
     2791                $sql = $this->get_sql_for_query( $this->queries );
     2792
     2793                if ( ! empty( $sql['where'] ) ) {
     2794                        $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n";
     2795                }
     2796
     2797                return $sql;
     2798        }
     2799
     2800        /**
     2801         * Generate SQL clauses for a single query array.
     2802         *
     2803         * If nested subqueries are found, this method recurses the tree to
     2804         * produce the properly nested SQL.
     2805         *
     2806         * Subclasses generally do not need to call this method. It is invoked
     2807         * automatically from get_sql_clauses().
     2808         *
     2809         * @since BuddyPress (2.2.0)
     2810         * @access protected
     2811         *
     2812         * @param  array $query Query to parse.
     2813         * @param  int   $depth Optional. Number of tree levels deep we
     2814         *                      currently are. Used to calculate indentation.
     2815         * @return array
     2816         */
     2817        protected function get_sql_for_query( $query, $depth = 0 ) {
     2818                $sql_chunks = array(
     2819                        'join'  => array(),
     2820                        'where' => array(),
     2821                );
     2822
     2823                $sql = array(
     2824                        'join'  => '',
     2825                        'where' => '',
     2826                );
     2827
     2828                $indent = '';
     2829                for ( $i = 0; $i < $depth; $i++ ) {
     2830                        $indent .= "\t";
     2831                }
     2832
     2833                foreach ( $query as $key => $clause ) {
     2834                        if ( 'relation' === $key ) {
     2835                                $relation = $query['relation'];
     2836                        } else if ( is_array( $clause ) ) {
     2837                                // This is a first-order clause
     2838                                if ( $this->is_first_order_clause( $clause ) ) {
     2839                                        $clause_sql = $this->get_sql_for_clause( $clause, $query );
     2840
     2841                                        $where_count = count( $clause_sql['where'] );
     2842                                        if ( ! $where_count ) {
     2843                                                $sql_chunks['where'][] = '';
     2844                                        } else if ( 1 === $where_count ) {
     2845                                                $sql_chunks['where'][] = $clause_sql['where'][0];
     2846                                        } else {
     2847                                                $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
     2848                                        }
     2849
     2850                                        $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
     2851                                // This is a subquery
     2852                                } else {
     2853                                        $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
     2854
     2855                                        $sql_chunks['where'][] = $clause_sql['where'];
     2856                                        $sql_chunks['join'][]  = $clause_sql['join'];
     2857
     2858                                }
     2859                        }
     2860                }
     2861
     2862                // Filter empties
     2863                $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
     2864                $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
     2865
     2866                if ( empty( $relation ) ) {
     2867                        $relation = 'AND';
     2868                }
     2869
     2870                if ( ! empty( $sql_chunks['join'] ) ) {
     2871                        $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
     2872                }
     2873
     2874                if ( ! empty( $sql_chunks['where'] ) ) {
     2875                        $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n";
     2876                }
     2877
     2878                return $sql;
     2879        }
     2880
     2881    /**
     2882     * Recursive-friendly query sanitizer.
     2883     *
     2884     * Ensures that each query-level clause has a 'relation' key, and that
     2885     * each first-order clause contains all the necessary keys from
     2886     * $defaults.
     2887     *
     2888     * Extend this method if your class uses different sanitizing logic.
     2889     *
     2890     * @since BuddyPress (2.2.0)
     2891     * @access public
     2892     *
     2893     * @param  array $queries Array of query clauses.
     2894     * @return array Sanitized array of query clauses.
     2895     */
     2896    protected function sanitize_query( $queries ) {
     2897        $clean_queries = array();
     2898
     2899        if ( ! is_array( $queries ) ) {
     2900            return $clean_queries;
     2901        }
     2902
     2903        foreach ( $queries as $key => $query ) {
     2904            if ( 'relation' === $key ) {
     2905                $relation = $query;
     2906
     2907            } else if ( ! is_array( $query ) ) {
     2908                continue;
     2909
     2910            // First-order clause.
     2911            } else if ( $this->is_first_order_clause( $query ) ) {
     2912                if ( isset( $query['value'] ) && array() === $query['value'] ) {
     2913                    unset( $query['value'] );
     2914                }
     2915
     2916                $clean_queries[] = $query;
     2917
     2918            // Otherwise, it's a nested query, so we recurse.
     2919            } else {
     2920                $cleaned_query = $this->sanitize_query( $query );
     2921
     2922                if ( ! empty( $cleaned_query ) ) {
     2923                    $clean_queries[] = $cleaned_query;
     2924                }
     2925            }
     2926        }
     2927
     2928        if ( empty( $clean_queries ) ) {
     2929            return $clean_queries;
     2930        }
     2931
     2932        // Sanitize the 'relation' key provided in the query.
     2933        if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
     2934            $clean_queries['relation'] = 'OR';
     2935
     2936        /*
     2937         * If there is only a single clause, call the relation 'OR'.
     2938         * This value will not actually be used to join clauses, but it
     2939         * simplifies the logic around combining key-only queries.
     2940         */
     2941        } else if ( 1 === count( $clean_queries ) ) {
     2942            $clean_queries['relation'] = 'OR';
     2943
     2944        // Default to AND.
     2945        } else {
     2946            $clean_queries['relation'] = 'AND';
     2947        }
     2948
     2949        return $clean_queries;
     2950    }
     2951
     2952        /**
     2953         * Generate JOIN and WHERE clauses for a first-order clause.
     2954         *
     2955         * Must be overridden in a subclass.
     2956         *
     2957         * @since BuddyPress (2.2.0)
     2958         * @access protected
     2959         *
     2960         * @param  array $clause       Array of arguments belonging to the clause.
     2961         * @param  array $parent_query Parent query to which the clause belongs.
     2962         * @return array {
     2963         *     @type array $join  Array of subclauses for the JOIN statement.
     2964         *     @type array $where Array of subclauses for the WHERE statement.
     2965         * }
     2966         */
     2967        abstract protected function get_sql_for_clause( $clause, $parent_query );
     2968
     2969        /**
     2970         * Determine whether a clause is first-order.
     2971         *
     2972         * Must be overridden in a subclass.
     2973         *
     2974         * @since BuddyPress (2.2.0)
     2975         * @access protected
     2976         *
     2977         * @param  array $q Clause to check.
     2978         * @return bool
     2979         */
     2980        abstract protected function is_first_order_clause( $query );
     2981}
Note: See TracChangeset for help on using the changeset viewer.