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/class-lp-helper.php +316 -228 4.1.74.4.8 View file →
@@ -2,8 +2,11 @@
2 2
3 3 /**
4 4 * Class LP_Helper
5 5 */
6 +
7 +use LearnPress\Helpers\Template;
8 +
6 9 defined( 'ABSPATH' ) || exit;
7 10
8 11 class LP_Helper {
9 12
@@ -61,22 +64,8 @@
61 64 return true;
62 65 }
63 66
64 67 /**
65 - * Sanitize array by removing empty and/or duplicating values.
66 - *
67 - * @param array $array
68 - *
69 - * @return array
70 - */
71 - public static function sanitize_array( $array ) {
72 - $array = array_filter( $array );
73 - $array = array_unique( $array );
74 -
75 - return $array;
76 - }
77 -
78 - /**
79 68 * MD5 an array.
80 69 *
81 70 * @param array $array
82 71 *
@@ -92,10 +81,11 @@
92 81 /**
93 82 * Load posts from database into cache by ids
94 83 *
95 84 * @param array|int $ids
96 - * @Todo: tungnx - need to review code
97 - * @depecated 4.1.6.9
85 + *
86 + * @Todo: tungnx - need to review code - addon h5p v4.0.3 still use
87 + * @deprecated 4.1.6.9
98 88 */
99 89 public static function cache_posts( $ids ) {
100 90 //_deprecated_function( __FUNCTION__, '4.1.6.9' );
101 91 }
@@ -100,38 +90,8 @@
100 90 //_deprecated_function( __FUNCTION__, '4.1.6.9' );
101 91 }
102 92
103 93 /**
104 - * Sort an array by a field.
105 - * Having some issue with default PHP usort function.
106 - *
107 - * @param array $array .
108 - * @param string $field .
109 - * @param int $default .
110 - */
111 - public static function sort_by_priority( &$array, $field = 'priority', $default = 10 ) {
112 - foreach ( $array as $k => $item ) {
113 - if ( ! array_key_exists( $field, $item ) ) {
114 - $array[ $k ][ $field ] = $default;
115 - }
116 - }
117 -
118 - $priority = array_unique( wp_list_pluck( $array, $field ) );
119 - sort( $priority );
120 - $priority = array_fill_keys( $priority, array() );
121 -
122 - foreach ( $array as $k => $item ) {
123 - $priority[ $item[ $field ] ][] = $item;
124 - }
125 -
126 - $sorted = array();
127 - foreach ( $priority as $item ) {
128 - $sorted = array_merge( $sorted, $item );
129 - }
130 - $array = $sorted;
131 - }
132 -
133 - /**
134 94 * Merge two or more classes into one.
135 95 *
136 96 * @return array
137 97 */
@@ -261,139 +221,79 @@
261 221
262 222 /**
263 223 * Create LP static page.
264 224 *
265 - * @param string $name
266 - * @param string $assign_to - Optional. Assign to LP page after creating successful.
225 + * @param array $args
226 + * @param string $key_option
267 227 *
268 228 * @return bool|int|WP_Error
269 229 */
270 - public static function create_page( $name, $assign_to = '' ) {
271 - $args = array(
272 - 'post_type' => 'page',
273 - 'post_title' => $name,
274 - 'post_status' => 'publish',
275 - );
230 + public static function create_page( array $args, string $key_option ) {
231 + $page_id = 0;
276 232
277 - $page_id = wp_insert_post( $args );
233 + try {
234 + /**
235 + * Whitelist of allowed page option keys for security.
236 + * Prevents arbitrary options from being set via the key_option parameter.
237 + */
238 + $key_pages_allow = apply_filters(
239 + 'learn-press/pages/create-allow',
240 + array(
241 + 'learn_press_checkout_page_id',
242 + 'learn_press_profile_page_id',
243 + 'learn_press_courses_page_id',
244 + 'learn_press_instructors_page_id',
245 + 'learn_press_single_instructor_page_id',
246 + 'learn_press_become_a_teacher_page_id',
247 + 'learn_press_term_conditions_page_id',
248 + 'learn_press_exams_page_id', // Must update Addon Exam v4.0.0
249 + 'learn_press_collections_page_id', // Must update Addon Collections v4.0.5
250 + 'learn_press_packages_page_id', // Must update Addon UpSell v4.0.9
251 + )
252 + );
278 253
279 - if ( ! $page_id ) {
280 - return false;
281 - }
254 + if ( ! in_array( $key_option, $key_pages_allow ) ) {
255 + throw new Exception( __( 'Invalid key page', 'learnpress' ) );
256 + }
282 257
283 - update_post_meta( $page_id, '_lp_page', 'yes' );
284 -
285 - if ( $assign_to ) {
286 - $pages = learn_press_static_pages();
287 -
288 - if ( ! empty( $pages[ $assign_to ] ) ) {
289 - update_option( "learn_press_{$assign_to}_page_id", $page_id );
290 -
291 - // Update cache
292 - $page_ids = learn_press_static_page_ids();
293 - $page_ids[ $assign_to ] = $page_id;
294 - LP_Object_Cache::set( 'static-page-ids', $page_ids, 'learnpress' );
258 + if ( ! isset( $args['post_title'] ) ) {
259 + throw new Exception( __( 'Missing post title', 'learnpress' ) );
295 260 }
296 - }
297 261
298 - return $page_id;
299 - }
300 -
301 - /**
302 - * Wrap function ksort of PHP itself and support recursive.
303 - *
304 - * @param array $array
305 - * @param int $sort_flags
306 - *
307 - * @return bool
308 - * @since 3.3.0
309 - */
310 - public static function ksort( &$array, $sort_flags = SORT_REGULAR ) {
311 - if ( ! is_array( $array ) ) {
312 - return false;
313 - }
314 -
315 - ksort( $array, $sort_flags );
316 -
317 - foreach ( $array as &$arr ) {
318 - self::ksort( $arr, $sort_flags );
319 - }
320 -
321 - return true;
322 - }
323 -
324 - /**
325 - * Return new array/object with the keys exists in list of props.
326 - *
327 - * @param array|string $props
328 - * @param array|object $obj
329 - *
330 - * @return array|object
331 - */
332 - public function pick( $props, $obj ) {
333 - $is_array = is_array( $obj );
334 - $new_array = array();
335 - settype( $props, 'array' );
336 -
337 - foreach ( $props as $prop ) {
338 - if ( $is_array && array_key_exists( $prop, $obj ) ) {
339 - $new_array[ $prop ] = $obj[ $prop ];
340 - } elseif ( ! $is_array && property_exists( $obj, $prop ) ) {
341 - $new_array[ $prop ] = $obj->{$prop};
262 + if ( preg_match( '#^learn_press_single_instructor_page_id.*#', $key_option ) ) {
263 + $args['post_content'] = '<!-- wp:shortcode -->[learn_press_single_instructor]<!-- /wp:shortcode -->';
264 + } elseif ( preg_match( '#^learn_press_instructors_page_id.*#', $key_option ) ) {
265 + $args['post_content'] = '<!-- wp:shortcode -->[learn_press_instructors]<!-- /wp:shortcode -->';
266 + } elseif ( preg_match( '#^learn_press_profile_page_id.*#', $key_option ) ) {
267 + $args['post_content'] = '<!-- wp:shortcode -->[learn_press_profile]<!-- /wp:shortcode -->';
342 268 }
343 - }
344 269
345 - return $is_array ? $new_array : (object) $new_array;
346 - }
270 + $args = array_merge(
271 + [
272 + 'post_title' => '',
273 + 'post_name' => '',
274 + 'post_status' => 'publish',
275 + 'post_type' => 'page',
276 + 'comment_status' => 'closed',
277 + 'post_content' => '',
278 + 'post_author' => get_current_user_id(),
279 + ],
280 + $args
281 + );
347 282
348 - public static function list_pluck( $list, $field, $index_key = null ) {
349 - $newlist = array();
350 -
351 - if ( ! $index_key ) {
352 - /*
353 - * This is simple. Could at some point wrap array_column()
354 - * if we knew we had an array of arrays.
355 - */
356 - foreach ( $list as $key => $value ) {
357 - if ( is_callable( array( $value, $field ) ) ) {
358 - $newlist[ $key ] = call_user_func( array( $value, $field ) );
359 - } elseif ( is_object( $value ) ) {
360 - $newlist[ $key ] = $value->$field;
361 - } else {
362 - $newlist[ $key ] = $value[ $field ];
363 - }
283 + $page_id = wp_insert_post( $args );
284 + if ( ! $page_id ) {
285 + return false;
364 286 }
365 287
366 - return $newlist;
288 + update_option( $key_option, $page_id );
289 + $lp_settings_cache = new LP_Settings_Cache( true );
290 + $lp_settings_cache->clean_lp_settings();
291 + } catch ( Throwable $e ) {
292 + error_log( __METHOD__ . ': ' . $e->getMessage() );
367 293 }
368 294
369 - /*
370 - * When index_key is not set for a particular item, push the value
371 - * to the end of the stack. This is how array_column() behaves.
372 - */
373 - foreach ( $list as $value ) {
374 - if ( is_callable( array( $value, $field ) ) ) {
375 - if ( isset( $value->$index_key ) ) {
376 - $newlist[ $value->$index_key ] = call_user_func( array( $value, $field ) );
377 - } else {
378 - $newlist[] = call_user_func( array( $value, $field ) );
379 - }
380 - } elseif ( is_object( $value ) ) {
381 - if ( isset( $value->$index_key ) ) {
382 - $newlist[ $value->$index_key ] = $value->$field;
383 - } else {
384 - $newlist[] = $value->$field;
385 - }
386 - } else {
387 - if ( isset( $value[ $index_key ] ) ) {
388 - $newlist[ $value[ $index_key ] ] = $value[ $field ];
389 - } else {
390 - $newlist[] = $value[ $field ];
391 - }
392 - }
393 - }
394 -
395 - return $newlist;
295 + return $page_id;
396 296 }
397 297
398 298 /**
399 299 * Get the current url
@@ -402,23 +302,27 @@
402 302 * @since 3.2.6.8
403 303 * @author tungnx
404 304 */
405 305 public static function getUrlCurrent(): string {
406 - $schema = is_ssl() ? 'https://' : 'http://';
407 - $http_host = $_SERVER['HTTP_HOST'] ?? '';
306 + $schema = is_ssl() ? 'https://' : 'http://';
307 + $http_host = LP_Helper::sanitize_params_submitted( urldecode( $_SERVER['HTTP_HOST'] ?? '' ) );
308 + $request_uri = LP_Helper::sanitize_params_submitted( urldecode( $_SERVER['REQUEST_URI'] ?? '' ) );
309 + //$http_host = LP_Helper::sanitize_params_submitted( filter_input( INPUT_SERVER, 'HTTP_HOST' ) ?? '' );
310 + //$request_uri = LP_Helper::sanitize_params_submitted( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ?? '' );
408 311
409 - return $schema . $http_host . untrailingslashit( esc_url_raw( $_SERVER['REQUEST_URI'] ?? '' ) );
312 + return untrailingslashit( $schema . $http_host . $request_uri );
410 313 }
411 314
412 315 /**
413 316 * Check request is rest api
414 317 *
318 + * @return bool
319 + * @author tungnx
415 320 * @since 4.1.6.6
416 - * @author tungnx
417 - * @return bool|int
418 321 */
419 - public static function isRestApiLP() {
420 - return strpos( self::getUrlCurrent(), '/wp-json/lp/' );
322 + public static function isRestApiLP(): bool {
323 + $restPrefix = '/' . rest_get_url_prefix();
324 + return strpos( self::getUrlCurrent(), $restPrefix . '/lp/' ) || strpos( self::getUrlCurrent(), $restPrefix . '/learnpress/' );
421 325 }
422 326
423 327 /**
424 328 * Sanitize string and array
@@ -423,21 +327,22 @@
423 327 /**
424 328 * Sanitize string and array
425 329 *
426 330 * @param array|string $value
427 - * @param string $type_content
428 - *
331 + * @param string $type_content
332 + * @param bool $unslash Set it is false when you don’t want to remove slashes (unslash) from $value
333 + * for example, in cases involving LaTeX math syntax.
429 334 * @return array|string
430 335 * @since 3.2.7.1
431 336 * @author tungnx
432 337 */
433 - public static function sanitize_params_submitted( $value, $type_content = 'text' ) {
434 - $value = wp_unslash( $value );
338 + public static function sanitize_params_submitted( $value, string $type_content = 'text', $unslash = true ) {
339 + $value = $unslash ? wp_unslash( $value ) : $value;
435 340
436 341 if ( is_string( $value ) ) {
437 342 switch ( $type_content ) {
438 343 case 'html':
439 - $value = wp_kses_post( $value );
344 + $value = Template::sanitize_html_content( $value );
440 345 break;
441 346 case 'textarea':
442 347 $value = sanitize_textarea_field( $value );
443 348 break;
@@ -443,14 +348,25 @@
443 348 break;
444 349 case 'key':
445 350 $value = sanitize_key( $value );
446 351 break;
352 + case 'int':
353 + $value = (int) $value;
354 + break;
355 + case 'float':
356 + $value = (float) $value;
357 + break;
447 358 default:
448 - $value = sanitize_text_field( $value );
359 + if ( is_callable( $type_content ) ) {
360 + $value = call_user_func( $type_content, $value );
361 + } else {
362 + $value = sanitize_text_field( $value );
363 + }
449 364 }
450 365 } elseif ( is_array( $value ) ) {
451 366 foreach ( $value as $k => $v ) {
452 - $value[ $k ] = self::sanitize_params_submitted( $v, $type_content );
367 + unset( $value[ $k ] );
368 + $value[ sanitize_text_field( $k ) ] = self::sanitize_params_submitted( $v, $type_content, $unslash );
453 369 }
454 370 }
455 371
456 372 return $value;
@@ -455,55 +371,8 @@
455 371
456 372 return $value;
457 373 }
458 374
459 - /**
460 - * Get wp file system
461 - *
462 - * @return mixed
463 - */
464 - public static function get_wp_filesystem() {
465 - global $wp_filesystem;
466 -
467 - if ( empty( $wp_filesystem ) ) {
468 - require_once( ABSPATH . '/wp-admin/includes/file.php' );
469 - WP_Filesystem();
470 - }
471 -
472 - return $wp_filesystem;
473 - }
474 -
475 - /**
476 - * Wrap function $wpdb->prepare(...) to support arguments as
477 - * array.
478 - *
479 - * @param string $query
480 - * @param array|mixed $args
481 - *
482 - * @return string
483 - * @example
484 - *
485 - * $this->prepare($sql, $one, $two, array($three, $four, $file))
486 - * => $wpdb->prepare($sql, $one, $two, $three, $four, $file)
487 - */
488 - public static function prepare( $query, $args ) {
489 - global $wpdb;
490 -
491 - $args = func_get_args();
492 - array_shift( $args );
493 - $new_args = array();
494 -
495 - foreach ( $args as $arg ) {
496 - if ( is_array( $arg ) ) {
497 - $new_args = array_merge( $new_args, $arg );
498 - } else {
499 - $new_args[] = $arg;
500 - }
501 - }
502 -
503 - return $wpdb->prepare( $query, $new_args );
504 - }
505 -
506 375 public static function db_format_array( array $arr, string $format = '%d' ): string {
507 376 $arr_formatted = array_fill( 0, sizeof( $arr ), $format );
508 377
509 378 return join( ',', $arr_formatted );
@@ -524,9 +393,9 @@
524 393 }
525 394
526 395 $percent = ( $offset + $limit ) * 100 / $total_row;
527 396
528 - $percent = $percent > 100 ? 100 : $percent;
397 + $percent = min( $percent, 100 );
529 398
530 399 return floatval( number_format( $percent, 2 ) );
531 400 }
532 401
@@ -555,12 +424,231 @@
555 424 /**
556 425 * Check string is json
557 426 *
558 427 * @param string $str
428 + * @param null $associative
559 429 *
560 - * @return bool
430 + * @return mixed
431 + * @throws Exception
432 + * @since 4.1.6.4
433 + * @version 1.0.1
561 434 */
562 - public static function is_json( string $str ): bool {
563 - json_decode( $str );
564 - return json_last_error() === JSON_ERROR_NONE;
435 + public static function json_decode( string $str, $associative = null ) {
436 + $obj = json_decode( $str, $associative );
437 + if ( json_last_error() !== JSON_ERROR_NONE ) {
438 + throw new Exception( 'JSON decode: ' . json_last_error_msg() );
439 + }
440 +
441 + return $obj;
442 + }
443 +
444 + /**
445 + * Handle permalink structure for LP
446 + *
447 + * @return string
448 + * @since 4.2.2
449 + * @version 1.0.4
450 + */
451 + public static function handle_lp_permalink_structure( $post_link, $post ) {
452 + if ( false === strpos( $post_link, '%' ) ) {
453 + return $post_link;
454 + }
455 +
456 + $find = [];
457 + $replace = [];
458 + if ( ! empty( $post->post_date_gmt ) ) {
459 + $find = array(
460 + '%year%',
461 + '%monthnum%',
462 + '%day%',
463 + '%hour%',
464 + '%minute%',
465 + '%second%',
466 + '%post_id%',
467 + );
468 +
469 + $time = strtotime( $post->post_date_gmt );
470 +
471 + $replace = array(
472 + date_i18n( 'Y', $time ),
473 + date_i18n( 'm', $time ),
474 + date_i18n( 'd', $time ),
475 + date_i18n( 'H', $time ),
476 + date_i18n( 'i', $time ),
477 + date_i18n( 's', $time ),
478 + $post->ID,
479 + );
480 + }
481 +
482 + if ( strpos( $post_link, '%course_category%' ) && get_post_type( $post ) === LP_COURSE_CPT ) {
483 + // Get the custom taxonomy terms in use by this post
484 + $terms = get_the_terms( $post->ID, 'course_category' );
485 +
486 + if ( ! empty( $terms ) ) {
487 + $terms = wp_list_sort( $terms, 'term_id' );
488 + // order by IDF
489 + $category_object = apply_filters(
490 + 'learn_press_course_post_type_link_course_category',
491 + $terms[0],
492 + $terms,
493 + $post
494 + );
495 + $category_object = get_term( $category_object, 'course_category' );
496 + if ( ! $category_object instanceof WP_Term ) {
497 + return $post_link;
498 + }
499 +
500 + $course_category = $category_object->slug;
501 + $parent = $category_object->parent;
502 + if ( $parent ) {
503 + $ancestors = get_ancestors( $category_object->term_id, 'course_category' );
504 + foreach ( $ancestors as $ancestor ) {
505 + $ancestor_object = get_term( $ancestor, 'course_category' );
506 + if ( $ancestor_object instanceof WP_Term ) {
507 + $course_category = $ancestor_object->slug . '/' . $course_category;
508 + }
509 + }
510 + }
511 + } else {
512 + // If no terms are assigned to this post, use a string instead (can't leave the placeholder there)
513 + $course_category = _x( 'uncategorized', 'slug', 'learnpress' );
514 + }
515 +
516 + $find[] = '%course_category%';
517 + $replace[] = urldecode( $course_category );
518 + }
519 +
520 + return apply_filters(
521 + 'learn-press/single-course/permalink',
522 + str_replace( $find, $replace, $post_link ),
523 + $post
524 + );
525 + }
526 +
527 + /**
528 + * Print variable script inline script tag.
529 + * If $name_variable_script is empty,
530 + * the script will be print as json with set $tag_args['type'] = application/json.
531 + *
532 + * @param string $name_variable_script
533 + * @param array $data
534 + * @param array $tag_args as ['type' => 'text/javascript', 'id' => '']
535 + *
536 + * @return void
537 + * @version 1.0.2
538 + * @since 4.2.5.5
539 + */
540 + public static function print_inline_script_tag( string $name_variable_script, array $data, array $tag_args = [] ) {
541 + /**
542 + * Comment block code reason by security
543 + * wp_json_encode() already produces a valid JavaScript string,
544 + * and there is no reason to decode HTML entities inside data that will be re-inserted with insertAdjacentHTML
545 + * @comment since 4.4.8
546 + */
547 + /*foreach ( $data as $key => $value ) {
548 + if ( ! is_scalar( $value ) ) {
549 + continue;
550 + }
551 +
552 + $data[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
553 + }*/
554 +
555 + $data_json = wp_json_encode( $data );
556 + $script = '';
557 + if ( ! empty( $name_variable_script ) ) {
558 + $script = "var {$name_variable_script} = {$data_json};";
559 + } elseif ( isset( $tag_args['type'] ) && $tag_args['type'] === 'application/json' ) {
560 + $script = $data_json;
561 + }
562 + wp_print_inline_script_tag( $script, $tag_args );
563 + }
564 +
565 + /**
566 + * Get translation string single/plural
567 + *
568 + * @param float $number
569 + * @param string $string_value
570 + * @param bool $include_number
571 + *
572 + * @return string
573 + * @since 4.2.7.4
574 + * @version 1.0.0
575 + */
576 + public static function get_i18n_string_plural( float $number, string $string_value = '', bool $include_number = true ): string {
577 + switch ( $string_value ) {
578 + case LP_COURSE_CPT:
579 + $plural = sprintf(
580 + _n( 'Course', 'Courses', $number, 'learnpress' ),
581 + $number
582 + );
583 + break;
584 + case LP_LESSON_CPT:
585 + $plural = sprintf(
586 + _n( 'Lesson', 'Lessons', $number, 'learnpress' ),
587 + $number
588 + );
589 + break;
590 + case LP_QUIZ_CPT:
591 + $plural = sprintf(
592 + _n( 'Quiz', 'Quizzes', $number, 'learnpress' ),
593 + $number
594 + );
595 + break;
596 + case LP_QUESTION_CPT:
597 + $plural = sprintf(
598 + _n( 'Question', 'Questions', $number, 'learnpress' ),
599 + $number
600 + );
601 + break;
602 + case 'lp_assignment':
603 + $plural = sprintf(
604 + _n( 'Assignment', 'Assignments', $number, 'learnpress' ),
605 + $number
606 + );
607 + break;
608 + case 'lp_h5p':
609 + $plural = sprintf(
610 + _n( 'H5P', 'H5Ps', $number, 'learnpress' ),
611 + $number
612 + );
613 + break;
614 + default:
615 + $plural = $string_value;
616 + break;
617 + }
618 +
619 + if ( $include_number ) {
620 + $plural = sprintf( '%s %s', $number, $plural );
621 + }
622 +
623 + return apply_filters( 'learn-press/i18n/plural', $plural, $number, $string_value, $include_number );
624 + }
625 +
626 + /**
627 + * Get client ip address
628 + *
629 + * @return mixed|string
630 + * @since 4.2.7.9
631 + * @version 1.0.0
632 + */
633 + public static function get_client_ip(): string {
634 + $ipaddress = 'ip-unknown';
635 + if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
636 + $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
637 + } elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
638 + $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
639 + } elseif ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) {
640 + $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
641 + } elseif ( isset( $_SERVER['HTTP_FORWARDED_FOR'] ) ) {
642 + $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
643 + } elseif ( isset( $_SERVER['HTTP_FORWARDED'] ) ) {
644 + $ipaddress = $_SERVER['HTTP_FORWARDED'];
645 + } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
646 + $ipaddress = $_SERVER['REMOTE_ADDR'];
647 + }
648 +
649 + $ipaddress = (string) $ipaddress;
650 + $ipaddress = 'gip-' . $ipaddress;
651 +
652 + return $ipaddress;
565 653 }
566 654 }