ID; } elseif ( $by_txn ) { if ( empty( $payment_or_txn_id ) ) { return false; } $payment_id = edd_get_order_id_from_transaction_id( $payment_or_txn_id ); if ( empty( $payment_id ) ) { return false; } } else { $payment_id = $payment_or_txn_id; } if ( empty( $payment_id ) ) { return false; } $payment = new EDD_Payment( $payment_id ); if ( empty( $payment->ID ) || ( ! $by_txn && (int) $payment->ID !== (int) $payment_id ) ) { return false; } return $payment; } /** * Retrieve payments from the database. * * Since 1.2, this function takes an array of arguments, instead of individual * parameters. All of the original parameters remain, but can be passed in any * order via the array. * * $offset = 0, $number = 20, $mode = 'live', $orderby = 'ID', $order = 'DESC', * $user = null, $status = 'any', $meta_key = null * * @since 1.0 * @since 1.8 Refactored to be a wrapper for EDD_Payments_Query. * * @param array $args Arguments passed to get payments. * @return EDD_Payment[]|int $payments Payments retrieved from the database. */ function edd_get_payments( $args = array() ) { $args = apply_filters( 'edd_get_payments_args', $args ); $payments = new EDD_Payments_Query( $args ); return $payments->get_payments(); } /** * Retrieve payment by a given field. * * @since 2.0 * * @param string $field The field to retrieve the payment with. * @param mixed $value The value for $field. * * @return mixed */ function edd_get_payment_by( $field = '', $value = '' ) { $payment = false; if ( ! empty( $field ) && ! empty( $value ) ) { switch ( strtolower( $field ) ) { case 'id': $payment = edd_get_payment( $value ); if ( ! $payment->ID > 0 ) { $payment = false; } break; case 'key': $order = edd_get_order_by( 'payment_key', $value ); if ( $order ) { $payment = edd_get_payment( $order->id ); if ( ! $payment->ID > 0 ) { $payment = false; } } break; case 'payment_number': $order = edd_get_order_by( 'order_number', $value ); if ( $order ) { $payment = edd_get_payment( $order->id ); if ( ! $payment->ID > 0 ) { $payment = false; } } break; } } return $payment; } /** * Insert an order into the database. * * @since 1.0 * @since 3.0 Refactored to add orders using new methods. * * @param array $order_data Order data to process. * @return int|bool Order ID if the order was successfully inserted, false otherwise. */ function edd_insert_payment( $order_data = array() ) { if ( empty( $order_data ) ) { return false; } return edd_build_order( $order_data ); } /** * Updates a payment status. * * @since 1.0 * @since 3.0 Updated to use new order methods. * * @param int $order_id Order ID. * @param string $new_status order status (default: publish) * * @return bool True if the status was updated successfully, false otherwise. */ function edd_update_payment_status( $order_id = 0, $new_status = 'complete' ) { return edd_update_order_status( $order_id, $new_status ); } /** * Deletes a Purchase * * @since 1.0 * @since 3.0 Added logic to bail early if order not found in database. * * @param int $payment_id Payment ID. Default 0. * @param bool $update_customer If we should update the customer stats. Default true. * @param bool $delete_download_logs If we should remove all file download logs associated with the payment. Default false. */ function edd_delete_purchase( $payment_id = 0, $update_customer = true, $delete_download_logs = false ) { $payment = edd_get_payment( $payment_id ); // Bail if an order does not exist. if ( ! $payment ) { return; } // Update sale counts and earnings for all purchased products edd_undo_purchase( false, $payment_id ); $amount = edd_get_payment_amount( $payment_id ); $status = $payment->post_status; $customer_id = edd_get_payment_customer_id( $payment_id ); $customer = edd_get_customer( $customer_id ); // Only decrease earnings if they haven't already been decreased (or were never increased for this payment). if ( 'revoked' === $status || 'complete' === $status ) { edd_decrease_total_earnings( $amount ); // Clear the This Month earnings (this_monththis_month is NOT a typo) delete_transient( md5( 'edd_earnings_this_monththis_month' ) ); } do_action( 'edd_payment_delete', $payment_id ); if ( $customer && $customer->id && $update_customer ) { // Remove the payment ID from the customer $customer->remove_payment( $payment_id ); } // Remove the order. edd_delete_order( $payment_id ); // Delete file download logs. if ( $delete_download_logs ) { $logs = edd_get_file_download_logs( array( 'order_id' => $payment_id, ) ); if ( $logs ) { foreach ( $logs as $log ) { edd_delete_file_download_log( $log->id ); } } } do_action( 'edd_payment_deleted', $payment_id ); } /** * Undo a purchase, including the decrease of sale and earning stats. Used for * when refunding or deleting a purchase. * * @since 1.0.8.1 * @since 3.0 Updated to use new refunds API and new query methods. * Updated to use new nomenclature. * Set default value of order ID to 0. * Method now returns the refunded order ID. * * @param int $download_id Download ID. * @param int $order_id Order ID. * * @return int|false Refunded order ID, false otherwise. */ function edd_undo_purchase( $download_id = 0, $order_id = 0 ) { /** * In 2.5.7, a bug was found that $download_id was an incorrect usage. Passing it in * now does nothing, but we're holding it in place for legacy support of the argument order. */ if ( ! empty( $download_id ) ) { _edd_deprected_argument( 'download_id', 'edd_undo_purchase', '2.5.7' ); } // Bail if no order ID was passed. if ( empty( $order_id ) ) { return false; } // Refund the order. return edd_refund_order( $order_id ); } /** * Count Payments * * Returns the total number of payments recorded. * * @since 1.0 * @since 3.0 Refactored to work with edd_orders table. * * @param array $args List of arguments to base the payments count on. * * @return object $stats Number of orders grouped by order status. */ function edd_count_payments( $args = array() ) { global $wpdb; $args = wp_parse_args( $args, array( 'user' => null, 'customer' => null, 's' => null, 'start-date' => null, 'end-date' => null, 'download' => null, 'gateway' => null, 'type' => 'sale', ) ); $select = 'SELECT edd_o.status, COUNT(*) AS count'; $from = "FROM {$wpdb->edd_orders} edd_o"; $join = ''; $where = 'WHERE 1=1'; $orderby = ''; $groupby = 'GROUP BY edd_o.status'; // Hold the query arguments passed to edd_count_orders(). $query_args = array(); // Count orders for a specific user. if ( ! empty( $args['user'] ) ) { if ( is_email( $args['user'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.email = %s', sanitize_email( $args['user'] ) ); } elseif ( is_numeric( $args['user'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.user_id = %d', absint( $args['user'] ) ); } // Count orders for a specific customer. } elseif ( ! empty( $args['customer'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.customer_id = %d', absint( $args['customer'] ) ); // Count payments for a search } elseif ( ! empty( $args['s'] ) ) { $args['s'] = sanitize_text_field( $args['s'] ); // Filter by email address if ( is_email( $args['s'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.email = %s', sanitize_email( $args['s'] ) ); // Filter by payment key. } elseif ( 32 === strlen( $args['s'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.payment_key = %s', sanitize_email( $args['s'] ) ); // Filter by download ID. } elseif ( '#' === substr( $args['s'], 0, 1 ) ) { $search = str_replace( '#:', '', $args['s'] ); $search = str_replace( '#', '', $search ); $join = "INNER JOIN {$wpdb->edd_order_items} edd_oi ON edd_o.id = edd_oi.order_id"; $where .= $wpdb->prepare( ' AND edd_oi.product_id = %d', $search ); // Filter by user ID. } elseif ( is_numeric( $args['s'] ) ) { $query_args['user_id'] = absint( $args['s'] ); // Filter by discount code. } elseif ( 0 === strpos( $args['s'], 'discount:' ) ) { $search = str_replace( 'discount:', '', $args['s'] ); $join = "INNER JOIN {$wpdb->edd_order_adjustments} edd_oa ON edd_o.id = edd_oa.order_id"; $where .= $wpdb->prepare( " AND edd_oa.description = %s AND edd_oa.type = 'discount'", $search ); } } if ( ! empty( $args['download'] ) && is_numeric( $args['download'] ) ) { $join = "INNER JOIN {$wpdb->edd_order_items} edd_oi ON edd_o.id = edd_oi.order_id"; $where .= $wpdb->prepare( ' AND edd_oi.product_id = %d', absint( $args['download'] ) ); } if ( ! empty( $args['gateway'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.gateway = %s', sanitize_text_field( $args['gateway'] ) ); } if ( ! empty( $args['type'] ) ) { $where .= $wpdb->prepare( ' AND edd_o.type = %s', sanitize_text_field( $args['type'] ) ); } if ( ! empty( $args['start-date'] ) && false !== strpos( $args['start-date'], '/' ) ) { $date_parts = explode( '/', $args['start-date'] ); $month = ! empty( $date_parts[0] ) && is_numeric( $date_parts[0] ) ? $date_parts[0] : 0; $day = ! empty( $date_parts[1] ) && is_numeric( $date_parts[1] ) ? $date_parts[1] : 0; $year = ! empty( $date_parts[2] ) && is_numeric( $date_parts[2] ) ? $date_parts[2] : 0; $is_date = checkdate( $month, $day, $year ); if ( false !== $is_date ) { $date = new DateTime( $args['start-date'] ); $where .= $wpdb->prepare( ' AND edd_o.date_created >= %s', $date->format( 'Y-m-d 00:00:00' ) ); } // Fixes an issue with the payments list table counts when no end date is specified (partly with stats class). if ( empty( $args['end-date'] ) ) { $args['end-date'] = $args['start-date']; } } if ( ! empty( $args['end-date'] ) && false !== strpos( $args['end-date'], '/' ) ) { $date_parts = explode( '/', $args['end-date'] ); $month = ! empty( $date_parts[0] ) ? $date_parts[0] : 0; $day = ! empty( $date_parts[1] ) ? $date_parts[1] : 0; $year = ! empty( $date_parts[2] ) ? $date_parts[2] : 0; $is_date = checkdate( $month, $day, $year ); if ( false !== $is_date ) { $date = date( 'Y-m-d', strtotime( '+1 day', mktime( 0, 0, 0, $month, $day, $year ) ) ); $where .= $wpdb->prepare( ' AND edd_o.date_created < %s', $date ); } } $where = apply_filters( 'edd_count_payments_where', $where ); $join = apply_filters( 'edd_count_payments_join', $join ); $query = "{$select} {$from} {$join} {$where} {$orderby} {$groupby}"; $cache_key = md5( $query ); $count = wp_cache_get( $cache_key, 'counts' ); if ( false !== $count ) { return $count; } $counts = $wpdb->get_results( $query, ARRAY_A ); // Here for backwards compatibility. $statuses = array_merge( get_post_stati(), edd_get_payment_status_keys() ); if ( isset( $statuses['private'] ) && empty( $args['s'] ) ) { unset( $statuses['private'] ); } $stats = array(); foreach ( $statuses as $status ) { $stats[ $status ] = 0; } foreach ( (array) $counts as $row ) { // Here for backwards compatibility. if ( 'private' === $row['status'] && empty( $args['s'] ) ) { continue; } $stats[ $row['status'] ] = absint( $row['count'] ); } $stats = (object) $stats; wp_cache_set( $cache_key, $stats, 'counts' ); return $stats; } /** * Check for existing payment. * * @since 1.0 * @since 3.0 Refactored to use EDD\Orders\Order. * * @param int $order_id Order ID. * @return bool True if payment exists, false otherwise. */ function edd_check_for_existing_payment( $order_id ) { $exists = false; $order = edd_get_order( $order_id ); // Bail if an order was not found. if ( ! $order ) { return false; } if ( (int) $order_id === (int) $order->id && $order->is_complete() ) { $exists = true; } return $exists; } /** * Get order status. * * @since 1.0 * @since 3.0 Updated to use new EDD\Order\Order class. * * @param mixed $order Payment post object, EDD_Payment object, or payment/post ID. * @param bool $return_label Whether to return the payment status or not * * @return bool|mixed if payment status exists, false otherwise */ function edd_get_payment_status( $order, $return_label = false ) { if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); if ( ! $order ) { return false; } } if ( $order instanceof EDD_Payment ) { /** @var EDD_Payment $order */ $order = edd_get_order( $order->id ); } if ( $order instanceof WP_Post ) { /** @var WP_Post $order */ $order = edd_get_order( $order->ID ); } if ( ! is_object( $order ) ) { return false; } $status = $order->status; if ( empty( $status ) ) { return false; } if ( true === $return_label ) { $status = edd_get_payment_status_label( $status ); } else { $keys = edd_get_payment_status_keys(); $found_key = array_search( strtolower( $status ), $keys ); $status = false !== $found_key && array_key_exists( $found_key, $keys ) ? $keys[ $found_key ] : false; } return ! empty( $status ) ? $status : false; } /** * Given a payment status string, return the label for that string. * * @since 2.9.2 * @param string $status * * @return bool|mixed */ function edd_get_payment_status_label( $status = '' ) { $default = str_replace( '_', ' ', $status ); $default = ucwords( $default ); $statuses = edd_get_payment_statuses(); if ( ! is_array( $statuses ) || empty( $statuses ) ) { return $default; } if ( array_key_exists( $status, $statuses ) ) { return $statuses[ $status ]; } return $default; } /** * Retrieves all available statuses for payments. * * @since 1.0.8.1 * @since 3.0 Updated 'publish' status to be 'Completed' for consistency with other statuses. * * @return array $payment_status All the available payment statuses. */ function edd_get_payment_statuses() { return apply_filters( 'edd_payment_statuses', array( 'pending' => __( 'Pending', 'easy-digital-downloads' ), 'processing' => __( 'Processing', 'easy-digital-downloads' ), 'complete' => __( 'Completed', 'easy-digital-downloads' ), 'refunded' => __( 'Refunded', 'easy-digital-downloads' ), 'partially_refunded' => __( 'Partially Refunded', 'easy-digital-downloads' ), 'revoked' => __( 'Revoked', 'easy-digital-downloads' ), 'failed' => __( 'Failed', 'easy-digital-downloads' ), 'abandoned' => __( 'Abandoned', 'easy-digital-downloads' ) ) ); } /** * Retrieves keys for all available statuses for payments. * * @since 2.3 * * @return array $payment_status All the available payment statuses. */ function edd_get_payment_status_keys() { $statuses = array_keys( edd_get_payment_statuses() ); asort( $statuses ); return array_values( $statuses ); } /** * Checks whether a payment has been marked as complete. * * @since 1.0.8 * @since 3.0 Refactored to use EDD\Orders\Order. * * @param int $order_id Order ID to check against. * @return bool True if complete, false otherwise. */ function edd_is_payment_complete( $order_id = 0 ) { $order = edd_get_order( $order_id ); $ret = false; $status = null; if ( $order ) { $status = $order->status; if ( (int) $order_id === (int) $order->id && $order->is_complete() ) { $ret = true; } } return apply_filters( 'edd_is_payment_complete', $ret, $order_id, $status ); } /** * Retrieve total number of orders. * * @since 1.2.2 * * @return int $count Total sales */ function edd_get_total_sales() { $payments = edd_count_payments( array( 'type' => 'sale' ) ); return $payments->revoked + $payments->complete; } /** * Calculate the total earnings of the store. * * @since 1.2 * @since 3.0 Refactored to work with new tables. * @since 3.0.4 Added the $force argument, to force querying again. * * @param bool $include_taxes Whether taxes should be included. Default true. * @param bool $force If we should force a new calculation. * @return float $total Total earnings. */ function edd_get_total_earnings( $include_taxes = true, $force = true ) { global $wpdb; $key = $include_taxes ? 'edd_earnings_total' : 'edd_earnings_total_without_tax'; $total = $force ? false : get_transient( $key ); // If no total stored in the database, use old method of calculating total earnings. if ( false === $total ) { $stats = new EDD\Stats(); $total = $stats->get_order_earnings( array( 'output' => 'typed', 'exclude_taxes' => ! $include_taxes, 'revenue_type' => 'net', ) ); // Cache results for 1 day. This cache is cleared automatically when a payment is made. set_transient( $key, $total, 86400 ); // Store as an option for backwards compatibility. update_option( $key, $total, false ); } else { // Always ensure that we're working with a float, since the transient comes back as a string. $total = (float) $total; } // Don't ever show negative earnings. if ( $total < 0 ) { $total = 0; } $total = edd_format_amount( $total, true, edd_get_currency(), 'typed' ); return apply_filters( 'edd_total_earnings', $total ); } /** * Increase the store's total earnings. * * @since 1.8.4 * * @param $amount int The amount you would like to increase the total earnings by. * @return float $total Total earnings */ function edd_increase_total_earnings( $amount = 0 ) { $total = floatval( edd_get_total_earnings( true, true ) ); $total += floatval( $amount ); return $total; } /** * Decrease the store's total earnings. * * @since 1.8.4 * * @param $amount int The amount you would like to decrease the total earnings by. * @return float $total Total earnings. */ function edd_decrease_total_earnings( $amount = 0 ) { $total = edd_get_total_earnings( true, true ); $total -= $amount; if ( $total < 0 ) { $total = 0; } return $total; } /** * Retrieve order meta field for an order. * * @since 1.2 * * @internal This needs to continue making a call to EDD_Payment::get_meta() as it handles the backwards compatibility for * _edd_payment_meta if requested. * * @param int $payment_id Payment ID. * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. Default '_edd_payment_meta'. * @param bool $single Optional, default is false. If true, return only the first value of the specified meta_key. * This parameter has no effect if meta_key is not specified. * * @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true. */ function edd_get_payment_meta( $payment_id = 0, $key = '_edd_payment_meta', $single = true ) { $payment = edd_get_payment( $payment_id ); return $payment ? $payment->get_meta( $key, $single ) : false; } /** * Update order meta field based on order ID. * * Use the $prev_value parameter to differentiate between meta fields with the * same key and order ID. * * If the meta field for the order does not exist, it will be added. * * @since 1.2 * * @internal This needs to continue making a call to EDD_Payment::update_meta() as it handles the backwards compatibility for * _edd_payment_meta. * * @param int $payment_id Payment ID. * @param string $meta_key Meta data key. * @param mixed $meta_value Meta data value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before removing. Default empty. * * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure. */ function edd_update_payment_meta( $payment_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) { $payment = edd_get_payment( $payment_id ); return $payment ? $payment->update_meta( $meta_key, $meta_value, $prev_value ) : false; } /** * Retrieve the user information associated with an order. * * This method exists for backwards compatibility; in future, the order query methods should be used. * * @since 1.2 * * @internal This needs to continue retrieving from EDD_Payment as it handles backwards compatibility. * * @param int $payment_id Payment ID. * @return array User Information. */ function edd_get_payment_meta_user_info( $payment_id ) { $payment = edd_get_payment( $payment_id ); return $payment ? $payment->user_info : array(); } /** * Retrieve the downloads associated with an order. * * This method exists for backwards compatibility; in future, the order query methods should be used. * * @since 1.2 * * @param int $payment_id Payment ID. * @return array Downloads. */ function edd_get_payment_meta_downloads( $payment_id ) { $payment = edd_get_payment( $payment_id ); return $payment ? $payment->downloads : array(); } /** * Retrieve the cart details. * * This method exists for backwards compatibility; in future, the order query methods should be used. * * @since 1.2 * * @internal This needs to continue retrieving from EDD_Payment as it handles backwards compatibility. * * @param int $payment_id Payment ID * @param bool $include_bundle_files Whether to retrieve product IDs associated with a bundled product and return them in the array * * @return array $cart_details Cart Details Meta Values */ function edd_get_payment_meta_cart_details( $payment_id, $include_bundle_files = false ) { $payment = edd_get_payment( $payment_id ); $cart_details = $payment->cart_details; $payment_currency = $payment->currency; if ( ! empty( $cart_details ) && is_array( $cart_details ) ) { foreach ( $cart_details as $key => $cart_item ) { $cart_details[ $key ]['currency'] = $payment_currency; // Ensure subtotal is set, for pre-1.9 orders. if ( ! isset( $cart_item['subtotal'] ) ) { $cart_details[ $key ]['subtotal'] = $cart_item['price']; } if ( $include_bundle_files ) { if ( 'bundle' !== edd_get_download_type( $cart_item['id'] ) ) { continue; } $price_id = edd_get_cart_item_price_id( $cart_item ); $products = edd_get_bundled_products( $cart_item['id'], $price_id ); if ( empty( $products ) ) { continue; } foreach ( $products as $product_id ) { $cart_details[] = array( 'id' => $product_id, 'name' => get_the_title( $product_id ), 'item_number' => array( 'id' => $product_id, 'options' => array(), ), 'price' => 0, 'subtotal' => 0, 'quantity' => 1, 'tax' => 0, 'in_bundle' => 1, 'parent' => array( 'id' => $cart_item['id'], 'options' => isset( $cart_item['item_number']['options'] ) ? $cart_item['item_number']['options'] : array(), ), 'order_item_id' => $cart_item['order_item_id'], ); } } } } return apply_filters( 'edd_payment_meta_cart_details', $cart_details, $payment_id ); } /** * Get the user email associated with a payment * * @since 1.2 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string $email User email. */ function edd_get_payment_user_email( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return ''; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->email : ''; } /** * Check if the order is associated with a user. * * @since 2.4.4 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return bool True if the payment is **not** associated with a user, false otherwise. */ function edd_is_guest_payment( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return false; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } $is_guest_payment = ! empty( $order->user_id ) && $order->user_id > 0 ? false : true; return (bool) apply_filters( 'edd_is_guest_payment', $is_guest_payment, $order->id ); } /** * Get the user ID associated with an order. * * @since 1.5.1 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string $user_id User ID. */ function edd_get_payment_user_id( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->user_id : 0; } /** * Get the customer ID associated with an order. * * @since 2.1 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return int $customer_id Customer ID. */ function edd_get_payment_customer_id( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->customer_id : 0; } /** * Get the status of the unlimited downloads flag * * @since 2.0 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return bool True if the payment has unlimited downloads, false otherwise. */ function edd_payment_has_unlimited_downloads( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return false; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->has_unlimited_downloads() : false; } /** * Get the IP address used to make a purchase. * * @since 1.9 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string User's IP address. */ function edd_get_payment_user_ip( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return ''; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->ip : ''; } /** * Get the date an order was completed. * * @since 2.0 * @since 3.0 Parameter renamed to $order_id. * * @param int $order_id Order ID. * @return string The date the order was completed. */ function edd_get_payment_completed_date( $order_id = 0 ) { $payment = edd_get_payment( $order_id ); return $payment->completed_date; } /** * Get the gateway associated with an order. * * @since 1.2 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string Payment gateway used for the order. */ function edd_get_payment_gateway( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return ''; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->gateway : ''; } /** * Get the currency code an order was made in. * * @since 2.2 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string $currency The currency code */ function edd_get_payment_currency_code( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return ''; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->currency : ''; } /** * Get the currency name a payment was made in. * * @since 2.2 * @since 3.0 Parameter renamed to $order_id. * * @param int $order_id Order ID. * @return string $currency The currency name. */ function edd_get_payment_currency( $order_id = 0 ) { $currency = edd_get_payment_currency_code( $order_id ); /** * Allow the currency to be filtered. * * @since 2.2 * * @param string $currency Currency name. * @param int $order_id Order ID. */ return apply_filters( 'edd_payment_currency', edd_get_currency_name( $currency ), $order_id ); } /** * Get the payment key for an order. * * @since 1.2 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return string $key Purchase key. */ function edd_get_payment_key( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return ''; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->payment_key : ''; } /** * Get the payment order number. * * This will return the order ID if sequential order numbers are not enabled or the order number does not exist. * * @since 2.0 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return int|string Payment order number. */ function edd_get_payment_number( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->get_number() : 0; } /** * Formats the order number with the prefix and postfix. * * @since 2.4 * * @param int $number The order number to format. * @return string The formatted order number */ function edd_format_payment_number( $number ) { if ( ! edd_get_option( 'enable_sequential' ) ) { return $number; } if ( ! is_numeric( $number ) ) { return $number; } $prefix = edd_get_option( 'sequential_prefix' ); $number = absint( $number ); $postfix = edd_get_option( 'sequential_postfix' ); $formatted_number = $prefix . $number . $postfix; return apply_filters( 'edd_format_payment_number', $formatted_number, $prefix, $number, $postfix ); } /** * Gets the next available order number. * * This is used when inserting a new order. * * @since 2.0 * * @return string $number The next available order number. */ function edd_get_next_payment_number() { if ( ! edd_get_option( 'enable_sequential' ) ) { return false; } $number = get_option( 'edd_last_payment_number' ); $start = edd_get_option( 'sequential_start', 1 ); $increment_number = true; if ( false !== $number ) { if ( empty( $number ) ) { $number = $start; $increment_number = false; } } else { $last_order = edd_get_orders( array( 'number' => 1, 'orderby' => 'id', 'order' => 'desc', ) ); if ( ! empty( $last_order ) && $last_order[0] instanceof EDD\Orders\Order ) { $number = (int) $last_order[0]->get_number(); } if ( ! empty( $number ) && $number !== (int) $last_order[0]->id ) { $number = edd_remove_payment_prefix_postfix( $number ); } else { $number = $start; $increment_number = false; } } $increment_number = apply_filters( 'edd_increment_payment_number', $increment_number, $number ); if ( $increment_number ) { $number++; } return apply_filters( 'edd_get_next_payment_number', $number ); } /** * Given a given a number, remove the pre/postfix. * * @since 2.4 * * @param string $number The formatted number to increment. * @return string The new order number without prefix and postfix. */ function edd_remove_payment_prefix_postfix( $number ) { $prefix = (string) edd_get_option( 'sequential_prefix' ); $postfix = (string) edd_get_option( 'sequential_postfix' ); // Remove prefix $number = preg_replace( '/' . $prefix . '/', '', $number, 1 ); // Remove the postfix $length = strlen( $number ); $postfix_pos = strrpos( $number, strval( $postfix ) ); if ( false !== $postfix_pos ) { $number = substr_replace( $number, '', $postfix_pos, $length ); } // Ensure it's a whole number $number = intval( $number ); return apply_filters( 'edd_remove_payment_prefix_postfix', $number, $prefix, $postfix ); } /** * Get the fully formatted order amount. The order amount is retrieved using * edd_get_payment_amount() and is then sent through edd_currency_filter() and * edd_format_amount() to format the amount correctly. * * @since 1.4 * @since 3.0 Parameter renamed to $order_id. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * * @return string $amount Fully formatted payment amount */ function edd_payment_amount( $order = 0 ) { if ( is_numeric( $order ) && ! empty( $order ) ) { $order = edd_get_order( $order ); } return edd_display_amount( edd_get_payment_amount( $order ), edd_get_payment_currency_code( $order ) ); } /** * Get the amount associated with an order. * * @since 1.2 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return float Order amount. */ function edd_get_payment_amount( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0.00; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } $total = $order ? $order->total : 0.00; /** * Filter the order amount. * * @since 1.2 * * @param float $total Order total. * @param int $order_id Order ID. */ return apply_filters( 'edd_payment_amount', floatval( $total ), $order->id ); } /** * Retrieves subtotal for an order (this is the amount before taxes) and then * returns a full formatted amount. This function essentially calls * edd_get_payment_subtotal(). * * @since 1.3.3 * @since 3.0 Parameter renamed to $order_id. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order_id Order ID. * * @return string Fully formatted order subtotal. */ function edd_payment_subtotal( $order_id = 0 ) { $subtotal = edd_get_payment_subtotal( $order_id ); return edd_display_amount( $subtotal, edd_get_payment_currency_code( $order_id ) ); } /** * Retrieves subtotal for an order (this is the amount before taxes) and then * returns a non formatted amount. * * @since 1.3.3 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @return float $subtotal Subtotal for the order (non formatted). */ function edd_get_payment_subtotal( $order = 0 ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0.00; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->subtotal : 0.00; } /** * Retrieves taxed amount for payment and then returns a full formatted amount * This function essentially calls edd_get_payment_tax() * * @since 1.3.3 * @since 3.0 Parameter renamed to $order_id. * * @param int $order_id Order ID. * @param bool $payment_meta Parameter no longer used. * * @return string $tax Fully formatted tax amount. */ function edd_payment_tax( $order_id = 0, $payment_meta = null ) { $tax = edd_get_payment_tax( $order_id, false ); return edd_display_amount( $tax, edd_get_payment_currency_code( $order_id ) ); } /** * Retrieves taxed amount for payment and then returns a non formatted amount * * @since 1.3.3 * @since 3.0 Refactored to use EDD\Orders\Order. * @since 3.1.1 Allows passing a full EDD\Orders\Order object in, instead of just the ID * * @param int $order Order ID or the EDD\Orders\Order object * @param bool $payment_meta Parameter no longer used. * * @return float $tax Tax for payment (non formatted) */ function edd_get_payment_tax( $order = 0, $payment_meta = null ) { // Bail if nothing was passed. if ( empty( $order ) ) { return 0.00; } if ( is_numeric( $order ) ) { $order = edd_get_order( $order ); } return $order ? $order->tax : 0.00; } /** * Retrieve the tax for a cart item by the cart key. * * @since 2.5 * @since 3.0 Refactored to use EDD\Orders\Order_Item. * * @param int $order_id Order ID. * @param int $cart_index Cart index. * * @return float Cart item tax amount. */ function edd_get_payment_item_tax( $order_id = 0, $cart_index = 0 ) { // Bail if nothing was passed. if ( empty( $order_id ) ) { return 0.00; } $order_item_tax = edd_get_order_items( array( 'number' => 1, 'order_id' => $order_id, 'cart_index' => $cart_index, 'fields' => 'tax', ) ); $order_item_tax = ( $order_item_tax && ! empty( $order_item_tax ) ) ? $order_item_tax[0] : 0.00; return (float) $order_item_tax; } /** * Retrieves arbitrary fees for the order. * * @since 1.5 * @since 3.0 Parameter renamed to $order_id. * * @param int $order_id Order ID. * @param string $type Fee type. Default all. * * @return array Order fees. */ function edd_get_payment_fees( $order_id = 0, $type = 'all' ) { // Bail if nothing was passed. if ( empty( $order_id ) ) { return array(); } $payment = edd_get_payment( $order_id ); return $payment ? $payment->get_fees( $type ) : array(); } /** * Retrieves the transaction ID for an order. * * @since 2.1 * @since 3.0 Refactored to use EDD\Orders\Order. * * @param int $order_id Order ID. * @return string Transaction ID. */ function edd_get_payment_transaction_id( $order_id = 0 ) { // Bail if nothing was passed. if ( empty( $order_id ) ) { return ''; } $order = edd_get_order( $order_id ); return $order ? $order->get_transaction_id() : ''; } /** * Sets a transaction ID for a given order. * * @since 2.1 * @since 3.0 Updated to use new methods and store data in the new tables. * Added $amount parameter. * * @param int $order_id Order ID. * @param string $transaction_id Transaction ID from the gateway. * @param mixed $amount Transaction amount. * * @return mixed Meta ID if successful, false if unsuccessful. */ function edd_set_payment_transaction_id( $order_id = 0, $transaction_id = '', $amount = false ) { // Bail if nothing was passed. if ( empty( $order_id ) || empty( $transaction_id ) ) { return false; } /** * Filter the transaction ID before being stored in the database. * * @since 2.1 * * @param string $transaction_id Transaction ID. * @param int $order_id Order ID. */ $transaction_id = apply_filters( 'edd_set_payment_transaction_id', $transaction_id, $order_id ); $order = edd_get_order( $order_id ); if ( $order ) { $amount = false === $amount ? $order->total : floatval( $amount ); $transaction_ids = array_values( edd_get_order_transactions( array( 'fields' => 'ids', 'number' => 1, 'object_id' => $order_id, 'object_type' => 'order', 'orderby' => 'date_created', 'order' => 'ASC', ) ) ); if ( $transaction_ids && isset( $transaction_ids[0] ) ) { return edd_update_order_transaction( $transaction_ids[0], array( 'transaction_id' => $transaction_id, 'gateway' => $order->gateway, 'total' => $amount, ) ); } else { return edd_add_order_transaction( array( 'object_id' => $order_id, 'object_type' => 'order', 'transaction_id' => $transaction_id, 'gateway' => $order->gateway, 'status' => 'complete', 'total' => $amount, ) ); } } return false; } /** * Retrieve the order ID based on the payment key. * * @since 1.3.2 * @since 3.0 Updated to use new query methods. Renamed parameter to $payment_key. * * @param string $payment_key Payment key to search for. * @return int Order ID. */ function edd_get_purchase_id_by_key( $payment_key ) { $global_key_string = 'edd_purchase_id_by_key' . $payment_key; global $$global_key_string; if ( null !== $$global_key_string ) { return $$global_key_string; } /** @var EDD\Orders\Order $order */ $order = edd_get_order_by( 'payment_key', $payment_key ); if ( false !== $order ) { $$global_key_string = $order->id; return $$global_key_string; } return 0; } /** * Retrieve the order ID based on the transaction ID. * * @since 2.4 * @since 3.0 Dispatch to edd_get_order_id_from_transaction_id(). * * @see edd_get_order_id_from_transaction_id() * * @param string $transaction_id Transaction ID to search for. * @return int Order ID. */ function edd_get_purchase_id_by_transaction_id( $transaction_id ) { return edd_get_order_id_from_transaction_id( $transaction_id ); } /** * Retrieve all notes attached to an order. * * @since 1.4 * @since 3.0 Updated to use the edd_notes custom table to store notes. * * @param int $order_id The order ID to retrieve notes for. * @param string $search Search for notes that contain a search term. * @return array|bool $notes Order notes, false otherwise. */ function edd_get_payment_notes( $order_id = 0, $search = '' ) { // Bail if nothing passed. if ( empty( $order_id ) && empty( $search ) ) { return false; } $args = array( 'object_type' => 'order', 'order' => 'ASC', ); if ( ! empty( $order_id ) ) { $args['object_id'] = $order_id; } if ( ! empty( $search ) ) { $args['search'] = sanitize_text_field( $search ); } return edd_get_notes( $args ); } /** * Add a note to an order. * * @since 1.4 * @since 3.0 Updated to use the edd_notes custom table to store notes. * * @param int $order_id The order ID to store a note for. * @param string $note The content of the note. * @return int|false The new note ID, false otherwise. */ function edd_insert_payment_note( $order_id = 0, $note = '' ) { // Sanitize note contents if ( ! empty( $note ) ) { $note = trim( wp_kses( $note, edd_get_allowed_tags() ) ); } // Bail if no order ID or note. if ( empty( $order_id ) || empty( $note ) ) { return false; } do_action( 'edd_pre_insert_payment_note', $order_id, $note ); /** * For backwards compatibility purposes, we need to pass the data to * wp_filter_comment in the event that the note data is filtered using the * WordPress Core filters prior to be inserted into the database. */ $filtered_data = wp_filter_comment( array( 'comment_post_ID' => $order_id, 'comment_content' => $note, 'user_id' => is_admin() ? get_current_user_id() : 0, 'comment_date' => current_time( 'mysql' ), 'comment_date_gmt' => current_time( 'mysql', 1 ), 'comment_approved' => 1, 'comment_parent' => 0, 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_url' => '', 'comment_author_email' => '', 'comment_type' => 'edd_payment_note' ) ); // Add the note $note_id = edd_add_note( array( 'object_id' => $filtered_data['comment_post_ID'], 'content' => $filtered_data['comment_content'], 'user_id' => $filtered_data['user_id'], 'object_type' => 'order', ) ); do_action( 'edd_insert_payment_note', $note_id, $order_id, $note ); // Return the ID of the new note return $note_id; } /** * Deletes an order note. * * @since 1.6 * @since 3.0 Updated to use the edd_notes custom table to store notes. * * @param int $note_id Note ID. * @param int $order_id Order ID. * @return bool True on success, false otherwise. */ function edd_delete_payment_note( $note_id = 0, $order_id = 0 ) { if ( empty( $note_id ) ) { return false; } do_action( 'edd_pre_delete_payment_note', $note_id, $order_id ); $ret = edd_delete_note( $note_id ); do_action( 'edd_post_delete_payment_note', $note_id, $order_id ); return $ret; } /** * Gets the payment note HTML. * * @since 1.9 * @since 3.0 Deprecated & unused (use edd_admin_get_note_html()) * * @param object|int $note The note object or ID. * @param int $payment_id The payment ID the note is connected to. * * @return string Payment note HTML. */ function edd_get_payment_note_html( $note, $payment_id = 0 ) { return edd_admin_get_note_html( $note ); } /** * Exclude notes (comments) on edd_payment post type from showing in Recent * Comments widgets. * * @since 1.4.1 * * @param object $query WordPress Comment Query Object */ function edd_hide_payment_notes( $query ) { global $wp_version; if ( version_compare( floatval( $wp_version ), '4.1', '>=' ) ) { $types = isset( $query->query_vars['type__not_in'] ) ? $query->query_vars['type__not_in'] : array(); if ( ! is_array( $types ) ) { $types = array( $types ); } $types[] = 'edd_payment_note'; $query->query_vars['type__not_in'] = $types; } } add_action( 'pre_get_comments', 'edd_hide_payment_notes', 10 ); /** * Exclude notes (comments) on edd_payment post type from showing in Recent * Comments widgets * * @since 2.2 * * @param array $clauses Comment clauses for comment query. * @param object $wp_comment_query WordPress Comment Query Object. * * @return array $clauses Updated comment clauses. */ function edd_hide_payment_notes_pre_41( $clauses, $wp_comment_query ) { global $wp_version; if ( version_compare( floatval( $wp_version ), '4.1', '<' ) ) { $clauses['where'] .= ' AND comment_type != "edd_payment_note"'; } return $clauses; } add_filter( 'comments_clauses', 'edd_hide_payment_notes_pre_41', 10, 2 ); /** * Exclude notes (comments) on edd_payment post type from showing in comment feeds. * * @since 1.5.1 * * @param string $where * @param object $wp_comment_query WordPress Comment Query Object * * @return string $where Updated WHERE clause. */ function edd_hide_payment_notes_from_feeds( $where, $wp_comment_query ) { global $wpdb; $where .= $wpdb->prepare( ' AND comment_type != %s', 'edd_payment_note' ); return $where; } add_filter( 'comment_feed_where', 'edd_hide_payment_notes_from_feeds', 10, 2 ); /** * Remove EDD Comments from the wp_count_comments function * * @since 1.5.2 * * @param array $stats Empty from core filter. * @param int $post_id Post ID. * * @return object Comment counts. */ function edd_remove_payment_notes_in_comment_counts( $stats, $post_id ) { global $wpdb, $pagenow; $array_excluded_pages = array( 'index.php', 'edit-comments.php' ); if ( ! in_array( $pagenow, $array_excluded_pages, true ) ) { return $stats; } $post_id = (int) $post_id; if ( apply_filters( 'edd_count_payment_notes_in_comments', false ) ) { return $stats; } $stats = wp_cache_get( "comments-{$post_id}", 'counts' ); if ( false !== $stats ) { return $stats; } $where = 'WHERE comment_type != "edd_payment_note"'; if ( $post_id > 0 ) { $where .= $wpdb->prepare( " AND comment_post_ID = %d", $post_id ); } $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} {$where} GROUP BY comment_approved", ARRAY_A ); $total = 0; $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed', ); foreach ( (array) $count as $row ) { // Don't count post-trashed toward totals. if ( 'post-trashed' !== $row['comment_approved'] && 'trash' !== $row['comment_approved'] ) { $total += $row['num_comments']; } if ( isset( $approved[ $row['comment_approved'] ] ) ) { $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments']; } } $stats['total_comments'] = $total; foreach ( $approved as $key ) { if ( empty( $stats[ $key ] ) ) { $stats[ $key ] = 0; } } $stats = (object) $stats; wp_cache_set( "comments-{$post_id}", $stats, 'counts' ); return $stats; } add_filter( 'wp_count_comments', 'edd_remove_payment_notes_in_comment_counts', 10, 2 ); /** * Filter where older than one week. * * @since 1.6 * * @param string $where Where clause. * @return string $where Modified where clause. */ function edd_filter_where_older_than_week( $where = '' ) { // Payments older than one week. $start = date( 'Y-m-d', strtotime( '-7 days' ) ); $where .= " AND post_date <= '{$start}'"; return $where; } /** * Gets the payment ID from the final edd_payment post. * This was set as an option when the custom orders table was created. * For internal use only. * * @todo deprecate in 3.1 * * @since 3.0 * @return false|int */ function _edd_get_final_payment_id() { return get_option( 'edd_v3_migration_pending', false ); } /** * Evaluates whether the EDD 3.0 migration should be run, * based on _any_ data existing which will need to be migrated. * * This should only be run after `edd_v30_is_migration_complete` has returned false. * * @todo deprecate in 3.1 * * @since 3.0.2 * @return bool */ function _edd_needs_v3_migration() { // Return true if a final payment ID was recorded. if ( _edd_get_final_payment_id() ) { return true; } // Return true if any tax rates were saved. $tax_rates = get_option( 'edd_tax_rates', array() ); if ( ! empty( $tax_rates ) ) { return true; } // Return true if a fallback tax rate was saved. if ( edd_get_option( 'tax_rate', false ) ) { return true; } global $wpdb; // Return true if any discounts were saved. $discounts = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_type = %s LIMIT 1", esc_sql( 'edd_discount' ) ) ); if ( ! empty( $discounts ) ) { return true; } // Return true if there are any customers. $customers = $wpdb->get_results( "SELECT * FROM {$wpdb->edd_customers} LIMIT 1" ); if ( ! empty( $customers ) ) { return true; } // Return true if any customer email addresses were saved. $customer_emails = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->edd_customermeta} WHERE meta_key = %s LIMIT 1", esc_sql( 'additional_email' ) ) ); if ( ! empty( $customer_emails ) ) { return true; } // Return true if any customer addresses are in the user meta table. $customer_addresses = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->usermeta} WHERE meta_key = %s ORDER BY umeta_id ASC LIMIT 1", esc_sql( '_edd_user_address' ) ) ); if ( ! empty( $customer_addresses ) ) { return true; } // Return true if there are any EDD logs (not sales) saved. $logs = $wpdb->get_results( $wpdb->prepare( "SELECT p.*, t.slug FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id) LEFT JOIN {$wpdb->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) LEFT JOIN {$wpdb->terms} AS t ON (tt.term_id = t.term_id) WHERE p.post_type = %s AND t.slug != %s GROUP BY p.ID LIMIT 1", esc_sql( 'edd_log' ), esc_sql( 'sale' ) ) ); if ( ! empty( $logs ) ) { return true; } return false; } /** * Maybe adds a migration in progress notice to the order history. * * @todo remove in 3.1 * @since 3.0 * @return void */ add_action( 'edd_pre_order_history', function( $orders, $user_id ) { if ( ! _edd_get_final_payment_id() ) { return; } ?>