<?php
use Sensei\Internal\Installer\Eraser;
use Sensei\Internal\Installer\Schema;
use Sensei\Internal\Services\Progress_Storage_Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/*
* Sensei Settings Class
*
* All functionality pertaining to the settings in Sensei.
*
* @package Core
* @author Automattic
*
* @since 1.0.0
*/
class Sensei_Settings extends Sensei_Settings_API {
const VISITED_SECTIONS_OPTION_KEY = 'sensei_settings_sections_visited';
/**
* Constructor.
*
* @access public
* @since 1.0.0
*/
public function __construct() {
parent::__construct(); // Required in extended classes.
// Setup Admin Settings data
if ( is_admin() ) {
$this->has_tabs = true;
$this->name = __( 'Sensei LMS Settings', 'sensei-lms' );
$this->menu_label = __( 'Settings', 'sensei-lms' );
$this->page_slug = 'sensei-settings';
}
$this->register_hook_listener();
$this->get_settings();
// Flush rewrite rules on settings update.
add_action( 'init', array( $this, 'flush_rewrite_rules_on_update' ), 10, 0 );
// Log when settings are updated by the user.
add_action( 'update_option_sensei-settings', [ $this, 'log_settings_update' ], 10, 2 );
// Mark settings section as visited on ajax action received.
add_action( 'wp_ajax_sensei_settings_section_visited', [ $this, 'mark_section_as_visited' ] );
// Do preparation for enabled experimental features.
add_filter( 'pre_update_option_sensei-settings', [ $this, 'before_experimental_features_saved' ], 10, 2 );
add_action( 'update_option_sensei-settings', [ $this, 'experimental_features_saved' ], 10, 2 );
}
/**
* Get settings value
*
* @since 1.9.0
* @param string $setting_name
* @return mixed
*/
public function get( $setting_name ) {
if ( isset( $this->settings[ $setting_name ] ) ) {
return $this->settings[ $setting_name ];
}
return false;
}
/**
* Get my courses page ID.
*
* @since 4.23.1
*
* @return int
*/
public function get_my_courses_page_id() {
/**
* Filters the My Courses page ID.
*
* @hook sensei_settings_my_course_page_id
*
* @since 4.23.1
*
* @param {int} $page_id The My Courses page ID.
* @return {int} Filtered My Courses page ID.
*/
$page_id = apply_filters( 'sensei_settings_my_course_page_id', $this->get( 'my_course_page' ) );
return absint( $page_id );
}
/**
* @since 1.9.0
*
* @param $setting
* @param $new_value
*/
public function set( $setting, $new_value ) {
$settings = $this->get_settings();
$settings[ $setting ] = $new_value;
// Update the cached setting.
$this->settings[ $setting ] = $new_value;
return update_option( $this->token, $settings );
}
/**
* Register the settings screen within the WordPress admin.
*
* @access public
* @since 1.0.0
*/
public function register_settings_screen() {
$this->settings_version = Sensei()->version; // Use the global plugin version on this settings screen.
$this->hook = add_submenu_page(
'sensei',
$this->name,
$this->menu_label,
'manage_sensei',
$this->page_slug,
array( $this, 'settings_screen' )
);
if ( isset( $_GET['page'] ) && ( $_GET['page'] == $this->page_slug ) ) {
add_action( 'admin_notices', array( $this, 'settings_errors' ) );
add_action( 'admin_print_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'admin_print_styles', array( $this, 'enqueue_styles' ) );
}
}
/**
* Output the settings screen.
*/
public function settings_screen() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification not required.
$tab_name = sanitize_key( wp_unslash( $_GET['tab'] ?? '' ) );
/**
* Filters content for the settings page.
*
* @since 4.12.0
*
* @hook sensei_settings_content
*
* @param {string} $tab_name The tab slug.
* @return {string} Filtered tab content.
*/
$content = apply_filters( 'sensei_settings_content', $tab_name );
if ( empty( $content ) ) {
parent::settings_screen();
return;
}
// Render content on the Emails settings page.
Sensei()->assets->enqueue_style( 'wp-components' );
?>
<div id="woothemes-sensei" class="wrap <?php echo esc_attr( $this->token ); ?>">
<div id="sensei-custom-navigation" class="sensei-custom-navigation">
<div class="sensei-custom-navigation__heading">
<div class="sensei-custom-navigation__title">
<h1>
<?php
echo esc_html( $this->name );
if ( '' !== $this->settings_version ) {
echo ' <span class="version">' . esc_html( $this->settings_version ) . '</span>';
}
?>
</h1>
</div>
<div class="sensei-custom-navigation__links">
<?php $this->settings_tabs(); ?>
</div>
</div>
<?php
/**
* Fires after the navigation links are displayed.
*
* @since 4.12.0
*
* @hook sensei_settings_after_links
*
* @param {string} $tab_name The tab slug.
*/
do_action( 'sensei_settings_after_links', $tab_name );
?>
</div>
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $content;
?>
</div>
<?php
}
/**
* Add legacy options to alloptions if they don't exist.
*
* @since 3.0.1
* @deprecated 4.13.1
*
* @param array $alloptions All options that are preloaded by WordPress.
*
* @return array
*/
public function no_special_query_for_legacy_options( $alloptions ) {
_deprecated_function( __METHOD__, '4.13.1' );
return $alloptions;
}
/**
* Add settings sections.
*
* @access public
* @since 1.0.0
* @return void
*/
public function init_sections() {
$sections = array();
$sections['default-settings'] = array(
'name' => __( 'General', 'sensei-lms' ),
'description' => __( 'Settings that apply to the entire plugin.', 'sensei-lms' ),
);
$sections['appearance-settings'] = array(
'name' => __( 'Appearance', 'sensei-lms' ),
'description' => __( 'Settings that apply to the entire plugin.', 'sensei-lms' ),
'badge' => __( 'new', 'sensei-lms' ),
);
$sections['course-settings'] = array(
'name' => __( 'Courses', 'sensei-lms' ),
'description' => __( 'Settings that apply to all Courses.', 'sensei-lms' ),
);
$sections['lesson-settings'] = array(
'name' => __( 'Lessons', 'sensei-lms' ),
'description' => __( 'Settings that apply to all Lessons.', 'sensei-lms' ),
);
$sections['email-notification-settings'] = array(
'name' => __( 'Email Notifications', 'sensei-lms' ),
'description' => __( 'Settings for email notifications sent from your site.', 'sensei-lms' ),
);
$sections['learner-profile-settings'] = array(
'name' => __( 'Student Profiles', 'sensei-lms' ),
'description' => __( 'Settings for public Student Profiles.', 'sensei-lms' ),
);
/**
* Filters the woocommerce promo settings section.
*
* Hook used to hide woocommerce promo banner and section.
* @since 4.1.0
*
* @hook sensei_settings_woocommerce_hide
*
* @param {bool} $hide_woocommerce_settings Boolean value that defines if the woocommerce promo banner should be hidden.
* @return {bool} Filtered boolean value that defines if the woocommerce promo banner should be hidden.
*/
$hide_woocommerce_settings = apply_filters( 'sensei_settings_woocommerce_hide', false );
if ( ! $hide_woocommerce_settings ) {
$sections['woocommerce-settings'] = array(
'name' => __( 'WooCommerce', 'sensei-lms' ),
'description' => __( 'Optional settings for WooCommerce functions.', 'sensei-lms' ),
);
}
/**
* Filters the content drip promo settings section.
*
* Hook used to hide content drip promo banner and section.
*
* @since 4.1.0
*
* @hook sensei_settings_content_drip_hide
*
* @param {bool} $hide_content_drip_settings Boolean value that defines if the content drip promo banner should be hidden.
* @return {bool} Filtered boolean value that defines if the content drip promo banner should be hidden.
*/
$hide_content_drip_settings = apply_filters( 'sensei_settings_content_drip_hide', false );
if ( ! $hide_content_drip_settings ) {
$sections['sensei-content-drip-settings'] = array(
'name' => __( 'Content Drip', 'sensei-lms' ),
'description' => __( 'Optional settings for the Content Drip extension.', 'sensei-lms' ),
);
}
if ( Sensei()->feature_flags->is_enabled( 'experimental_features_ui' ) ) {
$sections['sensei-experimental-features'] = array(
'name' => __( 'Experimental Features', 'sensei-lms' ),
'description' => __( 'Experimental features that are incomplete and not yet ready for production.', 'sensei-lms' ),
);
}
/**
* Filters settings tabs.
*
* @hook sensei_settings_tabs
*
* @param {array} $sections Array of settings sections.
* @return {array} Filtered array of settings sections.
*/
$this->sections = apply_filters( 'sensei_settings_tabs', $sections );
}
/**
* Add settings fields.
*
* @access public
* @since 1.0.0
* @uses Sensei_Utils::get_slider_types()
* @return void
*/
public function init_fields() {
$pages_array = $this->pages_array();
$posts_per_page_array = array(
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
'8' => '8',
'9' => '9',
'10' => '10',
'11' => '11',
'12' => '12',
'13' => '13',
'14' => '14',
'15' => '15',
'16' => '16',
'17' => '17',
'18' => '18',
'19' => '19',
'20' => '20',
);
$complete_settings = array(
'passed' => __( 'Once all the course lessons have been completed', 'sensei-lms' ),
'complete' => __( 'At any time (by clicking the \'Complete Course\' button)', 'sensei-lms' ),
);
$quiz_points_formats = array(
'none' => __( "Don't show quiz question points", 'sensei-lms' ),
'number' => __( 'Number (e.g. 1. Default)', 'sensei-lms' ),
'brackets' => __( 'Brackets (e.g. [1])', 'sensei-lms' ),
'text' => __( 'Text (e.g. Points: 1)', 'sensei-lms' ),
'full' => __( 'Text and Brackets (e.g. [Points: 1])', 'sensei-lms' ),
);
$fields = array();
$fields['access_permission'] = array(
'name' => __( 'Access Permissions', 'sensei-lms' ),
'description' => __( 'Users must be logged in to view lesson content.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'default-settings',
);
$fields['messages_disable'] = array(
'name' => __( 'Disable Private Messages', 'sensei-lms' ),
'description' => __( 'Disable the private message functions between students and teachers.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['course_page'] = array(
'name' => __( 'Course Archive Page', 'sensei-lms' ),
'description' => sprintf(
// translators: Placeholder is the docs link.
__( 'The <a href="%s" target="_blank">page</a> to use to display courses. If you leave this blank the default custom post type archive will apply.', 'sensei-lms' ),
'https://senseilms.com/documentation/sensei-pages/#courses'
),
'type' => 'select',
'default' => 0,
'section' => 'default-settings',
'required' => 0,
'options' => $pages_array,
);
$fields['my_course_page'] = array(
'name' => __( 'My Courses Page', 'sensei-lms' ),
'description' => sprintf(
// translators: Placeholder is the docs link.
__( 'The <a href="%s" target="_blank">page</a> to use to display the courses that a user is currently taking as well as the courses a user has complete.', 'sensei-lms' ),
'https://senseilms.com/documentation/sensei-pages/#my-courses'
),
'type' => 'select',
'default' => 0,
'section' => 'default-settings',
'required' => 0,
'options' => $pages_array,
);
$fields['course_completed_page'] = array(
'name' => __( 'Course Completed Page', 'sensei-lms' ),
'description' => sprintf(
// translators: Placeholder is the docs link.
__( 'The <a href="%s" target="_blank">page</a> that is displayed after a student completes a course.', 'sensei-lms' ),
'https://senseilms.com/documentation/sensei-pages/#course-completed'
),
'type' => 'select',
'default' => 0,
'section' => 'default-settings',
'required' => 0,
'options' => $pages_array,
);
$fields['placeholder_images_enable'] = array(
'name' => __( 'Use placeholder images', 'sensei-lms' ),
'description' => __( 'Output a placeholder image when no featured image has been specified for Courses and Lessons.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['styles_disable'] = array(
'name' => __( 'Disable Sensei LMS Styles', 'sensei-lms' ),
'description' => __( 'Prevent the frontend stylesheets from loading. This will remove the default styles for all Sensei LMS elements.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['quiz_question_points_format'] = array(
'name' => __( 'Quiz question points format', 'sensei-lms' ),
'description' => __( 'Set the quiz question points format', 'sensei-lms' ),
'type' => 'select',
'default' => 'number',
'section' => 'default-settings',
'options' => $quiz_points_formats,
);
$fields['js_disable'] = array(
'name' => __( 'Disable Sensei LMS Javascript', 'sensei-lms' ),
'description' => __( 'Prevent the frontend javascript from loading. This affects the progress bars and the My Courses tabs.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['sensei_video_embed_html_sanitization_disable'] = array(
'name' => __( 'Disable HTML security', 'sensei-lms' ),
'description' => __( 'Allow any HTML tags in the Video Embed field. Warning: Enabling this may leave your site more vulnerable to XSS attacks', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['sensei_delete_data_on_uninstall'] = array(
'name' => __( 'Delete data on uninstall', 'sensei-lms' ),
'description' => __( 'Delete Sensei LMS data when the plugin is deleted. Once removed, this data cannot be restored.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'default-settings',
);
$fields['course_completion'] = array(
'name' => __( 'Courses are complete:', 'sensei-lms' ),
'description' => __( 'This will determine when courses are marked as complete.', 'sensei-lms' ),
'type' => 'select',
'default' => 'passed',
'section' => 'course-settings',
'required' => 0,
'options' => $complete_settings,
);
$fields['course_author'] = array(
'name' => __( 'Course Author', 'sensei-lms' ),
'description' => __( 'Display the author on the Course Archive and My Courses pages. This setting does not apply when these pages use blocks.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'course-settings',
);
$fields['my_course_amount'] = array(
'name' => __( 'My Courses Pagination', 'sensei-lms' ),
'description' => __( 'The number of courses to output for the my courses page.', 'sensei-lms' ),
'type' => 'range',
'default' => '0',
'section' => 'course-settings',
'required' => 0,
'options' => $posts_per_page_array,
);
$fields['course_archive_image_enable'] = array(
'name' => __( 'Course Archive Image', 'sensei-lms' ),
'description' => __( 'Output the Course Image on the Course Archive Page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'course-settings',
);
$fields['course_archive_image_width'] = array(
'name' => __( 'Image Width - Archive', 'sensei-lms' ),
'description' => __( 'The width in pixels of the featured image for the Course Archive page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'course-settings',
'required' => 0,
);
$fields['course_archive_image_height'] = array(
'name' => __( 'Image Height - Archive', 'sensei-lms' ),
'description' => __( 'The height in pixels of the featured image for the Course Archive page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'course-settings',
'required' => 0,
);
$fields['course_archive_image_hard_crop'] = array(
'name' => __( 'Image Hard Crop - Archive', 'sensei-lms' ),
// translators: Placeholders are an opening and closing <a> tag linking to the documentation page.
'description' => sprintf( __( 'After changing this setting, you may need to %1$sregenerate your thumbnails%2$s.', 'sensei-lms' ), '<a href="' . esc_url( 'http://wordpress.org/extend/plugins/regenerate-thumbnails/' ) . '">', '</a>' ),
'type' => 'checkbox',
'default' => false,
'section' => 'course-settings',
);
$fields['course_single_image_enable'] = array(
'name' => __( 'Single Course Image', 'sensei-lms' ),
'description' => __( 'Output the Course Image on the Single Course Page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'course-settings',
);
$fields['course_single_image_width'] = array(
'name' => __( 'Image Width - Single', 'sensei-lms' ),
'description' => __( 'The width in pixels of the featured image for the Course single post page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'course-settings',
'required' => 0,
);
$fields['course_single_image_height'] = array(
'name' => __( 'Image Height - Single', 'sensei-lms' ),
'description' => __( 'The height in pixels of the featured image for the Course single post page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'course-settings',
'required' => 0,
);
$fields['course_single_image_hard_crop'] = array(
'name' => __( 'Image Hard Crop - Single', 'sensei-lms' ),
// translators: Placeholders are an opening and closing <a> tag linking to the documentation page.
'description' => sprintf( __( 'After changing this setting, you may need to %1$sregenerate your thumbnails%2$s.', 'sensei-lms' ), '<a href="' . esc_url( 'http://wordpress.org/extend/plugins/regenerate-thumbnails/' ) . '">', '</a>' ),
'type' => 'checkbox',
'default' => false,
'section' => 'course-settings',
);
$fields['course_archive_featured_enable'] = array(
'name' => __( 'Featured Courses Panel', 'sensei-lms' ),
'description' => __( 'Output the Featured Courses Panel on the Course Archive Page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'course-settings',
);
$fields['course_archive_more_link_text'] = array(
'name' => __( 'More link text', 'sensei-lms' ),
'description' => __( 'The text that will be displayed on the Course Archive for the more courses link.', 'sensei-lms' ),
'type' => 'text',
'default' => __( 'More', 'sensei-lms' ),
'section' => 'course-settings',
'required' => 0,
);
// Course Settings.
$fields['sensei_learning_mode_all'] = array(
'name' => __( 'Learning Mode', 'sensei-lms' ),
'description' => __( 'Show an immersive and distraction-free view for lessons and quizzes.', 'sensei-lms' ),
'form' => 'render_learning_mode_setting',
'type' => 'checkbox',
'default' => \Sensei()->install_version && version_compare( \Sensei()->install_version, '4.7.0', '>=' ),
'section' => 'appearance-settings',
);
// Course Settings.
$fields['sensei_learning_mode_template'] = array(
'name' => __( 'Learning Mode Templates', 'sensei-lms' ),
'description' => __( 'Choose a learning mode template that is most suited for your type of content and the style you want to offer to your students.', 'sensei-lms' ),
'form' => 'render_learning_mode_templates',
'type' => 'radio',
'default' => Sensei_Course_Theme_Template_Selection::DEFAULT_TEMPLATE_NAME,
'section' => 'appearance-settings',
'options' => $this->get_learning_mode_template_options(),
);
// Lesson Settings.
$fields['lesson_comments'] = array(
'name' => __( 'Allow Comments for Lessons', 'sensei-lms' ),
'description' => __( 'This will allow students to post comments on the single Lesson page, only student who have access to the Lesson will be allowed to comment.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'lesson-settings',
);
$fields['lesson_author'] = array(
'name' => __( 'Display Lesson Author', 'sensei-lms' ),
'description' => __( 'Output the Lesson Author on Course single page & Lesson archive page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'lesson-settings',
);
$fields['course_lesson_image_enable'] = array(
'name' => __( 'Course Lesson Images', 'sensei-lms' ),
'description' => __( 'Output the Lesson Image on the Single Course Page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'lesson-settings',
);
$fields['lesson_archive_image_width'] = array(
'name' => __( 'Image Width - Course Lessons', 'sensei-lms' ),
'description' => __( 'The width in pixels of the featured image for the Lessons on the Course Single page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'lesson-settings',
'required' => 0,
);
$fields['lesson_archive_image_height'] = array(
'name' => __( 'Image Height - Course Lessons', 'sensei-lms' ),
'description' => __( 'The height in pixels of the featured image for the Lessons on the Course Single page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'lesson-settings',
'required' => 0,
);
$fields['lesson_archive_image_hard_crop'] = array(
'name' => __( 'Image Hard Crop - Course Lessons', 'sensei-lms' ),
// translators: Placeholders are an opening and closing <a> tag linking to the documentation page.
'description' => sprintf( __( 'After changing this setting, you may need to %1$sregenerate your thumbnails%2$s.', 'sensei-lms' ), '<a href="' . esc_url( 'http://wordpress.org/extend/plugins/regenerate-thumbnails/' ) . '">', '</a>' ),
'type' => 'checkbox',
'default' => false,
'section' => 'lesson-settings',
);
$fields['lesson_single_image_enable'] = array(
'name' => __( 'Single Lesson Images', 'sensei-lms' ),
'description' => __( 'Output the Lesson Image on the Single Lesson Page.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => false,
'section' => 'lesson-settings',
);
$fields['lesson_single_image_width'] = array(
'name' => __( 'Image Width - Single', 'sensei-lms' ),
'description' => __( 'The width in pixels of the featured image for the Lessons single post page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'lesson-settings',
'required' => 0,
);
$fields['lesson_single_image_height'] = array(
'name' => __( 'Image Height - Single', 'sensei-lms' ),
'description' => __( 'The height in pixels of the featured image for the Lessons single post page.', 'sensei-lms' ),
'type' => 'text',
'default' => '100',
'section' => 'lesson-settings',
'required' => 0,
);
$fields['lesson_single_image_hard_crop'] = array(
'name' => __( 'Image Hard Crop - Single', 'sensei-lms' ),
// translators: Placeholders are an opening and closing <a> tag linking to the documentation page.
'description' => sprintf( __( 'After changing this setting, you may need to %1$sregenerate your thumbnails%2$s.', 'sensei-lms' ), '<a href="' . esc_url( 'http://wordpress.org/extend/plugins/regenerate-thumbnails/' ) . '">', '</a>' ),
'type' => 'checkbox',
'default' => false,
'section' => 'lesson-settings',
);
// Learner Profile settings
/**
* Filters the learner profile URL base.
*
* @hook sensei_learner_profiles_url_base
*
* @param {string} $profile_url_base The profile URL base, default is 'learner'.
* @return {string} Filtered profile URL base.
*/
$profile_url_base = apply_filters( 'sensei_learner_profiles_url_base', __( 'learner', 'sensei-lms' ) );
$profile_url_example = trailingslashit( get_home_url() ) . $profile_url_base . '/%username%';
$fields['learner_profile_enable'] = array(
'name' => __( 'Public student profiles', 'sensei-lms' ),
// translators: Placeholder is a profile URL example.
'description' => sprintf( __( 'Enable public student profiles that will be accessible to everyone. Profile URL format: %s', 'sensei-lms' ), $profile_url_example ),
'type' => 'checkbox',
'default' => true,
'section' => 'learner-profile-settings',
);
$fields['learner_profile_show_courses'] = array(
'name' => __( 'Show student\'s courses', 'sensei-lms' ),
'description' => __( 'Display the student\'s active and completed courses on their profile.', 'sensei-lms' ),
'type' => 'checkbox',
'default' => true,
'section' => 'learner-profile-settings',
);
// Email notifications
$learner_email_options = array(
'learner-graded-quiz' => __( 'Their quiz is graded (auto and manual grading)', 'sensei-lms' ),
'learner-completed-course' => __( 'They complete a course', 'sensei-lms' ),
);
$teacher_email_options = array(
'teacher-started-course' => __( 'A student starts their course', 'sensei-lms' ),
'teacher-completed-course' => __( 'A student completes their course', 'sensei-lms' ),
'teacher-completed-lesson' => __( 'A student completes a lesson', 'sensei-lms' ),
'teacher-quiz-submitted' => __( 'A student submits a quiz for grading', 'sensei-lms' ),
'teacher-new-message' => __( 'A student sends a private message to a teacher', 'sensei-lms' ),
);
$global_email_options = array(
'new-message-reply' => __( 'They receive a reply to their private message', 'sensei-lms' ),
);
$fields['email_learners'] = array(
'name' => __( 'Emails Sent to Students', 'sensei-lms' ),
'description' => __( 'Select the notifications that will be sent to students.', 'sensei-lms' ),
'type' => 'multicheck',
'options' => $learner_email_options,
'defaults' => array( 'learner-graded-quiz', 'learner-completed-course' ),
'section' => 'email-notification-settings',
);
$fields['email_teachers'] = array(
'name' => __( 'Emails Sent to Teachers', 'sensei-lms' ),
'description' => __( 'Select the notifications that will be sent to teachers.', 'sensei-lms' ),
'type' => 'multicheck',
'options' => $teacher_email_options,
'defaults' => array( 'teacher-completed-course', 'teacher-started-course', 'teacher-quiz-submitted', 'teacher-new-message' ),
'section' => 'email-notification-settings',
);
$fields['email_global'] = array(
'name' => __( 'Emails Sent to All Users', 'sensei-lms' ),
'description' => __( 'Select the notifications that will be sent to all users.', 'sensei-lms' ),
'type' => 'multicheck',
'options' => $global_email_options,
'defaults' => array( 'new-message-reply' ),
'section' => 'email-notification-settings',
);
$fields['email_from_name'] = array(
'name' => __( '"From" Name', 'sensei-lms' ),
'description' => __( 'The name from which all emails will be sent.', 'sensei-lms' ),
'type' => 'text',
'default' => get_bloginfo( 'name' ),
'section' => 'email-notification-settings',
'required' => 1,
);
$fields['email_from_address'] = array(
'name' => __( '"From" Address', 'sensei-lms' ),
'description' => __( 'The address from which all emails will be sent.', 'sensei-lms' ),
'type' => 'email',
'default' => get_bloginfo( 'admin_email' ),
'section' => 'email-notification-settings',
'required' => 1,
);
$fields['email_reply_to_name'] = [
'name' => __( '"Reply To" Name', 'sensei-lms' ),
'type' => 'input',
'default' => '',
'section' => 'email-notification-settings',
'required' => 0,
];
$fields['email_reply_to_address'] = [
'name' => __( '"Reply To" Address', 'sensei-lms' ),
'type' => 'email',
'default' => get_bloginfo( 'admin_email' ),
'section' => 'email-notification-settings',
'required' => 0,
];
$fields['email_cc'] = array(
'name' => __( 'CC', 'sensei-lms' ),
'description' => __( 'Enter email addresses to CC on all emails. Separate multiple email addresses with commas.', 'sensei-lms' ),
'type' => 'email',
'multiple' => true,
'default' => '',
'section' => 'email-notification-settings',
'required' => 0,
'error_message' => __( 'One or more of the email addresses entered for CC is invalid.', 'sensei-lms' ),
);
$fields['email_bcc'] = array(
'name' => __( 'BCC', 'sensei-lms' ),
'description' => __( 'Enter email addresses to BCC on all emails. Separate multiple email addresses with commas.', 'sensei-lms' ),
'type' => 'email',
'multiple' => true,
'default' => '',
'section' => 'email-notification-settings',
'required' => 0,
'error_message' => __( 'One or more of the email addresses entered for BCC is invalid.', 'sensei-lms' ),
);
$fields['email_header_image'] = array(
'name' => __( 'Header Image', 'sensei-lms' ),
// translators: Placeholders are opening and closing <a> tags linking to the media uploader.
'description' => sprintf( __( 'Enter a URL to an image you want to show in the email\'s header. Upload your image using the %1$smedia uploader%2$s.', 'sensei-lms' ), '<a href="' . admin_url( 'media-new.php' ) . '">', '</a>' ),
'type' => 'text',
'default' => '',
'section' => 'email-notification-settings',
'required' => 0,
);
$fields['email_footer_text'] = array(
'name' => __( 'Email Footer Text', 'sensei-lms' ),
'description' => __( 'The text to appear in the footer of Sensei LMS emails.', 'sensei-lms' ),
'type' => 'textarea',
// translators: Placeholder is the blog name.
'default' => sprintf( __( '%1$s - Powered by Sensei LMS', 'sensei-lms' ), get_bloginfo( 'name' ) ),
'section' => 'email-notification-settings',
'required' => 0,
);
$fields['email_base_color'] = array(
'name' => __( 'Base Colour', 'sensei-lms' ),
// translators: Placeholders are opening and closing <code> tags.
'description' => sprintf( __( 'The base colour for Sensei LMS email templates. Default %1$s#557da1%2$s.', 'sensei-lms' ), '<code>', '</code>' ),
'type' => 'color',
'default' => '#557da1',
'section' => 'email-notification-settings',
'required' => 1,
);
$fields['email_background_color'] = array(
'name' => __( 'Background Colour', 'sensei-lms' ),
// translators: Placeholders are opening and closing <code> tags.
'description' => sprintf( __( 'The background colour for Sensei LMS email templates. Default %1$s#f5f5f5%2$s.', 'sensei-lms' ), '<code>', '</code>' ),
'type' => 'color',
'default' => '#f5f5f5',
'section' => 'email-notification-settings',
'required' => 1,
);
$fields['email_body_background_color'] = array(
'name' => __( 'Body Background Colour', 'sensei-lms' ),
// translators: Placeholders are opening and closing <code> tags.
'description' => sprintf( __( 'The main body background colour for Sensei LMS email templates. Default %1$s#fdfdfd%2$s.', 'sensei-lms' ), '<code>', '</code>' ),
'type' => 'color',
'default' => '#fdfdfd',
'section' => 'email-notification-settings',
'required' => 1,
);
$fields['email_text_color'] = array(
'name' => __( 'Body Text Colour', 'sensei-lms' ),
// translators: Placeholders are opening and closing <code> tags.
'description' => sprintf( __( 'The main body text colour for Sensei LMS email templates. Default %1$s#505050%2$s.', 'sensei-lms' ), '<code>', '</code>' ),
'type' => 'color',
'default' => '#505050',
'section' => 'email-notification-settings',
'required' => 1,
);
if ( Sensei()->feature_flags->is_enabled( 'experimental_features_ui' ) ) {
$fields['experimental_progress_storage'] = array(
'name' => __( 'High-Performance Progress Storage', 'sensei-lms' ),
'description' => __( 'Store the progress of your students in separate tables.', 'sensei-lms' ),
'form' => 'render_progress_storage_feature',
'type' => 'checkbox',
'default' => false,
'section' => 'sensei-experimental-features',
);
$fields['experimental_progress_storage_synchronization'] = array(
'name' => '',
'description' => __( 'Synchronize the student progress between storages.', 'sensei-lms' ),
'form' => 'render_progress_storage_synchronization',
'type' => 'checkbox',
'default' => false,
'section' => 'sensei-experimental-features',
);
$fields['experimental_progress_storage_repository'] = array(
'name' => '', // ,
'description' => __( 'Choose a repository to store the progress and quiz submissions of your students.', 'sensei-lms' ),
'form' => 'render_progress_storage_repositories',
'type' => 'radio',
'default' => Progress_Storage_Settings::COMMENTS_STORAGE,
'section' => 'sensei-experimental-features',
'options' => Progress_Storage_Settings::get_storage_repositories(),
);
}
/**
* Filters settings fields.
*
* @hook sensei_settings_fields
*
* @param {array} $fields The array of fields.
* @return {array} Filtered array of fields.
*/
$this->fields = apply_filters( 'sensei_settings_fields', $fields );
}
/**
* Get options for the learning mode templates.
*/
private function get_learning_mode_template_options() {
return Sensei_Course_Theme_Template_Selection::get_templates();
}
/**
* Get options for the duration fields.
*
* @since 1.0.0
* @param $include_milliseconds (default: true) Whether or not to include milliseconds between 0 and 1.
* @return array Options between 0.1 and 10 seconds.
*/
private function get_duration_options( $include_milliseconds = true ) {
$numbers = array( '1.0', '1.5', '2.0', '2.5', '3.0', '3.5', '4.0', '4.5', '5.0', '5.5', '6.0', '6.5', '7.0', '7.5', '8.0', '8.5', '9.0', '9.5', '10.0' );
$options = array();
if ( true == (bool) $include_milliseconds ) {
$milliseconds = array( '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9' );
foreach ( $milliseconds as $k => $v ) {
$options[ $v ] = $v;
}
} else {
$options['0.5'] = '0.5';
}
foreach ( $numbers as $k => $v ) {
$options[ $v ] = $v;
}
return $options;
}
/**
* Return an array of pages.
*
* @access private
* @since 1.0.0
* @return array
*/
private function pages_array() {
if ( ! is_admin() ) {
return [];
}
// REFACTOR - Transform this into a field type instead.
// Setup an array of portfolio gallery terms for a dropdown.
$pages_dropdown = wp_dropdown_pages(
array(
'echo' => 0,
'hierarchical' => 1,
'sort_column' => 'post_title',
'sort_order' => 'ASC',
)
);
$page_items = array();
// Quick string hack to make sure we get the pages with the indents.
$pages_dropdown = str_replace( "<select class='' name='page_id' id='page_id'>", '', $pages_dropdown );
$pages_dropdown = str_replace( '</select>', '', $pages_dropdown );
$pages_split = explode( '</option>', $pages_dropdown );
$page_items[] = __( 'Select a Page:', 'sensei-lms' );
foreach ( $pages_split as $k => $v ) {
$id = '';
// Get the ID value.
preg_match( '/value="(.*?)"/i', $v, $matches );
if ( isset( $matches[1] ) ) {
$id = $matches[1];
$page_items[ $id ] = trim( strip_tags( $v ) );
}
}
$pages_array = $page_items;
return $pages_array;
}
/**
* Flush the rewrite rules after the settings have been updated.
* This is to ensure that the proper permalinks are set up for archive pages.
*
* @since 1.9.0
*
* @deprecated 1.24.0 Use flush_rewrite_rules_on_update instance method instead.
*/
public static function flush_rewrite_rules() {
_deprecated_function( __METHOD__, '1.24.0', 'Use flush_rewrite_rules_on_update instance method instead' );
$settings = new self();
$settings->flush_rewrite_rules_on_update();
}
/**
* Flush the rewrite rules after the settings have been updated.
* This is to ensure that the proper permalinks are set up for archive pages.
*
* @internal
*
* @since 1.24.0
*/
public function flush_rewrite_rules_on_update() {
$nonce_action = $this->token . '-options';
$nonce_value = sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ?? '' ) );
if ( ! wp_verify_nonce( $nonce_value, $nonce_action ) ) {
return;
}
if ( ! is_admin() || ! current_user_can( 'manage_sensei' ) ) {
return;
}
if ( isset( $_POST['option_page'] ) && 'sensei-settings' === $_POST['option_page']
&& isset( $_POST['action'] ) && 'update' === $_POST['action'] ) {
Sensei()->initiate_rewrite_rules_flush();
}
}
/**
* Logs settings update from the Settings form.
*
* @access private
* @since 2.1.0
*
* @param array $old_value The old settings value.
* @param array $value The new settings value.
*/
public function log_settings_update( $old_value, $value ) {
// Only process user-initiated settings updates.
if ( ! ( 'POST' === $_SERVER['REQUEST_METHOD'] && ! defined( 'REST_REQUEST' ) && 'options' === get_current_screen()->id ) ) {
return;
}
// Find changed fields.
$changed = [];
foreach ( $this->fields as $field => $field_config ) {
// Handle absent fields.
$old_field_value = isset( $old_value[ $field ] ) ? $old_value[ $field ] : '';
$new_field_value = isset( $value[ $field ] ) ? $value[ $field ] : '';
// phpcs:ignore Universal.Operators.StrictComparisons -- Loose comparison is okay for checking for changes.
if ( $new_field_value != $old_field_value ) {
// Create an array for this section of settings if needed.
$section = $field_config['section'];
if ( ! isset( $changed[ $section ] ) ) {
$changed[ $section ] = [];
}
// Get changed setting values to be logged. In most cases, this
// will be an array containing only the name of the field.
$changed_values = $this->get_changed_setting_values(
$field,
$new_field_value,
$old_field_value
);
$changed[ $section ] = array_merge( $changed[ $section ], $changed_values );
}
}
// Log changed sections.
foreach ( $changed as $section => $fields ) {
if ( empty( $fields ) ) {
continue;
}
sensei_log_event(
'settings_update',
[
'view' => $section,
'settings' => implode( ',', $fields ),
]
);
}
}
/**
* Ensure that the experimental features settings are consistent.
*
* @access private
* @since 4.20.0
*
* @param array $value The new settings value.
* @param array $old_value The old settings value.
* @return array Updated settings value.
*/
public function before_experimental_features_saved( $value, $old_value ) {
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
return $value;
}
$screen = get_current_screen();
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
if ( ! ( 'POST' === $_SERVER['REQUEST_METHOD'] && ! defined( 'REST_REQUEST' ) && $screen && 'options' === $screen->id ) ) {
return $value;
}
$old_hpps = (bool) ( $old_value['experimental_progress_storage'] ?? false );
$new_hpps = (bool) ( $value['experimental_progress_storage'] ?? false );
if ( $new_hpps !== $old_hpps ) {
if ( $new_hpps ) {
// If the feature is being enabled, set the default values for the other settings.
$value['experimental_progress_storage_synchronization'] = false;
$value['experimental_progress_storage_repository'] = Progress_Storage_Settings::COMMENTS_STORAGE;
} else {
// If the feature is being disabled, clear the values for the other settings.
unset( $value['experimental_progress_storage_repository'] );
unset( $value['experimental_progress_storage_synchronization'] );
}
}
if ( $new_hpps ) {
$sync_enabled = (bool) ( $value['experimental_progress_storage_synchronization'] ?? false );
if ( ! $sync_enabled ) {
// If the synchronization is disabled, set the default value for the repository setting.
$value['experimental_progress_storage_repository'] = Progress_Storage_Settings::COMMENTS_STORAGE;
}
}
return $value;
}
/**
* Do preparation when the experimental features setting were saved.
*
* @access private
* @since 4.19.2
*
* @param array $old_value The old settings value.
* @param array $value The new settings value.
*/
public function experimental_features_saved( $old_value, $value ) {
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
return;
}
$screen = get_current_screen();
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
if ( ! ( 'POST' === $_SERVER['REQUEST_METHOD'] && ! defined( 'REST_REQUEST' ) && $screen && 'options' === $screen->id ) ) {
return;
}
$old_hpps = (bool) ( $old_value['experimental_progress_storage'] ?? false );
$new_hpps = (bool) ( $value['experimental_progress_storage'] ?? false );
if ( $new_hpps !== $old_hpps ) {
sensei_log_event(
'hpps_status_change',
array(
'enabled' => $new_hpps,
)
);
if ( $new_hpps ) {
// Enable the feature flag to make progress tables available.
add_filter( 'sensei_feature_flag_tables_based_progress', '__return_true' );
( new Schema( Sensei()->feature_flags ) )->create_tables();
}
}
// Make sure the migration scheduler is initialized.
Sensei()->init_migration_scheduler();
$migration_scheduler = Sensei()->migration_scheduler;
if ( is_null( $migration_scheduler ) ) {
return;
}
$old_sync = (bool) ( $old_value['experimental_progress_storage_synchronization'] ?? false );
$new_sync = (bool) ( $value['experimental_progress_storage_synchronization'] ?? false );
if ( $new_sync !== $old_sync && $new_sync ) {
// Drop existing tables and clear the migration state.
( new Eraser() )->drop_tables();
$migration_scheduler->clear_state();
// Recreate tables and schedule the migration.
( new Schema( Sensei()->feature_flags ) )->create_tables();
$migration_scheduler->schedule();
}
$old_repository = $old_value['experimental_progress_storage_repository'] ?? Progress_Storage_Settings::COMMENTS_STORAGE;
$new_repository = $value['experimental_progress_storage_repository'] ?? Progress_Storage_Settings::COMMENTS_STORAGE;
if ( $new_repository !== $old_repository ) {
sensei_log_event(
'hpps_repository_change',
array(
'repository' => $new_repository,
)
);
}
}
/**
* Renders the High-Performance Progress Storage feature setting.
*
* @param array $args The field arguments.
*/
public function render_progress_storage_feature( $args ) {
// Checkbox field.
$settings = $this->get_settings();
$key = $args['key'];
$value = $settings[ $key ];
// Disable the checkbox if we can't set time limit. That's our current limitation for running migrations.
$disabled_feature = true;
$original_max_execution_time = (int) ini_get( 'max_execution_time' );
$disabled_php_functions = array_map( 'trim', explode( ',', ini_get( 'disable_functions' ) ) );
$set_time_limit_disabled = in_array( 'set_time_limit', $disabled_php_functions, true );
if ( 0 !== $original_max_execution_time && ! $set_time_limit_disabled ) {
// Set max execution time to 0 to check if we can set it in migrtions.
$disabled_feature = ! set_time_limit( 0 );
$current_max_execution_time = (int) ini_get( 'max_execution_time' );
if ( $disabled_feature && 0 === $current_max_execution_time ) {
$disabled_feature = false;
}
// Restore original max execution time.
set_time_limit( $original_max_execution_time );
}
?>
<div>
<label>
<input
class="sensei-settings_progress-storage-feature"
data-saved-state="<?php echo esc_attr( (int) $value ); ?>"
type="checkbox"
name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>"
value="1"
<?php disabled( true, $disabled_feature, true ); ?>
<?php checked( $value, true, true ); ?>
/>
<?php echo esc_html( $args['data']['description'] ); ?>
</label>
<?php if ( $disabled_feature ) : ?>
<input type="hidden" name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>" value="<?php echo esc_attr( $value ); ?>" />
<p>
<?php
echo esc_html( __( 'As this feature is currently experimental, it may not be available yet on some sites.', 'sensei-lms' ) );
?>
</p>
<?php endif; ?>
<?php if ( ! $value ) : ?>
<div class="notice notice-info inline sensei-settings__progress-storage-settings hidden">
<p>
<?php
echo esc_html( __( 'Save changes to make the feature settings available.', 'sensei-lms' ) );
?>
</p>
</div>
<?php endif; ?>
<h4><?php esc_html_e( 'Instructions', 'sensei-lms' ); ?></h4>
<p><?php esc_html_e( 'To enable High-Performance Progress Storage, follow these steps:', 'sensei-lms' ); ?></p>
<ol>
<li><?php esc_html_e( 'Select the "Store the progress of your students in separate tables" checkbox and save the changes.', 'sensei-lms' ); ?></li>
<li><?php esc_html_e( 'Select the "Synchronize the student progress between storages" checkbox and save the changes.', 'sensei-lms' ); ?></li>
<li><?php esc_html_e( 'Wait until the "Migration complete and data synchronization enabled" message is displayed. This may take awhile and you will need to refresh the page to see the updated status.', 'sensei-lms' ); ?></li>
<li><?php esc_html_e( 'Select the "High-Performance progress storage (experimental)" option and save the changes.', 'sensei-lms' ); ?></li>
<li><?php esc_html_e( 'You are now using High-Performance Progress Storage!', 'sensei-lms' ); ?></li>
</ol>
<p><?php echo wp_kses_post( __( 'To learn more about the feature, check the <a href="https://senseilms.com/documentation/high-performance-progress-storage/" target="_blank">docs</a>.', 'sensei-lms' ) ); ?></p>
</div>
<?php
Sensei()->assets->enqueue( 'sensei-experimental-features-progress-storage', 'js/admin/settings/experimental-features.js', array( 'jquery' ), true );
}
/**
* Renders the High-Performance Progress Storage repository setting.
*
* @param array $args The field arguments.
*/
public function render_progress_storage_repositories( $args ) {
$settings = $this->get_settings();
$key = $args['key'];
$value = $settings[ $key ];
$visible = $settings['experimental_progress_storage'] ?? false;
$block_display = $visible ? 'block' : 'none';
$sync_disabled = ! (bool) ( $settings['experimental_progress_storage_synchronization'] ?? false );
$migration_scheduler = Sensei()->migration_scheduler;
$migration_incomplete = ! $migration_scheduler || ! $migration_scheduler->is_complete();
?>
<div class="sensei-settings__progress-storage-settings" style="display: <?php echo esc_attr( $block_display ); ?>">
<h4><?php echo esc_html( __( 'Progress storage repository', 'sensei-lms' ) ); ?></h4>
<p><?php echo esc_html( $args['data']['description'] ); ?></p>
<ul>
<?php foreach ( $args['data']['options'] as $option_value => $option_name ) : ?>
<li>
<label>
<input
class="sensei-settings_progress-storage-repository"
type="radio"
name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>"
value="<?php echo esc_attr( $option_value ); ?>"
<?php disabled( $sync_disabled || $migration_incomplete ); ?>
<?php checked( $option_value, $value, true ); ?>
/>
<?php echo esc_html( $option_name ); ?>
</label>
</li>
<?php endforeach; ?>
</ul>
<?php if ( $sync_disabled ) : ?>
<p>
<?php
echo esc_html( __( 'Enable storage synchronization and wait for full synchronization to complete before switching to another repository.', 'sensei-lms' ) );
?>
</p>
<?php endif; ?>
<?php if ( ! $sync_disabled && $migration_incomplete ) : ?>
<p>
<?php
echo esc_html( __( 'Wait for full synchronization to complete before switching to another repository.', 'sensei-lms' ) );
?>
</p>
<?php endif; ?>
</div>
<?php
}
/**
* Renders the High-Performance Progress Storage synchronization setting.
*
* @param array $args The field arguments.
*/
public function render_progress_storage_synchronization( $args ) {
// Checkbox field.
$settings = $this->get_settings();
$key = $args['key'];
$value = $settings[ $key ];
// Setting visibility.
$visible = $settings['experimental_progress_storage'] ?? false;
$block_display = $visible ? 'block' : 'none';
// Migration state.
$migration_scheduler = Sensei()->migration_scheduler;
$migration_in_progress = $migration_scheduler && $migration_scheduler->is_in_progress();
$migration_complete = $migration_scheduler && $migration_scheduler->is_complete();
$migration_failed = $migration_scheduler && $migration_scheduler->is_failed();
$migration_errors = ! is_null( $migration_scheduler ) ? $migration_scheduler->get_errors() : array();
// Disables the checkbox if the migration is in progress or HPPS storage is in use.
$hpps_repository_in_use = Progress_Storage_Settings::TABLES_STORAGE === ( $settings['experimental_progress_storage_repository'] ?? null );
$disabled = ! $visible || $migration_in_progress || $hpps_repository_in_use || is_null( Sensei()->action_scheduler );
if ( $migration_errors ) {
// Enable the checkbox if there are errors to allow the user to retry.
$disabled = false;
}
?>
<div class="sensei-settings__progress-storage-settings" style="display: <?php echo esc_attr( $block_display ); ?>">
<h4><?php echo esc_html( __( 'Progress storage synchronization', 'sensei-lms' ) ); ?></h4>
<label>
<input
class="sensei-settings_progress-storage-synchronization"
data-saved-state="<?php echo esc_attr( (int) ( $value && $migration_complete ) ); ?>"
type="checkbox"
name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>"
value="1"
<?php disabled( true, $disabled, true ); ?>
<?php checked( $value, true, true ); ?>
/>
<?php echo esc_html( $args['data']['description'] ); ?>
</label>
<?php if ( $disabled ) : ?>
<input type="hidden" name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>" value="<?php echo esc_attr( $value ); ?>" />
<?php endif; ?>
<?php if ( $value ) : ?>
<?php if ( $migration_in_progress ) : ?>
<p>
<?php
echo esc_html( __( 'Data migration is in progress. Please wait for it to switch repository.', 'sensei-lms' ) );
?>
</p>
<?php elseif ( $migration_complete && empty( $migration_errors ) ) : ?>
<p>
<?php
echo esc_html( __( 'Migration complete and data synchronization enabled.', 'sensei-lms' ) );
?>
</p>
<?php elseif ( $migration_complete && ! empty( $migration_errors ) ) : ?>
<p>
<?php
echo esc_html( __( 'Migration complete, but errors occurred during data synchronization.', 'sensei-lms' ) );
?>
</p>
<?php elseif ( $migration_failed ) : ?>
<p>
<?php
echo esc_html( __( 'Migration failed. Please retry.', 'sensei-lms' ) );
?>
</p>
<?php elseif ( is_null( $migration_scheduler ) ) : ?>
<p>
<?php
echo esc_html( __( 'Cannot get the migration status.', 'sensei-lms' ) );
?>
<?php else : ?>
<p>
<?php
echo esc_html( __( 'Waiting for data migration to start.', 'sensei-lms' ) );
?>
</p>
<?php endif; ?>
<?php if ( ! empty( $migration_errors ) ) : ?>
<p>
<?php
echo esc_html( __( 'Errors occurred during migration:', 'sensei-lms' ) );
?>
</p>
<ul class="ul-disc">
<?php foreach ( $migration_errors as $error ) : ?>
<li><?php echo esc_html( $error ); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
}
/**
* Get an array of setting values which were changed. In most cases, this
* will simply be the name of the setting. However, if the setting is an
* array of strings, then this will return an array of the string values
* that were changed. These values returned will be of the form
* "field_name[value_name]".
*
* @since 2.1.0
*
* @param string $field The name of the setting field.
* @param array $new_value The new value.
* @param array $old_value The old value.
*
* @return array The array of strings representing the field that was
* changed, or an array containing the field name.
*/
private function get_changed_setting_values( $field, $new_value, $old_value ) {
// If the old and new values are not arrays, return the field name.
if ( ! is_array( $new_value ) || ! is_array( $old_value ) ) {
return [ $field ];
}
// Now, make sure they are both string arrays.
foreach ( array_merge( $new_value, $old_value ) as $value ) {
if ( ! is_string( $value ) ) {
return [ $field ];
}
}
// We have two string arrays. Return the difference in their values.
$added = array_diff( $new_value, $old_value );
$removed = array_diff( $old_value, $new_value );
return array_filter( array_merge( $added, $removed ) );
}
/**
* Learning mode setting.
*
* @param array $args The field arguments.
*/
public function render_learning_mode_setting( $args ) {
$options = $this->get_settings();
$key = $args['key'];
$value = $options[ $key ];
?>
<label for="<?php echo esc_attr( $key ); ?>">
<input id="<?php echo esc_attr( $key ); ?>" name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>" type="checkbox" value="1" <?php checked( $value, '1' ); ?> />
<?php esc_html_e( 'Enable for all courses', 'sensei-lms' ); ?>
</label>
<p>
<span class="description"><?php echo esc_html( $args['data']['description'] ); ?></span>
</p>
<?php
}
/**
* Renders the Learning Mode template selection setting.
*
* @param array $args The field arguments.
*/
public function render_learning_mode_templates( $args ) {
$settings = $this->get_settings();
$key = $args['key'];
$value = $settings[ $key ];
?>
<p> <?php echo esc_html( $args['data']['description'] ); ?></p>
<ul class="sensei-lm-block-template__options" id="sensei-lm-block-template__options">
<?php foreach ( $args['data']['options'] as $template ) : ?>
<?php
$upsell = isset( $template->upsell ) ? $template->upsell : false;
$title = isset( $template->title ) || empty( $template->title ) ? $template->title : $template->name;
$disabled = (bool) $upsell;
?>
<li class="sensei-lm-block-template__option">
<label class="sensei-lm-block-template__option-label">
<div class="sensei-lm-block-template__option-header">
<input
type="radio"
name="<?php echo esc_attr( "{$this->token}[{$key}]" ); ?>"
value="<?php echo esc_attr( $template->name ); ?>"
<?php disabled( true, $disabled, true ); ?>
<?php checked( $template->name, $value, true ); ?>
/>
<h4 class="sensei-lm-block-template__option-title <?php echo $disabled ? 'sensei-lm-block-template__option-title--disabled' : ''; ?>">
<?php echo esc_html( $title ); ?>
</h4>
<?php if ( $disabled ) : ?>
<a href="<?php echo esc_attr( $upsell['url'] ); ?>" target="_blank" rel="noopener">
<?php echo esc_attr( $upsell['title'] ); ?>
</a>
<?php endif; ?>
</div>
<img alt="<?php esc_attr( $template->title ); ?>" src="<?php echo esc_attr( $template->screenshots['thumbnail'] ); ?>" />
</label>
</li>
<?php endforeach; ?>
</ul>
<?php
$inline_data = [
'name' => "{$this->token}[{$key}]",
'value' => $value,
'options' => $args['data']['options'],
'customizeUrl' => Sensei_Course_Theme::get_learning_mode_fse_url(),
'formId' => "{$this->token}-form",
'section' => $args['data']['section'],
];
Sensei()->assets->enqueue( 'learning-mode-templates-styles', 'course-theme/learning-mode-templates/styles.css', [ 'wp-components' ] );
Sensei()->assets->enqueue( 'learning-mode-templates-script', 'course-theme/learning-mode-templates/index.js', [ 'wp-element', 'wp-components' ], true );
wp_add_inline_script(
'learning-mode-templates-script',
sprintf( 'window.sensei = window.sensei || {}; window.sensei.learningModeTemplateSetting = %s;', wp_json_encode( $inline_data ) ),
'before'
);
}
/**
* Enqueue javascript.
*/
public function enqueue_scripts() {
parent::enqueue_scripts();
// Generate nonce for marking section visited action.
wp_add_inline_script(
'sensei-settings',
'window.senseiSettingsSectionVisitNonce = "' . wp_create_nonce( 'sensei-mark-settings-section-visited' ) . '";',
'before'
);
}
/**
* Marks the given section as visited.
*/
public function mark_section_as_visited() {
if ( isset( $_POST['nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'sensei-mark-settings-section-visited' ) ) {
if ( isset( $_POST['section_id'] ) ) {
$section_id = sanitize_key( $_POST['section_id'] );
$visited = get_option( self::VISITED_SECTIONS_OPTION_KEY, [] );
if ( ! in_array( $section_id, $visited, true ) ) {
$visited[] = $section_id;
update_option( self::VISITED_SECTIONS_OPTION_KEY, $visited );
if ( 'appearance-settings' === $section_id ) {
sensei_log_event( 'home_task_complete', [ 'type' => Sensei_Home_Task_Configure_Learning_Mode::get_id() ] );
}
}
}
}
}
}
/**
* Class WooThemes_Sensei_Settings
*
* @ignore only for backward compatibility
* @since 1.9.0
*/
class WooThemes_Sensei_Settings extends Sensei_Settings{}