BP_Recursive_Query

Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params.

Defined (1)

The class is defined in the following location(s).

/bp-core/classes/class-bp-recursive-query.php  
  1. abstract class BP_Recursive_Query { 
  2.  
  3. /** 
  4. * Query arguments passed to the constructor. 
  5. * @since 2.2.0 
  6. * @var array 
  7. */ 
  8. public $queries = array(); 
  9.  
  10. /** 
  11. * Generate SQL clauses to be appended to a main query. 
  12. * Extending classes should call this method from within a publicly 
  13. * accessible get_sql() method, and manipulate the SQL as necessary. 
  14. * For example, {@link BP_XProfile_Query::get_sql()} is merely a wrapper for 
  15. * get_sql_clauses(), while {@link BP_Activity_Query::get_sql()} discards 
  16. * the empty 'join' clause, and only passes the 'where' clause. 
  17. * @since 2.2.0 
  18. * @return array 
  19. */ 
  20. protected function get_sql_clauses() { 
  21. $sql = $this->get_sql_for_query( $this->queries ); 
  22.  
  23. if ( ! empty( $sql['where'] ) ) { 
  24. $sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n"; 
  25.  
  26. return $sql; 
  27.  
  28. /** 
  29. * Generate SQL clauses for a single query array. 
  30. * If nested subqueries are found, this method recurses the tree to 
  31. * produce the properly nested SQL. 
  32. * Subclasses generally do not need to call this method. It is invoked 
  33. * automatically from get_sql_clauses(). 
  34. * @since 2.2.0 
  35. * @param array $query Query to parse. 
  36. * @param int $depth Optional. Number of tree levels deep we 
  37. * currently are. Used to calculate indentation. 
  38. * @return array 
  39. */ 
  40. protected function get_sql_for_query( $query, $depth = 0 ) { 
  41. $sql_chunks = array( 
  42. 'join' => array(),  
  43. 'where' => array(),  
  44. ); 
  45.  
  46. $sql = array( 
  47. 'join' => '',  
  48. 'where' => '',  
  49. ); 
  50.  
  51. $indent = ''; 
  52. for ( $i = 0; $i < $depth; $i++ ) { 
  53. $indent .= "\t"; 
  54.  
  55. foreach ( $query as $key => $clause ) { 
  56. if ( 'relation' === $key ) { 
  57. $relation = $query['relation']; 
  58. } elseif ( is_array( $clause ) ) { 
  59. // This is a first-order clause 
  60. if ( $this->is_first_order_clause( $clause ) ) { 
  61. $clause_sql = $this->get_sql_for_clause( $clause, $query ); 
  62.  
  63. $where_count = count( $clause_sql['where'] ); 
  64. if ( ! $where_count ) { 
  65. $sql_chunks['where'][] = ''; 
  66. } elseif ( 1 === $where_count ) { 
  67. $sql_chunks['where'][] = $clause_sql['where'][0]; 
  68. } else { 
  69. $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 
  70.  
  71. $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 
  72. // This is a subquery 
  73. } else { 
  74. $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 
  75.  
  76. $sql_chunks['where'][] = $clause_sql['where']; 
  77. $sql_chunks['join'][] = $clause_sql['join']; 
  78.  
  79. // Filter empties 
  80. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 
  81. $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 
  82.  
  83. if ( empty( $relation ) ) { 
  84. $relation = 'AND'; 
  85.  
  86. if ( ! empty( $sql_chunks['join'] ) ) { 
  87. $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 
  88.  
  89. if ( ! empty( $sql_chunks['where'] ) ) { 
  90. $sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n"; 
  91.  
  92. return $sql; 
  93.  
  94. /** 
  95. * Recursive-friendly query sanitizer. 
  96. * Ensures that each query-level clause has a 'relation' key, and that 
  97. * each first-order clause contains all the necessary keys from 
  98. * $defaults. 
  99. * Extend this method if your class uses different sanitizing logic. 
  100. * @since 2.2.0 
  101. * @param array $queries Array of query clauses. 
  102. * @return array Sanitized array of query clauses. 
  103. */ 
  104. protected function sanitize_query( $queries ) { 
  105. $clean_queries = array(); 
  106.  
  107. if ( ! is_array( $queries ) ) { 
  108. return $clean_queries; 
  109.  
  110. foreach ( $queries as $key => $query ) { 
  111. if ( 'relation' === $key ) { 
  112. $relation = $query; 
  113.  
  114. } elseif ( ! is_array( $query ) ) { 
  115. continue; 
  116.  
  117. // First-order clause. 
  118. } elseif ( $this->is_first_order_clause( $query ) ) { 
  119. if ( isset( $query['value'] ) && array() === $query['value'] ) { 
  120. unset( $query['value'] ); 
  121.  
  122. $clean_queries[] = $query; 
  123.  
  124. // Otherwise, it's a nested query, so we recurse. 
  125. } else { 
  126. $cleaned_query = $this->sanitize_query( $query ); 
  127.  
  128. if ( ! empty( $cleaned_query ) ) { 
  129. $clean_queries[] = $cleaned_query; 
  130.  
  131. if ( empty( $clean_queries ) ) { 
  132. return $clean_queries; 
  133.  
  134. // Sanitize the 'relation' key provided in the query. 
  135. if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { 
  136. $clean_queries['relation'] = 'OR'; 
  137.  
  138. /** 
  139. * If there is only a single clause, call the relation 'OR'. 
  140. * This value will not actually be used to join clauses, but it 
  141. * simplifies the logic around combining key-only queries. 
  142. */ 
  143. } elseif ( 1 === count( $clean_queries ) ) { 
  144. $clean_queries['relation'] = 'OR'; 
  145.  
  146. // Default to AND. 
  147. } else { 
  148. $clean_queries['relation'] = 'AND'; 
  149.  
  150. return $clean_queries; 
  151.  
  152. /** 
  153. * Generate JOIN and WHERE clauses for a first-order clause. 
  154. * Must be overridden in a subclass. 
  155. * @since 2.2.0 
  156. * @param array $clause Array of arguments belonging to the clause. 
  157. * @param array $parent_query Parent query to which the clause belongs. 
  158. * @return array { 
  159. * @type array $join Array of subclauses for the JOIN statement. 
  160. * @type array $where Array of subclauses for the WHERE statement. 
  161. * } 
  162. */ 
  163. abstract protected function get_sql_for_clause( $clause, $parent_query ); 
  164.  
  165. /** 
  166. * Determine whether a clause is first-order. 
  167. * Must be overridden in a subclass. 
  168. * @since 2.2.0 
  169. * @param array $query Clause to check. 
  170. * @return bool 
  171. */ 
  172. abstract protected function is_first_order_clause( $query );