' : " {$match_operator} "; $this->subquery->add_sql_clause( 'having', $preceding_match . implode( " {$match_operator} ", $having_clauses ) ); } } /** * Returns the report data based on parameters supplied by the user. * * @param array $query_args Query parameters. * @return stdClass|WP_Error Data. */ public function get_data( $query_args ) { global $wpdb; $customers_table_name = self::get_db_table_name(); $order_stats_table_name = $wpdb->prefix . 'wc_order_stats'; // These defaults are only partially applied when used via REST API, as that has its own defaults. $defaults = array( 'per_page' => get_option( 'posts_per_page' ), 'page' => 1, 'order' => 'DESC', 'orderby' => 'date_registered', 'order_before' => TimeInterval::default_before(), 'order_after' => TimeInterval::default_after(), 'fields' => '*', ); $query_args = wp_parse_args( $query_args, $defaults ); $this->normalize_timezones( $query_args, $defaults ); /* * We need to get the cache key here because * parent::update_intervals_sql_params() modifies $query_args. */ $cache_key = $this->get_cache_key( $query_args ); $data = $this->get_cached_data( $cache_key ); if ( false === $data ) { $this->initialize_queries(); $data = (object) array( 'data' => array(), 'total' => 0, 'pages' => 0, 'page_no' => 0, ); $selections = $this->selected_columns( $query_args ); $sql_query_params = $this->add_sql_query_params( $query_args ); $count_query = "SELECT COUNT(*) FROM ( {$this->subquery->get_query_statement()} ) as tt "; $db_records_count = (int) $wpdb->get_var( $count_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared ); $params = $this->get_limit_params( $query_args ); $total_pages = (int) ceil( $db_records_count / $params['per_page'] ); if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { return $data; } $this->subquery->clear_sql_clause( 'select' ); $this->subquery->add_sql_clause( 'select', $selections ); $this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) ); $this->subquery->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) ); $customer_data = $wpdb->get_results( $this->subquery->get_query_statement(), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared ARRAY_A ); if ( null === $customer_data ) { return $data; } $customer_data = array_map( array( $this, 'cast_numbers' ), $customer_data ); $data = (object) array( 'data' => $customer_data, 'total' => $db_records_count, 'pages' => $total_pages, 'page_no' => (int) $query_args['page'], ); $this->set_cached_data( $cache_key, $data ); } return $data; } /** * Returns an existing customer ID for an order if one exists. * * @param object $order WC Order. * @return int|bool */ public static function get_existing_customer_id_from_order( $order ) { global $wpdb; if ( ! is_a( $order, 'WC_Order' ) ) { return false; } $user_id = $order->get_customer_id(); if ( 0 === $user_id ) { $customer_id = $wpdb->get_var( $wpdb->prepare( "SELECT customer_id FROM {$wpdb->prefix}wc_order_stats WHERE order_id = %d", $order->get_id() ) ); if ( $customer_id ) { return $customer_id; } $email = $order->get_billing_email( 'edit' ); if ( $email ) { return self::get_guest_id_by_email( $email ); } else { return false; } } else { return self::get_customer_id_by_user_id( $user_id ); } } /** * Get or create a customer from a given order. * * @param object $order WC Order. * @return int|bool */ public static function get_or_create_customer_from_order( $order ) { if ( ! $order ) { return false; } global $wpdb; if ( ! is_a( $order, 'WC_Order' ) ) { return false; } $returning_customer_id = self::get_existing_customer_id_from_order( $order ); if ( $returning_customer_id ) { return $returning_customer_id; } list($data, $format) = self::get_customer_order_data_and_format( $order ); $result = $wpdb->insert( self::get_db_table_name(), $data, $format ); $customer_id = $wpdb->insert_id; /** * Fires when a new report customer is created. * * @param int $customer_id Customer ID. */ do_action( 'woocommerce_analytics_new_customer', $customer_id ); return $result ? $customer_id : false; } /** * Returns a data object and format object of the customers data coming from the order. * * @param object $order WC_Order where we get customer info from. * @param object|null $customer_user WC_Customer registered customer WP user. * @return array ($data, $format) */ public static function get_customer_order_data_and_format( $order, $customer_user = null ) { $data = array( 'first_name' => $order->get_customer_first_name(), 'last_name' => $order->get_customer_last_name(), 'email' => $order->get_billing_email( 'edit' ), 'city' => $order->get_billing_city( 'edit' ), 'state' => $order->get_billing_state( 'edit' ), 'postcode' => $order->get_billing_postcode( 'edit' ), 'country' => $order->get_billing_country( 'edit' ), 'date_last_active' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), ); $format = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', ); // Add registered customer data. if ( 0 !== $order->get_user_id() ) { $user_id = $order->get_user_id(); if ( is_null( $customer_user ) ) { $customer_user = new \WC_Customer( $user_id ); } $data['user_id'] = $user_id; $data['username'] = $customer_user->get_username( 'edit' ); $data['date_registered'] = $customer_user->get_date_created( 'edit' ) ? $customer_user->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ) : null; $format[] = '%d'; $format[] = '%s'; $format[] = '%s'; } return array( $data, $format ); } /** * Retrieve a guest ID (when user_id is null) by email. * * @param string $email Email address. * @return false|array Customer array if found, boolean false if not. */ public static function get_guest_id_by_email( $email ) { global $wpdb; $table_name = self::get_db_table_name(); $customer_id = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT customer_id FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1", $email ) ); return $customer_id ? (int) $customer_id : false; } /** * Retrieve a registered customer row id by user_id. * * @param string|int $user_id User ID. * @return false|int Customer ID if found, boolean false if not. */ public static function get_customer_id_by_user_id( $user_id ) { global $wpdb; $table_name = self::get_db_table_name(); $customer_id = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT customer_id FROM {$table_name} WHERE user_id = %d LIMIT 1", $user_id ) ); return $customer_id ? (int) $customer_id : false; } /** * Retrieve the last order made by a customer. * * @param int $customer_id Customer ID. * @return object WC_Order|false. */ public static function get_last_order( $customer_id ) { global $wpdb; $orders_table = $wpdb->prefix . 'wc_order_stats'; $last_order = $wpdb->get_var( $wpdb->prepare( // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT order_id, date_created_gmt FROM {$orders_table} WHERE customer_id = %d ORDER BY date_created_gmt DESC, order_id DESC LIMIT 1", // phpcs:enable $customer_id ) ); if ( ! $last_order ) { return false; } return wc_get_order( absint( $last_order ) ); } /** * Retrieve the oldest orders made by a customer. * * @param int $customer_id Customer ID. * @return array Orders. */ public static function get_oldest_orders( $customer_id ) { global $wpdb; $orders_table = $wpdb->prefix . 'wc_order_stats'; $excluded_statuses = array_map( array( __CLASS__, 'normalize_order_status' ), self::get_excluded_report_order_statuses() ); $excluded_statuses_condition = ''; if ( ! empty( $excluded_statuses ) ) { $excluded_statuses_str = implode( "','", $excluded_statuses ); $excluded_statuses_condition = "AND status NOT IN ('{$excluded_statuses_str}')"; } return $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT order_id, date_created FROM {$orders_table} WHERE customer_id = %d {$excluded_statuses_condition} ORDER BY date_created, order_id ASC LIMIT 2", $customer_id ) ); } /** * Retrieve the amount of orders made by a customer. * * @param int $customer_id Customer ID. * @return int|null Amount of orders for customer or null on failure. */ public static function get_order_count( $customer_id ) { global $wpdb; $customer_id = absint( $customer_id ); if ( 0 === $customer_id ) { return null; } $result = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( order_id ) FROM {$wpdb->prefix}wc_order_stats WHERE customer_id = %d", $customer_id ) ); if ( is_null( $result ) ) { return null; } return (int) $result; } /** * Update the database with customer data. * * @param int $user_id WP User ID to update customer data for. * @return int|bool|null Number or rows modified or false on failure. */ public static function update_registered_customer( $user_id ) { global $wpdb; $customer = new \WC_Customer( $user_id ); if ( ! self::is_valid_customer( $user_id ) ) { return false; } $last_order = $customer->get_last_order(); if ( ! $last_order ) { $first_name = get_user_meta( $user_id, 'first_name', true ); $last_name = get_user_meta( $user_id, 'last_name', true ); } else { $first_name = $last_order->get_customer_first_name(); $last_name = $last_order->get_customer_last_name(); } $last_active = $customer->get_meta( 'wc_last_active', true, 'edit' ); $data = array( 'user_id' => $user_id, 'username' => $customer->get_username( 'edit' ), 'first_name' => $first_name, 'last_name' => $last_name, 'email' => $customer->get_email( 'edit' ), 'city' => $customer->get_billing_city( 'edit' ), 'state' => $customer->get_billing_state( 'edit' ), 'postcode' => $customer->get_billing_postcode( 'edit' ), 'country' => $customer->get_billing_country( 'edit' ), 'date_registered' => $customer->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ), 'date_last_active' => $last_active ? gmdate( 'Y-m-d H:i:s', $last_active ) : null, ); $format = array( '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', ); $customer_id = self::get_customer_id_by_user_id( $user_id ); if ( $customer_id ) { // Preserve customer_id for existing user_id. $data['customer_id'] = $customer_id; $format[] = '%d'; } $results = $wpdb->replace( self::get_db_table_name(), $data, $format ); /** * Fires when customser's reports are updated. * * @param int $customer_id Customer ID. */ do_action( 'woocommerce_analytics_update_customer', $customer_id ); ReportsCache::invalidate(); return $results; } /** * Check if a user ID is a valid customer or other user role with past orders. * * @param int $user_id User ID. * @return bool */ protected static function is_valid_customer( $user_id ) { $customer = new \WC_Customer( $user_id ); if ( absint( $customer->get_id() ) !== absint( $user_id ) ) { return false; } $customer_roles = (array) apply_filters( 'woocommerce_analytics_customer_roles', array( 'customer' ) ); if ( $customer->get_order_count() < 1 && ! in_array( $customer->get_role(), $customer_roles, true ) ) { return false; } return true; } /** * Delete a customer lookup row. * * @param int $customer_id Customer ID. */ public static function delete_customer( $customer_id ) { global $wpdb; $customer_id = (int) $customer_id; $num_deleted = $wpdb->delete( self::get_db_table_name(), array( 'customer_id' => $customer_id ) ); if ( $num_deleted ) { /** * Fires when a customer is deleted. * * @param int $order_id Order ID. */ do_action( 'woocommerce_analytics_delete_customer', $customer_id ); ReportsCache::invalidate(); } } /** * Delete a customer lookup row by WordPress User ID. * * @param int $user_id WordPress User ID. */ public static function delete_customer_by_user_id( $user_id ) { global $wpdb; $user_id = (int) $user_id; $num_deleted = $wpdb->delete( self::get_db_table_name(), array( 'user_id' => $user_id ) ); if ( $num_deleted ) { ReportsCache::invalidate(); } } /** * Initialize query objects. */ protected function initialize_queries() { $this->clear_all_clauses(); $table_name = self::get_db_table_name(); $this->subquery = new SqlQuery( $this->context . '_subquery' ); $this->subquery->add_sql_clause( 'from', $table_name ); $this->subquery->add_sql_clause( 'select', "{$table_name}.customer_id" ); $this->subquery->add_sql_clause( 'group_by', "{$table_name}.customer_id" ); } }