<?php
/**
 * Admin: Visits List Table
 *
 * @package     AffiliateWP
 * @subpackage  Admin/Visits
 * @copyright   Copyright (c) 2014, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       1.0
 */

use AffWP\Admin\List_Table;

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

#[\AllowDynamicProperties]

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

	/**
	 * Default number of items to show per page
	 *
	 * @var int
	 * @since 1.0
	 */
	public $per_page = 30;

	/**
	 * Total number of visits found
	 *
	 * @var int
	 * @since 1.0
	 */
	public $total_count = 0;

	/**
	 * Get things started
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @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' => 'visit',
			'plural'   => 'visits',
		) );

		parent::__construct( $args );
	}

	/**
	 * Generates content for a single row of the visits table.
	 *
	 * @since 2.9.6
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 */
	public function single_row( $visit ) {
		/**
		 * Filters the CSS class for a single row of the visits table.
		 *
		 * @since 2.9.6
		 *
		 * @param string        $class CSS class.
		 * @param \AffWP\Visit $visit  Visit object.
		 */
		$class = apply_filters( 'affwp_visit_table_single_row_class', '', $visit );

		?>
		<tr class="<?php echo esc_attr( $class ); ?>">
			<?php $this->single_row_columns( $visit ); ?>
		</tr>
		<?php
	}

	/**
	 * Drops WP's `striped` class so rows render on a uniform white ground with
	 * hairline dividers + a hover tint, matching the other admin list tables.
	 *
	 * @since 2.33.0
	 *
	 * @return array
	 */
	protected function get_table_classes() {
		return $this->get_unstriped_table_classes();
	}

	/**
	 * Renders the visits table inside the shared scroll-shadow wrapper so it
	 * matches the other admin list tables (no zebra stripes, hairline dividers,
	 * hover tint, sticky-frame border). Visit rows are read-only, so no
	 * whole-row click affordance is added.
	 *
	 * @since 2.33.0
	 */
	public function display() {
		$this->display_scrollable_table();
	}

	/**
	 * Show the search field
	 *
	 * @access public
	 * @since 1.0
	 *
	 * @param string $text Label for the search box
	 * @param string $input_id ID of the search box
	 *
	 * @return svoid
	 */
	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
	}

	/**
	 * Retrieve the table columns
	 *
	 * @access public
	 * @since 1.0
	 * @return array $columns Array of all the list table columns
	 */
	public function get_columns() {
		$columns = array(
			'visit_id'     => __( 'Visit ID', 'affiliate-wp' ),
			'url'          => __( 'Landing Page', 'affiliate-wp' ),
			'referrer'     => __( 'Referring URL', 'affiliate-wp' ),
			'ip'           => __( 'IP', 'affiliate-wp' ),
			'converted'    => __( 'Converted', 'affiliate-wp' ),
			'referral_id'  => __( 'Referral ID', 'affiliate-wp' ),
			'affiliate'    => __( 'Affiliate', 'affiliate-wp' ),
			'date'         => __( 'Date', 'affiliate-wp' ),
		);

		/**
		 * Filters the visits list table columns.
		 *
		 * @since 1.0
		 *
		 * @param array              $prepared_columns Prepared columns.
		 * @param array               $columns          The columns for this list table.
		 * @param \AffWP_Visits_Table $this             List table instance.
		 */
		return apply_filters( 'affwp_visit_table_columns', $this->prepare_columns( $columns ), $columns, $this );
	}

	/**
	 * Retrieve the table's sortable columns
	 *
	 * @access public
	 * @since 1.0
	 * @return array Array of all the sortable columns
	 */
	public function get_sortable_columns() {
		$columns = array(
			'visit_id'  => array( 'visit_id', false ),
			'date'      => array( 'date', false ),
			'converted' => array( 'referral_id', false )
		);

		/**
		 * Filters the visits list table sortable columns.
		 *
		 * @since 1.0
		 *
		 * @param array               $columns          The sortable columns for this list table.
		 * @param \AffWP_Visits_Table $this             List table instance.
		 */
		return apply_filters( 'affwp_visit_table_sortable_columns', $columns, $this );
	}

	/**
	 * Renders most of the columns in the list table.
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @param \AffWP\Visit $visit       The current visit object.
	 * @param string       $column_name The name of the column
	 * @return string Column Name
	 */
	function column_default( $visit, $column_name ) {
		switch( $column_name ) {

			case 'date':
				$value = $visit->date_i18n( 'datetime' );
				break;

			default:
				$value = isset( $visit->$column_name ) ? $visit->$column_name : '';
				break;
		}

		/**
		 * Filters the default column value for the visits list table.
		 *
		 * The dynamic portion of the hook name, `$column_name`, refers to the column name.
		 *
		 * @since 1.0
		 *
		 * @param mixed        $value The name of the column
		 * @param \AffWP\Visit $visit All the data of the visit.
		 */
		return apply_filters( 'affwp_visit_table_' . $column_name, $value, $visit );
	}

	/**
	 * Render the affiliate column in the visits list table.
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 * @return string The affiliate
	 */
	function column_affiliate( $visit ) {
		$value = affwp_admin_link( 'visits', esc_html( affiliate_wp()->affiliates->get_affiliate_name( $visit->affiliate_id ) ), array( 'affiliate' => $visit->affiliate_id ) );

		/**
		 * Filters the affiliate column of the visits list table.
		 *
		 * @since 1.0
		 *
		 * @param string       $value The value of the affiliate column in the visits list table.
		 * @param \AffWP\Visit $visit The current visit object.
		 */
		return apply_filters( 'affwp_visit_table_affiliate', $value, $visit );
	}

	/**
	 * Renders the 'Referral ID' column in the visits list table.
	 *
	 * @access public
	 * @since  2.0.2
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 * @return string The 'Referral ID' column markup.
	 */
	function column_referral_id( $visit ) {
		if ( $visit->referral_id ) {
			$value = affwp_admin_link( 'referrals', $visit->referral_id, array(
				'action'      => 'edit_referral',
				'referral_id' => $visit->referral_id
			) );
		} else {
			$value = $visit->referral_id;
		}

		/**
		 * Filters the 'Referral ID' column of the visits list table.
		 *
		 * @since 2.0.2
		 *
		 * @param string       $value The value of the 'Referral ID' column in the visits list table.
		 * @param \AffWP\Visit $visit The current visit object.
		 */
		return apply_filters( 'affwp_visit_table_referral_id', $value, $visit );
	}

	/**
	 * Renders the 'Landing Page' column in the visits list table.
	 *
	 * The full URL is collapsed to a relative path when it points at this site (the host
	 * repeats on every row and carries no signal), truncated with an ellipsis, and the
	 * untouched URL is shown on hover. Landing pages are the merchant's own pages, so the
	 * text is not linked: the useful affordance is reading the path while scanning, not
	 * navigating to it.
	 *
	 * @access public
	 * @since  2.33.0
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 * @return string The 'Landing Page' column markup.
	 */
	function column_url( $visit ) {
		if ( empty( $visit->url ) ) {
			$value = '';
		} else {
			$value = sprintf(
				'<span class="max-w-[420px]! break-all whitespace-normal! line-clamp-2!" data-tooltip-html="%s" data-tooltip-arrow="false" data-tooltip-animation="false" data-tooltip-interactive="false">%s</span>',
				esc_attr( affwp_dark_tooltip_html( $visit->url, '280px' ) ),
				esc_html( $this->format_visit_url( $visit->url ) )
			);
		}

		/**
		 * Filters the 'Landing Page' (url) column of the visits list table.
		 *
		 * @since 1.0
		 *
		 * @param string       $value The value of the 'Landing Page' column in the visits list table.
		 * @param \AffWP\Visit $visit The current visit object.
		 */
		return apply_filters( 'affwp_visit_table_url', $value, $visit );
	}

	/**
	 * Renders the referrer column in the visits list table.
	 *
	 * Unlike the landing page, the referrer is an external URL the merchant doesn't control,
	 * so it stays a link they can open. It gets the same host/path collapse, truncation, and
	 * full-URL-on-hover treatment.
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 * @return string Referring URL.
	 */
	function column_referrer( $visit ) {
		if ( empty( $visit->referrer ) ) {
			$value = __( 'Direct traffic', 'affiliate-wp' );
		} else {
			$value = sprintf(
				'<a href="%s" target="_blank" rel="noopener noreferrer" class="max-w-[420px]! break-all whitespace-normal! line-clamp-2!" data-tooltip-html="%s" data-tooltip-arrow="false" data-tooltip-animation="false" data-tooltip-interactive="false">%s</a>',
				esc_url( $visit->referrer ),
				esc_attr( affwp_dark_tooltip_html( $visit->referrer, '280px' ) ),
				esc_html( $this->format_visit_url( $visit->referrer ) )
			);
		}

		/**
		 * Filters the referrer column of the visits list table.
		 *
		 * @since 1.0
		 *
		 * @param string       $value The value of the referrer column in the visits list table.
		 * @param \AffWP\Visit $visit Visit data.
		 */
		return apply_filters( 'affwp_visit_table_referrer', $value, $visit );
	}

	/**
	 * Collapses a URL for display: drops the scheme, and drops the host when it's this site.
	 *
	 * Own-site URLs become a relative path (`/summer-sale/?utm_campaign=jane`) since the host
	 * is identical on every row. External URLs keep their host so the source stays clear.
	 *
	 * @access private
	 * @since  2.33.0
	 *
	 * @param string $url The full URL.
	 * @return string The display string.
	 */
	private function format_visit_url( $url ) {
		$parts = wp_parse_url( $url );

		if ( false === $parts || empty( $parts['host'] ) ) {
			return $url;
		}

		$path  = isset( $parts['path'] ) ? $parts['path'] : '/';
		$query = isset( $parts['query'] ) ? '?' . $parts['query'] : '';

		$home_host = wp_parse_url( home_url(), PHP_URL_HOST );

		if ( $home_host && 0 === strcasecmp( $parts['host'], $home_host ) ) {
			return $path . $query;
		}

		return $parts['host'] . $path . $query;
	}

	/**
	 * Renders the converted column in the visits list table.
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @param \AffWP\Visit $visit The current visit object.
	 * @return string Converted status icon.
	 */
	function column_converted( $visit ) {
		$converted = ! empty( $visit->referral_id ) ? 'yes' : 'no';
		$value = '<span class="visit-converted ' . $converted . '"><i></i></span>';

		/**
		 * Filters the converted column of the visits list table.
		 *
		 * @since 1.0
		 *
		 * @param string       $value The value of the converted column in the visits list table.
		 * @param \AffWP\Visit $visit The current visit object.
		 */
		return apply_filters( 'affwp_visit_table_converted', $value, $visit );
	}

	/**
	 * Renders the message to be displayed when there are no visits.
	 *
	 * @access public
	 * @since  1.7.2
	 */
	function no_items() {
		_e( 'No visits found.', 'affiliate-wp' );
	}

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

	/**
	 * Retrieves all the data for all the affiliates.
	 *
	 * @access public
	 * @since  1.0
	 *
	 * @return array Array of all the data for the visits.
	 */
	public function visits_data() {

		$page        = isset( $_GET['paged'] )     ? absint( $_GET['paged'] )                 : 1;
		$referral_id = isset( $_GET['referral'] )  ? absint( $_GET['referral'] )              : false;
		$campaign    = isset( $_GET['campaign'] )  ? sanitize_text_field( $_GET['campaign'] ) : false;
		$context     = isset( $_GET['context'] )   ? sanitize_key( $_GET['context'] )         : false;
		$order       = isset( $_GET['order'] )     ? $_GET['order']                           : 'DESC';
		$orderby     = isset( $_GET['orderby'] )   ? $_GET['orderby']                         : 'date';
		$search      = isset( $_GET['s'] )         ? sanitize_text_field( $_GET['s'] )        : '';

		// Kept for back-compat. See $_REQUEST['user_name'].
		$user_id      = isset( $_GET['user_id'] )   ? absint( $_GET['user_id'] )               : false;
		$affiliate_id = isset( $_GET['affiliate'] ) ? absint( $_GET['affiliate'] )             : false;

		$from   = ! empty( $_REQUEST['filter_from'] )   ? $_REQUEST['filter_from']   : '';
		$to     = ! empty( $_REQUEST['filter_to'] )     ? $_REQUEST['filter_to']     : '';
		$status = ! empty( $_REQUEST['filter_status'] ) ? $_REQUEST['filter_status'] : '';

		$date = array();
		if( ! empty( $from ) ) {
			$date['start'] = $from;
		}
		if( ! empty( $to ) ) {
			$date['end'] = $to;
		}

		if ( $user_id && ! $affiliate_id ) {

			$affiliate_id = affiliate_wp()->affiliates->get_column_by( 'affiliate_id', 'user_id', $user_id );

		} elseif ( ! $user_id && ! $affiliate_id && isset( $_REQUEST['user_name'] ) ) {

			$data = affiliate_wp()->utils->process_request_data( $_REQUEST, 'user_name' );

			if( ! empty( $data['user_id'] ) ) {
				$affiliate_id = affiliate_wp()->affiliates->get_column_by( 'affiliate_id', 'user_id', $data['user_id'] );
			}

		}

		$url      = '';
		$visit_id = 0;

		if ( strpos( $search, 'referral:' ) !== false ) {
			$referral_id = absint( trim( str_replace( 'referral:', '', $search ) ) );
			$search      = '';
		} elseif ( strpos( $search, 'affiliate:' ) !== false ) {
			$affiliate_id = absint( trim( str_replace( 'affiliate:', '', $search ) ) );
			$search       = '';
		} elseif ( strpos( $search, 'campaign:' ) !== false ) {
			$campaign = trim( str_replace( 'campaign:', '', $search ) );
			$search   = '';
		} elseif ( strpos( $search, 'context:' ) !== false ) {
			$context = trim( str_replace( 'context:', '', $search ) );
			$search  = '';
		} elseif ( strpos( $search, 'url:' ) !== false ) {
			$url    = trim( str_replace( 'url:', '', $search ) );
			$url    = rtrim( $url, '/' ) . '/';
			$search = '';
		} elseif ( strpos( $search, 'visit:' ) !== false ) {
			$visit_id = absint( trim( str_replace( 'visit:', '', $search ) ) );
			$search   = '';
		}

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

		$args = wp_parse_args( $this->query_args, array(
			'number'          => $per_page,
			'offset'          => $per_page * ( $page - 1 ),
			'visit_id'        => $visit_id,
			'affiliate_id'    => $affiliate_id,
			'referral_id'     => $referral_id,
			'date'            => $date,
			'campaign'        => $campaign,
			'context'         => $context,
			'orderby'         => $orderby,
			'order'           => $order,
			'search'          => $search,
			'url'             => $url,
			'referral_status' => $status
		) );


		// Retrieve the "current" total count for pagination purposes.
		$count_args = $args;
		$count_args['number'] = -1;

		$this->total_count = affiliate_wp()->visits->count( $args );
		$this->current_count = affiliate_wp()->visits->count( $count_args );

		return affiliate_wp()->visits->get_visits( $args );

	}

	/**
	 * Setup the final data for the table
	 *
	 * @access public
	 * @since 1.0
	 * @uses AffWP_Visits_Table::get_columns()
	 * @uses AffWP_Visits_Table::get_sortable_columns()
	 * @uses AffWP_Visits_Table::process_bulk_action()
	 * @uses AffWP_Visits_Table::visits_data()
	 * @uses WP_List_Table::get_pagenum()
	 * @uses WP_List_Table::set_pagination_args()
	 * @return void
	 */
	public function prepare_items() {
		$per_page = $this->get_items_per_page( 'affwp_edit_visits_per_page', $this->per_page );

		$this->get_column_info();

		$this->process_bulk_action();

		$data = $this->visits_data();

		$current_page = $this->get_pagenum();

		$this->items = $data;

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