ID;
}
wp_enqueue_style( 'lp-edit-curriculum' );
wp_enqueue_script( 'lp-edit-course' );
echo self::instance()->html_course_settings( $post_id );
}
/**
* HTML for course settings metabox.
*
* @param int $post_id
*
* @return string
*/
public function html_course_settings( int $post_id ): string {
$tabs_data = LP_Meta_Box_Course::instance()->metabox( $post_id );
$nav_items = '';
$panels = '';
foreach ( $tabs_data as $key => $tab ) {
if ( $key === 'author' && ! is_super_admin() ) {
continue;
}
$class_tab = isset( $tab['class'] ) ? implode( ' ', (array) $tab['class'] ) : '';
$icon = isset( $tab['icon'] ) ? sprintf( '', esc_attr( $tab['icon'] ) ) : '';
$nav_items .= sprintf(
'
%s%s',
esc_attr( $key ),
esc_attr( $key ),
esc_attr( $class_tab ),
esc_attr( $tab['target'] ),
$icon,
esc_html( $tab['label'] )
);
if ( isset( $tab['content'] ) ) {
ob_start();
do_action( 'learnpress/course-settings/before-' . $key );
foreach ( $tab['content'] as $meta_key => $object ) {
if ( is_a( $object, LP_Meta_Box_Field::class ) ) {
$object->id = $meta_key;
$output = $object->output( $post_id );
if ( ! empty( $output ) ) {
echo wp_kses_post( $output );
}
}
}
do_action( 'learnpress/course-settings/after-' . $key );
$panel_html = ob_get_clean();
$panels .= sprintf(
'%s
',
esc_attr( $tab['target'] ),
$panel_html
);
}
}
$wrapper = [
'wrap' => '',
];
return Template::combine_components( $wrapper );
}
/**
* Helper to capture do_action output
*
* @param string $hook
* @param int $post_id
* @param bool $allow_course_builder_context
*
* @return string
*/
private function capture_hook_content( string $hook, int $post_id, bool $allow_course_builder_context = false ): string {
if ( ! $allow_course_builder_context && $this->is_course_builder_context() ) {
return '';
}
ob_start();
$post = get_post( $post_id );
do_action( $hook, $post );
return ob_get_clean();
}
/**
* Render Course Builder-only hook content for course settings.
* This keeps admin behavior unchanged while exposing a safe extension point for builder.
*
* @param int $post_id
*
* @return string
*/
private function capture_course_builder_hook_content( int $post_id ): string {
if ( ! $this->is_course_builder_context() ) {
return '';
}
return $this->capture_hook_content(
'learn-press/course-builder/edit-course/settings/tab/content',
$post_id,
true
);
}
/**
* Check current request is Course Builder page.
*
* @return bool
*/
private function is_course_builder_context(): bool {
if ( ! class_exists( '\\LP_Page_Controller' ) ) {
return false;
}
return \LP_Page_Controller::is_page_course_builder();
}
public function tab_general( $post_id ) {
$course = CourseModel::find( $post_id, true );
$repurchase_option_desc = sprintf(
'1. %s',
__(
'Reset course progress: The course progress and results of student will be removed.',
'learnpress'
)
);
$repurchase_option_desc .= '
' . sprintf(
'2. %s',
__(
'Keep course progress: The course progress and results of student will remain.',
'learnpress'
)
);
$repurchase_option_desc .= '
' . sprintf(
'3. %s',
__(
'Open popup: The student can decide whether their course progress will be reset with the confirm popup.',
'learnpress'
)
);
$max_students_desc = esc_html__(
'The maximum number of students that can join a course. Set 0 for unlimited.',
'learnpress'
);
$max_students_desc .= '
' . esc_html__( 'Not apply for case "No enroll requirement".', 'learnpress' );
$is_enable_allow_course_repurchase = false;
if ( $course instanceof CourseModel ) {
$is_enable_allow_course_repurchase = $course->get_meta_value_by_key( CoursePostModel::META_KEY_ALLOW_COURSE_REPURCHASE, 'no' ) === 'yes';
}
$fake_students_desc = esc_html__( 'Fake students enrolled for the course.', 'learnpress' );
$fake_students_desc .= '
' . esc_html__( 'It only to display, not calculate', 'learnpress' );
return apply_filters(
'lp/course/meta-box/fields/general',
array(
'_lp_duration' => new LP_Meta_Box_Duration_Field(
esc_html__( 'Duration', 'learnpress' ),
esc_html__( 'Set to 0 for the lifetime access.', 'learnpress' ),
'10 week',
array(
'default_time' => 'week',
'custom_attributes' => array(
'min' => '0',
'step' => '1',
),
)
),
'_lp_block_expire_duration' => new LP_Meta_Box_Checkbox_Field(
esc_html__( 'Block content', 'learnpress' ),
esc_html__( 'When the duration expires, the course is blocked.', 'learnpress' ),
'no'
),
'_lp_block_finished' => new LP_Meta_Box_Checkbox_Field(
'',
esc_html__( 'Block the course after the student finished this course.', 'learnpress' ),
'yes'
),
'_lp_allow_course_repurchase' => new LP_Meta_Box_Checkbox_Field(
__( 'Allow Repurchase', 'learnpress' ),
esc_html__( 'Allow users to repurchase this course after it has been finished or blocked (Do not apply to free courses or Create Order manual).', 'learnpress' ),
'no'
),
'_lp_course_repurchase_option' => new LP_Meta_Box_Select_Field(
esc_html__( 'Repurchase action', 'learnpress' ),
$repurchase_option_desc,
'reset',
array(
'options' => array(
'reset' => esc_html__( 'Reset course progress', 'learnpress' ),
'keep' => esc_html__( 'Keep course progress', 'learnpress' ),
'popup' => esc_html__( 'Open popup', 'learnpress' ),
),
'dependency' => [
'name' => '_lp_allow_course_repurchase',
'is_disable' => ! $is_enable_allow_course_repurchase,
],
)
),
'_lp_level' => new LP_Meta_Box_Select_Field(
esc_html__( 'Level', 'learnpress' ),
esc_html__( 'Choose a difficulty level.', 'learnpress' ),
'all',
array(
'options' => lp_course_level(),
)
),
'_lp_students' => new LP_Meta_Box_Text_Field(
esc_html__( 'Fake Students Enrolled', 'learnpress' ),
$fake_students_desc,
0,
array(
'type_input' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1',
),
'style' => 'width: 70px;',
)
),
'_lp_max_students' => new LP_Meta_Box_Text_Field(
esc_html__( 'Max student', 'learnpress' ),
$max_students_desc,
0,
array(
'type_input' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1',
),
'style' => 'width: 70px;',
)
),
'_lp_retake_count' => new LP_Meta_Box_Text_Field(
esc_html__( 'Re-take Course', 'learnpress' ),
esc_html__( 'The number of times a user can learn again from this course. To disable, set to 0.', 'learnpress' ),
0,
array(
'type_input' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1',
),
'style' => 'width: 70px;',
)
),
'_lp_has_finish' => new LP_Meta_Box_Checkbox_Field(
esc_html__( 'Finish button', 'learnpress' ),
esc_html__( 'Allow showing the finish button when the student has completed all items but has not passed the course assessment yet.', 'learnpress' ),
'yes'
),
'_lp_featured' => new LP_Meta_Box_Checkbox_Field(
esc_html__( 'Featured list', 'learnpress' ),
esc_html__( 'Add the course to the Featured List.', 'learnpress' ),
'no'
),
'_lp_featured_review' => new LP_Meta_Box_Textarea_Field(
esc_html__( 'Featured review', 'learnpress' ),
esc_html__( 'A good review to promote the course.', 'learnpress' )
),
'_lp_external_link_buy_course' => new LP_Meta_Box_Text_Field(
esc_html__( 'External link', 'learnpress' ),
esc_html__( 'Normally used for offline classes. Ex: link to a contact page. Format: https://google.com', 'learnpress' ),
'',
array(
'desc_tip' => 'You can apply for case: user register form.
You accept for user can learn courses by add manual order on backend',
)
),
),
$post_id
);
}
public function tab_offline( $post_id ): array {
$course = CourseModel::find( $post_id, true );
$is_offline_course = false;
if ( $course instanceof CourseModel ) {
$is_offline_course = $course->is_offline();
}
return apply_filters(
'lp/course/meta-box/fields/offline',
array(
CoursePostModel::META_KEY_OFFLINE_COURSE => new LP_Meta_Box_Checkbox_Field(
esc_html__( 'Enable offline course', 'learnpress' ),
esc_html__(
'When you enable the offline course feature, the system will disable certain online course functions, such as curriculum, finish button, re-take course, block content, repurchase. After checking the checkbox, make sure to click the "Update" button to apply the changes successfully.',
'learnpress'
),
'no'
),
CoursePostModel::META_KEY_OFFLINE_LESSON_COUNT => new LP_Meta_Box_Text_Field(
esc_html__( 'Lessons', 'learnpress' ),
esc_html__( 'Total lessons of the course.', 'learnpress' ),
10,
[
'type_input' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1',
),
'dependency' => [
'name' => '_lp_offline_course',
'is_disable' => ! $is_offline_course,
],
]
),
CoursePostModel::META_KEY_DELIVER => new LP_Meta_Box_Select_Field(
esc_html__( 'Delivery Type', 'learnpress' ),
esc_html__( 'How your content is conveyed to students.', 'learnpress' ),
'private_1_1',
[
'options' => Config::instance()->get( 'course-deliver-type' ),
'dependency' => [
'name' => '_lp_offline_course',
'is_disable' => ! $is_offline_course,
],
]
),
CoursePostModel::META_KEY_ADDRESS => new LP_Meta_Box_Text_Field(
esc_html__( 'Address', 'learnpress' ),
esc_html__( 'You can enter the physical address of your class or specify the meeting method (e.g., Zoom, Google Meet, etc.).', 'learnpress' ),
'',
[
'dependency' => [
'name' => '_lp_offline_course',
'is_disable' => ! $is_offline_course,
],
]
),
),
$post_id
);
}
public function tab_price( $post_id ): array {
$key_exists = LP_Database::getInstance()->check_key_postmeta_exists( $post_id, '_lp_regular_price' );
$price = get_post_meta( $post_id, '_lp_price', true );
$regular_price = $key_exists ? get_post_meta( $post_id, '_lp_regular_price', true ) : $price;
$sale_price = get_post_meta( $post_id, '_lp_sale_price', true );
$is_enable_no_required_enroll = get_post_meta( $post_id, '_lp_no_required_enroll', true ) === 'yes' ? 1 : 0;
return apply_filters(
'lp/course/meta-box/fields/price',
array(
'_lp_regular_price' => new LP_Meta_Box_Text_Field(
esc_html__( 'Regular price', 'learnpress' ),
sprintf( __( 'Set a regular price (%s). Leave it blank for Free.', 'learnpress' ), learn_press_get_currency() ),
$regular_price,
array(
'type_input' => 'text',
'custom_attributes' => array(
'min' => '0',
'step' => '0.01',
),
'style' => 'width: 150px;',
'class' => 'lp_meta_box_regular_price',
'dependency' => [
'name' => '_lp_no_required_enroll',
'is_disable' => $is_enable_no_required_enroll,
],
)
),
'_lp_sale_price' => new LP_Meta_Box_Text_Field(
esc_html__( 'Sale price', 'learnpress' ),
'' . esc_html__( 'Schedule', 'learnpress' ) . '',
$sale_price,
array(
'type_input' => 'text',
'custom_attributes' => array(
'min' => '0',
'step' => '0.01',
),
'style' => 'width: 150px;',
'class' => 'lp_meta_box_sale_price',
'dependency' => [
'name' => '_lp_no_required_enroll',
'is_disable' => $is_enable_no_required_enroll,
],
)
),
'_lp_sale_start' => new LP_Meta_Box_Date_Field(
esc_html__( 'Sale start dates', 'learnpress' ),
'',
'',
array(
'wrapper_class' => 'lp_sale_start_dates_fields',
'placeholder' => _x( 'From…', 'placeholder', 'learnpress' ),
'dependency' => [
'name' => '_lp_no_required_enroll',
'is_disable' => $is_enable_no_required_enroll,
],
)
),
'_lp_sale_end' => new LP_Meta_Box_Date_Field(
esc_html__( 'Sale end dates', 'learnpress' ),
'',
'',
array(
'wrapper_class' => 'lp_sale_end_dates_fields',
'placeholder' => _x( 'To…', 'placeholder', 'learnpress' ),
'cancel' => true,
'dependency' => [
'name' => '_lp_no_required_enroll',
'is_disable' => $is_enable_no_required_enroll,
],
)
),
'_lp_price_prefix' => new LP_Meta_Box_Text_Field(
esc_html__( 'Price prefix', 'learnpress' ),
esc_html__( 'Show additional information placed before the price such as: Only, From, Up to...', 'learnpress' ),
''
),
'_lp_price_suffix' => new LP_Meta_Box_Text_Field(
esc_html__( 'Price Suffix', 'learnpress' ),
esc_html__( 'Show additional information placed after the price such as: Included Tax, Per Hour, (Per Week)...', 'learnpress' ),
''
),
'_lp_no_required_enroll' => new LP_Meta_Box_Checkbox_Field(
esc_html__( 'There is no enrollment requirement', 'learnpress' ),
esc_html__( 'Students can see the content of all course items and take the quiz without logging in.', 'learnpress' ),
'no'
),
),
$post_id
);
}
public function tab_author( $thepostid ) {
$post = get_post( $thepostid );
$author_id = $post ? $post->post_author : get_current_user_id();
$options = array();
// Code old only use for addon Frontend Editor v4.0.4
// Code old only use for addon Co-Instructor v4.0.2
$can_get_options_users = false;
if ( class_exists( 'LP_Addon_Frontend_Editor_Preload' )
&& defined( 'LP_ADDON_FRONTEND_EDITOR_VER' )
&& version_compare( LP_ADDON_FRONTEND_EDITOR_VER, '4.0.5', '<' ) ) {
$can_get_options_users = true;
}
if ( $can_get_options_users ) {
$author_roles = array( ADMIN_ROLE, LP_TEACHER_ROLE );
$author_roles = apply_filters( 'learn_press_course_author_role_meta_box', $author_roles );
$authors = get_users( [ 'role__in' => $author_roles ] );
/**
* @var WP_User $author
*/
foreach ( $authors as $author ) {
$options[ $author->ID ] = $author->display_name . ' (#' . $author->ID . ')';
}
}
// Code old only use for addon Frontend Editor v4.0.4
$data_struct = [
'urlApi' => get_rest_url( null, 'lp/v1/admin/tools/search-user' ),
'dataSendApi' => [
'role_in' => ADMIN_ROLE . ',' . LP_TEACHER_ROLE,
],
'dataType' => 'users',
'keyGetValue' => [
'value' => 'ID',
'text' => '{{display_name}}(#{{ID}})',
'key_render' => [
'display_name' => 'display_name',
'user_email' => 'user_email',
'ID' => 'ID',
],
],
'setting' => [
'plugins' => array(),
],
];
return apply_filters(
'lp/course/meta-box/fields/author',
array(
// Not use key 'post_author' on single edit course, it special key of WP, Gutenberg save will not submit this key.
'_post_author' => new LP_Meta_Box_Select_Field(
esc_html__( 'Author', 'learnpress' ),
'',
$author_id,
array(
'options' => $options,
'style' => 'min-width:200px;',
'tom_select' => true,
'custom_attributes' => [ 'data-struct' => htmlentities2( json_encode( $data_struct ) ) ],
)
),
)
);
}
public function tab_material( $thepostid ) {
return apply_filters(
'lp/course/meta-box/fields/material',
array(
'_lp_course_material' => new LP_Meta_Box_Material_Fields(),
)
);
}
public function tab_assessment( $thepostid ) {
$post = get_post( $thepostid );
$course_result_desc = '';
$course_results = get_post_meta( $thepostid, '_lp_course_result', true );
$course_result_desc .= __( 'The method of evaluating a student\'s performance in a course.', 'learnpress' );
$course_result_desc .= sprintf(
'
%s',
__( 'Note: changing the evaluation type will affect the assessment results of student learning.', 'learnpress' )
);
if ( $course_results == 'evaluate_final_quiz' && ! get_post_meta( $thepostid, '_lp_final_quiz', true ) ) {
$course_result_desc .= __( '
Note! There is no final quiz in the course. Please add a final quiz.', 'learnpress' );
}
$final_quizz_passing = '';
$course = learn_press_get_course( $thepostid );
if ( $course ) {
$passing_grade = $url = '';
$final_quiz = $course->get_final_quiz();
if ( $final_quiz ) {
$passing_grade = get_post_meta( $final_quiz, '_lp_passing_grade', true );
if ( '' === (string) $passing_grade ) {
$quiz_model = QuizPostModel::find( absint( $final_quiz ), true );
if ( $quiz_model ) {
$passing_grade = $quiz_model->get_passing_grade();
} else {
$passing_grade = 80;
}
}
$url = get_edit_post_link( $final_quiz ) . '#_lp_passing_grade';
$final_quizz_passing = '
';
}
}
return apply_filters(
'lp/course/meta-box/fields/assessment',
array(
'_lp_course_result' => new LP_Meta_Box_Radio_Field(
esc_html__( 'Evaluation', 'learnpress' ),
$course_result_desc,
'evaluate_lesson',
array(
'options' => learn_press_course_evaluation_methods( $thepostid, '', $final_quizz_passing ),
)
),
'_lp_passing_condition' => new LP_Meta_Box_Text_Field(
esc_html__( 'Passing Grade(%)', 'learnpress' ),
esc_html__( 'The conditions that must be achieved to finish the course.', 'learnpress' ),
'80',
array(
'type_input' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '0.01',
'max' => '100',
),
'style' => 'width: 60px;',
)
),
)
);
}
public function tab_extra( $thepostid ) {
return apply_filters(
'lp/course/meta-box/fields/extra',
array(
'_lp_requirements' => new LP_Meta_Box_Extra_Field(
esc_html__( 'Requirements', 'learnpress' ),
'',
array()
),
'_lp_target_audiences' => new LP_Meta_Box_Extra_Field(
esc_html__( 'Target Audience', 'learnpress' ),
'',
array()
),
'_lp_key_features' => new LP_Meta_Box_Extra_Field(
esc_html__( 'Key Features', 'learnpress' ),
'',
array()
),
'_lp_faqs' => new LP_Meta_Box_Extra_Faq_Field(
esc_html__( 'FAQs', 'learnpress' ),
'',
array()
),
)
);
}
public function data_tabs_sort( $a, $b ) {
if ( ! isset( $a['priority'], $b['priority'] ) ) {
return - 1;
}
if ( $a['priority'] === $b['priority'] ) {
return 0;
}
return $a['priority'] < $b['priority'] ? - 1 : 1;
}
}