<?php
/**
 * Students Reporting Table
 *
 * @package LifterLMS/Admin/Reporting/Tables/Classes
 *
 * @since 3.2.0
 * @version 6.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * LLMS_Table_Students class
 *
 * @since 3.2.0
 * @since 3.28.0 Unknown.
 * @since 3.31.0 Allow filtering the table by Course or Membership
 * @since 3.36.0 Add "Last Seen" column.
 * @since 3.36.1 Fixed "Last Seen" column displaying wrong date when the student last login date was saved as timestamp.
 * @since 3.37.2 The post filter on the students table now limits post results based on instructor access.
 */
class LLMS_Table_Students extends LLMS_Admin_Table {

	/**
	 * Unique ID for the Table
	 *
	 * @var string
	 */
	protected $id = 'students';

	/**
	 * Value of the field being filtered by
	 *
	 * Only applicable if $filterby is set.
	 *
	 * @since 3.31.0
	 * @var string
	 */
	protected $filter = '';

	/**
	 * Field results are filtered by
	 *
	 * @since 3.31.0
	 * @var string
	 */
	protected $filterby = 'course_membership';

	/**
	 * Is the Table Exportable?
	 *
	 * @var boolean
	 */
	protected $is_exportable = true;

	/**
	 * Determine if the table is filterable
	 *
	 * @since 3.31.0
	 * @var boolean
	 */
	protected $is_filterable = true;

	/**
	 * If true, tfoot will add ajax pagination links
	 *
	 * @var boolean
	 */
	protected $is_paginated = true;

	/**
	 * Determine of the table is searchable
	 *
	 * @var boolean
	 */
	protected $is_searchable = true;

	/**
	 * Results sort order
	 *
	 * 'ASC' or 'DESC'
	 * Only applicable of $orderby is not set.
	 *
	 * @var string
	 */
	protected $order = 'ASC';

	/**
	 * Field results are sorted by
	 *
	 * @var string
	 */
	protected $orderby = 'name';

	/**
	 * Number of records to display per page
	 *
	 * @var int
	 */
	protected $per_page = 25;

	/**
	 * Retrieve data for the columns
	 *
	 * @since 3.2.0
	 * @since 3.15.0 Unknown.
	 * @since 3.36.0 Added "Last Seen" column.
	 * @since 3.36.1 Fixed "Last Seen" column displaying wrong date when the student last login date was saved as timestamp.
	 * @since 4.7.0 Speed up the query used to retrieve the last seen column by avoiding the found rows calculation.
	 * @since 6.0.0 Don't access `LLMS_Events_Query` properties directly
	 *              Use `LLMS_Student::get_awards_count()` for retrieving the number of earned achievements and certificates.
	 *
	 * @param string       $key     The column id / key.
	 * @param LLMS_Student $student Instance of the LLMS_Student.
	 * @return mixed
	 */
	public function get_data( $key, $student ) {

		switch ( $key ) {

			case 'achievements':
				$url   = LLMS_Admin_Reporting::get_current_tab_url(
					array(
						'stab'       => 'achievements',
						'student_id' => $student->get_id(),
					)
				);
				$value = '<a href="' . esc_url( $url ) . '">' . $student->get_awards_count( 'achievement' ) . '</a>';
				break;

			case 'certificates':
				$url   = LLMS_Admin_Reporting::get_current_tab_url(
					array(
						'stab'       => 'certificates',
						'student_id' => $student->get_id(),
					)
				);
				$value = '<a href="' . esc_url( $url ) . '">' . $student->get_awards_count( 'certificate' ) . '</a>';
				break;

			case 'completions':
				$courses = $student->get_completed_courses();
				$value   = count( $courses['results'] );
				break;

			case 'enrollments':
				$url         = LLMS_Admin_Reporting::get_current_tab_url(
					array(
						'stab'       => 'courses',
						'student_id' => $student->get_id(),
					)
				);
				$enrollments = $student->get_courses(
					array(
						'limit' => 1,
					)
				);
				$value       = '<a href="' . esc_url( $url ) . '">' . $enrollments['found'] . '</a>';
				break;

			case 'id':
				$id = $student->get_id();
				if ( current_user_can( 'list_users' ) ) {
					$value = '<a href="' . esc_url( get_edit_user_link( $id ) ) . '">' . $id . '</a>';
				} else {
					$value = $id;
				}
				break;

			case 'last_seen':
				$query = new LLMS_Events_Query(
					array(
						'actor'         => $student->get_id(),
						'per_page'      => 1,
						'sort'          => array(
							'date' => 'DESC',
						),
						'no_found_rows' => true,
					)
				);

				if ( $query->has_results() ) {
					$events = $query->get_events();
					$last   = array_shift( $events );
					$value  = $last->get( 'date' );
				} else {
					$value = $student->get( 'last_login' );
				}

				$value = $value ? date_i18n( get_option( 'date_format' ), is_numeric( $value ) ? $value : strtotime( $value ) ) : '&ndash;';

				break;

			case 'memberships':
				$url   = LLMS_Admin_Reporting::get_current_tab_url(
					array(
						'stab'       => 'memberships',
						'student_id' => $student->get_id(),
					)
				);
				$value = '<a href="' . esc_url( $url ) . '">' . count( $student->get_membership_levels() ) . '</a>';
				break;

			case 'name':
				$first = $student->get( 'first_name' );
				$last  = $student->get( 'last_name' );

				if ( ! $first || ! $last ) {
					$value = $student->get( 'display_name' );
				} else {
					$value = $last . ', ' . $first;
				}

				$url   = LLMS_Admin_Reporting::get_current_tab_url(
					array(
						'student_id' => $student->get_id(),
					)
				);
				$value = '<a href="' . esc_url( $url ) . '">' . $value . '</a>';

				break;

			case 'overall_grade':
				$value = $student->get_overall_grade( true );
				if ( is_numeric( $value ) ) {
					$value .= '%';
				}
				break;

			case 'overall_progress':
				$value = $this->get_progress_bar_html( $student->get_overall_progress( true ) );
				break;

			case 'registered':
				$value = $student->get_registration_date();
				break;

			default:
				$value = $key;

		}

		return $this->filter_get_data( $value, $key, $student );
	}

	/**
	 * Retrieve data for a cell in an export file
	 *
	 * Should be overridden in extending classes.
	 *
	 * @since 3.15.0
	 * @since 3.26.1 Unknown.
	 *
	 * @param string       $key     The column id / key.
	 * @param LLMS_Student $student Instance of the LLMS_Student.
	 * @return mixed
	 */
	public function get_export_data( $key, $student ) {

		switch ( $key ) {

			case 'id':
				$value = $student->get_id();
				break;

			case 'courses_cancelled':
			case 'courses_enrolled':
			case 'courses_expired':
				$status  = explode( '_', $key );
				$status  = array_pop( $status );
				$courses = $student->get_courses(
					array(
						'status' => $status,
					)
				);
				$titles  = array();
				foreach ( $courses['results'] as $id ) {
					$titles[] = get_the_title( $id );
				}
				$value = implode( ', ', $titles );

				break;

			case 'email':
				$value = $student->get( 'user_email' );
				break;

			case 'memberships_cancelled':
			case 'memberships_enrolled':
			case 'memberships_expired':
				$status      = explode( '_', $key );
				$status      = array_pop( $status );
				$memberships = $student->get_memberships(
					array(
						'status' => $status,
					)
				);
				$titles      = array();
				foreach ( $memberships['results'] as $id ) {
					$titles[] = get_the_title( $id );
				}
				$value = implode( ', ', $titles );

				break;

			case 'name_first':
				$value = $student->get( 'first_name' );
				break;

			case 'name_last':
				$value = $student->get( 'last_name' );
				break;

			case 'overall_grade':
				$value = $student->get_overall_grade( false );
				if ( is_numeric( $value ) ) {
					$value .= '%';
				}
				break;

			case 'overall_progress':
				$value = $student->get_overall_progress( false ) . '%';
				break;

			case 'billing_address_1':
			case 'billing_address_2':
			case 'billing_city':
			case 'billing_state':
			case 'billing_zip':
			case 'billing_country':
			case 'phone':
				$value = $student->get( $key );
				break;

			default:
				$value = $this->get_data( $key, $student );

		}

		return $this->filter_get_data( $value, $key, $student, 'export' );
	}

	/**
	 * Get the Text to be used as the placeholder in a searchable tables search input
	 *
	 * @since 3.2.0
	 * @since 3.15.0 Unknown.
	 *
	 * @return string
	 */
	public function get_table_search_form_placeholder() {
		return apply_filters( 'llms_table_get_' . $this->id . '_search_placeholder', __( 'Search students by name or email...', 'lifterlms' ) );
	}

	public function output_table_title_html() {
		parent::output_table_title_html();
		?>
		<form id="llms-clear-student-progress-cache" action="<?php echo esc_url( LLMS_Admin_Page_Status::get_url( 'tools' ) ); ?>" method="POST">
			<button class="button button-secondary" id="llms-clear-reporting-cache" name="llms_tool" type="submit" value="clear-cache"><?php echo esc_html__( 'Clear Reporting Cache', 'lifterlms' ); ?></button>
			<?php wp_nonce_field( 'llms_tool' ); ?>
			<label for="llms-clear-reporting-cache">
				<span class="screen-reader-text"><?php echo esc_html__( 'Click to refresh the data and display the most current reporting information', 'lifterlms' ); ?></span>
				<span class="tip--top-right" data-tip="<?php echo esc_html__( 'Click to refresh the data and display the most current reporting information', 'lifterlms' ); ?>">
						<i class="fa fa-question-circle"></i>
					</span>
			</label>
		</form>
		<?php
	}

	/**
	 * Get HTML for the filters displayed in the head of the table
	 *
	 * This overrides the LLMS_Admin_Table method.
	 *
	 * @since 3.31.0
	 * @since 3.37.2 Unknown.
	 *
	 * @return string
	 */
	public function output_table_filters_html() {
		$select_id     = sprintf( '%1$s-%2$s-filter', $this->id, 'course-membership' );
		$instructor_id = null;
		// Limit Course/Membership results based on instructor access.
		if ( ! current_user_can( 'view_others_lifterlms_reports' ) && current_user_can( 'view_lifterlms_reports' ) ) {
			$instructor = llms_get_instructor();
			if ( $instructor ) {
				$instructor_id = $instructor->get( 'id' );
			}
		}
		?>
		<div class="llms-table-filters">
			<div class="llms-table-filter-wrap">
				<label class="screen-reader-text" for="<?php echo esc_attr( $select_id ); ?>">
					<?php esc_html_e( 'Choose Course/Membership', 'lifterlms' ); ?>
				</label>
				<select
					data-post-type="llms_membership,course"
					class="llms-posts-select2 llms-table-filter"
					id="<?php echo esc_attr( $select_id ); ?>"
					name="course_membership"
					style="min-width:200px;max-width:500px;"
					<?php if ( $instructor_id ) : ?>
					data-instructor-id="<?php echo esc_attr( $instructor_id ); ?>"
					<?php endif; ?>
				></select>
			</div>
		</div>
		<?php
	}

	/**
	 * Retrieve an array of query arguments to pass to the LLMS_Student_Query
	 *
	 * @since 3.28.0
	 * @since 3.31.0 Added logic to setup the query args in order to allow the filtering by Course or Membership.
	 *
	 * @return array
	 */
	private function get_query_args() {

		$query_args = array(
			'page'     => $this->get_current_page(),
			'post_id'  => array(),
			'per_page' => $this->get_per_page(),
			'sort'     => $this->get_sort(),
		);

		if ( 'status' === $this->get_filterby() && 'any' !== $this->get_filter() ) {

			$query_args['statuses'] = array( $this->get_filter() );

		} elseif ( 'course_membership' === $this->get_filterby() && '' !== $this->get_filter() ) {

			$query_args['post_id']  = absint( $this->get_filter() );
			$query_args['statuses'] = 'enrolled';

		}

		if ( $this->get_search() ) {
			$query_args['search'] = $this->get_search();
		}

		return $query_args;
	}

	/**
	 * Execute a query to retrieve results from the table
	 *
	 * @since 3.2.0
	 * @since 3.28.0 Unknown.
	 * @since 6.0.0 Don't access `LLMS_Student_Query` properties directly.
	 *
	 * @param array $args Array of query args.
	 * @return void
	 */
	public function get_results( $args = array() ) {

		// Current user can't access this report.
		if ( ! current_user_can( 'view_others_lifterlms_reports' ) && ! current_user_can( 'view_lifterlms_reports' ) ) {
			return;
		}

		$this->parse_args( $args );

		$query_args = $this->get_query_args();

		if ( current_user_can( 'view_others_lifterlms_reports' ) ) {

			$query = new LLMS_Student_Query( $query_args );

		} elseif ( current_user_can( 'view_lifterlms_reports' ) ) {

			$instructor = llms_get_instructor();
			if ( ! $instructor ) {
				return;
			}
			$query = $instructor->get_students( $query_args );

		}

		$this->max_pages    = $query->get_max_pages();
		$this->is_last_page = $query->is_last_page();

		$this->tbody_data = $query->get_students();
	}

	/**
	 * Setup the array of sort arguments to pass to the LLMS_Student_Query for the table
	 *
	 * @since 3.28.0
	 *
	 * @return array
	 */
	private function get_sort() {

		$sort = array();
		switch ( $this->get_orderby() ) {

			case 'id':
				$sort = array(
					'id' => $this->get_order(),
				);
				break;

			case 'name':
				$sort = array(
					'last_name'  => $this->get_order(),
					'first_name' => 'ASC',
					'id'         => 'ASC',
				);
				break;

			case 'overall_grade':
				$sort = array(
					'overall_grade' => $this->get_order(),
					'last_name'     => 'ASC',
					'first_name'    => 'ASC',
					'id'            => 'ASC',
				);
				break;

			case 'overall_progress':
				$sort = array(
					'overall_progress' => $this->get_order(),
					'last_name'        => 'ASC',
					'first_name'       => 'ASC',
					'id'               => 'ASC',
				);
				break;

			case 'registered':
				$sort = array(
					'registered' => $this->get_order(),
					'last_name'  => 'ASC',
					'first_name' => 'ASC',
					'id'         => 'ASC',
				);
				break;

		}

		return $sort;
	}

	/**
	 * Parse arguments passed to get_results() method & setup table class variables.
	 *
	 * @since 3.28.0
	 * @since 3.31.0 Added logic to parse 'filterby' and 'filter' args when this table is filterable.
	 *
	 * @param array $args Array of arguments.
	 * @return void
	 */
	protected function parse_args( $args = array() ) {

		if ( ! $args ) {
			$args = $this->get_args();
		}

		$args = $this->clean_args( $args );

		if ( isset( $args['page'] ) ) {
			$this->current_page = absint( $args['page'] );
		}

		$this->order   = isset( $args['order'] ) ? $args['order'] : $this->get_order();
		$this->orderby = isset( $args['orderby'] ) ? $args['orderby'] : $this->get_orderby();

		$this->per_page = isset( $args['per_page'] ) ? $args['per_page'] : $this->get_per_page();

		if ( $this->is_filterable ) {
			$this->filterby = isset( $args['filterby'] ) ? $args['filterby'] : $this->get_filterby();
			$this->filter   = isset( $args['filter'] ) ? $args['filter'] : $this->get_filter();
		}

		if ( isset( $args['search'] ) ) {
			$this->search = $args['search'];
		}
	}

	/**
	 * Define the structure of arguments used to pass to the get_results method
	 *
	 * @since 2.3.0
	 * @since 3.28.0 Unknown.
	 *
	 * @return array
	 */
	public function set_args() {
		return array(
			'per_page' => apply_filters( 'llms_table_' . $this->id . '_per_page', $this->per_page ),
		);
	}

	/**
	 * Define the structure of the table
	 *
	 * @since 3.2.0
	 * @since 3.15.0 Unknown.
	 * @since 3.36.0 Add "Last Seen" column.
	 *
	 * @return array
	 */
	public function set_columns() {
		return array(
			'id'                    => array(
				'exportable' => true,
				'sortable'   => true,
				'title'      => __( 'ID', 'lifterlms' ),
			),
			'email'                 => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Email', 'lifterlms' ),
			),
			'name'                  => array(
				'sortable' => true,
				'title'    => __( 'Name', 'lifterlms' ),
			),
			'name_last'             => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Last Name', 'lifterlms' ),
			),
			'name_first'            => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'First Name', 'lifterlms' ),
			),
			'registered'            => array(
				'exportable' => true,
				'sortable'   => true,
				'title'      => __( 'Registration Date', 'lifterlms' ),
			),
			'last_seen'             => array(
				'exportable' => true,
				'sortable'   => false,
				'title'      => __( 'Last Seen', 'lifterlms' ),
			),
			'overall_progress'      => array(
				'exportable' => true,
				'sortable'   => true,
				'title'      => __( 'Progress', 'lifterlms' ),
			),
			'overall_grade'         => array(
				'exportable' => true,
				'sortable'   => true,
				'title'      => __( 'Grade', 'lifterlms' ),
			),
			'enrollments'           => array(
				'sortable' => false,
				'title'    => __( 'Enrollments', 'lifterlms' ),
			),
			'completions'           => array(
				'sortable' => false,
				'title'    => __( 'Completions', 'lifterlms' ),
			),
			'certificates'          => array(
				'sortable' => false,
				'title'    => __( 'Certificates', 'lifterlms' ),
			),
			'achievements'          => array(
				'sortable' => false,
				'title'    => __( 'Achievements', 'lifterlms' ),
			),
			'memberships'           => array(
				'sortable' => false,
				'title'    => __( 'Memberships', 'lifterlms' ),
			),
			'billing_address_1'     => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing Address 1', 'lifterlms' ),
			),
			'billing_address_2'     => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing Address 2', 'lifterlms' ),
			),
			'billing_city'          => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing City', 'lifterlms' ),
			),
			'billing_state'         => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing State', 'lifterlms' ),
			),
			'billing_zip'           => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing Zip', 'lifterlms' ),
			),
			'billing_country'       => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Billing Country', 'lifterlms' ),
			),
			'phone'                 => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Phone', 'lifterlms' ),
			),
			'courses_enrolled'      => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Courses (Enrolled)', 'lifterlms' ),
			),
			'courses_cancelled'     => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Courses (Cancelled)', 'lifterlms' ),
			),
			'courses_expired'       => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Courses (Expired)', 'lifterlms' ),
			),
			'memberships_enrolled'  => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Memberships (Enrolled)', 'lifterlms' ),
			),
			'memberships_cancelled' => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Memberships (Cancelled)', 'lifterlms' ),
			),
			'memberships_expired'   => array(
				'exportable'  => true,
				'export_only' => true,
				'title'       => __( 'Memberships (Expired)', 'lifterlms' ),
			),
		);
	}

	/**
	 * Set the table's title.
	 *
	 * @since 3.28.0
	 *
	 * @return string
	 */
	protected function set_title() {
		return __( 'Students', 'lifterlms' );
	}
}
