PluginProbe
WPGraphQL / trunk
WPGraphQL vtrunk
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / src / Data / Cursor / CursorBuilder.php

CursorBuilder.php in WPGraphQL trunk, at src/Data/Cursor/CursorBuilder.php

179 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPGraphQL\Data\Cursor;
4
5 /**
6 * Generic class for building AND&OR operators for cursor based paginators
7 */
8 class CursorBuilder {
9
10 /**
11 * The field by which the cursor should order the results
12 *
13 * @var array<string,mixed>[]
14 */
15 public $fields;
16
17 /**
18 * Default comparison operator. < or >
19 *
20 * @var string
21 */
22 public $compare;
23
24 /**
25 * CursorBuilder constructor.
26 *
27 * @param string $compare
28 *
29 * @return void
30 */
31 public function __construct( $compare = '>' ) {
32 $this->compare = $compare;
33 $this->fields = [];
34 }
35
36 /**
37 * Add ordering field. The order you call this method matters. First field
38 * will be the primary field and latter ones will be used if the primary
39 * field has duplicate values
40 *
41 * @param string $key database column
42 * @param mixed|string|int $value value from the current cursor
43 * @param string|null $type type cast
44 * @param string|null $order custom order
45 * @param object|null $object_cursor The Cursor class
46 *
47 * @return void
48 */
49 public function add_field( string $key, $value, ?string $type = null, ?string $order = null, $object_cursor = null ) {
50
51 /**
52 * Filters the field used for ordering when cursors are used for pagination
53 *
54 * @param array<string,mixed> $field The field key, value, type and order
55 * @param \WPGraphQL\Data\Cursor\CursorBuilder $cursor_builder The CursorBuilder class
56 * @param ?object $object_cursor The Cursor class
57 *
58 * @hookGroup connections
59 * @since 0.0.5
60 */
61 $field = apply_filters(
62 'graphql_cursor_ordering_field',
63 [
64 'key' => esc_sql( $key ),
65 'value' => esc_sql( $value ),
66 'type' => ! empty( $type ) ? esc_sql( $type ) : '',
67 'order' => ! empty( $order ) ? esc_sql( $order ) : '',
68 ],
69 $this,
70 $object_cursor
71 );
72
73 // Bail if the filtered field comes back empty
74 if ( empty( $field ) ) {
75 return;
76 }
77
78 // Bail if the filtered field doesn't come back as an array
79 if ( ! is_array( $field ) ) {
80 return;
81 }
82
83 $escaped_field = [];
84
85 // Escape the filtered array
86 foreach ( $field as $field_key => $value ) {
87 $escaped_field[ $field_key ] = esc_sql( $value );
88 }
89
90 $this->fields[] = $escaped_field;
91 }
92
93 /**
94 * Returns true at least one ordering field has been added
95 *
96 * @return bool
97 */
98 public function has_fields() {
99 return count( $this->fields ) > 0;
100 }
101
102 /**
103 * Generate the final SQL string to be appended to WHERE clause
104 *
105 * @param mixed|array<string,mixed>[]|null $fields
106 *
107 * @return string
108 */
109 public function to_sql( $fields = null ) {
110 if ( null === $fields ) {
111 $fields = $this->fields;
112 }
113
114 if ( count( $fields ) === 0 ) {
115 return '';
116 }
117
118 $field = $fields[0];
119
120 $key = $field['key'];
121 $value = $field['value'];
122 $type = $field['type'];
123 $order = $field['order'];
124
125 $compare = $this->compare;
126
127 if ( $order ) {
128 $compare = 'DESC' === $order ? '<' : '>';
129 }
130
131 if ( 'ID' !== $type ) {
132 $cast = $this->get_cast_for_type( $type );
133 if ( 'CHAR' === $cast ) {
134 $value = '"' . wp_unslash( $value ) . '"';
135 } elseif ( $cast ) {
136 $key = "CAST( $key as $cast )";
137 $value = "CAST( '$value' as $cast )";
138 }
139 }
140
141 if ( count( $fields ) === 1 ) {
142 return " {$key} {$compare} {$value} ";
143 }
144
145 $nest = $this->to_sql( \array_slice( $fields, 1 ) );
146
147 $sql = ' %1$s %2$s= %3$s AND ( %1$s %2$s %3$s OR ( %4$s ) ) ';
148
149 return sprintf( $sql, $key, $compare, $value, $nest );
150 }
151
152 /**
153 * Copied from
154 * https://github.com/WordPress/WordPress/blob/c4f8bc468db56baa2a3bf917c99cdfd17c3391ce/wp-includes/class-wp-meta-query.php#L272-L296
155 *
156 * It's an instance method. No way to call it without creating the instance?
157 *
158 * Return the appropriate alias for the given meta type if applicable.
159 *
160 * @param string $type MySQL type to cast meta_value.
161 *
162 * @return string MySQL type.
163 */
164 public function get_cast_for_type( $type = '' ) {
165 if ( empty( $type ) ) {
166 return 'CHAR';
167 }
168 $meta_type = strtoupper( $type );
169 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) {
170 return 'CHAR';
171 }
172 if ( 'NUMERIC' === $meta_type ) {
173 $meta_type = 'SIGNED';
174 }
175
176 return $meta_type;
177 }
178 }
179