<?php
/**
* File containing the class Sensei_Learners_Admin_Bulk_Actions_Controller.
*
* @package sensei
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
} // Exit if accessed directly
/**
* This class handles the bulk learner actions in learner management.
*/
class Sensei_Learners_Admin_Bulk_Actions_Controller {
const NONCE_SENSEI_BULK_LEARNER_ACTIONS = 'sensei-bulk-learner-actions';
const SENSEI_BULK_LEARNER_ACTIONS_NONCE_FIELD = '_sensei_bulk_learner_actions_field';
const ENROL_RESTORE_ENROLMENT = 'enrol_restore_enrolment';
const REMOVE_ENROLMENT = 'remove_enrolment';
const REMOVE_PROGRESS = 'remove_progress';
/**
* The page slug.
*
* @var string
*/
private $page_slug;
/**
* The page view.
*
* @var string
*/
private $view;
/**
* The Sensei_Learner_Management object.
*
* @var Sensei_Learner_Management
*/
private $learner_management;
/**
* The Sensei_Learner object with utility functions.
*
* @var Sensei_Learner
*/
private $learner;
/**
* Get the name of the page.
*
* @return string|void
*/
public function get_name() {
return __( 'Bulk Student Actions', 'sensei-lms' );
}
/**
* Get the page slug.
*
* @return string
*/
public function get_page_slug() {
return $this->page_slug;
}
/**
* This method returns an empty array and it exists only for backwards compatibility.
*
* @deprecated 3.0.0
* @return array Empty array
*/
public function get_query_args() {
_deprecated_function( __METHOD__, '3.0.0' );
return [];
}
/**
* This method adds a 'Learner Admin' submenu page in Sensei. If any content is generated for this page elsewhere,
* you should move this call beside it.
*
* @deprecated 3.0.0
*/
public function learners_admin_menu() {
_deprecated_function( __METHOD__, '3.0.0' );
if ( current_user_can( 'manage_sensei_grades' ) ) {
add_submenu_page( 'sensei', __( 'Student Admin', 'sensei-lms' ), __( 'Student Admin', 'sensei-lms' ), 'manage_sensei_grades', 'sensei_learner_admin', array( $this, 'learner_admin_page' ) );
}
}
/**
* Sensei_Learners_Admin_Main constructor.
*
* @param Sensei_Learner_Management $management The learner managemnt object.
* @param Sensei_Learner $learner The learner object with utility functions.
*/
public function __construct( $management, $learner ) {
$this->learner_management = $management;
$this->learner = $learner;
$this->page_slug = $management->page_slug;
$this->view = 'sensei_learner_admin';
if ( is_admin() ) {
$this->register_hooks();
}
}
/**
* Redirects to the bulk learner management screen and displays a message.
*
* @param string $result The result code or an error message to be displayed.
*
* @access private
*/
public function redirect_to_learner_admin_index( $result ) {
$url = add_query_arg(
array(
'page' => $this->get_page_slug(),
'view' => $this->get_view(),
'message' => $result,
),
admin_url( 'admin.php' )
);
wp_safe_redirect( $url );
exit;
}
/**
* Get the url of the bulk learner management screen.
*
* @return string
*/
public function get_url() {
return add_query_arg(
[
'page' => $this->get_page_slug(),
'view' => $this->get_view(),
],
admin_url( 'admin.php' )
);
}
/**
* Get the url of the learner management screen of a course.
*
* @param integer $course_id The course id.
* @return string
*/
public function get_learner_management_course_url( $course_id ) {
return add_query_arg(
[
'page' => 'sensei_learners',
'course_id' => absint( $course_id ),
'view' => 'learners',
],
admin_url( 'admin.php' )
);
}
/**
* Get the supported bulk actions.
*
* @return array
*/
public function get_known_bulk_actions() {
$known_bulk_actions = [
self::ENROL_RESTORE_ENROLMENT => __( 'Add to Course', 'sensei-lms' ),
self::REMOVE_ENROLMENT => __( 'Remove from Course', 'sensei-lms' ),
self::REMOVE_PROGRESS => __( 'Reset Progress', 'sensei-lms' ),
];
/**
* Filter the known bulk actions.
*
* @hook sensei_learners_admin_get_known_bulk_actions
*
* @param {array} $known_bulk_actions The known bulk actions.
* @return {array} Filtered known bulk actions.
*/
return (array) apply_filters( 'sensei_learners_admin_get_known_bulk_actions', $known_bulk_actions );
}
/**
* Handles the bulk action POST request. Required arguments are:
*
* 'sensei_bulk_action' The action to perform.
* 'bulk_action_user_ids' The users to perform the action on.
* 'bulk_action_course_ids and' The courses which the action is aimed on.
*
* @access private
*/
public function handle_http_post() {
if ( ! $this->is_current_page() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification -- Nonce checked in check_nonce below.
if ( ! isset( $_POST['sensei_bulk_action'], $_POST['bulk_action_course_ids'], $_POST['bulk_action_user_ids'] ) ) {
return;
}
$this->check_nonce();
// phpcs:ignore WordPress.Security.NonceVerification -- Nonce checked in check_nonce
$sensei_bulk_action = sanitize_text_field( wp_unslash( $_POST['sensei_bulk_action'] ) );
$course_ids = explode( ',', sanitize_text_field( wp_unslash( $_POST['bulk_action_course_ids'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
$user_ids = array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $_POST['bulk_action_user_ids'] ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
if ( ! array_key_exists( $sensei_bulk_action, $this->get_known_bulk_actions() ) ) {
$this->redirect_to_learner_admin_index( 'error-invalid-action' );
return;
}
$edit_course_cap = get_post_type_object( 'course' )->cap->edit_post;
foreach ( $course_ids as $course_id ) {
// Validate courses before continuing.
$course = get_post( absint( $course_id ) );
if ( empty( $course ) || ! current_user_can( $edit_course_cap, $course_id ) ) {
$this->redirect_to_learner_admin_index( 'error-invalid-course' );
return;
}
}
foreach ( $user_ids as $user_id ) {
$user = new WP_User( $user_id );
if ( $user->exists() ) {
foreach ( $course_ids as $course_id ) {
$this->do_user_action( $user_id, (int) $course_id, $sensei_bulk_action );
}
}
}
$this->redirect_to_learner_admin_index( 'action-success' );
}
/**
* Wraps global method to facilitate mocking.
*
* @access private
*/
public function check_nonce() {
check_admin_referer( self::NONCE_SENSEI_BULK_LEARNER_ACTIONS, self::SENSEI_BULK_LEARNER_ACTIONS_NONCE_FIELD );
}
/**
* Helper method to perform the action on a user and course.
*
* @param integer $user_id The user to perform the action on.
* @param integer $course_id The course which the action relates to.
* @param string $action The action.
*/
private function do_user_action( $user_id, $course_id, $action ) {
switch ( $action ) {
case self::ENROL_RESTORE_ENROLMENT:
$course_enrolment = Sensei_Course_Enrolment::get_course_instance( $course_id );
$course_enrolment->enrol( $user_id );
break;
case self::REMOVE_ENROLMENT:
$course_enrolment = Sensei_Course_Enrolment::get_course_instance( $course_id );
$course_enrolment->withdraw( $user_id );
break;
case self::REMOVE_PROGRESS:
if ( Sensei_Utils::has_started_course( $course_id, $user_id ) ) {
Sensei_Utils::reset_course_for_user( $course_id, $user_id );
}
break;
}
}
/**
* Enqueues JS and CSS dependencies.
*
* @access private
*/
public function enqueue_scripts() {
$is_debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
wp_enqueue_script( 'jquery-modal' );
wp_enqueue_style( 'jquery-modal' );
wp_enqueue_script( 'sensei-learners-admin-bulk-actions-js' );
$data = array(
'remove_generic_confirm' => __( 'Are you sure you want to remove this user?', 'sensei-lms' ),
'remove_from_lesson_confirm' => __( 'Are you sure you want to remove the user from this lesson?', 'sensei-lms' ),
'remove_from_course_confirm' => __( 'Are you sure you want to remove the user from this course?', 'sensei-lms' ),
'remove_user_from_post_nonce' => wp_create_nonce( 'remove_user_from_post_nonce' ),
'bulk_add_learners_nonce' => wp_create_nonce( self::NONCE_SENSEI_BULK_LEARNER_ACTIONS ),
'select_course_placeholder' => __( 'Select Course', 'sensei-lms' ),
'is_debug' => $is_debug,
'sensei_version' => Sensei()->version,
);
wp_localize_script( 'sensei-learners-admin-bulk-actions-js', 'sensei_learners_bulk_data', $data );
}
/**
* Display the learner bulk action page.
*
* @deprecated 4.4.0
*/
public function learner_admin_page() {
_deprecated_function( __METHOD__, '4.4.0', 'Sensei_Learner_Management::output_main_page' );
// Load Learners data.
$sensei_learners_main_view = new Sensei_Learners_Admin_Bulk_Actions_View( $this, $this->learner_management, $this->learner );
$sensei_learners_main_view->prepare_items();
// Wrappers.
/**
* Fires before rendering the students page.
*
* @hook sensei_learner_admin_before_container
*/
do_action( 'sensei_learner_admin_before_container' );
?>
<div id="woothemes-sensei" class="wrap woothemes-sensei">
<?php
/**
* Fires before rendering the students page container.
* This hook allows to wrap the container.
*
* @hook sensei_learner_admin_wrapper_container
*
* @param {string} $position The position of the container ('top' here).
*/
do_action( 'sensei_learner_admin_wrapper_container', 'top' );
$sensei_learners_main_view->output_headers();
?>
<div id="poststuff" class="sensei-learners-wrap">
<div class="sensei-learners-main">
<?php $sensei_learners_main_view->display(); ?>
</div>
<div class="sensei-learners-extra">
<?php
/**
* Fires after rendering the main content of the students page.
* Allowing for additional content to be added.
*
* @hook sensei_learner_admin_extra
*/
do_action( 'sensei_learner_admin_extra' );
?>
</div>
</div>
<?php
/**
* Fires after rendering the students page container.
* This hook allows to wrap the container.
*
* @hook sensei_learner_admin_wrapper_container
*
* @pram {string} $position The position of the container ('bottom' here).
*/
do_action( 'sensei_learner_admin_wrapper_container', 'bottom' );
?>
</div>
<?php
/**
* Fires after rendering the students page container.
*
* @hook sensei_learner_admin_after_container
*/
do_action( 'sensei_learner_admin_after_container' );
}
/**
* Checks if this is the bulk management page.
*/
public function is_current_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Arguments used for comparison.
return empty( $_GET['course_id'] ) && isset( $_GET['page'] ) && $_GET['page'] === $this->page_slug;
}
/**
* Registers the class' hooks.
*/
private function register_hooks() {
if ( $this->is_current_page() ) {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 30 );
}
add_action( 'admin_init', array( $this, 'handle_http_post' ) );
add_action( 'admin_notices', array( $this, 'add_notices' ) );
}
/**
* Get the page view string.
*
* @return string
*/
public function get_view() {
return $this->view;
}
/**
* Adds a notice in the bulk management page. The notice message is retrieved from the message GET argument. The
* GET argument can be either the actual message or a specific code.
*/
public function add_notices() {
if ( ! $this->is_current_page() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['message'] ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Safe use of message.
$msg = sanitize_text_field( wp_unslash( $_GET['message'] ) );
$msg_class = 'notice notice-error';
switch ( $msg ) {
case 'error-invalid-action':
$msg = __( 'This bulk action is not supported', 'sensei-lms' );
break;
case 'error-invalid-course':
$msg = __( 'Invalid Course', 'sensei-lms' );
break;
case 'action-success':
$msg_class = 'notice notice-success';
$msg = __( 'Bulk student action succeeded', 'sensei-lms' );
break;
}
?>
<div class="learners-notice <?php echo esc_attr( $msg_class ); ?>">
<p><?php echo esc_html( $msg ); ?></p>
</div>
<?php
}
}