/src/Data/Config.php

https://github.com/wp-graphql/wp-graphql · PHP · 335 lines · 141 code · 58 blank · 136 comment · 20 complexity · 02437ac869c0f2acf681b8be90e40a27 MD5 · raw file

  1. <?php
  2. namespace WPGraphQL\Data;
  3. use WPGraphQL\Data\Cursor\PostObjectCursor;
  4. use WPGraphQL\Data\Cursor\UserCursor;
  5. /**
  6. * Class Config
  7. *
  8. * This class contains configurations for various data-related things, such as query filters for
  9. * cursor pagination.
  10. *
  11. * @package WPGraphQL\Data
  12. */
  13. class Config {
  14. /**
  15. * Config constructor.
  16. */
  17. public function __construct() {
  18. /**
  19. * Filter the term_clauses in the WP_Term_Query to allow for cursor pagination support where a Term ID
  20. * can be used as a point of comparison when slicing the results to return.
  21. */
  22. add_filter(
  23. 'comments_clauses',
  24. [
  25. $this,
  26. 'graphql_wp_comments_query_cursor_pagination_support',
  27. ],
  28. 10,
  29. 2
  30. );
  31. /**
  32. * Filter the WP_Query to support cursor based pagination where a post ID can be used
  33. * as a point of comparison when slicing the results to return.
  34. */
  35. add_filter( 'posts_where', [ $this, 'graphql_wp_query_cursor_pagination_support' ], 10, 2 );
  36. /**
  37. * Filter the term_clauses in the WP_Term_Query to allow for cursor pagination support where a Term ID
  38. * can be used as a point of comparison when slicing the results to return.
  39. */
  40. add_filter(
  41. 'terms_clauses',
  42. [
  43. $this,
  44. 'graphql_wp_term_query_cursor_pagination_support',
  45. ],
  46. 10,
  47. 3
  48. );
  49. /**
  50. * Filter WP_Query order by add some stability to meta query ordering
  51. */
  52. add_filter(
  53. 'posts_orderby',
  54. [
  55. $this,
  56. 'graphql_wp_query_cursor_pagination_stability',
  57. ],
  58. 10,
  59. 2
  60. );
  61. if ( ! defined( 'ABSPATH' ) ) {
  62. exit;
  63. }
  64. /**
  65. * Copied from https://github.com/wp-graphql/wp-graphql/issues/274#issuecomment-510150571
  66. * Shoutouts to epeli!
  67. *
  68. * Add missing filters to WP_User_Query class.
  69. */
  70. add_filter(
  71. 'pre_user_query',
  72. function ( $query ) {
  73. if ( ! $query->get( 'suppress_filters' ) ) {
  74. $query->set( 'suppress_filters', 0 );
  75. }
  76. if ( ! $query->get( 'suppress_filters' ) ) {
  77. /**
  78. * Filters the WHERE clause of the query.
  79. *
  80. * Specifically for manipulating paging queries.
  81. **
  82. *
  83. * @param string $where The WHERE clause of the query.
  84. * @param WP_User_Query $query The WP_User_Query instance (passed by reference).
  85. */
  86. $query->query_where = apply_filters_ref_array( 'graphql_users_where', [ $query->query_where, &$query ] );
  87. /**
  88. * Filters the ORDER BY clause of the query.
  89. *
  90. * @param string $orderby The ORDER BY clause of the query.
  91. * @param WP_User_Query $query The WP_User_Query instance (passed by reference).
  92. */
  93. $query->query_orderby = apply_filters_ref_array( 'graphql_users_orderby', [ $query->query_orderby, &$query ] );
  94. }
  95. return $query;
  96. }
  97. );
  98. /**
  99. * Filter the WP_User_Query to support cursor based pagination where a user ID can be used
  100. * as a point of comparison when slicing the results to return.
  101. */
  102. add_filter(
  103. 'graphql_users_where',
  104. [
  105. $this,
  106. 'graphql_wp_user_query_cursor_pagination_support',
  107. ],
  108. 10,
  109. 2
  110. );
  111. /**
  112. * Filter WP_User_Query order by add some stability to meta query ordering
  113. */
  114. add_filter(
  115. 'graphql_users_orderby',
  116. [
  117. $this,
  118. 'graphql_wp_user_query_cursor_pagination_stability',
  119. ],
  120. 10,
  121. 2
  122. );
  123. }
  124. /**
  125. * When posts are ordered by a meta query the order might be random when
  126. * the meta values have same values multiple times. This filter adds a
  127. * secondary ordering by the post ID which forces stable order in such cases.
  128. *
  129. * @param string $orderby The ORDER BY clause of the query.
  130. *
  131. * @return string
  132. */
  133. public function graphql_wp_query_cursor_pagination_stability( $orderby ) {
  134. if ( true === is_graphql_request() ) {
  135. global $wpdb;
  136. return "{$orderby}, {$wpdb->posts}.ID DESC ";
  137. }
  138. return $orderby;
  139. }
  140. /**
  141. * This filters the WPQuery 'where' $args, enforcing the query to return results before or
  142. * after the referenced cursor
  143. *
  144. * @param string $where The WHERE clause of the query.
  145. * @param \WP_Query $query The WP_Query instance (passed by reference).
  146. *
  147. * @return string
  148. */
  149. public function graphql_wp_query_cursor_pagination_support( $where, \WP_Query $query ) {
  150. /**
  151. * If there's a graphql_cursor_offset in the query, we should check to see if
  152. * it should be applied to the query
  153. */
  154. if ( true === is_graphql_request() ) {
  155. $post_cursor = new PostObjectCursor( $query );
  156. return $where . $post_cursor->get_where();
  157. }
  158. return $where;
  159. }
  160. /**
  161. * When users are ordered by a meta query the order might be random when
  162. * the meta values have same values multiple times. This filter adds a
  163. * secondary ordering by the post ID which forces stable order in such cases.
  164. *
  165. * @param string $orderby The ORDER BY clause of the query.
  166. *
  167. * @return string
  168. */
  169. public function graphql_wp_user_query_cursor_pagination_stability( $orderby ) {
  170. if ( true === is_graphql_request() ) {
  171. global $wpdb;
  172. return "{$orderby}, {$wpdb->users}.ID DESC ";
  173. }
  174. return $orderby;
  175. }
  176. /**
  177. * This filters the WP_User_Query 'where' $args, enforcing the query to return results before or
  178. * after the referenced cursor
  179. *
  180. * @param string $where The WHERE clause of the query.
  181. * @param \WP_User_Query $query The WP_User_Query instance (passed by reference).
  182. *
  183. * @return string
  184. */
  185. public function graphql_wp_user_query_cursor_pagination_support( $where, \WP_User_Query $query ) {
  186. /**
  187. * If there's a graphql_cursor_offset in the query, we should check to see if
  188. * it should be applied to the query
  189. */
  190. if ( true === is_graphql_request() ) {
  191. $user_cursor = new UserCursor( $query );
  192. return $where . $user_cursor->get_where();
  193. }
  194. return $where;
  195. }
  196. /**
  197. * This filters the term_clauses in the WP_Term_Query to support cursor based pagination, where
  198. * we can move forward or backward from a particular record, instead of typical offset
  199. * pagination which can be much more expensive and less accurate.
  200. *
  201. * @param array $pieces Terms query SQL clauses.
  202. * @param array $taxonomies An array of taxonomies.
  203. * @param array $args An array of terms query arguments.
  204. *
  205. * @return array $pieces
  206. */
  207. public function graphql_wp_term_query_cursor_pagination_support( array $pieces, $taxonomies, $args ) {
  208. /**
  209. * Access the global $wpdb object
  210. */
  211. global $wpdb;
  212. if ( true === is_graphql_request() && ! empty( $args['graphql_cursor_offset'] ) ) {
  213. $cursor_offset = $args['graphql_cursor_offset'];
  214. /**
  215. * Ensure the cursor_offset is a positive integer
  216. */
  217. if ( is_integer( $cursor_offset ) && 0 < $cursor_offset ) {
  218. $compare = ! empty( $args['graphql_cursor_compare'] ) ? $args['graphql_cursor_compare'] : '>';
  219. $compare = in_array( $compare, [ '>', '<' ], true ) ? $compare : '>';
  220. $order_by = ! empty( $args['orderby'] ) ? $args['orderby'] : 'comment_date';
  221. $order = ! empty( $args['order'] ) ? $args['order'] : 'DESC';
  222. $order_compare = ( 'ASC' === $order ) ? '>' : '<';
  223. // Get the $cursor_post
  224. $cursor_term = get_term( $cursor_offset );
  225. if ( ! empty( $cursor_term ) && ! empty( $cursor_term->name ) ) {
  226. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  227. $pieces['where'] .= $wpdb->prepare( " AND t.{$order_by} {$order_compare} %s", $cursor_term->{$order_by} );
  228. } else {
  229. // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
  230. $pieces['where'] .= $wpdb->prepare( ' AND t.term_id %1$s %2$d', $compare, $cursor_offset );
  231. }
  232. }
  233. }
  234. return $pieces;
  235. }
  236. /**
  237. * This returns a modified version of the $pieces of the comment query clauses if the request
  238. * is a GraphQL Request and the query has a graphql_cursor_offset defined
  239. *
  240. * @param array $pieces A compacted array of comment query clauses.
  241. * @param \WP_Comment_Query $query Current instance of WP_Comment_Query, passed by reference.
  242. *
  243. * @return array $pieces
  244. */
  245. public function graphql_wp_comments_query_cursor_pagination_support( array $pieces, \WP_Comment_Query $query ) {
  246. /**
  247. * Access the global $wpdb object
  248. */
  249. global $wpdb;
  250. if (
  251. true === is_graphql_request() &&
  252. ( is_array( $query->query_vars ) && array_key_exists( 'graphql_cursor_offset', $query->query_vars ) )
  253. ) {
  254. $cursor_offset = $query->query_vars['graphql_cursor_offset'];
  255. /**
  256. * Ensure the cursor_offset is a positive integer
  257. */
  258. if ( is_integer( $cursor_offset ) && 0 < $cursor_offset ) {
  259. $compare = ! empty( $query->get( 'graphql_cursor_compare' ) ) ? $query->get( 'graphql_cursor_compare' ) : '>';
  260. $compare = in_array( $compare, [ '>', '<' ], true ) ? $compare : '>';
  261. $order_by = ! empty( $query->query_vars['order_by'] ) ? $query->query_vars['order_by'] : 'comment_date';
  262. $order = ! empty( $query->query_vars['order'] ) ? $query->query_vars['order'] : 'DESC';
  263. $order_compare = ( 'ASC' === $order ) ? '>' : '<';
  264. // Get the $cursor_post
  265. $cursor_comment = get_comment( $cursor_offset );
  266. if ( ! empty( $cursor_comment ) ) {
  267. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
  268. $pieces['where'] .= $wpdb->prepare( " AND {$order_by} {$order_compare} %s", $cursor_comment->{$order_by} );
  269. } else {
  270. // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
  271. $pieces['where'] .= $wpdb->prepare( ' AND comment_ID %1$s %2$d', $compare, $cursor_offset );
  272. }
  273. }
  274. }
  275. return $pieces;
  276. }
  277. }