PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | inc/custom-post-types/course.php +598 -507 4.3.94.4.8 View file →
@@ -1,507 +1,598 @@
1 -<?php
2 -/**
3 - * Class LP_Course_Post_Type
4 - *
5 - * @author ThimPress
6 - * @package LearnPress/Classes
7 - * @version 3.0.2
8 - */
9 -
10 -use LearnPress\Models\CourseModel;
11 -use LearnPress\Models\CoursePostModel;
12 -use LearnPress\Models\WPTables\CoursesTable;
13 -
14 -defined( 'ABSPATH' ) || exit();
15 -
16 -if ( ! class_exists( 'LP_Course_Post_Type' ) ) {
17 -
18 - /**
19 - * Class LP_Course_Post_Type
20 - */
21 - final class LP_Course_Post_Type extends LP_Abstract_Post_Type {
22 - /**
23 - * @var null
24 - */
25 - protected static $_instance = null;
26 - protected $_post_type = LP_COURSE_CPT;
27 - protected $_screen_list = 'edit-' . LP_COURSE_CPT;
28 -
29 - /**
30 - * Constructor
31 - */
32 - public function __construct() {
33 - parent::__construct();
34 -
35 - add_action( 'init', array( $this, 'register_taxonomy' ) );
36 - add_filter( 'posts_where_paged', array( $this, '_posts_where_paged_course_items' ), 10 );
37 - add_filter( 'posts_join_paged', array( $this, '_posts_join_paged_course_items' ), 10 );
38 - }
39 -
40 - /**
41 - * Register course post type.
42 - */
43 - public function args_register_post_type(): array {
44 - // Support Quick Edit multiple courses change author
45 - add_post_type_support( LP_COURSE_CPT, 'author' );
46 -
47 - $labels = array(
48 - 'name' => _x( 'Courses', 'Post Type General Name', 'learnpress' ),
49 - 'singular_name' => _x( 'Course', 'Post Type Singular Name', 'learnpress' ),
50 - 'menu_name' => __( 'Courses', 'learnpress' ),
51 - 'parent_item_colon' => __( 'Parent Item:', 'learnpress' ),
52 - 'all_items' => __( 'Courses', 'learnpress' ),
53 - 'view_item' => __( 'View Course', 'learnpress' ),
54 - 'add_new_item' => __( 'Add a New Course', 'learnpress' ),
55 - 'add_new' => __( 'Add New', 'learnpress' ),
56 - 'edit_item' => __( 'Edit Course', 'learnpress' ),
57 - 'update_item' => __( 'Update Course', 'learnpress' ),
58 - 'search_items' => __( 'Search Courses', 'learnpress' ),
59 - 'not_found' => sprintf(
60 - __( 'You have not had any courses yet. Click <a href="%s">Add new</a> to start', 'learnpress' ),
61 - admin_url( 'post-new.php?post_type=lp_course' )
62 - ),
63 - 'not_found_in_trash' => __( 'There was no course found in the trash', 'learnpress' ),
64 - );
65 - $course_base = LP_Settings::get_option( 'course_base', 'courses' );
66 - $course_permalink = empty( $course_base ) ? 'courses' : $course_base;
67 -
68 - // Set to $has_archive return link to courses page, is_archive will check is true
69 - $courses_page_id = learn_press_get_page_id( 'courses' );
70 - $has_archive = $courses_page_id ? urldecode( get_page_uri( $courses_page_id ) ) : 'courses';
71 -
72 - $args = array(
73 - 'labels' => $labels,
74 - 'public' => true,
75 - 'query_var' => true,
76 - 'publicly_queryable' => true,
77 - 'show_ui' => true,
78 - 'has_archive' => $has_archive,
79 - 'capability_type' => $this->_post_type,
80 - 'map_meta_cap' => true,
81 - 'show_in_menu' => 'learn_press',
82 - 'show_in_admin_bar' => true,
83 - 'show_in_nav_menus' => true,
84 - 'show_in_rest' => true,
85 - 'taxonomies' => array( 'course_category', 'course_tag' ),
86 - 'supports' => array( 'title', 'editor', 'thumbnail', 'revisions', 'comments', 'excerpt' ),
87 - 'hierarchical' => false,
88 - 'rewrite' => ! empty( $course_permalink ) ? array(
89 - 'slug' => untrailingslashit( $course_permalink ),
90 - 'with_front' => false,
91 - ) : false,
92 - );
93 -
94 - return $args;
95 - }
96 -
97 - /**
98 - * Register course taxonomy.
99 - */
100 - public function register_taxonomy() {
101 - $category_base = LP_Settings::get_option( 'course_category_base' );
102 - register_taxonomy(
103 - 'course_category',
104 - array( LP_COURSE_CPT ),
105 - array(
106 - 'label' => __( 'Course Categories', 'learnpress' ),
107 - 'labels' => array(
108 - 'name' => __( 'Course Categories', 'learnpress' ),
109 - 'menu_name' => __( 'Course Category', 'learnpress' ),
110 - 'singular_name' => __( 'Category', 'learnpress' ),
111 - 'add_new_item' => __( 'Add A New Course Category', 'learnpress' ),
112 - 'all_items' => __( 'All Categories', 'learnpress' ),
113 - ),
114 - 'query_var' => true,
115 - 'public' => true,
116 - 'hierarchical' => true,
117 - 'show_ui' => true,
118 - 'show_in_menu' => 'learn_press',
119 - 'show_admin_column' => true,
120 - 'show_in_admin_bar' => true,
121 - 'show_in_nav_menus' => true,
122 - 'show_in_rest' => true,
123 - 'rewrite' => array(
124 - 'slug' => empty( $category_base ) ? 'course-category' : $category_base,
125 - 'hierarchical' => true,
126 - 'with_front' => false,
127 - ),
128 - )
129 - );
130 -
131 - $tag_base = LP_Settings::get_option( 'course_tag_base' );
132 - register_taxonomy(
133 - 'course_tag',
134 - array( LP_COURSE_CPT ),
135 - array(
136 - 'labels' => array(
137 - 'name' => __( 'Course Tags', 'learnpress' ),
138 - 'singular_name' => __( 'Tag', 'learnpress' ),
139 - 'search_items' => __( 'Search Course Tags', 'learnpress' ),
140 - 'popular_items' => __( 'Popular Course Tags', 'learnpress' ),
141 - 'all_items' => __( 'All Course Tags', 'learnpress' ),
142 - 'parent_item' => null,
143 - 'parent_item_colon' => null,
144 - 'edit_item' => __( 'Edit Course Tag', 'learnpress' ),
145 - 'update_item' => __( 'Update Course Tag', 'learnpress' ),
146 - 'add_new_item' => __( 'Add A New Course Tag', 'learnpress' ),
147 - 'new_item_name' => __( 'New Course Tag Name', 'learnpress' ),
148 - 'separate_items_with_commas' => __( 'Separate tags with commas', 'learnpress' ),
149 - 'add_or_remove_items' => __( 'Add or remove tags', 'learnpress' ),
150 - 'choose_from_most_used' => __( 'Choose from the most used tags', 'learnpress' ),
151 - 'menu_name' => __( 'Course Tags', 'learnpress' ),
152 - ),
153 - 'public' => true,
154 - 'hierarchical' => false,
155 - 'show_ui' => true,
156 - 'show_in_menu' => 'learn_press',
157 - 'update_count_callback' => '_update_post_term_count',
158 - 'query_var' => true,
159 - 'show_in_rest' => true,
160 - 'rewrite' => array(
161 - 'slug' => empty( $tag_base ) ? 'course-tag' : $tag_base,
162 - 'with_front' => false,
163 - ),
164 - )
165 - );
166 - }
167 -
168 - /**
169 - * Declare class name of table list courses.
170 - *
171 - * @param string $class_name
172 - * @param array $args
173 - *
174 - * @return string
175 - * @since 4.2.9.5
176 - */
177 - public function wp_list_table_class_name( $class_name, $args ) {
178 - if ( $this->check_class_name_handle_table( $args['screen'] ) ) {
179 - $class_name = CoursesTable::class;
180 - }
181 -
182 - return $class_name;
183 - }
184 -
185 - /**
186 - * Delete course sections before delete course.
187 - *
188 - * @param int $post_id
189 - *
190 - * @throws Exception
191 - * @since modify 4.0.9
192 - * @since 3.0.0
193 - * @editor tungnx
194 - */
195 - public function before_delete( int $post_id ) {
196 - // Delete course from table learnpress_courses
197 - $courseModel = CourseModel::find( $post_id, true );
198 - if ( $courseModel ) {
199 - $courseModel->delete();
200 - }
201 -
202 - $course = learn_press_get_course( $post_id );
203 - if ( ! $course ) {
204 - return;
205 - }
206 - $course->delete_relate_data_when_delete_course();
207 - }
208 -
209 - /**
210 - * @param string $fields
211 - *
212 - * @return string
213 - */
214 - public function posts_fields( $fields ): string {
215 - if ( ! $this->is_page_list_posts_on_backend() ) {
216 - return $fields;
217 - }
218 -
219 - $fields = ' DISTINCT ' . $fields;
220 - if ( $this->get_order_by() == 'price' ) {
221 - $fields .= ', pm_price.meta_value as course_price';
222 - }
223 -
224 - return $fields;
225 - }
226 -
227 - public function _posts_join_paged_course_items( $join ) {
228 - global $wpdb;
229 -
230 - $course_id = $this->_filter_items_by_course();
231 - if ( $course_id || LP_Request::get_param( 'orderby' ) === 'course-name' ) {
232 - $join .= " LEFT JOIN {$wpdb->prefix}learnpress_section_items si ON {$wpdb->posts}.ID = si.item_id";
233 - $join .= " LEFT JOIN {$wpdb->prefix}learnpress_sections s ON s.section_id = si.section_id";
234 - $join .= " LEFT JOIN {$wpdb->posts} c ON c.ID = s.section_course_id";
235 - }
236 -
237 - return $join;
238 - }
239 -
240 - public function _posts_where_paged_course_items( $where ) {
241 - global $wpdb;
242 -
243 - $course_id = $this->_filter_items_by_course();
244 - if ( $course_id ) {
245 - $where .= $wpdb->prepare( ' AND (c.ID = %d)', $course_id );
246 - $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_status = %s", 'publish' );
247 - }
248 -
249 - return $where;
250 - }
251 -
252 - /**
253 - * @param $join
254 - *
255 - * @return string
256 - */
257 - public function posts_join_paged( $join ) {
258 - global $wpdb;
259 -
260 - if ( ! $this->is_page_list_posts_on_backend() ) {
261 - return $join;
262 - }
263 -
264 - if ( ! isset( $_GET['orderby'] ) || $_GET['orderby'] !== 'price' ) {
265 - return $join;
266 - }
267 -
268 - $join .= " LEFT JOIN {$wpdb->postmeta} pm_price ON pm_price.post_id = {$wpdb->posts}.ID AND pm_price.meta_key = '_lp_price'";
269 -
270 - return $join;
271 - }
272 -
273 - /**
274 - * @param $where
275 - *
276 - * @return mixed|string
277 - */
278 - public function posts_where_paged( $where ) {
279 - global $wpdb;
280 -
281 - if ( ! $this->is_page_list_posts_on_backend() ) {
282 - return $where;
283 - }
284 -
285 - $filter_price = LP_Helper::sanitize_params_submitted( $_REQUEST['filter_price'] ?? 0 );
286 -
287 - if ( array_key_exists( 'filter_price', $_REQUEST ) ) {
288 - if ( $filter_price == 0 ) {
289 - $where .= ' AND ( pm_price.meta_value IS NULL || pm_price.meta_value = 0 )';
290 - } else {
291 - $where .= $wpdb->prepare( ' AND ( pm_price.meta_value = %s )', $filter_price );
292 - }
293 - }
294 -
295 - /*$not_in = $wpdb->prepare(
296 - "
297 - SELECT ID
298 - FROM {$wpdb->posts} p
299 - INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s
300 - WHERE pm.meta_value = %s
301 - ",
302 - '_lp_preview_course',
303 - 'yes'
304 - );
305 -
306 - $where .= " AND {$wpdb->posts}.ID NOT IN( {$not_in} )";*/
307 -
308 - return $where;
309 - }
310 -
311 - /**
312 - * @param $orderby
313 - *
314 - * @return string
315 - */
316 - public function posts_orderby( $orderby ) {
317 - if ( ! $this->is_page_list_posts_on_backend() ) {
318 - return $orderby;
319 - }
320 -
321 - $order = $this->get_order_sort();
322 - switch ( $this->get_order_by() ) {
323 - case 'price':
324 - $orderby = "CAST(pm_price.meta_value AS UNSIGNED) {$order}";
325 - }
326 -
327 - return $orderby;
328 - }
329 -
330 - /**
331 - * Save course post
332 - *
333 - * @param int $post_id
334 - * @param WP_Post|null $post
335 - * @param bool $is_update
336 - *
337 - * @since 4.2.6.9
338 - * @version 1.0.2
339 - */
340 - public function save_post( int $post_id, ?WP_Post $post = null, bool $is_update = false ) {
341 - try {
342 - $wp_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
343 - // Save to table learnpress_courses
344 - LP_Install::instance()->create_table_courses();
345 - if ( empty( $post ) ) {
346 - $post = get_post( $post_id );
347 - }
348 -
349 - /*if ( $post->post_status === 'auto-draft' ) {
350 - return;
351 - }*/
352 -
353 - $courseModel = CourseModel::find( $post_id, true );
354 - if ( ! $courseModel ) {
355 - $courseModel = new CourseModel( $post );
356 - }
357 -
358 - // Merge object post and courseModel
359 - $new_obj = (array) $post;
360 - $old_obj = (array) $courseModel;
361 - $old_now = array_merge( $old_obj, $new_obj );
362 - $courseModel = new CourseModel( $old_now );
363 -
364 - // Get all metadata of course
365 - if ( $is_update && empty( $wp_screen ) ) {
366 - $coursePost = new CoursePostModel( $courseModel );
367 - $coursePost->get_all_metadata();
368 - // Temporary unset _elementor_page_assets of El, reason by method get_all_metadata get not use maybe_unserialize, make save invalid serialize
369 - $coursePost->meta_data->_elementor_page_assets = [];
370 -
371 - $courseModel->meta_data = $coursePost->meta_data;
372 - }
373 -
374 - // Save option single course
375 - include_once LP_PLUGIN_PATH . 'inc/admin/class-lp-admin.php';
376 - $lp_meta_box_course = new LP_Meta_Box_Course();
377 - $ground_fields = $lp_meta_box_course->metabox( $courseModel->ID );
378 - // Save meta fields
379 - foreach ( $ground_fields as $fields ) {
380 - if ( ! isset( $fields['content'] ) ) {
381 - continue;
382 - }
383 - foreach ( $fields['content'] as $meta_key => $option ) {
384 - $option->id = $meta_key;
385 - if ( ! $option instanceof LP_Meta_Box_Field ) {
386 - continue;
387 - }
388 -
389 - if ( isset( $_POST[ $meta_key ] ) && ! empty( $wp_screen ) && LP_COURSE_CPT === $wp_screen->id ) {
390 - $value_saved = $option->save( $courseModel->ID );
391 - if ( ! empty( $value_saved ) ) {
392 - $courseModel->meta_data->{$meta_key} = $value_saved;
393 - } else {
394 - $courseModel->meta_data->{$meta_key} = maybe_unserialize(
395 - get_post_meta( $courseModel->ID, $meta_key, true )
396 - );
397 - }
398 - } elseif ( ! $is_update ) {
399 - $courseModel->meta_data->{$meta_key} = $option->default ?? '';
400 - } elseif ( ! empty( $wp_screen ) && LP_COURSE_CPT === $wp_screen->id ) {
401 - $value_saved = $option->save( $courseModel->ID );
402 - $courseModel->meta_data->{$meta_key} = $value_saved;
403 - }
404 - }
405 - }
406 -
407 - // For case bulk edit multiple courses change author
408 - $bulk_edit = LP_Request::get_param( 'bulk_edit', false );
409 - if ( ! empty( $bulk_edit ) ) {
410 - $post_author = LP_Request::get_param( 'post_author', 0, 'int' );
411 - if ( $post_author != -1 ) { // -1 is for no change author
412 - $courseModel->post_author = $post_author;
413 - }
414 - } elseif ( ! empty( $_REQUEST['_post_author'] ) ) {
415 - // Not use key 'post_author' on single edit course, it special key of WP, Gutenberg save will not submit this key.
416 - $courseModel->post_author = LP_Request::get_param( '_post_author', 0, 'int' );
417 - /**
418 - * Save author to post table
419 - * Not use PostModel::save() or CoursePostModel::save() here
420 - * because it calls wp_update_post(), has hook 'save_post', it will cause infinite loop
421 - */
422 - $lp_db = LP_Database::getInstance();
423 - $filter_update = new LP_Post_Type_Filter();
424 - $filter_update->collection = $lp_db->tb_posts;
425 - $filter_update->set[] = "post_author = {$courseModel->post_author}";
426 - $filter_update->where[] = $lp_db->wpdb->prepare( 'AND ID = %d', $courseModel->ID );
427 - $lp_db->update_execute( $filter_update );
428 - clean_post_cache( $post->ID );
429 - } elseif ( isset( $_POST['post_author'] ) && isset( $_POST['screen'] ) && $_POST['screen'] === 'edit-' . LP_COURSE_CPT ) {
430 - // For case quick edit change author.
431 - $courseModel->post_author = LP_Request::get_param( 'post_author', 0, 'int' );
432 - }
433 -
434 - $this->save_price( $courseModel );
435 - $courseModel->save();
436 - // End save to table learnpress_courses
437 -
438 - // Save extra info course
439 - // Save in background.
440 - $bg = LP_Background_Single_Course::instance();
441 - $bg->data(
442 - array(
443 - 'handle_name' => 'save_post',
444 - 'course_id' => $post_id,
445 - 'data' => $_POST ?? [],
446 - )
447 - )->dispatch();
448 - } catch ( Throwable $e ) {
449 - LP_Debug::error_log( $e );
450 - }
451 - }
452 -
453 - /**
454 - * Save price course
455 - *
456 - * @return void
457 - * @throws Exception
458 - */
459 - protected function save_price( CourseModel &$courseObj ) {
460 - $coursePost = new CoursePostModel( $courseObj );
461 -
462 - $regular_price = $courseObj->get_regular_price();
463 - $sale_price = $courseObj->get_sale_price();
464 - if ( (float) $regular_price < 0 ) {
465 - $courseObj->meta_data->{CoursePostModel::META_KEY_REGULAR_PRICE} = '';
466 - $regular_price = $courseObj->get_regular_price();
467 - }
468 -
469 - if ( $sale_price !== '' && (float) $sale_price > (float) $regular_price ) {
470 - $courseObj->meta_data->{CoursePostModel::META_KEY_SALE_PRICE} = '';
471 - $sale_price = $courseObj->get_sale_price();
472 - }
473 -
474 - // Save sale regular price and sale price to table postmeta
475 - $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_REGULAR_PRICE, $regular_price );
476 - $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_SALE_PRICE, $sale_price );
477 -
478 - $has_sale = $courseObj->has_sale_price();
479 - if ( $has_sale ) {
480 - $courseObj->is_sale = 1;
481 - $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_IS_SALE, 1 );
482 - } else {
483 - $courseObj->is_sale = 0;
484 - delete_post_meta( $courseObj->get_id(), CoursePostModel::META_KEY_IS_SALE );
485 - }
486 -
487 - // Set price to sort on lists.
488 - $courseObj->price_to_sort = $courseObj->get_price();
489 - $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_PRICE, $courseObj->price_to_sort );
490 - }
491 -
492 - /**
493 - * Instance LP_Course_Post_Type.
494 - *
495 - * @return LP_Course_Post_Type|null
496 - */
497 - public static function instance() {
498 - if ( ! self::$_instance ) {
499 - self::$_instance = new self();
500 - }
501 -
502 - return self::$_instance;
503 - }
504 - }
505 -
506 - $course_post_type = LP_Course_Post_Type::instance();
507 -}
1 +<?php
2 +/**
3 + * Class LP_Course_Post_Type
4 + *
5 + * @author ThimPress
6 + * @package LearnPress/Classes
7 + * @version 3.0.3
8 + */
9 +
10 +use LearnPress\Databases\PostDB;
11 +use LearnPress\Filters\CoursePostFilter;
12 +use LearnPress\Models\CourseModel;
13 +use LearnPress\Models\CoursePostModel;
14 +use LearnPress\Models\PostModel;
15 +use LearnPress\Models\WPTables\CoursesTable;
16 +
17 +defined( 'ABSPATH' ) || exit();
18 +
19 +if ( ! class_exists( 'LP_Course_Post_Type' ) ) {
20 +
21 + /**
22 + * Class LP_Course_Post_Type
23 + */
24 + final class LP_Course_Post_Type extends LP_Abstract_Post_Type {
25 + /**
26 + * @var null
27 + */
28 + protected static $_instance = null;
29 + protected $_post_type = LP_COURSE_CPT;
30 + protected $_screen_list = 'edit-' . LP_COURSE_CPT;
31 +
32 + /**
33 + * Constructor
34 + */
35 + public function __construct() {
36 + parent::__construct();
37 +
38 + add_action( 'init', array( $this, 'register_taxonomy' ) );
39 + add_action( 'posts_pre_query', array( $this, 'posts_pre_query' ), 999, 2 );
40 + // add_filter( 'posts_where_paged', array( $this, '_posts_where_paged_course_items' ), 10 );
41 + // add_filter( 'posts_join_paged', array( $this, '_posts_join_paged_course_items' ), 10 );
42 + }
43 +
44 + /**
45 + * Register course post type.
46 + */
47 + public function args_register_post_type(): array {
48 + // Support Quick Edit multiple courses change author
49 + add_post_type_support( LP_COURSE_CPT, 'author' );
50 +
51 + $labels = array(
52 + 'name' => _x( 'Courses', 'Post Type General Name', 'learnpress' ),
53 + 'singular_name' => _x( 'Course', 'Post Type Singular Name', 'learnpress' ),
54 + 'menu_name' => __( 'Courses', 'learnpress' ),
55 + 'parent_item_colon' => __( 'Parent Item:', 'learnpress' ),
56 + 'all_items' => __( 'Courses', 'learnpress' ),
57 + 'view_item' => __( 'View Course', 'learnpress' ),
58 + 'add_new_item' => __( 'Add a New Course', 'learnpress' ),
59 + 'add_new' => __( 'Add New', 'learnpress' ),
60 + 'edit_item' => __( 'Edit Course', 'learnpress' ),
61 + 'update_item' => __( 'Update Course', 'learnpress' ),
62 + 'search_items' => __( 'Search Courses', 'learnpress' ),
63 + 'not_found' => sprintf(
64 + __( 'You have not had any courses yet. Click <a href="%s">Add new</a> to start', 'learnpress' ),
65 + admin_url( 'post-new.php?post_type=lp_course' )
66 + ),
67 + 'not_found_in_trash' => __( 'There was no course found in the trash', 'learnpress' ),
68 + );
69 + $course_base = LP_Settings::get_option( 'course_base', 'courses' );
70 + $course_permalink = empty( $course_base ) ? 'courses' : $course_base;
71 +
72 + // Set to $has_archive return link to courses page, is_archive will check is true
73 + $courses_page_id = learn_press_get_page_id( 'courses' );
74 + $has_archive = $courses_page_id ? urldecode( get_page_uri( $courses_page_id ) ) : 'courses';
75 +
76 + $args = array(
77 + 'labels' => $labels,
78 + 'public' => true,
79 + 'query_var' => true,
80 + 'publicly_queryable' => true,
81 + 'show_ui' => true,
82 + 'has_archive' => $has_archive,
83 + 'capability_type' => $this->_post_type,
84 + 'map_meta_cap' => true,
85 + 'show_in_menu' => 'learn_press',
86 + 'show_in_admin_bar' => true,
87 + 'show_in_nav_menus' => true,
88 + 'show_in_rest' => true,
89 + 'taxonomies' => array( 'course_category', 'course_tag' ),
90 + 'supports' => array( 'title', 'editor', 'thumbnail', 'revisions', 'comments', 'excerpt' ),
91 + 'hierarchical' => false,
92 + 'rewrite' => ! empty( $course_permalink ) ? array(
93 + 'slug' => untrailingslashit( $course_permalink ),
94 + 'with_front' => false,
95 + ) : false,
96 + );
97 +
98 + return $args;
99 + }
100 +
101 + /**
102 + * Register course taxonomy.
103 + */
104 + public function register_taxonomy() {
105 + $category_base = LP_Settings::get_option( 'course_category_base' );
106 + register_taxonomy(
107 + 'course_category',
108 + array( LP_COURSE_CPT ),
109 + array(
110 + 'label' => __( 'Course Categories', 'learnpress' ),
111 + 'labels' => array(
112 + 'name' => __( 'Course Categories', 'learnpress' ),
113 + 'menu_name' => __( 'Course Category', 'learnpress' ),
114 + 'singular_name' => __( 'Category', 'learnpress' ),
115 + 'add_new_item' => __( 'Add A New Course Category', 'learnpress' ),
116 + 'all_items' => __( 'All Categories', 'learnpress' ),
117 + ),
118 + 'query_var' => true,
119 + 'public' => true,
120 + 'hierarchical' => true,
121 + 'show_ui' => true,
122 + 'show_in_menu' => 'learn_press',
123 + 'show_admin_column' => true,
124 + 'show_in_admin_bar' => true,
125 + 'show_in_nav_menus' => true,
126 + 'show_in_rest' => true,
127 + 'rewrite' => array(
128 + 'slug' => empty( $category_base ) ? 'course-category' : $category_base,
129 + 'hierarchical' => true,
130 + 'with_front' => false,
131 + ),
132 + )
133 + );
134 +
135 + $tag_base = LP_Settings::get_option( 'course_tag_base' );
136 + register_taxonomy(
137 + 'course_tag',
138 + array( LP_COURSE_CPT ),
139 + array(
140 + 'labels' => array(
141 + 'name' => __( 'Course Tags', 'learnpress' ),
142 + 'singular_name' => __( 'Tag', 'learnpress' ),
143 + 'search_items' => __( 'Search Course Tags', 'learnpress' ),
144 + 'popular_items' => __( 'Popular Course Tags', 'learnpress' ),
145 + 'all_items' => __( 'All Course Tags', 'learnpress' ),
146 + 'parent_item' => null,
147 + 'parent_item_colon' => null,
148 + 'edit_item' => __( 'Edit Course Tag', 'learnpress' ),
149 + 'update_item' => __( 'Update Course Tag', 'learnpress' ),
150 + 'add_new_item' => __( 'Add A New Course Tag', 'learnpress' ),
151 + 'new_item_name' => __( 'New Course Tag Name', 'learnpress' ),
152 + 'separate_items_with_commas' => __( 'Separate tags with commas', 'learnpress' ),
153 + 'add_or_remove_items' => __( 'Add or remove tags', 'learnpress' ),
154 + 'choose_from_most_used' => __( 'Choose from the most used tags', 'learnpress' ),
155 + 'menu_name' => __( 'Course Tags', 'learnpress' ),
156 + ),
157 + 'public' => true,
158 + 'hierarchical' => false,
159 + 'show_ui' => true,
160 + 'show_in_menu' => 'learn_press',
161 + 'update_count_callback' => '_update_post_term_count',
162 + 'query_var' => true,
163 + 'show_in_rest' => true,
164 + 'rewrite' => array(
165 + 'slug' => empty( $tag_base ) ? 'course-tag' : $tag_base,
166 + 'with_front' => false,
167 + ),
168 + )
169 + );
170 + }
171 +
172 + /**
173 + * Declare class name of table list courses.
174 + *
175 + * @param string $class_name
176 + * @param array $args
177 + *
178 + * @return string
179 + * @since 4.2.9.5
180 + */
181 + public function wp_list_table_class_name( $class_name, $args ) {
182 + if ( $this->check_class_name_handle_table( $args['screen'] ) ) {
183 + $class_name = CoursesTable::class;
184 + }
185 +
186 + return $class_name;
187 + }
188 +
189 + /**
190 + * Delete course sections before delete course.
191 + *
192 + * @param int $post_id
193 + *
194 + * @throws Exception
195 + * @since modify 4.0.9
196 + * @since 3.0.0
197 + * @editor tungnx
198 + */
199 + public function before_delete( int $post_id ) {
200 + // Delete course from table learnpress_courses
201 + $courseModel = CourseModel::find( $post_id, true );
202 + if ( $courseModel ) {
203 + $courseModel->delete();
204 + }
205 +
206 + $course = learn_press_get_course( $post_id );
207 + if ( ! $course ) {
208 + return;
209 + }
210 + $course->delete_relate_data_when_delete_course();
211 + }
212 +
213 + /**
214 + * Query lp courses in admin via Post DB
215 + *
216 + * @param array $posts
217 + * @param WP_Query $wp_query
218 + *
219 + * @return array|WP_Query
220 + * @since 4.4.8
221 + * @version 1.0.0
222 + */
223 + public function posts_pre_query( $posts, $wp_query ) {
224 + try {
225 + if ( ! is_admin() ) {
226 + return $posts;
227 + }
228 +
229 + // Check screen
230 + $curren_screen = get_current_screen();
231 + if ( ! $curren_screen || $curren_screen->id !== 'edit-' . LP_COURSE_CPT ) {
232 + return $posts;
233 + }
234 +
235 + $post_type = $wp_query->get( 'post_type' );
236 +
237 + if ( empty( $post_type ) || $post_type !== LP_COURSE_CPT ) {
238 + return $posts;
239 + }
240 +
241 + // Convert params from WP_Query to CoursePostFilter
242 + $posts_per_page = get_user_option( "edit_{$post_type}_per_page", get_current_user_id() );
243 + if ( empty( $posts_per_page ) ) {
244 + $posts_per_page = 20;
245 + }
246 +
247 + $paged = max( 1, get_query_var( 'paged' ) );
248 + $author = $wp_query->get( 'author' );
249 + $status = $wp_query->get( 'post_status' );
250 + $search = $wp_query->get( 's' );
251 + $month = $wp_query->get( 'm' );
252 + $orderby = LP_Request::get_param( 'orderby', '', 'key' );
253 + $order = LP_Request::get_param( 'order', '', 'key' );
254 + $filter_price = LP_Helper::sanitize_params_submitted( $_REQUEST['filter_price'] ?? '' );
255 +
256 + $filter = new CoursePostFilter();
257 + $filter->page = $paged;
258 + $filter->limit = $posts_per_page;
259 + $post_db = PostDB::getInstance();
260 +
261 + if ( ! empty( $status ) ) {
262 + $filter->post_status = array( $status );
263 + }
264 +
265 + if ( ! empty( $author ) ) {
266 + $filter->post_author = absint( $author );
267 + }
268 +
269 + if ( ! empty( $search ) ) {
270 + $filter->post_title = sanitize_text_field( wp_unslash( $search ) );
271 + }
272 +
273 + if ( ! empty( $month ) && is_numeric( $month ) && strlen( (string) $month ) === 6 ) {
274 + $filter->where[] = $post_db->wpdb->prepare(
275 + 'AND YEAR(p.post_date) = %d AND MONTH(p.post_date) = %d',
276 + substr( $month, 0, 4 ),
277 + substr( $month, 4, 2 )
278 + );
279 + }
280 +
281 + /*$has_price_filter = false;
282 + if ( array_key_exists( 'filter_price', $_REQUEST ) && $filter_price !== '' ) {
283 + $filter_price = floatval( $filter_price );
284 + $has_price_filter = true;
285 + }
286 +
287 + if ( $orderby === 'price' || $has_price_filter ) {
288 + $filter->join[] = "LEFT JOIN {$post_db->wpdb->postmeta} pm_price ON pm_price.post_id = p.ID AND pm_price.meta_key = '_lp_price'";
289 + }
290 +
291 + if ( $has_price_filter ) {
292 + if ( $filter_price == 0 ) {
293 + $filter->where[] = 'AND ( pm_price.meta_value IS NULL || pm_price.meta_value = 0 )';
294 + } else {
295 + $filter->where[] = $post_db->wpdb->prepare( ' AND ( pm_price.meta_value = %s )', $filter_price );
296 + }
297 + }*/
298 +
299 + // Exclude status auto-draft
300 + $filter->where[] = $post_db->wpdb->prepare( 'AND p.post_status != %s', PostModel::STATUS_AUTO_DRAFT );
301 +
302 + if ( $orderby === 'price' ) {
303 + $filter->join[] = "LEFT JOIN {$post_db->wpdb->postmeta} pm_price
304 + ON pm_price.post_id = p.ID
305 + AND pm_price.meta_key = '_lp_price'";
306 + $filter->order_by = 'CAST(pm_price.meta_value AS UNSIGNED)';
307 + } elseif ( $orderby === 'title' ) {
308 + $filter->order_by = 'p.post_title';
309 + } elseif ( $orderby === 'author' ) {
310 + $filter->order_by = 'p.post_author';
311 + } elseif ( $orderby === 'date' ) {
312 + $filter->order_by = 'p.post_date';
313 + } else {
314 + $filter->order_by = 'p.menu_order';
315 + }
316 +
317 + $filter->order = strtolower( $order ) === 'asc' ? 'ASC' : 'DESC';
318 +
319 + // Get lp courses
320 + $total_rows = 0;
321 + $lp_courses = $post_db->get_posts( $filter, $total_rows );
322 +
323 + $wp_query->post_count = count( $lp_courses );
324 + $wp_query->found_posts = $total_rows;
325 + $wp_query->max_num_pages = (int) ceil( $total_rows / $posts_per_page );
326 + $posts = $lp_courses;
327 + } catch ( Throwable $e ) {
328 + LP_Debug::error_log( $e );
329 + }
330 +
331 + return $posts;
332 + }
333 +
334 + /*public function posts_fields( $fields ): string {
335 + if ( ! $this->is_page_list_posts_on_backend() ) {
336 + return $fields;
337 + }
338 +
339 + $fields = ' DISTINCT ' . $fields;
340 + if ( $this->get_order_by() == 'price' ) {
341 + $fields .= ', pm_price.meta_value as course_price';
342 + }
343 +
344 + return $fields;
345 + }
346 +
347 + public function _posts_join_paged_course_items( $join ) {
348 + global $wpdb;
349 +
350 + $course_id = $this->_filter_items_by_course();
351 + if ( $course_id || LP_Request::get_param( 'orderby' ) === 'course-name' ) {
352 + $join .= " LEFT JOIN {$wpdb->prefix}learnpress_section_items si ON {$wpdb->posts}.ID = si.item_id";
353 + $join .= " LEFT JOIN {$wpdb->prefix}learnpress_sections s ON s.section_id = si.section_id";
354 + $join .= " LEFT JOIN {$wpdb->posts} c ON c.ID = s.section_course_id";
355 + }
356 +
357 + return $join;
358 + }
359 +
360 + public function _posts_where_paged_course_items( $where ) {
361 + global $wpdb;
362 +
363 + $course_id = $this->_filter_items_by_course();
364 + if ( $course_id ) {
365 + $where .= $wpdb->prepare( ' AND (c.ID = %d)', $course_id );
366 + $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_status = %s", 'publish' );
367 + }
368 +
369 + return $where;
370 + }
371 +
372 + public function posts_join_paged( $join ) {
373 + global $wpdb;
374 +
375 + if ( ! $this->is_page_list_posts_on_backend() ) {
376 + return $join;
377 + }
378 +
379 + if ( ! isset( $_GET['orderby'] ) || $_GET['orderby'] !== 'price' ) {
380 + return $join;
381 + }
382 +
383 + $join .= " LEFT JOIN {$wpdb->postmeta} pm_price ON pm_price.post_id = {$wpdb->posts}.ID AND pm_price.meta_key = '_lp_price'";
384 +
385 + return $join;
386 + }
387 +
388 + public function posts_where_paged( $where ) {
389 + global $wpdb;
390 +
391 + if ( ! $this->is_page_list_posts_on_backend() ) {
392 + return $where;
393 + }
394 +
395 + $filter_price = LP_Helper::sanitize_params_submitted( $_REQUEST['filter_price'] ?? 0 );
396 +
397 + if ( array_key_exists( 'filter_price', $_REQUEST ) ) {
398 + if ( $filter_price == 0 ) {
399 + $where .= ' AND ( pm_price.meta_value IS NULL || pm_price.meta_value = 0 )';
400 + } else {
401 + $where .= $wpdb->prepare( ' AND ( pm_price.meta_value = %s )', $filter_price );
402 + }
403 + }
404 +
405 + return $where;
406 + }
407 +
408 + public function posts_orderby( $orderby ) {
409 + if ( ! $this->is_page_list_posts_on_backend() ) {
410 + return $orderby;
411 + }
412 +
413 + $order = $this->get_order_sort();
414 + switch ( $this->get_order_by() ) {
415 + case 'price':
416 + $orderby = "CAST(pm_price.meta_value AS UNSIGNED) {$order}";
417 + }
418 +
419 + return $orderby;
420 + }*/
421 +
422 + /**
423 + * Save course post
424 + *
425 + * @param int $post_id
426 + * @param WP_Post|null $post
427 + * @param bool $is_update
428 + *
429 + * @since 4.2.6.9
430 + * @version 1.0.3
431 + */
432 + public function save_post( int $post_id, ?WP_Post $post = null, bool $is_update = false ) {
433 + try {
434 + $wp_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
435 + // Save to table learnpress_courses
436 + LP_Install::instance()->create_table_courses();
437 + if ( empty( $post ) ) {
438 + $post = get_post( $post_id );
439 + }
440 +
441 + /*if ( $post->post_status === 'auto-draft' ) {
442 + return;
443 + }*/
444 +
445 + $courseModel = CourseModel::find( $post_id, true );
446 + if ( ! $courseModel ) {
447 + $courseModel = new CourseModel( $post );
448 + }
449 +
450 + // Get all metadata of post
451 + $all_metadata = $courseModel->get_post_model()->get_all_metadata();
452 + $courseModel->meta_data = $all_metadata;
453 + // Temporary unset _elementor_page_assets of El, reason by method get_all_metadata get not use maybe_unserialize, make save invalid serialize
454 + $courseModel->meta_data->_elementor_page_assets = [];
455 +
456 + // Merge object post and courseModel
457 + $new_obj = (array) $post;
458 + $old_obj = (array) $courseModel;
459 + $old_now = array_merge( $old_obj, $new_obj );
460 + $courseModel = new CourseModel( $old_now );
461 +
462 + // Save option single course
463 + include_once LP_PLUGIN_PATH . 'inc/admin/class-lp-admin.php';
464 + $lp_meta_box_course = new LP_Meta_Box_Course();
465 + $ground_fields = $lp_meta_box_course->metabox( $courseModel->ID );
466 + // Save meta fields
467 + foreach ( $ground_fields as $fields ) {
468 + if ( ! isset( $fields['content'] ) ) {
469 + continue;
470 + }
471 + foreach ( $fields['content'] as $meta_key => $option ) {
472 + $option->id = $meta_key;
473 + if ( ! $option instanceof LP_Meta_Box_Field ) {
474 + continue;
475 + }
476 +
477 + if ( isset( $_POST[ $meta_key ] ) && ! empty( $wp_screen ) && LP_COURSE_CPT === $wp_screen->id ) {
478 + $value_saved = $option->save( $courseModel->ID );
479 + if ( ! empty( $value_saved ) ) {
480 + $courseModel->meta_data->{$meta_key} = $value_saved;
481 + } else {
482 + $courseModel->meta_data->{$meta_key} = maybe_unserialize(
483 + get_post_meta( $courseModel->ID, $meta_key, true )
484 + );
485 + }
486 + } elseif ( ! $is_update ) {
487 + if ( isset( $courseModel->meta_data->{$meta_key} ) ) {
488 + continue;
489 + }
490 + $courseModel->meta_data->{$meta_key} = $option->default ?? '';
491 + } elseif ( ! empty( $wp_screen ) && LP_COURSE_CPT === $wp_screen->id ) {
492 + $value_saved = $option->save( $courseModel->ID );
493 + $courseModel->meta_data->{$meta_key} = $value_saved;
494 + }
495 + }
496 + }
497 +
498 + // For case bulk edit multiple courses change author
499 + $bulk_edit = LP_Request::get_param( 'bulk_edit', false );
500 + if ( ! empty( $bulk_edit ) ) {
501 + $post_author = LP_Request::get_param( 'post_author', 0, 'int' );
502 + if ( $post_author != -1 ) { // -1 is for no change author
503 + $courseModel->post_author = $post_author;
504 + }
505 + } elseif ( ! empty( $_REQUEST['_post_author'] ) ) {
506 + // Not use key 'post_author' on single edit course, it special key of WP, Gutenberg save will not submit this key.
507 + $courseModel->post_author = LP_Request::get_param( '_post_author', 0, 'int' );
508 + /**
509 + * Save author to post table
510 + * Not use PostModel::save() or CoursePostModel::save() here
511 + * because it calls wp_update_post(), has hook 'save_post', it will cause infinite loop
512 + */
513 + $lp_db = LP_Database::getInstance();
514 + $filter_update = new LP_Post_Type_Filter();
515 + $filter_update->collection = $lp_db->tb_posts;
516 + $filter_update->set[] = "post_author = {$courseModel->post_author}";
517 + $filter_update->where[] = $lp_db->wpdb->prepare( 'AND ID = %d', $courseModel->ID );
518 + $lp_db->update_execute( $filter_update );
519 + clean_post_cache( $post->ID );
520 + } elseif ( isset( $_POST['post_author'] ) && isset( $_POST['screen'] ) && $_POST['screen'] === 'edit-' . LP_COURSE_CPT ) {
521 + // For case quick edit change author.
522 + $courseModel->post_author = LP_Request::get_param( 'post_author', 0, 'int' );
523 + }
524 +
525 + $this->save_price( $courseModel );
526 + $courseModel->save();
527 + // End save to table learnpress_courses
528 +
529 + // Save extra info course
530 + // Save in background.
531 + $bg = LP_Background_Single_Course::instance();
532 + $bg->data(
533 + array(
534 + 'handle_name' => 'save_post',
535 + 'course_id' => $post_id,
536 + 'data' => $_POST ?? [],
537 + )
538 + )->dispatch();
539 + } catch ( Throwable $e ) {
540 + LP_Debug::error_log( $e );
541 + }
542 + }
543 +
544 + /**
545 + * Save price course
546 + *
547 + * @return void
548 + * @throws Exception
549 + */
550 + protected function save_price( CourseModel &$courseObj ) {
551 + $coursePost = new CoursePostModel( $courseObj );
552 +
553 + $regular_price = $courseObj->get_regular_price();
554 + $sale_price = $courseObj->get_sale_price();
555 + if ( (float) $regular_price < 0 ) {
556 + $courseObj->meta_data->{CoursePostModel::META_KEY_REGULAR_PRICE} = '';
557 + $regular_price = $courseObj->get_regular_price();
558 + }
559 +
560 + if ( $sale_price !== '' && (float) $sale_price > (float) $regular_price ) {
561 + $courseObj->meta_data->{CoursePostModel::META_KEY_SALE_PRICE} = '';
562 + $sale_price = $courseObj->get_sale_price();
563 + }
564 +
565 + // Save sale regular price and sale price to table postmeta
566 + $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_REGULAR_PRICE, $regular_price );
567 + $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_SALE_PRICE, $sale_price );
568 +
569 + $has_sale = $courseObj->has_sale_price();
570 + if ( $has_sale ) {
571 + $courseObj->is_sale = 1;
572 + $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_IS_SALE, 1 );
573 + } else {
574 + $courseObj->is_sale = 0;
575 + delete_post_meta( $courseObj->get_id(), CoursePostModel::META_KEY_IS_SALE );
576 + }
577 +
578 + // Set price to sort on lists.
579 + $courseObj->price_to_sort = $courseObj->get_price();
580 + $coursePost->save_meta_value_by_key( CoursePostModel::META_KEY_PRICE, $courseObj->price_to_sort );
581 + }
582 +
583 + /**
584 + * Instance LP_Course_Post_Type.
585 + *
586 + * @return LP_Course_Post_Type|null
587 + */
588 + public static function instance() {
589 + if ( ! self::$_instance ) {
590 + self::$_instance = new self();
591 + }
592 +
593 + return self::$_instance;
594 + }
595 + }
596 +
597 + $course_post_type = LP_Course_Post_Type::instance();
598 +}