laipower/wp-content/plugins/easy-digital-downloads/includes/admin/payments/class-refund-items-table.php

756 lines
21 KiB
PHP

<?php
/**
* Refund Items Table Class.
*
* @package EDD
* @subpackage Admin/Orders
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Admin;
// Exit if accessed directly
use EDD\Orders\Order;
use EDD\Orders\Order_Adjustment;
use EDD\Orders\Order_Item;
defined( 'ABSPATH' ) || exit;
/**
* Order_Items_Table Class.
*
* Renders the Refund Items table on the Refund modal.
*
* @since 3.0
*/
class Refund_Items_Table extends List_Table {
/**
* Constructor.
*
* @since 3.0
* @see WP_List_Table::__construct()
*/
public function __construct() {
global $hook_suffix;
parent::__construct( array(
'singular' => 'refund-item',
'plural' => 'refund-items',
'ajax' => false,
) );
$this->get_counts();
}
/**
* Get the base URL for the order item list table.
*
* @since 3.0
*
* @return string Base URL.
*/
public function get_base_url() {}
/**
* Retrieve the view types.
*
* @since 3.0
*
* @return array $views All the views available.
*/
public function get_views() {
return array();
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns.
*/
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />',
'name' => __( 'Product', 'easy-digital-downloads' ),
'amount' => __( 'Unit Price', 'easy-digital-downloads' ),
'quantity' => __( 'Quantity', 'easy-digital-downloads' ),
'subtotal' => __( 'Subtotal', 'easy-digital-downloads' ),
);
// Maybe add tax column.
$order = $this->get_order();
if ( $order && $order->get_tax_rate() ) {
$columns['tax'] = __( 'Tax', 'easy-digital-downloads' );
}
// Total at the end.
$columns['total'] = __( 'Total', 'easy-digital-downloads' );
// Return columns.
return $columns;
}
/**
* Retrieve the sortable columns.
*
* @since 3.0
*
* @return array Array of all the sortable columns.
*/
public function get_sortable_columns() { return array(); }
/**
* Gets the name of the primary column.
*
* @since 2.5
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* Generates a unique ID for an item, to be used as HTML IDs.
* We cannot simply use `$item->id` because it's possible that an order item and order adjustment
* could have the same ID.
*
* @param Order_Item|Order_Adjustment $item
*
* @since 3.0
* @return string
*/
private function get_item_unique_id( $item ) {
return $item instanceof Order_Item ? 'order-item-' . $item->id : 'order-adjustment-' . $item->id;
}
/**
* Returns a string that designates the type of object. This is used in HTML `name` attributes.
*
* @param Order_Item|Order_Adjustment $item
*
* @since 3.0
* @return string
*/
private function get_object_type( $item ) {
return $item instanceof Order_Item ? 'order_item' : 'order_adjustment';
}
/**
* Returns the item display name.
*
* @param Order_Item|Order_Adjustment $item
*
* @since 3.0
* @return string
*/
private function get_item_display_name( $item ) {
$name = '';
if ( $item instanceof Order_Item ) {
return $item->get_order_item_name();
}
if ( $item instanceof Order_Adjustment ) {
$name = __( 'Order Fee', 'easy-digital-downloads' );
if ( 'credit' === $item->type ) {
$name = __( 'Order Credit', 'easy-digital-downloads' );
}
if ( ! empty( $item->description ) ) {
$name .= ': ' . $item->description;
}
}
return $name;
}
/**
* This function renders most of the columns in the list table.
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Order item or adjustment object.
* @param string $column_name The name of the column.
*
* @return string Column name.
*/
public function column_default( $item, $column_name ) {
$object_type = $this->get_object_type( $item );
$item_id = $this->get_item_unique_id( $item );
switch ( $column_name ) {
case 'amount':
return $this->format_currency( $item, $column_name );
case 'total':
return $this->format_currency( $item, $column_name, 0 );
case 'quantity':
return $this->get_quantity_column( $item, $column_name, $item_id, $object_type );
case 'subtotal':
case 'tax':
return $this->get_adjustable_column( $item, $column_name, $item_id, $object_type );
default:
return property_exists( $item, $column_name )
? $item->{$column_name}
: '';
}
}
/**
* This private function formats a column value for currency.
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Item object.
* @param string $column_name ID of the column being displayed.
* @param false|float $amount_override Amount override, in case it's not in the order item.
*
* @return string Formatted amount.
*/
private function format_currency( $item, $column_name, $amount_override = false ) {
$symbol = $this->get_currency_symbol( $item->order_id );
$currency_pos = edd_get_option( 'currency_position', 'before' );
$formatted_amount = '';
if ( 'before' === $currency_pos ) {
$formatted_amount .= $symbol;
}
// Order Adjustments do not have an `amount` column. We can use `subtotal` instead.
if ( 'amount' === $column_name && $item instanceof Order_Adjustment ) {
$column_name = 'subtotal';
}
$amount = false !== $amount_override ? $amount_override : $item->{$column_name};
$formatted_amount .= '<span data-' . $column_name . '="' . edd_sanitize_amount( $amount ) . '">' . edd_format_amount( $amount, true, $this->get_order_currency_decimals( $item->order_id ) ) . '</span>';
if ( 'after' === $currency_pos ) {
$formatted_amount .= $symbol;
}
return $formatted_amount;
}
/**
* This private function returns the form input for refundable items,
* or amounts for items which have already been refunded.
*
* @param Order_Item $item The item object.
* @param string $column_name ID of the column being displayed.
* @param string $item_id Unique ID of the order item for the refund modal.
* @param string $object_type The item type.
* @return string
*/
private function get_adjustable_column( $item, $column_name, $item_id, $object_type ) {
if ( 'refunded' === $item->status ) {
return $this->format_currency( $item, $column_name, 0 );
}
$currency_pos = edd_get_option( 'currency_position', 'before' );
// Maximum amounts that can be refunded.
$refundable_amounts = $item->get_refundable_amounts();
$amount_remaining = array_key_exists( $column_name, $refundable_amounts ) ? $refundable_amounts[ $column_name ] : $item->{$column_name};
/*
* Original amount.
* For subtotals, we actually do subtotal minus discounts for simplicity so that the end user
* doesn't have to juggle that.
*/
$original_amount = $item->{$column_name};
if ( 'subtotal' === $column_name && ! empty( $item->discount ) ) {
$original_amount -= $item->discount;
}
ob_start();
?>
<div class="edd-form-group">
<label for="edd-order-item-<?php echo esc_attr( $item_id ); ?>-refund-<?php echo esc_attr( $column_name ); ?>" class="screen-reader-text">
<?php
if ( 'subtotal' === $column_name ) {
esc_html_e( 'Amount to refund, excluding tax', 'easy-digital-downloads' );
} else {
esc_html_e( 'Amount of tax to refund', 'easy-digital-downloads' );
}
?>
</label>
<div class="edd-form-group__control">
<?php
if ( 'before' === $currency_pos ) {
echo '<span class="edd-amount-control__currency is-before">';
echo esc_html( $this->get_currency_symbol( $item->order_id ) );
echo '</span>';
}
?>
<span class="edd-amount-control__input">
<input
type="text"
id="edd-order-item-<?php echo esc_attr( $item_id ); ?>-refund-<?php echo esc_attr( $column_name ); ?>"
class="edd-order-item-refund-<?php echo esc_attr( $column_name ); ?> edd-order-item-refund-input"
name="refund_<?php echo esc_attr( $object_type ); ?>[<?php echo esc_attr( $item->id ); ?>][<?php echo esc_attr( $column_name ); ?>]"
value="<?php echo esc_attr( edd_format_amount( $amount_remaining, true, $this->get_order_currency_decimals( $item->order_id ) ) ); ?>"
placeholder="<?php echo esc_attr( edd_format_amount( 0, true, $this->get_order_currency_decimals( $item->order_id ) ) ); ?>"
data-original="<?php echo esc_attr( $original_amount ); ?>"
data-max="<?php echo esc_attr( $amount_remaining ); ?>"
disabled
/>
</span>
<?php
if ( 'after' === $currency_pos ) {
echo '<span class="edd-amount-control__currency is-after">';
echo esc_html( $this->get_currency_symbol( $item->order_id ) );
echo '</span>';
}
?>
</div>
<small class="edd-order-item-refund-max-amount">
<?php
echo _x( 'Max:', 'Maximum input amount', 'easy-digital-downloads' ) . '&nbsp;';
echo $this->format_currency( $item, $column_name, $amount_remaining );
?>
</small>
</div>
<?php
return ob_get_clean();
}
/**
* Gets the quantity column content.
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Order item or adjustment object.
* @param string $column_name The name of the column.
* @param string $item_id Unique ID of the order item for the refund modal.
* @param string $object_type The item type.
* @return string
*/
private function get_quantity_column( $item, $column_name, $item_id, $object_type ) {
$refundable_amounts = $item->get_refundable_amounts();
$item_quantity = 'order_item' === $object_type ? $refundable_amounts['quantity'] : 1;
ob_start();
?>
<div class="edd-form-group">
<label for="edd_order_item_quantity_<?php echo esc_attr( $item_id ); ?>" class="screen-reader-text">
<?php esc_html_e( 'Quantity to refund', 'easy-digital-downloads' ); ?>
</label>
<div class="edd-form-group__control">
<?php if ( 'order_item' !== $object_type ) : ?>
<input type="hidden" data-original="<?php echo esc_attr( $item_quantity ); ?>" id="edd_order_item_quantity_<?php echo esc_attr( $item_id ); ?>" class="edd-order-item-refund-quantity edd-order-item-refund-input readonly" name="refund_<?php echo esc_attr( $object_type ); ?>[<?php echo esc_attr( $item->id ); ?>][quantity]" value="<?php echo esc_attr( $item_quantity ); ?>" disabled />
<?php else : ?>
<?php
$options = range( 1, $item_quantity );
array_unshift( $options, '' );
unset( $options[0] );
$args = array(
'options' => $options,
'name' => 'refund_' . esc_attr( $object_type ) . '[' . esc_attr( $item->id ) . '][quantity]',
'id' => 'edd-order-item-quantity-' . esc_attr( $item_id ),
'class' => 'edd-order-item-refund-quantity edd-order-item-refund-input',
'disabled' => true,
'show_option_all' => false,
'show_option_none' => false,
'chosen' => false,
'selected' => $item_quantity,
'data' => array(
'max' => intval( $item_quantity ),
'original' => intval( $item->quantity ),
),
);
?>
<?php echo EDD()->html->select( $args ); ?>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Retrieves the number of decimals for a given order.
*
* @param int $order_id
*
* @since 3.0
* @return int|null
*/
private function get_order_currency_decimals( $order_id ) {
static $currency_decimals = null;
if ( is_null( $currency_decimals ) ) {
$order = edd_get_order( $order_id );
if ( $order ) {
$currency_decimals = edd_currency_decimal_filter( 2, $order->currency );
} else {
$currency_decimals = 2;
}
}
return $currency_decimals;
}
/**
* Retrieves the currency symbol for a given order item.
*
* @param int $order_id
*
* @since 3.0
* @return string|null
*/
private function get_currency_symbol( $order_id ) {
static $symbol = null;
if ( is_null( $symbol ) ) {
$order = edd_get_order( $order_id );
if ( $order ) {
$symbol = edd_currency_symbol( $order->currency );
}
}
return $symbol;
}
/**
* Render the checkbox column
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Order Item or Order Adjustment object.
*
* @return string
*/
public function column_cb( $item ) {
$object_type = $this->get_object_type( $item );
$refundable_amounts = $item->get_refundable_amounts();
$total_remaining = array_key_exists( 'total', $refundable_amounts ) ? floatval( $refundable_amounts['total'] ) : 0.00;
if ( 'refunded' !== $item->status && 0.00 != $total_remaining ) {
return sprintf(
'<input type="checkbox" name="%1$s[]" id="%1$s-%2$s" class="edd-order-item-refund-checkbox" value="%2$s" /><label for="%1$s-%2$s" class="screen-reader-text">%3$s</label>',
/*$1%s*/
'refund_' . esc_attr( $object_type ),
/*$2%s*/
esc_attr( $item->id ),
/* translators: product name */
esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $this->get_item_display_name( $item ) ) )
);
}
return '';
}
/**
* Render the Name Column
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Order Item object.
*
* @return string Data shown in the Name column
*/
public function column_name( $item ) {
$checkbox_id = 'refund_' . $this->get_object_type( $item ) . '-' . $item->id;
$display_name = esc_html( $this->get_item_display_name( $item ) );
$status_label = ! empty( $item->status ) && 'complete' !== $item->status ? ' &mdash; ' . edd_get_status_label( $item->status ) : '';
if ( 'refunded' === $item->status ) {
return '<span class="row-title">' . $display_name . '</span>' . esc_html( $status_label );
}
return '<label for="' . esc_attr( $checkbox_id ) . '" class="row-title">' . $display_name . '</label>' . $status_label;
}
/**
* Message to be displayed when there are no items
*
* @since 3.0
*/
public function no_items() {
esc_html_e( 'No items found.', 'easy-digital-downloads' );
}
/**
* Retrieve the bulk actions
*
* @since 3.0
* @return array $actions Array of the bulk actions
*/
public function get_bulk_actions() { return array(); }
/**
* Process the bulk actions
*
* @since 3.0
*/
public function process_bulk_action() {}
/**
* Retrieve the order_item code counts
*
* @todo Fees aren't included in this count, but where does this actually get used anyway?
*
* @since 3.0
*/
public function get_counts() {
// Maybe retrieve counts.
if ( ! edd_is_add_order_page() ) {
// Check for an order ID
$order_id = ! empty( $_POST['order_id'] )
? absint( $_POST['order_id'] ) // WPCS: CSRF ok.
: 0;
// Get counts
$this->counts = edd_get_order_item_counts( array(
'order_id' => $order_id,
) );
}
}
/**
* Retrieve all order data to be shown on the refund table.
* This includes order items and order adjustments.
*
* @since 3.0
* @return Order[]|Order_Adjustment[] All order items and order adjustments associated with the current order.
*/
public function get_data() {
$order = $this->get_order();
if ( empty( $order ) ) {
return array();
}
// Get order items.
$order_items = edd_get_order_items( array(
'order_id' => $order->id,
'number' => 999,
) );
// Get order fees
$order_fees = $order->get_fees();
// Get order credits.
$credits = edd_get_order_adjustments( array(
'object_id' => $order->id,
'object_type' => 'order',
'type' => 'credit',
) );
return array_merge( $order_items, $order_fees, $credits );
}
/**
* Setup the final data for the table
*
* @since 3.0
*/
public function prepare_items() {
$this->_column_headers = array(
$this->get_columns(),
array(),
$this->get_sortable_columns(),
);
$this->items = $this->get_data();
}
/**
* Generates content for a single row of the table
*
* @since 3.0
*
* @param Order_Item|Order_Adjustment $item Order item object.
*/
public function single_row( $item ) {
$is_adjustment = $item instanceof Order_Adjustment;
$item_class = $is_adjustment ? $item->object_id : $item->order_id;
// Status.
$classes = array_map( 'sanitize_html_class', array(
'order-' . $item_class,
$item->status,
'refunditem',
) );
// Turn into a string.
$class = implode( ' ', $classes );
$item_id = $this->get_item_unique_id( $item );
$is_credit = $is_adjustment && 'credit' === $item->type;
?>
<tr id="order-item-<?php echo esc_attr( $item_id ); ?>" <?php echo esc_attr( $is_adjustment ? 'data-order-item-adjustment' : 'data-order-item' ); ?>="<?php echo esc_attr( $item->id ); ?>" <?php echo $is_credit ? 'data-credit="1"' : ''; ?> class="<?php echo esc_html( $class ); ?>">
<?php $this->single_row_columns( $item ); ?>
</tr>
<?php
}
/**
* Displays the table.
*
* @since 3.0
*/
public function display() {
$singular = $this->_args['singular'];
wp_nonce_field( 'edd_process_refund', 'edd_process_refund' );
$this->screen->render_screen_reader_content( 'heading_list' );
?>
<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>">
<thead>
<tr>
<?php $this->print_column_headers(); ?>
</tr>
</thead>
<tbody id="the-list"<?php
if ( $singular ) {
echo " data-wp-lists='list:$singular'";
} ?>>
<?php $this->display_rows_or_placeholder(); ?>
</tbody>
</table>
<div class="edd-submit-refund-actions">
<?php
/**
* Triggers after the table, but before the submit button.
*
* @param Order $order
*
* @since 3.0
*/
do_action( 'edd_after_submit_refund_table', $this->get_order() );
$this->display_tablenav( 'bottom' );
?>
</div>
<?php
}
/**
* Adds custom submit button below the refund items table.
*
* @param string $which
* @since 3.0
*/
protected function display_tablenav( $which ) {
if ( 'bottom' !== $which ) {
return;
}
?>
<div class="tablenav bottom">
<button id="edd-submit-refund-submit" class="button button-primary" disabled><?php esc_html_e( 'Submit Refund', 'easy-digital-downloads' ); ?></button>
</div>
<?php
}
/**
* Displays the rows.
*
* This is overridden in order to add columns for the totals.
*
* @since 3.0
*/
public function display_rows() {
static $currency_symbol = null;
$order_id = false;
$currency_position = edd_get_option( 'currency_position', 'before' );
foreach ( $this->items as $item ) {
if ( empty( $order_id ) ) {
$order_id = $item->order_id;
}
$this->single_row( $item );
}
$currency_symbol = $this->get_currency_symbol( $order_id );
// Now we need to add the columns for the totals.
?>
<tr id="edd-refund-submit-subtotal" class="edd-refund-submit-line-total">
<td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>">
<span class="row-title edd-refund-submit-line-total-name"><?php esc_html_e( 'Refund Subtotal:', 'easy-digital-downloads' ); ?></span>
<?php
$currency_symbol_output = sprintf( '<span>%s</span>', $currency_symbol );
$before = 'before' === $currency_position ? $currency_symbol_output : '';
$after = 'after' === $currency_position ? $currency_symbol_output : '';
$amount = edd_format_amount( 0.00, true, $this->get_order_currency_decimals( $order_id ) );
printf(
'<span class="edd-refund-submit-line-total-amount">%1$s<span id="edd-refund-submit-subtotal-amount">%2$s</span>%3$s</span>',
$before, // phpcs:ignore
esc_attr( $amount ),
$after // phpcs:ignore
);
?>
</td>
</tr>
<?php
$order = $this->get_order();
if ( $order && $order->get_tax_rate() ) :
?>
<tr id="edd-refund-submit-tax" class="edd-refund-submit-line-total">
<td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>">
<span class="row-title edd-refund-submit-line-total-name"><?php esc_html_e( 'Refund Tax Total:', 'easy-digital-downloads' ); ?></span>
<?php
printf(
'<span class="edd-refund-submit-line-total-amount">%1$s<span id="edd-refund-submit-tax-amount">%2$s</span>%3$s</span>',
$before, // phpcs:ignore
esc_attr( $amount ),
$after // phpcs:ignore
);
?>
</td>
</tr>
<?php endif; ?>
<tr id="edd-refund-submit-total" class="edd-refund-submit-line-total">
<td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>">
<span class="row-title edd-refund-submit-line-total-name"><?php esc_html_e( 'Refund Total:', 'easy-digital-downloads' ); ?></span>
<?php
printf(
'<span class="edd-refund-submit-line-total-amount">%1$s<span id="edd-refund-submit-total-amount">%2$s</span>%3$s</span>',
$before, // phpcs:ignore
esc_attr( $amount ),
$after // phpcs:ignore
);
?>
</td>
</tr>
<?php
}
/**
* Gets the order object.
*
* @since 3.0
* @return Order|false
*/
private function get_order() {
$order_id = ! empty( $_POST['order_id'] )
? absint( $_POST['order_id'] ) // phpcs:ignore
: 0;
return ! empty( $order_id ) ? edd_get_order( $order_id ) : false;
}
}