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