File: /var/www/html/wp-content/plugins/wp-graphql/src/Data/Cursor/CursorBuilder.php
<?php
namespace WPGraphQL\Data\Cursor;
/**
* Generic class for building AND&OR operators for cursor based paginators
*/
class CursorBuilder {
/**
* The field by which the cursor should order the results
*
* @var array<string,mixed>[]
*/
public $fields;
/**
* Default comparison operator. < or >
*
* @var string
*/
public $compare;
/**
* CursorBuilder constructor.
*
* @param string $compare
*
* @return void
*/
public function __construct( $compare = '>' ) {
$this->compare = $compare;
$this->fields = [];
}
/**
* Add ordering field. The order you call this method matters. First field
* will be the primary field and latter ones will be used if the primary
* field has duplicate values
*
* @param string $key database column
* @param mixed|string|int $value value from the current cursor
* @param string|null $type type cast
* @param string|null $order custom order
* @param object|null $object_cursor The Cursor class
*
* @return void
*/
public function add_field( string $key, $value, ?string $type = null, ?string $order = null, $object_cursor = null ) {
/**
* Filters the field used for ordering when cursors are used for pagination
*
* @param array<string,mixed> $field The field key, value, type and order
* @param \WPGraphQL\Data\Cursor\CursorBuilder $cursor_builder The CursorBuilder class
* @param ?object $object_cursor The Cursor class
*/
$field = apply_filters(
'graphql_cursor_ordering_field',
[
'key' => esc_sql( $key ),
'value' => esc_sql( $value ),
'type' => ! empty( $type ) ? esc_sql( $type ) : '',
'order' => ! empty( $order ) ? esc_sql( $order ) : '',
],
$this,
$object_cursor
);
// Bail if the filtered field comes back empty
if ( empty( $field ) ) {
return;
}
// Bail if the filtered field doesn't come back as an array
if ( ! is_array( $field ) ) {
return;
}
$escaped_field = [];
// Escape the filtered array
foreach ( $field as $field_key => $value ) {
$escaped_field[ $field_key ] = esc_sql( $value );
}
$this->fields[] = $escaped_field;
}
/**
* Returns true at least one ordering field has been added
*
* @return bool
*/
public function has_fields() {
return count( $this->fields ) > 0;
}
/**
* Generate the final SQL string to be appended to WHERE clause
*
* @param mixed|array<string,mixed>[]|null $fields
*
* @return string
*/
public function to_sql( $fields = null ) {
if ( null === $fields ) {
$fields = $this->fields;
}
if ( count( $fields ) === 0 ) {
return '';
}
$field = $fields[0];
$key = $field['key'];
$value = $field['value'];
$type = $field['type'];
$order = $field['order'];
$compare = $this->compare;
if ( $order ) {
$compare = 'DESC' === $order ? '<' : '>';
}
if ( 'ID' !== $type ) {
$cast = $this->get_cast_for_type( $type );
if ( 'CHAR' === $cast ) {
$value = '"' . wp_unslash( $value ) . '"';
} elseif ( $cast ) {
$key = "CAST( $key as $cast )";
$value = "CAST( '$value' as $cast )";
}
}
if ( count( $fields ) === 1 ) {
return " {$key} {$compare} {$value} ";
}
$nest = $this->to_sql( \array_slice( $fields, 1 ) );
$sql = ' %1$s %2$s= %3$s AND ( %1$s %2$s %3$s OR ( %4$s ) ) ';
return sprintf( $sql, $key, $compare, $value, $nest );
}
/**
* Copied from
* https://github.com/WordPress/WordPress/blob/c4f8bc468db56baa2a3bf917c99cdfd17c3391ce/wp-includes/class-wp-meta-query.php#L272-L296
*
* It's an instance method. No way to call it without creating the instance?
*
* Return the appropriate alias for the given meta type if applicable.
*
* @param string $type MySQL type to cast meta_value.
*
* @return string MySQL type.
*/
public function get_cast_for_type( $type = '' ) {
if ( empty( $type ) ) {
return 'CHAR';
}
$meta_type = strtoupper( $type );
if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) {
return 'CHAR';
}
if ( 'NUMERIC' === $meta_type ) {
$meta_type = 'SIGNED';
}
return $meta_type;
}
}