<?php
/**
 * 'Payouts' Admin List Table
 *
 * @package    AffiliateWP\Admin\Payouts
 * @copyright  Copyright (c) 2014, Pippin Williamson
 * @license    http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since      1.9
 */

use AffWP\Admin\List_Table;

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * AffWP_Payouts_Table Class
 *
 * Renders the Payouts table on the Payouts page
 *
 * @since 1.0
 *
 * @see \AffWP\Admin\List_Table
 */
class AffWP_Payouts_Table extends List_Table {

	/**
	 * Default number of items to show per page
	 *
	 * @access public
	 * @since  1.9
	 * @var    string
	 */
	public $per_page = 30;

	/**
	 * Total number of payouts found.
	 *
	 * @access public
	 * @since  1.9
	 * @var    int
	 */
	public $total_count;

	/**
	 * Number of 'processing' payouts found.
	 *
	 * @access public
	 * @since  2.4
	 * @var    int
	 */
	public $processing_count;

	/**
	 * Number of 'paid' payouts found.
	 *
	 * @access public
	 * @since  1.9
	 * @var    string
	 */
	public $paid_count;

	/**
	 * Number of 'failed' payouts found
	 *
	 * @access public
	 * @since  1.9
	 * @var    string
	 */
	public $failed_count;

	/**
	 * Payouts table constructor.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @see WP_List_Table::__construct()
	 *
	 * @param array $args Optional. Arbitrary display and query arguments to pass through
	 *                    the list table. Default empty array.
	 */
	public function __construct( $args = array() ) {
		$args = wp_parse_args( $args, array(
			'singular' => 'payout',
			'plural'   => 'payouts',
		) );

		parent::__construct( $args );

		$this->get_payout_counts();
	}

	/**
	 * Displays the search field.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param string $text     Label for the search box.
	 * @param string $input_id ID of the search box.
	 */
	public function search_box( $text, $input_id ) {
		if ( empty( $_REQUEST['s'] ) && ! $this->has_items() )
			return;

		$input_id = $input_id . '-search-input';

		if ( ! empty( $_REQUEST['orderby'] ) )
			echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />';
		if ( ! empty( $_REQUEST['order'] ) )
			echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />';
		?>
		<p class="search-box">
			<label class="screen-reader-text" for="<?php echo $input_id ?>"><?php echo $text; ?>:</label>
			<input type="search" id="<?php echo $input_id ?>" name="s" value="<?php _admin_search_query(); ?>" />
			<?php submit_button( $text, 'button', false, false, array( 'ID' => 'search-submit' ) ); ?>
		</p>
		<?php
	}

	/**
	 * Retrieves the payout view types.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @return array $views All the views available.
	 */
	public function get_views() {
		$base             = affwp_admin_url( 'payouts' );
		$current          = isset( $_GET['status'] ) ? $_GET['status'] : '';
		$total_count      = '&nbsp;<span class="count">(' . $this->total_count . ')</span>';
		$processing_count = '&nbsp;<span class="count">(' . $this->processing_count . ')</span>';
		$paid_count       = '&nbsp;<span class="count">(' . $this->paid_count . ')</span>';
		$failed_count     = '&nbsp;<span class="count">(' . $this->failed_count . ')</span>';

		$views = array(
			'all'        => sprintf( '<a href="%s"%s>%s</a>', esc_url( remove_query_arg( 'status', $base ) ), $current === 'all' || $current == '' ? ' class="current"' : '', _x( 'All', 'payouts', 'affiliate-wp' ) . $total_count ),
			'processing' => sprintf( '<a href="%s"%s>%s</a>', esc_url( add_query_arg( 'status', 'processing', $base ) ), $current === 'processing' ? ' class="current"' : '', __( 'Processing', 'affiliate-wp' ) . $processing_count ),
			'paid'       => sprintf( '<a href="%s"%s>%s</a>', esc_url( add_query_arg( 'status', 'paid', $base ) ), $current === 'paid' ? ' class="current"' : '', __( 'Paid', 'affiliate-wp' ) . $paid_count ),
			'failed'     => sprintf( '<a href="%s"%s>%s</a>', esc_url( add_query_arg( 'status', 'failed', $base ) ), $current === 'failed' ? ' class="current"' : '', __( 'Failed', 'affiliate-wp' ) . $failed_count ),
		);

		return $views;
	}

	/**
	 * Retrieves the payouts table columns.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @return array $columns Array of all the payouts list table columns.
	 */
	public function get_columns() {
		$columns = array(
			'cb'              => '<input type="checkbox" />',
			'payout_id'       => __( 'Payout ID', 'affiliate-wp' ),
			'amount'          => _x( 'Amount', 'payout', 'affiliate-wp' ),
			'affiliate'       => __( 'Affiliate', 'affiliate-wp' ),
			'referrals'       => __( 'Referrals', 'affiliate-wp' ),
			'owner'           => __( 'Generated By', 'affiliate-wp' ),
			'payout_method'   => __( 'Payout Method', 'affiliate-wp' ),
			'service_account' => __( 'Payout Account', 'affiliate-wp' ),
			'status'          => _x( 'Status', 'payout', 'affiliate-wp' ),
			'date'            => _x( 'Date', 'payout', 'affiliate-wp' ),
			'actions'         => __( 'Actions', 'affiliate-wp' ),
		);

		if ( ! affiliate_wp()->settings->get( 'enable_payouts_service' ) ) {
			unset( $columns['service_account'] );
		}

		/**
		 * Filters the payouts list table columns.
		 *
		 * @since 1.9
		 *
		 * @param array $columns List table columns.
		 */
		return apply_filters( 'affwp_payout_table_columns', $this->prepare_columns( $columns ) );
	}

	/**
	 * Retrieves the payouts table's sortable columns.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @return array Array of all the sortable columns
	 */
	public function get_sortable_columns() {
		$columns = array(
			'payout_id'     => array( 'payout_id', false ),
			'amount'        => array( 'amount', false ),
			'affiliate'     => array( 'affiliate', false ),
			'payout_method' => array( 'payout_method', false ),
			'status'        => array( 'status', false ),
			'date'          => array( 'date', false ),
		);

		/**
		 * Filters the payouts list table sortable columns.
		 *
		 * @since 1.9
		 *
		 * @param array                $columns          The sortable columns for this list table.
		 * @param \AffWP_Payouts_Table $this             List table instance.
		 */
		return apply_filters( 'affwp_payout_table_sortable_columns', $columns, $this );
	}

	/**
	 * Renders the checkbox column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Checkbox markup.
	 */
	function column_cb( $payout ) {
		return '<input type="checkbox" name="payout_id[]" value="' . absint( $payout->ID ) . '" />';
	}

	/**
	 * Renders the 'Payout ID' column
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Payout ID.
	 */
	public function column_payout_id( $payout ) {
		$value = esc_html( $payout->ID );

		/**
		 * Filters the value of the 'Payout ID' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param int                     $value  Payout ID.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_payout_id', $value, $payout );
	}

	/**
	 * Renders the 'Amount' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Payout ID.
	 */
	public function column_amount( $payout ) {
		$value = affwp_currency_filter( affwp_format_amount( $payout->amount ) );

		/**
		 * Filters the value of the 'Amount' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param string                  $$value Formatted payout amount.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_amount', $value, $payout );
	}

	/**
	 * Renders the 'Affiliate' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Linked affiliate name and ID.
	 */
	function column_affiliate( $payout ) {
		$url = affwp_admin_url( 'affiliates', array(
			'action'       => 'view_affiliate',
			'affiliate_id' => $payout->affiliate_id
		) );

		$name      = affiliate_wp()->affiliates->get_affiliate_name( $payout->affiliate_id );
		$affiliate = affwp_get_affiliate( $payout->affiliate_id );

		if ( $affiliate && $name ) {
			$value = sprintf( '<a href="%1$s">%2$s</a> (ID: %3$s)',
				esc_url( $url ),
				esc_html( $name ),
				esc_html( $affiliate->ID )
			);
		} else {
			$value = __( '(user deleted)', 'affiliate-wp' );
		}

		/**
		 * Filters the value of the 'Affiliate' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param mixed                   $value  Column value.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_affiliate', $value, $payout );
	}

	/**
	 * Renders the 'Referrals' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Linked affiliate name and ID.
	 */
	public function column_referrals( $payout ) {
		$referrals = affiliate_wp()->affiliates->payouts->get_referral_ids( $payout );
		$links     = array();

		foreach ( $referrals as $referral_id ) {
			$links[] = affwp_admin_link( 'referrals', esc_html( $referral_id ), array( 'action' => 'edit_referral', 'referral_id' => $referral_id ) );
		}

		$value = implode( ', ', $links );

		/**
		 * Filters the value of the 'Referrals' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param mixed                   $value  Column value.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_referrals', $value, $payout );
	}

	/**
	 * Renders the 'Generated By' column.
	 *
	 * @access public
	 * @since  1.9.5
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 */
	public function column_owner( $payout ) {

		// Handle for back-compat first (no owners pre-1.9.5).
		if ( 0 === $payout->owner ) {
			$value = _x( '(none)', 'payout owner', 'affiliate-wp' );
		} else {
			$user = get_user_by( 'id', $payout->owner );
			// If the owner exists, use it.
			if ( $user ) {
				$value = sprintf( '%1$s %2$s',
					affwp_admin_link( 'payouts', esc_html( $user->data->display_name ), array( 'owner' => $payout->owner ) ),
					sprintf( _x( '(User ID: %d)', 'payout owner ID', 'affiliate-wp' ),
						esc_html( $payout->owner )
					)
				);
			} else {
				// Otherwise the owner doesn't exist.
				$value = __( '(user deleted)', 'affiliate-wp' );
			}
		}

		/**
		 * Filters the value of the 'Generated By' column in the payouts list table.
		 *
		 * @since 1.9.5
		 *
		 * @param string                  $value  Payout owner display name.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_owner', $value, $payout );
	}

	/**
	 * Renders the 'Payout Method' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Payout method.
	 */
	public function column_payout_method( $payout ) {
		$value = empty( $payout->payout_method ) ? __( '(none)', 'affiliate-wp' ) : esc_html( $payout->payout_method );

		/**
		 * Filters the value of the 'Payout Method' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param string                  $value  Payout method.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_payout_method', $value, $payout );
	}

	/**
	 * Renders the 'Payout Account' column.
	 *
	 * @access public
	 * @since  2.4
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Payout account.
	 */
	public function column_service_account( $payout ) {
		$value = empty( $payout->service_account ) ? _x( '(none)', 'No payout account', 'affiliate-wp' ) : esc_html( $payout->service_account );

		/**
		 * Filters the value of the 'Payout Account' column in the payouts list table.
		 *
		 * @since 2.4
		 *
		 * @param string                  $value  Payout account.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_service_account', $value, $payout );
	}

	/**
	 * Renders the 'Date' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Localized payout date.
	 */
	public function column_date( $payout ) {
		$value = $payout->date_i18n();

		/**
		 * Filters the value of the 'Date' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param string                  $value  Localized payout date.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_date', $value, $payout );
	}

	/**
	 * Renders the 'Actions' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @see WP_List_Table::row_actions()
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Action links markup.
	 */
	function column_actions( $payout ) {

		$base_query_args = array(
			'page'      => 'affiliate-wp-payouts',
			'payout_id' => $payout->ID
		);

		// View.
		$row_actions['view'] = $this->get_row_action_link(
			__( 'View', 'affiliate-wp' ),
			array_merge( $base_query_args, array(
				'action'       => 'view_payout',
				'affwp_notice' => false,
			) )
		);

		if ( strtolower( $payout->status ) == 'failed' ) {
			// Retry Payment.
			$row_actions['retry'] = $this->get_row_action_link(
				__( 'Retry Payment', 'affiliate-wp' ),
				array_merge( $base_query_args, array(
					'affwp_notice' => 'payout_retried',
					'action'       => 'retry_payment',
				) ),
				'payout-nonce'
			);
		}

		// Delete.
		$row_actions['delete'] = $this->get_row_action_link(
			__( 'Delete', 'affiliate-wp' ),
			array_merge( $base_query_args, array(
				'affwp_action' => 'process_delete_payout'
			) ),
			array(
				'nonce' => 'affwp_delete_payout_nonce',
				'class' => 'delete'
			)
		);
		$row_actions['delete'] = '<span class="trash">' . $row_actions['delete'] . '</span>';

		/**
		 * Filters the row actions for the payouts list table row.
		 *
		 * @since 1.9
		 *
		 * @param array                   $row_actions Row actions markup.
		 * @param \AffWP\Affiliate\Payout $payout      Current payout object.
		 */
		$row_actions = apply_filters( 'affwp_payout_row_actions', $row_actions, $payout );

		return $this->row_actions( $row_actions, true );
	}

	/**
	 * Renders the 'Status' column.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout Current payout object.
	 * @return string Payout status.
	 */
	public function column_status( $payout ) {
		$value = sprintf( '<span class="affwp-status %1$s"><i></i>%2$s</span>',
			esc_attr( $payout->status ),
			affwp_get_payout_status_label( $payout )
		);

		/**
		 * Filters the value of the 'Status' column in the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param string                  $value  Payout status.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_referral_table_status', $value, $payout );
	}

	/**
	 * Renders the default output for a custom column in the payouts list table.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @param \AffWP\Affiliate\Payout $payout      Current payout object.
	 * @param string                  $column_name The name of the column.
	 * @return string Column name.
	 */
	function column_default( $payout, $column_name ) {
		$value = isset( $payout->$column_name ) ? $payout->$column_name : '';

		/**
		 * Filters the value of the default column in the payouts list table.
		 *
		 * The dynamic portion of the hook name, `$column_name`, refers to the column name.
		 *
		 * @since 1.9
		 *
		 * @param mixed                   $value  Column value.
		 * @param \AffWP\Affiliate\Payout $payout Current payout object.
		 */
		return apply_filters( 'affwp_payout_table_' . $column_name, $value, $payout );
	}

	/**
	 * Message to be displayed when there are no items.
	 *
	 * @access public
	 * @since  1.9
	 */
	function no_items() {
		_e( 'No payouts found.', 'affiliate-wp' );
	}

	/**
	 * Retrieves the bulk actions.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @return array $actions Array of the bulk actions.
	 */
	public function get_bulk_actions() {
		$actions = array(
			'retry_payment' => __( 'Retry Payment', 'affiliate-wp' ),
			'delete'        => __( 'Delete', 'affiliate-wp' ),
		);

		/**
		 * Filters the list of bulk actions for the payouts list table.
		 *
		 * @since 1.9
		 *
		 * @param array $actions Bulk actions.
		 */
		return apply_filters( 'affwp_payout_bulk_actions', $actions );
	}

	/**
	 * Processes the bulk actions.
	 *
	 * @access public
	 * @since  1.9
	 */
	public function process_bulk_action() {

		if( empty( $_REQUEST['_wpnonce'] ) ) {
			return;
		}

		if( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-payouts' ) && ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'payout-nonce' ) ) {
			return;
		}

		$ids = isset( $_GET['payout_id'] ) ? $_GET['payout_id'] : array();

		if ( ! is_array( $ids ) ) {
			$ids = array( $ids );
		}

		$ids    = array_map( 'absint', $ids );
		$action = ! empty( $_REQUEST['action'] ) ? $_REQUEST['action'] : false;

		if( empty( $ids ) || empty( $action ) ) {
			return;
		}

		foreach ( $ids as $id ) {

			if ( 'delete' === $this->current_action() ) {
				affwp_delete_payout( $id );
			}

			/**
			 * Fires after a payout bulk action is performed.
			 *
			 * The dynamic portion of the hook name, `$this->current_action()` refers
			 * to the current bulk action being performed.
			 *
			 * @since 1.9
			 *
			 * @param int $id The ID of the object.
			 */
			do_action( 'affwp_payouts_do_bulk_action_' . $this->current_action(), $id );
		}

	}

	/**
	 * Retrieves the payout counts.
	 *
	 * @access public
	 * @since  1.9
	 */
	public function get_payout_counts() {
		$this->processing_count = affiliate_wp()->affiliates->payouts->count(
			array_merge( $this->query_args, array( 'status' => 'processing' ) )
		);

		$this->paid_count = affiliate_wp()->affiliates->payouts->count(
			array_merge( $this->query_args, array( 'status' => 'paid' ) )
		);

		$this->failed_count = affiliate_wp()->affiliates->payouts->count(
			array_merge( $this->query_args, array( 'status' => 'failed' ) )
		);

		$this->total_count  = $this->processing_count + $this->paid_count + $this->failed_count;
	}

	/**
	 * Retrieves all the data for all the payouts.
	 *
	 * @access public
	 * @since  1.9
	 *
	 * @return array Array of all the data for the payouts.
	 */
	public function payouts_data() {

		$page    = isset( $_GET['paged'] )   ? absint( $_GET['paged'] )         :           1;
		$owner   = isset( $_GET['owner'] )   ? absint( $_GET['owner'] )         :           0;
		$status  = isset( $_GET['status'] )  ? sanitize_key( $_GET['status'] )  :          '';
		$order   = isset( $_GET['order'] )   ? sanitize_key( $_GET['order'] )   :      'DESC';
		$orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'payout_id';

		$is_search = false;

		if ( isset( $_GET['payout_id'] ) ) {
			$payout_ids = sanitize_text_field( $_GET['payout_id'] );
		} else {
			$payout_ids = 0;
		}

		if ( isset( $_GET['affiliate_id'] ) ) {
			$affiliates = sanitize_text_field( $_GET['affiliate_id'] );
		} else {
			$affiliates = 0;
		}

		if ( isset( $_GET['referrals'] ) ) {
			$referrals = sanitize_text_field( $_GET['referrals'] );
		} else {
			$referrals = array();
		}

		if( ! empty( $_GET['s'] ) ) {

			$is_search = true;

			$search = sanitize_text_field( $_GET['s'] );

			if ( is_numeric( $search ) || preg_match( '/^([0-9]+\,[0-9]+)/', $search, $matches ) ) {
				// Searching for specific payouts.
				if ( ! empty( $matches[0] ) ) {
					$is_search  = false;
					$payout_ids = array_map( 'absint', explode( ',', $search ) );
				} else {
					$payout_ids = absint( $search );
				}
			} elseif ( strpos( $search, 'referrals:' ) !== false ) {
				$referrals = trim( str_replace( array( ' ', 'referrals:' ), '', $search ) );
				if ( false !== strpos( $referrals, ',' ) ) {
					$is_search = false;
					$referrals = array_map( 'absint', explode( ',', $referrals ) );
				} else {
					$referrals = absint( $referrals );
				}
			} elseif ( strpos( $search, 'affiliate:' ) !== false ) {
				$affiliates = trim( str_replace( array( ' ', 'affiliate:' ), '', $search ) );
				if ( false !== strpos( $affiliates, ',' ) ) {
					$is_search  = false;
					$affiliates = array_map( 'absint', explode( ',', $affiliates ) );
				} else {
					$affiliates = absint( $affiliates );
				}
			}

		}

		$per_page = $this->get_items_per_page( 'affwp_edit_payouts_per_page', $this->per_page );

		$args = wp_parse_args( $this->query_args, array(
			'number'       => $per_page,
			'offset'       => $per_page * ( $page - 1 ),
			'payout_id'    => $payout_ids,
			'referrals'    => $referrals,
			'affiliate_id' => $affiliates,
			'owner'        => $owner,
			'status'       => $status,
			'search'       => $is_search,
			'orderby'      => $orderby,
			'order'        => $order
		) );

		$payouts = affiliate_wp()->affiliates->payouts->get_payouts( $args );

		// Retrieve the "current" total count for pagination purposes.
		$args['number']      = -1;
		$this->current_count = affiliate_wp()->affiliates->payouts->count( $args );

		return $payouts;
	}

	/**
	 * Sets up the final data for the payouts list table.
	 *
	 * @access public
	 * @since  1.9
	 */
	public function prepare_items() {
		$per_page = $this->get_items_per_page( 'affwp_edit_payouts_per_page', $this->per_page );

		$this->get_column_info();

		$this->process_bulk_action();

		$data = $this->payouts_data();

		$current_page = $this->get_pagenum();

		$status = isset( $_GET['status'] ) ? $_GET['status'] : 'any';

		switch( $status ) {
			case 'processing':
				$total_items = $this->processing_count;
				break;
			case 'paid':
				$total_items = $this->paid_count;
				break;
			case 'failed':
				$total_items = $this->failed_count;
				break;
			case 'any':
				$total_items = $this->current_count;
				break;
		}

		$this->items = $data;

		$this->set_pagination_args( array(
			'total_items' => $total_items,
			'per_page'    => $per_page,
			'total_pages' => ceil( $total_items / $per_page )
		) );
	}
}
