'table name'. * * @var array */ private $tables; /** * Undocumented variable * * @var array */ private $column_mappings; /** * Meta query parser. * * @var \Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableMetaQuery|null */ private $meta_query; /** * COT_Cursor constructor. * * @param array $query_vars The query vars to use when building the SQL statement. * @param \Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableQuery $query The query to use when building the SQL statement. * @param string|null $cursor Whether to generate the before or after cursor. Default "after". */ public function __construct( $query_vars, $query, $cursor = 'after' ) { // Initialize the class properties. parent::__construct( $query_vars, $cursor ); $this->query = $query; // Get tables. /** @var \Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore $order_datastore */ $order_datastore = wc_get_container()->get( OrdersTableDataStore::class ); $this->tables = $order_datastore::get_all_table_names_with_id(); $mappings = $order_datastore->get_all_order_column_mappings(); $this->column_mappings = []; foreach ( $mappings['orders'] as $column => $meta_value ) { $this->column_mappings[ "{$this->tables['orders']}.{$column}" ] = $meta_value['name']; } if ( ! is_null( $this->get_query_var( 'meta_query' ) ) ) { $this->meta_query = new OrdersTableMetaQuery( $this->query ); } } /** * Resolves an orderby alias (e.g. 'date', 'total') to the fully qualified * COT column name used in the column_mappings. * * @param string $alias The orderby alias. * * @return string The resolved column name, or the original alias if no mapping found. */ public function resolve_orderby_alias( $alias ) { $alias_map = [ // Short aliases used by WC OrdersTableQuery. 'post_date' => 'date_created_gmt', 'date' => 'date_created_gmt', 'date_created' => 'date_created_gmt', 'modified' => 'date_updated_gmt', 'date_modified' => 'date_updated_gmt', 'type' => 'type', 'parent' => 'parent_order_id', 'total' => 'total_amount', 'order_total' => 'total_amount', // Legacy meta keys that map to COT columns in HPOS mode. '_order_total' => 'total_amount', '_order_tax' => 'tax_amount', '_cart_discount' => 'discount_total_amount', '_date_paid' => 'date_paid_gmt', '_date_completed' => 'date_completed_gmt', '_order_key' => 'order_key', ]; return $alias_map[ $alias ] ?? $alias; } /** * {@inheritDoc} * * @return ?\WC_Abstract_Order */ public function get_cursor_node() { // If we have a bad cursor, just skip. if ( ! $this->cursor_offset ) { return null; } // Get the order. $order = wc_get_order( $this->cursor_offset ); return $order instanceof \WC_Abstract_Order ? $order : null; } /** * {@inheritDoc} */ public function to_sql() { $orderby = isset( $this->query_vars['orderby'] ) ? $this->query_vars['orderby'] : null; $orderby_should_not_convert_to_sql = isset( $orderby ) && in_array( $orderby, [ 'include', 'id', 'parent_order_id', ], true ); if ( true === $orderby_should_not_convert_to_sql ) { return ''; } $sql = $this->builder->to_sql(); if ( empty( $sql ) ) { return ''; } return ' AND ' . $sql; } /** * {@inheritDoc} */ public function get_where() { // If we have a bad cursor, just skip. if ( ! $this->is_valid_offset_and_node() ) { return ''; } $orderby = $this->get_query_var( 'orderby' ); $order = $this->get_query_var( 'order' ); if ( ! empty( $orderby ) && is_array( $orderby ) ) { /** * Loop through all order keys if it is an array */ foreach ( $orderby as $by => $order ) { $this->compare_with( $by, $order ); } } elseif ( ! empty( $orderby ) && is_string( $orderby ) ) { /** * If $orderby is just a string just compare with it directly as DESC */ $this->compare_with( $orderby, $order ); } /** * No custom comparing. Use the default date */ if ( ! $this->builder->has_fields() ) { $this->compare_with_date(); } $this->builder->add_field( "{$this->tables['orders']}.id", $this->cursor_offset, 'ID', $order ); return $this->to_sql(); } /** * Get AND operator for given order by key * * @param string $by The order by key. * @param string $order The order direction ASC or DESC. * * @return void */ private function compare_with( $by, $order ) { if ( null === $this->cursor_node ) { return; } // Check for explicit cursor compare fields set by the connection resolver. $key = $this->get_query_var( "graphql_cursor_compare_by_{$by}_key" ); $value = $this->get_query_var( "graphql_cursor_compare_by_{$by}_value" ); if ( ! empty( $key ) && ! empty( $value ) ) { $this->builder->add_field( $key, $value, null, $order ); return; } // Resolve the orderby key to a COT column. For meta_value/meta_value_num, // use the meta_key query var since that holds the actual field name. $table_name = $this->tables['orders']; $source = in_array( $by, [ 'meta_value', 'meta_value_num' ], true ) ? ( $this->get_query_var( 'meta_key' ) ?? $by ) : $by; $column = $this->resolve_orderby_alias( $source ); $orderby = "{$table_name}.{$column}"; $getter_name = $this->column_mappings[ $orderby ] ?? null; if ( null !== $getter_name ) { $method = "get_{$getter_name}"; $value = is_callable( [ $this->cursor_node, $method ] ) ? $this->cursor_node->$method() : null; } else { // Fall back to meta query if the field doesn't map to a COT column. $meta_orderby_keys = $this->meta_query ? $this->meta_query->get_orderby_keys() : []; if ( in_array( $by, $meta_orderby_keys, true ) && null !== $this->meta_query ) { $orderby = $this->meta_query->get_orderby_clause_for_key( $by ); $value = $this->cursor_node->get_meta( $source, true ) ?? null; } else { return; } } if ( ! empty( $value ) && is_a( $value, '\WC_DateTime' ) ) { $value = ( new \DateTime( $value ) )->setTimezone( new \DateTimeZone( '+00:00' ) )->format( 'Y-m-d H:i:s' ); $this->builder->add_field( $orderby, $value, 'DATETIME', $order ); return; } /** * Compare by the post field if the key matches a value */ if ( ! empty( $value ) ) { $this->builder->add_field( $orderby, $value, null, $order ); } } /** * Use post date based comparison * * @return void */ private function compare_with_date() { $value = null; if ( null !== $this->cursor_node ) { $date_created = $this->cursor_node->get_date_created(); $value = ! empty( $date_created ) ? ( new \DateTime( $date_created ) )->setTimezone( new \DateTimeZone( '+00:00' ) )->format( 'Y-m-d H:i:s' ) : null; } $this->builder->add_field( "{$this->tables['orders']}.date_created_gmt", $value, 'DATETIME' ); } }