<?php

add_action( 'plugins_loaded', function() {
	global $wpdb;
	$wpdb->sessions = $wpdb->base_prefix . 'sessions';
	/*
	CREATE TABLE `wp_sessions` (
		`verifier` char(64) NOT NULL,
		`user_id` bigint(20) NOT NULL,
		`ua` varchar(400) NOT NULL,
		`ip` varchar(45) NOT NULL,
		`expiration` int(10) NOT NULL,
		`start` int(10) NOT NULL,
		PRIMARY KEY (`verifier`),
		KEY `user_id` (`user_id`)
	)
	*/

	add_filter( 'attach_session_information', function( $info ) {
		$info['ip'] = $_SERVER['REMOTE_ADDR'];
		$info['ua'] = $_SERVER['HTTP_USER_AGENT'];
		$info['start'] = time();
		return $info;
	});

	add_filter( 'session_token_manager', function() {
		return 'Global_Table_Session_Tokens';
	});
});

/**
 * Class for managing a user's session tokens.
 */
class Global_Table_Session_Tokens extends WP_Session_Tokens {

	/**
	 * Get all sessions of a user.
	 *
	 * @since 4.0.0
	 *
	 * @return array
	 */
	public function get_sessions() {
		global $wpdb;
		$_sessions = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->sessions WHERE user_id = %d", $this->user_id ), ARRAY_A );
		if ( ! is_array( $_sessions ) ) {
			return array();
		}

		$sessions = array();
		foreach ( $_sessions as $session ) {
			$verifier = $session['verifier'];
			unset( $session['user_id'], $session['verifier'] );
			$sessions[ $verifier ] = $session;
		}

		return $sessions;
	}

	/**
	 * Update a user's sessions.
	 *
	 * @since 4.0.0
	 *
	 * @param array $sessions
	 */
	protected function update_sessions( $sessions ) {
		global $wpdb;
		if ( ! $sessions ) {
			$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->sessions WHERE user_id = %d", $this->user_id ) );
			return;
		}

		$current_sessions = $this->get_sessions();

 		// Find all sessions not yet in the DB that need to be added.
		$new_sessions = array_diff_key( $sessions, $current_sessions );

		// This class doesn't implement session updates (like "last logged in").
		// $changed_sessions = array_diff_assoc( ... )

		// Find all sessions we need to delete, either due to destruction or expiration.
		// Alternatively, we could set an expired flag, and keep it for auditing purposes.
		$to_delete = array();
		$time = time();
		foreach ( $current_sessions as $current_verifier => $current_session ) {
			if ( ! isset( $sessions[ $current_verifier ] ) ) {
				$to_delete[] = $current_verifier;
			} elseif ( $current_session['expiration'] < $time ) {
				$to_delete[] = $current_verifier;
			}
		}

		$to_delete = "'" . implode( "', '", esc_sql( $to_delete ) ) . "'";
		$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->sessions WHERE user_id = %d AND verifier IN ($to_delete)", $this->user_id ) );

		foreach ( $new_sessions as $verifier => $new_session ) {
			$new_session['verifier'] = $verifier;
			$new_session['user_id'] = $this->user_id;
			$wpdb->insert( $wpdb->sessions, $new_session );
		}
	}
}
