<?php
/**
* File containing the class Sensei_Enrolment_Provider_Journal_Store.
*
* @package sensei
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* This class is responsible for storing provider metadata like logs and status history.
*/
class Sensei_Enrolment_Provider_Journal_Store implements JsonSerializable {
const META_ENROLMENT_PROVIDERS_JOURNAL = 'sensei_enrolment_providers_journal';
/**
* Flag for if a state the store has changed.
*
* @var bool
*/
private $has_changed = false;
/**
* Journal objects for each course and provider. The format of the array is the following:
* [
* $course_id => [
* $provider_id => Sensei_Enrolment_Provider_Journal
* ]
* ]
*
* @var Sensei_Enrolment_Provider_Journal[][]
*/
private $providers_journal;
/**
* User ID that this store is used for.
*
* @var int
*/
private $user_id;
/**
* Keeps track of instances of this class.
*
* @var self[]
*/
private static $instances = [];
/**
* Class constructor.
*
* @param int $user_id User ID.
*/
private function __construct( $user_id ) {
$this->user_id = $user_id;
$this->providers_journal = [];
}
/**
* Get a journal store record for a user/course.
*
* @param int $user_id User ID.
*
* @return self
*/
private static function get( $user_id ) {
if ( ! isset( self::$instances[ $user_id ] ) ) {
self::$instances[ $user_id ] = new self( $user_id );
$provider_journal_stores = get_user_meta( $user_id, self::get_provider_journal_store_meta_key(), true );
if ( ! empty( $provider_journal_stores ) ) {
self::$instances[ $user_id ]->restore_from_json( $provider_journal_stores );
}
}
return self::$instances[ $user_id ];
}
/**
* Restore a provider journal store from a serialized JSON string.
*
* @param string $json_string JSON representation of enrolment state.
*/
private function restore_from_json( $json_string ) {
$json_arr = json_decode( $json_string, true );
if ( ! $json_arr ) {
return;
}
foreach ( $json_arr as $course_id => $course_journal ) {
foreach ( $course_journal as $provider_id => $provider_journal_data ) {
$provider_journal = Sensei_Enrolment_Provider_Journal::from_serialized_array( $provider_journal_data );
if ( $provider_journal ) {
$this->providers_journal[ $course_id ][ $provider_id ] = $provider_journal;
}
}
}
}
#[\ReturnTypeWillChange]
/**
* Return object that can be serialized by `json_encode()`.
*
* @return array
*/
public function jsonSerialize() {
return $this->providers_journal;
}
/**
* Persist this store. If the store isn't changed, this method has no effect.
*
* @return bool
*/
public function save() {
/**
* Enables journal storage for Sensei.
*
* @since 3.0.0
*
* @hook sensei_enable_enrolment_provider_journal
*
* @param {bool} $enable_journal True to enable.
* @return {bool} Filtered value.
*/
if ( ! apply_filters( 'sensei_enable_enrolment_provider_journal', false ) ) {
return false;
}
if ( ! $this->has_changed ) {
return true;
}
$result = update_user_meta( $this->user_id, self::get_provider_journal_store_meta_key(), wp_slash( wp_json_encode( $this ) ) );
if ( ! $result || is_wp_error( $result ) ) {
return false;
}
$this->has_changed = false;
return true;
}
/**
* Save all stores that need it.
*
* As this isn't a singleton, Sensei_Course_Enrolment_Manager hooks this into `shutdown` in its `init` method.
*/
public static function persist_all() {
foreach ( self::$instances as $user_id => $instance ) {
$instance->save();
}
}
/**
* Register a possible update in the course enrolment status of a provider for a user. If there was no actual change
* on the status, this method has no effect. Changes start being registered only after a user is enrolled.
*
* @param Sensei_Course_Enrolment_Provider_Results $enrolment_results The enrolment results.
* @param int $user_id The user which the change applies to.
* @param int $course_id The course which the change applies to.
*/
public static function register_possible_enrolment_change( $enrolment_results, $user_id, $course_id ) {
$journal_store = self::get( $user_id );
// Register the status of the user for the first time only if he is enrolled.
if ( ! isset( $journal_store->providers_journal[ $course_id ] ) && ! $enrolment_results->is_enrolment_provided() ) {
return;
}
// Loop through all providers to update any changes in status.
$has_changed = false;
foreach ( $enrolment_results->get_provider_results() as $provider_id => $is_enrolled ) {
if ( ! isset( $journal_store->providers_journal[ $course_id ][ $provider_id ] ) ) {
$journal_store->providers_journal[ $course_id ][ $provider_id ] = Sensei_Enrolment_Provider_Journal::create();
}
$has_changed = $journal_store->providers_journal[ $course_id ][ $provider_id ]->update_enrolment_status( $is_enrolled ) || $has_changed;
}
// Mark any removed providers as deleted in enrolment history.
$current_snapshot = self::get_enrolment_snanpshot( $user_id, $course_id );
$removed_providers = array_diff( array_keys( $current_snapshot ), array_keys( $enrolment_results->get_provider_results() ) );
foreach ( $removed_providers as $removed_provider ) {
$has_changed = $journal_store->providers_journal[ $course_id ][ $removed_provider ]->delete_enrolment_status() || $has_changed;
}
$journal_store->has_changed = $has_changed || $journal_store->has_changed;
}
/**
* Get a snapshot of the providers' enrolment status for a user and course at a specific timestamp. If no timestamp
* is provided the current snapshot is returned.
*
* @param int $user_id The user to return the snapshot for.
* @param int $course_id The course to return the snapshot for.
* @param int|float $timestamp The timestamp of the snapshot. If omitted the current snapshot is returned.
*
* @return array
*/
public static function get_enrolment_snanpshot( $user_id, $course_id, $timestamp = null ) {
$timestamp = null === $timestamp ? microtime( true ) : (float) $timestamp;
$journal_store = self::get( $user_id );
$snapshot = [];
if ( ! isset( $journal_store->providers_journal[ $course_id ] ) ) {
return $snapshot;
}
foreach ( $journal_store->providers_journal[ $course_id ] as $provider_id => $journal ) {
$status = $journal->get_status_at( $timestamp );
if ( null !== $status['enrolment_status'] ) {
$snapshot[ $provider_id ] = $status['enrolment_status'];
}
}
return $snapshot;
}
/**
* Get the enrolment status history of a provider for a specified user and course.
*
* @param Sensei_Course_Enrolment_Provider_Interface $provider The provider.
* @param int $user_id The user id.
* @param int $course_id The course id.
*
* @return array The history of the provider. Each element of the array has the format:
* [ 'timestamp' => Timestamp of the status change, 'enrolment_status' => true|false|null ]
*/
public static function get_provider_history( Sensei_Course_Enrolment_Provider_Interface $provider, $user_id, $course_id ) {
$journal_store = self::get( $user_id );
return isset( $journal_store->providers_journal[ $course_id ][ $provider->get_id() ] ) ?
$journal_store->providers_journal[ $course_id ][ $provider->get_id() ]->get_history() :
[];
}
/**
* Add a log message to a provider.
*
* @param Sensei_Course_Enrolment_Provider_Interface $provider The provider.
* @param int $user_id The user id.
* @param int $course_id The course id.
* @param string $message The message to be added.
*/
public static function add_provider_log_message( Sensei_Course_Enrolment_Provider_Interface $provider, $user_id, $course_id, $message ) {
$journal_store = self::get( $user_id );
if ( ! isset( $journal_store->providers_journal[ $course_id ][ $provider->get_id() ] ) ) {
$journal_store->providers_journal[ $course_id ][ $provider->get_id() ] = Sensei_Enrolment_Provider_Journal::create();
}
$journal_store->providers_journal[ $course_id ][ $provider->get_id() ]->add_log_message( $message );
$journal_store->has_changed = true;
}
/**
* Get the log messages of a provider.
*
* @param Sensei_Course_Enrolment_Provider_Interface $provider The provider.
* @param int $user_id The user id.
* @param int $course_id The course id.
*
* @return array The message log of the provider. Each element of the array has the format:
* [ 'timestamp' => Timestamp of the message, 'message' => The actual message ]
*/
public static function get_provider_logs( Sensei_Course_Enrolment_Provider_Interface $provider, $user_id, $course_id ) {
$journal_store = self::get( $user_id );
return isset( $journal_store->providers_journal[ $course_id ][ $provider->get_id() ] ) ?
$journal_store->providers_journal[ $course_id ][ $provider->get_id() ]->get_logs() :
[];
}
/**
* Get the provider journal store meta key.
*
* @return string
*/
public static function get_provider_journal_store_meta_key() {
global $wpdb;
return $wpdb->get_blog_prefix() . self::META_ENROLMENT_PROVIDERS_JOURNAL;
}
}