PluginProbe
Tutor LMS – eLearning and online course solution / 1.9.10
Tutor LMS – eLearning and online course solution v1.9.10
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 All 191 releases
← All changes | classes/Course.php +886 -3030 4.0.31.9.10 View file →
@@ -1,2575 +1,959 @@
1 1 <?php
2 -/**
3 - * Manage Course Related Logic
4 - *
5 - * @package Tutor
6 - * @author Themeum <support@themeum.com>
7 - * @link https://themeum.com
8 - * @since 1.0.0
9 - */
10 -
11 2 namespace TUTOR;
12 3
13 -defined( 'ABSPATH' ) || exit;
4 +if ( ! defined( 'ABSPATH' ) )
5 + exit;
14 6
15 -use Tutor\Components\Button;
16 -use Tutor\Components\Constants\Size;
17 -use Tutor\Components\Constants\Variant;
18 -use Tutor\Components\Progress;
19 -use Tutor\Ecommerce\Tax;
20 -use Tutor\Models\QuizModel;
21 -use Tutor\Helpers\HttpHelper;
22 -use Tutor\Models\CourseModel;
23 -use Tutor\Components\Tooltip;
24 -use Tutor\Ecommerce\Ecommerce;
25 -use Tutor\Helpers\DateTimeHelper;
26 -use Tutor\Traits\JsonResponse;
27 -use Tutor\Helpers\ValidationHelper;
28 -use Tutor\Models\EnrollmentModel;
29 -use Tutor\Options_V2;
30 -use TUTOR_ASSIGNMENTS\Assignments;
31 -
32 -/**
33 - * Course Class
34 - *
35 - * @since 1.0.0
36 - */
37 7 class Course extends Tutor_Base {
38 - use JsonResponse;
39 -
40 - /**
41 - * Course Price type
42 - *
43 - * @since 3.0.0
44 - *
45 - * @var string
46 - */
47 - const PRICE_TYPE_FREE = 'free';
48 - const PRICE_TYPE_PAID = 'paid';
49 - const PRICE_TYPE_SUBSCRIPTION = 'subscription';
50 -
51 - /**
52 - * Course price and sale price
53 - *
54 - * @since 3.0.0
55 - */
56 - const COURSE_PRICE_TYPE_META = '_tutor_course_price_type';
57 - const COURSE_PRICE_META = 'tutor_course_price';
58 - const COURSE_SALE_PRICE_META = 'tutor_course_sale_price';
59 - const COURSE_SELLING_OPTION_META = 'tutor_course_selling_option';
60 - const COURSE_PRODUCT_ID_META = '_tutor_course_product_id';
61 -
62 - /**
63 - * Selling option constants
64 - *
65 - * @since 3.0.0
66 - */
67 - const SELLING_OPTION_ONE_TIME = 'one_time';
68 - const SELLING_OPTION_SUBSCRIPTION = 'subscription';
69 - const SELLING_OPTION_BOTH = 'both'; // onetime and subscription.
70 - const SELLING_OPTION_MEMBERSHIP = 'membership';
71 - const SELLING_OPTION_ALL = 'all';
72 -
73 - /**
74 - * Tax collection settings meta
75 - *
76 - * @since 3.7.0
77 - */
78 - const TAX_ON_SINGLE_META = 'tutor_tax_on_single';
79 - const TAX_ON_SUBSCRIPTION_META = 'tutor_tax_on_subscription';
80 -
81 - /**
82 - * Additional data meta
83 - *
84 - * @since 3.6.0
85 - */
86 - const COURSE_BENEFITS_META = '_tutor_course_benefits';
87 - const COURSE_REQUIREMENTS_META = '_tutor_course_requirements';
88 - const COURSE_TARGET_AUDIENCE_META = '_tutor_course_target_audience';
89 - const COURSE_MATERIAL_INCLUDE_META = '_tutor_course_material_includes';
90 - const COURSE_DURATION_META = '_course_duration';
91 -
92 - /**
93 - * Course settings meta
94 - *
95 - * @since 3.6.0
96 - */
97 - const COURSE_ENABLE_QA_META = '_tutor_enable_qa';
98 - const PUBLIC_COURSE_META = '_tutor_is_public_course';
99 - const COURSE_SETTINGS_META = '_tutor_course_settings';
100 - const COURSE_LEVEL_META = '_tutor_course_level';
101 -
102 - /**
103 - * Meta keys used to identify and store Tutor course order data.
104 - *
105 - * @since 4.0.0
106 - */
107 - const IS_TUTOR_ORDER_FOR_COURSE_META = '_is_tutor_order_for_course';
108 - const TUTOR_ORDER_FOR_COURSE_ID_META = '_tutor_order_for_course_id_';
109 -
110 - /**
111 - * Additional course meta info
112 - *
113 - * @var array
114 - */
115 - private $additional_meta = array(
116 - '_tutor_enable_qa',
117 - '_tutor_is_public_course',
8 +
9 + private $additional_meta=array(
10 + '_tutor_disable_qa',
11 + '_tutor_is_public_course'
118 12 );
119 13
120 - /**
121 - * Constructor
122 - *
123 - * @since 1.0.0
124 - * @since 3.0.0 $register_hooks param added to reuse this class.
125 - *
126 - * @param bool $register_hooks register hooks.
127 - *
128 - * @return void
129 - */
130 - public function __construct( $register_hooks = true ) {
14 + public function __construct() {
131 15 parent::__construct();
16 +
17 + add_action( 'add_meta_boxes', array($this, 'register_meta_box') );
18 + add_action('save_post_'.$this->course_post_type, array($this, 'save_course_meta'), 10, 2);
19 + add_action('wp_ajax_tutor_add_course_topic', array($this, 'tutor_add_course_topic'));
20 + add_action('wp_ajax_tutor_update_topic', array($this, 'tutor_update_topic'));
132 21
133 - if ( ! $register_hooks ) {
134 - return;
135 - }
22 + //Add Column
23 + add_filter( "manage_{$this->course_post_type}_posts_columns", array($this, 'add_column'), 10,1 );
24 + add_action( "manage_{$this->course_post_type}_posts_custom_column" , array($this, 'custom_lesson_column'), 10, 2 );
136 25
137 - add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ), 10, 2 );
26 + add_action('admin_action_tutor_delete_topic', array($this, 'tutor_delete_topic'));
27 + add_action('admin_action_tutor_delete_announcement', array($this, 'tutor_delete_announcement'));
138 28
139 - add_action( 'wp_ajax_tutor_save_topic', array( $this, 'tutor_save_topic' ) );
140 - add_action( 'wp_ajax_tutor_delete_topic', array( $this, 'tutor_delete_topic' ) );
29 + //Frontend Action
30 + add_action('template_redirect', array($this, 'enroll_now'));
31 + add_action('init', array($this, 'mark_course_complete'));
141 32
33 + //Modal Perform
34 + add_action('wp_ajax_tutor_load_instructors_modal', array($this, 'tutor_load_instructors_modal'));
35 + add_action('wp_ajax_tutor_add_instructors_to_course', array($this, 'tutor_add_instructors_to_course'));
36 + add_action('wp_ajax_detach_instructor_from_course', array($this, 'detach_instructor_from_course'));
37 +
142 38 /**
143 - * Frontend Action
39 + * Frontend Dashboard
144 40 */
145 - add_action( 'template_redirect', array( $this, 'enroll_now' ) );
146 - add_action( 'init', array( $this, 'mark_course_complete' ) );
41 + add_action('wp_ajax_tutor_delete_dashboard_course', array($this, 'tutor_delete_dashboard_course'));
147 42
148 43 /**
149 - * Frontend Dashboard
44 + * Gutenberg author support
150 45 */
151 - add_action( 'wp_ajax_tutor_delete_dashboard_course', array( $this, 'tutor_delete_dashboard_course' ) );
46 + add_filter('wp_insert_post_data', array($this, 'tutor_add_gutenberg_author'), '99', 2);
152 47
153 48 /**
154 - * Gutenberg author support
49 + * Frontend metabox supports for course builder
50 + * @since v.1.3.4
155 51 */
156 - add_filter( 'wp_insert_post_data', array( $this, 'tutor_add_gutenberg_author' ), 99, 2 );
52 + add_action('tutor/dashboard_course_builder_form_field_after', array($this, 'register_meta_box_in_frontend'));
157 53
158 54 /**
159 55 * Do Stuff for the course save from frontend
160 56 */
161 - add_action( 'save_tutor_course', array( $this, 'attach_product_with_course' ), 10, 2 );
57 + add_action('save_tutor_course', array($this, 'attach_product_with_course'), 10, 2);
162 58
163 59 /**
60 + * Add course level to course settings
61 + * @since v.1.4.1
62 + */
63 + add_action('tutor_course/settings_tab_content/after/general', array($this, 'add_course_level_to_settings'));
64 +
65 + /**
164 66 * Enable Disable Course Details Page Feature
165 - *
166 67 * @since v.1.4.8
167 68 */
168 69 $this->course_elements_enable_disable();
169 70
170 71 /**
72 + * @since v.1.4.8
171 73 * Check if course starting, set meta if starting
172 - *
173 - * @since v.1.4.8
174 74 */
175 - add_action( 'tutor_lesson_load_before', array( $this, 'tutor_lesson_load_before' ) );
75 + add_action('tutor_lesson_load_before', array($this, 'tutor_lesson_load_before'));
176 76
177 77 /**
78 + * @since v.1.4.9
178 79 * Filter product in shop page
179 - *
180 - * @since v.1.4.9
181 80 */
182 81 $this->filter_product_in_shop_page();
183 82
184 - /**
185 - * Remove the course price if enrolled
186 - *
187 - * @since 1.5.8
188 - */
189 - add_filter( 'tutor_course_price', array( $this, 'remove_price_if_enrolled' ) );
83 + /**
84 + * Remove the course price if enrolled
85 + * @since 1.5.8
86 + */
87 + add_filter('tutor_course_price', array($this, 'remove_price_if_enrolled'));
190 88
191 - /**
192 - * Remove course complete button if course completion is strict mode
193 - *
194 - * @since v.1.6.1
195 - */
196 - add_filter( 'tutor_course/single/complete_form', array( $this, 'tutor_lms_hide_course_complete_btn' ) );
197 - add_filter( 'get_gradebook_generate_form_html', array( $this, 'get_generate_greadbook' ) );
89 + /**
90 + * Remove course complete button if course completion is strict mode
91 + * @since v.1.6.1
92 + */
93 + add_filter('tutor_course/single/complete_form', array($this, 'tutor_lms_hide_course_complete_btn'));
94 + add_filter('get_gradebook_generate_form_html', array($this, 'get_generate_greadbook'));
95 +
96 + /**
97 + * Add social share content in header
98 + * @since v.1.6.3
99 + */
100 + add_action('wp_head', array($this, 'social_share_content'));
101 +
102 + /**
103 + * Delete course data after deleted course
104 + * @since v.1.6.6
105 + */
106 + add_action('deleted_post', array($this, 'delete_tutor_course_data'));
107 + add_action('tutor/dashboard_course_builder_form_field_after', array($this, 'tutor_course_setting_metabox_frontend'));
198 108
199 109 /**
200 - * Add social share content in header
201 - *
202 - * @since v.1.6.3
203 - */
204 - add_action( 'wp_head', array( $this, 'social_share_content' ) );
110 + * Delete course data after deleted course
111 + * @since v.1.8.2
112 + */
113 + add_action('before_delete_post', array($this, 'delete_associated_enrollment'));
205 114
206 115 /**
207 - * Delete course data after deleted course
208 - *
209 - * @since v.1.6.6
210 - */
211 - add_action( 'deleted_post', array( new CourseModel(), 'delete_course_data' ) );
212 -
213 - /**
214 - * Delete course data after deleted course
215 - *
216 - * @since v.1.8.2
217 - */
218 - add_action( 'before_delete_post', array( $this, 'delete_associated_enrollment' ) );
219 -
220 - /**
221 116 * Show only own uploads in media library if user is instructor
222 - *
223 117 * @since v1.8.9
224 118 */
225 - add_filter( 'posts_where', array( $this, 'restrict_media' ) );
119 + add_filter('posts_where', array($this, 'restrict_media' ) );
226 120
227 121 /**
228 122 * Restrict new enrol/purchase button if course member limit reached
229 - *
230 123 * @since v1.9.0
231 124 */
232 - add_filter( 'tutor_course_restrict_new_entry', array( $this, 'restrict_new_student_entry' ) );
125 + add_filter('tutor_course_restrict_new_entry', array($this, 'restrict_new_student_entry'));
233 126
234 127 /**
235 128 * Reset course progress on retake
236 - *
237 129 * @since v1.9.5
238 130 */
239 - add_action( 'wp_ajax_tutor_reset_course_progress', array( $this, 'tutor_reset_course_progress' ) );
240 -
131 + add_action( 'wp_ajax_tutor_reset_course_progress', array($this, 'tutor_reset_course_progress') );
132 +
241 133 /**
242 134 * Popup for review
243 - *
244 135 * @since v1.9.7
245 136 */
246 - add_action( 'wp_footer', array( $this, 'popup_review_form' ) );
247 - add_action( 'wp_ajax_tutor_clear_review_popup_data', array( $this, 'clear_review_popup_data' ) );
137 + add_action( 'wp_footer', array($this, 'popup_review_form') );
248 138
249 139 /**
250 140 * Do enroll after login if guest take enroll attempt
251 - *
141 + *
252 142 * @since 1.9.8
253 143 */
254 - add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 2 );
144 + add_action( 'tutor_do_enroll_after_login_if_attempt', array( $this, 'enroll_after_login_if_attempt' ), 10, 1 );
145 + }
255 146
256 - add_action( 'wp_ajax_tutor_update_course_content_order', array( $this, 'ajax_update_course_content_order' ) );
147 + public function restrict_new_student_entry($content) {
257 148
258 - add_action( 'wp_ajax_tutor_get_wc_product', array( $this, 'get_wc_product' ) );
259 - add_action( 'wp_ajax_tutor_get_wc_products', array( $this, 'get_wc_products' ) );
260 -
261 - add_action( 'wp_ajax_tutor_course_enrollment', array( $this, 'course_enrollment' ) );
262 -
263 - /**
264 - * After trash a course redirect to course list page
265 - *
266 - * @since 2.1.7
267 - */
268 - add_action( 'trashed_post', __CLASS__ . '::redirect_to_course_list_page' );
269 -
270 - add_filter( 'tutor_enroll_required_login_class', array( $this, 'add_enroll_required_login_class' ) );
271 -
272 - /**
273 - * Remove wp trash button if instructor settings is disabled
274 - *
275 - * @since 2.7.3
276 - */
277 - add_action( 'tutor_option_save_after', array( $this, 'disable_course_trash_instructor' ) );
278 -
279 - /**
280 - * New course builder
281 - *
282 - * @since 3.0.0
283 - */
284 - add_action( 'template_redirect', array( $this, 'load_course_builder' ) );
285 - add_action( 'tutor_before_course_builder_load', array( $this, 'enqueue_course_builder_assets' ) );
286 - add_filter( 'tutor_localize_data', array( $this, 'localize_course_builder_data' ) );
287 -
288 - /**
289 - * Ajax list
290 - *
291 - * @since 3.0.0
292 - */
293 - add_action( 'wp_ajax_tutor_create_new_draft_course', array( $this, 'ajax_create_new_draft_course' ) );
294 - add_action( 'wp_ajax_tutor_course_list', array( $this, 'ajax_course_list' ) );
295 - add_action( 'wp_ajax_tutor_create_course', array( $this, 'ajax_create_course' ) );
296 - add_action( 'wp_ajax_tutor_course_details', array( $this, 'ajax_course_details' ) );
297 - add_action( 'wp_ajax_tutor_course_contents', array( $this, 'ajax_course_contents' ) );
298 - add_action( 'wp_ajax_tutor_update_course', array( $this, 'ajax_update_course' ) );
299 - add_action( 'wp_ajax_tutor_unlink_page_builder', array( $this, 'ajax_unlink_page_builder' ) );
300 -
301 - add_filter( 'tutor_user_list_access', array( $this, 'user_list_access_for_instructor' ) );
302 - add_filter( 'tutor_user_list_args', array( $this, 'user_list_args_for_instructor' ) );
303 -
304 - add_filter( 'template_include', array( $this, 'handle_password_protected' ) );
305 - add_action( 'login_form_postpass', array( $this, 'handle_password_submit' ) );
306 - add_filter( 'post_password_required', array( $this, 'bypass_password_for_enrolled' ), 10, 2 );
307 - add_filter( 'the_preview', array( $this, 'handle_schedule_courses' ) );
308 -
309 - add_action( 'tutor_course_action_btn', array( $this, 'render_course_action_btn' ) );
310 - add_action( 'wp_ajax_tutor_complete_course', array( $this, 'ajax_tutor_complete_course' ) );
311 - }
312 -
313 - /**
314 - * Handle schedule courses preview for instructors.
315 - *
316 - * @since 3.5.0
317 - *
318 - * @param \WP_Post $content the preview post content.
319 - *
320 - * @return \WP_Post
321 - */
322 - public function handle_schedule_courses( $content ) {
323 - global $wp_query;
324 - $course_coming_soon_enabled = (int) get_post_meta( $content->ID, '_tutor_course_enable_coming_soon', true );
325 - $is_instructor = tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $content->ID, true );
326 - if ( ! CourseModel::get_post_types( $content ) || current_user_can( 'administrator' ) || $is_instructor || $course_coming_soon_enabled ) {
149 + if(!tutils()->is_course_fully_booked()) {
150 + // No restriction if not fully booked
327 151 return $content;
328 152 }
329 -
330 - $wp_query->set_404();
331 - status_header( 404 );
332 - nocache_headers();
333 - return $content;
153 +
154 + return '<span class="tutor-course-booked-fully">
155 + <img src="' . tutor()->url . '/assets/images/icon-warning-info.svg"/>
156 + <span>' . __('Fully booked', 'tutor') . '</span>
157 + </span>';
334 158 }
335 159
336 - /**
337 - * Handle password protected course and bundle form submission.
338 - *
339 - * @since 3.2.1
340 - *
341 - * @return void
342 - */
343 - public function handle_password_submit() {
344 - if ( Input::has( 'post_password' ) && Input::has( 'course_id' ) ) {
345 - $course_id = Input::post( 'course_id', 0, Input::TYPE_NUMERIC );
346 - $password_required = post_password_required( $course_id );
347 - if ( $password_required ) {
348 - set_transient( 'tutor_post_password_error', __( 'Invalid password', 'tutor' ) );
160 + function restrict_media( $where ){
161 +
162 + if( isset( $_POST['action'] ) && $_POST['action'] == 'query-attachments' && tutor_utils()->is_instructor()){
163 + if(!tutor_utils()->has_user_role(array('administrator', 'editor'))) {
164 + $where .= ' AND post_author=' . get_current_user_id();
349 165 }
350 166 }
167 +
168 + return $where;
351 169 }
352 170
353 171 /**
354 - * Bypass password protection for enrolled users.
355 - *
356 - * @since 4.0.0
357 - *
358 - * @param bool $required Whether the password is required.
359 - * @param WP_Post $post The post object.
360 - *
361 - * @return bool false if the current user is enrolled, original value otherwise.
172 + * Registering metabox
362 173 */
363 - public function bypass_password_for_enrolled( $required, $post ) {
364 - if ( ! $required ) {
365 - return $required;
174 + public function register_meta_box(){
175 + $coursePostType = tutor()->course_post_type;
176 + $course_marketplace = tutor_utils()->get_option('enable_course_marketplace');
177 + //add_meta_box( 'tutor-course-levels', __( 'Course Level', 'tutor' ), array($this, 'course_level_metabox'), $coursePostType );
178 + add_meta_box( 'tutor-course-topics', __( 'Course Builder', 'tutor' ), array($this, 'course_meta_box'), $coursePostType );
179 + add_meta_box( 'tutor-course-additional-data', __( 'Additional Data', 'tutor' ), array($this, 'course_additional_data_meta_box'), $coursePostType );
180 + add_meta_box( 'tutor-course-videos', __( 'Video', 'tutor' ), array($this, 'video_metabox'), $coursePostType );
181 + if ($course_marketplace) {
182 + add_meta_box( 'tutor-instructors', __( 'Instructors', 'tutor' ), array( $this, 'instructors_metabox' ), $coursePostType );
366 183 }
367 184
368 - $post_types = array( tutor()->course_post_type, tutor()->bundle_post_type );
369 - if ( in_array( $post->post_type, $post_types, true ) && EnrollmentModel::is_enrolled( $post->ID ) ) {
370 - return false;
371 - }
372 -
373 - return $required;
185 + /**
186 + * Tutor course sidebar settings metabox
187 + * @since v.1.7.0
188 + */
189 + add_meta_box( 'tutor-course-sidebar-settings', __( 'Tutor Settings', 'tutor' ), array($this, 'tutor_course_setting_metabox'), $coursePostType, 'side' );
374 190 }
375 191
376 - /**
377 - * Handle password protected course/bundle.
378 - *
379 - * @since 3.0.0
380 - *
381 - * @param string $template template path.
382 - *
383 - * @return string template path.
384 - */
385 - public function handle_password_protected( $template ) {
386 - if ( is_single() ) {
387 - $current_post_type = get_post_type( get_the_ID() );
388 - $post_types = array( tutor()->course_post_type, tutor()->bundle_post_type );
389 - if ( in_array( $current_post_type, $post_types, true ) && post_password_required() ) {
390 - remove_all_filters( 'template_include' );
391 - return tutor()->path . '/templates/single/password-protected.php';
392 - }
393 - }
192 + public function course_meta_box($echo = true){
193 + ob_start();
194 + include tutor()->path.'views/metabox/course-topics.php';
195 + $content = ob_get_clean();
394 196
395 - return $template;
396 - }
397 -
398 - /**
399 - * Remove move to trash button on WordPress editor for instructor.
400 - *
401 - * @since 2.7.3
402 - *
403 - * @return void
404 - */
405 - public function disable_course_trash_instructor() {
406 - $can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );
407 - $role = get_role( tutor()->instructor_role );
408 - if ( ! $can_trash_post ) {
409 - $role->remove_cap( 'delete_tutor_courses' );
410 - $role->remove_cap( 'delete_tutor_course' );
411 - } else {
412 - $role->add_cap( 'delete_tutor_courses' );
413 - $role->add_cap( 'delete_tutor_course' );
197 + if ($echo){
198 + echo $content;
199 + }else{
200 + return $content;
414 201 }
415 202 }
416 203
417 - /**
418 - * Check if the video source type is valid
419 - *
420 - * @since 3.0.0
421 - *
422 - * @param string $source_type source type.
423 - *
424 - * @return boolean
425 - */
426 - private function is_valid_video_source_type( string $source_type ): bool {
427 - $supported_types = tutor_utils()->get_option( 'supported_video_sources', array() );
428 - if ( is_string( $supported_types ) ) {
429 - $supported_types = array( $supported_types );
430 - }
204 + public function course_additional_data_meta_box($echo = true){
431 205
432 - return in_array( $source_type, $supported_types, true );
433 - }
206 + ob_start();
207 + include tutor()->path.'views/metabox/course-additional-data.php';
208 + $content = ob_get_clean();
434 209
435 - /**
436 - * Get course selling options.
437 - *
438 - * @since 3.0.0
439 - *
440 - * @return array
441 - */
442 - public static function get_selling_options() {
443 - return array(
444 - self::SELLING_OPTION_ONE_TIME,
445 - self::SELLING_OPTION_SUBSCRIPTION,
446 - self::SELLING_OPTION_BOTH,
447 - self::SELLING_OPTION_MEMBERSHIP,
448 - self::SELLING_OPTION_ALL,
449 - );
450 - }
451 -
452 - /**
453 - * Get course selling option
454 - *
455 - * @since 3.0.0
456 - *
457 - * @param int $course_id course id.
458 - *
459 - * @return string
460 - */
461 - public static function get_selling_option( $course_id ) {
462 - return get_post_meta( $course_id, self::COURSE_SELLING_OPTION_META, true );
463 - }
464 -
465 - /**
466 - * Validate video source
467 - *
468 - * @since 3.0.0
469 - *
470 - * @param array $params array of params.
471 - * @param array $errors array of errors.
472 - *
473 - * @return void
474 - */
475 - public function validate_video_source( $params, &$errors ) {
476 - if ( isset( $params['video'] ) ) {
477 - $video_source_type = isset( $params['video']['source'] ) ? $params['video']['source'] : '';
478 - if ( tutor_is_rest() ) {
479 - $video_source_type = isset( $params['video']['source_type'] ) ? $params['video']['source_type'] : '';
480 - }
481 -
482 - if ( '' === $video_source_type ) {
483 - $errors['video_source'] = __( 'Video source is required', 'tutor' );
484 - } elseif ( ! $this->is_valid_video_source_type( $video_source_type ) ) {
485 - $errors['video_source'] = __( 'Invalid video source', 'tutor' );
486 - }
210 + if ($echo){
211 + echo $content;
212 + }else{
213 + return $content;
487 214 }
488 215 }
489 216
490 - /**
491 - * Validate scheduled courses
492 - *
493 - * @since 3.3.0
494 - *
495 - * @param array $params array of params.
496 - * @param array $errors array of errors.
497 - *
498 - * @return void
499 - */
500 - public function validate_scheduled_course( $params, &$errors ) {
501 - if ( isset( $params['post_status'] ) && isset( $params['course_settings'] ) ) {
502 - if ( 'future' !== $params['post_status'] ) {
503 - return;
504 - }
217 + public function video_metabox($echo = true){
218 + ob_start();
219 + include tutor()->path.'views/metabox/video-metabox.php';
220 + $content = ob_get_clean();
505 221
506 - $course_settings = $params['course_settings'];
507 -
508 - if ( $course_settings['course_enrollment_period'] && 'no' === $course_settings['course_enrollment_period'] ) {
509 - return;
510 - }
511 -
512 - if ( isset( $course_settings['enrollment_starts_at'] ) && ! empty( $course_settings['enrollment_starts_at'] ) ) {
513 - $enrollment_start = strtotime( $course_settings['enrollment_starts_at'] );
514 - $scheduled_date = strtotime( $params['post_date_gmt'] );
515 -
516 - if ( $enrollment_start < $scheduled_date ) {
517 - $errors['scheduled_course'] = __( 'The enrollment start date cannot be earlier than the course start date', 'tutor' );
518 - }
519 - }
222 + if ($echo){
223 + echo $content;
224 + }else{
225 + return $content;
520 226 }
521 227 }
522 228
523 - /**
524 - * Prepare course categories & tags
525 - *
526 - * @since 3.0.0
527 - *
528 - * @param array $params post params.
529 - * @param array $errors array of errors.
530 - *
531 - * @return void
532 - */
533 - public function prepare_course_cats_tags( &$params, &$errors ) {
534 - if ( isset( $params['course_categories'] ) ) {
535 - if ( ! is_array( $params['course_categories'] ) || empty( $params['course_categories'] ) ) {
536 - $errors['course_categories'] = __( 'Invalid course categories', 'tutor' );
537 - } else {
538 - $params['course_categories'] = $params['course_categories'];
539 - }
540 - }
229 + public function course_level_metabox($echo = true){
230 + ob_start();
231 + include tutor()->path.'views/metabox/course-level-metabox.php';
232 + $content = ob_get_clean();
541 233
542 - if ( isset( $params['course_tags'] ) ) {
543 - if ( ! is_array( $params['course_tags'] ) || empty( $params['course_tags'] ) ) {
544 - $errors['course_tags'] = __( 'Invalid course tags', 'tutor' );
545 - } else {
546 - $params['course_tags'] = $params['course_tags'];
547 - }
234 + if ($echo){
235 + echo $content;
236 + }else{
237 + return $content;
548 238 }
549 239 }
550 240
551 - /**
552 - * Setup course categories and tags.
553 - *
554 - * @since 3.0.0
555 - *
556 - * @param int $post_id Post ID.
557 - * @param array $params Array of params.
558 - *
559 - * @return void
560 - */
561 - public function setup_course_categories_tags( $post_id, $params ) {
562 - if ( isset( $params['course_categories'] ) && is_array( $params['course_categories'] ) ) {
563 - $valid_category_ids = ValidationHelper::validate_term_ids(
564 - $params['course_categories'],
565 - CourseModel::COURSE_CATEGORY
566 - );
567 - wp_set_object_terms( $post_id, $valid_category_ids, CourseModel::COURSE_CATEGORY );
568 - } else {
569 - wp_set_object_terms( $post_id, array(), CourseModel::COURSE_CATEGORY );
570 - }
241 + public function instructors_metabox($echo = true){
242 + ob_start();
243 + include tutor()->path . 'views/metabox/instructors-metabox.php';
244 + $content = ob_get_clean();
571 245
572 - if ( isset( $params['course_tags'] ) && is_array( $params['course_tags'] ) ) {
573 - $valid_tag_ids = ValidationHelper::validate_term_ids(
574 - $params['course_tags'],
575 - CourseModel::COURSE_TAG
576 - );
577 - wp_set_object_terms( $post_id, $valid_tag_ids, CourseModel::COURSE_TAG );
578 - } else {
579 - wp_set_object_terms( $post_id, array(), CourseModel::COURSE_TAG );
246 + if ($echo){
247 + echo $content;
248 + }else{
249 + return $content;
580 250 }
581 251 }
582 252
583 253 /**
584 - * Validate price for course create
585 - *
586 - * @since 3.0.0
587 - *
588 - * @param array $params array of params.
589 - * @param array $errors array of errors.
590 - *
591 - * @return void
254 + * Register metabox in course builder tutor
255 + * @since v.1.3.4
592 256 */
593 - public function validate_price( $params, &$errors ) {
594 - if ( isset( $params['pricing'] ) ) {
595 - $type = $params['pricing']['type'] ?? '';
596 -
597 - if ( '' === $type || ! in_array( $type, array( self::PRICE_TYPE_FREE, self::PRICE_TYPE_PAID ), true ) ) {
598 - $errors['pricing'] = __( 'Invalid price type', 'tutor' );
599 - }
600 -
601 - if ( self::PRICE_TYPE_PAID === $type ) {
602 - $monetize_by = tutor_utils()->get_option( 'monetize_by' );
603 - if ( 'wc' === $monetize_by ) {
604 - $product_id = (int) isset( $params['pricing']['product_id'] ) ? $params['pricing']['product_id'] : 0;
605 - // $product_id = 0 then new WC product will be created.
606 - if ( $product_id ) {
607 - $product = wc_get_product( $product_id );
608 - if ( is_a( $product, 'WC_Product' ) ) {
609 - $is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
610 - if ( $is_linked_with_course ) {
611 - $errors['pricing'] = __( 'Product already linked with course', 'tutor' );
612 - }
613 - }
614 - }
615 - }
616 - }
617 - }
257 + public function register_meta_box_in_frontend(){
258 + do_action('tutor_course_builder_metabox_before', get_the_ID());
259 + course_builder_section_wrap($this->video_metabox($echo = false), __( 'Video', 'tutor' ) );
260 + course_builder_section_wrap($this->course_meta_box($echo = false), __( 'Course Builder', 'tutor' ) );
261 + course_builder_section_wrap($this->instructors_metabox($echo = false), __( 'Instructors', 'tutor' ) );
262 + course_builder_section_wrap($this->course_additional_data_meta_box($echo = false), __( 'Additional Data', 'tutor' ) );
263 + do_action('tutor_course_builder_metabox_after', get_the_ID());
618 264 }
619 265
620 266 /**
621 - * Validate price
267 + * @param $post_ID
622 268 *
623 - * @since 3.0.0
624 - *
625 - * @param array $params array of params.
626 - * @param array $errors array of errors.
627 - * @param int $course_id course id.
628 - *
629 - * @return void
269 + * Insert Topic and attached it with Course
630 270 */
631 - public function validate_price_for_update( $params, &$errors, $course_id ) {
632 - if ( isset( $params['pricing'] ) ) {
633 - $type = $params['pricing']['type'] ?? '';
271 + public function save_course_meta($post_ID, $post){
272 + global $wpdb;
634 273
635 - if ( '' === $type || ! in_array( $type, array( self::PRICE_TYPE_FREE, self::PRICE_TYPE_PAID, self::PRICE_TYPE_SUBSCRIPTION ), true ) ) {
636 - $errors['pricing'] = __( 'Invalid price type', 'tutor' );
637 - }
638 -
639 - if ( self::PRICE_TYPE_PAID === $type ) {
640 - $monetize_by = tutor_utils()->get_option( 'monetize_by' );
641 - if ( 'wc' === $monetize_by ) {
642 - $course_product_id = tutor_utils()->get_course_product_id( $course_id );
643 - $product_id = isset( $params['pricing']['product_id'] ) ? (int) $params['pricing']['product_id'] : 0;
644 -
645 - if ( $product_id ) {
646 - $product = wc_get_product( $product_id );
647 - if ( is_a( $product, 'WC_Product' ) ) {
648 - if ( $course_product_id !== $product_id ) {
649 - $is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
650 - if ( $is_linked_with_course ) {
651 - $errors['pricing'] = __( 'Product already linked with course', 'tutor' );
652 - }
653 - }
654 - } else {
655 - $errors['pricing'] = __( 'Invalid product', 'tutor' );
656 - }
657 - } else {
658 - /**
659 - * If user does not select WC product
660 - * Then automatic WC product will be create name with course title.
661 - */
662 - if ( ! isset( $params['course_price'] ) || ! floatval( $params['course_price'] ) ) {
663 - $errors['pricing'] = __( 'Price is required', 'tutor' );
664 - }
665 - }
666 - }
667 - }
274 + do_action( "tutor_save_course", $post_ID, $post);
275 +
276 + /**
277 + * Save course price type
278 + */
279 + $price_type = tutils()->array_get('tutor_course_price_type', $_POST);
280 + if ($price_type){
281 + update_post_meta($post_ID, '_tutor_course_price_type', $price_type);
668 282 }
669 - }
670 283
671 - /**
672 - * Prepare course meta data for update
673 - *
674 - * @param array $params params.
675 - *
676 - * @return void
677 - */
678 - public function prepare_create_post_meta( $params ) {
679 - $additional_content = isset( $params['additional_content'] ) ? $params['additional_content'] : array();
680 -
681 - $course_benefits = isset( $additional_content['course_benefits'] ) ? $additional_content['course_benefits'] : '';
682 -
683 - $course_target_audience = isset( $additional_content['course_target_audience'] ) ? $additional_content['course_target_audience'] : '';
684 -
685 - $course_duration = isset( $additional_content['course_duration'] ) ? array(
686 - 'hours' => $additional_content['course_duration']['hours'] ?? '',
687 - 'minutes' => $additional_content['course_duration']['minutes'] ?? '',
688 - ) : array();
689 -
690 - $course_materials = isset( $additional_content['course_material_includes'] ) ? $additional_content['course_material_includes'] : '';
691 -
692 - $course_requirements = isset( $additional_content['course_requirements'] ) ? $additional_content['course_requirements'] : '';
693 -
694 - $pricing = isset( $params['pricing'] ) ? array(
695 - 'type' => $params['pricing']['type'] ?? self::PRICE_TYPE_FREE,
696 - 'product_id' => (int) $params['pricing']['product_id'] ?? -1,
697 - ) : array(
698 - 'type' => self::PRICE_TYPE_FREE,
699 - 'product_id' => -1,
700 - );
701 -
702 - // Setup global $_POST array.
703 - $_POST['_tutor_course_additional_data_edit'] = true;
704 -
705 - $_POST['tutor_course_price_type'] = $pricing['type'];
706 - $_POST['course_duration'] = $course_duration;
707 - $_POST['tutor_course_price_type'] = $pricing['type'];
708 - $_POST['_tutor_course_product_id'] = $pricing['product_id'];
709 - $_POST['_tutor_course_level'] = $params['course_level'];
710 - $_POST['course_benefits'] = $course_benefits;
711 - $_POST['course_requirements'] = $course_requirements;
712 - $_POST['course_target_audience'] = $course_target_audience;
713 - $_POST['course_material_includes'] = $course_materials;
714 -
715 - if ( isset( $params['enable_qna'] ) && 'yes' === $params['enable_qna'] ) {
716 - $_POST['_tutor_enable_qa'] = 'yes';
284 + //Course Duration
285 + if ( ! empty($_POST['course_duration'])){
286 + $video = tutils()->sanitize_array($_POST['course_duration']);
287 + update_post_meta($post_ID, '_course_duration', $video);
717 288 }
718 289
719 - if ( isset( $params['_tutor_is_public_course'] ) && 'yes' === $params['_tutor_is_public_course'] ) {
720 - $_POST['_tutor_is_public_course'] = 'yes';
290 + if ( ! empty($_POST['course_level'])){
291 + $course_level = sanitize_text_field($_POST['course_level']);
292 + update_post_meta($post_ID, '_tutor_course_level', $course_level);
721 293 }
722 294
723 - // Set course price.
724 - if ( -1 !== $pricing['product_id'] ) {
725 - $product = wc_get_product( $pricing['product_id'] );
726 - if ( is_a( $product, 'WC_Product' ) ) {
727 - $regular_price = $product->get_regular_price();
728 - $sale_price = $product->get_sale_price();
729 -
730 - $_POST['course_price'] = $regular_price;
731 - $_POST['course_sale_price'] = $sale_price;
295 + $additional_data_edit = tutils()->avalue_dot('_tutor_course_additional_data_edit', $_POST);
296 + if ($additional_data_edit) {
297 + if (!empty($_POST['course_benefits'])) {
298 + $course_benefits = wp_kses_post($_POST['course_benefits']);
299 + update_post_meta($post_ID, '_tutor_course_benefits', $course_benefits);
300 + } else {
301 + delete_post_meta($post_ID, '_tutor_course_benefits');
732 302 }
733 - }
734 - }
735 303
736 - /**
737 - * Prepare course meta data for update
738 - *
739 - * @since 3.0.0
740 - *
741 - * @param array $params params.
742 - *
743 - * @throws \Exception Throw new exception.
744 - *
745 - * @return mixed
746 - */
747 - public function prepare_update_post_meta( $params ) {
748 - $post_id = (int) $params['ID'];
749 -
750 - $additional_content = isset( $params['additional_content'] ) ? $params['additional_content'] : array();
751 -
752 - if ( ! empty( $additional_content ) ) {
753 -
754 - $course_benefits = isset( $additional_content['course_benefits'] ) ? $additional_content['course_benefits'] : '';
755 -
756 - $course_target_audience = isset( $additional_content['course_target_audience'] ) ? $additional_content['course_target_audience'] : '';
757 -
758 - $course_duration = isset( $additional_content['course_duration'] ) ? array(
759 - 'hours' => $additional_content['course_duration']['hours'] ?? '',
760 - 'minutes' => $additional_content['course_duration']['minutes'] ?? '',
761 - ) : array();
762 -
763 - $course_materials = isset( $additional_content['course_material_includes'] ) ? $additional_content['course_material_includes'] : '';
764 -
765 - $course_requirements = isset( $additional_content['course_requirements'] ) ? $additional_content['course_requirements'] : '';
766 -
767 - if ( '' !== $course_benefits ) {
768 - update_post_meta( $post_id, '_tutor_course_benefits', $course_benefits );
304 + if (!empty($_POST['course_requirements'])) {
305 + $requirements = wp_kses_post($_POST['course_requirements']);
306 + update_post_meta($post_ID, '_tutor_course_requirements', $requirements);
307 + } else {
308 + delete_post_meta($post_ID, '_tutor_course_requirements');
769 309 }
770 310
771 - if ( '' !== $course_requirements ) {
772 - update_post_meta( $post_id, '_tutor_course_requirements', $course_requirements );
311 + if (!empty($_POST['course_target_audience'])) {
312 + $target_audience = wp_kses_post($_POST['course_target_audience']);
313 + update_post_meta($post_ID, '_tutor_course_target_audience', $target_audience);
314 + } else {
315 + delete_post_meta($post_ID, '_tutor_course_target_audience');
773 316 }
774 317
775 - if ( '' !== $course_target_audience ) {
776 - update_post_meta( $post_id, '_tutor_course_target_audience', $course_target_audience );
318 + if (!empty($_POST['course_material_includes'])) {
319 + $material_includes = wp_kses_post($_POST['course_material_includes']);
320 + update_post_meta($post_ID, '_tutor_course_material_includes', $material_includes);
321 + } else {
322 + delete_post_meta($post_ID, '_tutor_course_material_includes');
777 323 }
778 -
779 - if ( '' !== $course_materials ) {
780 - update_post_meta( $post_id, '_tutor_course_material_includes', $course_materials );
781 - }
782 -
783 - if ( ! empty( $course_duration ) ) {
784 - update_post_meta( $post_id, '_course_duration', $course_duration );
785 - }
786 324 }
787 325
788 - if ( isset( $params['pricing'] ) && ! empty( $params['pricing'] ) ) {
789 - try {
790 - if ( isset( $params['pricing']['type'] ) ) {
791 - update_post_meta( $post_id, self::COURSE_PRICE_TYPE_META, $params['pricing']['type'] );
792 - }
793 - } catch ( \Throwable $th ) {
794 - throw new \Exception( $th->getMessage() );
795 - }
796 - }
797 326
798 - update_post_meta( $post_id, '_tutor_enable_qa', $params['enable_qna'] ?? 'yes' );
799 - update_post_meta( $post_id, '_tutor_is_public_course', $params['is_public_course'] ?? 'no' );
800 - update_post_meta( $post_id, '_tutor_course_level', $params['course_level'] );
801 -
802 327 /**
803 - * Save tax collection settings
804 - *
805 - * @since 3.7.0
328 + * Sorting Topics and lesson
806 329 */
807 - if ( isset( $params['tax_on_single'] ) ) {
808 - update_post_meta( $post_id, self::TAX_ON_SINGLE_META, $params['tax_on_single'] );
809 - }
330 + if ( ! empty($_POST['tutor_topics_lessons_sorting'])){
331 + $new_order = sanitize_text_field(stripslashes($_POST['tutor_topics_lessons_sorting']));
332 + $order = json_decode($new_order, true);
810 333
811 - if ( isset( $params['tax_on_subscription'] ) ) {
812 - update_post_meta( $post_id, self::TAX_ON_SUBSCRIPTION_META, $params['tax_on_subscription'] );
813 - }
334 + if (is_array($order) && count($order)){
335 + $i = 0;
336 + foreach ($order as $topic ){
337 + $i++;
338 + $wpdb->update(
339 + $wpdb->posts,
340 + array('menu_order' => $i),
341 + array('ID' => $topic['topic_id'])
342 + );
814 343
815 - do_action( 'tutor_after_prepare_update_post_meta', $post_id, $params );
816 - }
344 + /**
345 + * Removing All lesson with topic
346 + */
817 347
818 - /**
819 - * Prepare course settings meta
820 - *
821 - * @since 3.0.0
822 - *
823 - * @param array $params params.
824 - *
825 - * @return void
826 - */
827 - public function prepare_course_settings( $params ) {
828 - if ( isset( $params['course_settings'] ) ) {
829 - $_POST['_tutor_course_settings'] = $params['course_settings'];
830 - }
831 - }
348 + $wpdb->update(
349 + $wpdb->posts,
350 + array('post_parent' => 0),
351 + array('post_parent' => $topic['topic_id'])
352 + );
832 353
833 - /**
834 - * Check access before course builder ajax request.
835 - *
836 - * @since 3.0.0
837 - *
838 - * @param int $course_id course id.
839 - *
840 - * @return void
841 - */
842 - public function check_access( $course_id = null ) {
843 - $has_access = false;
844 -
845 - if ( $course_id ) {
846 - $has_access = tutor_utils()->can_user_edit_course( get_current_user_id(), $course_id );
847 - } else {
848 - $has_access = User::is_admin() || User::is_instructor();
354 + /**
355 + * Lesson Attaching with topic ID
356 + * sorting lesson
357 + */
358 + if (isset($topic['lesson_ids'])){
359 + $lesson_ids = $topic['lesson_ids'];
360 + }else{
361 + $lesson_ids = array();
362 + }
363 + if (count($lesson_ids)){
364 + foreach ($lesson_ids as $lesson_key => $lesson_id ){
365 + $wpdb->update(
366 + $wpdb->posts,
367 + array('post_parent' => $topic['topic_id'], 'menu_order' => $lesson_key),
368 + array('ID' => $lesson_id)
369 + );
370 + }
371 + }
372 + }
373 + }
849 374 }
850 -
851 - if ( ! $has_access ) {
852 - $this->json_response(
853 - tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ),
854 - null,
855 - HttpHelper::STATUS_UNAUTHORIZED
856 - );
857 - }
858 - }
859 -
860 - /**
861 - * Validate request inputs.
862 - *
863 - * @param array $params input params.
864 - * @param array $exclude exclude key from rules.
865 - *
866 - * @return object
867 - */
868 - public function validate_inputs( $params, $exclude = array() ) {
869 - $status_str = implode( ',', CourseModel::get_status_list() );
870 - $rules = array(
871 - 'course_id' => 'required|numeric',
872 - 'post_title' => 'required',
873 - 'post_author' => 'user_exists',
874 - 'post_status' => "required|match_string:{$status_str}",
875 - 'enable_qna' => 'if_input|match_string:yes,no',
876 - 'is_public_course' => 'if_input|match_string:yes,no',
877 - );
878 -
879 - foreach ( $exclude as $key ) {
880 - if ( isset( $rules[ $key ] ) ) {
881 - unset( $rules[ $key ] );
375 +
376 + if ($additional_data_edit) {
377 + if ( ! empty($_POST['video']['source'])) { //Video
378 + $video = tutor_utils()->array_get('video', $_POST);
379 + update_post_meta($post_ID, '_video', $video);
380 + }else{
381 + delete_post_meta($post_ID, '_video');
882 382 }
883 383 }
884 384
885 - return ValidationHelper::validate( $rules, $params );
886 - }
385 + /**
386 + * Adding author to instructor automatically
387 + */
887 388
888 - /**
889 - * Create new draft course
890 - *
891 - * @since 3.0.0
892 - *
893 - * @return void JSON response
894 - */
895 - public function ajax_create_new_draft_course() {
896 - tutor_utils()->check_nonce();
389 + $author_id = $post->post_author;
390 + $attached = (int) $wpdb->get_var($wpdb->prepare(
391 + "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
392 + WHERE user_id = %d AND meta_key = '_tutor_instructor_course_id' AND meta_value = %d ", $author_id, $post_ID));
897 393
898 - $this->check_access();
899 -
900 - $course_id = wp_insert_post(
901 - array(
902 - 'post_title' => __( 'New Course', 'tutor' ),
903 - 'post_type' => tutor()->course_post_type,
904 - 'post_status' => 'draft',
905 - 'post_name' => 'new-course',
906 - )
907 - );
908 -
909 - if ( is_wp_error( $course_id ) ) {
910 - $this->json_response( $course_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
394 + if ( ! $attached){
395 + add_user_meta($author_id, '_tutor_instructor_course_id', $post_ID);
911 396 }
912 397
913 - update_post_meta( $course_id, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_FREE );
914 -
915 - $link = admin_url( 'admin.php?page=create-course' );
916 - if ( tutor()->has_pro && Input::post( 'from_dashboard', false, Input::TYPE_BOOL ) ) {
917 - $link = tutor_utils()->tutor_dashboard_url( 'create-course' );
398 + /**
399 + * Disable question and answer for this course
400 + * @since 1.7.0
401 + */
402 + if ($additional_data_edit) {
403 + foreach($this->additional_meta as $key){
404 + update_post_meta($post_ID, $key, (isset($_POST[$key]) ? 'yes' : 'no'));
405 + }
918 406 }
919 407
920 - $link = add_query_arg( array( 'course_id' => $course_id ), $link );
921 -
922 - do_action( 'tutor_draft_course_created', $course_id );
923 -
924 - $this->json_response(
925 - __( 'Draft course created', 'tutor' ),
926 - $link,
927 - HttpHelper::STATUS_CREATED
928 - );
408 + do_action( "tutor_save_course_after", $post_ID, $post);
929 409 }
930 410
931 411 /**
932 - * Get course list
933 - *
934 - * @since 3.0.0
935 - *
936 - * @since 3.2.0 Refactor the arguments & response as per new design.
937 - *
938 - * @return void
412 + * Tutor add course topic
939 413 */
940 - public function ajax_course_list() {
941 - tutor_utils()->check_nonce();
942 - $this->check_access();
414 + public function tutor_add_course_topic(){
415 + tutils()->checking_nonce();
943 416
944 - $limit = Input::post( 'limit', 10, Input::TYPE_INT );
945 - $offset = Input::post( 'offset', 0, Input::TYPE_INT );
946 - $search_term = '';
947 - $post_status = Input::post( 'post_status', null );
948 -
949 - $filter = json_decode( wp_unslash( $_POST['filter'] ) ); //phpcs:ignore --sanitized already
950 - if ( ! empty( $filter ) && property_exists( $filter, 'search' ) ) {
951 - $search_term = Input::sanitize( $filter->search );
417 + if (empty($_POST['topic_title']) ) {
418 + wp_send_json_error();
952 419 }
953 -
954 - $args = array(
955 - 'post_status' => is_null( $post_status ) ? 'publish' : $post_status,
956 - 'posts_per_page' => $limit,
957 - 'offset' => $offset,
958 - 's' => $search_term,
959 - );
960 -
961 - $exclude = Input::post( 'exclude', array(), Input::TYPE_ARRAY );
962 - if ( count( $exclude ) ) {
963 - $exclude = array_filter( $exclude, fn( $id ) => is_numeric( $id ) && $id > 0 );
964 - $args['post__not_in'] = $exclude;
420 + $course_id = (int) tutor_utils()->avalue_dot('tutor_topic_course_ID', $_POST);
421 + $next_topic_order_id = tutor_utils()->get_next_topic_order_id($course_id);
422 +
423 + if(!tutils()->can_user_manage('course', $course_id)) {
424 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
965 425 }
966 426
967 - $courses = CourseModel::get_courses_by_args( $args );
427 + $topic_title = sanitize_text_field( $_POST['topic_title'] );
428 + $topic_summery = wp_kses_post( $_POST['topic_summery'] );
968 429
969 - $response = array(
970 - 'results' => array(),
971 - 'total_items' => 0,
430 + $post_arr = array(
431 + 'post_type' => 'topics',
432 + 'post_title' => $topic_title,
433 + 'post_content' => $topic_summery,
434 + 'post_status' => 'publish',
435 + 'post_author' => get_current_user_id(),
436 + 'post_parent' => $course_id,
437 + 'menu_order' => $next_topic_order_id,
972 438 );
439 + $current_topic_id = wp_insert_post( $post_arr );
973 440
974 - $response['total_items'] = is_a( $courses, 'WP_Query' ) ? $courses->found_posts : 0;
441 + ob_start();
442 + include tutor()->path.'views/metabox/course-contents.php';
443 + $course_contents = ob_get_clean();
975 444
976 - if ( is_a( $courses, 'WP_Query' ) && $courses->have_posts() ) {
977 - $courses = $courses->get_posts();
978 - foreach ( $courses as $course ) {
979 - $response['results'][] = self::get_mini_info( $course );
980 - }
981 - }
982 -
983 - $this->json_response(
984 - __( 'Course list retrieved successfully!', 'tutor' ),
985 - $response
986 - );
445 + wp_send_json_success(array('course_contents' => $course_contents));
987 446 }
988 447
989 448 /**
990 - * Create course by ajax request.
991 - *
992 - * @since 3.0.0
993 - *
994 - * @return void
449 + * Update the topic
995 450 */
996 - public function ajax_create_course() {
997 - tutor_utils()->check_nonce();
451 + public function tutor_update_topic(){
452 + tutils()->checking_nonce();
998 453
999 - $this->check_access();
454 + $topic_id = (int) sanitize_text_field($_POST['topic_id']);
455 + $topic_title = sanitize_text_field($_POST['topic_title']);
456 + $topic_summery = wp_kses_post($_POST['topic_summery']);
1000 457
1001 - $params = Input::sanitize_array(
1002 - //phpcs:ignore WordPress.Security.NonceVerification.Missing
1003 - $_POST,
1004 - array(
1005 - 'post_content' => 'wp_kses_post',
1006 - 'course_material_includes' => 'sanitize_textarea_field',
1007 - )
1008 - );
1009 -
1010 - $params['post_type'] = tutor()->course_post_type;
1011 -
1012 - // Validate inputs.
1013 - $errors = array();
1014 - $validation = $this->validate_inputs( $params, array( 'course_id' ) );
1015 - if ( ! $validation->success ) {
1016 - $errors = $validation->errors;
458 + if(!tutils()->can_user_manage('topic', $topic_id)) {
459 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
1017 460 }
1018 461
1019 - if ( User::is_instructor() ) {
1020 - $params['post_author'] = get_current_user_id();
1021 - }
462 + $topic_attr = array(
463 + 'ID' => $topic_id,
464 + 'post_title' => $topic_title,
465 + 'post_content' => $topic_summery,
466 + );
467 + wp_update_post( $topic_attr );
1022 468
1023 - // Validate video source if user set video.
1024 - $this->validate_video_source( $params, $errors );
1025 -
1026 - // Validate WC product.
1027 - $this->validate_price( $params, $errors );
1028 -
1029 - // Set course categories and tags.
1030 - $this->prepare_course_cats_tags( $params, $errors );
1031 - $this->setup_course_price( $params );
1032 -
1033 - if ( ! empty( $errors ) ) {
1034 - $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1035 - }
1036 -
1037 - $this->prepare_course_settings( $params );
1038 -
1039 - try {
1040 - $this->prepare_create_post_meta( $params );
1041 - } catch ( \Exception $e ) {
1042 - $this->json_response( $e->getMessage(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
1043 - }
1044 -
1045 - $course_id = wp_insert_post( $params );
1046 - if ( is_wp_error( $course_id ) ) {
1047 - $this->json_response( $course_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
1048 - }
1049 -
1050 - // Set course cats & tags.
1051 - $this->setup_course_categories_tags( $course_id, $params );
1052 -
1053 - // Update course thumb.
1054 - if ( isset( $params['thumbnail_id'] ) ) {
1055 - set_post_thumbnail( $course_id, $params['thumbnail_id'] );
1056 - }
1057 -
1058 - $this->json_response(
1059 - __( 'Course created successfully', 'tutor' ),
1060 - $course_id,
1061 - HttpHelper::STATUS_CREATED
1062 - );
469 + wp_send_json_success(array('msg' => __('Topic has been updated', 'tutor') ));
1063 470 }
1064 471
1065 - /**
1066 - * Setup course price
1067 - *
1068 - * @since 3.0.0
1069 - *
1070 - * @param array $params params.
1071 - *
1072 - * @return void
1073 - */
1074 - public function setup_course_price( $params ) {
1075 - if ( isset( $params['pricing'] )
1076 - && isset( $params['pricing']['product_id'] )
1077 - && is_numeric( $params['pricing']['product_id'] ) ) {
1078 - $_POST['_tutor_course_product_id'] = $params['pricing']['product_id'];
1079 - }
1080 - }
1081 472
1082 473 /**
1083 - * Update course by ajax request.
474 + * @param $columns
1084 475 *
1085 - * @since 3.0.0
476 + * @return mixed
1086 477 *
1087 - * @return void
478 + * Add Lesson column
1088 479 */
1089 - public function ajax_update_course() {
1090 - tutor_utils()->check_nonce();
480 + public function add_column($columns){
481 + $date_col = $columns['date'];
482 + unset($columns['date']);
483 + $columns['lessons'] = __('Lessons', 'tutor');
484 + $columns['students'] = __('Students', 'tutor');
485 + $columns['price'] = __('Price', 'tutor');
486 + $columns['date'] = $date_col;
1091 487
1092 - $params = Input::sanitize_array(
1093 - //phpcs:ignore WordPress.Security.NonceVerification.Missing
1094 - wp_slash( $_POST ),
1095 - array(
1096 - 'post_content' => 'wp_kses_post',
1097 - 'course_benefits' => 'sanitize_textarea_field',
1098 - 'course_target_audience' => 'sanitize_textarea_field',
1099 - 'course_material_includes' => 'sanitize_textarea_field',
1100 - 'course_requirements' => 'sanitize_textarea_field',
1101 - )
1102 - );
1103 -
1104 - $course_id = (int) $params['course_id'];
1105 - if ( ! $course_id ) {
1106 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
1107 - }
1108 -
1109 - $this->check_access( $course_id );
1110 -
1111 - $errors = array();
1112 - $validation = $this->validate_inputs( $params );
1113 - if ( ! $validation->success ) {
1114 - $errors = $validation->errors;
1115 - }
1116 -
1117 - // Validate video source if user set video.
1118 - $this->validate_video_source( $params, $errors );
1119 -
1120 - // Validate WC product.
1121 - $this->validate_price_for_update( $params, $errors, $course_id );
1122 -
1123 - // Set course categories and tags.
1124 - $this->prepare_course_cats_tags( $params, $errors );
1125 -
1126 - $this->prepare_course_settings( $params );
1127 -
1128 - // Validate scheduled courses.
1129 - $this->validate_scheduled_course( $params, $errors );
1130 -
1131 - $this->setup_course_price( $params );
1132 -
1133 - if ( ! empty( $errors ) ) {
1134 - $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1135 - }
1136 -
1137 - /**
1138 - * Can trash a course when user is admin or option `instructor_can_delete_course` is turned on.
1139 - */
1140 - if ( CourseModel::STATUS_TRASH === $params['post_status'] ) {
1141 - if ( User::is_admin() || tutor_utils()->get_option( 'instructor_can_delete_course', false ) ) {
1142 - $params['post_status'] = CourseModel::STATUS_TRASH;
1143 - } else {
1144 - unset( $params['post_status'] );
1145 - }
1146 - }
1147 -
1148 - /**
1149 - * Can publish a course when user is admin or option `instructor_can_publish_course` is turned on.
1150 - * If instructor_can_publish_course is turned off then course status will be pending.
1151 - */
1152 - if ( CourseModel::STATUS_PUBLISH === $params['post_status'] ) {
1153 - $is_instructor_allowed_to_publish = (bool) tutor_utils()->get_option( 'instructor_can_publish_course', false );
1154 - if ( ! User::is_admin() && ! $is_instructor_allowed_to_publish ) {
1155 - $params['post_status'] = CourseModel::STATUS_PENDING;
1156 - }
1157 - }
1158 -
1159 - $is_error = apply_filters( 'tutor_is_error_before_course_update', false, $params );
1160 - if ( is_wp_error( $is_error ) ) {
1161 - $this->response_bad_request( $is_error->get_error_message() );
1162 - }
1163 -
1164 - $params['ID'] = $course_id;
1165 - $update_id = wp_update_post( $params, true );
1166 - if ( is_wp_error( $update_id ) ) {
1167 - $this->json_response( $update_id->get_error_message(), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
1168 - }
1169 -
1170 - $this->setup_course_categories_tags( $update_id, $params );
1171 - $this->prepare_update_post_meta( $params );
1172 -
1173 - // Update course thumb.
1174 - $thumbnail_id = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
1175 - if ( $thumbnail_id ) {
1176 - set_post_thumbnail( $update_id, $thumbnail_id );
1177 - } else {
1178 - delete_post_meta( $update_id, '_thumbnail_id' );
1179 - }
1180 -
1181 - $this->json_response(
1182 - __( 'Course updated successfully.', 'tutor' ),
1183 - $update_id,
1184 - HttpHelper::STATUS_OK
1185 - );
488 + return $columns;
1186 489 }
1187 490
1188 491 /**
1189 - * Unlink page builder from editor.
492 + * @param $column
493 + * @param $post_id
1190 494 *
1191 - * @since 3.0.0
1192 - *
1193 - * @return void
1194 495 */
1195 - public function ajax_unlink_page_builder() {
1196 - tutor_utils()->check_nonce();
1197 -
1198 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1199 - if ( ! $course_id ) {
1200 - $this->response_bad_request( __( 'Invalid course id', 'tutor' ) );
496 + public function custom_lesson_column($column, $post_id ){
497 + if ($column === 'lessons'){
498 + echo tutor_utils()->get_lesson_count_by_course($post_id);
1201 499 }
1202 500
1203 - $this->check_access( $course_id );
1204 -
1205 - $builder = Input::post( 'builder' );
1206 - if ( 'elementor' === $builder ) {
1207 - delete_post_meta( $course_id, '_elementor_edit_mode' );
1208 - } elseif ( 'droip' === $builder ) {
1209 - delete_post_meta( $course_id, 'droip_editor_mode' );
1210 - } elseif ( 'divi' === $builder ) {
1211 - $old_post_content = get_post_meta( $course_id, '_et_pb_old_content', true );
1212 - $course = get_post( $course_id );
1213 - $course->post_content = $old_post_content;
1214 - $result = wp_update_post( $course );
1215 -
1216 - if ( $result && ! is_wp_error( $result ) ) {
1217 - update_post_meta( $course_id, '_et_pb_use_builder', 'off' );
1218 - update_post_meta( $course_id, '_et_pb_old_content', '' );
1219 - delete_post_meta( $course_id, '_et_dynamic_cached_shortcodes' );
1220 - delete_post_meta( $course_id, '_et_dynamic_cached_attributes' );
1221 - delete_post_meta( $course_id, '_et_builder_module_features_cache' );
1222 - }
501 + if ($column === 'students'){
502 + echo tutor_utils()->count_enrolled_users_by_course($post_id);
1223 503 }
1224 504
1225 - $this->json_response(
1226 - __( 'Builder unlinked successfully.', 'tutor' ),
1227 - $course_id,
1228 - HttpHelper::STATUS_OK
1229 - );
1230 - }
1231 -
1232 - /**
1233 - * Get all course contents by course id.
1234 - *
1235 - * @since 3.0.0
1236 - *
1237 - * @param int $course_id course id.
1238 - *
1239 - * @return array
1240 - */
1241 - public function get_course_contents( $course_id ) {
1242 - $data = array();
1243 - $topics = tutor_utils()->get_topics( $course_id );
1244 -
1245 - if ( $topics->have_posts() ) {
1246 - foreach ( $topics->get_posts() as $post ) {
1247 - $current_topic = array(
1248 - 'id' => $post->ID,
1249 - 'title' => $post->post_title,
1250 - 'summary' => $post->post_content,
1251 - 'contents' => array(),
1252 - );
1253 -
1254 - $topic_contents = tutor_utils()->get_course_contents_by_topic( $post->ID, -1 );
1255 -
1256 - if ( $topic_contents->have_posts() ) {
1257 - foreach ( $topic_contents->get_posts() as $post ) {
1258 - if ( tutor()->quiz_post_type === $post->post_type ) {
1259 - $questions = tutor_utils()->get_questions_by_quiz( $post->ID );
1260 - $post->total_question = is_array( $questions ) ? count( $questions ) : 0;
1261 - }
1262 -
1263 - array_push( $current_topic['contents'], $post );
1264 - }
505 + if ($column === 'price'){
506 + $price = tutor_utils()->get_course_price($post_id);
507 + if ($price){
508 + $monetize_by = tutils()->get_option('monetize_by');
509 + if (function_exists('wc_price') && $monetize_by === 'wc'){
510 + echo '<span class="tutor-label-success">'.wc_price($price).'</span>';
511 + }else{
512 + echo '<span class="tutor-label-success">'.$price.'</span>';
1265 513 }
1266 -
1267 - $current_topic = apply_filters( 'tutor_filter_course_content', $current_topic );
1268 -
1269 - array_push( $data, $current_topic );
514 + }else{
515 + echo apply_filters('tutor-loop-default-price', __( 'free', 'tutor' ));
1270 516 }
1271 517 }
1272 -
1273 - return $data;
1274 518 }
1275 519
1276 - /**
1277 - * Get course contents
1278 - *
1279 - * @since 3.0.0
1280 - *
1281 - * @return void
1282 - */
1283 - public function ajax_course_contents() {
1284 - tutor_utils()->check_nonce();
1285 520
1286 - $errors = array();
1287 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
521 + public function tutor_delete_topic(){
1288 522
1289 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1290 - $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1291 - }
523 + tutils()->checking_nonce('get');
524 +
525 + !isset($_GET['topic_id']) ? exit() : 0;
1292 526
1293 - $this->check_access( $course_id );
527 + global $wpdb;
1294 528
1295 - if ( ! empty( $errors ) ) {
1296 - $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1297 - }
1298 -
1299 - $contents = $this->get_course_contents( $course_id );
1300 -
1301 - $this->json_response(
1302 - __( 'Course contents fetched successfully', 'tutor' ),
1303 - $contents
529 + $topic_id = (int) sanitize_text_field($_GET['topic_id']);
530 + $wpdb->update(
531 + $wpdb->posts,
532 + array('post_parent' => 0),
533 + array('post_parent' => $topic_id)
1304 534 );
1305 - }
1306 535
1307 - /**
1308 - * Get course details by ID
1309 - *
1310 - * @since 3.0.0
1311 - *
1312 - * @return void
1313 - */
1314 - public function ajax_course_details() {
1315 - tutor_utils()->check_nonce();
1316 -
1317 - $errors = array();
1318 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1319 -
1320 - if ( ! $course_id || tutor()->course_post_type !== get_post_type( $course_id ) ) {
1321 - $errors['course_id'] = __( 'Invalid course id', 'tutor' );
1322 - }
1323 -
1324 - $this->check_access( $course_id );
1325 -
1326 - if ( ! empty( $errors ) ) {
1327 - $this->json_response( __( 'Invalid input', 'tutor' ), $errors, HttpHelper::STATUS_UNPROCESSABLE_ENTITY );
1328 - }
1329 -
1330 - $price_type = tutor_utils()->price_type( $course_id );
1331 - $monetize_by = tutor_utils()->get_option( 'monetize_by' );
1332 -
1333 - $product_name = '';
1334 - $price = 0;
1335 - $sale_price = 0;
1336 - $product_id = tutor_utils()->get_course_product_id( $course_id );
1337 -
1338 - if ( WooCommerce::MONETIZE_BY === $monetize_by ) {
1339 - $product = wc_get_product( $product_id );
1340 - if ( $product ) {
1341 - $product_name = $product->get_name();
1342 - $price = $product->get_regular_price();
1343 - $sale_price = $product->get_sale_price();
1344 - }
1345 - }
1346 -
1347 - if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
1348 - $price = get_post_meta( $course_id, self::COURSE_PRICE_META, true );
1349 - $sale_price = get_post_meta( $course_id, self::COURSE_SALE_PRICE_META, true );
1350 - }
1351 -
1352 - $course_pricing = array(
1353 - 'type' => $price_type,
1354 - 'product_id' => $product_id,
1355 - 'product_name' => $product_name,
1356 - 'price' => $price,
1357 - 'sale_price' => $sale_price,
536 + $wpdb->delete(
537 + $wpdb->postmeta,
538 + array('post_id' => $topic_id)
1358 539 );
1359 540
1360 - $video_intro = get_post_meta( $course_id, '_video', true );
1361 - if ( $video_intro ) {
1362 - $source = $video_intro['source'] ?? '';
1363 - if ( 'html5' === $source ) {
1364 - $poster_url = wp_get_attachment_url( $video['poster'] ?? 0 );
1365 - $source_html5 = wp_get_attachment_url( $video['source_video_id'] ?? 0 );
1366 - $video['poster_url'] = $poster_url;
1367 - $video['source_html5'] = $source_html5;
1368 - }
1369 - }
1370 -
1371 - $course = get_post( $course_id, ARRAY_A );
1372 - if ( $course ) {
1373 - $course['post_name'] = urldecode( $course['post_name'] );
1374 - }
1375 -
1376 - $editors = tutor_utils()->get_editor_list( $course_id );
1377 -
1378 - $data = array(
1379 - 'editors' => array_values( $editors ),
1380 - 'editor_used' => tutor_utils()->get_editor_used( $course_id ),
1381 - 'preview_link' => get_preview_post_link( $course_id ),
1382 - 'post_author' => tutor_utils()->get_tutor_user( $course['post_author'] ),
1383 - 'course_categories' => wp_get_post_terms( $course_id, CourseModel::COURSE_CATEGORY ),
1384 - 'course_tags' => wp_get_post_terms( $course_id, CourseModel::COURSE_TAG ),
1385 - 'thumbnail_id' => get_post_meta( $course_id, '_thumbnail_id', true ),
1386 - 'thumbnail' => get_the_post_thumbnail_url( $course_id ),
1387 -
1388 - 'enable_qna' => get_post_meta( $course_id, '_tutor_enable_qa', true ),
1389 - 'is_public_course' => get_post_meta( $course_id, '_tutor_is_public_course', true ),
1390 - 'course_level' => get_post_meta( $course_id, '_tutor_course_level', true ),
1391 - 'video' => $video_intro,
1392 - 'course_duration' => get_post_meta( $course_id, '_course_duration', true ),
1393 - 'course_benefits' => get_post_meta( $course_id, '_tutor_course_benefits', true ),
1394 - 'course_requirements' => get_post_meta( $course_id, '_tutor_course_requirements', true ),
1395 - 'course_target_audience' => get_post_meta( $course_id, '_tutor_course_target_audience', true ),
1396 - 'course_material_includes' => get_post_meta( $course_id, '_tutor_course_material_includes', true ),
1397 - 'monetize_by' => $monetize_by,
1398 - 'course_pricing' => $course_pricing,
1399 - 'course_settings' => get_post_meta( $course_id, '_tutor_course_settings', true ),
1400 - 'step_completion_status' => array(
1401 - 'basic' => true,
1402 - 'curriculum' => false,
1403 - 'additional' => false,
1404 - 'certificate' => false,
1405 - ),
1406 - );
1407 -
1408 - $tax_on_single = get_post_meta( $course_id, self::TAX_ON_SINGLE_META, true );
1409 - $tax_on_subscription = get_post_meta( $course_id, self::TAX_ON_SUBSCRIPTION_META, true );
1410 -
1411 - $data['tax_collection'] = array(
1412 - 'tax_on_single' => '' === $tax_on_single ? '1' : $tax_on_single,
1413 - 'tax_on_subscription' => '' === $tax_on_subscription ? '1' : $tax_on_subscription,
1414 - );
1415 -
1416 - $data = apply_filters( 'tutor_course_details_response', array_merge( $course, $data ) );
1417 -
1418 - $this->json_response( __( 'Data retrieved successfully!', 'tutor' ), $data );
541 + wp_delete_post($topic_id);
542 + wp_safe_redirect(wp_get_referer());
1419 543 }
1420 544
1421 - /**
1422 - * Load course builder.
1423 - *
1424 - * @since 3.0.0
1425 - *
1426 - * @return void
1427 - */
1428 - public function load_course_builder() {
1429 - global $pagenow;
545 + public function tutor_delete_announcement(){
546 + tutor_utils()->checking_nonce('get');
1430 547
1431 - $has_pro = tutor()->has_pro;
1432 - $has_access_role = User::has_any_role( array( User::ADMIN, User::INSTRUCTOR ) );
548 + $announcement_id = (int) sanitize_text_field($_GET['topic_id']);
1433 549
1434 - $course_id = Input::get( 'course_id', 0, Input::TYPE_INT );
1435 - $backend_builder = is_admin() && 'admin.php' === $pagenow && 'create-course' === Input::get( 'page' );
1436 - $backend_edit = $backend_builder && $course_id;
1437 -
1438 - $is_frontend_builder = tutor_utils()->is_tutor_frontend_dashboard( 'create-course' );
1439 - $frontend_edit = $is_frontend_builder && $course_id;
1440 -
1441 - if ( $has_access_role && ( $backend_edit || ( $has_pro && $frontend_edit ) ) ) {
1442 - $post_type = get_post_type( $course_id );
1443 - $can_edit_course = tutor_utils()->can_user_edit_course( get_current_user_id(), $course_id );
1444 -
1445 - if ( tutor()->course_post_type === $post_type && ( User::is_admin() || $can_edit_course ) ) {
1446 - /**
1447 - * Edit trash course behavior
1448 - *
1449 - * @since 3.0.0
1450 - */
1451 - if ( CourseModel::STATUS_TRASH === get_post_status( $course_id ) ) {
1452 - $message = User::is_admin()
1453 - ? __( 'You cannot edit this course because it is in the Trash. Please restore it and try again', 'tutor' )
1454 - : tutor_utils()->error_message();
1455 - wp_die( esc_html( $message ) );
1456 - }
1457 -
1458 - $this->load_course_builder_view();
1459 - }
1460 - }
550 + wp_delete_post($announcement_id);
551 + wp_safe_redirect(wp_get_referer());
1461 552 }
1462 553
1463 - /**
1464 - * Enqueue course builder assets like CSS, JS
1465 - *
1466 - * @since 3.0.0
1467 - *
1468 - * @return void
1469 - */
1470 - public function enqueue_course_builder_assets() {
1471 - // Fix: function print_emoji_styles is deprecated since version 6.4.0!
1472 - remove_action( 'wp_print_styles', 'print_emoji_styles' );
1473 - remove_action( 'wp_head', 'wp_admin_bar_header' );
1474 - add_action( 'wp_head', 'wp_enqueue_admin_bar_header_styles' );
554 + public function enroll_now(){
1475 555
1476 - do_action( 'tutor_course_builder_before_wp_editor_load' );
1477 - wp_enqueue_script( 'wp-tinymce' );
1478 - wp_enqueue_script( 'mce-view' );
1479 - wp_enqueue_editor();
1480 -
1481 - wp_enqueue_media();
1482 - wp_enqueue_script( 'tutor-course-builder', tutor()->url . 'assets/js/tutor-course-builder.js', array( 'wp-date', 'wp-i18n', 'wp-element', 'wp-api' ), TUTOR_VERSION, true );
1483 - wp_set_script_translations( 'tutor-course-builder', 'tutor', tutor()->path . 'languages/' );
1484 -
1485 - wp_localize_script(
1486 - 'mce-view',
1487 - 'mceViewL10n',
1488 - array(
1489 - 'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(),
1490 - )
1491 - );
1492 - }
1493 -
1494 - /**
1495 - * Localize custom course builder data for _tutorobject.
1496 - *
1497 - * @since 3.3.1
1498 - *
1499 - * @param array $data the localized data.
1500 - *
1501 - * @return array
1502 - */
1503 - public function localize_course_builder_data( $data ) {
1504 - global $pagenow;
1505 -
1506 - $course_id = Input::get( 'course_id', 0, Input::TYPE_INT );
1507 - $backend_builder = is_admin() && 'admin.php' === $pagenow && 'create-course' === Input::get( 'page' );
1508 - $backend_edit = $backend_builder && $course_id;
1509 -
1510 - $is_frontend_builder = tutor_utils()->is_tutor_frontend_dashboard( 'create-course' );
1511 - $frontend_edit = $is_frontend_builder && $course_id;
1512 -
1513 - if ( ! $backend_edit && ! $frontend_edit ) {
1514 - return $data;
556 + //Checking if action comes from Enroll form
557 + if (tutor_utils()->array_get('tutor_course_action', $_POST) !== '_tutor_course_enroll_now' || ! isset($_POST['tutor_course_id']) ){
558 + return;
1515 559 }
560 + //Checking Nonce
561 + tutor_utils()->checking_nonce();
1516 562
1517 - /**
1518 - * Prepare course builder data.
1519 - */
1520 - $default_data = ( new Assets( false ) )->get_default_localized_data();
1521 -
1522 - if ( isset( $default_data['current_user']['data']['id'] ) ) {
1523 - $tutor_user = tutor_utils()->get_tutor_user( $default_data['current_user']['data']['id'] );
1524 - $default_data['current_user']['data']['tutor_profile_photo_url'] = $tutor_user->tutor_profile_photo_url;
563 + $user_id = get_current_user_id();
564 + if ( ! $user_id){
565 + exit(__('Please Sign In first', 'tutor'));
1525 566 }
1526 567
1527 - /**
1528 - * Localized only options to protect sensitive info like API keys.
1529 - */
1530 - $required_options = array(
1531 - 'learning_mode',
1532 - 'monetize_by',
1533 - 'enable_course_marketplace',
1534 - 'course_permalink_base',
1535 - 'supported_video_sources',
1536 - 'enrollment_expiry_enabled',
1537 - 'enable_q_and_a_on_course',
1538 - 'instructor_can_delete_course',
1539 - 'chatgpt_enable',
1540 - 'hide_admin_bar_for_users',
1541 - 'enable_redirect_on_course_publish_from_frontend',
1542 - 'instructor_can_publish_course',
1543 - 'instructor_can_change_course_author',
1544 - 'instructor_can_manage_co_instructors',
1545 - );
568 + $course_id = (int) sanitize_text_field($_POST['tutor_course_id']);
569 + $user_id = get_current_user_id();
1546 570
1547 - $full_settings = get_option( 'tutor_option', array() );
1548 - $settings = Options_V2::get_only( $required_options );
1549 - $settings['course_builder_logo_url'] = wp_get_attachment_image_url( $full_settings['tutor_frontend_course_page_logo_id'] ?? 0, 'full' );
1550 - $settings['chatgpt_key_exist'] = tutor()->has_pro && ! empty( $full_settings['chatgpt_api_key'] ?? '' );
1551 - $settings['youtube_api_key_exist'] = ! empty( $full_settings['lesson_video_duration_youtube_api_key'] ?? '' );
1552 -
1553 - $settings['enable_tax'] = Tax::get_setting( 'enable_tax', true );
1554 - $settings['is_tax_included_in_price'] = Tax::is_tax_included_in_price();
1555 - $settings['enable_individual_tax_control'] = Tax::get_setting( 'enable_individual_tax_control' );
1556 -
1557 - $new_data = array( 'settings' => $settings );
1558 -
1559 - $data = array_merge( $default_data, $new_data );
1560 -
1561 571 /**
1562 - * Course builder dashboard URL based on role and settings.
572 + * TODO: need to check purchase information
1563 573 */
1564 - $dashboard_url = tutor_utils()->tutor_dashboard_url();
1565 - if ( User::is_admin() ) {
1566 - $dashboard_url = get_admin_url();
1567 - }
1568 574
1569 - /**
1570 - * EDD product list
1571 - */
1572 - $monetize_by = tutor_utils()->get_option( 'monetize_by' );
1573 - if ( 'edd' === $monetize_by && tutor_utils()->has_edd() ) {
1574 - $data['edd_products'] = tutor_utils()->get_edd_products();
1575 - }
575 + $is_purchasable = tutor_utils()->is_course_purchasable($course_id);
1576 576
1577 - $difficulty_levels = array();
1578 - foreach ( tutor_utils()->course_levels() as $value => $label ) {
1579 - $difficulty_levels[] = array(
1580 - 'label' => $label,
1581 - 'value' => $value,
1582 - );
1583 - }
1584 -
1585 - $supported_video_sources = array();
1586 - $saved_video_source_list = (array) ( $settings['supported_video_sources'] ?? array() );
1587 -
1588 - foreach ( tutor_utils()->get_video_sources( true ) as $value => $label ) {
1589 - if ( in_array( $value, $saved_video_source_list, true ) ) {
1590 - $supported_video_sources[] = array(
1591 - 'label' => $label,
1592 - 'value' => $value,
1593 - );
1594 - }
1595 - }
1596 -
1597 - $data['dashboard_url'] = $dashboard_url;
1598 - $data['backend_course_list_url'] = get_admin_url( null, 'admin.php?page=tutor' );
1599 - $data['frontend_course_list_url'] = tutor_utils()->tutor_dashboard_url( 'my-courses' );
1600 - $data['timezones'] = tutor_global_timezone_lists();
1601 - $data['difficulty_levels'] = $difficulty_levels;
1602 - $data['supported_video_sources'] = $supported_video_sources;
1603 - $data['wp_rest_nonce'] = wp_create_nonce( 'wp_rest' );
1604 -
1605 - if ( 'en_US' !== $data['local'] ) {
1606 - $data['course_builder_basic_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-basic', $data['local'] );
1607 - $data['course_builder_curriculum_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-curriculum', $data['local'] );
1608 - $data['course_builder_additional_locales'] = tutils()->get_script_locale_data( 'tutor-course-builder-additional', $data['local'] );
1609 - }
1610 -
1611 - $data = apply_filters( 'tutor_course_builder_localized_data', $data );
1612 -
1613 - return $data;
1614 - }
1615 -
1616 - /**
1617 - * Load view for course builder.
1618 - *
1619 - * @since 3.0.0
1620 - *
1621 - * @return void
1622 - */
1623 - public function load_course_builder_view() {
1624 577 /**
1625 - * Hide admin menu and footer.
578 + * If is is not purchasable, it's free, and enroll right now
1626 579 *
1627 - * @since 3.3.0
1628 - */
1629 - echo '<style>
1630 - #adminmenumain, #wpfooter, .notice, #tutor-page-wrap { display: none !important; }
1631 - #wpcontent { margin: 0 !important; padding: 0 !important; }
1632 - #wpbody-content { padding-bottom: 0px !important; float: none; }
1633 - </style>';
1634 -
1635 - do_action( 'tutor_before_course_builder_load' );
1636 - include_once tutor()->path . 'views/pages/course-builder.php';
1637 - do_action( 'tutor_after_course_builder_load' );
1638 - }
1639 -
1640 - /**
1641 - * Add enroll require login class
1642 - *
1643 - * @since 2.6.0
1644 - *
1645 - * @param string $class_name css class name.
1646 - *
1647 - * @return string
1648 - */
1649 - public function add_enroll_required_login_class( $class_name ) {
1650 - $enabled_tutor_login = tutor_utils()->get_option( 'enable_tutor_native_login', null, true, true );
1651 - if ( ! $enabled_tutor_login ) {
1652 - return '';
1653 - }
1654 -
1655 - return $class_name;
1656 - }
1657 -
1658 - /**
1659 - * Get list of WC products.
1660 - *
1661 - * @since 2.5.0
1662 - * @since 3.0.0 exclude_linked_products, course_id are added.
1663 - *
1664 - * @return void
1665 - */
1666 - public function get_wc_products() {
1667 - $exclude = array();
1668 - $exclude_linked_products = Input::has( 'exclude_linked_products' );
1669 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1670 -
1671 - if ( $exclude_linked_products ) {
1672 - $exclude = tutor_utils()->get_linked_product_ids();
1673 - }
1674 -
1675 - if ( $course_id ) {
1676 - $linked_product_id = tutor_utils()->get_course_product_id( $course_id );
1677 - if ( $linked_product_id ) {
1678 - $exclude = array_filter( $exclude, fn( $id )=> $linked_product_id !== (int) $id );
1679 - }
1680 - }
1681 -
1682 - $exclude = array_unique( $exclude );
1683 -
1684 - $this->json_response(
1685 - __( 'Products retrieved successfully!', 'tutor' ),
1686 - tutor_utils()->get_wc_products_db( $exclude ),
1687 - HttpHelper::STATUS_OK
1688 - );
1689 - }
1690 -
1691 - /**
1692 - * Get course associate WC product info by Ajax request
1693 - *
1694 - * @since 2.0.7
1695 - *
1696 - * @return void
1697 - */
1698 - public function get_wc_product() {
1699 - tutor_utils()->checking_nonce();
1700 - $product_id = Input::post( 'product_id' );
1701 - $product = wc_get_product( $product_id );
1702 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
1703 -
1704 - $is_linked_with_course = tutor_utils()->product_belongs_with_course( $product_id );
1705 -
1706 - /**
1707 - * If selected product is already linked with
1708 - * a course & it is not the current course the
1709 - * return error
580 + * if purchasable, then process purchase.
1710 581 *
1711 - * @since 2.1.0
582 + * @since: v.1.0.0
1712 583 */
1713 - if ( is_object( $is_linked_with_course ) && $is_linked_with_course->post_id != $course_id ) {
1714 - wp_send_json_error(
1715 - __( 'One product can not be added to multiple course!', 'tutor' )
1716 - );
1717 - }
584 + if ($is_purchasable){
585 + //process purchase
1718 586
1719 - if ( $product ) {
1720 - $data = array(
1721 - 'name' => $product->get_name(),
1722 - 'regular_price' => $product->get_regular_price(),
1723 - 'sale_price' => $product->get_sale_price(),
1724 - );
1725 - wp_send_json_success( $data );
1726 - } else {
1727 - wp_send_json_error( __( 'Product not found', 'tutor' ) );
587 + }else{
588 + //Free enroll
589 + tutor_utils()->do_enroll($course_id);
1728 590 }
1729 - }
1730 591
1731 - /**
1732 - * Update course content order
1733 - *
1734 - * @since 1.0.0
1735 - * @since 3.9.9 Check if user can manage course before updating order.
1736 - *
1737 - * @return void
1738 - */
1739 - public function ajax_update_course_content_order() {
1740 - tutor_utils()->checking_nonce();
1741 -
1742 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1743 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1744 -
1745 - if ( ! tutor_utils()->count( $sorting_order ) ) {
1746 - wp_send_json_error( __( 'Sorting order is required', 'tutor' ) );
1747 - }
1748 -
1749 - $topic_id = (int) isset( $sorting_order[0], $sorting_order[0]['topic_id'] ) ? $sorting_order[0]['topic_id'] : 0;
1750 - $course_id = wp_get_post_parent_id( $topic_id );
1751 -
1752 - if ( ! $topic_id || ! $course_id ) {
1753 - wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
1754 - }
1755 -
1756 - if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ! User::is_admin() ) {
1757 - wp_send_json_error( tutor_utils()->error_message() );
1758 - }
1759 -
1760 - if ( Input::has( 'content_parent' ) ) {
1761 - $content_parent = Input::post( 'content_parent', array(), Input::TYPE_ARRAY );
1762 - $topic_id = tutor_utils()->array_get( 'parent_topic_id', $content_parent );
1763 - $content_id = tutor_utils()->array_get( 'content_id', $content_parent );
1764 -
1765 - // Update the parent topic id of the content.
1766 - global $wpdb;
1767 - $wpdb->update( $wpdb->posts, array( 'post_parent' => $topic_id ), array( 'ID' => $content_id ) );
1768 - }
1769 -
1770 - // Save course content order.
1771 - $this->save_course_content_order( $sorting_order );
1772 -
1773 - wp_send_json_success();
592 + $referer_url = wp_get_referer();
593 + wp_redirect($referer_url);
1774 594 }
1775 595
1776 596 /**
1777 - * Restrict new student entry
1778 597 *
1779 - * @since 1.0.0
1780 - * @param mixed $content content.
598 + * Mark complete completed
1781 599 *
1782 - * @return mixed
600 + * @since v.1.0.0
1783 601 */
1784 - public function restrict_new_student_entry( $content ) {
1785 -
1786 - if ( ! tutor_utils()->is_course_fully_booked() ) {
1787 - // No restriction if not fully booked.
1788 - return $content;
1789 - }
1790 -
1791 - return '<div class="list-item-booking booking-full tutor-d-flex tutor-align-center"><div class="booking-progress tutor-d-flex"><span class="tutor-mr-8 tutor-color-warning tutor-icon-circle-info"></span></div><div class="tutor-fs-7 tutor-fw-medium">' .
1792 - __( 'Fully Booked', 'tutor' )
1793 - . '</div></div>';
1794 - }
1795 -
1796 - /**
1797 - * Restrict media
1798 - *
1799 - * @since 1.0.0
1800 - *
1801 - * @param string $where where clause.
1802 - *
1803 - * @return string
1804 - */
1805 - public function restrict_media( $where ) {
1806 - $action = Input::post( 'action' );
1807 - if ( 'query-attachments' === $action && tutor_utils()->is_instructor() ) {
1808 - if ( ! tutor_utils()->has_user_role( array( 'administrator', 'editor' ) ) ) {
1809 - $where .= ' AND post_author=' . get_current_user_id();
1810 - }
1811 - }
1812 -
1813 - return $where;
1814 - }
1815 -
1816 - /**
1817 - * Save course content order
1818 - *
1819 - * @since 1.0.0
1820 - * @since 3.9.8 param $order added.
1821 - *
1822 - * @param array $sort_order the lesson and topic order array.
1823 - *
1824 - * @return void
1825 - */
1826 - private function save_course_content_order( $sort_order = array() ) {
1827 - global $wpdb;
1828 -
1829 - if ( ! tutor_utils()->count( $sort_order ) ) {
602 + public function mark_course_complete(){
603 + if ( ! isset($_POST['tutor_action']) || $_POST['tutor_action'] !== 'tutor_complete_course' ){
1830 604 return;
1831 605 }
606 + //Checking nonce
607 + tutor_utils()->checking_nonce();
1832 608
1833 - $i = 0;
1834 - foreach ( $sort_order as $topic ) {
1835 - ++$i;
1836 - $wpdb->update(
1837 - $wpdb->posts,
1838 - array( 'menu_order' => $i ),
1839 - array( 'ID' => $topic['topic_id'] )
1840 - );
609 + $user_id = get_current_user_id();
1841 610
1842 - /**
1843 - * Removing All lesson with topic
1844 - */
1845 - $wpdb->update(
1846 - $wpdb->posts,
1847 - array( 'post_parent' => 0 ),
1848 - array( 'post_parent' => $topic['topic_id'] )
1849 - );
1850 -
1851 - /**
1852 - * Lesson Attaching with topic ID
1853 - * Sorting lesson
1854 - */
1855 - if ( isset( $topic['lesson_ids'] ) ) {
1856 - $lesson_ids = $topic['lesson_ids'];
1857 - } else {
1858 - $lesson_ids = array();
1859 - }
1860 - if ( count( $lesson_ids ) ) {
1861 - foreach ( $lesson_ids as $lesson_key => $lesson_id ) {
1862 - $wpdb->update(
1863 - $wpdb->posts,
1864 - array(
1865 - 'post_parent' => $topic['topic_id'],
1866 - 'menu_order' => $lesson_key,
1867 - ),
1868 - array( 'ID' => $lesson_id )
1869 - );
1870 - }
1871 - }
611 + //TODO: need to show view if not signed_in
612 + if ( ! $user_id){
613 + die(__('Please Sign-In', 'tutor'));
1872 614 }
1873 - }
1874 615
1875 - /**
1876 - * Insert Topic and attached it with Course
1877 - *
1878 - * @since 1.0.0
1879 - *
1880 - * @param integer $post_ID post ID.
1881 - * @param object $post post object.
1882 - *
1883 - * @return void
1884 - */
1885 - public function save_course_meta( $post_ID, $post ) {
1886 - global $wpdb;
616 + $course_id = (int) sanitize_text_field($_POST['course_id']);
1887 617
1888 - do_action( 'tutor_save_course', $post_ID, $post );
1889 -
618 + do_action('tutor_course_complete_before', $course_id);
1890 619 /**
1891 - * Save course price type
620 + * Marking course completed at Comment
1892 621 */
1893 - $price_type = Input::post( 'tutor_course_price_type' );
1894 - if ( $price_type ) {
1895 - update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, $price_type );
1896 - }
1897 622
1898 - //phpcs:disable WordPress.Security.NonceVerification.Missing
1899 - // Course Duration.
1900 - if ( ! empty( $_POST['course_duration'] ) ) {
1901 - $video = Input::post( 'course_duration', array(), Input::TYPE_ARRAY );
1902 - update_post_meta( $post_ID, '_course_duration', $video );
1903 - }
623 + global $wpdb;
1904 624
1905 - if ( ! empty( $_POST['_tutor_course_level'] ) ) {
1906 - $course_level = Input::post( '_tutor_course_level' );
1907 - update_post_meta( $post_ID, '_tutor_course_level', $course_level );
1908 - }
625 + $date = date("Y-m-d H:i:s", tutor_time());
1909 626
1910 - $additional_data_edit = Input::post( '_tutor_course_additional_data_edit' );
1911 - if ( $additional_data_edit ) {
1912 - if ( ! empty( $_POST['course_benefits'] ) ) {
1913 - $course_benefits = Input::post( 'course_benefits', '', Input::TYPE_KSES_POST );
1914 - update_post_meta( $post_ID, '_tutor_course_benefits', $course_benefits );
1915 - } elseif ( ! tutor_is_rest() ) {
1916 - delete_post_meta( $post_ID, '_tutor_course_benefits' );
1917 - }
627 + //Making sure that, hash is unique
628 + do{
629 + $hash = substr(md5(wp_generate_password(32).$date.$course_id.$user_id), 0, 16);
630 + $hasHash = (int) $wpdb->get_var($wpdb->prepare(
631 + "SELECT COUNT(comment_ID) from {$wpdb->comments}
632 + WHERE comment_agent = 'TutorLMSPlugin' AND comment_type = 'course_completed' AND comment_content = %s ", $hash));
1918 633
1919 - if ( ! empty( $_POST['course_requirements'] ) ) {
1920 - $requirements = Input::post( 'course_requirements', '', Input::TYPE_KSES_POST );
1921 - update_post_meta( $post_ID, '_tutor_course_requirements', $requirements );
1922 - } elseif ( ! tutor_is_rest() ) {
1923 - delete_post_meta( $post_ID, '_tutor_course_requirements' );
1924 - }
634 + }while($hasHash > 0);
1925 635
1926 - if ( ! empty( $_POST['course_target_audience'] ) ) {
1927 - $target_audience = Input::post( 'course_target_audience', '', Input::TYPE_KSES_POST );
1928 - update_post_meta( $post_ID, '_tutor_course_target_audience', $target_audience );
1929 - } elseif ( ! tutor_is_rest() ) {
1930 - delete_post_meta( $post_ID, '_tutor_course_target_audience' );
1931 - }
636 + $data = array(
637 + 'comment_post_ID' => $course_id,
638 + 'comment_author' => $user_id,
639 + 'comment_date' => $date,
640 + 'comment_date_gmt' => get_gmt_from_date($date),
641 + 'comment_content' => $hash, //Identification Hash
642 + 'comment_approved' => 'approved',
643 + 'comment_agent' => 'TutorLMSPlugin',
644 + 'comment_type' => 'course_completed',
645 + 'user_id' => $user_id,
646 + );
1932 647
1933 - if ( ! empty( $_POST['course_material_includes'] ) ) {
1934 - $material_includes = Input::post( 'course_material_includes', '', Input::TYPE_KSES_POST );
1935 - update_post_meta( $post_ID, '_tutor_course_material_includes', $material_includes );
1936 - } elseif ( ! tutor_is_rest() ) {
1937 - delete_post_meta( $post_ID, '_tutor_course_material_includes' );
1938 - }
1939 - //phpcs:enable WordPress.Security.NonceVerification.Missing
1940 - }
648 + $wpdb->insert($wpdb->comments, $data);
1941 649
1942 - $sorting_order = Input::post( 'tutor_topics_lessons_sorting', '' );
1943 - $sorting_order = json_decode( $sorting_order, true ) ?? array();
1944 - /**
1945 - * Sorting Topics and lesson
1946 - */
1947 - $this->save_course_content_order( $sorting_order );
650 + do_action('tutor_course_complete_after', $course_id, $user_id);
1948 651
1949 - // Additional data like course intro video.
1950 - if ( $additional_data_edit ) {
1951 - // Sanitize data through helper method.
1952 - $video = Input::sanitize_array(
1953 - $_POST['video'] ?? array(), //phpcs:ignore
1954 - array(
1955 - 'source_external_url' => 'esc_url',
1956 - 'source_embedded' => 'wp_kses_post',
1957 - ),
1958 - true
1959 - );
1960 - $video_source = tutor_utils()->array_get( 'source', $video );
1961 - if ( -1 !== $video_source ) {
1962 - update_post_meta( $post_ID, '_video', $video );
1963 - } elseif ( ! tutor_is_rest() ) {
1964 - delete_post_meta( $post_ID, '_video' );
1965 - }
1966 - }
652 + $permalink = get_the_permalink($course_id);
1967 653
1968 - /**
1969 - * Adding author to instructor automatically
1970 - */
1971 -
1972 - // Override post author id.
1973 - $author_id = isset( $_POST['post_author_override'] ) ? $_POST['post_author_override'] : $post->post_author; //phpcs:ignore
1974 - $attached = (int) $wpdb->get_var(
1975 - $wpdb->prepare(
1976 - "SELECT COUNT(umeta_id) FROM {$wpdb->usermeta}
1977 - WHERE user_id = %d
1978 - AND meta_key = '_tutor_instructor_course_id'
1979 - AND meta_value = %d ",
1980 - $author_id,
1981 - $post_ID
1982 - )
1983 - );
1984 -
1985 - if ( ! $attached ) {
1986 - add_user_meta( $author_id, '_tutor_instructor_course_id', $post_ID );
1987 - }
1988 -
1989 - /**
1990 - * Disable question and answer for this course
1991 - *
1992 - * @since 1.7.0
1993 - */
1994 - if ( $additional_data_edit ) {
1995 - foreach ( $this->additional_meta as $key ) {
1996 - //phpcs:ignore WordPress.Security.NonceVerification.Missing
1997 - update_post_meta( $post_ID, $key, ( isset( $_POST[ $key ] ) ? 'yes' : 'no' ) );
654 + // Set temporary identifier to show review pop up
655 + if(!get_tutor_option( 'disable_course_review' )) {
656 + $rating = tutor_utils()->get_course_rating_by_user($course_id, $user_id);
657 + if(!$rating || (empty($rating->rating) && empty($rating->review))) {
658 + update_option( 'tutor_course_complete_popup_'.$user_id, array(
659 + 'course_id' => $course_id,
660 + 'course_url' => $permalink,
661 + 'expires' => time()+10
662 + ));
1998 663 }
1999 664 }
2000 -
2001 - do_action( 'tutor_save_course_after', $post_ID, $post );
665 +
666 + wp_redirect($permalink);
667 + exit;
2002 668 }
2003 669
2004 - /**
2005 - * Save course topic
2006 - *
2007 - * @since 1.0.0
2008 - * @since 3.0.0 response and input name updated.
2009 - *
2010 - * @return void
2011 - */
2012 - public function tutor_save_topic() {
2013 - tutor_utils()->check_nonce();
670 + public function popup_review_form() {
671 + if(is_user_logged_in()) {
672 + $key = 'tutor_course_complete_popup_' . get_current_user_id();
673 + $popup = get_option( $key );
2014 674
2015 - $is_update = false;
2016 - $errors = array();
2017 - $topic_title = Input::post( 'title' );
675 + if(is_array($popup)) {
2018 676
2019 - if ( empty( $topic_title ) ) {
2020 - $errors['topic_title'] = __( 'Topic title is required!', 'tutor' );
2021 - $this->json_response(
2022 - __( 'Invalid inputs', 'tutor' ),
2023 - $errors,
2024 - HttpHelper::STATUS_UNPROCESSABLE_ENTITY
2025 - );
2026 - }
677 + if($popup['expires']>time()) {
678 + $course_id = $popup['course_id'];
679 + include tutor()->path.'views/modal/review.php';
680 + }
2027 681
2028 - // Gather parameters.
2029 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
2030 - $topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT );
2031 - $topic_summary = Input::post( 'summary', '', Input::TYPE_KSES_POST );
2032 -
2033 - $next_topic_order_id = tutor_utils()->get_next_topic_order_id( $course_id, $topic_id );
2034 -
2035 - // Validate if user can manage the topic.
2036 - if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) || ( $topic_id && ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) ) {
2037 - wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
682 + delete_option($key);
683 + }
2038 684 }
2039 -
2040 - // Create payload to create/update the topic.
2041 - $post_arr = array(
2042 - 'post_type' => 'topics',
2043 - 'post_title' => $topic_title,
2044 - 'post_content' => $topic_summary,
2045 - 'post_status' => 'publish',
2046 - 'post_author' => get_current_user_id(),
2047 - 'post_parent' => $course_id,
2048 - 'menu_order' => $next_topic_order_id,
2049 - );
2050 -
2051 - if ( $topic_id ) {
2052 - $is_update = true;
2053 - $post_arr['ID'] = $topic_id;
2054 - }
2055 -
2056 - $current_topic_id = wp_insert_post( $post_arr );
2057 -
2058 - if ( $is_update ) {
2059 - $this->json_response(
2060 - __( 'Topic updated successfully!', 'tutor' ),
2061 - $current_topic_id
2062 - );
2063 - } else {
2064 - $this->json_response(
2065 - __( 'Topic created successfully!', 'tutor' ),
2066 - $current_topic_id,
2067 - HttpHelper::STATUS_CREATED
2068 - );
2069 - }
2070 685 }
2071 -
2072 - /**
2073 - * Delete a course topic
2074 - *
2075 - * @since 1.0.0
2076 - * @since 3.0.0 code refactor and response updated.
2077 - *
2078 - * @return void
2079 - */
2080 - public function tutor_delete_topic() {
2081 - tutor_utils()->check_nonce();
2082 -
2083 - $topic_id = Input::post( 'topic_id', 0, Input::TYPE_INT );
2084 - if ( ! $topic_id || ! is_numeric( $topic_id ) || ! tutor_utils()->can_user_manage( 'topic', $topic_id ) ) {
2085 - $this->json_response(
2086 - tutor_utils()->error_message(),
2087 - null,
2088 - HttpHelper::STATUS_FORBIDDEN
2089 - );
2090 - }
2091 -
686 +
687 + public function tutor_load_instructors_modal(){
688 + tutils()->checking_nonce();
689 +
2092 690 global $wpdb;
2093 691
2094 - // Assign course ID to orphan content IDs since the topic will be deleted.
2095 - $course_id = tutor_utils()->get_course_id_by( 'topic', $topic_id );
2096 - $content_ids = tutor_utils()->get_course_content_ids_by( null, 'topic', $topic_id );
2097 - foreach ( $content_ids as $content_id ) {
2098 - update_post_meta( $content_id, '_tutor_course_id_for_lesson', $course_id );
2099 - // Actually all kind of contents.
2100 - // This keyword '_tutor_course_id_for_lesson' used just to support backward compatibility.
2101 - }
692 + $course_id = (int) sanitize_text_field($_POST['course_id']);
693 + $search_terms = sanitize_text_field(tutor_utils()->avalue_dot('search_terms', $_POST));
2102 694
2103 - // Set contents under the topic orphan.
2104 - $wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'post_parent' => $topic_id ) );
2105 -
2106 - // Then delete the topic from database.
2107 - $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $topic_id ) );
2108 - wp_delete_post( $topic_id );
2109 -
2110 - $this->json_response(
2111 - __( 'Topic deleted successfully!', 'tutor' )
2112 - );
2113 - }
2114 -
2115 - /**
2116 - * Handle enroll now action
2117 - *
2118 - * @since 1.0.0
2119 - *
2120 - * @return void
2121 - */
2122 - public function enroll_now() {
2123 -
2124 - if ( '_tutor_course_enroll_now' !== Input::post( 'tutor_course_action' ) || ! Input::has( 'tutor_course_id' ) ) {
2125 - return;
695 + if(!tutils()->can_user_manage('course', $course_id)) {
696 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
2126 697 }
698 +
699 + $saved_instructors = tutor_utils()->get_instructors_by_course($course_id);
700 + $instructors = array();
2127 701
2128 - // Checking Nonce.
2129 - tutor_utils()->checking_nonce();
702 + $not_in_sql = apply_filters('tutor_instructor_query_when_exists', " AND ID <1 ");
2130 703
2131 - $user_id = get_current_user_id();
2132 - if ( ! $user_id ) {
2133 - exit( esc_html__( 'Please Sign In first', 'tutor' ) );
704 + if ($saved_instructors){
705 + $saved_instructors_ids = wp_list_pluck($saved_instructors, 'ID');
706 + $instructor_not_in_ids = implode(',', $saved_instructors_ids);
707 + $not_in_sql .= "AND user.ID NOT IN($instructor_not_in_ids) ";
2134 708 }
2135 709
2136 - $course_id = Input::post( 'tutor_course_id', 0, Input::TYPE_INT );
2137 -
2138 - /**
2139 - * TODO: need to check purchase information
2140 - */
2141 -
2142 - $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
2143 -
2144 - $course = get_post( $course_id );
2145 -
2146 - if ( 'private' === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
2147 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
710 + $search_sql = '';
711 + if ($search_terms){
712 + $search_sql = "AND (user.user_login like '%{$search_terms}%' or user.user_nicename like '%{$search_terms}%' or user.display_name like '%{$search_terms}%') ";
2148 713 }
2149 714
2150 - /**
2151 - * If is is not purchasable, it's free, and enroll right now
2152 - * If purchasable, then process purchase.
2153 - *
2154 - * @since: v.1.0.0
2155 - */
2156 - if ( $is_purchasable ) { //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
2157 - // Process purchase.
715 + $instructors = $wpdb->get_results("SELECT user.ID, user.display_name from {$wpdb->users} user
716 + INNER JOIN {$wpdb->usermeta} meta ON user.ID = meta.user_id AND meta.meta_key = '_tutor_instructor_status' AND meta.meta_value = 'approved'
717 + WHERE 1=1 {$not_in_sql} {$search_sql} limit 10 ");
2158 718
2159 - } else {
2160 - // Free enroll.
2161 - EnrollmentModel::do_enroll( $course_id );
2162 - }
719 + $output = '';
720 + if (is_array($instructors) && count($instructors)){
721 + $instructor_output = '';
722 + foreach ($instructors as $instructor){
723 + $instructor_output .= "<p><label><input type='radio' name='tutor_instructor_ids[]' value='{$instructor->ID}' > {$instructor->display_name} </label></p>";
724 + }
2163 725
2164 - $referer_url = wp_get_referer();
2165 - wp_safe_redirect( tutor_utils()->get_nocache_url( $referer_url ) );
2166 - exit;
2167 - }
726 + $output .= apply_filters('tutor_course_instructors_html', $instructor_output, $instructors);
2168 727
2169 - /**
2170 - * Mark complete completed
2171 - *
2172 - * @since 1.0.0
2173 - *
2174 - * @since 3.7.1 Filter hook: tutor_user_can_complete_course added
2175 - *
2176 - * @return void
2177 - */
2178 - public function mark_course_complete() {
2179 - $tutor_action = Input::post( 'tutor_action' );
2180 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
2181 - if ( 'tutor_complete_course' !== $tutor_action || ! $course_id ) {
2182 - return;
728 + }else{
729 + $output .= __('<p>No instructor available or you have already added maximum instructors</p>', 'tutor');
2183 730 }
2184 731
2185 - $permalink = get_the_permalink( $course_id );
2186 732
2187 - // Checking nonce.
2188 - tutor_utils()->checking_nonce();
2189 -
2190 - $user_id = get_current_user_id();
2191 -
2192 - // TODO: need to show view if not signed_in.
2193 - if ( ! $user_id ) {
2194 - die( esc_html__( 'Please Sign-In', 'tutor' ) );
733 + if ( ! defined('TUTOR_MT_VERSION')){
734 + $output .= '<p class="tutor-notice-warning" style="margin-top: 50px; font-size: 14px;">'. sprintf( __('To add unlimited multiple instructors in your course, get %sTutor LMS Pro%s', 'tutor'), '<a href="https://www.themeum.com/product/tutor-lms" target="_blank">', "</a>" ) .'</p>';
2195 735 }
2196 736
2197 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2198 - die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
2199 - }
2200 -
2201 - /**
2202 - * Filter hook provided to restrict course completion. This is useful
2203 - * for specific cases like prerequisites. WP_Error should be returned
2204 - * from the filter value to prevent the completion.
2205 - */
2206 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
2207 -
2208 - if ( is_wp_error( $can_complete ) ) {
2209 - tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' );
2210 - } elseif ( ! $can_complete ) {
2211 - tutor_utils()->redirect_to( $permalink, __( 'You do not have permission to complete this course.', 'tutor' ), 'error' );
2212 - } else {
2213 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2214 - // Set temporary identifier to show review pop up.
2215 - self::set_review_popup_data( $user_id, $course_id, $permalink );
2216 -
2217 - wp_safe_redirect( $permalink );
2218 - exit;
2219 - }
737 + wp_send_json_success(array('output' => $output));
2220 738 }
2221 739
2222 - /**
2223 - * Ajax course complete handler
2224 - *
2225 - * @since 4.0.0
2226 - *
2227 - * @return void
2228 - */
2229 - public function ajax_tutor_complete_course() {
2230 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
740 + public function tutor_add_instructors_to_course(){
741 + tutils()->checking_nonce();
2231 742
2232 - // Checking nonce.
2233 - if ( ! tutor_utils()->is_nonce_verified() ) {
2234 - $this->response_bad_request( tutor_utils()->error_message( 'nonce' ) );
743 + $course_id = (int) sanitize_text_field($_POST['course_id']);
744 + $instructor_ids = tutor_utils()->avalue_dot('tutor_instructor_ids', $_POST);
745 +
746 + if(!tutils()->can_user_manage('course', $course_id)) {
747 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
2235 748 }
2236 749
2237 - if ( ! $course_id ) {
2238 - $this->response_bad_request( __( 'Invalid course', 'tutor' ) );
750 + if (is_array($instructor_ids) && count($instructor_ids)){
751 + foreach ($instructor_ids as $instructor_id){
752 + add_user_meta($instructor_id, '_tutor_instructor_course_id', $course_id);
753 + }
2239 754 }
2240 755
2241 - $user_id = get_current_user_id();
2242 - if ( ! EnrollmentModel::is_enrolled( $course_id, $user_id ) ) {
2243 - $this->response_bad_request( __( 'You are not enrolled in this course', 'tutor' ) );
2244 - }
756 + $saved_instructors = tutor_utils()->get_instructors_by_course($course_id);
757 + $output = '';
2245 758
2246 - /**
2247 - * Filter hook provided to restrict course completion. This is useful
2248 - * for specific cases like prerequisites. WP_Error should be returned
2249 - * from the filter value to prevent the completion.
2250 - */
2251 - $can_complete = apply_filters( 'tutor_user_can_complete_course', CourseModel::can_complete_course( $course_id, $user_id ), $user_id, $course_id );
759 + if ($saved_instructors){
760 + foreach ($saved_instructors as $t){
2252 761
2253 - if ( is_wp_error( $can_complete ) ) {
2254 - $this->response_bad_request( $can_complete->get_error_message() );
2255 - } elseif ( ! $can_complete ) {
2256 - $this->response_bad_request( __( 'You do not have permission to complete this course.', 'tutor' ) );
2257 - } else {
2258 - CourseModel::mark_course_as_completed( $course_id, $user_id );
2259 - // Set temporary identifier to show review pop up.
2260 - self::set_review_popup_data( $user_id, $course_id );
2261 -
2262 - $this->response_success( __( 'Course completed successfully', 'tutor' ) );
2263 - }
2264 - }
2265 -
2266 - /**
2267 - * Set data for review popup.
2268 - *
2269 - * @since 2.2.5
2270 - * @since 2.4.0 removed $permalink param. store user meta instead of option data.
2271 - *
2272 - * @param int $user_id user id.
2273 - * @param int $course_id course id.
2274 - *
2275 - * @return void
2276 - */
2277 - public static function set_review_popup_data( $user_id, $course_id ) {
2278 - if ( get_tutor_option( 'enable_course_review' ) ) {
2279 - $rating = tutor_utils()->get_course_rating_by_user( $course_id, $user_id );
2280 - if ( ! $rating || ( empty( $rating->rating ) && empty( $rating->review ) ) ) {
2281 - $meta_key = User::get_review_popup_meta( $course_id );
2282 - add_user_meta( $user_id, $meta_key, $course_id, true );
762 + $output .= '<div id="added-instructor-id-'.$t->ID.'" class="added-instructor-item added-instructor-item-'.$t->ID.'" data-instructor-id="'.$t->ID.'">
763 + <span class="instructor-icon">'.get_avatar($t->ID, 30).'</span>
764 + <span class="instructor-name"> '.$t->display_name.' </span>
765 + <span class="instructor-control">
766 + <a href="javascript:;" class="tutor-instructor-delete-btn"><i class="tutor-icon-line-cross"></i></a>
767 + </span>
768 + </div>';
2283 769 }
2284 770 }
2285 - }
2286 771
2287 - /**
2288 - * Popup review form on course details
2289 - *
2290 - * @since 1.0.0
2291 - * @return void
2292 - */
2293 - public function popup_review_form() {
2294 - if ( ! is_user_logged_in() ) {
2295 - return;
2296 - }
2297 -
2298 - $is_learning_area = tutor_utils()->is_learning_area();
2299 - $is_legacy_learning = tutor_utils()->is_legacy_learning_mode();
2300 - if ( $is_legacy_learning && $is_learning_area ) {
2301 - return;
2302 - }
2303 -
2304 - $course_id = get_the_ID();
2305 -
2306 - if ( $is_learning_area && ! Input::has( 'subpage' ) ) {
2307 - $course_id = wp_get_post_parent_id( wp_get_post_parent_id( get_the_ID() ) );
2308 - }
2309 -
2310 - if ( empty( $course_id ) ) {
2311 - return;
2312 - }
2313 -
2314 - $user_id = get_current_user_id();
2315 - $meta_key = User::get_review_popup_meta( $course_id );
2316 - $review_course_id = (int) get_user_meta( $user_id, $meta_key, true );
2317 - if ( is_single() && $course_id === $review_course_id ) {
2318 - include tutor()->path . 'views/modal/review.php';
2319 - }
772 + wp_send_json_success(array('output' => $output));
2320 773 }
2321 774
2322 - /**
2323 - * Review popup data clear
2324 - *
2325 - * @since 2.4.0
2326 - *
2327 - * @return void
2328 - */
2329 - public function clear_review_popup_data() {
775 + public function detach_instructor_from_course(){
2330 776 tutils()->checking_nonce();
2331 777
2332 - if ( is_user_logged_in() ) {
2333 - $user_id = get_current_user_id();
2334 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
778 + global $wpdb;
2335 779
2336 - if ( $course_id ) {
2337 - $meta_key = User::get_review_popup_meta( $course_id );
2338 - delete_user_meta( $user_id, $meta_key, $course_id );
2339 - }
780 + $instructor_id = (int) sanitize_text_field($_POST['instructor_id']);
781 + $course_id = (int) sanitize_text_field($_POST['course_id']);
2340 782
2341 - wp_send_json_success();
783 + if(!tutils()->can_user_manage('course', $course_id)) {
784 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
2342 785 }
786 +
787 + $wpdb->delete($wpdb->usermeta, array('user_id' => $instructor_id, 'meta_key' => '_tutor_instructor_course_id', 'meta_value' => $course_id) );
788 + wp_send_json_success();
2343 789 }
2344 790
2345 - /**
2346 - * Delete course delete from frontend dashboard
2347 - *
2348 - * @since 2.0.0
2349 - *
2350 - * @return void
2351 - */
2352 - public function tutor_delete_dashboard_course() {
2353 - tutor_utils()->checking_nonce();
791 + public function tutor_delete_dashboard_course(){
792 + tutils()->checking_nonce();
2354 793
2355 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
2356 - if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) ) {
2357 - wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
2358 - }
794 + $course_id = intval(sanitize_text_field($_POST['course_id']));
2359 795
2360 - /**
2361 - * Co-instructor can not delete a course
2362 - *
2363 - * @since 2.1.6
2364 - */
2365 - if ( false === CourseModel::is_main_instructor( $course_id ) ) {
2366 - wp_send_json_error( array( 'message' => __( 'Only main instructor can delete this course', 'tutor' ) ) );
796 + if(!tutils()->can_user_manage('course', $course_id)) {
797 + wp_send_json_error( array('message'=>__('Access Denied', 'tutor')) );
2367 798 }
2368 799
2369 - // Check if user is only an instructor.
2370 - if ( ! User::is_admin() ) {
2371 - // Check if instructor can trash course.
2372 - $can_trash_post = tutor_utils()->get_option( 'instructor_can_delete_course' );
800 + wp_trash_post($course_id);
801 + wp_send_json_success(['element'=>'course']);
802 + }
2373 803
2374 - if ( ! $can_trash_post ) {
2375 - wp_send_json_error( tutor_utils()->error_message() );
2376 - }
2377 - }
2378 804
2379 - $trash_course = wp_update_post(
2380 - array(
2381 - 'ID' => $course_id,
2382 - 'post_status' => CourseModel::STATUS_TRASH,
2383 - )
2384 - );
805 + public function tutor_add_gutenberg_author($data , $postarr){
806 + global $wpdb;
2385 807
2386 - if ( $trash_course ) {
2387 - wp_send_json_success( __( 'Course has been trashed successfully ', 'tutor' ) );
2388 - }
2389 - wp_send_json_success();
2390 - }
2391 -
2392 - /**
2393 - * Main author change from gutenberg editor
2394 - *
2395 - * @since 2.0.0
2396 - *
2397 - * @param array $data data.
2398 - * @param array $postarr post array.
2399 - *
2400 - * @return mixed
2401 - */
2402 - public function tutor_add_gutenberg_author( $data, $postarr ) {
2403 - $gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
2404 - $post_type = $postarr['post_type'];
2405 808 $courses_post_type = tutor()->course_post_type;
809 + $post_type = tutils()->array_get('post_type', $postarr);
2406 810
2407 - if ( false === is_admin() || false === $gutenberg_enabled || $post_type !== $courses_post_type ) {
2408 - return $data;
2409 - }
811 + if ($courses_post_type === $post_type){
812 + $post_ID = (int) tutor_utils()->avalue_dot('ID', $postarr);
813 + $post_author = (int) $wpdb->get_var($wpdb->prepare("SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID));
2410 814
2411 - /**
2412 - * Only admin can change main author
2413 - */
2414 - if ( $courses_post_type === $post_type && ! current_user_can( 'administrator' ) ) {
2415 - global $wpdb;
2416 - $post_ID = (int) tutor_utils()->avalue_dot( 'ID', $postarr );
2417 - $post_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT post_author FROM {$wpdb->posts} WHERE ID = %d ", $post_ID ) );
815 + if ($post_author > 0){
816 + $data['post_author'] = $post_author;
817 + }else{
818 + $data['post_author'] = get_current_user_id();
819 + }
820 + }
2418 821
2419 - if ( $post_author > 0 ) {
2420 - $data['post_author'] = $post_author;
2421 - } else {
2422 - $data['post_author'] = get_current_user_id();
2423 - }
2424 - }
2425 -
2426 822 return $data;
2427 823 }
2428 824
2429 825
2430 826 /**
2431 - * Attach product with course when course save from frontend or backend.
827 + * @param $post_ID
828 + * @param $postData
2432 829 *
2433 - * @since 1.3.4
830 + * Attach product during save course from the frontend course dashboard.
831 + *
832 + * @return string
2434 833 *
2435 - * @since 3.0.0 Store regular & sale price in meta to make compatible with Tutor monetization
2436 - *
2437 - * @param integer $post_ID course ID.
2438 - * @param array $post_data created course post details.
2439 - *
2440 - * @return void
834 + * @since v.1.3.4
2441 835 */
2442 - public function attach_product_with_course( $post_ID, $post_data ) {
2443 - $monetize_by = tutor_utils()->get_option( 'monetize_by' );
2444 - $product_id = Input::post( '_tutor_course_product_id', 0, Input::TYPE_INT );
836 + public function attach_product_with_course($post_ID, $postData){
837 + $attached_product_id = tutor_utils()->get_course_product_id($post_ID);
838 + $course_price = sanitize_text_field(tutor_utils()->array_get('course_price', $_POST));
2445 839
2446 - /**
2447 - * For native monetization, just return
2448 - * No need to attach anything.
2449 - */
2450 - if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
840 + if ( ! $course_price){
2451 841 return;
2452 842 }
2453 843
2454 - /**
2455 - * When course moved paid to free
2456 - * Keep the product linked and return.
2457 - */
2458 - if ( -1 === $product_id ) {
2459 - return;
2460 - }
844 + $monetize_by = tutor_utils()->get_option('monetize_by');
845 + $course = get_post($post_ID);
2461 846
2462 - /**
2463 - * Free user can only select product from dropdown
2464 - */
2465 - if ( tutor()->has_pro === false && 'wc' === $monetize_by ) {
2466 - if ( $product_id > 0 ) {
2467 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
847 + if ($monetize_by === 'wc'){
848 +
849 + $is_update = false;
850 + if ($attached_product_id){
851 + $wc_product = get_post_meta($attached_product_id, '_product_version', true);
852 + if ($wc_product){
853 + $is_update = true;
854 + }
2468 855 }
2469 856
2470 - return;
2471 - }
2472 -
2473 - $attached_product_id = tutor_utils()->get_course_product_id( $post_ID );
2474 - $course_price = Input::post( 'course_price', 0, Input::TYPE_NUMERIC );
2475 - $sale_price = Input::post( 'course_sale_price', 0, Input::TYPE_NUMERIC );
2476 -
2477 - if ( ! $course_price || $sale_price >= $course_price ) {
2478 - return;
2479 - }
2480 -
2481 - $course = get_post( $post_ID );
2482 -
2483 - update_post_meta( $post_ID, self::COURSE_PRICE_TYPE_META, self::PRICE_TYPE_PAID );
2484 -
2485 - if ( 'wc' === $monetize_by ) {
2486 -
2487 - $is_update = ( $product_id && wc_get_product( $product_id ) ) ? true : false;
2488 -
2489 - if ( $is_update ) {
2490 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
2491 -
2492 - $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price, $product_id );
2493 - $product_obj = wc_get_product( $product_id );
2494 - if ( $product_obj->is_type( 'subscription' ) ) {
2495 - update_post_meta( $product_id, '_subscription_price', $course_price );
857 + if ($is_update) {
858 + $productObj = wc_get_product($attached_product_id);
859 + $productObj->set_price($course_price); // set product price
860 + $productObj->set_regular_price($course_price); // set product regular price
861 + $productObj->set_sold_individually(true);
862 + $product_id = $productObj->save();
863 + if($productObj->is_type('subscription')) {
864 + update_post_meta( $attached_product_id, '_subscription_price', $course_price );
2496 865 }
2497 -
2498 - // Set course regular & sale price.
2499 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
2500 866 } else {
2501 - // Create new WC product name with course title.
2502 - $product_id = self::create_wc_product( $course->post_title, $course_price, $sale_price );
2503 - if ( $product_id ) {
2504 - $product_obj = wc_get_product( $product_id );
867 + $productObj = new \WC_Product();
868 + $productObj->set_name($course->post_title);
869 + $productObj->set_status('publish');
870 + $productObj->set_price($course_price); // set product price
871 + $productObj->set_regular_price($course_price); // set product regular price
872 + $productObj->set_sold_individually(true);
2505 873
2506 - self::sync_course_with_wc_product( $post_ID, $product_id );
874 + $product_id = $productObj->save();
875 + if ($product_id) {
876 + update_post_meta( $post_ID, '_tutor_course_product_id', $product_id );
877 + //Mark product for woocommerce
878 + update_post_meta( $product_id, '_virtual', 'yes' );
879 + update_post_meta( $product_id, '_tutor_product', 'yes' );
2507 880
2508 - // Set course regular & sale price.
2509 - self::set_course_regular_and_sale_price( $post_ID, $product_obj->get_regular_price(), $product_obj->get_sale_price() );
881 + $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
882 + if ( $coursePostThumbnail ) {
883 + set_post_thumbnail( $product_id, $coursePostThumbnail );
884 + }
2510 885 }
2511 886 }
2512 887
2513 - $course_post_thumbnail = Input::post( 'thumbnail_id', 0, Input::TYPE_INT );
2514 - if ( $product_id && $course_post_thumbnail ) {
2515 - set_post_thumbnail( $product_id, $course_post_thumbnail );
2516 - }
2517 - } elseif ( 'edd' === $monetize_by ) {
888 + }elseif ($monetize_by === 'edd'){
2518 889
2519 890 $is_update = false;
2520 -
2521 - if ( $attached_product_id ) {
2522 - $edd_price = get_post_meta( $attached_product_id, 'edd_price', true );
2523 - if ( $edd_price ) {
891 +
892 + if ($attached_product_id){
893 + $edd_price = get_post_meta($attached_product_id, 'edd_price', true);
894 + if ($edd_price){
2524 895 $is_update = true;
2525 896 }
2526 897 }
2527 898
2528 - if ( $is_update ) {
2529 - // Update the product.
899 + if ($is_update){
900 + //Update the product
2530 901 update_post_meta( $attached_product_id, 'edd_price', $course_price );
2531 - } else {
2532 - // Create new product.
902 + }else{
903 + //Create new product
2533 904
2534 - $post_arr = array(
2535 - 'post_type' => 'download',
2536 - 'post_title' => $course->post_title,
2537 - 'post_status' => 'publish',
2538 - 'post_author' => get_current_user_id(),
905 + $post_arr = array(
906 + 'post_type' => 'download',
907 + 'post_title' => $course->post_title,
908 + 'post_status' => 'publish',
909 + 'post_author' => get_current_user_id(),
2539 910 );
2540 911 $download_id = wp_insert_post( $post_arr );
2541 - if ( $download_id ) {
2542 - // EDD edd_price.
912 + if ($download_id ) {
913 + //edd_price
2543 914 update_post_meta( $download_id, 'edd_price', $course_price );
2544 915
2545 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $download_id );
2546 - // Mark product for EDD.
916 + update_post_meta( $post_ID, '_tutor_course_product_id', $download_id );
917 + //Mark product for EDD
2547 918 update_post_meta( $download_id, '_tutor_product', 'yes' );
2548 919
2549 - $course_post_thumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
2550 - if ( $course_post_thumbnail ) {
2551 - set_post_thumbnail( $download_id, $course_post_thumbnail );
920 + $coursePostThumbnail = get_post_meta( $post_ID, '_thumbnail_id', true );
921 + if ( $coursePostThumbnail ) {
922 + set_post_thumbnail( $download_id, $coursePostThumbnail );
2552 923 }
924 +
2553 925 }
926 +
2554 927 }
928 +
929 +
2555 930 }
931 +
2556 932 }
2557 933
934 +
2558 935 /**
936 + * Add Course level to course settings
937 + * @since v.1.4.1
938 + */
939 + public function add_course_level_to_settings(){
940 + include tutor()->path.'views/metabox/course-level-metabox.php';
941 + }
942 +
943 + /**
2559 944 * Check if course starting
2560 945 *
2561 - * @since 1.4.8
2562 - * @return void
946 + * @since v.1.4.8
2563 947 */
2564 - public function tutor_lesson_load_before() {
2565 - $course_id = tutor_utils()->get_course_id_by_content( get_the_ID() );
2566 - $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id );
2567 - if ( is_user_logged_in() ) {
2568 - $is_course_started = get_post_meta( $course_id, '_tutor_course_started', true );
2569 - if ( ! $completed_lessons && ! $is_course_started ) {
2570 - update_post_meta( $course_id, '_tutor_course_started', tutor_time() );
2571 - do_action( 'tutor/course/started', $course_id );
948 + public function tutor_lesson_load_before(){
949 + $course_id = tutils()->get_course_id_by_content(get_the_ID());
950 + $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course($course_id);
951 + if (is_user_logged_in()){
952 + $is_course_started = get_post_meta($course_id, '_tutor_course_started', true);
953 + if ( ! $completed_lessons && ! $is_course_started){
954 + update_post_meta($course_id, '_tutor_course_started', tutor_time());
955 + do_action('tutor/course/started', $course_id);
2572 956 }
2573 957 }
2574 958 }
2575 959
@@ -2574,34 +958,27 @@
2574 958 }
2575 959
2576 960 /**
2577 961 * Add Course level to course settings
2578 - *
2579 - * @since 1.4.8
2580 - *
2581 - * @return void
962 + * @since v.1.4.8
2582 963 */
2583 - public function course_elements_enable_disable() {
2584 - add_filter( 'tutor_course/single/completing-progress-bar', array( $this, 'enable_disable_course_progress_bar' ) );
2585 - add_filter( 'tutor_course/single/material_includes', array( $this, 'enable_disable_material_includes' ) );
2586 - add_filter( 'tutor_course/single/content', array( $this, 'enable_disable_course_content' ) );
2587 - add_filter( 'tutor_course/single/benefits_html', array( $this, 'enable_disable_course_benefits' ) );
2588 - add_filter( 'tutor_course/single/requirements_html', array( $this, 'enable_disable_course_requirements' ) );
2589 - add_filter( 'tutor_course/single/audience_html', array( $this, 'enable_disable_course_target_audience' ) );
2590 - add_filter( 'tutor_course/single/nav_items', array( $this, 'enable_disable_course_nav_items' ), 999, 2 );
964 + public function course_elements_enable_disable(){
965 + add_filter('tutor_course/single/completing-progress-bar', array($this, 'enable_disable_course_progress_bar') );
966 + add_filter('tutor_course/single/material_includes', array($this, 'enable_disable_material_includes') );
967 + add_filter('tutor_course/single/content', array($this, 'enable_disable_course_content') );
968 + add_filter('tutor_course/single/benefits_html', array($this, 'enable_disable_course_benefits') );
969 + add_filter('tutor_course/single/requirements_html', array($this, 'enable_disable_course_requirements') );
970 + add_filter('tutor_course/single/audience_html', array($this, 'enable_disable_course_target_audience') );
971 + add_filter('tutor_course/single/enrolled/nav_items', array($this, 'enable_disable_course_nav_items') );
2591 972 }
2592 973
2593 974 /**
2594 975 * Enable disable course progress bar
2595 - *
2596 - * @since 1.4.8
2597 - *
2598 - * @param string $html HTML string.
2599 - * @return string
976 + * @since v.1.4.8
2600 977 */
2601 - public function enable_disable_course_progress_bar( $html ) {
2602 - $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_progress_bar', true, true );
2603 - if ( $disable_option ) {
978 + public function enable_disable_course_progress_bar($html){
979 + $disable_option = (bool) get_tutor_option('disable_course_progress_bar');
980 + if($disable_option){
2604 981 return '';
2605 982 }
2606 983 return $html;
2607 984 }
@@ -2607,18 +984,13 @@
2607 984 }
2608 985
2609 986 /**
2610 987 * Enable disable material includes
2611 - *
2612 - * @since 1.4.8
2613 - *
2614 - * @param string $html HTML string.
2615 - *
2616 - * @return string
988 + * @since v.1.4.8
2617 989 */
2618 - public function enable_disable_material_includes( $html ) {
2619 - $disable_option = ! (bool) get_tutor_option( 'enable_course_material', true, true );
2620 - if ( $disable_option ) {
990 + public function enable_disable_material_includes($html){
991 + $disable_option = (bool) get_tutor_option('disable_course_material');
992 + if($disable_option){
2621 993 return '';
2622 994 }
2623 995 return $html;
2624 996 }
@@ -2624,18 +996,13 @@
2624 996 }
2625 997
2626 998 /**
2627 999 * Enable disable course content
2628 - *
2629 - * @since 1.4.8
2630 - *
2631 - * @param string $html HTML string.
2632 - *
2633 - * @return string
1000 + * @since v.1.4.8
2634 1001 */
2635 - public function enable_disable_course_content( $html ) {
2636 - $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_description', true, true );
2637 - if ( $disable_option ) {
1002 + public function enable_disable_course_content($html){
1003 + $disable_option = (bool) get_tutor_option('disable_course_description');
1004 + if($disable_option){
2638 1005 return '';
2639 1006 }
2640 1007 return $html;
2641 1008 }
@@ -2641,18 +1008,13 @@
2641 1008 }
2642 1009
2643 1010 /**
2644 1011 * Enable disable course benefits
2645 - *
2646 - * @since 1.4.8
2647 - *
2648 - * @param string $html HTML string.
2649 - *
2650 - * @return string
1012 + * @since v.1.4.8
2651 1013 */
2652 - public function enable_disable_course_benefits( $html ) {
2653 - $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_benefits', true, true );
2654 - if ( $disable_option ) {
1014 + public function enable_disable_course_benefits($html){
1015 + $disable_option = (bool) get_tutor_option('disable_course_benefits');
1016 + if($disable_option){
2655 1017 return '';
2656 1018 }
2657 1019 return $html;
2658 1020 }
@@ -2658,18 +1020,13 @@
2658 1020 }
2659 1021
2660 1022 /**
2661 1023 * Enable disable course requirements
2662 - *
2663 - * @since 1.4.8
2664 - *
2665 - * @param string $html HTML string.
2666 - *
2667 - * @return string
1024 + * @since v.1.4.8
2668 1025 */
2669 - public function enable_disable_course_requirements( $html ) {
2670 - $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_requirements', true, true );
2671 - if ( $disable_option ) {
1026 + public function enable_disable_course_requirements($html){
1027 + $disable_option = (bool) get_tutor_option('disable_course_requirements');
1028 + if($disable_option){
2672 1029 return '';
2673 1030 }
2674 1031 return $html;
2675 1032 }
@@ -2675,101 +1032,64 @@
2675 1032 }
2676 1033
2677 1034 /**
2678 1035 * Enable disable course target audience
2679 - *
2680 - * @since 1.4.8
2681 - *
2682 - * @param string $html HTML string.
2683 - *
2684 - * @return string
1036 + * @since v.1.4.8
2685 1037 */
2686 - public function enable_disable_course_target_audience( $html ) {
2687 - $disable_option = ! (bool) tutor_utils()->get_option( 'enable_course_target_audience', true, true );
2688 - if ( $disable_option ) {
1038 + public function enable_disable_course_target_audience($html){
1039 + $disable_option = (bool) get_tutor_option('disable_course_target_audience');
1040 + if($disable_option){
2689 1041 return '';
2690 1042 }
2691 1043 return $html;
2692 1044 }
2693 -
1045 +
2694 1046 /**
2695 1047 * Enable disable course nav items
2696 - *
2697 - * @since 1.4.8
2698 - *
2699 - * @param array $items item list.
2700 - * @param integer $course_id course ID.
2701 - *
2702 - * @return array
1048 + * @since v.1.4.8
2703 1049 */
2704 - public function enable_disable_course_nav_items( $items, $course_id ) {
1050 + public function enable_disable_course_nav_items($items){
2705 1051 global $wp_query, $post;
2706 - $enable_q_and_a_on_course = (bool) get_tutor_option( 'enable_q_and_a_on_course' );
2707 - $disable_course_announcements = ! (bool) tutor_utils()->get_option( 'enable_course_announcements', true, true );
2708 - $disable_qa_for_this_course = ( $wp_query->is_single && ! empty( $post ) ) ? get_post_meta( $post->ID, '_tutor_enable_qa', true ) != 'yes' : false;
1052 + $enable_q_and_a_on_course = (bool) get_tutor_option('enable_q_and_a_on_course');
1053 + $disable_course_announcements = (bool) get_tutor_option('disable_course_announcements');
2709 1054
2710 - // Whether Q&A enabled.
2711 - if ( ! $enable_q_and_a_on_course || $disable_qa_for_this_course ) {
2712 - if ( tutor_utils()->array_get( 'questions', $items ) ) {
2713 - unset( $items['questions'] );
1055 + $disable_qa_for_this_course = ($wp_query->is_single && !empty($post)) ? get_post_meta($post->ID, '_tutor_disable_qa', true) : '';
1056 +
1057 + if(!$enable_q_and_a_on_course || $disable_qa_for_this_course == 'yes') {
1058 + if(tutils()->array_get('questions', $items)) {
1059 + unset($items['questions']);
2714 1060 }
2715 1061 }
2716 -
2717 - // Whether announcment enabled.
2718 - if ( $disable_course_announcements ) {
2719 - if ( tutor_utils()->array_get( 'announcements', $items ) ) {
2720 - unset( $items['announcements'] );
1062 + if($disable_course_announcements){
1063 + if(tutils()->array_get('announcements', $items)) {
1064 + unset($items['announcements']);
2721 1065 }
2722 1066 }
2723 -
2724 - // Hide review section if disabled.
2725 - if ( ! get_tutor_option( 'enable_course_review' ) ) {
2726 - unset( $items['reviews'] );
2727 - }
2728 -
2729 - // Whether enrollment require.
2730 - $is_enrolled = EnrollmentModel::is_enrolled();
2731 -
2732 - return array_filter(
2733 - $items,
2734 - function ( $item ) use ( $is_enrolled ) {
2735 - if ( isset( $item['require_enrolment'] ) && $item['require_enrolment'] ) {
2736 - return $is_enrolled;
2737 - }
2738 - return true;
2739 - }
2740 - );
1067 + return $items;
2741 1068 }
2742 1069
2743 1070 /**
2744 1071 * Filter product in shop page
2745 - *
2746 - * @since 1.4.9
2747 - *
2748 - * @return void|null
1072 + * @since v.1.4.9
2749 1073 */
2750 - public function filter_product_in_shop_page() {
2751 - $hide_course_from_shop_page = (bool) get_tutor_option( 'hide_course_from_shop_page' );
2752 - if ( ! $hide_course_from_shop_page ) {
1074 + public function filter_product_in_shop_page(){
1075 + $hide_course_from_shop_page = (bool) get_tutor_option('hide_course_from_shop_page');
1076 + if(!$hide_course_from_shop_page){
2753 1077 return;
2754 1078 }
2755 - add_action( 'woocommerce_product_query', array( $this, 'filter_woocommerce_product_query' ) );
2756 - add_filter( 'edd_downloads_query', array( $this, 'filter_edd_downloads_query' ), 10, 2 );
2757 - add_action( 'pre_get_posts', array( $this, 'filter_archive_meta_query' ), 1 );
1079 + add_action('woocommerce_product_query', array($this, 'filter_woocommerce_product_query'));
1080 + add_filter('edd_downloads_query', array($this, 'filter_edd_downloads_query'), 10, 2);
1081 + add_action('pre_get_posts', array($this, 'filter_archive_meta_query'), 1);
2758 1082 }
2759 1083
2760 -
2761 1084 /**
2762 1085 * Tutor product meta query
2763 - *
2764 - * @since 1.4.9
2765 - *
2766 - * @return array
1086 + * @since v.1.4.9
2767 1087 */
2768 - public function tutor_product_meta_query() {
1088 + public function tutor_product_meta_query(){
2769 1089 $meta_query = array(
2770 - 'key' => '_tutor_product',
2771 - 'compare' => 'NOT EXISTS',
1090 + 'key' => '_tutor_product',
1091 + 'compare' => 'NOT EXISTS'
2772 1092 );
2773 1093 return $meta_query;
2774 1094 }
2775 1095
@@ -2774,60 +1094,20 @@
2774 1094 }
2775 1095
2776 1096 /**
2777 1097 * Filter product in woocommerce shop page
2778 - *
2779 - * @since 1.4.9
2780 - *
2781 - * @param \WP_Query $wp_query WP Query instance.
2782 - *
2783 - * @return \WP_Query
1098 + * @since v.1.4.9
2784 1099 */
2785 - public function filter_woocommerce_product_query( $wp_query ) {
2786 - $product_ids = $this->get_connected_wc_product_ids();
2787 - $wp_query->set( 'post__not_in', $product_ids );
1100 + public function filter_woocommerce_product_query($wp_query){
1101 + $wp_query->set('meta_query', array($this->tutor_product_meta_query()));
2788 1102 return $wp_query;
2789 1103 }
2790 1104
2791 1105 /**
2792 - * Get connected woocommerce product ids for course and course bundle
2793 - *
2794 - * @since 2.7.2
2795 - *
2796 - * @return array
2797 - */
2798 - public function get_connected_wc_product_ids() {
2799 - global $wpdb;
2800 -
2801 - $results = $wpdb->get_results(
2802 - $wpdb->prepare(
2803 - "SELECT DISTINCT pm.meta_value product_id
2804 - FROM {$wpdb->posts} p
2805 - INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
2806 - AND pm.meta_key = %s
2807 - WHERE post_type IN( 'courses','course-bundle' )",
2808 - '_tutor_course_product_id'
2809 - )
2810 - );
2811 -
2812 - $ids = array();
2813 - if ( is_array( $results ) && count( $results ) ) {
2814 - $ids = array_column( $results, 'product_id' );
2815 - }
2816 -
2817 - return $ids;
2818 - }
2819 -
2820 - /**
2821 1106 * Filter product in edd downloads shortcode page
2822 - *
2823 - * @since 1.4.9
2824 - *
2825 - * @param \WP_Query $query WP Query instance.
2826 - *
2827 - * @return \WP_Query
1107 + * @since v.1.4.9
2828 1108 */
2829 - public function filter_edd_downloads_query( $query ) {
1109 + public function filter_edd_downloads_query($query){
2830 1110 $query['meta_query'][] = $this->tutor_product_meta_query();
2831 1111 return $query;
2832 1112 }
2833 1113
@@ -2832,712 +1112,288 @@
2832 1112 }
2833 1113
2834 1114 /**
2835 1115 * Filter product in edd downloads archive page
2836 - *
2837 - * @since 1.4.9
2838 - *
2839 - * @param \WP_Query $wp_query WP Query instance.
2840 - *
2841 - * @return \WP_Query
1116 + * @since v.1.4.9
2842 1117 */
2843 - public function filter_archive_meta_query( $wp_query ) {
2844 - if ( ! is_admin() && $wp_query->is_archive && $wp_query->get( 'post_type' ) === 'download' ) {
2845 - $wp_query->set( 'meta_query', array( $this->tutor_product_meta_query() ) );
1118 + public function filter_archive_meta_query($wp_query){
1119 + if(!is_admin() && $wp_query->is_archive && $wp_query->get('post_type') === 'download'){
1120 + $wp_query->set('meta_query', array($this->tutor_product_meta_query()));
2846 1121 }
2847 1122 return $wp_query;
2848 1123 }
2849 1124
2850 - /**
2851 - * Removed course price if already enrolled at single course
2852 - *
2853 - * @since 1.5.8
2854 - *
2855 - * @param string $html HTML string.
2856 - *
2857 - * @return string
2858 - */
2859 - public function remove_price_if_enrolled( $html ) {
2860 - $should_removed = apply_filters( 'should_remove_price_if_enrolled', true );
1125 + /**
1126 + * @param $html
1127 + * @return string
1128 + *
1129 + * Removed course price if already enrolled at single course
1130 + *
1131 + * @since v.1.5.8
1132 + */
1133 + public function remove_price_if_enrolled($html){
1134 + $should_removed = apply_filters('should_remove_price_if_enrolled', true);
2861 1135
2862 - if ( $should_removed ) {
2863 - $course_id = get_the_ID();
2864 - $enrolled = EnrollmentModel::is_enrolled( $course_id );
2865 - if ( $enrolled ) {
2866 - $html = '';
2867 - }
2868 - }
2869 - return $html;
2870 - }
1136 + if ($should_removed){
1137 + $course_id = get_the_ID();
1138 + $enrolled = tutils()->is_enrolled($course_id);
1139 + if ($enrolled){
1140 + $html = '';
1141 + }
1142 + }
1143 + return $html;
1144 + }
2871 1145
2872 - /**
2873 - * Get course completion missing requirements message.
2874 - *
2875 - * @since 4.0.0
2876 - *
2877 - * @param int $course_id Course ID.
2878 - * @param int $user_id User ID.
2879 - *
2880 - * @return string|null
2881 - */
2882 - public static function get_course_completion_restrict_msg( $course_id = 0, $user_id = 0 ) {
2883 - $course_id = tutor_utils()->get_post_id( $course_id );
2884 - $user_id = tutor_utils()->get_user_id( $user_id );
1146 + /**
1147 + * @param $html
1148 + * @return string
1149 + *
1150 + * Check if all lessons and quizzes done before mark course complete.
1151 + */
1152 + function tutor_lms_hide_course_complete_btn($html){
2885 1153
2886 - if ( 'strict' !== tutor_utils()->get_option( 'course_completion_process' ) ) {
2887 - return null;
2888 - }
1154 + $completion_mode = tutils()->get_option('course_completion_process');
1155 + if ($completion_mode !== 'strict'){
1156 + return $html;
1157 + }
2889 1158
2890 - $completed_lessons = tutor_utils()->get_completed_lesson_count_by_course( $course_id, $user_id );
2891 - $total_lessons = tutor_utils()->get_lesson_count_by_course( $course_id );
1159 + $completed_lesson = tutils()->get_completed_lesson_count_by_course();
1160 + $lesson_count = tutils()->get_lesson_count_by_course();
2892 1161
2893 - if ( $completed_lessons < $total_lessons ) {
2894 - return __( 'Complete all lessons to mark this course as complete', 'tutor' );
2895 - }
1162 + if ($completed_lesson < $lesson_count){
1163 + return '<p class="suggestion-before-course-complete">'.__('complete all lessons to mark this course as complete', 'tutor').'</p>';
1164 + }
2896 1165
2897 - $course_contents = tutor_utils()->get_course_contents_by_id( $course_id );
1166 + $quizzes = array();
2898 1167
2899 - $required_quiz_pass_count = 0;
2900 - $required_assignment_pass_count = 0;
1168 + $course_contents = tutils()->get_course_contents_by_id();
1169 + if (tutils()->count($course_contents)){
1170 + foreach ($course_contents as $content){
1171 + if ($content->post_type === 'tutor_quiz'){
1172 + $quizzes[] = $content;
1173 + }
1174 + }
1175 + }
2901 1176
2902 - $is_assignment_addon_enabled = tutor_utils()->is_addon_enabled( 'tutor-assignments' );
1177 + $is_pass = true;
1178 + $required_quiz_pass = 0;
2903 1179
2904 - foreach ( $course_contents as $content ) {
1180 + if (tutils()->count($quizzes)){
1181 + foreach ($quizzes as $quiz){
2905 1182
2906 - if ( tutor()->quiz_post_type === $content->post_type ) {
2907 - if ( ! QuizModel::is_quiz_passed( $content->ID, $user_id ) ) {
2908 - ++$required_quiz_pass_count;
2909 - }
2910 - }
1183 + $attempt = tutils()->get_quiz_attempt($quiz->ID);
1184 + if ($attempt) {
1185 + $passing_grade = tutor_utils()->get_quiz_option($quiz->ID, 'passing_grade', 0);
1186 + $earned_percentage = $attempt->earned_marks > 0 ? (number_format(($attempt->earned_marks * 100) / $attempt->total_marks)) : 0;
2911 1187
2912 - if ( $is_assignment_addon_enabled && tutor()->assignment_post_type === $content->post_type ) {
2913 - if ( ! Assignments::is_assignment_passed( $content->ID, $user_id ) ) {
2914 - ++$required_assignment_pass_count;
2915 - }
2916 - }
2917 - }
1188 + if ($earned_percentage < $passing_grade) {
1189 + $required_quiz_pass++;
1190 + $is_pass = false;
1191 + }
1192 + }else{
1193 + $required_quiz_pass++;
1194 + $is_pass = false;
1195 + }
1196 + }
1197 + }
2918 1198
2919 - if ( ! $required_quiz_pass_count && ! $required_assignment_pass_count ) {
2920 - return null;
2921 - }
1199 + if ( ! $is_pass){
1200 + return '<p class="suggestion-before-course-complete">'.sprintf(__('You have to pass %s quizzes to complete this course.', 'tutor'), $required_quiz_pass).'</p>';
1201 + }
2922 1202
2923 - return self::get_course_completion_requirement_message(
2924 - $required_quiz_pass_count,
2925 - $required_assignment_pass_count
2926 - );
2927 - }
1203 + return $html;
1204 + }
2928 1205
2929 - /**
2930 - * Build missing course completion requirements message.
2931 - *
2932 - * @since 4.0.0
2933 - *
2934 - * @param int $quiz_count Total quiz count.
2935 - * @param int $assignment_count Total assignment count.
2936 - *
2937 - * @return string
2938 - */
2939 - private static function get_course_completion_requirement_message( $quiz_count, $assignment_count ) {
2940 - $quiz_label = _n( 'quiz', 'quizzes', $quiz_count, 'tutor' );
2941 - $assignment_label = _n( 'assignment', 'assignments', $assignment_count, 'tutor' );
2942 -
2943 - if ( $quiz_count && ! $assignment_count ) {
2944 - return sprintf(
2945 - /* translators: %1$s: item count; %2$s: item label */
2946 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2947 - $quiz_count,
2948 - $quiz_label
2949 - );
2950 - }
2951 -
2952 - if ( ! $quiz_count && $assignment_count ) {
2953 - return sprintf(
2954 - /* translators: %1$s: item count; %2$s: item label */
2955 - __( 'You have to pass %1$s %2$s to complete this course.', 'tutor' ),
2956 - $assignment_count,
2957 - $assignment_label
2958 - );
2959 - }
2960 -
2961 - return sprintf(
2962 - /* translators: %1$s: quiz count; %2$s: quiz label; %3$s: assignment count; %4$s: assignment label */
2963 - __( 'You have to pass %1$s %2$s and %3$s %4$s to complete this course.', 'tutor' ),
2964 - $quiz_count,
2965 - $quiz_label,
2966 - $assignment_count,
2967 - $assignment_label
2968 - );
1206 + public function get_generate_greadbook($html){
1207 + if ( ! tutils()->is_completed_course()){
1208 + return '';
1209 + }
1210 + return $html;
2969 1211 }
2970 1212
2971 1213 /**
2972 - * Check if all lessons and quizzes done before mark course complete.
2973 - *
2974 - * @since 1.5.8
2975 - *
2976 - * @param string $html HTML string.
2977 - *
2978 - * @return string
2979 - */
2980 - public function tutor_lms_hide_course_complete_btn( $html ) {
2981 - $_msg = self::get_course_completion_restrict_msg();
2982 -
2983 - if ( $_msg ) {
2984 - return '<div class="tutor-alert tutor-warning tutor-mt-28">
2985 - <div class="tutor-alert-text">
2986 - <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
2987 - <span>' . $_msg . '</span>
2988 - </div>
2989 - </div>';
2990 - }
2991 -
2992 - return $html;
2993 - }
2994 -
2995 - /**
2996 - * Generate Gradebook
2997 - *
2998 - * @since 1.5.8
2999 - *
3000 - * @param string $html HTML string.
3001 - *
3002 - * @return string
3003 - */
3004 - public function get_generate_greadbook( $html ) {
3005 - if ( ! tutor_utils()->is_completed_course() ) {
3006 - return '';
3007 - }
3008 - return $html;
3009 - }
3010 -
3011 - /**
3012 1214 * Add social share content in header
3013 - *
3014 - * @since 1.6.3
3015 - *
3016 - * @return void
1215 + * @since v.1.6.3
3017 1216 */
3018 - public function social_share_content() {
1217 + public function social_share_content(){
3019 1218 global $wp_query, $post;
3020 - if ( $wp_query->is_single && ! empty( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] === $this->course_post_type ) { ?>
1219 + if ($wp_query->is_single && ! empty($wp_query->query_vars['post_type']) && $wp_query->query_vars['post_type'] === $this->course_post_type) { ?>
3021 1220 <!--Facebook-->
3022 1221 <meta property="og:type" content="website"/>
3023 - <meta property="og:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>" />
3024 - <meta property="og:description" content="<?php echo esc_html( $post->post_content ); ?>" />
1222 + <meta property="og:image" content="<?php echo get_tutor_course_thumbnail_src(); ?>" />
1223 + <meta property="og:description" content="<?php echo esc_html($post->post_content); ?>" />
3025 1224 <!--Twitter-->
3026 - <meta name="twitter:image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
3027 - <meta name="twitter:description" content="<?php echo esc_html( $post->post_content ); ?>">
1225 + <meta name="twitter:image" content="<?php echo get_tutor_course_thumbnail_src(); ?>">
1226 + <meta name="twitter:description" content="<?php echo esc_html($post->post_content); ?>">
3028 1227 <!--Google+-->
3029 - <meta itemprop="image" content="<?php echo esc_url( get_tutor_course_thumbnail_src() ); ?>">
3030 - <meta itemprop="description" content="<?php echo esc_html( $post->post_content ); ?>">
3031 - <?php
1228 + <meta itemprop="image" content="<?php echo get_tutor_course_thumbnail_src(); ?>">
1229 + <meta itemprop="description" content="<?php echo esc_html($post->post_content); ?>"> <?php
3032 1230 }
3033 1231 }
3034 1232
3035 1233 /**
3036 - * Delete associated enrollment
3037 - *
3038 - * @since 1.8.2
3039 - *
3040 - * @param integer $post_id post ID.
3041 - *
3042 - * @return void
1234 + * Get posts by type and parent
1235 + * @since v.1.6.6
3043 1236 */
3044 - public function delete_associated_enrollment( $post_id ) {
3045 - global $wpdb;
3046 -
3047 - $enroll_id = $wpdb->get_var(
3048 - $wpdb->prepare(
3049 - "SELECT
3050 - post_id
3051 - FROM
3052 - {$wpdb->postmeta}
3053 - WHERE
3054 - meta_key=%s
3055 - AND meta_value = %d
3056 - ",
3057 - EnrollmentModel::ENROLLMENT_ORDER_ID_META,
3058 - $post_id
3059 - )
1237 + public function tutor_get_post_ids($post_type, $post_parent) {
1238 + $args = array(
1239 + 'fields' => 'ids',
1240 + 'post_type' => $post_type,
1241 + 'post_parent' => $post_parent,
1242 + 'post_status' => 'any',
1243 + 'posts_per_page' => -1,
3060 1244 );
3061 -
3062 - if ( is_numeric( $enroll_id ) && $enroll_id > 0 ) {
3063 -
3064 - $course_id = get_post_field( 'post_parent', $enroll_id );
3065 - $user_id = get_post_field( 'post_author', $enroll_id );
3066 -
3067 - tutor_utils()->cancel_course_enrol( $course_id, $user_id );
3068 - }
1245 + return get_posts($args);
3069 1246 }
3070 -
1247 +
3071 1248 /**
3072 - * Reset course progress.
3073 - *
3074 - * @since 1.5.8
3075 - *
3076 - * @return void
1249 + * Delete course data when permanently deleting a course.
1250 + * @since v.1.6.6
3077 1251 */
3078 - public function tutor_reset_course_progress() {
3079 - tutor_utils()->checking_nonce();
3080 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3081 - $context = Input::post( 'context', '' );
3082 - $course_reset_progress = tutor_utils()->get_option( 'course_reset_progress', false );
3083 - $course_retake_feature = tutor_utils()->get_option( 'course_retake_feature', false );
1252 + function delete_tutor_course_data( $post_id ) {
1253 + $course_post_type = tutor()->course_post_type;
1254 + $lesson_post_type = tutor()->lesson_post_type;
3084 1255
3085 - if ( ! $course_reset_progress && 'learning-area-sidebar' === $context ) {
3086 - $this->response_bad_request( __( 'You do not have permission to reset this course.', 'tutor' ) );
3087 - return;
3088 - }
1256 + if (get_post_type($post_id) == $course_post_type) {
1257 + global $wpdb;
1258 + $topic_ids = $this->tutor_get_post_ids('topics', $post_id);
1259 + if ( !empty($topic_ids) ) {
1260 + foreach ($topic_ids as $topic_id) {
1261 + $content_post_type = apply_filters('tutor_course_contents_post_types', array($lesson_post_type, 'tutor_quiz'));
1262 + $topic_content_ids = $this->tutor_get_post_ids($content_post_type, $topic_id);
1263 +
1264 + foreach ($topic_content_ids as $content_id) {
1265 + if( get_post_type($content_id) == 'tutor_quiz') {
1266 + $wpdb->delete($wpdb->prefix.'tutor_quiz_attempts', array('quiz_id' => $content_id));
1267 + $wpdb->delete($wpdb->prefix.'tutor_quiz_attempt_answers', array('quiz_id' => $content_id));
3089 1268
3090 - if ( ! $course_retake_feature && ( 'course-landing' === $context || 'learning-area' === $context ) ) {
3091 - $this->response_bad_request( __( 'You do not have permission to retake this course.', 'tutor' ) );
3092 - return;
3093 - }
3094 -
3095 - if ( ! $course_id || ! is_numeric( $course_id ) || ! EnrollmentModel::is_enrolled( $course_id ) ) {
3096 - $this->response_bad_request( __( 'Invalid Course ID or Access Denied.', 'tutor' ) );
3097 - return;
3098 - }
3099 -
3100 - tutor_utils()->delete_course_progress( $course_id );
3101 - $this->json_response( '', array( 'redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ) );
3102 - }
3103 -
3104 - /**
3105 - * Do enroll if guest attempt to enroll and course is free
3106 - *
3107 - * @since 1.9.8
3108 - *
3109 - * @param integer $course_id course ID.
3110 - * @param integer $user_id user ID.
3111 - *
3112 - * @return void
3113 - */
3114 - public function enroll_after_login_if_attempt( int $course_id, int $user_id ) {
3115 - $course_id = sanitize_text_field( $course_id );
3116 - $is_allowed = apply_filters( 'tutor_allow_guest_attempt_enrollment', true, $course_id, $user_id );
3117 -
3118 - if ( $course_id && $is_allowed ) {
3119 - $is_purchasable = tutor_utils()->is_course_purchasable( $course_id );
3120 - if ( ! $is_purchasable ) {
3121 - EnrollmentModel::do_enroll( $course_id, $order_id = 0, $user_id );
3122 - do_action( 'guest_attempt_after_enrollment', $course_id );
1269 + $questions_ids = $wpdb->get_col($wpdb->prepare("SELECT question_id FROM {$wpdb->prefix}tutor_quiz_questions WHERE quiz_id = %d ", $content_id));
1270 + if (is_array($questions_ids) && count($questions_ids)){
1271 + $in_question_ids = "'".implode("','", $questions_ids)."'";
1272 + $wpdb->query("DELETE FROM {$wpdb->prefix}tutor_quiz_question_answers WHERE belongs_question_id IN({$in_question_ids}) ");
1273 + }
1274 + $wpdb->delete($wpdb->prefix.'tutor_quiz_questions', array('quiz_id' => $content_id));
1275 + }
1276 + wp_delete_post($content_id, true);
1277 + }
1278 + wp_delete_post($topic_id, true);
1279 + }
3123 1280 }
3124 - }
3125 - }
3126 -
3127 - /**
3128 - * Handle course enrollment
3129 - *
3130 - * @since 2.1.0
3131 - *
3132 - * @return void
3133 - */
3134 - public function course_enrollment() {
3135 - tutor_utils()->checking_nonce();
3136 -
3137 - $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
3138 - $user_id = get_current_user_id();
3139 -
3140 - if ( ! $course_id || ! $user_id ) {
3141 - wp_send_json_error( tutor_utils()->error_message( 'invalid_req' ) );
3142 - }
3143 -
3144 - $password_protected = post_password_required( $course_id );
3145 - if ( $password_protected ) {
3146 - wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
3147 - }
3148 -
3149 - $course = get_post( $course_id );
3150 -
3151 - if ( CourseModel::STATUS_PRIVATE === $course->post_status && ! current_user_can( 'read_private_tutor_courses' ) ) {
3152 - wp_send_json_error( __( 'You do not have permission to enroll in this course', 'tutor' ) );
3153 - }
3154 -
3155 - /**
3156 - * This check was added to address a security issue where users could
3157 - * enroll in a course via an AJAX call without purchasing it.
3158 - *
3159 - * To prevent this, we now verify whether the course is paid.
3160 - * Additionally, we check if the user is already enrolled, since
3161 - * Tutor's default behavior enrolls users automatically upon purchase.
3162 - *
3163 - * @since 3.9.4
3164 - */
3165 - if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
3166 - $is_enrolled = (bool) EnrollmentModel::is_enrolled( $course_id, $user_id );
3167 -
3168 - if ( ! $is_enrolled ) {
3169 - wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
1281 + $child_post_ids = $this->tutor_get_post_ids(array('tutor_announcements', 'tutor_enrolled'), $post_id);
1282 + if ( !empty($child_post_ids) ) {
1283 + foreach ($child_post_ids as $child_post_id) {
1284 + wp_delete_post($child_post_id, true);
1285 + }
3170 1286 }
3171 1287 }
3172 -
3173 - $enroll = EnrollmentModel::do_enroll( $course_id, 0, $user_id );
3174 - if ( $enroll ) {
3175 - wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
3176 - } else {
3177 - wp_send_json_error( __( 'Enrollment failed, please try again!', 'tutor' ) );
3178 - }
3179 1288 }
3180 1289
3181 1290 /**
3182 - * After trash a course direct to the course list page
3183 - *
3184 - * @since 2.1.7
3185 - *
3186 - * @param integer $post_id int course id.
3187 - *
3188 - * @return void
1291 + * tutor course setting metabox
1292 + * @since v.1.7.0
3189 1293 */
3190 - public static function redirect_to_course_list_page( int $post_id ): void {
3191 - $post = get_post( $post_id );
3192 - if ( tutor()->course_post_type === $post->post_type ) {
3193 - $is_gutenberg_enabled = tutor_utils()->get_option( 'enable_gutenberg_course_edit' );
3194 - if ( ! $is_gutenberg_enabled ) {
3195 - wp_safe_redirect( admin_url( 'admin.php?page=tutor' ) );
3196 - exit;
3197 - }
3198 - }
3199 - }
1294 + function tutor_course_setting_metabox( $post ) {
1295 +
1296 + $disable_qa = $this->additional_meta[0];
1297 + $is_public = $this->additional_meta[1];
3200 1298
3201 - /**
3202 - * Create or update WooCommerce product
3203 - *
3204 - * If product id not set it will create new one.
3205 - *
3206 - * @since 2.2.0
3207 - *
3208 - * @param string $title product title.
3209 - * @param string $reg_price product price.
3210 - * @param string $sale_price product sale price.
3211 - * @param int $product_id product ID.
3212 - * @param string $status product status.
3213 - *
3214 - * @return integer Product id or return 0 if WC not exists
3215 - */
3216 - public static function create_wc_product( $title, $reg_price, $sale_price, $product_id = 0, $status = 'publish' ) {
3217 - if ( ! tutor_utils()->has_wc() ) {
3218 - return 0;
3219 - }
1299 + $disable_qa_checked = get_post_meta($post->ID, $disable_qa, true)=='yes' ? 'checked="checked"' : '';
1300 + $is_public_checked = get_post_meta($post->ID, $is_public, true)=='yes' ? 'checked="checked"' : '';
3220 1301
3221 - $product_obj = new \WC_Product();
3222 - if ( $product_id ) {
3223 - $product_obj = wc_get_product( $product_id );
3224 - }
3225 -
3226 - $product_obj->set_name( $title );
3227 - $product_obj->set_status( $status );
3228 - $product_obj->set_price( $reg_price );
3229 - $product_obj->set_regular_price( $reg_price );
3230 -
3231 - if ( $sale_price > 0 ) {
3232 - $product_obj->set_sale_price( $sale_price );
3233 - } else {
3234 - $product_obj->set_sale_price( null );
3235 - }
3236 -
3237 - $product_obj->set_sold_individually( true );
3238 -
3239 - return $product_obj->save();
1302 + do_action('tutor_before_course_sidebar_settings_metabox', $post);
1303 + ?>
1304 + <div class="tutor-course-sidebar-settings-item" id="_tutor_is_course_public_meta_checkbox" style="display:none">
1305 + <label for="<?php echo $is_public; ?>">
1306 + <input id="<?php echo $is_public; ?>" type="checkbox" name="<?php echo $is_public; ?>" value="yes" <?php echo $is_public_checked; ?> />
1307 + <?php _e('Make This Course Public', 'tutor'); ?>
1308 + <small style="display:block;padding-left:24px">
1309 + <?php _e('No enrollment required.', 'tutor'); ?>
1310 + </small>
1311 + </label>
1312 + </div>
1313 + <div class="tutor-course-sidebar-settings-item">
1314 + <label for="<?php echo $disable_qa; ?>">
1315 + <input type="hidden" name="_tutor_course_additional_data_edit" value="true" />
1316 + <input id="<?php echo $disable_qa; ?>" type="checkbox" name="<?php echo $disable_qa; ?>" value="yes" <?php echo $disable_qa_checked; ?> />
1317 + <?php _e('Disable Q&A', 'tutor'); ?>
1318 + </label>
1319 + </div>
1320 + <?php
1321 + do_action('tutor_after_course_sidebar_settings_metabox', $post);
3240 1322 }
3241 1323
3242 - /**
3243 - * Get course/bundle mini info
3244 - *
3245 - * @since 3.0.0
3246 - *
3247 - * @param object $post Course or bundle post.
3248 - *
3249 - * @return array
3250 - */
3251 - public static function get_mini_info( object $post ) {
3252 - $is_purchasable = tutor_utils()->is_course_purchasable( $post->ID );
3253 - $course_price = tutor_utils()->get_raw_course_price( $post->ID );
3254 - $regular_price = tutor_get_formatted_price( $course_price->regular_price );
3255 - $sale_price = ! empty( $course_price->sale_price ) ? tutor_get_formatted_price( $course_price->sale_price ) : null;
3256 -
3257 - $info = array(
3258 - 'id' => $post->ID,
3259 - 'title' => $post->post_title,
3260 - 'image' => get_tutor_course_thumbnail_src( 'post-thumbnail', $post->ID ),
3261 - 'is_purchasable' => $is_purchasable,
3262 - 'regular_price' => $regular_price,
3263 - 'sale_price' => $sale_price,
3264 - );
3265 -
3266 - $card_data = apply_filters( 'tutor_course_mini_info', $info, $post );
3267 -
3268 - return $card_data;
1324 + function tutor_course_setting_metabox_frontend( $post ){
1325 + ?>
1326 + <div class="tutor-course-builder-section tutor-course-builder-info">
1327 + <div class="tutor-course-builder-section-title">
1328 + <h3><i class="tutor-icon-down"></i><span><?php esc_html_e('Tutor Settings', 'tutor'); ?></span></h3>
1329 + </div>
1330 + <div class="tutor-course-builder-section-content">
1331 + <div class="tutor-frontend-builder-item-scope">
1332 + <div class="tutor-form-group">
1333 + <?php $this->tutor_course_setting_metabox($post); ?>
1334 + </div>
1335 + </div>
1336 + </div>
1337 + </div>
1338 + <?php
3269 1339 }
3270 1340
3271 1341 /**
3272 - * Get course/bundle card data
3273 - *
3274 - * This method will return all data that contain in
3275 - * course card
3276 - *
3277 - * @since 3.0.0
3278 - *
3279 - * @param object $post Course or bundle post.
3280 - *
3281 - * @return array
1342 + * Delete associated enrollment
1343 + * @since v.1.8.2
3282 1344 */
3283 - public static function get_card_data( object $post ) {
3284 - $info = self::get_mini_info( $post );
1345 + public function delete_associated_enrollment($post_id) {
1346 + global $wpdb;
3285 1347
3286 - $info['last_updated'] = tutor_i18n_get_formated_date( $post->post_modified_at );
3287 - $info['course_duration'] = tutor_utils()->get_course_duration( $post->ID, false );
3288 - $info['total_enrolled'] = tutor_utils()->count_enrolled_users_by_course( $post->ID );
1348 + $enroll_id = $wpdb->get_var( $wpdb->prepare(
1349 + "SELECT
1350 + post_id
1351 + FROM
1352 + {$wpdb->postmeta}
1353 + WHERE
1354 + meta_key='_tutor_enrolled_by_order_id'
1355 + AND meta_value = %d
1356 + ",
1357 + $post_id
1358 + ) );
1359 +
1360 + if(is_numeric($enroll_id) && $enroll_id>0) {
3289 1361
3290 - $card_data = apply_filters( 'tutor_course_card_data', $info, $post );
1362 + $course_id = get_post_field('post_parent', $enroll_id);
1363 + $user_id = get_post_field('post_author', $enroll_id);
3291 1364
3292 - return $card_data;
3293 - }
1365 + tutils()->cancel_course_enrol($course_id, $user_id);
1366 + }
1367 + }
3294 1368
3295 - /**
3296 - * Filter user list access for instructor
3297 - *
3298 - * @since 3.0.0
3299 - *
3300 - * @param bool $access access.
3301 - *
3302 - * @return bool
3303 - */
3304 - public function user_list_access_for_instructor( $access ) {
3305 - $is_instructor = User::is_instructor();
3306 - return $access || $is_instructor;
3307 - }
1369 + public function tutor_reset_course_progress() {
1370 + tutils()->checking_nonce();
1371 + $course_id = tutor_utils()->array_get('course_id', $_POST);
3308 1372
3309 - /**
3310 - * Filter user list args for instructor
3311 - *
3312 - * @since 3.0.0
3313 - *
3314 - * @param array $args args.
3315 - *
3316 - * @return array
3317 - */
3318 - public function user_list_args_for_instructor( $args ) {
3319 - if ( User::is_instructor() ) {
3320 - if ( isset( $args['fields'] ) && isset( $args['fields']['user_email'] ) ) {
3321 - unset( $args['fields']['user_email'] );
3322 - }
1373 + if(!$course_id || !is_numeric($course_id) || !tutor_utils()->is_enrolled( $course_id )) {
1374 + wp_send_json_error(array('message' => __('Invalid Course ID or Access Denied.', 'tutor')));
1375 + return;
3323 1376 }
3324 1377
3325 - $filter = json_decode( wp_unslash( $_POST['filter'] ?? '{}' ) );//phpcs:ignore
3326 - if ( isset( $filter->role ) && is_array( $filter->role ) ) {
3327 - $args['role__in'] = array_map( 'sanitize_text_field', $filter->role );
3328 - }
3329 -
3330 - return $args;
1378 + tutor_utils()->delete_course_progress($course_id);
1379 + wp_send_json_success(array('redirect_to' => tutor_utils()->get_course_first_lesson( $course_id ) ));
3331 1380 }
3332 1381
3333 1382 /**
3334 - * Get a list of possible course status.
3335 - *
3336 - * @since 3.6.2
3337 - *
3338 - * @return array
1383 + * Do enroll if guest attempt to enroll and course is free
1384 + *
1385 + * @param $course_id
1386 + *
1387 + * @since 1.9.8
3339 1388 */
3340 - public static function course_status_list() {
3341 - return array(
3342 - 'publish',
3343 - 'private',
3344 - 'draft',
3345 - 'trash',
3346 - 'pending',
3347 - 'future',
3348 - );
3349 - }
3350 -
3351 - /**
3352 - * Link a course/bundle post to a WooCommerce product.
3353 - *
3354 - * @since 3.8.2
3355 - *
3356 - * @param int $post_ID The WordPress post ID of the course.
3357 - * @param int $product_id The WooCommerce product ID to associate with the course.
3358 - *
3359 - * @return void
3360 - */
3361 - public static function sync_course_with_wc_product( $post_ID, $product_id ) {
3362 -
3363 - update_post_meta( $post_ID, self::COURSE_PRODUCT_ID_META, $product_id );
3364 -
3365 - // Mark product for woocommerce.
3366 - update_post_meta( $product_id, '_virtual', 'yes' );
3367 - update_post_meta( $product_id, '_tutor_product', 'yes' );
3368 - }
3369 -
3370 - /**
3371 - * Map Tutor's course prices to WooCommerce.
3372 - *
3373 - * @since 3.8.2
3374 - *
3375 - * @param int $post_ID The WordPress post ID of the course.
3376 - * @param string|int|float $regular_price The regular price.
3377 - * @param string|int|float $sale_price The sale price.
3378 - * @return void
3379 - */
3380 - private static function set_course_regular_and_sale_price( $post_ID, $regular_price, $sale_price ) {
3381 -
3382 - // Set course regular & sale price.
3383 - update_post_meta( $post_ID, self::COURSE_PRICE_META, $regular_price );
3384 - update_post_meta( $post_ID, self::COURSE_SALE_PRICE_META, $sale_price );
3385 - }
3386 -
3387 - /**
3388 - * Render start/resume button in enrolled course action btn.
3389 - *
3390 - * @since 4.0.0
3391 - *
3392 - * @param int $course_id course id.
3393 - *
3394 - * @return void
3395 - */
3396 - public function render_course_action_btn( int $course_id ) {
3397 - $is_completed = tutor_utils()->is_completed_course( $course_id );
3398 - if ( $is_completed ) {
3399 - $certificate_addon_active = tutor_utils()->is_addon_enabled( 'tutor-certificate' );
3400 - if ( ! $certificate_addon_active ) {
3401 - Button::make()
3402 - ->tag( 'a' )
3403 - ->label( __( 'Completed', 'tutor' ) )
3404 - ->icon( Icon::COMPLETED_CIRCLE )
3405 - ->variant( Variant::PRIMARY )
3406 - ->size( Size::X_SMALL )
3407 - ->attr( 'href', esc_url( get_the_permalink( $course_id ) ) )
3408 - ->render();
1389 + public function enroll_after_login_if_attempt( $course_id ) {
1390 + $course_id = sanitize_text_field( $course_id );
1391 + if ( $course_id ) {
1392 + $is_purchasable = tutor_utils()->is_course_purchasable($course_id);
1393 + if ( !$is_purchasable ) {
1394 + tutor_utils()->do_enroll($course_id);
1395 + do_action( 'guest_attempt_after_enrollment', $course_id );
3409 1396 }
3410 - return;
3411 1397 }
3412 -
3413 - $course_progress = tutor_utils()->get_course_completed_percent( $course_id, 0, true );
3414 - $button_text = $course_progress['completed_percent'] > 0 ? __( 'Resume', 'tutor' ) : __( 'Start', 'tutor' );
3415 - $button_url = tutor_utils()->get_course_first_lesson( $course_id );
3416 -
3417 - if ( ! $button_url ) {
3418 - $button_url = get_the_permalink( $course_id );
3419 - }
3420 -
3421 - Button::make()
3422 - ->tag( 'a' )
3423 - ->label( $button_text )
3424 - ->icon( Icon::PLAY_2 )
3425 - ->variant( Variant::PRIMARY )
3426 - ->size( Size::X_SMALL )
3427 - ->attr( 'href', esc_url( $button_url ) )
3428 - ->render();
3429 1398 }
3430 -
3431 - /**
3432 - * Get the content for the course completion modal.
3433 - *
3434 - * This method returns HTML for displaying a modal that informs the user
3435 - * that they have not completed all required lessons and assessments. It includes
3436 - * the user's current progress shown as a percentage and a progress bar.
3437 - *
3438 - * @since 4.0.0
3439 - *
3440 - * @param float $course_progress The completion percentage of the course.
3441 - *
3442 - * @return string The rendered HTML content for the modal.
3443 - */
3444 - public static function get_complete_modal_content( float $course_progress = 0 ): string {
3445 - ob_start();
3446 - ?>
3447 - <div>
3448 - <p class="tutor-p3 tutor-text-secondary tutor-text-center tutor-mb-7 tutor-px-11">
3449 - <?php esc_html_e( 'You have not completed all required lessons and assessments. ', 'tutor' ); ?>
3450 - </p>
3451 - <div class="tutor-finish-course-progress tutor-border tutor-p-5 tutor-flex tutor-flex-column tutor-gap-4 tutor-surface-base tutor-rounded-md">
3452 - <div class="tutor-flex tutor-items-center tutor-justify-between">
3453 - <div class="tutor-p3 tutor-text-secondary">
3454 - <?php esc_html_e( 'Your Progress', 'tutor' ); ?>
3455 - </div>
3456 - <div class="tutor-p1 tutor-font-bold">
3457 - <?php echo esc_html( number_format_i18n( $course_progress ) . '%' ); ?>
3458 - </div>
3459 - </div>
3460 - <?php
3461 - Progress::make()->type( 'bar' )->value( $course_progress )->render();
3462 - ?>
3463 - </div>
3464 - </div>
3465 - <?php
3466 - return ob_get_clean();
3467 - }
3468 -
3469 - /**
3470 - * Render the course complete button.
3471 - *
3472 - * Displays a button that allows the user to complete the course. If the course
3473 - * progress is less than 100%, clicking the button shows a modal informing the user
3474 - * that not all requirements are fulfilled. If the course progress is 100%,
3475 - * clicking the button attempts to complete the course.
3476 - *
3477 - * @since 4.0.0
3478 - *
3479 - * @param string $modal_id The HTML ID of the modal to display if not complete.
3480 - * @param int $course_id The ID of the course.
3481 - * @param float $course_progress The current completion percentage of the course.
3482 - * @param string $size The button size.
3483 - * @param string $tooltip Optional. Tooltip message.
3484 - * @param bool $block Optional. Whether the button is full-width.
3485 - *
3486 - * @return void
3487 - */
3488 - public static function render_course_complete_btn( string $modal_id, int $course_id, float $course_progress = 0, string $size = Size::MEDIUM, string $tooltip = '', bool $block = false ): void {
3489 - $button = Button::make()
3490 - ->variant( Variant::SECONDARY )
3491 - ->label( __( 'Complete the Course', 'tutor' ) )
3492 - ->icon( Icon::TICK_MARK )
3493 - ->size( $size )
3494 - ->block( $block )
3495 - ->attr( 'type', 'button' );
3496 -
3497 - if ( ! empty( $tooltip ) ) {
3498 - $button->disabled();
3499 - }
3500 -
3501 - if ( $course_progress < 100 ) {
3502 - $button->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" );
3503 - } else {
3504 - $button->attr( '@click', "handleCourseComplete({$course_id})" );
3505 - $button->attr( ':class', "courseCompleteMutation?.isPending ? 'tutor-btn-loading' : ''" );
3506 - $button->attr( ':disabled', 'courseCompleteMutation?.isPending' );
3507 - }
3508 -
3509 - if ( ! empty( $tooltip ) ) {
3510 - Tooltip::make()
3511 - ->content( $tooltip )
3512 - ->placement( Tooltip::PLACEMENT_BOTTOM )
3513 - ->arrow( Tooltip::ARROW_CENTER )
3514 - ->trigger_element( $button->get() )
3515 - ->render();
3516 - } else {
3517 - $button->render();
3518 - }
3519 - }
3520 -
3521 - /**
3522 - * Render course retake button
3523 - *
3524 - * @since 4.0.0
3525 - *
3526 - * @param string $modal_id Modal id.
3527 - * @param string $size The button size.
3528 - * @param bool $block Optional. Whether the button is full-width.
3529 - *
3530 - * @return void
3531 - */
3532 - public static function render_course_retake_btn( string $modal_id, string $size = Size::MEDIUM, bool $block = false ): void {
3533 - Button::make()
3534 - ->variant( Variant::SECONDARY )
3535 - ->label( __( 'Retake this Course', 'tutor' ) )
3536 - ->icon( Icon::RELOAD_4 )
3537 - ->size( $size )
3538 - ->block( $block )
3539 - ->attr( 'type', 'button' )
3540 - ->attr( '@click', "TutorCore.modal.showModal('{$modal_id}')" )
3541 - ->render();
3542 - }
3543 -}
1399 +}